@spikedpunch/mast 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MAST_SPEC.md +2926 -0
- package/README.md +842 -0
- package/assets/skill.md +45 -0
- package/dist/ast/extract.d.ts +65 -0
- package/dist/ast/extract.d.ts.map +1 -0
- package/dist/ast/extract.js +147 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/extractor.d.ts +85 -0
- package/dist/ast/extractor.d.ts.map +1 -0
- package/dist/ast/extractor.js +2 -0
- package/dist/ast/extractor.js.map +1 -0
- package/dist/ast/extractors/markdown.d.ts +40 -0
- package/dist/ast/extractors/markdown.d.ts.map +1 -0
- package/dist/ast/extractors/markdown.js +175 -0
- package/dist/ast/extractors/markdown.js.map +1 -0
- package/dist/ast/extractors/typescript.d.ts +179 -0
- package/dist/ast/extractors/typescript.d.ts.map +1 -0
- package/dist/ast/extractors/typescript.js +1439 -0
- package/dist/ast/extractors/typescript.js.map +1 -0
- package/dist/ast/parser.d.ts +5 -0
- package/dist/ast/parser.d.ts.map +1 -0
- package/dist/ast/parser.js +21 -0
- package/dist/ast/parser.js.map +1 -0
- package/dist/ast/types.d.ts +586 -0
- package/dist/ast/types.d.ts.map +1 -0
- package/dist/ast/types.js +3 -0
- package/dist/ast/types.js.map +1 -0
- package/dist/cli/docs-cmd.d.ts +22 -0
- package/dist/cli/docs-cmd.d.ts.map +1 -0
- package/dist/cli/docs-cmd.js +100 -0
- package/dist/cli/docs-cmd.js.map +1 -0
- package/dist/cli/index-cmd.d.ts +72 -0
- package/dist/cli/index-cmd.d.ts.map +1 -0
- package/dist/cli/index-cmd.js +263 -0
- package/dist/cli/index-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +64 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/install-hooks.d.ts +3 -0
- package/dist/cli/install-hooks.d.ts.map +1 -0
- package/dist/cli/install-hooks.js +34 -0
- package/dist/cli/install-hooks.js.map +1 -0
- package/dist/cli/metrics-cmd.d.ts +3 -0
- package/dist/cli/metrics-cmd.d.ts.map +1 -0
- package/dist/cli/metrics-cmd.js +164 -0
- package/dist/cli/metrics-cmd.js.map +1 -0
- package/dist/cli/program.d.ts +14 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +44 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/query.d.ts +46 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +153 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/search-cmd.d.ts +33 -0
- package/dist/cli/search-cmd.d.ts.map +1 -0
- package/dist/cli/search-cmd.js +114 -0
- package/dist/cli/search-cmd.js.map +1 -0
- package/dist/cli/serve.d.ts +3 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +19 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/skill-install.d.ts +39 -0
- package/dist/cli/skill-install.d.ts.map +1 -0
- package/dist/cli/skill-install.js +57 -0
- package/dist/cli/skill-install.js.map +1 -0
- package/dist/cli/status.d.ts +35 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +117 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/upgrade-cmd.d.ts +48 -0
- package/dist/cli/upgrade-cmd.d.ts.map +1 -0
- package/dist/cli/upgrade-cmd.js +129 -0
- package/dist/cli/upgrade-cmd.js.map +1 -0
- package/dist/cli/version.d.ts +3 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +19 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/env.d.ts +17 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +17 -0
- package/dist/env.js.map +1 -0
- package/dist/graph/checker-resolver.d.ts +156 -0
- package/dist/graph/checker-resolver.d.ts.map +1 -0
- package/dist/graph/checker-resolver.js +458 -0
- package/dist/graph/checker-resolver.js.map +1 -0
- package/dist/graph/db.d.ts +318 -0
- package/dist/graph/db.d.ts.map +1 -0
- package/dist/graph/db.js +266 -0
- package/dist/graph/db.js.map +1 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/local-type-env.d.ts +45 -0
- package/dist/graph/local-type-env.d.ts.map +1 -0
- package/dist/graph/local-type-env.js +59 -0
- package/dist/graph/local-type-env.js.map +1 -0
- package/dist/graph/path-range.d.ts +40 -0
- package/dist/graph/path-range.d.ts.map +1 -0
- package/dist/graph/path-range.js +42 -0
- package/dist/graph/path-range.js.map +1 -0
- package/dist/graph/populate.d.ts +263 -0
- package/dist/graph/populate.d.ts.map +1 -0
- package/dist/graph/populate.js +984 -0
- package/dist/graph/populate.js.map +1 -0
- package/dist/graph/queries.d.ts +114 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +415 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/sqliteBatch.d.ts +48 -0
- package/dist/graph/sqliteBatch.d.ts.map +1 -0
- package/dist/graph/sqliteBatch.js +93 -0
- package/dist/graph/sqliteBatch.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +72 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/import-resolver.d.ts +50 -0
- package/dist/indexer/import-resolver.d.ts.map +1 -0
- package/dist/indexer/import-resolver.js +318 -0
- package/dist/indexer/import-resolver.js.map +1 -0
- package/dist/indexer/index.d.ts +249 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +560 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/walker.d.ts +60 -0
- package/dist/indexer/walker.d.ts.map +1 -0
- package/dist/indexer/walker.js +128 -0
- package/dist/indexer/walker.js.map +1 -0
- package/dist/indexer/watcher.d.ts +80 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +151 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/context.d.ts +23 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +2 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/register-tools.d.ts +21 -0
- package/dist/mcp/register-tools.d.ts.map +1 -0
- package/dist/mcp/register-tools.js +42 -0
- package/dist/mcp/register-tools.js.map +1 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/staleness.d.ts +72 -0
- package/dist/mcp/staleness.d.ts.map +1 -0
- package/dist/mcp/staleness.js +213 -0
- package/dist/mcp/staleness.js.map +1 -0
- package/dist/mcp/startup.d.ts +40 -0
- package/dist/mcp/startup.d.ts.map +1 -0
- package/dist/mcp/startup.js +114 -0
- package/dist/mcp/startup.js.map +1 -0
- package/dist/mcp/tools/_helpers.d.ts +26 -0
- package/dist/mcp/tools/_helpers.d.ts.map +1 -0
- package/dist/mcp/tools/_helpers.js +54 -0
- package/dist/mcp/tools/_helpers.js.map +1 -0
- package/dist/mcp/tools/callers.d.ts +4 -0
- package/dist/mcp/tools/callers.d.ts.map +1 -0
- package/dist/mcp/tools/callers.js +112 -0
- package/dist/mcp/tools/callers.js.map +1 -0
- package/dist/mcp/tools/dependencies.d.ts +4 -0
- package/dist/mcp/tools/dependencies.d.ts.map +1 -0
- package/dist/mcp/tools/dependencies.js +36 -0
- package/dist/mcp/tools/dependencies.js.map +1 -0
- package/dist/mcp/tools/efficiency.d.ts +4 -0
- package/dist/mcp/tools/efficiency.d.ts.map +1 -0
- package/dist/mcp/tools/efficiency.js +39 -0
- package/dist/mcp/tools/efficiency.js.map +1 -0
- package/dist/mcp/tools/exports.d.ts +4 -0
- package/dist/mcp/tools/exports.d.ts.map +1 -0
- package/dist/mcp/tools/exports.js +56 -0
- package/dist/mcp/tools/exports.js.map +1 -0
- package/dist/mcp/tools/implementors.d.ts +4 -0
- package/dist/mcp/tools/implementors.d.ts.map +1 -0
- package/dist/mcp/tools/implementors.js +47 -0
- package/dist/mcp/tools/implementors.js.map +1 -0
- package/dist/mcp/tools/project-skeleton.d.ts +4 -0
- package/dist/mcp/tools/project-skeleton.d.ts.map +1 -0
- package/dist/mcp/tools/project-skeleton.js +61 -0
- package/dist/mcp/tools/project-skeleton.js.map +1 -0
- package/dist/mcp/tools/reindex.d.ts +14 -0
- package/dist/mcp/tools/reindex.d.ts.map +1 -0
- package/dist/mcp/tools/reindex.js +32 -0
- package/dist/mcp/tools/reindex.js.map +1 -0
- package/dist/mcp/tools/rename-impact.d.ts +4 -0
- package/dist/mcp/tools/rename-impact.d.ts.map +1 -0
- package/dist/mcp/tools/rename-impact.js +124 -0
- package/dist/mcp/tools/rename-impact.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +4 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +69 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/signature.d.ts +4 -0
- package/dist/mcp/tools/signature.d.ts.map +1 -0
- package/dist/mcp/tools/signature.js +127 -0
- package/dist/mcp/tools/signature.js.map +1 -0
- package/dist/mcp/tools/status.d.ts +4 -0
- package/dist/mcp/tools/status.d.ts.map +1 -0
- package/dist/mcp/tools/status.js +30 -0
- package/dist/mcp/tools/status.js.map +1 -0
- package/dist/search/declex.d.ts +109 -0
- package/dist/search/declex.d.ts.map +1 -0
- package/dist/search/declex.js +242 -0
- package/dist/search/declex.js.map +1 -0
- package/dist/search/fts.d.ts +77 -0
- package/dist/search/fts.d.ts.map +1 -0
- package/dist/search/fts.js +223 -0
- package/dist/search/fts.js.map +1 -0
- package/dist/search/fused.d.ts +110 -0
- package/dist/search/fused.d.ts.map +1 -0
- package/dist/search/fused.js +323 -0
- package/dist/search/fused.js.map +1 -0
- package/dist/search/potential-matches.d.ts +88 -0
- package/dist/search/potential-matches.d.ts.map +1 -0
- package/dist/search/potential-matches.js +83 -0
- package/dist/search/potential-matches.js.map +1 -0
- package/dist/search/scope.d.ts +66 -0
- package/dist/search/scope.d.ts.map +1 -0
- package/dist/search/scope.js +71 -0
- package/dist/search/scope.js.map +1 -0
- package/dist/store/config.d.ts +57 -0
- package/dist/store/config.d.ts.map +1 -0
- package/dist/store/config.js +208 -0
- package/dist/store/config.js.map +1 -0
- package/dist/store/lock.d.ts +101 -0
- package/dist/store/lock.d.ts.map +1 -0
- package/dist/store/lock.js +182 -0
- package/dist/store/lock.js.map +1 -0
- package/dist/store/lockMetrics.d.ts +52 -0
- package/dist/store/lockMetrics.d.ts.map +1 -0
- package/dist/store/lockMetrics.js +29 -0
- package/dist/store/lockMetrics.js.map +1 -0
- package/dist/store/sqliteChunkStore.d.ts +44 -0
- package/dist/store/sqliteChunkStore.d.ts.map +1 -0
- package/dist/store/sqliteChunkStore.js +135 -0
- package/dist/store/sqliteChunkStore.js.map +1 -0
- package/dist/telemetry/lockMetricsSummary.d.ts +42 -0
- package/dist/telemetry/lockMetricsSummary.d.ts.map +1 -0
- package/dist/telemetry/lockMetricsSummary.js +139 -0
- package/dist/telemetry/lockMetricsSummary.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +140 -0
- package/dist/telemetry/metrics.d.ts.map +1 -0
- package/dist/telemetry/metrics.js +312 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/telemetry/tokenizer.d.ts +125 -0
- package/dist/telemetry/tokenizer.d.ts.map +1 -0
- package/dist/telemetry/tokenizer.js +174 -0
- package/dist/telemetry/tokenizer.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranker D — the declaration-exact ranker: a match against `chunks.symbol_name`
|
|
3
|
+
* (full-name or final-dot-segment), case-insensitive, deterministically
|
|
4
|
+
* ordered.
|
|
5
|
+
*
|
|
6
|
+
* Ported from `eval/declex-ranker.mjs`, the Q1/DECLEX-measured construction
|
|
7
|
+
* (IMPLEMENTATION_PLAN.md § "Q1/DECLEX — the declaration-exact ranker" +
|
|
8
|
+
* AMENDMENT 1, commit dd10796; Stage 6 of the productization plan). PRIMARY
|
|
9
|
+
* ARM ONLY — this file intentionally does NOT port the escape variant
|
|
10
|
+
* (`escape`/`escapeCap` options, `escapeEligibleTerms`/
|
|
11
|
+
* `lowercaseTokenMatchCounts` diagnostics) or `classifyTargetReach`
|
|
12
|
+
* (instrument-analysis machinery, not product). The escape variant was
|
|
13
|
+
* measured harmful off-stratum (M2 decision memo) and is barred from
|
|
14
|
+
* shipping without a fresh pre-registration.
|
|
15
|
+
*
|
|
16
|
+
* Ranker D queries the `chunks` table DIRECTLY (not `chunk_fts`/
|
|
17
|
+
* `identifier_fts`): `chunk_fts.symbol_name` is stored UNINDEXED
|
|
18
|
+
* (`src/graph/db.ts`) — not reachable via FTS5 MATCH — and the registered
|
|
19
|
+
* match rule ("equals the chunk's own symbol_name OR its final dot-segment")
|
|
20
|
+
* is a structural string comparison, not a full-text match, so a direct SQL
|
|
21
|
+
* predicate against `chunks.symbol_name` is the correct primitive.
|
|
22
|
+
*/
|
|
23
|
+
import { sql } from 'kysely';
|
|
24
|
+
import { compileScopeMatcher } from './scope.js';
|
|
25
|
+
/**
|
|
26
|
+
* Ranker D's candidate pool multiplier — 4x the caller's own `limit`, the
|
|
27
|
+
* SAME convention as ranker I (`eval/idfuse-ranker.mjs`, AMENDMENT 1 F8) and
|
|
28
|
+
* the pre-Stage-7 vector search helper: the caller multiplies by 4 and passes
|
|
29
|
+
* the already-multiplied limit directly; `searchRankerD` applies no further
|
|
30
|
+
* internal multiplication.
|
|
31
|
+
*/
|
|
32
|
+
export const RANKER_D_POOL_MULTIPLIER = 4;
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Term derivation + eligibility gate (pure, no DB)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/** Raw token split — identical character class to ranker I's own derivation
|
|
37
|
+
* (`eval/idfuse-ranker.mjs`); no camelCase split, no lowercasing. `Class.method`
|
|
38
|
+
* splits into two independent terms (the dot is not in the character class). */
|
|
39
|
+
export function deriveRankerDTerms(query) {
|
|
40
|
+
return query.match(/[A-Za-z0-9_$]+/g) ?? [];
|
|
41
|
+
}
|
|
42
|
+
const DIGIT_ADJACENT_LETTER = /[0-9][A-Za-z]|[A-Za-z][0-9]/;
|
|
43
|
+
/**
|
|
44
|
+
* Symbol-shaped eligibility predicate for ranker D's PRIMARY arm — reproduces
|
|
45
|
+
* `isSymbolShapedTerm` (`eval/idfuse-ranker.mjs`) MINUS the dead
|
|
46
|
+
* `term.includes('.')` clause (registration: "the implementation also tests
|
|
47
|
+
* term.includes('.'), but the upstream split character class
|
|
48
|
+
* `/[A-Za-z0-9_$]+/` never includes `.` in any surviving token ... Ranker D's
|
|
49
|
+
* eligibility gate reproduces this predicate but drops the dead clause rather
|
|
50
|
+
* than reimplementing dead code"). A term qualifies if it contains an
|
|
51
|
+
* uppercase letter, an underscore, a dollar sign, or a digit adjacent to a
|
|
52
|
+
* letter.
|
|
53
|
+
*/
|
|
54
|
+
export function isEligiblePrimaryTerm(term) {
|
|
55
|
+
return (/[A-Z]/.test(term) ||
|
|
56
|
+
term.includes('_') ||
|
|
57
|
+
term.includes('$') ||
|
|
58
|
+
DIGIT_ADJACENT_LETTER.test(term));
|
|
59
|
+
}
|
|
60
|
+
/** Ranker D's PRIMARY-arm eligible terms: raw split, filtered to symbol-shaped. */
|
|
61
|
+
export function deriveEligiblePrimaryTerms(query) {
|
|
62
|
+
return deriveRankerDTerms(query).filter(isEligiblePrimaryTerm);
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// SQL match primitive — direct predicate against `chunks.symbol_name`
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
/** Escape `\`, `%`, `_` for a SQLite LIKE pattern (registration's segment
|
|
68
|
+
* match is a STRUCTURAL suffix-after-dot comparison; a raw token containing
|
|
69
|
+
* `_` — a legal, even common, eligible character per the split regex — would
|
|
70
|
+
* otherwise be silently reinterpreted as a single-character LIKE wildcard,
|
|
71
|
+
* producing false-positive segment matches. `ESCAPE '\'` in the query below
|
|
72
|
+
* neutralises that.) */
|
|
73
|
+
function escapeLikeToken(token) {
|
|
74
|
+
return token.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Query `chunks` for every row whose `symbol_name` equals `token`
|
|
78
|
+
* case-insensitively (full-name match) OR whose `symbol_name` ends with
|
|
79
|
+
* `.` + `token` case-insensitively (segment match — the only chunk type with
|
|
80
|
+
* a dot in `symbol_name` is `method`, `symbol_name = `${className}.${methodName}``,
|
|
81
|
+
* `typescript.ts:324`; a chunk with no dot can only ever full-name-match, per
|
|
82
|
+
* the registration's "no segment logic needed for class_shell").
|
|
83
|
+
*
|
|
84
|
+
* Full-name equality is a plain parameterized `=` (no wildcard risk). Segment
|
|
85
|
+
* matching uses `LIKE` with the token pre-escaped (see `escapeLikeToken`) so
|
|
86
|
+
* an underscore inside the token cannot masquerade as a wildcard.
|
|
87
|
+
*/
|
|
88
|
+
async function matchToken(db, token) {
|
|
89
|
+
const lower = token.toLowerCase();
|
|
90
|
+
const likePattern = `%.${escapeLikeToken(lower)}`;
|
|
91
|
+
const rows = await db
|
|
92
|
+
.selectFrom('chunks')
|
|
93
|
+
.select(['chunk_id', 'symbol_name', 'chunk_type', 'parent_symbol', 'file_path', 'language'])
|
|
94
|
+
.where('symbol_name', 'is not', null)
|
|
95
|
+
// NOTE: the doubled backslash below is JS template-literal escaping for a
|
|
96
|
+
// SINGLE literal backslash in the resulting SQL text (`ESCAPE '\'`) — a
|
|
97
|
+
// lone `\'` here would be parsed by JS as an escaped quote, silently
|
|
98
|
+
// dropping the backslash and producing invalid SQL (`ESCAPE ''`).
|
|
99
|
+
.where(sql `(LOWER(symbol_name) = ${lower} OR LOWER(symbol_name) LIKE ${likePattern} ESCAPE '\\')`)
|
|
100
|
+
.execute();
|
|
101
|
+
// `WHERE symbol_name IS NOT NULL` guarantees no row here has a null
|
|
102
|
+
// symbol_name, but Kysely's column type (`string | null`, per the schema)
|
|
103
|
+
// does not narrow from a runtime predicate — filter with a type guard
|
|
104
|
+
// rather than a non-null assertion (banned in product code, §3.1).
|
|
105
|
+
return rows
|
|
106
|
+
.filter((r) => r.symbol_name !== null)
|
|
107
|
+
.map((r) => {
|
|
108
|
+
const matchType = r.symbol_name.toLowerCase() === lower ? 'full' : 'segment';
|
|
109
|
+
return {
|
|
110
|
+
chunk_id: r.chunk_id,
|
|
111
|
+
symbol_name: r.symbol_name,
|
|
112
|
+
chunk_type: r.chunk_type,
|
|
113
|
+
parent_symbol: r.parent_symbol,
|
|
114
|
+
file_path: r.file_path,
|
|
115
|
+
language: r.language,
|
|
116
|
+
match_type: matchType,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Drop candidates outside the caller's scope.
|
|
122
|
+
*
|
|
123
|
+
* Applied to the merged pool BEFORE ordering and capping, not after, for two
|
|
124
|
+
* reasons. The cap would otherwise be spent on rows that are about to be
|
|
125
|
+
* discarded, so a narrow `file_pattern` over a crowded symbol name would
|
|
126
|
+
* silently return fewer in-scope declarations than the index holds — the same
|
|
127
|
+
* silently-incomplete answer the scope filter exists to prevent. And the
|
|
128
|
+
* ordering itself is scope-dependent: rank position 2 is "how many candidates
|
|
129
|
+
* share this matched name", which is a property of the pool being ranked, so a
|
|
130
|
+
* scoped search must count within its own scope to order consistently with what
|
|
131
|
+
* it returns.
|
|
132
|
+
*/
|
|
133
|
+
function applyScope(candidates, matcher) {
|
|
134
|
+
if (matcher === null)
|
|
135
|
+
return [...candidates];
|
|
136
|
+
return candidates.filter((c) => matcher(c.file_path, c.language));
|
|
137
|
+
}
|
|
138
|
+
/** The final dot-segment of a symbol name (itself, if no dot present). */
|
|
139
|
+
function finalDotSegment(symbolName) {
|
|
140
|
+
const idx = symbolName.lastIndexOf('.');
|
|
141
|
+
return idx === -1 ? symbolName : symbolName.slice(idx + 1);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Ranker D: declaration-exact match against `chunks.symbol_name` (full-name
|
|
145
|
+
* or final-dot-segment), case-insensitive, deterministically ordered.
|
|
146
|
+
*
|
|
147
|
+
* Ordering (registration): full-name matches before segment-only matches;
|
|
148
|
+
* then fewer total same-matched-name candidates first; then ascending
|
|
149
|
+
* chunk_id. JUDGMENT CALL (flagged per this codebase's convention, e.g.
|
|
150
|
+
* `eval/idfuse-score.mjs`'s a-fortiori routing note): "same-name" is read as
|
|
151
|
+
* "same MATCHED name" — full-name candidates group by their own
|
|
152
|
+
* `symbol_name`; segment candidates group by the shared final dot-segment
|
|
153
|
+
* (the quantity the design review's F-4 finding is actually about — a
|
|
154
|
+
* `toJSON`-class query facing ~140 candidates that all share the SEGMENT
|
|
155
|
+
* "toJSON", not 140 candidates sharing one `symbol_name`, since each belongs
|
|
156
|
+
* to a different class). This groups the exact multiplicity class the
|
|
157
|
+
* registration's own fixture (Gate B, high-multiplicity segment) is built to
|
|
158
|
+
* exercise.
|
|
159
|
+
*
|
|
160
|
+
* `options.scope` restricts the candidate pool to the caller's `file_pattern` /
|
|
161
|
+
* `language` before any of the above is computed, so `diagnostics.candidate_count`
|
|
162
|
+
* and the ordering both describe the scoped pool — a scoped search behaves as
|
|
163
|
+
* though the index contained only the files in scope.
|
|
164
|
+
*
|
|
165
|
+
* @param query - the RAW query string (term derivation happens here).
|
|
166
|
+
*/
|
|
167
|
+
export async function searchRankerD(db, query, options) {
|
|
168
|
+
const rawTerms = deriveRankerDTerms(query);
|
|
169
|
+
const primaryTerms = [...new Set(rawTerms.filter(isEligiblePrimaryTerm))];
|
|
170
|
+
// matchesByTerm caches every matchToken() call so the final candidate
|
|
171
|
+
// assembly never re-queries the same term twice.
|
|
172
|
+
const matchesByTerm = new Map();
|
|
173
|
+
for (const t of primaryTerms)
|
|
174
|
+
matchesByTerm.set(t, await matchToken(db, t));
|
|
175
|
+
if (primaryTerms.length === 0) {
|
|
176
|
+
return {
|
|
177
|
+
rows: [],
|
|
178
|
+
diagnostics: {
|
|
179
|
+
fired: false,
|
|
180
|
+
top_match_channel: null,
|
|
181
|
+
candidate_count: 0,
|
|
182
|
+
primary_eligible_terms: primaryTerms,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Merge per-term matches into one candidate map keyed by chunk_id — a
|
|
187
|
+
// chunk reachable via more than one eligible term is counted once. If any
|
|
188
|
+
// matching term produced a 'full' hit for that chunk, 'full' wins (a chunk
|
|
189
|
+
// can only structurally be a 'full' match for its OWN symbol_name and a
|
|
190
|
+
// 'segment' match for its OWN symbol_name's final segment — these are the
|
|
191
|
+
// same string only when the symbol_name has no dot, so this precedence
|
|
192
|
+
// never silently discards information; it only resolves the case where two
|
|
193
|
+
// DIFFERENT terms both matched the same chunk).
|
|
194
|
+
const byChunkId = new Map();
|
|
195
|
+
for (const t of primaryTerms) {
|
|
196
|
+
const matches = matchesByTerm.get(t) ?? [];
|
|
197
|
+
for (const m of matches) {
|
|
198
|
+
const existing = byChunkId.get(m.chunk_id);
|
|
199
|
+
if (existing === undefined || (existing.match_type === 'segment' && m.match_type === 'full')) {
|
|
200
|
+
byChunkId.set(m.chunk_id, m);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const candidates = applyScope([...byChunkId.values()], compileScopeMatcher(options.scope ?? {}));
|
|
205
|
+
// Ordering: matchedName grouping (see JSDoc above) — count how many
|
|
206
|
+
// candidates in THIS query's pool share the same (case-insensitive)
|
|
207
|
+
// matched name, then sort full-before-segment, ascending count, ascending
|
|
208
|
+
// chunk_id.
|
|
209
|
+
const matchedNameOf = (c) => (c.match_type === 'full' ? c.symbol_name : finalDotSegment(c.symbol_name)).toLowerCase();
|
|
210
|
+
const nameCounts = new Map();
|
|
211
|
+
for (const c of candidates) {
|
|
212
|
+
const key = matchedNameOf(c);
|
|
213
|
+
nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1);
|
|
214
|
+
}
|
|
215
|
+
candidates.sort((a, b) => {
|
|
216
|
+
const typeRank = (c) => (c.match_type === 'full' ? 0 : 1);
|
|
217
|
+
if (typeRank(a) !== typeRank(b))
|
|
218
|
+
return typeRank(a) - typeRank(b);
|
|
219
|
+
const countA = nameCounts.get(matchedNameOf(a)) ?? 0;
|
|
220
|
+
const countB = nameCounts.get(matchedNameOf(b)) ?? 0;
|
|
221
|
+
if (countA !== countB)
|
|
222
|
+
return countA - countB;
|
|
223
|
+
return a.chunk_id < b.chunk_id ? -1 : a.chunk_id > b.chunk_id ? 1 : 0;
|
|
224
|
+
});
|
|
225
|
+
const capped = candidates.slice(0, options.limit);
|
|
226
|
+
const first = candidates[0];
|
|
227
|
+
return {
|
|
228
|
+
rows: capped.map((c) => ({
|
|
229
|
+
chunk_id: c.chunk_id,
|
|
230
|
+
symbol_name: c.symbol_name,
|
|
231
|
+
chunk_type: c.chunk_type,
|
|
232
|
+
match_type: c.match_type,
|
|
233
|
+
})),
|
|
234
|
+
diagnostics: {
|
|
235
|
+
fired: candidates.length > 0,
|
|
236
|
+
top_match_channel: first !== undefined ? first.match_type : null,
|
|
237
|
+
candidate_count: candidates.length,
|
|
238
|
+
primary_eligible_terms: primaryTerms,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=declex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declex.js","sourceRoot":"","sources":["../../src/search/declex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Db } from '../graph/db.js';
|
|
2
|
+
export interface FtsSearchOptions {
|
|
3
|
+
readonly limit: number;
|
|
4
|
+
readonly filePattern?: string | null;
|
|
5
|
+
readonly language?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface FtsSearchRow {
|
|
8
|
+
readonly chunk_id: string;
|
|
9
|
+
readonly symbol_name: string | null;
|
|
10
|
+
readonly bm25_score: number;
|
|
11
|
+
readonly match_snippet: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* BM25 search over `chunk_fts` using SQLite FTS5.
|
|
15
|
+
*
|
|
16
|
+
* The trigram tokeniser surfaces camelCase identifiers and partial matches.
|
|
17
|
+
* `bm25(chunk_fts)` returns negative scores — lower (more negative) is a
|
|
18
|
+
* better match. Results are sorted ascending (best first) before RRF ranking.
|
|
19
|
+
*
|
|
20
|
+
* `filePattern` and `language` are applied as an `IN` list on `file_path`,
|
|
21
|
+
* built before the ORDER BY and LIMIT clauses so the restriction is part of
|
|
22
|
+
* the ranked query rather than a post-filter on its window. Both are resolved
|
|
23
|
+
* through `./scope.ts`, which owns the glob and language semantics for every
|
|
24
|
+
* ranker `fusedSearch` fuses — this function must not translate the pattern
|
|
25
|
+
* itself, or the two rankers can disagree about what the caller asked for.
|
|
26
|
+
* `chunk_type` and `only_exported` are post-filters applied by the caller
|
|
27
|
+
* after fetching full chunk records from the chunk store.
|
|
28
|
+
*/
|
|
29
|
+
export declare function searchFts(db: Db, query: string, options: FtsSearchOptions): Promise<FtsSearchRow[]>;
|
|
30
|
+
export interface IdentifierFtsRow {
|
|
31
|
+
readonly chunk_id: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Exact-identifier search over `identifier_fts`.
|
|
35
|
+
*
|
|
36
|
+
* Used by `mast_callers` to produce the `potential_matches` set — chunks that
|
|
37
|
+
* contain the symbol name as an identifier token but whose call site the
|
|
38
|
+
* heuristic resolver could not statically link.
|
|
39
|
+
*/
|
|
40
|
+
export declare function searchIdentifiers(db: Db, symbolName: string, limit?: number): Promise<IdentifierFtsRow[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Uncapped match count for `symbolName` over `identifier_fts` — same
|
|
43
|
+
* phrase-quoted MATCH expression as {@link searchIdentifiers} (via the shared
|
|
44
|
+
* {@link buildIdentifierMatchExpr} helper, never duplicated), no `LIMIT`.
|
|
45
|
+
*
|
|
46
|
+
* F10 (Stage 3, IMPLEMENTATION_PLAN.md): `searchIdentifiers`' cap silently
|
|
47
|
+
* dropped real matches with no signal the cap was hit
|
|
48
|
+
* (`eval/GITNEXUS_COMPARISON.md` M4 — `isUndefined` reported 50 candidates
|
|
49
|
+
* when the real count was 71). Callers should run this ONLY when the capped
|
|
50
|
+
* fetch came back full (`identRows.length === limit`) — below the cap, the
|
|
51
|
+
* fetch count already IS the real count, and this query would be pure waste.
|
|
52
|
+
*/
|
|
53
|
+
export declare function countIdentifierMatches(db: Db, symbolName: string): Promise<number>;
|
|
54
|
+
/**
|
|
55
|
+
* Near-miss identifier search over `identifier_fts`.
|
|
56
|
+
*
|
|
57
|
+
* Unlike {@link searchIdentifiers} (which requires an exact phrase match on the
|
|
58
|
+
* full symbol), this ORs each term so a chunk matching *any* sub-term is
|
|
59
|
+
* returned. Used by the zero-result assist path to gather candidate chunks
|
|
60
|
+
* whose identifiers partially overlap a query that otherwise found nothing.
|
|
61
|
+
*
|
|
62
|
+
* Terms are quoted as phrases so separator chars never leak into FTS5 query
|
|
63
|
+
* syntax. Returns an empty array when no usable term remains.
|
|
64
|
+
*/
|
|
65
|
+
export declare function searchIdentifierNearMiss(db: Db, terms: readonly string[], limit?: number): Promise<IdentifierFtsRow[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Split an identifier-shaped query into its constituent sub-terms.
|
|
68
|
+
*
|
|
69
|
+
* Handles camelCase (`getUser` → `get`, `user`), acronym boundaries
|
|
70
|
+
* (`HTTPServer` → `http`, `server`), and snake/kebab separators. Sub-terms are
|
|
71
|
+
* lowercased (FTS5 trigram matching is case-insensitive), de-duplicated, and
|
|
72
|
+
* filtered below the 3-char trigram floor — a sub-term shorter than a trigram
|
|
73
|
+
* cannot match `chunk_fts` anyway. Returns an empty array when nothing usable
|
|
74
|
+
* remains, so callers can short-circuit the assist.
|
|
75
|
+
*/
|
|
76
|
+
export declare function splitIdentifierTerms(query: string): string[];
|
|
77
|
+
//# sourceMappingURL=fts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fts.d.ts","sourceRoot":"","sources":["../../src/search/fts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAQzC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,YAAY,EAAE,CAAC,CA+DzB;AAMD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,EAAE,EACN,UAAU,EAAE,MAAM,EAClB,KAAK,SAAK,GACT,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAS7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASxF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,SAAK,GACT,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAU7B;AAMD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAc5D"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { sql } from 'kysely';
|
|
2
|
+
import { resolveScopedPaths } from './scope.js';
|
|
3
|
+
import { chunkValuesForSqlite } from '../graph/sqliteBatch.js';
|
|
4
|
+
/**
|
|
5
|
+
* BM25 search over `chunk_fts` using SQLite FTS5.
|
|
6
|
+
*
|
|
7
|
+
* The trigram tokeniser surfaces camelCase identifiers and partial matches.
|
|
8
|
+
* `bm25(chunk_fts)` returns negative scores — lower (more negative) is a
|
|
9
|
+
* better match. Results are sorted ascending (best first) before RRF ranking.
|
|
10
|
+
*
|
|
11
|
+
* `filePattern` and `language` are applied as an `IN` list on `file_path`,
|
|
12
|
+
* built before the ORDER BY and LIMIT clauses so the restriction is part of
|
|
13
|
+
* the ranked query rather than a post-filter on its window. Both are resolved
|
|
14
|
+
* through `./scope.ts`, which owns the glob and language semantics for every
|
|
15
|
+
* ranker `fusedSearch` fuses — this function must not translate the pattern
|
|
16
|
+
* itself, or the two rankers can disagree about what the caller asked for.
|
|
17
|
+
* `chunk_type` and `only_exported` are post-filters applied by the caller
|
|
18
|
+
* after fetching full chunk records from the chunk store.
|
|
19
|
+
*/
|
|
20
|
+
export async function searchFts(db, query, options) {
|
|
21
|
+
// Sanitise the user query into a safe FTS5 expression. Passing the raw string
|
|
22
|
+
// to MATCH lets FTS5 interpret `(`, `:`, `"`, `OR`, etc. as query syntax — a
|
|
23
|
+
// query like `handleLogin(req` would throw "fts5: syntax error" and (because
|
|
24
|
+
// this runs outside fusedSearch's try/catch) fail the whole search.
|
|
25
|
+
const matchExpr = toFtsMatch(query);
|
|
26
|
+
if (matchExpr === null)
|
|
27
|
+
return [];
|
|
28
|
+
// SQLite FTS5 UNINDEXED columns support IN with a literal list reliably,
|
|
29
|
+
// but LIKE and subquery IN may not be applied by the FTS5 query planner.
|
|
30
|
+
// The scope is therefore materialised against the regular `files` table
|
|
31
|
+
// first, then passed as an IN list to the FTS query.
|
|
32
|
+
const allowedPaths = await resolveScopedPaths(db, {
|
|
33
|
+
filePattern: options.filePattern,
|
|
34
|
+
language: options.language,
|
|
35
|
+
});
|
|
36
|
+
// A scope that matched no indexed file is an empty result, not an
|
|
37
|
+
// unrestricted one — `null` means "no scope given" and must not be conflated.
|
|
38
|
+
if (allowedPaths !== null && allowedPaths.length === 0)
|
|
39
|
+
return [];
|
|
40
|
+
const rowLimit = options.limit * 2;
|
|
41
|
+
// Build all WHERE conditions before ORDER BY / LIMIT.
|
|
42
|
+
const ranked = (paths) => {
|
|
43
|
+
let q = db
|
|
44
|
+
.selectFrom('chunk_fts')
|
|
45
|
+
.select([
|
|
46
|
+
'chunk_id',
|
|
47
|
+
'symbol_name',
|
|
48
|
+
sql `bm25(chunk_fts)`.as('bm25_score'),
|
|
49
|
+
sql `snippet(chunk_fts, 0, '**', '**', '...', 12)`.as('match_snippet'),
|
|
50
|
+
])
|
|
51
|
+
.where(sql `chunk_fts MATCH ${matchExpr}`);
|
|
52
|
+
if (paths !== null)
|
|
53
|
+
q = q.where('file_path', 'in', [...paths]);
|
|
54
|
+
return q.orderBy(sql `bm25(chunk_fts)`, 'asc').limit(rowLimit).execute();
|
|
55
|
+
};
|
|
56
|
+
if (allowedPaths === null)
|
|
57
|
+
return ranked(null);
|
|
58
|
+
// The scope can name every indexed file — a `language` filter on a large
|
|
59
|
+
// monorepo does exactly that — and an IN list is one bound parameter per
|
|
60
|
+
// path, so past SQLITE_MAX_VARIABLES the statement throws `too many SQL
|
|
61
|
+
// variables` and the search fails outright.
|
|
62
|
+
//
|
|
63
|
+
// Splitting is safe for a ranked, limited query: each batch is a subset of
|
|
64
|
+
// the scope, so the global best `rowLimit` rows are necessarily contained in
|
|
65
|
+
// the union of the per-batch best `rowLimit` rows. Re-sorting that union and
|
|
66
|
+
// re-applying the limit therefore reproduces the single-statement result
|
|
67
|
+
// exactly. bm25 is negative, best-first is ascending — the same convention
|
|
68
|
+
// the SQL above uses.
|
|
69
|
+
// Two parameters of the statement's budget are not path values: the MATCH
|
|
70
|
+
// expression and the LIMIT. Declaring them is not defensive padding — a
|
|
71
|
+
// batch of exactly SQLITE_MAX_VARIABLES paths threw here, and the test at
|
|
72
|
+
// `graph/__tests__/in-list-batching.test.ts` fails again if a future
|
|
73
|
+
// predicate binds a third without being counted.
|
|
74
|
+
const NON_PATH_BOUND_PARAMS = 2;
|
|
75
|
+
const batches = chunkValuesForSqlite(allowedPaths, 1, NON_PATH_BOUND_PARAMS);
|
|
76
|
+
if (batches.length === 1)
|
|
77
|
+
return ranked(allowedPaths);
|
|
78
|
+
const merged = [];
|
|
79
|
+
for (const batch of batches)
|
|
80
|
+
merged.push(...await ranked(batch));
|
|
81
|
+
merged.sort((a, b) => a.bm25_score - b.bm25_score);
|
|
82
|
+
return merged.slice(0, rowLimit);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Exact-identifier search over `identifier_fts`.
|
|
86
|
+
*
|
|
87
|
+
* Used by `mast_callers` to produce the `potential_matches` set — chunks that
|
|
88
|
+
* contain the symbol name as an identifier token but whose call site the
|
|
89
|
+
* heuristic resolver could not statically link.
|
|
90
|
+
*/
|
|
91
|
+
export async function searchIdentifiers(db, symbolName, limit = 50) {
|
|
92
|
+
const matchExpr = buildIdentifierMatchExpr(symbolName);
|
|
93
|
+
if (matchExpr === null)
|
|
94
|
+
return [];
|
|
95
|
+
return db
|
|
96
|
+
.selectFrom('identifier_fts')
|
|
97
|
+
.select('chunk_id')
|
|
98
|
+
.where(sql `identifier_fts MATCH ${matchExpr}`)
|
|
99
|
+
.limit(limit)
|
|
100
|
+
.execute();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Uncapped match count for `symbolName` over `identifier_fts` — same
|
|
104
|
+
* phrase-quoted MATCH expression as {@link searchIdentifiers} (via the shared
|
|
105
|
+
* {@link buildIdentifierMatchExpr} helper, never duplicated), no `LIMIT`.
|
|
106
|
+
*
|
|
107
|
+
* F10 (Stage 3, IMPLEMENTATION_PLAN.md): `searchIdentifiers`' cap silently
|
|
108
|
+
* dropped real matches with no signal the cap was hit
|
|
109
|
+
* (`eval/GITNEXUS_COMPARISON.md` M4 — `isUndefined` reported 50 candidates
|
|
110
|
+
* when the real count was 71). Callers should run this ONLY when the capped
|
|
111
|
+
* fetch came back full (`identRows.length === limit`) — below the cap, the
|
|
112
|
+
* fetch count already IS the real count, and this query would be pure waste.
|
|
113
|
+
*/
|
|
114
|
+
export async function countIdentifierMatches(db, symbolName) {
|
|
115
|
+
const matchExpr = buildIdentifierMatchExpr(symbolName);
|
|
116
|
+
if (matchExpr === null)
|
|
117
|
+
return 0;
|
|
118
|
+
const row = await db
|
|
119
|
+
.selectFrom('identifier_fts')
|
|
120
|
+
.select((eb) => eb.fn.count('chunk_id').as('count'))
|
|
121
|
+
.where(sql `identifier_fts MATCH ${matchExpr}`)
|
|
122
|
+
.executeTakeFirst();
|
|
123
|
+
return row?.count ?? 0;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Near-miss identifier search over `identifier_fts`.
|
|
127
|
+
*
|
|
128
|
+
* Unlike {@link searchIdentifiers} (which requires an exact phrase match on the
|
|
129
|
+
* full symbol), this ORs each term so a chunk matching *any* sub-term is
|
|
130
|
+
* returned. Used by the zero-result assist path to gather candidate chunks
|
|
131
|
+
* whose identifiers partially overlap a query that otherwise found nothing.
|
|
132
|
+
*
|
|
133
|
+
* Terms are quoted as phrases so separator chars never leak into FTS5 query
|
|
134
|
+
* syntax. Returns an empty array when no usable term remains.
|
|
135
|
+
*/
|
|
136
|
+
export async function searchIdentifierNearMiss(db, terms, limit = 20) {
|
|
137
|
+
const cleaned = terms.map((t) => t.trim()).filter((t) => t.length > 0);
|
|
138
|
+
if (cleaned.length === 0)
|
|
139
|
+
return [];
|
|
140
|
+
const matchExpr = cleaned.map((t) => `"${t.replace(/"/g, '""')}"`).join(' OR ');
|
|
141
|
+
return db
|
|
142
|
+
.selectFrom('identifier_fts')
|
|
143
|
+
.select('chunk_id')
|
|
144
|
+
.where(sql `identifier_fts MATCH ${matchExpr}`)
|
|
145
|
+
.limit(limit)
|
|
146
|
+
.execute();
|
|
147
|
+
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Helpers
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Split an identifier-shaped query into its constituent sub-terms.
|
|
153
|
+
*
|
|
154
|
+
* Handles camelCase (`getUser` → `get`, `user`), acronym boundaries
|
|
155
|
+
* (`HTTPServer` → `http`, `server`), and snake/kebab separators. Sub-terms are
|
|
156
|
+
* lowercased (FTS5 trigram matching is case-insensitive), de-duplicated, and
|
|
157
|
+
* filtered below the 3-char trigram floor — a sub-term shorter than a trigram
|
|
158
|
+
* cannot match `chunk_fts` anyway. Returns an empty array when nothing usable
|
|
159
|
+
* remains, so callers can short-circuit the assist.
|
|
160
|
+
*/
|
|
161
|
+
export function splitIdentifierTerms(query) {
|
|
162
|
+
const spaced = query
|
|
163
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // camelCase: fooBar -> foo Bar
|
|
164
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2'); // acronym: HTTPServer -> HTTP Server
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const out = [];
|
|
167
|
+
for (const part of spaced.split(/[^A-Za-z0-9]+/)) {
|
|
168
|
+
const term = part.toLowerCase();
|
|
169
|
+
if (term.length < 3)
|
|
170
|
+
continue;
|
|
171
|
+
if (seen.has(term))
|
|
172
|
+
continue;
|
|
173
|
+
seen.add(term);
|
|
174
|
+
out.push(term);
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Build the phrase-quoted FTS5 MATCH expression shared by
|
|
180
|
+
* {@link searchIdentifiers} and {@link countIdentifierMatches} — the two must
|
|
181
|
+
* agree on exactly which rows count as a match, or the "real count" F10
|
|
182
|
+
* reports could disagree with what the capped fetch actually returned.
|
|
183
|
+
* Quoting as a phrase means any separator char (e.g. `Class.method`) is
|
|
184
|
+
* matched literally rather than parsed as FTS5 query syntax. Returns null for
|
|
185
|
+
* an empty/whitespace-only name, so callers can short-circuit instead of
|
|
186
|
+
* running an invalid MATCH query.
|
|
187
|
+
*/
|
|
188
|
+
function buildIdentifierMatchExpr(symbolName) {
|
|
189
|
+
const term = symbolName.trim();
|
|
190
|
+
if (term === '')
|
|
191
|
+
return null;
|
|
192
|
+
return `"${term.replace(/"/g, '""')}"`;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Turn a free-form query into a safe FTS5 MATCH expression for the trigram
|
|
196
|
+
* `chunk_fts`: identifier-ish tokens (length ≥ 3, the trigram minimum), each
|
|
197
|
+
* quoted as a phrase so no character is treated as query syntax, joined by
|
|
198
|
+
* `OR`. Returns null when no usable token remains, so the caller can
|
|
199
|
+
* short-circuit to an empty result instead of running an invalid query.
|
|
200
|
+
*
|
|
201
|
+
* **F15 — why OR and not AND.** FTS5 ANDs bare space-separated phrases, so the
|
|
202
|
+
* previous space-join required a chunk to contain *every* token. A conceptual
|
|
203
|
+
* multi-word query therefore matched nothing at all: measured against the nest
|
|
204
|
+
* corpus, 6 of 20 TSDoc-derived queries returned zero rows despite the corpus
|
|
205
|
+
* plainly containing the target symbol (e.g. "precondition failed exception
|
|
206
|
+
* defines an http for type errors" → 0 rows ANDed, 5 ORed). That silently
|
|
207
|
+
* crippled the lexical half of hybrid search, and made the vector store look
|
|
208
|
+
* indispensable when it was partly compensating for this bug — see
|
|
209
|
+
* IMPLEMENTATION_PLAN.md § "nest replication".
|
|
210
|
+
*
|
|
211
|
+
* Recall is not traded for precision here: `bm25()` already ranks by term
|
|
212
|
+
* coverage and inverse document frequency, so a chunk matching every token
|
|
213
|
+
* still outranks one matching a single common token. OR widens the candidate
|
|
214
|
+
* pool and lets the ranker do the discriminating — which is what
|
|
215
|
+
* `identifier_fts` (searchIdentifiers, below) has always done.
|
|
216
|
+
*/
|
|
217
|
+
function toFtsMatch(query) {
|
|
218
|
+
const tokens = (query.match(/[A-Za-z0-9_]+/g) ?? []).filter((t) => t.length >= 3);
|
|
219
|
+
if (tokens.length === 0)
|
|
220
|
+
return null;
|
|
221
|
+
return tokens.map((t) => `"${t.replace(/"/g, '""')}"`).join(' OR ');
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=fts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fts.js","sourceRoot":"","sources":["../../src/search/fts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAgB,MAAM,QAAQ,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAmB/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAM,EACN,KAAa,EACb,OAAyB;IAEzB,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAElC,yEAAyE;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,qDAAqD;IACrD,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAAC,EAAE,EAAE;QAChD,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC,CAAC;IACH,kEAAkE;IAClE,8EAA8E;IAC9E,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAElE,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;IAEnC,sDAAsD;IACtD,MAAM,MAAM,GAAG,CAAC,KAA+B,EAA2B,EAAE;QAC1E,IAAI,CAAC,GAAG,EAAE;aACP,UAAU,CAAC,WAAW,CAAC;aACvB,MAAM,CAAC;YACN,UAAU;YACV,aAAa;YACb,GAAG,CAAQ,iBAAiB,CAAC,EAAE,CAAC,YAAY,CAAC;YAC7C,GAAG,CAAQ,8CAA8C,CAAC,EAAE,CAAC,eAAe,CAAC;SAC9E,CAAC;aACD,KAAK,CAAC,GAAG,CAAS,mBAAmB,SAAS,EAAE,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,IAAI;YAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAA,iBAAiB,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC;IAC1E,CAAC,CAAC;IAEF,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;IAE/C,yEAAyE;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,4CAA4C;IAC5C,EAAE;IACF,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,yEAAyE;IACzE,2EAA2E;IAC3E,sBAAsB;IACtB,0EAA0E;IAC1E,wEAAwE;IACxE,0EAA0E;IAC1E,qEAAqE;IACrE,iDAAiD;IACjD,MAAM,qBAAqB,GAAG,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,oBAAoB,CAAC,YAAY,EAAE,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC7E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,YAAY,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACjE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAUD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAM,EACN,UAAkB,EAClB,KAAK,GAAG,EAAE;IAEV,MAAM,SAAS,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,EAAE;SACN,UAAU,CAAC,gBAAgB,CAAC;SAC5B,MAAM,CAAC,UAAU,CAAC;SAClB,KAAK,CAAC,GAAG,CAAS,wBAAwB,SAAS,EAAE,CAAC;SACtD,KAAK,CAAC,KAAK,CAAC;SACZ,OAAO,EAAE,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,EAAM,EAAE,UAAkB;IACrE,MAAM,SAAS,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,EAAE;SACjB,UAAU,CAAC,gBAAgB,CAAC;SAC5B,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAS,UAAU,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;SAC3D,KAAK,CAAC,GAAG,CAAS,wBAAwB,SAAS,EAAE,CAAC;SACtD,gBAAgB,EAAE,CAAC;IACtB,OAAO,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,EAAM,EACN,KAAwB,EACxB,KAAK,GAAG,EAAE;IAEV,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChF,OAAO,EAAE;SACN,UAAU,CAAC,gBAAgB,CAAC;SAC5B,MAAM,CAAC,UAAU,CAAC;SAClB,KAAK,CAAC,GAAG,CAAS,wBAAwB,SAAS,EAAE,CAAC;SACtD,KAAK,CAAC,KAAK,CAAC;SACZ,OAAO,EAAE,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,MAAM,GAAG,KAAK;SACjB,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAK,+BAA+B;SAC1E,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC,CAAC,qCAAqC;IACnF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,wBAAwB,CAAC,UAAkB;IAClD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAS,UAAU,CAAC,KAAa;IAC/B,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACtE,CAAC"}
|