mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
- Added integration tests for adaptive RRF weights in hybrid search. - Enhanced query intent detection with new classifications: keyword, semantic, and mixed. - Introduced symbol boosting in search results based on explicit symbol matches. - Implemented embedding-based reranking with configurable options. - Added global symbol index for efficient symbol lookups across projects. - Improved file deletion handling on Windows to avoid permission errors. - Updated chunk configuration to increase overlap for better context. - Modified package.json test script to target specific test files. - Created comprehensive writing style guidelines for documentation. - Added TypeScript tests for query intent detection and adaptive weights. - Established performance benchmarks for global symbol indexing.
123 lines
5.0 KiB
JavaScript
123 lines
5.0 KiB
JavaScript
/**
|
|
* Tests for query intent detection + adaptive RRF weights (TypeScript/Python parity).
|
|
*
|
|
* References:
|
|
* - `ccw/src/tools/smart-search.ts` (detectQueryIntent, adjustWeightsByIntent, getRRFWeights)
|
|
* - `codex-lens/src/codexlens/search/hybrid_search.py` (weight intent concept + defaults)
|
|
*/
|
|
|
|
import { describe, it, before } from 'node:test';
|
|
import assert from 'node:assert';
|
|
|
|
const smartSearchPath = new URL('../dist/tools/smart-search.js', import.meta.url).href;
|
|
|
|
describe('Smart Search - Query Intent + RRF Weights', async () => {
|
|
/** @type {any} */
|
|
let smartSearchModule;
|
|
|
|
before(async () => {
|
|
try {
|
|
smartSearchModule = await import(smartSearchPath);
|
|
} catch (err) {
|
|
// Keep tests non-blocking for environments that haven't built `ccw/dist` yet.
|
|
console.log('Note: smart-search module import skipped:', err.message);
|
|
}
|
|
});
|
|
|
|
describe('detectQueryIntent', () => {
|
|
it('classifies "def authenticate" as keyword', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('def authenticate'), 'keyword');
|
|
});
|
|
|
|
it('classifies CamelCase identifiers as keyword', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('MyClass'), 'keyword');
|
|
});
|
|
|
|
it('classifies snake_case identifiers as keyword', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('user_id'), 'keyword');
|
|
});
|
|
|
|
it('classifies namespace separators "::" as keyword', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('UserService::authenticate'), 'keyword');
|
|
});
|
|
|
|
it('classifies pointer arrows "->" as keyword', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('ptr->next'), 'keyword');
|
|
});
|
|
|
|
it('classifies dotted member access as keyword', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('foo.bar'), 'keyword');
|
|
});
|
|
|
|
it('classifies natural language questions as semantic', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('how to handle user login'), 'semantic');
|
|
});
|
|
|
|
it('classifies interrogatives with question marks as semantic', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('what is authentication?'), 'semantic');
|
|
});
|
|
|
|
it('classifies queries with both code + NL signals as mixed', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('why does FooBar crash?'), 'mixed');
|
|
});
|
|
|
|
it('classifies long NL queries containing identifiers as mixed', () => {
|
|
if (!smartSearchModule) return;
|
|
assert.strictEqual(smartSearchModule.detectQueryIntent('how to use user_id in query'), 'mixed');
|
|
});
|
|
});
|
|
|
|
describe('adjustWeightsByIntent', () => {
|
|
it('maps keyword intent to exact-heavy weights', () => {
|
|
if (!smartSearchModule) return;
|
|
const weights = smartSearchModule.adjustWeightsByIntent('keyword', { exact: 0.3, fuzzy: 0.1, vector: 0.6 });
|
|
assert.deepStrictEqual(weights, { exact: 0.5, fuzzy: 0.1, vector: 0.4 });
|
|
});
|
|
});
|
|
|
|
describe('getRRFWeights parity set', () => {
|
|
it('produces stable weights for 20 representative queries', () => {
|
|
if (!smartSearchModule) return;
|
|
|
|
const base = { exact: 0.3, fuzzy: 0.1, vector: 0.6 };
|
|
const expected = [
|
|
['def authenticate', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['class UserService', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['user_id', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['MyClass', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['Foo::Bar', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['ptr->next', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['foo.bar', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['import os', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['how to handle user login', { exact: 0.2, fuzzy: 0.1, vector: 0.7 }],
|
|
['what is the best way to search?', { exact: 0.2, fuzzy: 0.1, vector: 0.7 }],
|
|
['explain the authentication flow', { exact: 0.2, fuzzy: 0.1, vector: 0.7 }],
|
|
['generate embeddings for this repo', { exact: 0.2, fuzzy: 0.1, vector: 0.7 }],
|
|
['how does FooBar work', base],
|
|
['user_id how to handle', base],
|
|
['Find UserService::authenticate method', base],
|
|
['where is foo.bar used', base],
|
|
['parse_json function', { exact: 0.5, fuzzy: 0.1, vector: 0.4 }],
|
|
['How to parse_json output?', base],
|
|
['', base],
|
|
['authentication', base],
|
|
];
|
|
|
|
for (const [query, expectedWeights] of expected) {
|
|
const actual = smartSearchModule.getRRFWeights(query, base);
|
|
assert.deepStrictEqual(actual, expectedWeights, `unexpected weights for query: ${JSON.stringify(query)}`);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|