pi-mega-compact 0.8.19 → 0.8.21

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 (72) hide show
  1. package/README.md +8 -2
  2. package/dist/extensions/dashboard-client/src/hooks/useApi.js +51 -0
  3. package/dist/extensions/dashboard-client/src/hooks/useSSE.js +63 -0
  4. package/dist/extensions/mega-compact-s38.test.js +28 -2
  5. package/dist/extensions/mega-events/agent-handlers.js +16 -0
  6. package/dist/extensions/mega-events/error-classifier.js +8 -3
  7. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DVgFowc-.js → AchievementTiles-QPo46Pwg.js} +2 -2
  8. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DVgFowc-.js.map → AchievementTiles-QPo46Pwg.js.map} +1 -1
  9. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Bg86TWPU.js → AchievementsTab-BDJxRXRY.js} +2 -2
  10. package/extensions/dashboard-client/dist/assets/{AchievementsTab-Bg86TWPU.js.map → AchievementsTab-BDJxRXRY.js.map} +1 -1
  11. package/extensions/dashboard-client/dist/assets/{CacheTab-DBzoH_q_.js → CacheTab-CUMqXQjd.js} +2 -2
  12. package/extensions/dashboard-client/dist/assets/{CacheTab-DBzoH_q_.js.map → CacheTab-CUMqXQjd.js.map} +1 -1
  13. package/extensions/dashboard-client/dist/assets/{ConfigTab-Bj12LgTh.js → ConfigTab-j1dRzurG.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{ConfigTab-Bj12LgTh.js.map → ConfigTab-j1dRzurG.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{EventsTab-EC0wMsp_.js → EventsTab-CMglXK4p.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{EventsTab-EC0wMsp_.js.map → EventsTab-CMglXK4p.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{GameTab-CISQitbu.js → GameTab-fpaDoyxi.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{GameTab-CISQitbu.js.map → GameTab-fpaDoyxi.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{MetricsTab-us4vVG5D.js → MetricsTab-CWG2crG_.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{MetricsTab-us4vVG5D.js.map → MetricsTab-CWG2crG_.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/OverviewTab-BaV2D2nf.js +2 -0
  22. package/extensions/dashboard-client/dist/assets/OverviewTab-BaV2D2nf.js.map +1 -0
  23. package/extensions/dashboard-client/dist/assets/{ReposTab-CiU9AJkb.js → ReposTab-C-0kuZ_0.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{ReposTab-CiU9AJkb.js.map → ReposTab-C-0kuZ_0.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{SessionsTab-DaCUvTC7.js → SessionsTab-CSYhddCA.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{SessionsTab-DaCUvTC7.js.map → SessionsTab-CSYhddCA.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BAUSCzq5.js → TimeSavedCard-Dy4KRjSM.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BAUSCzq5.js.map → TimeSavedCard-Dy4KRjSM.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{index-5MPZyayK.js → index-D_WtU2TV.js} +3 -3
  30. package/extensions/dashboard-client/dist/assets/{index-5MPZyayK.js.map → index-D_WtU2TV.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{index-CDkmYP9l.css → index-og3ghxyY.css} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js → useSSE-C9yVJlGP.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js.map → useSSE-C9yVJlGP.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/index.html +2 -2
  35. package/extensions/dashboard-client/package-lock.json +1 -1
  36. package/extensions/dashboard-client/package.json +1 -1
  37. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +181 -0
  38. package/extensions/dashboard-client/src/main.tsx +1 -0
  39. package/extensions/dashboard-client/src/styles/session-gauges.css +85 -0
  40. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +13 -11
  41. package/extensions/mega-compact-s38.test.ts +32 -2
  42. package/extensions/mega-events/agent-handlers.ts +15 -0
  43. package/extensions/mega-events/error-classifier.ts +8 -2
  44. package/package.json +1 -1
  45. package/dist/extensions/dashboard-server/helpers.js +0 -37
  46. package/dist/extensions/dashboard-server/html/all-repos-tab.js +0 -26
  47. package/dist/extensions/dashboard-server/html/body-open.js +0 -23
  48. package/dist/extensions/dashboard-server/html/current-repo-tab.js +0 -130
  49. package/dist/extensions/dashboard-server/html/head-open.js +0 -16
  50. package/dist/extensions/dashboard-server/html/high-score-tab.js +0 -25
  51. package/dist/extensions/dashboard-server/html/repo-detail-modal.js +0 -26
  52. package/dist/extensions/dashboard-server/html/script.js +0 -259
  53. package/dist/extensions/dashboard-server/html/styles.js +0 -103
  54. package/dist/extensions/dashboard-server/html/summary-tab.js +0 -19
  55. package/dist/extensions/dashboard-server/html-template.js +0 -41
  56. package/dist/src/store/sqlite/connection.js +0 -35
  57. package/dist/src/store/sqlite/index-store.js +0 -167
  58. package/dist/src/store/sqlite/memory.js +0 -54
  59. package/dist/src/store/sqlite/minhash-lsh.js +0 -47
  60. package/dist/src/store/sqlite/sessions.js +0 -39
  61. package/dist/src/store/sqlite/transaction.js +0 -19
  62. package/dist/src/vectorStore/add.js +0 -260
  63. package/dist/src/vectorStore/dedup.js +0 -52
  64. package/dist/src/vectorStore/index.js +0 -10
  65. package/dist/src/vectorStore/queries.js +0 -83
  66. package/dist/src/vectorStore/search.js +0 -95
  67. package/dist/src/vectorStore/session.js +0 -19
  68. package/dist/src/vectorStore/store.js +0 -105
  69. package/dist/src/vectorStore/types.js +0 -6
  70. package/dist/src/vectorStore/utils.js +0 -23
  71. package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js +0 -2
  72. package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js.map +0 -1
@@ -1,260 +0,0 @@
1
- /**
2
- * vectorStore/add.ts — Add/insert operations: the dedup cascade and L1 lookup.
3
- */
4
- import { createHash } from "node:crypto";
5
- import { cosineSimilarity } from "../embedder.js";
6
- import { normalizeSessionId, compressSmart } from "../store.js";
7
- import { computeContentDigest } from "../dedup/digest.js";
8
- import { minhashSignature, SIGNATURE_VERSION, NUM_HASHES } from "../dedup/l1-minhash.js";
9
- import { lshBands } from "../dedup/l1-lsh.js";
10
- import { isNearDuplicate } from "../dedup/l1-verify.js";
11
- import { openBloom, saveBloom } from "../store/bloom.js";
12
- import { listCheckpoints, nextCheckpointId, upsertCheckpoint, loadSessionState, saveSessionState, upsertMinhashSignature, insertLshBuckets, lshCandidateChunks, bumpDedupStats, addTokensSaved, } from "../store/sqlite.js";
13
- import { computeRegionHash, recordDecision } from "./utils.js";
14
- /**
15
- * L1 near-duplicate lookup: MinHash → LSH candidate retrieval → trigram verify.
16
- * Returns the matching checkpoint or undefined. Bounded by a 100-candidate cap
17
- * and a 20ms verify budget (QA #7/#15) so it never hangs a large session.
18
- */
19
- export function findL1Duplicate(ctx, sessionId, regionText, all) {
20
- if (all.length === 0)
21
- return undefined;
22
- const sig = minhashSignature(regionText);
23
- if (sig.length !== NUM_HASHES)
24
- return undefined;
25
- const bands = lshBands(sig, sessionId, SIGNATURE_VERSION);
26
- // Cheap candidate retrieval (single query, capped). Exclude nothing yet —
27
- // the new checkpoint has no id, so pass a sentinel that never matches.
28
- const candidateIds = lshCandidateChunks(bands, sessionId, "__new__", ctx.stateDir, 100);
29
- if (candidateIds.length === 0)
30
- return undefined;
31
- const byId = new Map(all.map((cp) => [cp.checkpointId, cp]));
32
- const VERIFY_BUDGET_MS = 20;
33
- const start = Date.now();
34
- for (const id of candidateIds) {
35
- if (Date.now() - start > VERIFY_BUDGET_MS)
36
- break; // QA #15: abort → "not dup"
37
- const cand = byId.get(id);
38
- if (!cand)
39
- continue;
40
- const candText = cand.normalizedText ?? cand.summary ?? "";
41
- if (isNearDuplicate(regionText, candText))
42
- return cand;
43
- }
44
- return undefined;
45
- }
46
- /**
47
- * Add a checkpoint. Dedup cascade:
48
- * 1. regionHash exact match (legacy, backward-compat)
49
- * 2. summaryHash exact match (new: catches same-topic incremental compactions)
50
- * 3. content similarity ≥ dedupSim (catches near-identical summaries)
51
- * 4. If none match → create new checkpoint
52
- */
53
- export function addToStore(ctx, input) {
54
- const t0 = Date.now();
55
- const sessionId = normalizeSessionId(input.sessionId);
56
- const regionHash = computeRegionHash(input.regionText);
57
- const all = listCheckpoints(sessionId, ctx.stateDir);
58
- // Honest "tokens saved" base for this region. For a deduped add the whole
59
- // original region is discarded (nothing new stored); for a new checkpoint
60
- // we persist (orig − stored). Falls back to stored when orig is unknown.
61
- const origTokens = input.originalTokenEstimate ?? input.tokenEstimate ?? 0;
62
- const cfg = ctx.cfg;
63
- // Live per-tier progress hook (Phase 1). Sync + optional; fired at each tier
64
- // so the UI can paint "L0 ✓ → L1 ✓ → L2 0.91 → stored" during a compaction.
65
- const onTier = input.onTier;
66
- // Tracks whether a tier matched while in MARK_ONLY (record-but-don't-collapse),
67
- // and which tier.
68
- let markOnly = null;
69
- // 0. L0 content-hash dedup (Sprint 9) — catches identical content arriving
70
- // under different regionText. Normalization handles case/whitespace/ANSI so
71
- // variants collapse to one row. Dual-hash guards a single-hash collision.
72
- // Sprint 10: bloom is the accelerator — a miss means "definitely new" and
73
- // skips the scan; a hit is only a candidate, confirmed against `all` below.
74
- // Gated by L0_ENABLED (Sprint 14). MARK_ONLY_L0 records the decision but
75
- // does not collapse — the new region is still stored.
76
- onTier?.({ tier: "L0", status: "scanning" });
77
- const digest = computeContentDigest(input.regionText);
78
- const bloom = openBloom(ctx.stateDir);
79
- if (cfg.L0_ENABLED && bloom.maybeHas(digest.contentHash)) {
80
- const contentMatch = all.find((cp) => cp.contentHash === digest.contentHash &&
81
- cp.contentHash2 === digest.contentHash2);
82
- if (contentMatch) {
83
- if (cfg.MARK_ONLY_L0) {
84
- markOnly = "L0"; // Record-but-don't-collapse: fall through.
85
- }
86
- else {
87
- contentMatch.timestamp = input.timestamp;
88
- upsertCheckpoint(contentMatch, ctx.stateDir);
89
- bumpDedupStats(true, ctx.stateDir);
90
- // Deduped: whole original region discarded, nothing new stored.
91
- addTokensSaved(origTokens, ctx.stateDir);
92
- const r = { checkpoint: contentMatch, deduped: true, reason: "contentHash" };
93
- recordDecision(ctx, "L0", "deduped", "contentHash", Date.now() - t0);
94
- onTier?.({ tier: "L0", status: "deduped", detail: "contentHash" });
95
- return r;
96
- }
97
- }
98
- }
99
- // 1. Legacy regionHash dedup (backward-compat) — part of L0 tier gating.
100
- if (cfg.L0_ENABLED) {
101
- const regionMatch = all.find((cp) => cp.regionHash === regionHash);
102
- if (regionMatch) {
103
- if (cfg.MARK_ONLY_L0) {
104
- markOnly = "L0"; // fall through
105
- }
106
- else {
107
- bumpDedupStats(true, ctx.stateDir);
108
- // Deduped: whole original region discarded, nothing new stored.
109
- addTokensSaved(origTokens, ctx.stateDir);
110
- const r = { checkpoint: regionMatch, deduped: true, reason: "regionHash" };
111
- recordDecision(ctx, "L0", "deduped", "regionHash", Date.now() - t0);
112
- onTier?.({ tier: "L0", status: "deduped", detail: "regionHash" });
113
- return r;
114
- }
115
- }
116
- }
117
- // 2. SummaryHash dedup — catches same-topic incremental compactions.
118
- // Full 64-hex SHA-256 (was 16-hex in Sprint 8 — collision-prone).
119
- const summaryHash = input.topicSummary
120
- ? createHash("sha256").update(input.topicSummary).digest("hex")
121
- : undefined;
122
- if (summaryHash && cfg.L0_ENABLED) {
123
- const summaryMatch = all.find((cp) => cp.summaryHash === summaryHash);
124
- if (summaryMatch) {
125
- if (cfg.MARK_ONLY_L0) {
126
- markOnly = "L0"; // fall through
127
- }
128
- else {
129
- summaryMatch.timestamp = input.timestamp;
130
- upsertCheckpoint(summaryMatch, ctx.stateDir);
131
- bumpDedupStats(true, ctx.stateDir);
132
- // Deduped: whole original region discarded, nothing new stored.
133
- addTokensSaved(origTokens, ctx.stateDir);
134
- const r = { checkpoint: summaryMatch, deduped: true, reason: "summaryHash" };
135
- recordDecision(ctx, "L0", "deduped", "summaryHash", Date.now() - t0);
136
- onTier?.({ tier: "L0", status: "deduped", detail: "summaryHash" });
137
- return r;
138
- }
139
- }
140
- }
141
- // L0 did not collapse this region.
142
- onTier?.({ tier: "L0", status: "passed" });
143
- // 2b. L1 MinHash/LSH near-duplicate dedup (Sprint 11) — catches one-word
144
- // edits / rewordings that L0's exact hash misses. Cheap LSH bucket
145
- // retrieval → trigram verification (pg_trgm-equivalent) as the final gate.
146
- // Gated by L1_ENABLED (Sprint 14); MARK_ONLY_L1 records but doesn't collapse.
147
- onTier?.({ tier: "L1", status: "scanning" });
148
- if (cfg.L1_ENABLED) {
149
- const l1 = findL1Duplicate(ctx, sessionId, input.regionText, all);
150
- if (l1 && !cfg.MARK_ONLY_L1) {
151
- l1.timestamp = input.timestamp;
152
- upsertCheckpoint(l1, ctx.stateDir);
153
- bumpDedupStats(true, ctx.stateDir);
154
- const r = { checkpoint: l1, deduped: true, reason: "l1MinHash" };
155
- recordDecision(ctx, "L1", "deduped", "l1MinHash", Date.now() - t0);
156
- onTier?.({ tier: "L1", status: "deduped", detail: "l1MinHash" });
157
- return r;
158
- }
159
- if (l1 && cfg.MARK_ONLY_L1)
160
- markOnly = "L1";
161
- }
162
- onTier?.({ tier: "L1", status: "passed" });
163
- // 3. L2 semantic dedup — catches near-identical / semantically-similar regions
164
- // via cosine over the embedding. topicSummary is used for summaryHash dedup
165
- // (tier 2); the vector index is keyed on the original region for backward-
166
- // compat search semantics. Threshold from cfg (L2_COSINE trigram honest
167
- // firing point). QA #13 timeout guard: if the O(n) scan exceeds the budget,
168
- // degrade to "store without dedup this pass" so we never lose a checkpoint.
169
- // Gated by L2_ENABLED (Sprint 14); MARK_ONLY_L2 records but doesn't collapse.
170
- const SIMILARITY_BUDGET_MS = cfg.SIMILARITY_BUDGET_MS;
171
- const simThreshold = ctx.l2Threshold; // from cfg.L2_COSINE (default 0.85 trigram)
172
- const embedding = ctx.embedder.embed(input.regionText);
173
- onTier?.({ tier: "L2", status: "scanning" });
174
- if (cfg.L2_ENABLED && all.length > 0) {
175
- const start = Date.now();
176
- let timedOut = false;
177
- const nearest = all.reduce((best, cp) => {
178
- if (!timedOut && Date.now() - start > SIMILARITY_BUDGET_MS)
179
- timedOut = true;
180
- if (timedOut)
181
- return best;
182
- const sim = cosineSimilarity(embedding, cp.embedding);
183
- return sim > best.sim ? { checkpoint: cp, sim } : best;
184
- }, { checkpoint: all[0], sim: -1 });
185
- if (!timedOut && nearest.sim >= simThreshold) {
186
- if (!cfg.MARK_ONLY_L2) {
187
- // Near-identical — update timestamp on existing checkpoint
188
- nearest.checkpoint.timestamp = input.timestamp;
189
- upsertCheckpoint(nearest.checkpoint, ctx.stateDir);
190
- bumpDedupStats(true, ctx.stateDir);
191
- // Deduped: whole original region discarded, nothing new stored.
192
- addTokensSaved(origTokens, ctx.stateDir);
193
- const r = { checkpoint: nearest.checkpoint, deduped: true, reason: "contentSimilarity" };
194
- recordDecision(ctx, "L2", "deduped", "contentSimilarity", Date.now() - t0);
195
- onTier?.({ tier: "L2", status: "deduped", detail: nearest.sim.toFixed(2) });
196
- return r;
197
- }
198
- markOnly = "L2";
199
- }
200
- onTier?.({ tier: "L2", status: "passed", detail: `best ${nearest.sim.toFixed(2)}` });
201
- }
202
- // 4. Genuinely new — create checkpoint
203
- const checkpointId = nextCheckpointId(sessionId, ctx.stateDir);
204
- const checkpoint = {
205
- checkpointId,
206
- sessionId,
207
- summary: input.summary,
208
- topicSummary: input.topicSummary,
209
- summaryHash,
210
- keyDecisions: input.keyDecisions ?? [],
211
- nextSteps: input.nextSteps ?? [],
212
- filesModified: input.filesModified ?? [],
213
- tokenEstimate: input.tokenEstimate ?? 0,
214
- originalTokenEstimate: input.originalTokenEstimate,
215
- regionHash,
216
- contentHash: digest.contentHash,
217
- contentHash2: digest.contentHash2,
218
- contentHashVersion: digest.contentHashVersion,
219
- normalizedText: digest.normalizedText,
220
- compressedOriginal: compressSmart(Buffer.from(input.regionText, "utf-8"), input.compressionPressure),
221
- embedding,
222
- timestamp: input.timestamp,
223
- };
224
- // Persistence is SQLite (store/sqlite.ts). upsertCheckpoint keeps the
225
- // idempotent-by-id semantics the old JSON append implied.
226
- upsertCheckpoint(checkpoint, ctx.stateDir);
227
- // Cumulative "tokens saved" counter (per-repo SQLite meta). For a NEW
228
- // checkpoint the saved amount is (original − stored); for a deduped add the
229
- // whole original region is discarded (handled in the deduped return paths
230
- // below). Survives sessions and travels with the repo.
231
- const stored = input.tokenEstimate ?? 0;
232
- addTokensSaved(Math.max(0, origTokens - stored), ctx.stateDir);
233
- // L1: persist this checkpoint's MinHash signature + LSH buckets so future
234
- // near-duplicate inserts can find it. Deterministic given the seed.
235
- const sig = minhashSignature(input.regionText);
236
- upsertMinhashSignature(checkpointId, sessionId, SIGNATURE_VERSION, sig, ctx.stateDir);
237
- insertLshBuckets(checkpointId, sessionId, SIGNATURE_VERSION, lshBands(sig, sessionId, SIGNATURE_VERSION), ctx.stateDir);
238
- // Bloom accelerator: record the new content_hash so a future add() can short-
239
- // circuit the scan on a hit (still confirmed by the SELECT-based `all` above).
240
- bloom.add(digest.contentHash);
241
- saveBloom(ctx.stateDir);
242
- // Track the region hash in session state for fast sentinel checks.
243
- const state = loadSessionState(sessionId, ctx.stateDir);
244
- if (!state.storedRegionHashes.includes(regionHash)) {
245
- state.storedRegionHashes.push(regionHash);
246
- saveSessionState(sessionId, state, ctx.stateDir);
247
- }
248
- // A new checkpoint. If a tier matched while MARK_ONLY, record that (the
249
- // decision fired but we intentionally did not collapse).
250
- if (markOnly) {
251
- recordDecision(ctx, markOnly, "mark_only", "mark_only", Date.now() - t0);
252
- }
253
- else {
254
- recordDecision(ctx, "L0", "new", undefined, Date.now() - t0);
255
- }
256
- // Cumulative store-wide dedup accounting (attempt, not collapsed).
257
- bumpDedupStats(false, ctx.stateDir);
258
- onTier?.({ tier: "new", status: "stored" });
259
- return { checkpoint, deduped: false };
260
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * vectorStore/dedup.ts — SemDeDup cleanup and dedup sentinel check.
3
- */
4
- import { cosineSimilarity } from "../embedder.js";
5
- import { normalizeSessionId } from "../store.js";
6
- import { listCheckpoints, setDedupStatus, loadSessionState } from "../store/sqlite.js";
7
- import { computeRegionHash } from "./utils.js";
8
- /**
9
- * SemDeDup offline cleanup (Sprint 12, QA #17): within a session, mark the
10
- * lower-quality row of any pair scoring cosine > `threshold` as
11
- * `dedup_status='removed'` (kept, not deleted — retrieval excludes it). Keeps
12
- * the row with the higher `tokenEstimate` (more context preserved). Runs as a
13
- * single scan; idempotent (re-running skips already-removed rows).
14
- *
15
- * Returns the number of rows marked removed.
16
- */
17
- export function semDedupStore(ctx, sessionId, threshold) {
18
- const sid = normalizeSessionId(sessionId);
19
- const cps = listCheckpoints(sid, ctx.stateDir).filter((c) => c.dedupStatus !== "removed");
20
- let removed = 0;
21
- for (let i = 0; i < cps.length; i++) {
22
- for (let j = i + 1; j < cps.length; j++) {
23
- const a = cps[i];
24
- const b = cps[j];
25
- if (a.dedupStatus === "removed" || b.dedupStatus === "removed")
26
- continue;
27
- if (cosineSimilarity(a.embedding, b.embedding) > threshold) {
28
- // Keep the higher-tokenEstimate row; remove the other.
29
- const keep = a.tokenEstimate >= b.tokenEstimate ? a : b;
30
- const drop = keep === a ? b : a;
31
- setDedupStatus(drop.checkpointId, sid, "removed", ctx.stateDir);
32
- drop.dedupStatus = "removed";
33
- removed++;
34
- }
35
- }
36
- }
37
- return removed;
38
- }
39
- /**
40
- * Dedup sentinel check: has this region already been stored/represented?
41
- * Consulted by both the persist path and the recall/inline path.
42
- */
43
- export function dedupeCheck(ctx, sessionId, regionHashOrText, isText = false) {
44
- const sid = normalizeSessionId(sessionId);
45
- const hash = isText
46
- ? computeRegionHash(regionHashOrText)
47
- : regionHashOrText;
48
- const state = loadSessionState(sid, ctx.stateDir);
49
- if (state.storedRegionHashes.includes(hash))
50
- return true;
51
- return listCheckpoints(sid, ctx.stateDir).some((c) => c.regionHash === hash);
52
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * vectorStore/index.ts — Barrel re-export for the vector store sub-modules.
3
- *
4
- * All public symbols previously exported from the monolithic `vectorStore.ts`
5
- * are re-exported here so existing imports (`from "./vectorStore.js"`) continue
6
- * to work unchanged.
7
- */
8
- export { L2_ENABLED } from "./types.js";
9
- export { computeRegionHash } from "./utils.js";
10
- export { VectorStore } from "./store.js";
@@ -1,83 +0,0 @@
1
- /**
2
- * vectorStore/queries.ts — Read-only queries: list, topSimilar, stats, repoStats,
3
- * dataInvariant, similarity.
4
- */
5
- import { cosineSimilarity } from "../embedder.js";
6
- import { normalizeSessionId } from "../store.js";
7
- import { listCheckpoints, loadSessionState, getDedupStats, repoStats as repoStatsFromStore, dataInvariantStats, } from "../store/sqlite.js";
8
- /** All checkpoints for a session (sorted by checkpointId). */
9
- export function listSession(ctx, sessionId) {
10
- return listCheckpoints(normalizeSessionId(sessionId), ctx.stateDir);
11
- }
12
- /** Convenience for a raw vector cosine (exposed for tests). */
13
- export function similarityScore(a, b) {
14
- return cosineSimilarity(a, b);
15
- }
16
- /**
17
- * Return the n most similar checkpoints to the current (most recent) checkpoint
18
- * by cosine similarity. Returns fewer than n if the session has fewer checkpoints.
19
- * The current checkpoint itself is excluded from results.
20
- */
21
- export function topSimilarSession(ctx, sessionId, n) {
22
- const sid = normalizeSessionId(sessionId);
23
- const checkpoints = listCheckpoints(sid, ctx.stateDir);
24
- if (checkpoints.length <= 1)
25
- return [];
26
- // Find the most recent checkpoint (by checkpointId, which is sequential)
27
- const ordered = [...checkpoints].sort((a, b) => a.checkpointId.localeCompare(b.checkpointId));
28
- const current = ordered[ordered.length - 1];
29
- // Score all other checkpoints by similarity to current
30
- const scored = ordered
31
- .filter((cp) => cp.checkpointId !== current.checkpointId)
32
- .map((cp) => ({
33
- checkpoint: cp,
34
- score: cosineSimilarity(current.embedding, cp.embedding),
35
- }))
36
- .sort((a, b) => b.score - a.score);
37
- return scored.slice(0, n);
38
- }
39
- /**
40
- * Store statistics for status reporting / logging. Returns counts + the last
41
- * (highest-numbered) checkpoint, or nulls when the session is empty.
42
- */
43
- export function statsSession(ctx, sessionId) {
44
- const sid = normalizeSessionId(sessionId);
45
- const cps = listCheckpoints(sid, ctx.stateDir);
46
- const state = loadSessionState(sid, ctx.stateDir);
47
- const ordered = [...cps].sort((a, b) => a.checkpointId.localeCompare(b.checkpointId));
48
- const last = ordered[ordered.length - 1];
49
- const injected = state.injectedCheckpointIds.length;
50
- const ds = getDedupStats(ctx.stateDir);
51
- const sessionTok = cps.reduce((s, c) => s + (c.tokenEstimate ?? 0), 0);
52
- const sessionOrig = cps.reduce((s, c) => s + (c.originalTokenEstimate ?? 0), 0);
53
- // Per-session "tokens saved" = Σ(original − stored) over this session's
54
- // stored checkpoints. Deduped adds (whole region discarded, nothing stored)
55
- // are counted in the repo-wide meta counter via repoStats(); the per-session
56
- // DB sum here covers the rows that exist.
57
- const sessionSaved = cps.reduce((s, c) => s + Math.max(0, (c.originalTokenEstimate ?? 0) - (c.tokenEstimate ?? 0)), 0);
58
- return {
59
- checkpointCount: cps.length,
60
- totalTokenEstimate: sessionTok,
61
- lastCheckpointId: last?.checkpointId,
62
- lastSummary: last?.summary,
63
- injectedCount: injected,
64
- dedupHitRate: cps.length === 0 ? 0 : injected / cps.length,
65
- storageDedupRate: ds.attempts === 0 ? 0 : ds.deduped / ds.attempts,
66
- tokensSaved: sessionSaved,
67
- originalTokens: sessionOrig,
68
- dedupAttempts: ds.attempts,
69
- dedupCollapsed: ds.deduped,
70
- };
71
- }
72
- /**
73
- * Repo-wide stats — aggregates every session in this store (one per repo).
74
- * Cumulative, resumable, cross-device. Surfaces the dashboard's "Repo …"
75
- * figures; distinct from {@link statsSession} (per-session).
76
- */
77
- export function repoStatsStore(ctx) {
78
- return repoStatsFromStore(ctx.stateDir);
79
- }
80
- /** Data-safety invariant (Phase 0): regions retained vs bytes permanently deleted. */
81
- export function dataInvariantStore(ctx) {
82
- return dataInvariantStats(ctx.stateDir);
83
- }
@@ -1,95 +0,0 @@
1
- /**
2
- * vectorStore/search.ts — Semantic search and RAPTOR integration.
3
- */
4
- import { cosineSimilarity } from "../embedder.js";
5
- import { normalizeSessionId } from "../store.js";
6
- import { mmrRerank } from "../dedup/mmr.js";
7
- import { topK } from "../dedup/topk.js";
8
- import { listCheckpoints } from "../store/sqlite.js";
9
- import { rehydrateRaptorTree } from "../dedup/raptor/index.js";
10
- import { stagedExpansion } from "../dedup/raptor/retrieval.js";
11
- /**
12
- * Serve the RAPTOR tree for a query (Fix D): rehydrate the persisted tree and
13
- * return its staged-expansion leaf hits as SearchHits. Returns [] when no tree
14
- * exists (small sessions — flat search remains the path). Best-effort/non-fatal.
15
- */
16
- export function raptorSearchHits(ctx, sid, query, k) {
17
- try {
18
- const tree = rehydrateRaptorTree(sid, ctx.stateDir);
19
- if (!tree || !tree.rootId)
20
- return [];
21
- const leafIds = stagedExpansion(query, tree, {
22
- embedder: ctx.embedder,
23
- k,
24
- topM: ctx.cfg.RAPTOR_CLUSTERS_PER_LEVEL,
25
- mmrLambda: ctx.cfg.MMR_LAMBDA,
26
- });
27
- if (leafIds.length === 0)
28
- return [];
29
- const all = listCheckpoints(sid, ctx.stateDir).filter((cp) => cp.dedupStatus !== "removed");
30
- const qv = ctx.embedder.embed(query);
31
- const hits = [];
32
- for (const id of leafIds) {
33
- const cp = all.find((c) => c.checkpointId === id);
34
- if (cp)
35
- hits.push({ checkpoint: cp, score: cosineSimilarity(qv, cp.embedding) });
36
- }
37
- return hits;
38
- }
39
- catch {
40
- return [];
41
- }
42
- }
43
- /**
44
- * Semantic search within a session's checkpoints. Returns top-K by cosine
45
- * similarity, diversified via MMR (QA #10) so a cluster of near-identical
46
- * hits yields at most a few distinct-relevance results.
47
- *
48
- * Heap-based top-K (QA #4, O(N log k)) replaces the old full sort; MMR then
49
- * reranks the candidate window for diversity.
50
- */
51
- export function searchStore(ctx, sessionId, query, k = 3) {
52
- const sid = normalizeSessionId(sessionId);
53
- const checkpoints = listCheckpoints(sid, ctx.stateDir).filter((cp) => cp.dedupStatus !== "removed");
54
- if (checkpoints.length === 0)
55
- return [];
56
- const qv = ctx.embedder.embed(query);
57
- const scored = checkpoints.map((cp) => ({
58
- checkpoint: cp,
59
- score: cosineSimilarity(qv, cp.embedding),
60
- }));
61
- // Heap top-K over a widened window (2k) so MMR has diverse candidates.
62
- const window = topK(scored.map((h) => ({ item: h, score: h.score })), Math.max(k * 2, k)).map((s) => s.item);
63
- // MMR (QA #10) is part of the L2 semantic tier: skip it when L2 is disabled
64
- // (Sprint 14 flag), returning the plain relevance-ranked window instead.
65
- if (!ctx.cfg.L2_ENABLED)
66
- return window.slice(0, k);
67
- // Fix D: when RAPTOR is promoted, ALSO recall high-level tree summaries and
68
- // merge them with the flat hits via MMR so RAPTOR + flat don't double-cover.
69
- // RAPTOR returns fewer, broader hits (O(log n) high-level nodes) than the
70
- // O(n) flat leaves, tightening the block at read time.
71
- if (ctx.cfg.RAPTOR_ENABLED) {
72
- const raptorHits = raptorSearchHits(ctx, sid, query, k);
73
- if (raptorHits.length > 0) {
74
- const merged = [...window];
75
- for (const rh of raptorHits) {
76
- if (!merged.some((m) => m.checkpoint.checkpointId === rh.checkpoint.checkpointId)) {
77
- merged.push(rh);
78
- }
79
- }
80
- const mmrItems = merged.map((h) => ({
81
- item: h,
82
- vector: h.checkpoint.embedding,
83
- relevance: h.score,
84
- }));
85
- return mmrRerank(mmrItems, k, ctx.cfg.MMR_LAMBDA);
86
- }
87
- }
88
- const mmrItems = window.map((h) => ({
89
- item: h,
90
- vector: h.checkpoint.embedding,
91
- relevance: h.score,
92
- }));
93
- const ranked = mmrRerank(mmrItems, k, ctx.cfg.MMR_LAMBDA);
94
- return ranked;
95
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * vectorStore/session.ts — Session-state mutations (injection tracking).
3
- */
4
- import { normalizeSessionId } from "../store.js";
5
- import { loadSessionState, saveSessionState } from "../store/sqlite.js";
6
- /** Mark a checkpoint as injected into the window (recall dedup). */
7
- export function markInjectedStore(ctx, sessionId, checkpointId) {
8
- const sid = normalizeSessionId(sessionId);
9
- const state = loadSessionState(sid, ctx.stateDir);
10
- if (!state.injectedCheckpointIds.includes(checkpointId)) {
11
- state.injectedCheckpointIds.push(checkpointId);
12
- saveSessionState(sid, state, ctx.stateDir);
13
- }
14
- }
15
- /** True if this checkpoint was already injected this session. */
16
- export function wasInjectedStore(ctx, sessionId, checkpointId) {
17
- const state = loadSessionState(normalizeSessionId(sessionId), ctx.stateDir);
18
- return state.injectedCheckpointIds.includes(checkpointId);
19
- }
@@ -1,105 +0,0 @@
1
- /**
2
- * vectorStore/store.ts — VectorStore class (thin wrapper delegating to
3
- * sub-modules). Public API is identical to the pre-split monolith.
4
- */
5
- import { defaultEmbedder } from "../embedder.js";
6
- import { loadDedupConfig } from "../config/dedup.js";
7
- import { getStateDir } from "../store.js";
8
- import { openBloom } from "../store/bloom.js";
9
- import { migrateJsonToSqlite } from "../store/migrate.js";
10
- import { addToStore } from "./add.js";
11
- import { searchStore } from "./search.js";
12
- import { semDedupStore, dedupeCheck } from "./dedup.js";
13
- import { markInjectedStore, wasInjectedStore } from "./session.js";
14
- import { listSession, topSimilarSession, statsSession, repoStatsStore, dataInvariantStore, similarityScore, } from "./queries.js";
15
- export class VectorStore {
16
- ctx;
17
- constructor(opts = {}) {
18
- const embedder = opts.embedder ?? defaultEmbedder();
19
- const stateDir = opts.stateDir ?? getStateDir();
20
- // Sprint 14: all tier flags/thresholds flow from the single config source
21
- // (DedupConfig). The legacy opts.dedupSim / opts.l2Enabled remain accepted
22
- // for backward-compat callers but flags are authoritative via `cfg`.
23
- void opts.dedupSim;
24
- void opts.l2Enabled;
25
- // Sprint 12 L2 semantic tier. Threshold 0.85 is the default trigram
26
- // embedder's honest firing point; a direct override is allowed for tests.
27
- const cfg = opts.config ?? loadDedupConfig();
28
- const l2Threshold = opts.l2Threshold ?? cfg.L2_COSINE;
29
- this.ctx = {
30
- embedder,
31
- stateDir,
32
- l2Threshold,
33
- cfg,
34
- eventsPath: opts.eventsPath,
35
- };
36
- // Sprint 8: bring any v0.1.0 JSON checkpoint files into SQLite (idempotent).
37
- migrateJsonToSqlite(stateDir);
38
- // Sprint 10: warm the bloom accelerator (accelerator only — SQLite stays
39
- // source of truth; a bloom hit is always confirmed by a query below).
40
- openBloom(stateDir);
41
- }
42
- /** @internal Shared context for sub-module operations. */
43
- get context() {
44
- return this.ctx;
45
- }
46
- /** @internal Kept for test compatibility — delegates to ctx.stateDir. */
47
- get stateDir() {
48
- return this.ctx.stateDir;
49
- }
50
- add(input) {
51
- return addToStore(this.ctx, input);
52
- }
53
- search(sessionId, query, k = 3) {
54
- return searchStore(this.ctx, sessionId, query, k);
55
- }
56
- semDedup(sessionId, threshold = this.ctx.cfg.SEMDEDUP_COSINE) {
57
- return semDedupStore(this.ctx, sessionId, threshold);
58
- }
59
- dedupe(sessionId, regionHashOrText, isText = false) {
60
- return dedupeCheck(this.ctx, sessionId, regionHashOrText, isText);
61
- }
62
- /** Mark a checkpoint as injected into the window (recall dedup). */
63
- markInjected(sessionId, checkpointId) {
64
- markInjectedStore(this.ctx, sessionId, checkpointId);
65
- }
66
- /** True if this checkpoint was already injected this session. */
67
- wasInjected(sessionId, checkpointId) {
68
- return wasInjectedStore(this.ctx, sessionId, checkpointId);
69
- }
70
- /** Convenience for a raw vector cosine (exposed for tests). */
71
- similarity(a, b) {
72
- return similarityScore(a, b);
73
- }
74
- /** All checkpoints for a session (sorted by checkpointId). */
75
- list(sessionId) {
76
- return listSession(this.ctx, sessionId);
77
- }
78
- /**
79
- * Return the n most similar checkpoints to the current (most recent) checkpoint
80
- * by cosine similarity. Returns fewer than n if the session has fewer checkpoints.
81
- * The current checkpoint itself is excluded from results.
82
- */
83
- topSimilar(sessionId, n) {
84
- return topSimilarSession(this.ctx, sessionId, n);
85
- }
86
- /**
87
- * Store statistics for status reporting / logging. Returns counts + the last
88
- * (highest-numbered) checkpoint, or nulls when the session is empty.
89
- */
90
- stats(sessionId) {
91
- return statsSession(this.ctx, sessionId);
92
- }
93
- /**
94
- * Repo-wide stats — aggregates every session in this store (one per repo).
95
- * Cumulative, resumable, cross-device. Surfaces the dashboard's "Repo …"
96
- * figures; distinct from {@link stats} (per-session).
97
- */
98
- repoStats() {
99
- return repoStatsStore(this.ctx);
100
- }
101
- /** Data-safety invariant (Phase 0): regions retained vs bytes permanently deleted. */
102
- dataInvariant() {
103
- return dataInvariantStore(this.ctx);
104
- }
105
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * vectorStore/types.ts — Shared types, constants, and context interface for the
3
- * vector store sub-modules.
4
- */
5
- /** Default L2 semantic-dedup enable flag (trigram embedder is local, zero-network). */
6
- export const L2_ENABLED = true;
@@ -1,23 +0,0 @@
1
- /**
2
- * vectorStore/utils.ts — Hashing and monitoring utilities.
3
- */
4
- import { createHash } from "node:crypto";
5
- import { logDecision } from "../monitoring.js";
6
- /** Stable hash of a compacted region, the dedup sentinel key. */
7
- export function computeRegionHash(regionText) {
8
- // Normalize whitespace before hashing so "foo bar" and "foo bar" dedup.
9
- const normalized = regionText.replace(/\s+/g, " ").trim();
10
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
11
- }
12
- /** Emit a structured dedup-decision event (best-effort, never throws). */
13
- export function recordDecision(ctx, tier, result, reason, latencyMs) {
14
- if (!ctx.eventsPath)
15
- return;
16
- logDecision(ctx.eventsPath, {
17
- ts: Date.now(),
18
- tier,
19
- result,
20
- reason,
21
- latencyMs: Math.round(latencyMs * 100) / 100,
22
- });
23
- }