@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,110 @@
|
|
|
1
|
+
import type { SearchInput, SearchResult, SearchSuggestion, RelatedHint } from '../ast/types.js';
|
|
2
|
+
import type { Db } from '../graph/db.js';
|
|
3
|
+
import type { ChunkStore, ChunkRecord } from '../store/sqliteChunkStore.js';
|
|
4
|
+
import { type DeclexDiagnostics } from './declex.js';
|
|
5
|
+
/**
|
|
6
|
+
* RRF score for a document at rank `r` (1-indexed) with fusion constant `k`.
|
|
7
|
+
*
|
|
8
|
+
* Standard formula: Score(d) = Σ 1/(k + r(d)) over all rankers.
|
|
9
|
+
* The default k=60 dampens rank differences — a #1 vs #2 gap matters less
|
|
10
|
+
* than the absolute score gap would suggest.
|
|
11
|
+
*/
|
|
12
|
+
export declare function rrfScore(rank: number, k: number): number;
|
|
13
|
+
export interface FusedSearchConfig {
|
|
14
|
+
readonly rrf_k: number;
|
|
15
|
+
/**
|
|
16
|
+
* F18 kill-switch (M2 decision memo condition 3) — fuses ranker D
|
|
17
|
+
* (declaration-exact, `./declex.ts`) into RRF as a third input. OPTIONAL,
|
|
18
|
+
* and absent means OFF: the eval instruments (`eval/*-rank-check.mjs`,
|
|
19
|
+
* frozen at the `mast-pre-vector-delete` tag per IMPLEMENTATION_PLAN.md
|
|
20
|
+
* Stage 7) call this function directly to reconstruct measured experiment
|
|
21
|
+
* arms, so a function-level default-on would silently contaminate future
|
|
22
|
+
* instrument re-runs against a config shape they never asked for. The
|
|
23
|
+
* product's default-on lives in `MastConfig` DEFAULTS (`store/config.ts`) —
|
|
24
|
+
* the config layer a kill-switch belongs to, not the function signature
|
|
25
|
+
* every caller (including eval) shares.
|
|
26
|
+
*/
|
|
27
|
+
readonly declaration_exact_ranker?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* One chunk whose fused rank differs between the shipped (with-D) ranking
|
|
31
|
+
* and the reconstructed without-D ranking.
|
|
32
|
+
*
|
|
33
|
+
* `rank_with_d`/`rank_without_d` are the ACTUAL 1-based fused rank in each
|
|
34
|
+
* list — not `null` for a merely-displaced-out-of-window entry. `null` is
|
|
35
|
+
* reserved for genuine absence from that list (a D-only chunk has no
|
|
36
|
+
* `rank_without_d` because it never entered the without-D candidate pool at
|
|
37
|
+
* all). This is a deliberate refinement of IMPLEMENTATION_PLAN.md Stage 6
|
|
38
|
+
* design decision 4, which sketched reporting `null` for anything pushed out
|
|
39
|
+
* of the window — the shipped contract is strictly more informative;
|
|
40
|
+
* consumers who want window semantics compare the reported rank against
|
|
41
|
+
* `limit` themselves.
|
|
42
|
+
*/
|
|
43
|
+
export interface DeclexWindowEffect {
|
|
44
|
+
readonly chunk_id: string;
|
|
45
|
+
/** From the already-fetched chunk records; `null` when the id fell outside the fetched candidate pool entirely. */
|
|
46
|
+
readonly symbol_name: string | null;
|
|
47
|
+
readonly rank_with_d: number | null;
|
|
48
|
+
readonly rank_without_d: number | null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* D-fire telemetry attached to `fusedSearch`'s return, present ONLY when
|
|
52
|
+
* ranker D actually fired (`fired` is always `true` here by construction —
|
|
53
|
+
* the field itself is omitted, not set to `{fired: false}`, when D was
|
|
54
|
+
* silent or the flag was off).
|
|
55
|
+
*/
|
|
56
|
+
export interface DeclexTelemetry {
|
|
57
|
+
readonly fired: true;
|
|
58
|
+
readonly top_match_channel: DeclexDiagnostics['top_match_channel'];
|
|
59
|
+
readonly candidate_count: number;
|
|
60
|
+
readonly window_effects: readonly DeclexWindowEffect[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* BM25 lexical search fused with ranker D (declaration-exact, `./declex.ts`)
|
|
64
|
+
* via Reciprocal Rank Fusion.
|
|
65
|
+
*
|
|
66
|
+
* Stage 7 (IMPLEMENTATION_PLAN.md "Stage 7: Vector-store deletion") excised
|
|
67
|
+
* the vector leg entirely — `mast_search` is FTS + ranker D only now, and
|
|
68
|
+
* Stage 7.2 renamed this function (decision 4): its old name asserted a
|
|
69
|
+
* vector-plus-lexical fusion that no longer exists. The response no longer
|
|
70
|
+
* carries a search-mode discriminator or a per-result similarity score —
|
|
71
|
+
* both described the retired vector leg and have no honest value to report
|
|
72
|
+
* now that it's gone.
|
|
73
|
+
*
|
|
74
|
+
* Post-filters (`chunk_type`, `only_exported`) are applied after RRF ranking,
|
|
75
|
+
* on the full chunk records fetched from `chunkStore`.
|
|
76
|
+
*
|
|
77
|
+
* The scope filters (`file_pattern`, `language`) are NOT post-filters: each
|
|
78
|
+
* ranker applies them to its own candidate pool, through the shared
|
|
79
|
+
* `./scope.ts`, before that pool is capped. Applying them here instead would
|
|
80
|
+
* spend each ranker's candidate budget on rows about to be discarded. Anything
|
|
81
|
+
* later fused into this function must be scoped the same way — the union below
|
|
82
|
+
* is what carries a leak into the result set, and `__tests__/search-scope.test.ts`
|
|
83
|
+
* asserts the union, not the individual legs, for exactly that reason.
|
|
84
|
+
*/
|
|
85
|
+
export declare function fusedSearch(db: Db, input: SearchInput, config: FusedSearchConfig, chunkStore: ChunkStore): Promise<{
|
|
86
|
+
results: SearchResult[];
|
|
87
|
+
suggestions?: SearchSuggestion[];
|
|
88
|
+
/** Present only when ranker D fired — Stage 6.3 telemetry, see {@link DeclexTelemetry}. */
|
|
89
|
+
declex?: DeclexTelemetry;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Walk RRF-ranked candidates and keep up to `limit`, suppressing the
|
|
93
|
+
* lower-ranked half of every shell↔method pair from the same class:
|
|
94
|
+
*
|
|
95
|
+
* - a `class_shell` is dropped when any method of that class is already kept —
|
|
96
|
+
* the `{ parent_symbol }` hint lands on the highest-ranked such method;
|
|
97
|
+
* - a `method` is dropped when its class's shell is already kept — its
|
|
98
|
+
* qualified name is appended to the shell's `{ methods_matched }` hint;
|
|
99
|
+
* - methods never suppress each other, and classes are matched by
|
|
100
|
+
* file_path + class name so same-named classes in different files
|
|
101
|
+
* never collapse.
|
|
102
|
+
*
|
|
103
|
+
* Suppression frees slots, so later candidates backfill until `limit` distinct
|
|
104
|
+
* results are kept. Purely presentational — RRF scores are not modified.
|
|
105
|
+
*/
|
|
106
|
+
export declare function dedupShellMethodCollisions(candidates: readonly ChunkRecord[], limit: number): Array<{
|
|
107
|
+
chunk: ChunkRecord;
|
|
108
|
+
related: RelatedHint | undefined;
|
|
109
|
+
}>;
|
|
110
|
+
//# sourceMappingURL=fused.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fused.d.ts","sourceRoot":"","sources":["../../src/search/fused.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAa,MAAM,iBAAiB,CAAC;AAC3G,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG5E,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOpE;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;CAC7C;AAaD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mHAAmH;IACnH,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IACnE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACxD;AAMD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,iBAAiB,EAIzB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC;IACT,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,2FAA2F;IAC3F,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B,CAAC,CAuKD;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,SAAS,WAAW,EAAE,EAClC,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,CAAC,CAiDjE"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { searchFts, searchIdentifierNearMiss, splitIdentifierTerms } from './fts.js';
|
|
2
|
+
import { querySymbolsBySimilarity } from '../graph/queries.js';
|
|
3
|
+
import { searchRankerD } from './declex.js';
|
|
4
|
+
import { resolveScopedPaths } from './scope.js';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Reciprocal Rank Fusion helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* RRF score for a document at rank `r` (1-indexed) with fusion constant `k`.
|
|
10
|
+
*
|
|
11
|
+
* Standard formula: Score(d) = Σ 1/(k + r(d)) over all rankers.
|
|
12
|
+
* The default k=60 dampens rank differences — a #1 vs #2 gap matters less
|
|
13
|
+
* than the absolute score gap would suggest.
|
|
14
|
+
*/
|
|
15
|
+
export function rrfScore(rank, k) {
|
|
16
|
+
return 1 / (k + rank);
|
|
17
|
+
}
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Fused search
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* BM25 lexical search fused with ranker D (declaration-exact, `./declex.ts`)
|
|
23
|
+
* via Reciprocal Rank Fusion.
|
|
24
|
+
*
|
|
25
|
+
* Stage 7 (IMPLEMENTATION_PLAN.md "Stage 7: Vector-store deletion") excised
|
|
26
|
+
* the vector leg entirely — `mast_search` is FTS + ranker D only now, and
|
|
27
|
+
* Stage 7.2 renamed this function (decision 4): its old name asserted a
|
|
28
|
+
* vector-plus-lexical fusion that no longer exists. The response no longer
|
|
29
|
+
* carries a search-mode discriminator or a per-result similarity score —
|
|
30
|
+
* both described the retired vector leg and have no honest value to report
|
|
31
|
+
* now that it's gone.
|
|
32
|
+
*
|
|
33
|
+
* Post-filters (`chunk_type`, `only_exported`) are applied after RRF ranking,
|
|
34
|
+
* on the full chunk records fetched from `chunkStore`.
|
|
35
|
+
*
|
|
36
|
+
* The scope filters (`file_pattern`, `language`) are NOT post-filters: each
|
|
37
|
+
* ranker applies them to its own candidate pool, through the shared
|
|
38
|
+
* `./scope.ts`, before that pool is capped. Applying them here instead would
|
|
39
|
+
* spend each ranker's candidate budget on rows about to be discarded. Anything
|
|
40
|
+
* later fused into this function must be scoped the same way — the union below
|
|
41
|
+
* is what carries a leak into the result set, and `__tests__/search-scope.test.ts`
|
|
42
|
+
* asserts the union, not the individual legs, for exactly that reason.
|
|
43
|
+
*/
|
|
44
|
+
export async function fusedSearch(db, input, config,
|
|
45
|
+
// Required (Stage 7.1): retires two recorded defects — the retired-Lance
|
|
46
|
+
// default and the swallowed embedder failure that used to live in this
|
|
47
|
+
// function's now-deleted vector-search try/catch (HANDOFF_Q1.md §5).
|
|
48
|
+
chunkStore) {
|
|
49
|
+
const limit = input.limit ?? 10;
|
|
50
|
+
// Over-fetch so post-filters don't starve the final result set.
|
|
51
|
+
const candidateLimit = limit * 4;
|
|
52
|
+
// One scope, handed to every ranker — see this function's doc.
|
|
53
|
+
const scope = { filePattern: input.file_pattern, language: input.language };
|
|
54
|
+
// --- BM25 ---
|
|
55
|
+
const ftsRows = await searchFts(db, input.query, {
|
|
56
|
+
limit: candidateLimit,
|
|
57
|
+
filePattern: scope.filePattern,
|
|
58
|
+
language: scope.language,
|
|
59
|
+
});
|
|
60
|
+
// Build rank + metadata maps for RRF.
|
|
61
|
+
const ftsMap = new Map();
|
|
62
|
+
ftsRows.forEach((r, i) => {
|
|
63
|
+
ftsMap.set(r.chunk_id, { rank: i + 1, bm25Score: r.bm25_score, snippet: r.match_snippet });
|
|
64
|
+
});
|
|
65
|
+
// --- Ranker D (declaration-exact) ---
|
|
66
|
+
// Gated on `=== true` (not truthiness) so `undefined` and `false` behave
|
|
67
|
+
// identically — the absent-means-OFF contract `FusedSearchConfig`
|
|
68
|
+
// documents above. `declex` is kept as the full result (not just its rows)
|
|
69
|
+
// so a later telemetry stage can lift its diagnostics into the return value
|
|
70
|
+
// without re-plumbing this call.
|
|
71
|
+
const dMap = new Map();
|
|
72
|
+
// Lifted out of the `if` block (rather than left local) so the telemetry
|
|
73
|
+
// block below can read `top_match_channel`/`candidate_count` without
|
|
74
|
+
// re-running `searchRankerD` — see this function's return-type doc.
|
|
75
|
+
let declexDiagnostics;
|
|
76
|
+
if (config.declaration_exact_ranker === true) {
|
|
77
|
+
const declex = await searchRankerD(db, input.query, { limit: candidateLimit, scope });
|
|
78
|
+
declex.rows.forEach((r, i) => {
|
|
79
|
+
dMap.set(r.chunk_id, i + 1);
|
|
80
|
+
});
|
|
81
|
+
declexDiagnostics = declex.diagnostics;
|
|
82
|
+
}
|
|
83
|
+
// --- RRF fusion ---
|
|
84
|
+
const allIds = new Set([...ftsMap.keys(), ...dMap.keys()]);
|
|
85
|
+
const scored = [];
|
|
86
|
+
for (const id of allIds) {
|
|
87
|
+
let rrf = 0;
|
|
88
|
+
const ftsMeta = ftsMap.get(id);
|
|
89
|
+
const dRank = dMap.get(id);
|
|
90
|
+
if (ftsMeta !== undefined)
|
|
91
|
+
rrf += rrfScore(ftsMeta.rank, config.rrf_k);
|
|
92
|
+
if (dRank !== undefined)
|
|
93
|
+
rrf += rrfScore(dRank, config.rrf_k);
|
|
94
|
+
scored.push({ chunk_id: id, rrf });
|
|
95
|
+
}
|
|
96
|
+
scored.sort((a, b) => b.rrf - a.rrf);
|
|
97
|
+
// --- Fetch chunk data and apply post-filters ---
|
|
98
|
+
const topIds = scored.slice(0, candidateLimit).map((s) => s.chunk_id);
|
|
99
|
+
const chunkRecords = await chunkStore.getChunksByIds(topIds);
|
|
100
|
+
// --- Ranker D dual-fusion diff (Stage 6.3 D-fire telemetry) ---
|
|
101
|
+
// Pure in-memory arithmetic over the already-fetched ftsMap list — no
|
|
102
|
+
// second FTS/D query, no second chunk fetch. Reconstructs the RRF sum RRF
|
|
103
|
+
// would have produced with D excluded entirely (not merely zero-scored — a
|
|
104
|
+
// D-only id has no rank without D, so it cannot appear in this list at
|
|
105
|
+
// all), to isolate exactly what D changed. Computed against `chunkRecords`
|
|
106
|
+
// (PRE-dedup, PRE-post-filter) per this feature's contract — the fused rank
|
|
107
|
+
// a consumer sees is the presentation-independent one.
|
|
108
|
+
let declexTelemetry;
|
|
109
|
+
if (dMap.size > 0 && declexDiagnostics !== undefined) {
|
|
110
|
+
const idsWithoutD = new Set([...ftsMap.keys()]);
|
|
111
|
+
const scoredWithoutD = [];
|
|
112
|
+
for (const id of idsWithoutD) {
|
|
113
|
+
let rrf = 0;
|
|
114
|
+
const ftsMeta = ftsMap.get(id);
|
|
115
|
+
if (ftsMeta !== undefined)
|
|
116
|
+
rrf += rrfScore(ftsMeta.rank, config.rrf_k);
|
|
117
|
+
scoredWithoutD.push({ chunk_id: id, rrf });
|
|
118
|
+
}
|
|
119
|
+
scoredWithoutD.sort((a, b) => b.rrf - a.rrf);
|
|
120
|
+
const rankWithD = new Map(scored.map((s, i) => [s.chunk_id, i + 1]));
|
|
121
|
+
const rankWithoutD = new Map(scoredWithoutD.map((s, i) => [s.chunk_id, i + 1]));
|
|
122
|
+
const symbolNameOf = new Map(chunkRecords.map((c) => [c.chunk_id, c.symbol_name]));
|
|
123
|
+
// Union of both lists' top-`limit` ids — the window a consumer actually
|
|
124
|
+
// sees, from either side of the diff.
|
|
125
|
+
const unionIds = new Set([
|
|
126
|
+
...scored.slice(0, limit).map((s) => s.chunk_id),
|
|
127
|
+
...scoredWithoutD.slice(0, limit).map((s) => s.chunk_id),
|
|
128
|
+
]);
|
|
129
|
+
const windowEffects = [];
|
|
130
|
+
for (const id of unionIds) {
|
|
131
|
+
const rankWith = rankWithD.get(id) ?? null;
|
|
132
|
+
const rankWithout = rankWithoutD.get(id) ?? null;
|
|
133
|
+
if (rankWith === rankWithout)
|
|
134
|
+
continue; // unaffected by D — not an "effect"
|
|
135
|
+
windowEffects.push({
|
|
136
|
+
chunk_id: id,
|
|
137
|
+
symbol_name: symbolNameOf.get(id) ?? null,
|
|
138
|
+
rank_with_d: rankWith,
|
|
139
|
+
rank_without_d: rankWithout,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Ascending rank_with_d, nulls last, ascending chunk_id tie-break — determinism.
|
|
143
|
+
windowEffects.sort((a, b) => {
|
|
144
|
+
if (a.rank_with_d === null && b.rank_with_d === null) {
|
|
145
|
+
return a.chunk_id < b.chunk_id ? -1 : a.chunk_id > b.chunk_id ? 1 : 0;
|
|
146
|
+
}
|
|
147
|
+
if (a.rank_with_d === null)
|
|
148
|
+
return 1;
|
|
149
|
+
if (b.rank_with_d === null)
|
|
150
|
+
return -1;
|
|
151
|
+
if (a.rank_with_d !== b.rank_with_d)
|
|
152
|
+
return a.rank_with_d - b.rank_with_d;
|
|
153
|
+
return a.chunk_id < b.chunk_id ? -1 : a.chunk_id > b.chunk_id ? 1 : 0;
|
|
154
|
+
});
|
|
155
|
+
declexTelemetry = {
|
|
156
|
+
fired: true,
|
|
157
|
+
top_match_channel: declexDiagnostics.top_match_channel,
|
|
158
|
+
candidate_count: declexDiagnostics.candidate_count,
|
|
159
|
+
window_effects: windowEffects,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const filtered = chunkRecords.filter((c) => {
|
|
163
|
+
if (input.chunk_type != null && c.chunk_type !== input.chunk_type)
|
|
164
|
+
return false;
|
|
165
|
+
if (input.only_exported === true && !c.is_exported)
|
|
166
|
+
return false;
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
169
|
+
// Re-sort filtered chunks by their RRF score (getChunksByIds order is arbitrary).
|
|
170
|
+
const rrfByChunkId = new Map(scored.map((s) => [s.chunk_id, s.rrf]));
|
|
171
|
+
filtered.sort((a, b) => (rrfByChunkId.get(b.chunk_id) ?? 0) - (rrfByChunkId.get(a.chunk_id) ?? 0));
|
|
172
|
+
// Post-RRF presentation pass (§9 mast_search): a method chunk and its class
|
|
173
|
+
// shell repeat the same signature, so when both land in the window only the
|
|
174
|
+
// higher-ranked one is returned, with a hint at the suppressed counterpart.
|
|
175
|
+
// Ranking math is untouched; freed slots backfill from the RRF tail.
|
|
176
|
+
const deduped = dedupShellMethodCollisions(filtered, limit);
|
|
177
|
+
const results = deduped.map(({ chunk: c, related }, i) => {
|
|
178
|
+
const ftsMeta = ftsMap.get(c.chunk_id);
|
|
179
|
+
return {
|
|
180
|
+
file_path: c.file_path,
|
|
181
|
+
start_line: c.start_line,
|
|
182
|
+
end_line: c.end_line,
|
|
183
|
+
content: c.content,
|
|
184
|
+
chunk_type: c.chunk_type,
|
|
185
|
+
symbol_name: c.symbol_name,
|
|
186
|
+
parent_symbol: c.parent_symbol,
|
|
187
|
+
is_exported: c.is_exported,
|
|
188
|
+
match_score: ftsMeta?.bm25Score ?? null,
|
|
189
|
+
// Re-ranked after dedup so consumers keep contiguous ranks from 1.
|
|
190
|
+
rank: i + 1,
|
|
191
|
+
match_snippet: ftsMeta?.snippet ?? null,
|
|
192
|
+
...(related !== undefined ? { related } : {}),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
// Zero-result assist: when nothing survived (no FTS/D hit, or filters
|
|
196
|
+
// emptied the set), gather advisory "did you mean" candidates instead of
|
|
197
|
+
// returning a bare dead end (§9 mast_search). Suggestions never become
|
|
198
|
+
// results — the caller keeps `results: []`.
|
|
199
|
+
const declexField = declexTelemetry !== undefined ? { declex: declexTelemetry } : {};
|
|
200
|
+
if (results.length === 0) {
|
|
201
|
+
const suggestions = await gatherSuggestions(db, chunkStore, input.query, limit, scope);
|
|
202
|
+
return { results, suggestions, ...declexField };
|
|
203
|
+
}
|
|
204
|
+
return { results, ...declexField };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Walk RRF-ranked candidates and keep up to `limit`, suppressing the
|
|
208
|
+
* lower-ranked half of every shell↔method pair from the same class:
|
|
209
|
+
*
|
|
210
|
+
* - a `class_shell` is dropped when any method of that class is already kept —
|
|
211
|
+
* the `{ parent_symbol }` hint lands on the highest-ranked such method;
|
|
212
|
+
* - a `method` is dropped when its class's shell is already kept — its
|
|
213
|
+
* qualified name is appended to the shell's `{ methods_matched }` hint;
|
|
214
|
+
* - methods never suppress each other, and classes are matched by
|
|
215
|
+
* file_path + class name so same-named classes in different files
|
|
216
|
+
* never collapse.
|
|
217
|
+
*
|
|
218
|
+
* Suppression frees slots, so later candidates backfill until `limit` distinct
|
|
219
|
+
* results are kept. Purely presentational — RRF scores are not modified.
|
|
220
|
+
*/
|
|
221
|
+
export function dedupShellMethodCollisions(candidates, limit) {
|
|
222
|
+
const kept = [];
|
|
223
|
+
const keptShellByClass = new Map();
|
|
224
|
+
const firstKeptMethodByClass = new Map();
|
|
225
|
+
// NUL never appears in paths or identifiers, so the key cannot collide.
|
|
226
|
+
const classKey = (filePath, className) => `${filePath}\u0000${className}`;
|
|
227
|
+
for (const c of candidates) {
|
|
228
|
+
if (kept.length >= limit)
|
|
229
|
+
break;
|
|
230
|
+
if (c.chunk_type === 'class_shell' && c.symbol_name !== null) {
|
|
231
|
+
const key = classKey(c.file_path, c.symbol_name);
|
|
232
|
+
const method = firstKeptMethodByClass.get(key);
|
|
233
|
+
if (method !== undefined) {
|
|
234
|
+
method.parentSymbolHint = c.symbol_name;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const entry = { chunk: c };
|
|
238
|
+
kept.push(entry);
|
|
239
|
+
// First shell claims the class slot (split shells share the key).
|
|
240
|
+
if (!keptShellByClass.has(key))
|
|
241
|
+
keptShellByClass.set(key, entry);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (c.chunk_type === 'method' && c.parent_symbol !== null) {
|
|
245
|
+
const key = classKey(c.file_path, c.parent_symbol);
|
|
246
|
+
const shell = keptShellByClass.get(key);
|
|
247
|
+
if (shell !== undefined) {
|
|
248
|
+
if (c.symbol_name !== null) {
|
|
249
|
+
(shell.methodsMatched ??= []).push(c.symbol_name);
|
|
250
|
+
}
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const entry = { chunk: c };
|
|
254
|
+
kept.push(entry);
|
|
255
|
+
if (!firstKeptMethodByClass.has(key))
|
|
256
|
+
firstKeptMethodByClass.set(key, entry);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
kept.push({ chunk: c });
|
|
260
|
+
}
|
|
261
|
+
return kept.map((k) => ({
|
|
262
|
+
chunk: k.chunk,
|
|
263
|
+
related: k.methodsMatched !== undefined ? { methods_matched: k.methodsMatched }
|
|
264
|
+
: k.parentSymbolHint !== undefined ? { parent_symbol: k.parentSymbolHint }
|
|
265
|
+
: undefined,
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Zero-result assist
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
/**
|
|
272
|
+
* Build advisory suggestions for a query that returned no results, from three
|
|
273
|
+
* complementary passes:
|
|
274
|
+
* (a) trigram similarity against the `symbols` table ("did you mean"),
|
|
275
|
+
* (b) an FTS retry over camelCase/snake_case-split query terms,
|
|
276
|
+
* (c) an `identifier_fts` near-miss over the same split terms.
|
|
277
|
+
* Results are de-duplicated by (symbol, file) and capped at `limit`.
|
|
278
|
+
*
|
|
279
|
+
* All three passes honour the caller's `scope`. A suggestion naming a file the
|
|
280
|
+
* caller excluded is the same misleading answer a *result* naming it would be —
|
|
281
|
+
* advisory or not, it points at code outside the boundary the caller asked
|
|
282
|
+
* about. Only pass (b) can take the filters natively; (a) and (c) return just a
|
|
283
|
+
* symbol and a path, so the scope is enforced in `add`, the one funnel all
|
|
284
|
+
* three go through.
|
|
285
|
+
*/
|
|
286
|
+
async function gatherSuggestions(db, chunkStore, query, limit, scope) {
|
|
287
|
+
const scopedList = await resolveScopedPaths(db, scope);
|
|
288
|
+
const scopedPaths = scopedList === null ? null : new Set(scopedList);
|
|
289
|
+
const out = [];
|
|
290
|
+
const seen = new Set();
|
|
291
|
+
const add = (symbol, filePath, reason) => {
|
|
292
|
+
if (symbol === null || out.length >= limit)
|
|
293
|
+
return;
|
|
294
|
+
if (scopedPaths !== null && !scopedPaths.has(filePath))
|
|
295
|
+
return;
|
|
296
|
+
const key = `${symbol}\u0000${filePath}`;
|
|
297
|
+
if (seen.has(key))
|
|
298
|
+
return;
|
|
299
|
+
seen.add(key);
|
|
300
|
+
out.push({ symbol, file_path: filePath, reason });
|
|
301
|
+
};
|
|
302
|
+
// (a) Trigram-similar symbol names — the highest-signal "did you mean".
|
|
303
|
+
const similar = await querySymbolsBySimilarity(db, query, limit);
|
|
304
|
+
for (const s of similar)
|
|
305
|
+
add(s.name, s.file_path, 'similar symbol name');
|
|
306
|
+
const terms = splitIdentifierTerms(query);
|
|
307
|
+
if (terms.length > 0) {
|
|
308
|
+
// (b) FTS retry over the split terms.
|
|
309
|
+
const ftsRows = await searchFts(db, terms.join(' '), {
|
|
310
|
+
limit, filePattern: scope.filePattern, language: scope.language,
|
|
311
|
+
});
|
|
312
|
+
const ftsChunks = await chunkStore.getChunksByIds(ftsRows.map((r) => r.chunk_id));
|
|
313
|
+
for (const c of ftsChunks)
|
|
314
|
+
add(c.symbol_name, c.file_path, 'matched split query terms');
|
|
315
|
+
// (c) Identifier near-miss over the split terms.
|
|
316
|
+
const nearRows = await searchIdentifierNearMiss(db, terms, limit);
|
|
317
|
+
const nearChunks = await chunkStore.getChunksByIds(nearRows.map((r) => r.chunk_id));
|
|
318
|
+
for (const c of nearChunks)
|
|
319
|
+
add(c.symbol_name, c.file_path, 'identifier near-miss');
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=fused.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fused.js","sourceRoot":"","sources":["../../src/search/fused.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Db } from '../graph/db.js';
|
|
2
|
+
import type { VerifiedCaller, PotentialMatch } from '../ast/types.js';
|
|
3
|
+
/** The chunk fields candidate collection actually reads. */
|
|
4
|
+
export interface CandidateChunkRecord {
|
|
5
|
+
readonly chunk_id: string;
|
|
6
|
+
readonly file_path: string;
|
|
7
|
+
readonly start_line: number;
|
|
8
|
+
readonly end_line: number;
|
|
9
|
+
readonly symbol_name: string | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Role interface (§4.3) over "fetch chunks by id" — the only capability this
|
|
13
|
+
* module needs from a chunk store. `SqliteChunkStore` satisfies it
|
|
14
|
+
* structurally, so `mast_callers`/`mast_rename_impact` pass their
|
|
15
|
+
* `ctx.chunkStore` directly. The `mast index --checker` pass passes an
|
|
16
|
+
* in-memory prefetched source instead: its Phase A runs candidate collection
|
|
17
|
+
* for EVERY indexed symbol, and one round-trip per symbol was measured at
|
|
18
|
+
* 50+ CPU-minutes on the kluster monorepo (10,733 symbols, against the
|
|
19
|
+
* pre-M1 LanceDB chunk store) without completing — one full scan up front
|
|
20
|
+
* plus Map lookups keeps identical semantics at a fraction of the cost.
|
|
21
|
+
*/
|
|
22
|
+
export interface ChunkByIdSource {
|
|
23
|
+
getChunksByIds(ids: readonly string[]): Promise<readonly CandidateChunkRecord[]>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Chunk-level detail behind a potential match. `collectPotentialMatches` (the
|
|
27
|
+
* public shape `mast_callers`/`mast_rename_impact` consume) and the checker
|
|
28
|
+
* pass (which additionally needs `end_line` and the call site's enclosing
|
|
29
|
+
* symbol to classify, and on a definite match, write a caller edge) both
|
|
30
|
+
* derive from this one query — never forked.
|
|
31
|
+
*/
|
|
32
|
+
export interface PotentialMatchCandidate {
|
|
33
|
+
readonly file_path: string;
|
|
34
|
+
readonly start_line: number;
|
|
35
|
+
readonly end_line: number;
|
|
36
|
+
/** The call site's own enclosing symbol (e.g. the function it appears
|
|
37
|
+
* inside) — NOT the queried symbol. Null for a `block` chunk (top-level
|
|
38
|
+
* code with no enclosing declaration). */
|
|
39
|
+
readonly chunk_symbol_name: string | null;
|
|
40
|
+
}
|
|
41
|
+
export interface PotentialMatchCandidatesResult {
|
|
42
|
+
readonly candidates: readonly PotentialMatchCandidate[];
|
|
43
|
+
/**
|
|
44
|
+
* F10 (Stage 3, IMPLEMENTATION_PLAN.md): the real, uncapped `identifier_fts`
|
|
45
|
+
* match count for `symbolName` — present ONLY when the capped fetch
|
|
46
|
+
* (`limit`) came back full AND the true count exceeds it (an exactly-full
|
|
47
|
+
* fetch with no more real matches is not truncation). `mast_callers` /
|
|
48
|
+
* `mast_rename_impact` surface this as `summary.potential_truncated`
|
|
49
|
+
* (MAST_SPEC.md §9.0's Confidence signals table). Omitted whenever the
|
|
50
|
+
* fetch returned fewer rows than `limit` — the fetch count already IS the
|
|
51
|
+
* real count on that path, so the extra `COUNT(*)` query
|
|
52
|
+
* ({@link countIdentifierMatches}) is never run and would be pure waste.
|
|
53
|
+
*/
|
|
54
|
+
readonly truncatedMatchCount?: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Identifier-FTS hits for `symbolName` that are NOT already covered by a
|
|
58
|
+
* verified caller — the raw candidate pool behind `potential_matches` (§9
|
|
59
|
+
* mast_callers), before checker-verdict filtering.
|
|
60
|
+
*/
|
|
61
|
+
export declare function collectPotentialMatchCandidates(db: Db, chunkSource: ChunkByIdSource, symbolName: string, verified: readonly VerifiedCaller[], limit?: number): Promise<PotentialMatchCandidatesResult>;
|
|
62
|
+
export interface PotentialMatchesResult {
|
|
63
|
+
readonly matches: readonly PotentialMatch[];
|
|
64
|
+
/** Candidates the checker pass classified as not a call site (§10.3.2), dropped from `matches`. */
|
|
65
|
+
readonly checkerClassifiedNonCallSite: number;
|
|
66
|
+
/** Candidates the checker pass resolved to a DIFFERENT declaration, dropped from `matches`. */
|
|
67
|
+
readonly checkerClassifiedDifferentDeclaration: number;
|
|
68
|
+
/** @see {@link PotentialMatchCandidatesResult.truncatedMatchCount} — same signal, passed through unchanged. */
|
|
69
|
+
readonly truncatedMatchCount?: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Identifier-FTS hits for `symbolName` that are NOT already covered by a
|
|
73
|
+
* verified caller — the "review required" set. Shared by `mast_callers` and
|
|
74
|
+
* `mast_rename_impact` so the two tools can never disagree about what counts
|
|
75
|
+
* as a potential match.
|
|
76
|
+
*
|
|
77
|
+
* When `mast index --checker` (Stage 1.2) has classified a candidate as
|
|
78
|
+
* `non_call_site` or `resolves_to_different`, it is dropped here rather than
|
|
79
|
+
* re-surfaced as a review site forever — a `resolves_to_queried` candidate is
|
|
80
|
+
* also dropped defensively (it should already be excluded via `verified`
|
|
81
|
+
* through the checker edge's own `call_line`, but the checker's more-precise
|
|
82
|
+
* call line can differ from the chunk's `start_line` for a multi-line chunk;
|
|
83
|
+
* this filter closes that gap using the SAME (file_path, start_line) key the
|
|
84
|
+
* verdict was recorded against). `unresolved` candidates are never persisted
|
|
85
|
+
* as verdicts (see `checker_verdicts` in `graph/db.ts`) and always remain.
|
|
86
|
+
*/
|
|
87
|
+
export declare function collectPotentialMatches(db: Db, chunkSource: ChunkByIdSource, symbolId: number, symbolName: string, verified: readonly VerifiedCaller[], limit?: number): Promise<PotentialMatchesResult>;
|
|
88
|
+
//# sourceMappingURL=potential-matches.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"potential-matches.d.ts","sourceRoot":"","sources":["../../src/search/potential-matches.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AActE,4DAA4D;AAC5D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;CAClF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;+CAE2C;IAC3C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,UAAU,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACxD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;GAIG;AACH,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,eAAe,EAC5B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,SAAK,GACT,OAAO,CAAC,8BAA8B,CAAC,CA2BzC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,mGAAmG;IACnG,QAAQ,CAAC,4BAA4B,EAAE,MAAM,CAAC;IAC9C,+FAA+F;IAC/F,QAAQ,CAAC,qCAAqC,EAAE,MAAM,CAAC;IACvD,+GAA+G;IAC/G,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,eAAe,EAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,KAAK,SAAK,GACT,OAAO,CAAC,sBAAsB,CAAC,CAwBjC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { searchIdentifiers, countIdentifierMatches } from './fts.js';
|
|
2
|
+
import { queryCheckerVerdicts } from '../graph/queries.js';
|
|
3
|
+
/**
|
|
4
|
+
* Identifier-FTS hits for `symbolName` that are NOT already covered by a
|
|
5
|
+
* verified caller — the raw candidate pool behind `potential_matches` (§9
|
|
6
|
+
* mast_callers), before checker-verdict filtering.
|
|
7
|
+
*/
|
|
8
|
+
export async function collectPotentialMatchCandidates(db, chunkSource, symbolName, verified, limit = 50) {
|
|
9
|
+
const identRows = await searchIdentifiers(db, symbolName, limit);
|
|
10
|
+
const chunks = await chunkSource.getChunksByIds(identRows.map((r) => r.chunk_id));
|
|
11
|
+
// Exclude chunk IDs already covered by the verified set.
|
|
12
|
+
const verifiedKeys = new Set(verified.map((c) => `${c.file_path}:${c.line}`));
|
|
13
|
+
const candidates = [];
|
|
14
|
+
for (const chunk of chunks) {
|
|
15
|
+
if (verifiedKeys.has(`${chunk.file_path}:${chunk.start_line}`))
|
|
16
|
+
continue;
|
|
17
|
+
candidates.push({
|
|
18
|
+
file_path: chunk.file_path,
|
|
19
|
+
start_line: chunk.start_line,
|
|
20
|
+
end_line: chunk.end_line,
|
|
21
|
+
chunk_symbol_name: chunk.symbol_name,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// The capped fetch hides how many real matches exist. Only worth paying
|
|
25
|
+
// for an uncapped COUNT(*) when the fetch actually came back full — under
|
|
26
|
+
// the cap, identRows.length already IS the real count (see F10 note above).
|
|
27
|
+
let truncatedMatchCount;
|
|
28
|
+
if (identRows.length === limit) {
|
|
29
|
+
const rawCount = await countIdentifierMatches(db, symbolName);
|
|
30
|
+
if (rawCount > limit)
|
|
31
|
+
truncatedMatchCount = rawCount;
|
|
32
|
+
}
|
|
33
|
+
return truncatedMatchCount !== undefined ? { candidates, truncatedMatchCount } : { candidates };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Identifier-FTS hits for `symbolName` that are NOT already covered by a
|
|
37
|
+
* verified caller — the "review required" set. Shared by `mast_callers` and
|
|
38
|
+
* `mast_rename_impact` so the two tools can never disagree about what counts
|
|
39
|
+
* as a potential match.
|
|
40
|
+
*
|
|
41
|
+
* When `mast index --checker` (Stage 1.2) has classified a candidate as
|
|
42
|
+
* `non_call_site` or `resolves_to_different`, it is dropped here rather than
|
|
43
|
+
* re-surfaced as a review site forever — a `resolves_to_queried` candidate is
|
|
44
|
+
* also dropped defensively (it should already be excluded via `verified`
|
|
45
|
+
* through the checker edge's own `call_line`, but the checker's more-precise
|
|
46
|
+
* call line can differ from the chunk's `start_line` for a multi-line chunk;
|
|
47
|
+
* this filter closes that gap using the SAME (file_path, start_line) key the
|
|
48
|
+
* verdict was recorded against). `unresolved` candidates are never persisted
|
|
49
|
+
* as verdicts (see `checker_verdicts` in `graph/db.ts`) and always remain.
|
|
50
|
+
*/
|
|
51
|
+
export async function collectPotentialMatches(db, chunkSource, symbolId, symbolName, verified, limit = 50) {
|
|
52
|
+
const { candidates, truncatedMatchCount } = await collectPotentialMatchCandidates(db, chunkSource, symbolName, verified, limit);
|
|
53
|
+
const verdicts = await queryCheckerVerdicts(db, symbolId);
|
|
54
|
+
const verdictByKey = new Map();
|
|
55
|
+
for (const v of verdicts)
|
|
56
|
+
verdictByKey.set(`${v.file_path}:${v.call_site_line}`, v.verdict);
|
|
57
|
+
const matches = [];
|
|
58
|
+
let checkerClassifiedNonCallSite = 0;
|
|
59
|
+
let checkerClassifiedDifferentDeclaration = 0;
|
|
60
|
+
for (const c of candidates) {
|
|
61
|
+
const verdict = verdictByKey.get(`${c.file_path}:${c.start_line}`);
|
|
62
|
+
if (verdict === 'non_call_site') {
|
|
63
|
+
checkerClassifiedNonCallSite++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (verdict === 'resolves_to_different') {
|
|
67
|
+
checkerClassifiedDifferentDeclaration++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (verdict === 'resolves_to_queried')
|
|
71
|
+
continue;
|
|
72
|
+
matches.push({
|
|
73
|
+
file_path: c.file_path,
|
|
74
|
+
line: c.start_line,
|
|
75
|
+
context: c.chunk_symbol_name ?? '',
|
|
76
|
+
reason: 'identifier_match_no_resolved_edge',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return truncatedMatchCount !== undefined
|
|
80
|
+
? { matches, checkerClassifiedNonCallSite, checkerClassifiedDifferentDeclaration, truncatedMatchCount }
|
|
81
|
+
: { matches, checkerClassifiedNonCallSite, checkerClassifiedDifferentDeclaration };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=potential-matches.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"potential-matches.js","sourceRoot":"","sources":["../../src/search/potential-matches.ts"],"names":[],"mappings":"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"}
|