mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Add CLAUDE.md freshness tracking and update reminders
- Add SQLite table and CRUD methods for tracking update history - Create freshness calculation service based on git file changes - Add API endpoints for freshness data, marking updates, and history - Display freshness badges in file tree (green/yellow/red indicators) - Show freshness gauge and details in metadata panel - Auto-mark files as updated after CLI sync - Add English and Chinese i18n translations Freshness algorithm: 100 - min((changedFilesCount / 20) * 100, 100) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
257
ccw/tests/smart-search-enrich.test.js
Normal file
257
ccw/tests/smart-search-enrich.test.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Tests for smart_search with enrich parameter
|
||||
*
|
||||
* Tests the following:
|
||||
* - enrich parameter is passed to codex-lens
|
||||
* - relationship data is parsed from response
|
||||
* - SemanticMatch interface with relationships field
|
||||
*/
|
||||
|
||||
import { describe, it, before, mock } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Import the smart-search module (exports schema, not smartSearchTool)
|
||||
const smartSearchPath = new URL('../dist/tools/smart-search.js', import.meta.url).href;
|
||||
|
||||
describe('Smart Search Enrich Parameter', async () => {
|
||||
let smartSearchModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
smartSearchModule = await import(smartSearchPath);
|
||||
} catch (err) {
|
||||
console.log('Note: smart-search module import skipped:', err.message);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Parameter Schema', () => {
|
||||
it('should have enrich parameter in schema', async () => {
|
||||
if (!smartSearchModule) {
|
||||
console.log('Skipping: smart-search module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { schema } = smartSearchModule;
|
||||
assert.ok(schema, 'Should export schema');
|
||||
// Schema uses inputSchema (MCP standard), not parameters
|
||||
const params = schema.inputSchema || schema.parameters;
|
||||
assert.ok(params, 'Should have inputSchema or parameters');
|
||||
|
||||
const props = params.properties;
|
||||
assert.ok(props.enrich, 'Should have enrich parameter');
|
||||
assert.strictEqual(props.enrich.type, 'boolean', 'enrich should be boolean');
|
||||
assert.strictEqual(props.enrich.default, false, 'enrich should default to false');
|
||||
});
|
||||
|
||||
it('should describe enrich parameter purpose', async () => {
|
||||
if (!smartSearchModule) {
|
||||
console.log('Skipping: smart-search module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { schema } = smartSearchModule;
|
||||
const params = schema.inputSchema || schema.parameters;
|
||||
const enrichDesc = params.properties.enrich?.description || '';
|
||||
|
||||
// Description should mention relationships or graph
|
||||
const mentionsRelationships = enrichDesc.toLowerCase().includes('relationship') ||
|
||||
enrichDesc.toLowerCase().includes('graph') ||
|
||||
enrichDesc.toLowerCase().includes('enrich');
|
||||
assert.ok(mentionsRelationships, 'enrich description should mention relationships/graph');
|
||||
});
|
||||
});
|
||||
|
||||
describe('SemanticMatch Interface', () => {
|
||||
it('should handle results with relationships field', async () => {
|
||||
if (!smartSearchModule) {
|
||||
console.log('Skipping: smart-search module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a mock result with relationships
|
||||
const mockResult = {
|
||||
file: 'test.py',
|
||||
score: 0.95,
|
||||
content: 'def main(): pass',
|
||||
symbol: 'main',
|
||||
relationships: [
|
||||
{
|
||||
type: 'calls',
|
||||
direction: 'outgoing',
|
||||
target: 'helper',
|
||||
file: 'test.py',
|
||||
line: 5
|
||||
},
|
||||
{
|
||||
type: 'called_by',
|
||||
direction: 'incoming',
|
||||
source: 'entrypoint',
|
||||
file: 'app.py',
|
||||
line: 10
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Verify structure
|
||||
assert.ok(Array.isArray(mockResult.relationships), 'relationships should be array');
|
||||
assert.strictEqual(mockResult.relationships.length, 2, 'should have 2 relationships');
|
||||
|
||||
const outgoing = mockResult.relationships[0];
|
||||
assert.strictEqual(outgoing.type, 'calls');
|
||||
assert.strictEqual(outgoing.direction, 'outgoing');
|
||||
assert.ok(outgoing.target, 'outgoing should have target');
|
||||
|
||||
const incoming = mockResult.relationships[1];
|
||||
assert.strictEqual(incoming.type, 'called_by');
|
||||
assert.strictEqual(incoming.direction, 'incoming');
|
||||
assert.ok(incoming.source, 'incoming should have source');
|
||||
});
|
||||
});
|
||||
|
||||
describe('RelationshipInfo Structure', () => {
|
||||
it('should validate relationship info structure', () => {
|
||||
// Test the expected structure of RelationshipInfo
|
||||
const validRelationship = {
|
||||
type: 'calls',
|
||||
direction: 'outgoing',
|
||||
target: 'some_function',
|
||||
file: 'module.py',
|
||||
line: 42
|
||||
};
|
||||
|
||||
assert.ok(['calls', 'imports', 'extends', 'called_by', 'imported_by', 'extended_by']
|
||||
.includes(validRelationship.type), 'type should be valid relationship type');
|
||||
assert.ok(['outgoing', 'incoming'].includes(validRelationship.direction),
|
||||
'direction should be outgoing or incoming');
|
||||
assert.ok(typeof validRelationship.file === 'string', 'file should be string');
|
||||
});
|
||||
|
||||
it('should allow optional line number', () => {
|
||||
const withLine = {
|
||||
type: 'calls',
|
||||
direction: 'outgoing',
|
||||
target: 'func',
|
||||
file: 'test.py',
|
||||
line: 10
|
||||
};
|
||||
|
||||
const withoutLine = {
|
||||
type: 'imports',
|
||||
direction: 'outgoing',
|
||||
target: 'os',
|
||||
file: 'test.py'
|
||||
// line is optional
|
||||
};
|
||||
|
||||
assert.strictEqual(withLine.line, 10);
|
||||
assert.strictEqual(withoutLine.line, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Smart Search Tool Definition', async () => {
|
||||
let smartSearchModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
smartSearchModule = await import(smartSearchPath);
|
||||
} catch (err) {
|
||||
console.log('Note: smart-search module not available');
|
||||
}
|
||||
});
|
||||
|
||||
it('should have correct tool name', () => {
|
||||
if (!smartSearchModule) {
|
||||
console.log('Skipping: smart-search module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
assert.strictEqual(smartSearchModule.schema.name, 'smart_search');
|
||||
});
|
||||
|
||||
it('should have all required parameters', () => {
|
||||
if (!smartSearchModule) {
|
||||
console.log('Skipping: smart-search module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = smartSearchModule.schema.inputSchema || smartSearchModule.schema.parameters;
|
||||
const props = params.properties;
|
||||
|
||||
// Core parameters
|
||||
assert.ok(props.action, 'Should have action parameter');
|
||||
assert.ok(props.query, 'Should have query parameter');
|
||||
assert.ok(props.path, 'Should have path parameter');
|
||||
|
||||
// Search parameters
|
||||
assert.ok(props.mode, 'Should have mode parameter');
|
||||
assert.ok(props.maxResults || props.limit, 'Should have maxResults/limit parameter');
|
||||
|
||||
// New enrich parameter
|
||||
assert.ok(props.enrich, 'Should have enrich parameter');
|
||||
});
|
||||
|
||||
it('should support search modes', () => {
|
||||
if (!smartSearchModule) {
|
||||
console.log('Skipping: smart-search module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = smartSearchModule.schema.inputSchema || smartSearchModule.schema.parameters;
|
||||
const modeEnum = params.properties.mode?.enum;
|
||||
|
||||
assert.ok(modeEnum, 'Should have mode enum');
|
||||
assert.ok(modeEnum.includes('auto'), 'Should support auto mode');
|
||||
assert.ok(modeEnum.includes('hybrid'), 'Should support hybrid mode');
|
||||
assert.ok(modeEnum.includes('exact'), 'Should support exact mode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Enrich Flag Integration', async () => {
|
||||
let codexLensModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
const codexLensPath = new URL('../dist/tools/codex-lens.js', import.meta.url).href;
|
||||
codexLensModule = await import(codexLensPath);
|
||||
} catch (err) {
|
||||
console.log('Note: codex-lens module not available');
|
||||
}
|
||||
});
|
||||
|
||||
it('codex-lens should support enrich parameter', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use schema export (primary) or codexLensTool (backward-compatible)
|
||||
const toolDef = codexLensModule.schema || codexLensModule.codexLensTool;
|
||||
assert.ok(toolDef, 'Should have schema or codexLensTool export');
|
||||
|
||||
// Schema uses inputSchema (MCP standard), codexLensTool uses parameters
|
||||
const params = toolDef.inputSchema || toolDef.parameters;
|
||||
const props = params.properties;
|
||||
assert.ok(props.enrich, 'should have enrich parameter');
|
||||
assert.strictEqual(props.enrich.type, 'boolean', 'enrich should be boolean');
|
||||
});
|
||||
|
||||
it('should pass enrich flag to command line', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if executeCodexLens function is exported
|
||||
const { executeCodexLens } = codexLensModule;
|
||||
if (executeCodexLens) {
|
||||
// The function should be available for passing enrich parameter
|
||||
assert.ok(typeof executeCodexLens === 'function', 'executeCodexLens should be a function');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user