@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.
Files changed (257) hide show
  1. package/LICENSE +21 -0
  2. package/MAST_SPEC.md +2926 -0
  3. package/README.md +842 -0
  4. package/assets/skill.md +45 -0
  5. package/dist/ast/extract.d.ts +65 -0
  6. package/dist/ast/extract.d.ts.map +1 -0
  7. package/dist/ast/extract.js +147 -0
  8. package/dist/ast/extract.js.map +1 -0
  9. package/dist/ast/extractor.d.ts +85 -0
  10. package/dist/ast/extractor.d.ts.map +1 -0
  11. package/dist/ast/extractor.js +2 -0
  12. package/dist/ast/extractor.js.map +1 -0
  13. package/dist/ast/extractors/markdown.d.ts +40 -0
  14. package/dist/ast/extractors/markdown.d.ts.map +1 -0
  15. package/dist/ast/extractors/markdown.js +175 -0
  16. package/dist/ast/extractors/markdown.js.map +1 -0
  17. package/dist/ast/extractors/typescript.d.ts +179 -0
  18. package/dist/ast/extractors/typescript.d.ts.map +1 -0
  19. package/dist/ast/extractors/typescript.js +1439 -0
  20. package/dist/ast/extractors/typescript.js.map +1 -0
  21. package/dist/ast/parser.d.ts +5 -0
  22. package/dist/ast/parser.d.ts.map +1 -0
  23. package/dist/ast/parser.js +21 -0
  24. package/dist/ast/parser.js.map +1 -0
  25. package/dist/ast/types.d.ts +586 -0
  26. package/dist/ast/types.d.ts.map +1 -0
  27. package/dist/ast/types.js +3 -0
  28. package/dist/ast/types.js.map +1 -0
  29. package/dist/cli/docs-cmd.d.ts +22 -0
  30. package/dist/cli/docs-cmd.d.ts.map +1 -0
  31. package/dist/cli/docs-cmd.js +100 -0
  32. package/dist/cli/docs-cmd.js.map +1 -0
  33. package/dist/cli/index-cmd.d.ts +72 -0
  34. package/dist/cli/index-cmd.d.ts.map +1 -0
  35. package/dist/cli/index-cmd.js +263 -0
  36. package/dist/cli/index-cmd.js.map +1 -0
  37. package/dist/cli/index.d.ts +3 -0
  38. package/dist/cli/index.d.ts.map +1 -0
  39. package/dist/cli/index.js +4 -0
  40. package/dist/cli/index.js.map +1 -0
  41. package/dist/cli/init.d.ts +14 -0
  42. package/dist/cli/init.d.ts.map +1 -0
  43. package/dist/cli/init.js +64 -0
  44. package/dist/cli/init.js.map +1 -0
  45. package/dist/cli/install-hooks.d.ts +3 -0
  46. package/dist/cli/install-hooks.d.ts.map +1 -0
  47. package/dist/cli/install-hooks.js +34 -0
  48. package/dist/cli/install-hooks.js.map +1 -0
  49. package/dist/cli/metrics-cmd.d.ts +3 -0
  50. package/dist/cli/metrics-cmd.d.ts.map +1 -0
  51. package/dist/cli/metrics-cmd.js +164 -0
  52. package/dist/cli/metrics-cmd.js.map +1 -0
  53. package/dist/cli/program.d.ts +14 -0
  54. package/dist/cli/program.d.ts.map +1 -0
  55. package/dist/cli/program.js +44 -0
  56. package/dist/cli/program.js.map +1 -0
  57. package/dist/cli/query.d.ts +46 -0
  58. package/dist/cli/query.d.ts.map +1 -0
  59. package/dist/cli/query.js +153 -0
  60. package/dist/cli/query.js.map +1 -0
  61. package/dist/cli/search-cmd.d.ts +33 -0
  62. package/dist/cli/search-cmd.d.ts.map +1 -0
  63. package/dist/cli/search-cmd.js +114 -0
  64. package/dist/cli/search-cmd.js.map +1 -0
  65. package/dist/cli/serve.d.ts +3 -0
  66. package/dist/cli/serve.d.ts.map +1 -0
  67. package/dist/cli/serve.js +19 -0
  68. package/dist/cli/serve.js.map +1 -0
  69. package/dist/cli/skill-install.d.ts +39 -0
  70. package/dist/cli/skill-install.d.ts.map +1 -0
  71. package/dist/cli/skill-install.js +57 -0
  72. package/dist/cli/skill-install.js.map +1 -0
  73. package/dist/cli/status.d.ts +35 -0
  74. package/dist/cli/status.d.ts.map +1 -0
  75. package/dist/cli/status.js +117 -0
  76. package/dist/cli/status.js.map +1 -0
  77. package/dist/cli/upgrade-cmd.d.ts +48 -0
  78. package/dist/cli/upgrade-cmd.d.ts.map +1 -0
  79. package/dist/cli/upgrade-cmd.js +129 -0
  80. package/dist/cli/upgrade-cmd.js.map +1 -0
  81. package/dist/cli/version.d.ts +3 -0
  82. package/dist/cli/version.d.ts.map +1 -0
  83. package/dist/cli/version.js +19 -0
  84. package/dist/cli/version.js.map +1 -0
  85. package/dist/env.d.ts +17 -0
  86. package/dist/env.d.ts.map +1 -0
  87. package/dist/env.js +17 -0
  88. package/dist/env.js.map +1 -0
  89. package/dist/graph/checker-resolver.d.ts +156 -0
  90. package/dist/graph/checker-resolver.d.ts.map +1 -0
  91. package/dist/graph/checker-resolver.js +458 -0
  92. package/dist/graph/checker-resolver.js.map +1 -0
  93. package/dist/graph/db.d.ts +318 -0
  94. package/dist/graph/db.d.ts.map +1 -0
  95. package/dist/graph/db.js +266 -0
  96. package/dist/graph/db.js.map +1 -0
  97. package/dist/graph/index.d.ts +4 -0
  98. package/dist/graph/index.d.ts.map +1 -0
  99. package/dist/graph/index.js +5 -0
  100. package/dist/graph/index.js.map +1 -0
  101. package/dist/graph/local-type-env.d.ts +45 -0
  102. package/dist/graph/local-type-env.d.ts.map +1 -0
  103. package/dist/graph/local-type-env.js +59 -0
  104. package/dist/graph/local-type-env.js.map +1 -0
  105. package/dist/graph/path-range.d.ts +40 -0
  106. package/dist/graph/path-range.d.ts.map +1 -0
  107. package/dist/graph/path-range.js +42 -0
  108. package/dist/graph/path-range.js.map +1 -0
  109. package/dist/graph/populate.d.ts +263 -0
  110. package/dist/graph/populate.d.ts.map +1 -0
  111. package/dist/graph/populate.js +984 -0
  112. package/dist/graph/populate.js.map +1 -0
  113. package/dist/graph/queries.d.ts +114 -0
  114. package/dist/graph/queries.d.ts.map +1 -0
  115. package/dist/graph/queries.js +415 -0
  116. package/dist/graph/queries.js.map +1 -0
  117. package/dist/graph/sqliteBatch.d.ts +48 -0
  118. package/dist/graph/sqliteBatch.d.ts.map +1 -0
  119. package/dist/graph/sqliteBatch.js +93 -0
  120. package/dist/graph/sqliteBatch.js.map +1 -0
  121. package/dist/indexer/freshness.d.ts +48 -0
  122. package/dist/indexer/freshness.d.ts.map +1 -0
  123. package/dist/indexer/freshness.js +72 -0
  124. package/dist/indexer/freshness.js.map +1 -0
  125. package/dist/indexer/import-resolver.d.ts +50 -0
  126. package/dist/indexer/import-resolver.d.ts.map +1 -0
  127. package/dist/indexer/import-resolver.js +318 -0
  128. package/dist/indexer/import-resolver.js.map +1 -0
  129. package/dist/indexer/index.d.ts +249 -0
  130. package/dist/indexer/index.d.ts.map +1 -0
  131. package/dist/indexer/index.js +560 -0
  132. package/dist/indexer/index.js.map +1 -0
  133. package/dist/indexer/walker.d.ts +60 -0
  134. package/dist/indexer/walker.d.ts.map +1 -0
  135. package/dist/indexer/walker.js +128 -0
  136. package/dist/indexer/walker.js.map +1 -0
  137. package/dist/indexer/watcher.d.ts +80 -0
  138. package/dist/indexer/watcher.d.ts.map +1 -0
  139. package/dist/indexer/watcher.js +151 -0
  140. package/dist/indexer/watcher.js.map +1 -0
  141. package/dist/mcp/context.d.ts +23 -0
  142. package/dist/mcp/context.d.ts.map +1 -0
  143. package/dist/mcp/context.js +2 -0
  144. package/dist/mcp/context.js.map +1 -0
  145. package/dist/mcp/register-tools.d.ts +21 -0
  146. package/dist/mcp/register-tools.d.ts.map +1 -0
  147. package/dist/mcp/register-tools.js +42 -0
  148. package/dist/mcp/register-tools.js.map +1 -0
  149. package/dist/mcp/server.d.ts +58 -0
  150. package/dist/mcp/server.d.ts.map +1 -0
  151. package/dist/mcp/server.js +177 -0
  152. package/dist/mcp/server.js.map +1 -0
  153. package/dist/mcp/staleness.d.ts +72 -0
  154. package/dist/mcp/staleness.d.ts.map +1 -0
  155. package/dist/mcp/staleness.js +213 -0
  156. package/dist/mcp/staleness.js.map +1 -0
  157. package/dist/mcp/startup.d.ts +40 -0
  158. package/dist/mcp/startup.d.ts.map +1 -0
  159. package/dist/mcp/startup.js +114 -0
  160. package/dist/mcp/startup.js.map +1 -0
  161. package/dist/mcp/tools/_helpers.d.ts +26 -0
  162. package/dist/mcp/tools/_helpers.d.ts.map +1 -0
  163. package/dist/mcp/tools/_helpers.js +54 -0
  164. package/dist/mcp/tools/_helpers.js.map +1 -0
  165. package/dist/mcp/tools/callers.d.ts +4 -0
  166. package/dist/mcp/tools/callers.d.ts.map +1 -0
  167. package/dist/mcp/tools/callers.js +112 -0
  168. package/dist/mcp/tools/callers.js.map +1 -0
  169. package/dist/mcp/tools/dependencies.d.ts +4 -0
  170. package/dist/mcp/tools/dependencies.d.ts.map +1 -0
  171. package/dist/mcp/tools/dependencies.js +36 -0
  172. package/dist/mcp/tools/dependencies.js.map +1 -0
  173. package/dist/mcp/tools/efficiency.d.ts +4 -0
  174. package/dist/mcp/tools/efficiency.d.ts.map +1 -0
  175. package/dist/mcp/tools/efficiency.js +39 -0
  176. package/dist/mcp/tools/efficiency.js.map +1 -0
  177. package/dist/mcp/tools/exports.d.ts +4 -0
  178. package/dist/mcp/tools/exports.d.ts.map +1 -0
  179. package/dist/mcp/tools/exports.js +56 -0
  180. package/dist/mcp/tools/exports.js.map +1 -0
  181. package/dist/mcp/tools/implementors.d.ts +4 -0
  182. package/dist/mcp/tools/implementors.d.ts.map +1 -0
  183. package/dist/mcp/tools/implementors.js +47 -0
  184. package/dist/mcp/tools/implementors.js.map +1 -0
  185. package/dist/mcp/tools/project-skeleton.d.ts +4 -0
  186. package/dist/mcp/tools/project-skeleton.d.ts.map +1 -0
  187. package/dist/mcp/tools/project-skeleton.js +61 -0
  188. package/dist/mcp/tools/project-skeleton.js.map +1 -0
  189. package/dist/mcp/tools/reindex.d.ts +14 -0
  190. package/dist/mcp/tools/reindex.d.ts.map +1 -0
  191. package/dist/mcp/tools/reindex.js +32 -0
  192. package/dist/mcp/tools/reindex.js.map +1 -0
  193. package/dist/mcp/tools/rename-impact.d.ts +4 -0
  194. package/dist/mcp/tools/rename-impact.d.ts.map +1 -0
  195. package/dist/mcp/tools/rename-impact.js +124 -0
  196. package/dist/mcp/tools/rename-impact.js.map +1 -0
  197. package/dist/mcp/tools/search.d.ts +4 -0
  198. package/dist/mcp/tools/search.d.ts.map +1 -0
  199. package/dist/mcp/tools/search.js +69 -0
  200. package/dist/mcp/tools/search.js.map +1 -0
  201. package/dist/mcp/tools/signature.d.ts +4 -0
  202. package/dist/mcp/tools/signature.d.ts.map +1 -0
  203. package/dist/mcp/tools/signature.js +127 -0
  204. package/dist/mcp/tools/signature.js.map +1 -0
  205. package/dist/mcp/tools/status.d.ts +4 -0
  206. package/dist/mcp/tools/status.d.ts.map +1 -0
  207. package/dist/mcp/tools/status.js +30 -0
  208. package/dist/mcp/tools/status.js.map +1 -0
  209. package/dist/search/declex.d.ts +109 -0
  210. package/dist/search/declex.d.ts.map +1 -0
  211. package/dist/search/declex.js +242 -0
  212. package/dist/search/declex.js.map +1 -0
  213. package/dist/search/fts.d.ts +77 -0
  214. package/dist/search/fts.d.ts.map +1 -0
  215. package/dist/search/fts.js +223 -0
  216. package/dist/search/fts.js.map +1 -0
  217. package/dist/search/fused.d.ts +110 -0
  218. package/dist/search/fused.d.ts.map +1 -0
  219. package/dist/search/fused.js +323 -0
  220. package/dist/search/fused.js.map +1 -0
  221. package/dist/search/potential-matches.d.ts +88 -0
  222. package/dist/search/potential-matches.d.ts.map +1 -0
  223. package/dist/search/potential-matches.js +83 -0
  224. package/dist/search/potential-matches.js.map +1 -0
  225. package/dist/search/scope.d.ts +66 -0
  226. package/dist/search/scope.d.ts.map +1 -0
  227. package/dist/search/scope.js +71 -0
  228. package/dist/search/scope.js.map +1 -0
  229. package/dist/store/config.d.ts +57 -0
  230. package/dist/store/config.d.ts.map +1 -0
  231. package/dist/store/config.js +208 -0
  232. package/dist/store/config.js.map +1 -0
  233. package/dist/store/lock.d.ts +101 -0
  234. package/dist/store/lock.d.ts.map +1 -0
  235. package/dist/store/lock.js +182 -0
  236. package/dist/store/lock.js.map +1 -0
  237. package/dist/store/lockMetrics.d.ts +52 -0
  238. package/dist/store/lockMetrics.d.ts.map +1 -0
  239. package/dist/store/lockMetrics.js +29 -0
  240. package/dist/store/lockMetrics.js.map +1 -0
  241. package/dist/store/sqliteChunkStore.d.ts +44 -0
  242. package/dist/store/sqliteChunkStore.d.ts.map +1 -0
  243. package/dist/store/sqliteChunkStore.js +135 -0
  244. package/dist/store/sqliteChunkStore.js.map +1 -0
  245. package/dist/telemetry/lockMetricsSummary.d.ts +42 -0
  246. package/dist/telemetry/lockMetricsSummary.d.ts.map +1 -0
  247. package/dist/telemetry/lockMetricsSummary.js +139 -0
  248. package/dist/telemetry/lockMetricsSummary.js.map +1 -0
  249. package/dist/telemetry/metrics.d.ts +140 -0
  250. package/dist/telemetry/metrics.d.ts.map +1 -0
  251. package/dist/telemetry/metrics.js +312 -0
  252. package/dist/telemetry/metrics.js.map +1 -0
  253. package/dist/telemetry/tokenizer.d.ts +125 -0
  254. package/dist/telemetry/tokenizer.d.ts.map +1 -0
  255. package/dist/telemetry/tokenizer.js +174 -0
  256. package/dist/telemetry/tokenizer.js.map +1 -0
  257. 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":"AAEA,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAqE3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,EAAM,EACN,WAA4B,EAC5B,UAAkB,EAClB,QAAmC,EACnC,KAAK,GAAG,EAAE;IAEV,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElF,yDAAyD;IACzD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,UAAU,GAA8B,EAAE,CAAC;IACjD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YAAE,SAAS;QACzE,UAAU,CAAC,IAAI,CAAC;YACd,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,iBAAiB,EAAE,KAAK,CAAC,WAAW;SACrC,CAAC,CAAC;IACL,CAAC;IAED,wEAAwE;IACxE,0EAA0E;IAC1E,4EAA4E;IAC5E,IAAI,mBAAuC,CAAC;IAC5C,IAAI,SAAS,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,QAAQ,GAAG,KAAK;YAAE,mBAAmB,GAAG,QAAQ,CAAC;IACvD,CAAC;IAED,OAAO,mBAAmB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,mBAAmB,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;AAClG,CAAC;AAYD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,EAAM,EACN,WAA4B,EAC5B,QAAgB,EAChB,UAAkB,EAClB,QAAmC,EACnC,KAAK,GAAG,EAAE;IAEV,MAAM,EAAE,UAAU,EAAE,mBAAmB,EAAE,GAAG,MAAM,+BAA+B,CAAC,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChI,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IAE5F,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,IAAI,4BAA4B,GAAG,CAAC,CAAC;IACrC,IAAI,qCAAqC,GAAG,CAAC,CAAC;IAC9C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;QACnE,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;YAAC,4BAA4B,EAAE,CAAC;YAAC,SAAS;QAAC,CAAC;QAC9E,IAAI,OAAO,KAAK,uBAAuB,EAAE,CAAC;YAAC,qCAAqC,EAAE,CAAC;YAAC,SAAS;QAAC,CAAC;QAC/F,IAAI,OAAO,KAAK,qBAAqB;YAAE,SAAS;QAChD,OAAO,CAAC,IAAI,CAAC;YACX,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,IAAI,EAAE,CAAC,CAAC,UAAU;YAClB,OAAO,EAAE,CAAC,CAAC,iBAAiB,IAAI,EAAE;YAClC,MAAM,EAAE,mCAAmC;SAC5C,CAAC,CAAC;IACL,CAAC;IACD,OAAO,mBAAmB,KAAK,SAAS;QACtC,CAAC,CAAC,EAAE,OAAO,EAAE,4BAA4B,EAAE,qCAAqC,EAAE,mBAAmB,EAAE;QACvG,CAAC,CAAC,EAAE,OAAO,EAAE,4BAA4B,EAAE,qCAAqC,EAAE,CAAC;AACvF,CAAC"}