mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-19 18:58:47 +08:00
Major improvements to smart-search, chain-search cascade, ranking pipeline, reranker factory, CLI history store, codex-lens integration, and uv-manager. Simplify command-generator skill by inlining phases. Add comprehensive tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
704 lines
24 KiB
JavaScript
704 lines
24 KiB
JavaScript
import { after, afterEach, before, describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
const smartSearchPath = new URL('../dist/tools/smart-search.js', import.meta.url).href;
|
|
const originalAutoInitMissing = process.env.CODEXLENS_AUTO_INIT_MISSING;
|
|
const originalAutoEmbedMissing = process.env.CODEXLENS_AUTO_EMBED_MISSING;
|
|
|
|
describe('Smart Search MCP usage defaults and path handling', async () => {
|
|
let smartSearchModule;
|
|
const tempDirs = [];
|
|
|
|
before(async () => {
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = 'false';
|
|
try {
|
|
smartSearchModule = await import(smartSearchPath);
|
|
} catch (err) {
|
|
console.log('Note: smart-search module import skipped:', err?.message ?? String(err));
|
|
}
|
|
});
|
|
|
|
after(() => {
|
|
if (originalAutoInitMissing === undefined) {
|
|
delete process.env.CODEXLENS_AUTO_INIT_MISSING;
|
|
} else {
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = originalAutoInitMissing;
|
|
}
|
|
|
|
if (originalAutoEmbedMissing === undefined) {
|
|
delete process.env.CODEXLENS_AUTO_EMBED_MISSING;
|
|
return;
|
|
}
|
|
process.env.CODEXLENS_AUTO_EMBED_MISSING = originalAutoEmbedMissing;
|
|
});
|
|
|
|
afterEach(() => {
|
|
while (tempDirs.length > 0) {
|
|
rmSync(tempDirs.pop(), { recursive: true, force: true });
|
|
}
|
|
if (smartSearchModule?.__testables) {
|
|
smartSearchModule.__testables.__resetRuntimeOverrides();
|
|
smartSearchModule.__testables.__resetBackgroundJobs();
|
|
}
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = 'false';
|
|
delete process.env.CODEXLENS_AUTO_EMBED_MISSING;
|
|
});
|
|
|
|
function createWorkspace() {
|
|
const dir = mkdtempSync(join(tmpdir(), 'ccw-smart-search-'));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
function createDetachedChild() {
|
|
return {
|
|
on() {
|
|
return this;
|
|
},
|
|
unref() {},
|
|
};
|
|
}
|
|
|
|
it('keeps schema defaults aligned with runtime docs', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { schema } = smartSearchModule;
|
|
const props = schema.inputSchema.properties;
|
|
|
|
assert.equal(props.maxResults.default, 5);
|
|
assert.equal(props.limit.default, 5);
|
|
assert.match(schema.description, /static FTS index/i);
|
|
assert.match(schema.description, /semantic\/vector embeddings/i);
|
|
assert.ok(props.action.enum.includes('embed'));
|
|
assert.match(props.embeddingBackend.description, /litellm\/api/i);
|
|
assert.match(props.apiMaxWorkers.description, /endpoint pool/i);
|
|
assert.match(schema.description, /apiMaxWorkers=8/i);
|
|
assert.match(props.path.description, /single file path/i);
|
|
assert.ok(props.output_mode.enum.includes('ace'));
|
|
assert.match(props.output_mode.description, /ACE-style/i);
|
|
assert.equal(props.output_mode.default, 'ace');
|
|
});
|
|
|
|
it('defaults auto embedding warmup off on Windows unless explicitly enabled', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { __testables } = smartSearchModule;
|
|
delete process.env.CODEXLENS_AUTO_EMBED_MISSING;
|
|
assert.equal(__testables.isAutoEmbedMissingEnabled(undefined), process.platform !== 'win32');
|
|
assert.equal(__testables.isAutoEmbedMissingEnabled({}), process.platform !== 'win32');
|
|
assert.equal(
|
|
__testables.isAutoEmbedMissingEnabled({ embedding_auto_embed_missing: true }),
|
|
process.platform === 'win32' ? false : true,
|
|
);
|
|
assert.equal(__testables.isAutoEmbedMissingEnabled({ embedding_auto_embed_missing: false }), false);
|
|
process.env.CODEXLENS_AUTO_EMBED_MISSING = 'true';
|
|
assert.equal(__testables.isAutoEmbedMissingEnabled({ embedding_auto_embed_missing: false }), true);
|
|
process.env.CODEXLENS_AUTO_EMBED_MISSING = 'off';
|
|
assert.equal(__testables.isAutoEmbedMissingEnabled({ embedding_auto_embed_missing: true }), false);
|
|
});
|
|
|
|
it('defaults auto index warmup off on Windows unless explicitly enabled', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { __testables } = smartSearchModule;
|
|
delete process.env.CODEXLENS_AUTO_INIT_MISSING;
|
|
assert.equal(__testables.isAutoInitMissingEnabled(), process.platform !== 'win32');
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = 'off';
|
|
assert.equal(__testables.isAutoInitMissingEnabled(), false);
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = '1';
|
|
assert.equal(__testables.isAutoInitMissingEnabled(), true);
|
|
});
|
|
|
|
it('explains when Windows disables background warmup by default', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { __testables } = smartSearchModule;
|
|
delete process.env.CODEXLENS_AUTO_INIT_MISSING;
|
|
delete process.env.CODEXLENS_AUTO_EMBED_MISSING;
|
|
|
|
const initReason = __testables.getAutoInitMissingDisabledReason();
|
|
const embedReason = __testables.getAutoEmbedMissingDisabledReason({});
|
|
|
|
if (process.platform === 'win32') {
|
|
assert.match(initReason, /disabled by default on Windows/i);
|
|
assert.match(embedReason, /disabled by default on Windows/i);
|
|
assert.match(embedReason, /auto_embed_missing=true/i);
|
|
} else {
|
|
assert.match(initReason, /disabled/i);
|
|
assert.match(embedReason, /disabled/i);
|
|
}
|
|
});
|
|
|
|
it('builds hidden subprocess options for Smart Search child processes', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const options = smartSearchModule.__testables.buildSmartSearchSpawnOptions(tmpdir(), {
|
|
detached: true,
|
|
stdio: 'ignore',
|
|
timeout: 12345,
|
|
});
|
|
|
|
assert.equal(options.cwd, tmpdir());
|
|
assert.equal(options.shell, false);
|
|
assert.equal(options.windowsHide, true);
|
|
assert.equal(options.detached, true);
|
|
assert.equal(options.timeout, 12345);
|
|
assert.equal(options.env.PYTHONIOENCODING, 'utf-8');
|
|
});
|
|
|
|
it('avoids detached background warmup children on Windows consoles', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
assert.equal(
|
|
smartSearchModule.__testables.shouldDetachBackgroundSmartSearchProcess(),
|
|
process.platform !== 'win32',
|
|
);
|
|
});
|
|
|
|
it('checks tool availability without shell-based lookup popups', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const lookupCalls = [];
|
|
const available = smartSearchModule.__testables.checkToolAvailability(
|
|
'rg',
|
|
(command, args, options) => {
|
|
lookupCalls.push({ command, args, options });
|
|
return { status: 0, stdout: '', stderr: '' };
|
|
},
|
|
);
|
|
|
|
assert.equal(available, true);
|
|
assert.equal(lookupCalls.length, 1);
|
|
assert.equal(lookupCalls[0].command, process.platform === 'win32' ? 'where' : 'which');
|
|
assert.deepEqual(lookupCalls[0].args, ['rg']);
|
|
assert.equal(lookupCalls[0].options.shell, false);
|
|
assert.equal(lookupCalls[0].options.windowsHide, true);
|
|
assert.equal(lookupCalls[0].options.stdio, 'ignore');
|
|
assert.equal(lookupCalls[0].options.env.PYTHONIOENCODING, 'utf-8');
|
|
});
|
|
|
|
it('starts background static index build once for unindexed paths', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { __testables } = smartSearchModule;
|
|
const dir = createWorkspace();
|
|
const fakePython = join(dir, 'python.exe');
|
|
writeFileSync(fakePython, '');
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = 'true';
|
|
|
|
const spawnCalls = [];
|
|
__testables.__setRuntimeOverrides({
|
|
getVenvPythonPath: () => fakePython,
|
|
now: () => 1234567890,
|
|
spawnProcess: (command, args, options) => {
|
|
spawnCalls.push({ command, args, options });
|
|
return createDetachedChild();
|
|
},
|
|
});
|
|
|
|
const scope = { workingDirectory: dir, searchPaths: ['.'] };
|
|
const indexStatus = { indexed: false, has_embeddings: false };
|
|
|
|
const first = await __testables.maybeStartBackgroundAutoInit(scope, indexStatus);
|
|
const second = await __testables.maybeStartBackgroundAutoInit(scope, indexStatus);
|
|
|
|
assert.match(first.note, /started/i);
|
|
assert.match(second.note, /already running/i);
|
|
assert.equal(spawnCalls.length, 1);
|
|
assert.equal(spawnCalls[0].command, fakePython);
|
|
assert.deepEqual(spawnCalls[0].args, ['-m', 'codexlens', 'index', 'init', dir, '--no-embeddings']);
|
|
assert.equal(spawnCalls[0].options.cwd, dir);
|
|
assert.equal(
|
|
spawnCalls[0].options.detached,
|
|
smartSearchModule.__testables.shouldDetachBackgroundSmartSearchProcess(),
|
|
);
|
|
assert.equal(spawnCalls[0].options.windowsHide, true);
|
|
});
|
|
|
|
it('starts background embedding build without detached Windows consoles', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { __testables } = smartSearchModule;
|
|
const dir = createWorkspace();
|
|
const fakePython = join(dir, 'python.exe');
|
|
writeFileSync(fakePython, '');
|
|
process.env.CODEXLENS_AUTO_EMBED_MISSING = 'true';
|
|
|
|
const spawnCalls = [];
|
|
__testables.__setRuntimeOverrides({
|
|
getVenvPythonPath: () => fakePython,
|
|
checkSemanticStatus: async () => ({ available: true, litellmAvailable: true }),
|
|
now: () => 1234567890,
|
|
spawnProcess: (command, args, options) => {
|
|
spawnCalls.push({ command, args, options });
|
|
return createDetachedChild();
|
|
},
|
|
});
|
|
|
|
const status = await __testables.maybeStartBackgroundAutoEmbed(
|
|
{ workingDirectory: dir, searchPaths: ['.'] },
|
|
{
|
|
indexed: true,
|
|
has_embeddings: false,
|
|
config: { embedding_backend: 'fastembed' },
|
|
},
|
|
);
|
|
|
|
assert.match(status.note, /started/i);
|
|
assert.equal(spawnCalls.length, 1);
|
|
assert.equal(spawnCalls[0].command, fakePython);
|
|
assert.deepEqual(spawnCalls[0].args.slice(0, 1), ['-c']);
|
|
assert.equal(spawnCalls[0].options.cwd, dir);
|
|
assert.equal(
|
|
spawnCalls[0].options.detached,
|
|
smartSearchModule.__testables.shouldDetachBackgroundSmartSearchProcess(),
|
|
);
|
|
assert.equal(spawnCalls[0].options.windowsHide, true);
|
|
assert.equal(spawnCalls[0].options.stdio, 'ignore');
|
|
});
|
|
|
|
it('surfaces warnings when background static index warmup cannot start', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const { __testables } = smartSearchModule;
|
|
const dir = createWorkspace();
|
|
process.env.CODEXLENS_AUTO_INIT_MISSING = 'true';
|
|
|
|
__testables.__setRuntimeOverrides({
|
|
getVenvPythonPath: () => join(dir, 'missing-python.exe'),
|
|
});
|
|
|
|
const status = await __testables.maybeStartBackgroundAutoInit(
|
|
{ workingDirectory: dir, searchPaths: ['.'] },
|
|
{ indexed: false, has_embeddings: false },
|
|
);
|
|
|
|
assert.match(status.warning, /Automatic static index warmup could not start/i);
|
|
assert.match(status.warning, /not ready yet/i);
|
|
});
|
|
|
|
it('honors explicit small limit values', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const dir = createWorkspace();
|
|
const file = join(dir, 'many.ts');
|
|
writeFileSync(file, ['const hit = 1;', 'const hit = 2;', 'const hit = 3;'].join('\n'));
|
|
|
|
const toolResult = await smartSearchModule.handler({
|
|
action: 'search',
|
|
query: 'hit',
|
|
path: dir,
|
|
output_mode: 'full',
|
|
limit: 1,
|
|
regex: false,
|
|
tokenize: false,
|
|
});
|
|
|
|
assert.equal(toolResult.success, true, toolResult.error);
|
|
assert.equal(toolResult.result.success, true);
|
|
assert.equal(toolResult.result.results.length, 1);
|
|
assert.equal(toolResult.result.metadata.pagination.limit, 1);
|
|
});
|
|
|
|
it('scopes search results to a single file path', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const dir = createWorkspace();
|
|
const target = join(dir, 'target.ts');
|
|
const other = join(dir, 'other.ts');
|
|
writeFileSync(target, 'const TARGET_TOKEN = 1;\n');
|
|
writeFileSync(other, 'const TARGET_TOKEN = 2;\n');
|
|
|
|
const toolResult = await smartSearchModule.handler({
|
|
action: 'search',
|
|
query: 'TARGET_TOKEN',
|
|
path: target,
|
|
output_mode: 'full',
|
|
regex: false,
|
|
tokenize: false,
|
|
});
|
|
|
|
assert.equal(toolResult.success, true, toolResult.error);
|
|
assert.equal(toolResult.result.success, true);
|
|
assert.ok(Array.isArray(toolResult.result.results));
|
|
assert.ok(toolResult.result.results.length >= 1);
|
|
|
|
const normalizedFiles = toolResult.result.results.map((item) => String(item.file).replace(/\\/g, '/'));
|
|
assert.ok(normalizedFiles.every((file) => file.endsWith('/target.ts') || file === 'target.ts'));
|
|
assert.ok(normalizedFiles.every((file) => !file.endsWith('/other.ts')));
|
|
});
|
|
|
|
it('normalizes wrapped multiline query and file path inputs', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const dir = createWorkspace();
|
|
const nestedDir = join(dir, 'hydro_generator_module', 'builders');
|
|
mkdirSync(nestedDir, { recursive: true });
|
|
const target = join(nestedDir, 'full_machine_builders.py');
|
|
writeFileSync(target, 'def _resolve_rotor_inner():\n return rotor_main_seg\n');
|
|
|
|
const wrappedPath = target.replace(/([\\/])builders([\\/])/, '$1\n builders$2');
|
|
const wrappedQuery = '_resolve_rotor_inner OR\n rotor_main_seg';
|
|
|
|
const toolResult = await smartSearchModule.handler({
|
|
action: 'search',
|
|
query: wrappedQuery,
|
|
path: wrappedPath,
|
|
output_mode: 'full',
|
|
regex: false,
|
|
caseSensitive: false,
|
|
});
|
|
|
|
assert.equal(toolResult.success, true, toolResult.error);
|
|
assert.equal(toolResult.result.success, true);
|
|
assert.ok(toolResult.result.results.length >= 1);
|
|
});
|
|
|
|
it('falls back to literal ripgrep matching for invalid regex-like code queries', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const dir = createWorkspace();
|
|
const target = join(dir, 'component.ts');
|
|
writeFileSync(target, 'defineExpose({ handleResize });\n');
|
|
|
|
const toolResult = await smartSearchModule.handler({
|
|
action: 'search',
|
|
query: 'defineExpose({ handleResize',
|
|
path: dir,
|
|
output_mode: 'full',
|
|
limit: 5,
|
|
});
|
|
|
|
assert.equal(toolResult.success, true, toolResult.error);
|
|
assert.equal(toolResult.result.success, true);
|
|
assert.ok(toolResult.result.results.length >= 1);
|
|
assert.match(toolResult.result.metadata.warning, /literal ripgrep matching/i);
|
|
});
|
|
|
|
it('renders grouped ace-style output by default with multi-line chunks', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const dir = createWorkspace();
|
|
const target = join(dir, 'ace-target.ts');
|
|
writeFileSync(target, [
|
|
'const before = 1;',
|
|
'const TARGET_TOKEN = 1;',
|
|
'const after = 2;',
|
|
'',
|
|
'function useToken() {',
|
|
' return TARGET_TOKEN;',
|
|
'}',
|
|
].join('\n'));
|
|
|
|
const toolResult = await smartSearchModule.handler({
|
|
action: 'search',
|
|
query: 'TARGET_TOKEN',
|
|
path: dir,
|
|
contextLines: 1,
|
|
regex: false,
|
|
tokenize: false,
|
|
});
|
|
|
|
assert.equal(toolResult.success, true, toolResult.error);
|
|
assert.equal(toolResult.result.success, true);
|
|
assert.equal(toolResult.result.results.format, 'ace');
|
|
assert.equal(Array.isArray(toolResult.result.results.groups), true);
|
|
assert.equal(Array.isArray(toolResult.result.results.sections), true);
|
|
assert.equal(toolResult.result.results.groups.length, 1);
|
|
assert.equal(toolResult.result.results.groups[0].sections.length, 2);
|
|
assert.match(toolResult.result.results.text, /The following code sections were retrieved:/);
|
|
assert.match(toolResult.result.results.text, /Path: .*ace-target\.ts/);
|
|
assert.match(toolResult.result.results.text, /Chunk 1: lines 1-3/);
|
|
assert.match(toolResult.result.results.text, />\s+2 \| const TARGET_TOKEN = 1;/);
|
|
assert.match(toolResult.result.results.text, /Chunk 2: lines 5-7/);
|
|
assert.equal(toolResult.result.metadata.pagination.total >= 1, true);
|
|
});
|
|
|
|
it('defaults embed selection to local-fast for bulk indexing', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const selection = smartSearchModule.__testables.resolveEmbeddingSelection(undefined, undefined, {
|
|
embedding_backend: 'litellm',
|
|
embedding_model: 'qwen3-embedding-sf',
|
|
});
|
|
|
|
assert.equal(selection.backend, 'fastembed');
|
|
assert.equal(selection.model, 'fast');
|
|
assert.equal(selection.preset, 'bulk-local-fast');
|
|
assert.match(selection.note, /local-fast/i);
|
|
});
|
|
|
|
it('keeps explicit api embedding selection when requested', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const selection = smartSearchModule.__testables.resolveEmbeddingSelection('api', 'qwen3-embedding-sf', {
|
|
embedding_backend: 'fastembed',
|
|
embedding_model: 'fast',
|
|
});
|
|
|
|
assert.equal(selection.backend, 'litellm');
|
|
assert.equal(selection.model, 'qwen3-embedding-sf');
|
|
assert.equal(selection.preset, 'explicit');
|
|
});
|
|
|
|
it('parses warning-prefixed JSON and plain-text file lists for semantic fallback', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const dir = createWorkspace();
|
|
const target = join(dir, 'target.ts');
|
|
writeFileSync(target, 'export const target = 1;\n');
|
|
|
|
const parsed = smartSearchModule.__testables.parseCodexLensJsonOutput([
|
|
'RuntimeWarning: compatibility shim',
|
|
JSON.stringify({ results: [{ file: 'target.ts', score: 0.25, excerpt: 'target' }] }),
|
|
].join('\n'));
|
|
assert.equal(Array.isArray(parsed.results), true);
|
|
assert.equal(parsed.results[0].file, 'target.ts');
|
|
|
|
const matches = smartSearchModule.__testables.parsePlainTextFileMatches(target, {
|
|
workingDirectory: dir,
|
|
searchPaths: ['.'],
|
|
});
|
|
assert.equal(matches.length, 1);
|
|
assert.match(String(matches[0].file).replace(/\\/g, '/'), /target\.ts$/);
|
|
});
|
|
|
|
it('uses root-scoped embedding status instead of subtree artifacts', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const summary = smartSearchModule.__testables.extractEmbeddingsStatusSummary({
|
|
total_indexes: 3,
|
|
indexes_with_embeddings: 2,
|
|
total_chunks: 24,
|
|
coverage_percent: 66.7,
|
|
root: {
|
|
total_files: 4,
|
|
files_with_embeddings: 0,
|
|
total_chunks: 0,
|
|
coverage_percent: 0,
|
|
has_embeddings: false,
|
|
},
|
|
subtree: {
|
|
total_indexes: 3,
|
|
indexes_with_embeddings: 2,
|
|
total_files: 12,
|
|
files_with_embeddings: 8,
|
|
total_chunks: 24,
|
|
coverage_percent: 66.7,
|
|
},
|
|
centralized: {
|
|
dense_index_exists: true,
|
|
binary_index_exists: true,
|
|
meta_db_exists: true,
|
|
usable: false,
|
|
},
|
|
});
|
|
|
|
assert.equal(summary.coveragePercent, 0);
|
|
assert.equal(summary.totalChunks, 0);
|
|
assert.equal(summary.hasEmbeddings, false);
|
|
});
|
|
|
|
it('accepts validated root centralized readiness from CLI status payloads', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const summary = smartSearchModule.__testables.extractEmbeddingsStatusSummary({
|
|
total_indexes: 2,
|
|
indexes_with_embeddings: 1,
|
|
total_chunks: 10,
|
|
coverage_percent: 25,
|
|
root: {
|
|
total_files: 2,
|
|
files_with_embeddings: 1,
|
|
total_chunks: 3,
|
|
coverage_percent: 50,
|
|
has_embeddings: true,
|
|
},
|
|
centralized: {
|
|
usable: true,
|
|
dense_ready: true,
|
|
chunk_metadata_rows: 3,
|
|
},
|
|
});
|
|
|
|
assert.equal(summary.coveragePercent, 50);
|
|
assert.equal(summary.totalChunks, 3);
|
|
assert.equal(summary.hasEmbeddings, true);
|
|
});
|
|
|
|
it('prefers embeddings_status over legacy embeddings summary payloads', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const payload = smartSearchModule.__testables.selectEmbeddingsStatusPayload({
|
|
embeddings: {
|
|
total_indexes: 7,
|
|
indexes_with_embeddings: 4,
|
|
total_chunks: 99,
|
|
},
|
|
embeddings_status: {
|
|
total_indexes: 7,
|
|
total_chunks: 3,
|
|
root: {
|
|
total_files: 2,
|
|
files_with_embeddings: 1,
|
|
total_chunks: 3,
|
|
coverage_percent: 50,
|
|
has_embeddings: true,
|
|
},
|
|
centralized: {
|
|
usable: true,
|
|
dense_ready: true,
|
|
chunk_metadata_rows: 3,
|
|
},
|
|
},
|
|
});
|
|
|
|
assert.equal(payload.root.total_chunks, 3);
|
|
assert.equal(payload.centralized.usable, true);
|
|
});
|
|
|
|
it('recognizes CodexLens CLI compatibility failures and invalid regex fallback', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const compatibilityError = [
|
|
'UsageError: Got unexpected extra arguments (20 0 fts)',
|
|
'TypeError: TyperArgument.make_metavar() takes 1 positional argument but 2 were given',
|
|
].join('\n');
|
|
|
|
assert.equal(
|
|
smartSearchModule.__testables.isCodexLensCliCompatibilityError(compatibilityError),
|
|
true,
|
|
);
|
|
|
|
const resolution = smartSearchModule.__testables.resolveRipgrepQueryMode(
|
|
'defineExpose({ handleResize',
|
|
true,
|
|
true,
|
|
);
|
|
|
|
assert.equal(resolution.regex, false);
|
|
assert.equal(resolution.literalFallback, true);
|
|
assert.match(resolution.warning, /literal ripgrep matching/i);
|
|
});
|
|
|
|
it('suppresses compatibility-only fuzzy warnings when ripgrep already produced hits', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
assert.equal(
|
|
smartSearchModule.__testables.shouldSurfaceCodexLensFtsCompatibilityWarning({
|
|
compatibilityTriggeredThisQuery: true,
|
|
skipExactDueToCompatibility: false,
|
|
ripgrepResultCount: 2,
|
|
}),
|
|
false,
|
|
);
|
|
|
|
assert.equal(
|
|
smartSearchModule.__testables.shouldSurfaceCodexLensFtsCompatibilityWarning({
|
|
compatibilityTriggeredThisQuery: true,
|
|
skipExactDueToCompatibility: false,
|
|
ripgrepResultCount: 0,
|
|
}),
|
|
true,
|
|
);
|
|
|
|
assert.equal(
|
|
smartSearchModule.__testables.shouldSurfaceCodexLensFtsCompatibilityWarning({
|
|
compatibilityTriggeredThisQuery: false,
|
|
skipExactDueToCompatibility: true,
|
|
ripgrepResultCount: 0,
|
|
}),
|
|
true,
|
|
);
|
|
});
|
|
|
|
it('builds actionable index suggestions for unhealthy index states', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const suggestions = smartSearchModule.__testables.buildIndexSuggestions(
|
|
{
|
|
indexed: true,
|
|
has_embeddings: false,
|
|
embeddings_coverage_percent: 0,
|
|
warning: 'Index exists but no embeddings generated. Run smart_search(action="embed") to build the vector index.',
|
|
},
|
|
{
|
|
workingDirectory: 'D:/tmp/demo',
|
|
searchPaths: ['.'],
|
|
},
|
|
);
|
|
|
|
assert.equal(Array.isArray(suggestions), true);
|
|
assert.match(suggestions[0].command, /smart_search\(action="embed"/);
|
|
});
|
|
|
|
it('surfaces backend failure details when fuzzy search fully fails', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const missingPath = join(createWorkspace(), 'missing-folder', 'missing.ts');
|
|
const toolResult = await smartSearchModule.handler({
|
|
action: 'search',
|
|
query: 'TARGET_TOKEN',
|
|
path: missingPath,
|
|
output_mode: 'full',
|
|
regex: false,
|
|
tokenize: false,
|
|
});
|
|
|
|
assert.equal(toolResult.success, false);
|
|
assert.match(toolResult.error, /Both search backends failed:/);
|
|
assert.match(toolResult.error, /(FTS|Ripgrep)/);
|
|
});
|
|
|
|
it('returns structured semantic results after local init and embed without JSON parse warnings', async () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const codexLensModule = await import(new URL(`../dist/tools/codex-lens.js?smart-semantic=${Date.now()}`, import.meta.url).href);
|
|
const ready = await codexLensModule.checkVenvStatus(true);
|
|
if (!ready.ready) {
|
|
console.log('Skipping: CodexLens not ready');
|
|
return;
|
|
}
|
|
|
|
const semantic = await codexLensModule.checkSemanticStatus();
|
|
if (!semantic.available) {
|
|
console.log('Skipping: semantic dependencies not ready');
|
|
return;
|
|
}
|
|
|
|
const dir = createWorkspace();
|
|
writeFileSync(
|
|
join(dir, 'sample.ts'),
|
|
'export function parseCodexLensOutput() { return stripAnsiOutput(); }\nexport const sum = (a, b) => a + b;\n',
|
|
);
|
|
|
|
const init = await smartSearchModule.handler({ action: 'init', path: dir });
|
|
assert.equal(init.success, true, init.error ?? 'Expected init to succeed');
|
|
|
|
const embed = await smartSearchModule.handler({
|
|
action: 'embed',
|
|
path: dir,
|
|
embeddingBackend: 'local',
|
|
force: true,
|
|
});
|
|
assert.equal(embed.success, true, embed.error ?? 'Expected local embed to succeed');
|
|
|
|
const search = await smartSearchModule.handler({
|
|
action: 'search',
|
|
mode: 'semantic',
|
|
path: dir,
|
|
query: 'parse CodexLens output strip ANSI',
|
|
limit: 5,
|
|
});
|
|
|
|
assert.equal(search.success, true, search.error ?? 'Expected semantic search to succeed');
|
|
assert.equal(search.result.success, true);
|
|
assert.equal(search.result.results.format, 'ace');
|
|
assert.ok(search.result.results.total >= 1, 'Expected at least one structured semantic match');
|
|
assert.doesNotMatch(search.result.metadata?.warning ?? '', /Failed to parse JSON output/i);
|
|
});
|
|
});
|