pi-mega-compact 0.8.24 → 0.8.26

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 (111) hide show
  1. package/README.md +26 -0
  2. package/dist/extensions/mega-compact-s38.test.js +263 -14
  3. package/dist/extensions/mega-compact.js +15 -0
  4. package/dist/extensions/mega-config.js +3 -0
  5. package/dist/extensions/mega-events/agent-handlers.js +211 -26
  6. package/dist/extensions/mega-events/context-handler.js +45 -7
  7. package/dist/extensions/mega-events/error-classifier.js +125 -18
  8. package/dist/extensions/mega-pipeline/compact.js +24 -13
  9. package/dist/extensions/mega-pipeline/recall.js +31 -2
  10. package/dist/extensions/mega-runtime/dashboard-snapshot.js +4 -0
  11. package/dist/extensions/mega-runtime/runtime-snapshot.js +4 -0
  12. package/dist/extensions/mega-runtime/runtime.js +58 -5
  13. package/dist/src/boundary.js +79 -43
  14. package/dist/src/boundary.test.js +119 -2
  15. package/dist/src/canary.js +10 -0
  16. package/dist/src/config/dedup.js +14 -0
  17. package/dist/src/config.js +3 -1
  18. package/dist/src/dedup/raptor/buildHistory.js +164 -0
  19. package/dist/src/dedup/raptor/buildHistory.test.js +292 -0
  20. package/dist/src/dedup/raptor/index.js +38 -0
  21. package/dist/src/dedup/raptor/multilevel-serve.test.js +229 -0
  22. package/dist/src/dedup/raptor/multilevel.js +17 -5
  23. package/dist/src/dedup/raptor/multilevel.test.js +36 -1
  24. package/dist/src/dedup/raptor/raptor.test.js +43 -0
  25. package/dist/src/dedup/raptor/retrieval.js +14 -2
  26. package/dist/src/dedup/raptor/retrieval.test.js +95 -0
  27. package/dist/src/dedup/raptor/serve-gate.test.js +298 -0
  28. package/dist/src/dedup/raptor/summarizer.js +1 -0
  29. package/dist/src/dedup/raptor/tree.js +16 -2
  30. package/dist/src/engine.js +18 -2
  31. package/dist/src/httpEmbedder.js +96 -6
  32. package/dist/src/httpEmbedder.test.js +277 -0
  33. package/dist/src/mechanical-fix.test.js +65 -0
  34. package/dist/src/raptor-inject-summaries.test.js +162 -0
  35. package/dist/src/recall.js +153 -24
  36. package/dist/src/recall.test.js +179 -4
  37. package/dist/src/store/sqlite/dedup-mirror.js +32 -15
  38. package/dist/src/store/sqlite/maintenance.js +2 -2
  39. package/dist/src/store/sqlite/mechanical-fix.test.js +146 -0
  40. package/dist/src/store/sqlite/memories.js +5 -5
  41. package/dist/src/store/sqlite/meta.js +1 -1
  42. package/dist/src/store/sqlite/raptor.js +56 -17
  43. package/dist/src/store/sqlite/raptor.test.js +106 -0
  44. package/dist/src/store/sqlite/schema.js +90 -1
  45. package/dist/src/store/sqlite/session-state.js +9 -3
  46. package/dist/src/store/sqlite/stats.js +9 -5
  47. package/dist/src/store/sqlite/turns.js +179 -0
  48. package/dist/src/store/sqlite/turns.test.js +183 -0
  49. package/dist/src/store/sqlite/utils.js +15 -4
  50. package/dist/src/store/sqlite.js +1 -0
  51. package/dist/src/store.js +2 -2
  52. package/dist/src/vector-search-cache.test.js +157 -0
  53. package/dist/src/vector-search.js +107 -15
  54. package/dist/src/vectorStore.js +36 -8
  55. package/extensions/mega-compact-s38.test.ts +259 -14
  56. package/extensions/mega-compact.ts +15 -0
  57. package/extensions/mega-config.ts +18 -0
  58. package/extensions/mega-dashboard.ts +10 -1
  59. package/extensions/mega-events/agent-handlers.ts +211 -26
  60. package/extensions/mega-events/context-handler.ts +43 -7
  61. package/extensions/mega-events/error-classifier.ts +125 -17
  62. package/extensions/mega-pipeline/compact.ts +28 -16
  63. package/extensions/mega-pipeline/recall.ts +34 -2
  64. package/extensions/mega-runtime/dashboard-snapshot.ts +8 -0
  65. package/extensions/mega-runtime/helpers.ts +25 -1
  66. package/extensions/mega-runtime/runtime-snapshot.ts +4 -0
  67. package/extensions/mega-runtime/runtime.ts +69 -23
  68. package/package.json +1 -1
  69. package/src/boundary.test.ts +128 -2
  70. package/src/boundary.ts +75 -39
  71. package/src/canary.ts +10 -0
  72. package/src/config/dedup.ts +25 -0
  73. package/src/config.ts +3 -1
  74. package/src/dedup/raptor/buildHistory.test.ts +353 -0
  75. package/src/dedup/raptor/buildHistory.ts +259 -0
  76. package/src/dedup/raptor/index.ts +38 -0
  77. package/src/dedup/raptor/multilevel-serve.test.ts +273 -0
  78. package/src/dedup/raptor/multilevel.test.ts +47 -0
  79. package/src/dedup/raptor/multilevel.ts +18 -8
  80. package/src/dedup/raptor/raptor.test.ts +59 -0
  81. package/src/dedup/raptor/retrieval.test.ts +118 -0
  82. package/src/dedup/raptor/retrieval.ts +14 -2
  83. package/src/dedup/raptor/serve-gate.test.ts +348 -0
  84. package/src/dedup/raptor/summarizer.ts +1 -0
  85. package/src/dedup/raptor/tree.ts +17 -2
  86. package/src/engine.ts +32 -3
  87. package/src/httpEmbedder.test.ts +286 -0
  88. package/src/httpEmbedder.ts +98 -8
  89. package/src/mechanical-fix.test.ts +70 -0
  90. package/src/raptor-inject-summaries.test.ts +228 -0
  91. package/src/recall.test.ts +220 -4
  92. package/src/recall.ts +462 -265
  93. package/src/store/sqlite/dedup-mirror.ts +35 -18
  94. package/src/store/sqlite/maintenance.ts +2 -2
  95. package/src/store/sqlite/mechanical-fix.test.ts +162 -0
  96. package/src/store/sqlite/memories.ts +5 -5
  97. package/src/store/sqlite/meta.ts +1 -1
  98. package/src/store/sqlite/raptor.test.ts +139 -0
  99. package/src/store/sqlite/raptor.ts +135 -81
  100. package/src/store/sqlite/schema.ts +90 -1
  101. package/src/store/sqlite/session-state.ts +9 -3
  102. package/src/store/sqlite/stats.ts +10 -8
  103. package/src/store/sqlite/turns.test.ts +218 -0
  104. package/src/store/sqlite/turns.ts +302 -0
  105. package/src/store/sqlite/utils.ts +14 -4
  106. package/src/store/sqlite.ts +1 -0
  107. package/src/store.ts +9 -2
  108. package/src/vector-search-cache.test.ts +190 -0
  109. package/src/vector-search.ts +273 -156
  110. package/src/vectorStore.ts +443 -382
  111. package/extensions/mega-runtime/reset-runtime.ts +0 -80
@@ -16,18 +16,24 @@ import { normalizeSessionId } from "./store.js";
16
16
  import { mmrRerank, type MmrItem } from "./dedup/mmr.js";
17
17
  import { topK } from "./dedup/topk.js";
18
18
  import {
19
- listCheckpoints,
20
- getCheckpoint,
21
- maxCheckpointTimestamp,
19
+ listCheckpoints,
20
+ getCheckpoint,
21
+ maxCheckpointTimestamp,
22
+ maxRaptorNodeBuiltAt,
22
23
  } from "./store/sqlite.js";
23
24
  import {
24
- initVectorIndex,
25
- searchAsync as vectorIndexSearch,
26
- type VectorIndexHit,
25
+ initVectorIndex,
26
+ searchAsync as vectorIndexSearch,
27
+ type VectorIndexHit,
27
28
  } from "./store/vectorIndex.js";
28
29
  import { rehydrateRaptorTree, isShadowMode } from "./dedup/raptor/index.js";
29
30
  import { stagedExpansion } from "./dedup/raptor/retrieval.js";
31
+ import { multilevelRetrieval } from "./dedup/raptor/multilevel.js";
32
+ import { Logger } from "./log.js";
30
33
  import type { SearchHit, VectorStore } from "./vectorStore.js";
34
+ import type { StoredCheckpoint } from "./store.js";
35
+
36
+ const logger = new Logger();
31
37
 
32
38
  // ---------------------------------------------------------------------------
33
39
  // raptorSearchHits — internal helper (NOT exported)
@@ -37,50 +43,152 @@ import type { SearchHit, VectorStore } from "./vectorStore.js";
37
43
  * Serve the RAPTOR tree for a query (Fix D): rehydrate the persisted tree and
38
44
  * return its staged-expansion leaf hits as SearchHits. Returns [] when no tree
39
45
  * exists (small sessions — flat search remains the path). Best-effort/non-fatal.
46
+ *
47
+ * `checkpoints` is the caller's already-loaded, dedup-status-filtered checkpoint
48
+ * list — pass it in so the flat-search path and the RAPTOR merge path share ONE
49
+ * `listCheckpoints` call per `vectorSearch` (eliminates a redundant full scan +
50
+ * N-row hydration at 500+ checkpoints — QA perf review).
40
51
  */
41
- function raptorSearchHits(store: VectorStore, sid: string, query: string, k: number): SearchHit[] {
42
- const t0 = Date.now();
43
- try {
44
- const stateDir = store.stateDir;
45
- const cfg = store.cfg;
46
- const embedder = store.embedder;
47
- const record = store.record;
48
-
49
- // S25 gate (a): honor the shadow contract at SERVE time. The tree is still
50
- // built + persisted (logging-only) but NOT merged into recall while
51
- // RAPTOR_SHADOW_MODE is anything other than "false".
52
- if (isShadowMode()) return [];
53
- const tree = rehydrateRaptorTree(sid, stateDir);
54
- if (!tree || !tree.rootId) return [];
55
- // S25 gate (b): freshness + fallback guards. Skip a tree built before the
56
- // newest checkpoint (stale may reference trimmed/deduped leaves) or one
57
- // whose root is a budget-exhausted extractive fallback (level 99).
58
- if (tree.timedOut) return [];
59
- const maxTs = maxCheckpointTimestamp(sid, stateDir);
60
- if (tree.builtAt && tree.builtAt < maxTs) return [];
61
- const leafIds = stagedExpansion(query, tree, {
62
- embedder,
63
- k,
64
- topM: cfg.RAPTOR_CLUSTERS_PER_LEVEL,
65
- mmrLambda: cfg.MMR_LAMBDA,
66
- });
67
- if (leafIds.length === 0) return [];
68
- const all = listCheckpoints(sid, stateDir).filter(
69
- (cp) => cp.dedupStatus !== "removed",
70
- );
71
- const qv = embedder.embed(query);
72
- const hits: SearchHit[] = [];
73
- for (const id of leafIds) {
74
- const cp = all.find((c) => c.checkpointId === id);
75
- if (cp) hits.push({ checkpoint: cp, score: cosineSimilarity(qv, cp.embedding) });
76
- }
77
- // S25 monitoring: emit a raptor_serve decision so canary.ts can track
78
- // p95 latency + the tier's live traffic (non-fatal, best-effort).
79
- record("RAPTOR", hits.length > 0 ? "new" : "mark_only", `leaves=${leafIds.length}`, Date.now() - t0);
80
- return hits;
81
- } catch {
82
- return [];
83
- }
52
+ function raptorSearchHits(
53
+ store: VectorStore,
54
+ sid: string,
55
+ query: string,
56
+ k: number,
57
+ checkpoints: StoredCheckpoint[],
58
+ ): SearchHit[] {
59
+ const t0 = Date.now();
60
+ try {
61
+ const stateDir = store.stateDir;
62
+ const cfg = store.cfg;
63
+ const embedder = store.embedder;
64
+ const record = store.record;
65
+
66
+ // S25 gate (a): honor the shadow contract at SERVE time. The tree is still
67
+ // built + persisted (logging-only) but NOT merged into recall while
68
+ // RAPTOR_SHADOW_MODE is anything other than "false".
69
+ if (isShadowMode()) return [];
70
+ // S25 gate (b): freshness + fallback guards with per-session cache.
71
+ // The cache avoids O(n·leaves) rehydrate on every search when the tree
72
+ // hasn't changed. Freshness is validated via maxRaptorNodeBuiltAt (cheap
73
+ // indexed MAX query) — if the persisted builtAt matches the cached builtAt,
74
+ // the tree hasn't been rebuilt and can be served from cache.
75
+ const cacheKey = `${stateDir}::${sid}`;
76
+ const cached = store.raptorCache.get(cacheKey);
77
+ const latestBuiltAt = maxRaptorNodeBuiltAt(sid, stateDir);
78
+
79
+ let tree: ReturnType<typeof rehydrateRaptorTree>;
80
+ if (cached && cached.builtAt === latestBuiltAt && latestBuiltAt > 0) {
81
+ tree = cached.tree;
82
+ } else {
83
+ tree = rehydrateRaptorTree(sid, stateDir);
84
+ if (tree && tree.rootId && !tree.timedOut) {
85
+ store.raptorCache.set(cacheKey, { tree, builtAt: tree.builtAt ?? 0 });
86
+ } else {
87
+ store.raptorCache.delete(cacheKey);
88
+ }
89
+ }
90
+
91
+ if (!tree || !tree.rootId) return [];
92
+
93
+ // Freshness: skip persisted tree if the session has been compacted since
94
+ // the tree was built. The next runRaptor will rebuild.
95
+ const maxTs = maxCheckpointTimestamp(sid, stateDir);
96
+ if (tree.builtAt && tree.builtAt < maxTs) return [];
97
+
98
+ // S25: skip timedOut extractive-fallback trees (level === 99).
99
+ if (tree.timedOut) return [];
100
+ // Use the caller's already-loaded checkpoint list (no second scan).
101
+ const all = checkpoints;
102
+ const qv = embedder.embed(query);
103
+ const hits: SearchHit[] = [];
104
+
105
+ // S42B: multi-level retrieval — score all tree levels (leaves + internal
106
+ // clusters), expand cluster hits to their leaf descendants, dedup overlaps,
107
+ // and MMR-rerank. Default ON (RAPTOR_MULTILEVEL_ENABLED). When OFF, the
108
+ // leaf-only stagedExpansion path below is used (identical to pre-S42B).
109
+ if (cfg.RAPTOR_MULTILEVEL_ENABLED) {
110
+ const mlHits = multilevelRetrieval(query, tree, {
111
+ embedder,
112
+ levelWeights: cfg.RAPTOR_LEVEL_WEIGHTS,
113
+ leafExpansion: cfg.RAPTOR_LEAF_EXPANSION,
114
+ maxLeafExpansion: cfg.RAPTOR_MAX_LEAF_EXPANSION,
115
+ k,
116
+ mmrLambda: cfg.MMR_LAMBDA,
117
+ });
118
+ if (mlHits.length === 0) return [];
119
+ for (const mh of mlHits) {
120
+ // Leaf hits: nodeId IS the checkpointId — hydrate from the stored list.
121
+ const cp = all.find((c) => c.checkpointId === mh.nodeId);
122
+ if (cp) {
123
+ hits.push({ checkpoint: cp, score: mh.score });
124
+ continue;
125
+ }
126
+ // Orphaned leaf (checkpoint missing from the stored list, e.g. SemDeDup
127
+ // removed) — skip; synthesizing it would yield an empty cluster block.
128
+ if (mh.isLeaf) continue;
129
+ // Cluster hit: synthesize a SearchHit so the recall block can surface
130
+ // the hierarchical summary (not a stored checkpoint). The minimal
131
+ // StoredCheckpoint carries the node's centroid embedding for any
132
+ // downstream cosine / MMR the caller does.
133
+ hits.push({
134
+ checkpoint: {
135
+ checkpointId: mh.nodeId,
136
+ sessionId: sid,
137
+ summary: mh.summary,
138
+ keyDecisions: [],
139
+ nextSteps: [],
140
+ filesModified: [],
141
+ tokenEstimate: 0,
142
+ regionHash: `raptor:${mh.nodeId}`,
143
+ embedding: mh.embedding,
144
+ timestamp: tree.builtAt ?? Date.now(),
145
+ },
146
+ score: mh.score,
147
+ raptorSummary: mh.summary,
148
+ raptorLevel: mh.level,
149
+ });
150
+ }
151
+ record(
152
+ "RAPTOR",
153
+ hits.length > 0 ? "new" : "mark_only",
154
+ `ml=${mlHits.length}`,
155
+ Date.now() - t0,
156
+ );
157
+ return hits;
158
+ }
159
+
160
+ // S42B: flag OFF — leaf-only stagedExpansion (pre-S42B production path).
161
+ const leafIds = stagedExpansion(query, tree, {
162
+ embedder,
163
+ k,
164
+ topM: cfg.RAPTOR_CLUSTERS_PER_LEVEL,
165
+ mmrLambda: cfg.MMR_LAMBDA,
166
+ });
167
+ if (leafIds.length === 0) return [];
168
+ for (const id of leafIds) {
169
+ const cp = all.find((c) => c.checkpointId === id);
170
+ if (cp)
171
+ hits.push({
172
+ checkpoint: cp,
173
+ score: cosineSimilarity(qv, cp.embedding),
174
+ });
175
+ }
176
+ // S25 monitoring: emit a raptor_serve decision so canary.ts can track
177
+ // p95 latency + the tier's live traffic (non-fatal, best-effort).
178
+ record(
179
+ "RAPTOR",
180
+ hits.length > 0 ? "new" : "mark_only",
181
+ `leaves=${leafIds.length}`,
182
+ Date.now() - t0,
183
+ );
184
+ return hits;
185
+ } catch (e) {
186
+ logger.warn("raptor_search_error", {
187
+ error: String(e),
188
+ sessionId: sid,
189
+ });
190
+ return [];
191
+ }
84
192
  }
85
193
 
86
194
  // ---------------------------------------------------------------------------
@@ -96,65 +204,69 @@ function raptorSearchHits(store: VectorStore, sid: string, query: string, k: num
96
204
  * reranks the candidate window for diversity.
97
205
  */
98
206
  export function vectorSearch(
99
- store: VectorStore,
100
- sessionId: string,
101
- query: string,
102
- k = 3,
207
+ store: VectorStore,
208
+ sessionId: string,
209
+ query: string,
210
+ k = 3,
103
211
  ): SearchHit[] {
104
- const stateDir = store.stateDir;
105
- const cfg = store.cfg;
106
- const embedder = store.embedder;
107
-
108
- const sid = normalizeSessionId(sessionId);
109
- const checkpoints = listCheckpoints(sid, stateDir).filter(
110
- (cp) => cp.dedupStatus !== "removed", // SemDeDup: exclude removed rows
111
- );
112
- if (checkpoints.length === 0) return [];
113
- const qv = embedder.embed(query);
114
-
115
- const scored: SearchHit[] = checkpoints.map((cp) => ({
116
- checkpoint: cp,
117
- score: cosineSimilarity(qv, cp.embedding),
118
- }));
119
-
120
- // Heap top-K over a widened window (2k) so MMR has diverse candidates.
121
- const window = topK(
122
- scored.map((h) => ({ item: h, score: h.score })),
123
- Math.max(k * 2, k),
124
- ).map((s) => s.item);
125
- // MMR (QA #10) is part of the L2 semantic tier: skip it when L2 is disabled
126
- // (Sprint 14 flag), returning the plain relevance-ranked window instead.
127
- if (!cfg.L2_ENABLED) return window.slice(0, k);
128
-
129
- // Fix D: when RAPTOR is promoted, ALSO recall high-level tree summaries and
130
- // merge them with the flat hits via MMR so RAPTOR + flat don't double-cover.
131
- // RAPTOR returns fewer, broader hits (O(log n) high-level nodes) than the
132
- // O(n) flat leaves, tightening the block at read time.
133
- if (cfg.RAPTOR_ENABLED) {
134
- const rh = raptorSearchHits(store, sid, query, k);
135
- if (rh.length > 0) {
136
- const merged: SearchHit[] = [...window];
137
- for (const h of rh) {
138
- if (!merged.some((m) => m.checkpoint.checkpointId === h.checkpoint.checkpointId)) {
139
- merged.push(h);
140
- }
141
- }
142
- const mmrItems: MmrItem<SearchHit>[] = merged.map((h) => ({
143
- item: h,
144
- vector: h.checkpoint.embedding,
145
- relevance: h.score,
146
- }));
147
- return mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
148
- }
149
- }
150
-
151
- const mmrItems: MmrItem<SearchHit>[] = window.map((h) => ({
152
- item: h,
153
- vector: h.checkpoint.embedding,
154
- relevance: h.score,
155
- }));
156
- const ranked = mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
157
- return ranked;
212
+ const stateDir = store.stateDir;
213
+ const cfg = store.cfg;
214
+ const embedder = store.embedder;
215
+
216
+ const sid = normalizeSessionId(sessionId);
217
+ const checkpoints = listCheckpoints(sid, stateDir).filter(
218
+ (cp) => cp.dedupStatus !== "removed", // SemDeDup: exclude removed rows
219
+ );
220
+ if (checkpoints.length === 0) return [];
221
+ const qv = embedder.embed(query);
222
+
223
+ const scored: SearchHit[] = checkpoints.map((cp) => ({
224
+ checkpoint: cp,
225
+ score: cosineSimilarity(qv, cp.embedding),
226
+ }));
227
+
228
+ // Heap top-K over a widened window (2k) so MMR has diverse candidates.
229
+ const window = topK(
230
+ scored.map((h) => ({ item: h, score: h.score })),
231
+ Math.max(k * 2, k),
232
+ ).map((s) => s.item);
233
+ // MMR (QA #10) is part of the L2 semantic tier: skip it when L2 is disabled
234
+ // (Sprint 14 flag), returning the plain relevance-ranked window instead.
235
+ if (!cfg.L2_ENABLED) return window.slice(0, k);
236
+
237
+ // Fix D: when RAPTOR is promoted, ALSO recall high-level tree summaries and
238
+ // merge them with the flat hits via MMR so RAPTOR + flat don't double-cover.
239
+ // RAPTOR returns fewer, broader hits (O(log n) high-level nodes) than the
240
+ // O(n) flat leaves, tightening the block at read time.
241
+ if (cfg.RAPTOR_ENABLED) {
242
+ const rh = raptorSearchHits(store, sid, query, k, checkpoints);
243
+ if (rh.length > 0) {
244
+ const merged: SearchHit[] = [...window];
245
+ for (const h of rh) {
246
+ if (
247
+ !merged.some(
248
+ (m) => m.checkpoint.checkpointId === h.checkpoint.checkpointId,
249
+ )
250
+ ) {
251
+ merged.push(h);
252
+ }
253
+ }
254
+ const mmrItems: MmrItem<SearchHit>[] = merged.map((h) => ({
255
+ item: h,
256
+ vector: h.checkpoint.embedding,
257
+ relevance: h.score,
258
+ }));
259
+ return mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
260
+ }
261
+ }
262
+
263
+ const mmrItems: MmrItem<SearchHit>[] = window.map((h) => ({
264
+ item: h,
265
+ vector: h.checkpoint.embedding,
266
+ relevance: h.score,
267
+ }));
268
+ const ranked = mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
269
+ return ranked;
158
270
  }
159
271
 
160
272
  // ---------------------------------------------------------------------------
@@ -162,10 +274,10 @@ export function vectorSearch(
162
274
  // ---------------------------------------------------------------------------
163
275
 
164
276
  export interface VectorSearchOptions {
165
- /** Scope to a specific repo (omit for cross-repo NN). */
166
- repoId?: string;
167
- /** Include hits from all repos (cross-repo NN). Mutually exclusive with repoId. */
168
- crossRepo?: boolean;
277
+ /** Scope to a specific repo (omit for cross-repo NN). */
278
+ repoId?: string;
279
+ /** Include hits from all repos (cross-repo NN). Mutually exclusive with repoId. */
280
+ crossRepo?: boolean;
169
281
  }
170
282
 
171
283
  /**
@@ -183,49 +295,54 @@ export interface VectorSearchOptions {
183
295
  * MMR-dedupes the merged set.
184
296
  */
185
297
  export async function vectorSearchAsync(
186
- store: VectorStore,
187
- sessionId: string,
188
- query: string,
189
- k = 3,
190
- opts: VectorSearchOptions = {},
298
+ store: VectorStore,
299
+ sessionId: string,
300
+ query: string,
301
+ k = 3,
302
+ opts: VectorSearchOptions = {},
191
303
  ): Promise<SearchHit[]> {
192
- const cfg = store.cfg;
193
- const embedder = store.embedder;
194
-
195
- const sid = normalizeSessionId(sessionId);
196
- const qv = embedder.embed(query);
197
- // repoId filter: explicit opts.repoId wins; else this repo unless crossRepo.
198
- const selfRepo = store.repoId;
199
- const repoId = opts.repoId ?? (opts.crossRepo ? undefined : selfRepo);
200
- let indexHits: VectorIndexHit[] = [];
201
- try {
202
- await initVectorIndex();
203
- indexHits = await vectorIndexSearch(qv, { k: Math.max(k * 2, k), repoId });
204
- } catch {
205
- indexHits = [];
206
- }
207
- if (indexHits.length === 0) {
208
- // Index empty/unavailable → synchronous per-session fallback (this repo).
209
- return vectorSearch(store, sid, query, k);
210
- }
211
- // Hydrate each index hit from the authoritative node:sqlite store. repoId is
212
- // that repo's stateDir, so cross-repo hits resolve against their own store.
213
- // Tag cross-repo hits with their source repoId so the recall block can label
214
- // them ("from repo <name>"); same-repo hits stay unlabeled.
215
- const hydrated: SearchHit[] = [];
216
- for (const h of indexHits) {
217
- const cp = getCheckpoint(h.sessionId, h.checkpointId, h.repoId);
218
- if (cp && cp.dedupStatus !== "removed") {
219
- const crossRepo = opts.crossRepo && selfRepo && h.repoId && h.repoId !== selfRepo;
220
- hydrated.push({ checkpoint: cp, score: h.score, repoId: crossRepo ? h.repoId : undefined });
221
- }
222
- }
223
- if (hydrated.length === 0) return vectorSearch(store, sid, query, k);
224
- // MMR-dedupe the merged candidate set for diversity (mirrors sync search).
225
- const mmrItems: MmrItem<SearchHit>[] = hydrated.map((h) => ({
226
- item: h,
227
- vector: h.checkpoint.embedding,
228
- relevance: h.score,
229
- }));
230
- return mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
231
- }
304
+ const cfg = store.cfg;
305
+ const embedder = store.embedder;
306
+
307
+ const sid = normalizeSessionId(sessionId);
308
+ const qv = embedder.embed(query);
309
+ // repoId filter: explicit opts.repoId wins; else this repo unless crossRepo.
310
+ const selfRepo = store.repoId;
311
+ const repoId = opts.repoId ?? (opts.crossRepo ? undefined : selfRepo);
312
+ let indexHits: VectorIndexHit[] = [];
313
+ try {
314
+ await initVectorIndex();
315
+ indexHits = await vectorIndexSearch(qv, { k: Math.max(k * 2, k), repoId });
316
+ } catch {
317
+ indexHits = [];
318
+ }
319
+ if (indexHits.length === 0) {
320
+ // Index empty/unavailable → synchronous per-session fallback (this repo).
321
+ return vectorSearch(store, sid, query, k);
322
+ }
323
+ // Hydrate each index hit from the authoritative node:sqlite store. repoId is
324
+ // that repo's stateDir, so cross-repo hits resolve against their own store.
325
+ // Tag cross-repo hits with their source repoId so the recall block can label
326
+ // them ("from repo <name>"); same-repo hits stay unlabeled.
327
+ const hydrated: SearchHit[] = [];
328
+ for (const h of indexHits) {
329
+ const cp = getCheckpoint(h.sessionId, h.checkpointId, h.repoId);
330
+ if (cp && cp.dedupStatus !== "removed") {
331
+ const crossRepo =
332
+ opts.crossRepo && selfRepo && h.repoId && h.repoId !== selfRepo;
333
+ hydrated.push({
334
+ checkpoint: cp,
335
+ score: h.score,
336
+ repoId: crossRepo ? h.repoId : undefined,
337
+ });
338
+ }
339
+ }
340
+ if (hydrated.length === 0) return vectorSearch(store, sid, query, k);
341
+ // MMR-dedupe the merged candidate set for diversity (mirrors sync search).
342
+ const mmrItems: MmrItem<SearchHit>[] = hydrated.map((h) => ({
343
+ item: h,
344
+ vector: h.checkpoint.embedding,
345
+ relevance: h.score,
346
+ }));
347
+ return mmrRerank(mmrItems, k, cfg.MMR_LAMBDA);
348
+ }