@nanonets/graft 0.10.0 → 0.12.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 (238) hide show
  1. package/README.md +34 -13
  2. package/dist/ai/crux.d.ts.map +1 -1
  3. package/dist/ai/crux.js +7 -6
  4. package/dist/ai/crux.js.map +1 -1
  5. package/dist/ai/failure.d.ts +49 -0
  6. package/dist/ai/failure.d.ts.map +1 -0
  7. package/dist/ai/failure.js +74 -0
  8. package/dist/ai/failure.js.map +1 -0
  9. package/dist/ai/llm/anthropic.d.ts.map +1 -1
  10. package/dist/ai/llm/anthropic.js +4 -1
  11. package/dist/ai/llm/anthropic.js.map +1 -1
  12. package/dist/ai/llm/claude-cli.d.ts +60 -0
  13. package/dist/ai/llm/claude-cli.d.ts.map +1 -0
  14. package/dist/ai/llm/claude-cli.js +417 -0
  15. package/dist/ai/llm/claude-cli.js.map +1 -0
  16. package/dist/ai/llm/openai.d.ts.map +1 -1
  17. package/dist/ai/llm/openai.js +2 -0
  18. package/dist/ai/llm/openai.js.map +1 -1
  19. package/dist/ai/llm/types.d.ts +9 -0
  20. package/dist/ai/llm/types.d.ts.map +1 -1
  21. package/dist/ai/llm/types.js +12 -1
  22. package/dist/ai/llm/types.js.map +1 -1
  23. package/dist/ask/ask.d.ts.map +1 -1
  24. package/dist/ask/ask.js +39 -45
  25. package/dist/ask/ask.js.map +1 -1
  26. package/dist/ask/fuse.d.ts +83 -46
  27. package/dist/ask/fuse.d.ts.map +1 -1
  28. package/dist/ask/fuse.js +165 -67
  29. package/dist/ask/fuse.js.map +1 -1
  30. package/dist/blast/blast-cli.d.ts +30 -0
  31. package/dist/blast/blast-cli.d.ts.map +1 -0
  32. package/dist/blast/blast-cli.js +163 -0
  33. package/dist/blast/blast-cli.js.map +1 -0
  34. package/dist/blast/blast.d.ts +119 -0
  35. package/dist/blast/blast.d.ts.map +1 -0
  36. package/dist/blast/blast.js +383 -0
  37. package/dist/blast/blast.js.map +1 -0
  38. package/dist/blast/diff.d.ts +53 -0
  39. package/dist/blast/diff.d.ts.map +1 -0
  40. package/dist/blast/diff.js +193 -0
  41. package/dist/blast/diff.js.map +1 -0
  42. package/dist/blast/modules.d.ts +33 -0
  43. package/dist/blast/modules.d.ts.map +1 -0
  44. package/dist/blast/modules.js +83 -0
  45. package/dist/blast/modules.js.map +1 -0
  46. package/dist/blast/name.d.ts +61 -0
  47. package/dist/blast/name.d.ts.map +1 -0
  48. package/dist/blast/name.js +233 -0
  49. package/dist/blast/name.js.map +1 -0
  50. package/dist/blast/render.d.ts +37 -0
  51. package/dist/blast/render.d.ts.map +1 -0
  52. package/dist/blast/render.js +300 -0
  53. package/dist/blast/render.js.map +1 -0
  54. package/dist/blast/viz.d.ts +17 -0
  55. package/dist/blast/viz.d.ts.map +1 -0
  56. package/dist/blast/viz.js +324 -0
  57. package/dist/blast/viz.js.map +1 -0
  58. package/dist/claude/hooks.d.ts +13 -0
  59. package/dist/claude/hooks.d.ts.map +1 -1
  60. package/dist/claude/hooks.js +44 -4
  61. package/dist/claude/hooks.js.map +1 -1
  62. package/dist/claude/shim-template.d.ts.map +1 -1
  63. package/dist/claude/shim-template.js +45 -15
  64. package/dist/claude/shim-template.js.map +1 -1
  65. package/dist/claude/state.d.ts +5 -0
  66. package/dist/claude/state.d.ts.map +1 -1
  67. package/dist/claude/state.js.map +1 -1
  68. package/dist/cli-picker.d.ts +44 -5
  69. package/dist/cli-picker.d.ts.map +1 -1
  70. package/dist/cli-picker.js +73 -26
  71. package/dist/cli-picker.js.map +1 -1
  72. package/dist/cli.js +270 -14
  73. package/dist/cli.js.map +1 -1
  74. package/dist/context/build.d.ts +8 -0
  75. package/dist/context/build.d.ts.map +1 -1
  76. package/dist/context/build.js +57 -9
  77. package/dist/context/build.js.map +1 -1
  78. package/dist/context/check.d.ts.map +1 -1
  79. package/dist/context/check.js +7 -6
  80. package/dist/context/check.js.map +1 -1
  81. package/dist/context/node-file.d.ts.map +1 -1
  82. package/dist/context/node-file.js +9 -2
  83. package/dist/context/node-file.js.map +1 -1
  84. package/dist/graph/bindings.d.ts.map +1 -1
  85. package/dist/graph/bindings.js +245 -2
  86. package/dist/graph/bindings.js.map +1 -1
  87. package/dist/graph/build.d.ts.map +1 -1
  88. package/dist/graph/build.js +36 -16
  89. package/dist/graph/build.js.map +1 -1
  90. package/dist/graph/check.d.ts +4 -0
  91. package/dist/graph/check.d.ts.map +1 -1
  92. package/dist/graph/check.js +8 -1
  93. package/dist/graph/check.js.map +1 -1
  94. package/dist/graph/container.d.ts +63 -0
  95. package/dist/graph/container.d.ts.map +1 -0
  96. package/dist/graph/container.js +185 -0
  97. package/dist/graph/container.js.map +1 -0
  98. package/dist/graph/enrich.d.ts +18 -0
  99. package/dist/graph/enrich.d.ts.map +1 -1
  100. package/dist/graph/enrich.js +55 -2
  101. package/dist/graph/enrich.js.map +1 -1
  102. package/dist/graph/extract.d.ts +8 -1
  103. package/dist/graph/extract.d.ts.map +1 -1
  104. package/dist/graph/extract.js +401 -12
  105. package/dist/graph/extract.js.map +1 -1
  106. package/dist/graph/generic.d.ts +34 -1
  107. package/dist/graph/generic.d.ts.map +1 -1
  108. package/dist/graph/generic.js +176 -6
  109. package/dist/graph/generic.js.map +1 -1
  110. package/dist/graph/invariants.d.ts +32 -0
  111. package/dist/graph/invariants.d.ts.map +1 -0
  112. package/dist/graph/invariants.js +49 -0
  113. package/dist/graph/invariants.js.map +1 -0
  114. package/dist/graph/queries/clojure.scm +58 -0
  115. package/dist/graph/resolve.d.ts.map +1 -1
  116. package/dist/graph/resolve.js +249 -23
  117. package/dist/graph/resolve.js.map +1 -1
  118. package/dist/graph/scopes.d.ts +2 -2
  119. package/dist/graph/scopes.d.ts.map +1 -1
  120. package/dist/graph/scopes.js +8 -6
  121. package/dist/graph/scopes.js.map +1 -1
  122. package/dist/graph/source-files.d.ts +13 -4
  123. package/dist/graph/source-files.d.ts.map +1 -1
  124. package/dist/graph/source-files.js +35 -12
  125. package/dist/graph/source-files.js.map +1 -1
  126. package/dist/graph/types.d.ts +3 -1
  127. package/dist/graph/types.d.ts.map +1 -1
  128. package/dist/graph/workspace-cli.d.ts +2 -0
  129. package/dist/graph/workspace-cli.d.ts.map +1 -1
  130. package/dist/graph/workspace-cli.js +10 -4
  131. package/dist/graph/workspace-cli.js.map +1 -1
  132. package/dist/graph/workspace.d.ts +11 -6
  133. package/dist/graph/workspace.d.ts.map +1 -1
  134. package/dist/graph/workspace.js +24 -13
  135. package/dist/graph/workspace.js.map +1 -1
  136. package/dist/graph/write.d.ts.map +1 -1
  137. package/dist/graph/write.js +25 -2
  138. package/dist/graph/write.js.map +1 -1
  139. package/dist/hosts/antigravity.d.ts +7 -0
  140. package/dist/hosts/antigravity.d.ts.map +1 -0
  141. package/dist/hosts/antigravity.js +42 -0
  142. package/dist/hosts/antigravity.js.map +1 -0
  143. package/dist/hosts/codex-hooks.d.ts +3 -3
  144. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  145. package/dist/hosts/codex-hooks.js +27 -21
  146. package/dist/hosts/codex-hooks.js.map +1 -1
  147. package/dist/hosts/init.d.ts.map +1 -1
  148. package/dist/hosts/init.js +6 -1
  149. package/dist/hosts/init.js.map +1 -1
  150. package/dist/hosts/mcp-config.d.ts.map +1 -1
  151. package/dist/hosts/mcp-config.js +7 -0
  152. package/dist/hosts/mcp-config.js.map +1 -1
  153. package/dist/hosts/plan.d.ts +1 -1
  154. package/dist/hosts/plan.d.ts.map +1 -1
  155. package/dist/hosts/plan.js +2 -0
  156. package/dist/hosts/plan.js.map +1 -1
  157. package/dist/hosts/registry.d.ts.map +1 -1
  158. package/dist/hosts/registry.js +13 -0
  159. package/dist/hosts/registry.js.map +1 -1
  160. package/dist/ingest/fs.d.ts +9 -2
  161. package/dist/ingest/fs.d.ts.map +1 -1
  162. package/dist/ingest/fs.js +100 -16
  163. package/dist/ingest/fs.js.map +1 -1
  164. package/dist/mcp/server.d.ts.map +1 -1
  165. package/dist/mcp/server.js +45 -2
  166. package/dist/mcp/server.js.map +1 -1
  167. package/dist/telemetry/contract.d.ts +90 -0
  168. package/dist/telemetry/contract.d.ts.map +1 -0
  169. package/dist/telemetry/contract.js +203 -0
  170. package/dist/telemetry/contract.js.map +1 -0
  171. package/dist/telemetry/flush.d.ts +21 -0
  172. package/dist/telemetry/flush.d.ts.map +1 -0
  173. package/dist/telemetry/flush.js +84 -0
  174. package/dist/telemetry/flush.js.map +1 -0
  175. package/dist/telemetry/gate.d.ts +16 -0
  176. package/dist/telemetry/gate.d.ts.map +1 -0
  177. package/dist/telemetry/gate.js +64 -0
  178. package/dist/telemetry/gate.js.map +1 -0
  179. package/dist/telemetry/identity.d.ts +33 -0
  180. package/dist/telemetry/identity.d.ts.map +1 -0
  181. package/dist/telemetry/identity.js +80 -0
  182. package/dist/telemetry/identity.js.map +1 -0
  183. package/dist/telemetry/index.d.ts +18 -0
  184. package/dist/telemetry/index.d.ts.map +1 -0
  185. package/dist/telemetry/index.js +16 -0
  186. package/dist/telemetry/index.js.map +1 -0
  187. package/dist/telemetry/key.d.ts +21 -0
  188. package/dist/telemetry/key.d.ts.map +1 -0
  189. package/dist/telemetry/key.js +49 -0
  190. package/dist/telemetry/key.js.map +1 -0
  191. package/dist/telemetry/notice.d.ts +19 -0
  192. package/dist/telemetry/notice.d.ts.map +1 -0
  193. package/dist/telemetry/notice.js +90 -0
  194. package/dist/telemetry/notice.js.map +1 -0
  195. package/dist/telemetry/queue.d.ts +35 -0
  196. package/dist/telemetry/queue.d.ts.map +1 -0
  197. package/dist/telemetry/queue.js +162 -0
  198. package/dist/telemetry/queue.js.map +1 -0
  199. package/dist/telemetry/send.d.ts +19 -0
  200. package/dist/telemetry/send.d.ts.map +1 -0
  201. package/dist/telemetry/send.js +84 -0
  202. package/dist/telemetry/send.js.map +1 -0
  203. package/dist/telemetry/sessions.d.ts +8 -0
  204. package/dist/telemetry/sessions.d.ts.map +1 -0
  205. package/dist/telemetry/sessions.js +81 -0
  206. package/dist/telemetry/sessions.js.map +1 -0
  207. package/dist/telemetry/track.d.ts +43 -0
  208. package/dist/telemetry/track.d.ts.map +1 -0
  209. package/dist/telemetry/track.js +108 -0
  210. package/dist/telemetry/track.js.map +1 -0
  211. package/dist/upkeep-run.d.ts +15 -0
  212. package/dist/upkeep-run.d.ts.map +1 -0
  213. package/dist/upkeep-run.js +60 -0
  214. package/dist/upkeep-run.js.map +1 -0
  215. package/dist/upkeep.d.ts +114 -0
  216. package/dist/upkeep.d.ts.map +1 -0
  217. package/dist/upkeep.js +232 -0
  218. package/dist/upkeep.js.map +1 -0
  219. package/dist/util/num.d.ts +3 -0
  220. package/dist/util/num.d.ts.map +1 -0
  221. package/dist/util/num.js +20 -0
  222. package/dist/util/num.js.map +1 -0
  223. package/dist/util/state.d.ts +11 -4
  224. package/dist/util/state.d.ts.map +1 -1
  225. package/dist/util/state.js +9 -0
  226. package/dist/util/state.js.map +1 -1
  227. package/dist/viewer/app.js +1 -1
  228. package/dist/viewer/index.html +1 -0
  229. package/dist/viewer/style.css +23 -2
  230. package/dist/viz/assemble.d.ts +33 -0
  231. package/dist/viz/assemble.d.ts.map +1 -1
  232. package/dist/viz/assemble.js.map +1 -1
  233. package/dist/viz/export.d.ts +39 -0
  234. package/dist/viz/export.d.ts.map +1 -0
  235. package/dist/viz/export.js +98 -0
  236. package/dist/viz/export.js.map +1 -0
  237. package/package.json +15 -5
  238. package/scripts/stamp-telemetry-key.mjs +53 -0
package/dist/ask/fuse.js CHANGED
@@ -1,27 +1,44 @@
1
1
  /**
2
- * Scope-aware ranking for `graft ask` — per-scope ranking + reciprocal-rank
3
- * fusion (RRF). Pure functions, no fs.
2
+ * Scope-aware ranking for `graft ask`. Pure functions, no fs.
4
3
  *
5
4
  * The problem: lexical + graph scores are corpus-relative. In a multi-scope
6
5
  * repo (a monorepo's `frontend/` + `backend/`, sub-projects under one root),
7
- * pooling every node into one ranking lets the biggest sub-project set the
8
- * score scale and drown the small one — its IDF, its BM25 length prior, its
9
- * PageRank mass. The fix: rank each scope against ITS OWN corpus (per-scope
10
- * IDF/BM25/walk, done by the caller), then fuse the per-scope ORDERINGS here
11
- * with RRF — rank positions are comparable across corpora where raw scores
12
- * are not, so the tiny scope's best hit lands next to the huge scope's best.
6
+ * scoring every node against one pooled corpus lets the biggest sub-project set
7
+ * the score scale and drown the small one.
8
+ *
9
+ * This module solves it by keeping the scope PARTITION — each scope is scored
10
+ * and walked separately — while making the resulting scores COMPARABLE, so the
11
+ * combined order can be read straight off the score. Comparability has two
12
+ * requirements, and both must hold or the order is meaningless:
13
+ *
14
+ * 1. shared corpus statistics — the caller scores every scope against the
15
+ * repo-wide IDF and length prior (see `ask.ts`), so the same term is worth
16
+ * the same everywhere;
17
+ * 2. a shared normalization denominator — {@link rankScopesAndFuse} divides
18
+ * every scope by the best raw score in the REPO, not by each scope's own
19
+ * best (see `globalMaxLex` there).
20
+ *
21
+ * Issue #117 was the consequence of neither holding. Each scope divided by its
22
+ * own maximum, so a barely-relevant scope's top hit normalized to ~1.0 exactly
23
+ * like the genuinely relevant scope's top hit; RRF then fused by RANK, which
24
+ * discards magnitude outright, so the weak scope's rank-1 tied the strong
25
+ * scope's rank-1 and displaced its rank-2 — frequently the answer. Measurement
26
+ * across four repositories and four ecosystems put 73% of missed files in that
27
+ * bucket. RRF is retained for `federateAsk` (see `graph/workspace.ts`), where
28
+ * the corpora really are separate repositories and no shared denominator
29
+ * exists — that is the case rank fusion is for.
13
30
  *
14
31
  * Soft federation: a scope whose top hit is probably an incidental word
15
- * collision, not a second home for the query, is left out of the fused order
32
+ * collision, not a second home for the query, is left out of the combined order
16
33
  * and reported in `alsoMatched` instead, so the output can say "also matched:
17
- * docs/ — narrow with --in docs/" rather than diluting the pack. The REAL
18
- * participation gate — used by both `rankScopesAndFuse` here and `federateAsk`
19
- * (workspace federation) — is the match-STRENGTH signal in {@link
20
- * STRONG_FLOOR}/{@link HIGH_FLOOR}: a scope's top hit must have matched a
21
- * query term in a NAME/PATH field, or have broad enough overall coverage to be
22
- * real even body-only. `fuseScopes`'s own {@link PARTICIPATION_RATIO} gate is
23
- * a separate, cruder ratio check that still runs internally as a
24
- * post-normalization safety net, not the primary defense.
34
+ * docs/ — narrow with --in docs/" rather than diluting the pack. With
35
+ * comparable scores that gate is simply {@link PARTICIPATION_RATIO} — a scope
36
+ * must reach a real share of the best score anywhere in the repo. Under
37
+ * per-scope normalization that check was vacuous (every scope's top was ~1.0),
38
+ * which is why a separate match-STRENGTH gate ({@link STRONG_FLOOR}/{@link
39
+ * HIGH_FLOOR}) had to stand in for it; that signal is still exported and still
40
+ * used by `federateAsk`, which normalizes per child repo and therefore still
41
+ * needs it.
25
42
  */
26
43
  /** RRF smoothing constant — the standard value from the original RRF paper;
27
44
  * high enough that rank 1 vs 2 differ gently rather than by 2×. */
@@ -135,25 +152,99 @@ export function fuseScopes(docs) {
135
152
  ranked.sort((a, b) => b.score - a.score || a.scope.localeCompare(b.scope) || a.id.localeCompare(b.id));
136
153
  return { ranked, federated, alsoMatched };
137
154
  }
155
+ /**
156
+ * Combine scopes whose scores are ALREADY comparable (shared corpus statistics
157
+ * and a shared normalization denominator — see the module header). Same shape
158
+ * and same gate as {@link fuseScopes}, but the combined order is read straight
159
+ * off the score instead of from reciprocal rank, because with a common scale
160
+ * the magnitude is exactly the signal rank fusion would throw away.
161
+ *
162
+ * Only {@link rankScopesAndFuse} may call this. `federateAsk` fuses separate
163
+ * repositories, which have no common denominator, and must keep using
164
+ * {@link fuseScopes}.
165
+ */
166
+ export function combineComparableScopes(docs) {
167
+ const byScope = new Map();
168
+ for (const d of docs) {
169
+ if (d.score <= 0)
170
+ continue;
171
+ const list = byScope.get(d.scope);
172
+ if (list)
173
+ list.push(d);
174
+ else
175
+ byScope.set(d.scope, [d]);
176
+ }
177
+ for (const list of byScope.values())
178
+ list.sort(byScore);
179
+ if (byScope.size === 0)
180
+ return { ranked: [], federated: [], alsoMatched: [] };
181
+ // One scoring scope: pass through untouched. Identical to the single-scope
182
+ // path by construction — the guarantee that a single-scope repo cannot move.
183
+ if (byScope.size === 1) {
184
+ const [scope, list] = byScope.entries().next().value;
185
+ return {
186
+ ranked: list.map((d) => ({ id: d.id, scope, score: d.score })),
187
+ federated: [scope],
188
+ alsoMatched: [],
189
+ };
190
+ }
191
+ // Gate on a real share of the best score in the repo. This is the same
192
+ // ratio check `fuseScopes` runs, but here it MEANS something: the scores
193
+ // being compared share a denominator.
194
+ const scopesByBest = [...byScope.entries()].sort((a, b) => b[1][0].score - a[1][0].score || a[0].localeCompare(b[0]));
195
+ const gate = PARTICIPATION_RATIO * scopesByBest[0][1][0].score;
196
+ const federated = [];
197
+ const alsoMatched = [];
198
+ for (const [scope, list] of scopesByBest) {
199
+ if (list[0].score >= gate)
200
+ federated.push(scope);
201
+ else
202
+ alsoMatched.push({ scope, bestId: list[0].id });
203
+ }
204
+ // An id can only belong to one scope (scope is a path prefix), but keep the
205
+ // defensive max so a duplicate cannot double-count or flip its attribution.
206
+ const acc = new Map();
207
+ for (const scope of federated) {
208
+ for (const d of byScope.get(scope)) {
209
+ const prev = acc.get(d.id);
210
+ if (!prev || d.score > prev.score)
211
+ acc.set(d.id, { scope, score: d.score });
212
+ }
213
+ }
214
+ let max = 0;
215
+ for (const e of acc.values())
216
+ if (e.score > max)
217
+ max = e.score;
218
+ const ranked = [...acc.entries()].map(([id, e]) => ({
219
+ id,
220
+ scope: e.scope,
221
+ score: max > 0 ? e.score / max : 0,
222
+ }));
223
+ ranked.sort((a, b) => b.score - a.score || a.scope.localeCompare(b.scope) || a.id.localeCompare(b.id));
224
+ return { ranked, federated, alsoMatched };
225
+ }
138
226
  /**
139
227
  * Multi-scope orchestration for `ask`'s symbol ranking: for each scope, score
140
- * lexically, gate by match STRENGTH (not a lenient ratio), walk the scope's
141
- * subgraph, blend exactly like the single-scope path (`lexN +
142
- * graphWeight·pr`, both per-scope-normalized; walk-rescued nodes join above
143
- * `rescueFloor`), then fuse the surviving scopes' blended lists with
144
- * {@link fuseScopes}. A scope with zero lexical matches contributes nothing
145
- * (no seeds → no walk → no docs).
228
+ * lexically (against the repo-wide statistics the caller supplies), walk the
229
+ * scope's subgraph, blend exactly like the single-scope path (`lexN +
230
+ * graphWeight·pr`, walk-rescued nodes joining above `rescueFloor`), then
231
+ * combine the scopes with {@link combineComparableScopes}. A scope with zero
232
+ * lexical matches contributes nothing (no seeds → no walk → no docs).
146
233
  *
147
- * The gate is the SAME two-floor signal `federateAsk` (workspace federation,
148
- * `src/graph/workspace.ts`) uses: a scope participates iff its top hit's
149
- * `coverageStrong` ≥ {@link STRONG_FLOOR} OR its `coverage` ≥
150
- * {@link HIGH_FLOOR}. It is evaluated here, on each scope's raw-lex top doc,
151
- * BEFORE normalization/walk/blend — gated-out scopes never enter the
152
- * walk/blend/fuse pipeline at all, they go straight to `alsoMatched` with
153
- * their best raw-lex doc id. (`fuseScopes`'s own internal ratio gate still
154
- * runs on the survivors' already-normalized input afterward, but since every
155
- * survivor's blended top is ~1.0 by construction, that gate is now just a
156
- * no-op safety net rather than the real gate — the real gate is this one.)
234
+ * The one thing that differs from the single-scope path: `lexN` divides by
235
+ * `globalMaxLex`, the best raw lexical score across ALL scopes, rather than by
236
+ * each scope's own best. That shared denominator is what makes the blended
237
+ * scores comparable, and therefore what lets the combined order be read off the
238
+ * score. Dividing per scope — as this did before #117 — pinned every scope's
239
+ * top hit to ~1.0 regardless of how well it matched, which is the defect.
240
+ *
241
+ * Participation is then the {@link PARTICIPATION_RATIO} share check inside
242
+ * {@link combineComparableScopes}: a scope must reach a real fraction of the
243
+ * best score anywhere in the repo, else it is reported in `alsoMatched` for
244
+ * `--in`. The match-STRENGTH floors ({@link STRONG_FLOOR}/{@link HIGH_FLOOR})
245
+ * are no longer applied here — they existed to stand in for a share check that
246
+ * per-scope normalization had made vacuous. `federateAsk` still normalizes per
247
+ * child repo, so it still gates on strength; see `src/graph/workspace.ts`.
157
248
  */
158
249
  export function rankScopesAndFuse(scopes, ops, graphWeight, rescueFloor) {
159
250
  // Pass 1: lexical scoring only, per scope.
@@ -166,9 +257,10 @@ export function rankScopesAndFuse(scopes, ops, graphWeight, rescueFloor) {
166
257
  }
167
258
  const alsoMatched = [];
168
259
  const scoped = [];
169
- // Per-scope raw best + strength, computed once so we can pick survivors and,
170
- // if the gate would exclude EVERY scope, rescue the strongest one (below).
260
+ // Per-scope raw best, plus the best raw score anywhere in the repo. That
261
+ // repo-wide maximum is the shared denominator every scope normalizes by.
171
262
  const meta = new Map();
263
+ let globalMaxLex = 0;
172
264
  for (const [scope, lex] of lexByScope) {
173
265
  let maxLex = 0, bestId = "";
174
266
  for (const [id, v] of lex)
@@ -178,52 +270,58 @@ export function rankScopesAndFuse(scopes, ops, graphWeight, rescueFloor) {
178
270
  }
179
271
  if (maxLex <= 0)
180
272
  continue;
181
- const { coverage, coverageStrong } = ops.strength(scope, bestId);
182
- // strength is the scale-INVARIANT [0,1] match share (name vs name+path+body);
183
- // used to pick the rescue scope fairly across corpora, unlike raw maxLex.
184
- meta.set(scope, { lex, maxLex, bestId, strength: Math.max(coverage, coverageStrong), passes: coverageStrong >= STRONG_FLOOR || coverage >= HIGH_FLOOR });
273
+ meta.set(scope, { lex, maxLex, bestId });
274
+ if (maxLex > globalMaxLex)
275
+ globalMaxLex = maxLex;
276
+ }
277
+ // Body-only suppression. With a shared denominator a weak scope simply
278
+ // scores low, so participation is decided on score in
279
+ // `combineComparableScopes` — no scope is excluded on a share threshold any
280
+ // more. The single exception is a scope in which NO doc matched a query term
281
+ // in a name or path while some other scope did: that is the comment-banner
282
+ // collision (see `hasIdentifierMatch` above), and score cannot separate it
283
+ // from a real match. Only applied when a genuine identifier match exists
284
+ // somewhere, so a query that legitimately matches only prose still returns
285
+ // something.
286
+ const bodyOnly = new Set();
287
+ if (ops.hasIdentifierMatch) {
288
+ const named = new Map();
289
+ for (const scope of meta.keys())
290
+ named.set(scope, ops.hasIdentifierMatch(scope));
291
+ if ([...named.values()].some(Boolean)) {
292
+ for (const [scope, ok] of named)
293
+ if (!ok)
294
+ bodyOnly.add(scope);
295
+ }
185
296
  }
186
- const survivors = [...meta.entries()].filter(([, m]) => m.passes).map(([s]) => s);
187
- // Anti-abstain rescue: the strength gate is a SHARE of the whole query, so a
188
- // long/verbose query (a pasted issue body) can push every scope below the
189
- // floor and make `ask` return nothing — measurably worse than grep. When the
190
- // gate would exclude every scope yet real matches exist, keep the single
191
- // strongest scope so `ask` returns best-effort hits (the escalation nudge
192
- // already tells the agent to switch to grep if they look weak) instead of
193
- // abstaining. The gate still does its real job — suppressing a weak scope
194
- // beside a strong one — whenever any scope passes.
195
- if (survivors.length === 0 && meta.size > 0) {
196
- // Tiebreak on scale-invariant match STRENGTH (not raw per-corpus maxLex,
197
- // which would let the biggest scope's numerically-larger score win — the very
198
- // cross-corpus scale bias RRF exists to avoid). Fall back to maxLex only when
199
- // strength ties (e.g. all zero), then scope name for determinism.
200
- survivors.push([...meta.entries()].sort((a, b) => b[1].strength - a[1].strength || b[1].maxLex - a[1].maxLex || a[0].localeCompare(b[0]))[0][0]);
297
+ for (const scope of bodyOnly) {
298
+ alsoMatched.push({ scope, bestId: meta.get(scope).bestId });
299
+ meta.delete(scope);
201
300
  }
202
- for (const [scope, m] of meta)
203
- if (!survivors.includes(scope))
204
- alsoMatched.push({ scope, bestId: m.bestId });
205
- for (const scope of survivors) {
206
- const { lex, maxLex } = meta.get(scope);
301
+ // The denominator must come from the scopes that actually rank, or a
302
+ // suppressed scope could still set the scale for everyone else.
303
+ globalMaxLex = 0;
304
+ for (const { maxLex } of meta.values())
305
+ if (maxLex > globalMaxLex)
306
+ globalMaxLex = maxLex;
307
+ for (const [scope, { lex }] of meta) {
207
308
  const pr = ops.walk(scope, lex);
208
309
  const candidates = new Set(lex.keys());
209
310
  for (const [id, p] of pr)
210
311
  if (p >= rescueFloor)
211
312
  candidates.add(id);
212
313
  for (const id of candidates) {
213
- const lexN = maxLex > 0 ? (lex.get(id) ?? 0) / maxLex : 0;
314
+ const lexN = globalMaxLex > 0 ? (lex.get(id) ?? 0) / globalMaxLex : 0;
214
315
  const blended = (lexN + graphWeight * (pr.get(id) ?? 0)) * (ops.rankFactor?.(scope, id) ?? 1);
215
316
  if (blended > 0)
216
317
  scoped.push({ id, scope, score: blended });
217
318
  }
218
319
  }
219
- // fuseScopes's own gate still runs on this (already-survivor, already-
220
- // normalized) input — left as-is per its pure-test contract; it's now
221
- // effectively a no-op safety net rather than the real gate.
222
- const fused = fuseScopes(scoped);
320
+ const combined = combineComparableScopes(scoped);
223
321
  return {
224
- ranked: fused.ranked,
225
- federated: fused.federated,
226
- alsoMatched: [...alsoMatched, ...fused.alsoMatched],
322
+ ranked: combined.ranked,
323
+ federated: combined.federated,
324
+ alsoMatched: [...alsoMatched, ...combined.alsoMatched],
227
325
  };
228
326
  }
229
327
  //# sourceMappingURL=fuse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/ask/fuse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fuse.js","sourceRoot":"","sources":["../../src/ask/fuse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export type BlastFormat = "text" | "markdown" | "mermaid" | "json";
2
+ export interface BlastCliOptions {
3
+ /** Base ref to diff against (`origin/main`); omitted → working tree vs HEAD. */
4
+ base?: string;
5
+ /** Raw `--depth`: a positive integer, or `all`/`full`/`max`. Default 2. */
6
+ depth?: string;
7
+ format?: string;
8
+ /** Ask a model to name the clusters that have no concept name (one call, cached).
9
+ * Opt-in: a local `graft blast` must not need a key or a network round-trip. */
10
+ name?: boolean;
11
+ /** Write the interactive page for this radius here (one self-contained file). */
12
+ exportViz?: string;
13
+ /** Subtitle beside the repo name on the exported page, e.g. "PR #171". Same
14
+ * meaning as `graft viz --title`. Without it a reader of a published page has no
15
+ * way to tell which pull request they are looking at. */
16
+ title?: string;
17
+ /** The top-level `--dir` override. */
18
+ globalDir?: string;
19
+ }
20
+ export declare function runBlastCommand(dir: string, opts: BlastCliOptions): Promise<void>;
21
+ /**
22
+ * What to call the repository in the appbar.
23
+ *
24
+ * `basename(root)` is right for someone running this in their own clone, but CI
25
+ * checks a pull request out into a directory named for the job — ours is literally
26
+ * `pr` — which titled the published page "pr". The origin remote is the repository,
27
+ * so it wins when there is one; a detached tarball checkout still falls back.
28
+ */
29
+ export declare function repoLabel(root: string): string;
30
+ //# sourceMappingURL=blast-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blast-cli.d.ts","sourceRoot":"","sources":["../../src/blast/blast-cli.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;oFACgF;IAChF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;6DAEyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAuBD,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CvF;AAsCD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY9C"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * CLI wiring for `graft blast` — the command a CI job runs on a pull request.
3
+ *
4
+ * Kept out of cli.ts (argument wiring only) so the diff → seeds → walk → render
5
+ * chain stays unit-testable without shelling out, matching `graph/traverse-cli.ts`.
6
+ */
7
+ import { execFileSync } from "node:child_process";
8
+ import { basename, resolve } from "node:path";
9
+ import { contextDirFor } from "../context/node-file.js";
10
+ import { loadGraphCached } from "../graph/load.js";
11
+ import { blastRadiusIn } from "./blast.js";
12
+ import { changedFiles, refExists } from "./diff.js";
13
+ import { markdownReport, mermaidDiagram, textReport } from "./render.js";
14
+ const DEFAULT_DEPTH = 2;
15
+ function resolveFormat(raw) {
16
+ if (raw === undefined)
17
+ return "text";
18
+ if (raw === "text" || raw === "markdown" || raw === "mermaid" || raw === "json")
19
+ return raw;
20
+ console.error(`✗ --format must be text, markdown, mermaid or json, got "${raw}"`);
21
+ process.exit(1);
22
+ }
23
+ /** Same grammar as `graft callers --depth`, so the two commands stay learnable together. */
24
+ function resolveDepth(raw) {
25
+ if (raw === undefined)
26
+ return DEFAULT_DEPTH;
27
+ if (/^(all|full|max)$/i.test(raw))
28
+ return Number.POSITIVE_INFINITY;
29
+ const d = Number(raw);
30
+ if (!Number.isFinite(d) || d < 1) {
31
+ console.error(`✗ --depth must be a positive number or "all", got "${raw}"`);
32
+ process.exit(1);
33
+ }
34
+ return Math.floor(d);
35
+ }
36
+ export async function runBlastCommand(dir, opts) {
37
+ const root = resolve(dir);
38
+ const contextDir = contextDirFor(root, opts.globalDir);
39
+ const format = resolveFormat(opts.format);
40
+ const depth = resolveDepth(opts.depth);
41
+ const graph = loadGraphCached(contextDir);
42
+ if (!graph) {
43
+ console.error(`✗ no graph found at ${contextDir} — run \`graft build\` first`);
44
+ process.exit(1);
45
+ }
46
+ // An unknown base is the most common CI misconfiguration by a distance: an
47
+ // `actions/checkout` without `fetch-depth: 0` leaves the base branch absent, and
48
+ // git's own message ("unknown revision or path not in the working tree") sends
49
+ // people looking for a typo instead of the checkout depth.
50
+ if (opts.base !== undefined && !refExists(root, opts.base)) {
51
+ console.error(`✗ base ref "${opts.base}" is not in this checkout.\n` +
52
+ " In CI, fetch enough history for the merge base: actions/checkout with `fetch-depth: 0`.");
53
+ process.exit(1);
54
+ }
55
+ const diff = changedFiles(root, opts.base);
56
+ if (!diff) {
57
+ console.error(`✗ could not read a diff in ${root} — is this a git repository?`);
58
+ process.exit(1);
59
+ }
60
+ const report = blastRadiusIn(graph, contextDir, diff.files, diff.basis, depth);
61
+ if (opts.name)
62
+ await nameClusters(graph, report, contextDir);
63
+ if (opts.exportViz)
64
+ await exportRadius(report, contextDir, root, opts.exportViz, opts.title);
65
+ if (format === "json") {
66
+ console.log(JSON.stringify(report, null, 2));
67
+ return;
68
+ }
69
+ if (format === "mermaid") {
70
+ const diagram = mermaidDiagram(report);
71
+ // Exit 0 with a comment, not an error: "nothing depends on this diff" is a
72
+ // legitimate answer, and a CI step must not fail on it.
73
+ console.log(diagram ?? "%% no dependents to draw");
74
+ return;
75
+ }
76
+ process.stdout.write(format === "markdown" ? markdownReport(report) : textReport(report));
77
+ }
78
+ /**
79
+ * Name the clusters left on their symbol backstop, then report what it cost.
80
+ *
81
+ * Everything here is best-effort by construction: no key, a spent quota or a
82
+ * refused call leaves the backstop labels in place and the command still exits 0,
83
+ * because a PR check must not fail over a cosmetic layer.
84
+ */
85
+ async function nameClusters(graph, report, contextDir) {
86
+ const { applyNames, ChatNamer } = await import("./name.js");
87
+ const { resolveConfig } = await import("../ai/providers.js");
88
+ const cfg = resolveConfig({ contextDir });
89
+ let namer;
90
+ if (cfg.chatModel) {
91
+ namer = new ChatNamer(cfg.chatModel);
92
+ }
93
+ else if (cfg.apiKey) {
94
+ const { createChatModel } = await import("../ai/llm/factory.js");
95
+ namer = new ChatNamer(createChatModel({
96
+ provider: cfg.provider, apiKey: cfg.apiKey, model: cfg.model,
97
+ baseUrl: cfg.baseUrl, headers: cfg.headers,
98
+ }));
99
+ }
100
+ const stats = await applyNames(graph, report, { namer, contextDir });
101
+ if (!namer) {
102
+ console.error("• --name: no API key (GRAFT_API_KEY), so areas keep their symbol names");
103
+ return;
104
+ }
105
+ if (stats.error)
106
+ console.error(`• --name: naming failed (${stats.error}) — areas keep their symbol names`);
107
+ else if (stats.named + stats.cached + stats.declined > 0) {
108
+ const bits = [`${stats.named} named`, `${stats.cached} cached`];
109
+ if (stats.declined > 0)
110
+ bits.push(`${stats.declined} left as symbols (mixed)`);
111
+ console.error(`• --name: ${bits.join(", ")}`);
112
+ }
113
+ }
114
+ /**
115
+ * What to call the repository in the appbar.
116
+ *
117
+ * `basename(root)` is right for someone running this in their own clone, but CI
118
+ * checks a pull request out into a directory named for the job — ours is literally
119
+ * `pr` — which titled the published page "pr". The origin remote is the repository,
120
+ * so it wins when there is one; a detached tarball checkout still falls back.
121
+ */
122
+ export function repoLabel(root) {
123
+ try {
124
+ const url = execFileSync("git", ["-C", root, "remote", "get-url", "origin"], {
125
+ encoding: "utf8",
126
+ stdio: ["ignore", "pipe", "ignore"],
127
+ }).trim();
128
+ const name = url.replace(/\.git$/, "").split(/[/:]/).pop();
129
+ if (name)
130
+ return name;
131
+ }
132
+ catch {
133
+ // No remote, or no git at all: the directory name is the best guess left.
134
+ }
135
+ return basename(root);
136
+ }
137
+ /**
138
+ * Write the interactive page for this radius: the same viewer `graft viz` serves,
139
+ * with the blast graph as its Context tab.
140
+ *
141
+ * Done here rather than in `viz` because the radius is what a reviewer opened the
142
+ * link for, and only `blast` has it — `viz --export` on its own can offer the deep
143
+ * tier's concept map, which a PR build no longer produces.
144
+ */
145
+ async function exportRadius(report, contextDir, root, outDir, subtitle) {
146
+ const { resolve: resolvePath } = await import("node:path");
147
+ const { fileURLToPath } = await import("node:url");
148
+ const { exportViz } = await import("../viz/export.js");
149
+ const { blastVizGraph } = await import("./viz.js");
150
+ const out = exportViz({
151
+ contextDir,
152
+ viewerDir: fileURLToPath(new URL("../viewer/", import.meta.url)), // prebuilt, ships in dist
153
+ outDir: resolvePath(outDir),
154
+ repoName: repoLabel(root),
155
+ subtitle,
156
+ contextGraph: blastVizGraph(report, { root }),
157
+ // Context alone: see VizExportOptions.tabs — the other two tabs are about the
158
+ // repository, and this page is about one pull request.
159
+ tabs: ["context"],
160
+ });
161
+ console.error(`• --export-viz: ${out.file} (${Math.round(out.bytes / 1024)} kB, ${out.contextNodes} areas, ${out.codeNodes} code nodes)`);
162
+ }
163
+ //# sourceMappingURL=blast-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blast-cli.js","sourceRoot":"","sources":["../../src/blast/blast-cli.ts"],"names":[],"mappings":"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"}