pi-mega-compact 0.8.23 → 0.8.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +8 -0
- package/dist/extensions/dashboard-server/api-contracts/game-types.js +7 -0
- package/dist/extensions/mega-compact-s38.test.js +263 -14
- package/dist/extensions/mega-compact.js +15 -0
- package/dist/extensions/mega-config.js +3 -0
- package/dist/extensions/mega-events/agent-handlers.js +211 -26
- package/dist/extensions/mega-events/context-handler.js +45 -7
- package/dist/extensions/mega-events/error-classifier.js +125 -18
- package/dist/extensions/mega-pipeline/compact.js +24 -13
- package/dist/extensions/mega-pipeline/recall.js +31 -2
- package/dist/extensions/mega-runtime/append-event.js +24 -0
- package/dist/extensions/mega-runtime/bind-repo.js +65 -0
- package/dist/extensions/mega-runtime/capture-model.js +87 -0
- package/dist/extensions/mega-runtime/dashboard-snapshot.js +122 -0
- package/dist/extensions/mega-runtime/effects.js +86 -0
- package/dist/extensions/mega-runtime/engine-view.js +11 -0
- package/dist/extensions/mega-runtime/game-state.js +116 -0
- package/dist/extensions/mega-runtime/get-state-dir.js +10 -0
- package/dist/extensions/mega-runtime/perf.js +49 -0
- package/dist/extensions/mega-runtime/pressure-getters.js +64 -0
- package/dist/extensions/mega-runtime/render-widget.js +17 -0
- package/dist/extensions/mega-runtime/reset-runtime.js +50 -0
- package/dist/extensions/mega-runtime/runtime-helpers.js +73 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +208 -0
- package/dist/extensions/mega-runtime/runtime.js +405 -0
- package/dist/extensions/mega-runtime/snapshot.js +142 -0
- package/dist/extensions/mega-runtime/state.js +5 -1151
- package/dist/extensions/mega-runtime/status.js +11 -0
- package/dist/extensions/mega-runtime/widget-ansi.js +207 -0
- package/dist/extensions/mega-runtime/widget-types.js +8 -0
- package/dist/extensions/mega-runtime/widget.js +15 -204
- package/dist/extensions/openclaw-mega-compact.js +291 -0
- package/dist/src/boundary.js +79 -43
- package/dist/src/boundary.test.js +119 -2
- package/dist/src/canary.js +10 -0
- package/dist/src/config/dedup.js +14 -0
- package/dist/src/config.js +3 -1
- package/dist/src/dedup/raptor/buildHistory.js +164 -0
- package/dist/src/dedup/raptor/buildHistory.test.js +292 -0
- package/dist/src/dedup/raptor/index.js +38 -0
- package/dist/src/dedup/raptor/multilevel-serve.test.js +229 -0
- package/dist/src/dedup/raptor/multilevel.js +17 -5
- package/dist/src/dedup/raptor/multilevel.test.js +36 -1
- package/dist/src/dedup/raptor/raptor.test.js +43 -0
- package/dist/src/dedup/raptor/retrieval.js +14 -2
- package/dist/src/dedup/raptor/retrieval.test.js +95 -0
- package/dist/src/dedup/raptor/serve-gate.test.js +298 -0
- package/dist/src/dedup/raptor/summarizer.js +1 -0
- package/dist/src/dedup/raptor/tree.js +16 -2
- package/dist/src/engine.js +18 -2
- package/dist/src/httpEmbedder.js +96 -6
- package/dist/src/httpEmbedder.test.js +277 -0
- package/dist/src/mechanical-fix.test.js +65 -0
- package/dist/src/minilm.js +92 -0
- package/dist/src/raptor-inject-summaries.test.js +155 -0
- package/dist/src/recall.js +135 -21
- package/dist/src/recall.test.js +179 -4
- package/dist/src/store/sqlite/dedup-mirror.js +32 -15
- package/dist/src/store/sqlite/maintenance.js +2 -2
- package/dist/src/store/sqlite/mechanical-fix.test.js +146 -0
- package/dist/src/store/sqlite/memories.js +5 -5
- package/dist/src/store/sqlite/meta.js +1 -1
- package/dist/src/store/sqlite/raptor.js +56 -17
- package/dist/src/store/sqlite/raptor.test.js +106 -0
- package/dist/src/store/sqlite/schema.js +90 -1
- package/dist/src/store/sqlite/session-state.js +9 -3
- package/dist/src/store/sqlite/stats.js +9 -5
- package/dist/src/store/sqlite/turns.js +181 -0
- package/dist/src/store/sqlite/turns.test.js +183 -0
- package/dist/src/store/sqlite/utils.js +15 -4
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/store.js +2 -2
- package/dist/src/vector-search-cache.test.js +157 -0
- package/dist/src/vector-search.js +107 -15
- package/dist/src/vectorStore.js +36 -8
- package/dist/src/wordpiece.js +129 -0
- package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +1 -1
- package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -155
- package/extensions/dashboard-server/api-contracts/game-types.ts +172 -0
- package/extensions/mega-compact-s38.test.ts +259 -14
- package/extensions/mega-compact.ts +15 -0
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-dashboard.ts +10 -1
- package/extensions/mega-events/agent-handlers.ts +211 -26
- package/extensions/mega-events/context-handler.ts +43 -7
- package/extensions/mega-events/error-classifier.ts +125 -17
- package/extensions/mega-pipeline/compact.ts +28 -16
- package/extensions/mega-pipeline/recall.ts +34 -2
- package/extensions/mega-runtime/DECOMPOSITION.md +180 -0
- package/extensions/mega-runtime/README.md +38 -0
- package/extensions/mega-runtime/append-event.ts +40 -0
- package/extensions/mega-runtime/bind-repo.ts +81 -0
- package/extensions/mega-runtime/capture-model.ts +101 -0
- package/extensions/mega-runtime/dashboard-snapshot.ts +181 -0
- package/extensions/mega-runtime/effects.ts +129 -0
- package/extensions/mega-runtime/engine-view.ts +17 -0
- package/extensions/mega-runtime/game-state.ts +149 -0
- package/extensions/mega-runtime/get-state-dir.ts +19 -0
- package/extensions/mega-runtime/helpers.ts +25 -1
- package/extensions/mega-runtime/perf.ts +60 -0
- package/extensions/mega-runtime/pressure-getters.ts +96 -0
- package/extensions/mega-runtime/render-widget.ts +41 -0
- package/extensions/mega-runtime/runtime-helpers.ts +119 -0
- package/extensions/mega-runtime/runtime-snapshot.ts +293 -0
- package/extensions/mega-runtime/runtime.ts +483 -0
- package/extensions/mega-runtime/snapshot.ts +230 -0
- package/extensions/mega-runtime/state.ts +5 -1268
- package/extensions/mega-runtime/status.ts +26 -0
- package/extensions/mega-runtime/widget-ansi.ts +217 -0
- package/extensions/mega-runtime/widget-types.ts +80 -0
- package/extensions/mega-runtime/widget.ts +34 -285
- package/package.json +1 -1
- package/src/boundary.test.ts +128 -2
- package/src/boundary.ts +75 -39
- package/src/canary.ts +10 -0
- package/src/config/dedup.ts +25 -0
- package/src/config.ts +3 -1
- package/src/dedup/raptor/buildHistory.test.ts +353 -0
- package/src/dedup/raptor/buildHistory.ts +259 -0
- package/src/dedup/raptor/index.ts +38 -0
- package/src/dedup/raptor/multilevel-serve.test.ts +273 -0
- package/src/dedup/raptor/multilevel.test.ts +47 -0
- package/src/dedup/raptor/multilevel.ts +18 -8
- package/src/dedup/raptor/raptor.test.ts +59 -0
- package/src/dedup/raptor/retrieval.test.ts +118 -0
- package/src/dedup/raptor/retrieval.ts +14 -2
- package/src/dedup/raptor/serve-gate.test.ts +348 -0
- package/src/dedup/raptor/summarizer.ts +1 -0
- package/src/dedup/raptor/tree.ts +17 -2
- package/src/engine.ts +32 -3
- package/src/httpEmbedder.test.ts +286 -0
- package/src/httpEmbedder.ts +98 -8
- package/src/mechanical-fix.test.ts +70 -0
- package/src/raptor-inject-summaries.test.ts +212 -0
- package/src/recall.test.ts +220 -4
- package/src/recall.ts +151 -22
- package/src/store/sqlite/dedup-mirror.ts +35 -18
- package/src/store/sqlite/maintenance.ts +2 -2
- package/src/store/sqlite/mechanical-fix.test.ts +162 -0
- package/src/store/sqlite/memories.ts +5 -5
- package/src/store/sqlite/meta.ts +1 -1
- package/src/store/sqlite/raptor.test.ts +139 -0
- package/src/store/sqlite/raptor.ts +135 -81
- package/src/store/sqlite/schema.ts +90 -1
- package/src/store/sqlite/session-state.ts +9 -3
- package/src/store/sqlite/stats.ts +10 -8
- package/src/store/sqlite/turns.test.ts +218 -0
- package/src/store/sqlite/turns.ts +292 -0
- package/src/store/sqlite/utils.ts +14 -4
- package/src/store/sqlite.ts +1 -0
- package/src/store.ts +9 -2
- package/src/vector-search-cache.test.ts +190 -0
- package/src/vector-search.ts +273 -156
- package/src/vectorStore.ts +443 -382
- package/dist/extensions/dashboard-client/src/hooks/useApi.js +0 -51
- package/dist/extensions/dashboard-client/src/hooks/useSSE.js +0 -63
|
@@ -14,10 +14,13 @@ import { cosineSimilarity } from "./embedder.js";
|
|
|
14
14
|
import { normalizeSessionId } from "./store.js";
|
|
15
15
|
import { mmrRerank } from "./dedup/mmr.js";
|
|
16
16
|
import { topK } from "./dedup/topk.js";
|
|
17
|
-
import { listCheckpoints, getCheckpoint, maxCheckpointTimestamp, } from "./store/sqlite.js";
|
|
17
|
+
import { listCheckpoints, getCheckpoint, maxCheckpointTimestamp, maxRaptorNodeBuiltAt, } from "./store/sqlite.js";
|
|
18
18
|
import { initVectorIndex, searchAsync as vectorIndexSearch, } from "./store/vectorIndex.js";
|
|
19
19
|
import { rehydrateRaptorTree, isShadowMode } from "./dedup/raptor/index.js";
|
|
20
20
|
import { stagedExpansion } from "./dedup/raptor/retrieval.js";
|
|
21
|
+
import { multilevelRetrieval } from "./dedup/raptor/multilevel.js";
|
|
22
|
+
import { Logger } from "./log.js";
|
|
23
|
+
const logger = new Logger();
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
22
25
|
// raptorSearchHits — internal helper (NOT exported)
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
@@ -25,8 +28,13 @@ import { stagedExpansion } from "./dedup/raptor/retrieval.js";
|
|
|
25
28
|
* Serve the RAPTOR tree for a query (Fix D): rehydrate the persisted tree and
|
|
26
29
|
* return its staged-expansion leaf hits as SearchHits. Returns [] when no tree
|
|
27
30
|
* exists (small sessions — flat search remains the path). Best-effort/non-fatal.
|
|
31
|
+
*
|
|
32
|
+
* `checkpoints` is the caller's already-loaded, dedup-status-filtered checkpoint
|
|
33
|
+
* list — pass it in so the flat-search path and the RAPTOR merge path share ONE
|
|
34
|
+
* `listCheckpoints` call per `vectorSearch` (eliminates a redundant full scan +
|
|
35
|
+
* N-row hydration at 500+ checkpoints — QA perf review).
|
|
28
36
|
*/
|
|
29
|
-
function raptorSearchHits(store, sid, query, k) {
|
|
37
|
+
function raptorSearchHits(store, sid, query, k, checkpoints) {
|
|
30
38
|
const t0 = Date.now();
|
|
31
39
|
try {
|
|
32
40
|
const stateDir = store.stateDir;
|
|
@@ -38,17 +46,93 @@ function raptorSearchHits(store, sid, query, k) {
|
|
|
38
46
|
// RAPTOR_SHADOW_MODE is anything other than "false".
|
|
39
47
|
if (isShadowMode())
|
|
40
48
|
return [];
|
|
41
|
-
|
|
49
|
+
// S25 gate (b): freshness + fallback guards with per-session cache.
|
|
50
|
+
// The cache avoids O(n·leaves) rehydrate on every search when the tree
|
|
51
|
+
// hasn't changed. Freshness is validated via maxRaptorNodeBuiltAt (cheap
|
|
52
|
+
// indexed MAX query) — if the persisted builtAt matches the cached builtAt,
|
|
53
|
+
// the tree hasn't been rebuilt and can be served from cache.
|
|
54
|
+
const cacheKey = `${stateDir}::${sid}`;
|
|
55
|
+
const cached = store.raptorCache.get(cacheKey);
|
|
56
|
+
const latestBuiltAt = maxRaptorNodeBuiltAt(sid, stateDir);
|
|
57
|
+
let tree;
|
|
58
|
+
if (cached && cached.builtAt === latestBuiltAt && latestBuiltAt > 0) {
|
|
59
|
+
tree = cached.tree;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
tree = rehydrateRaptorTree(sid, stateDir);
|
|
63
|
+
if (tree && tree.rootId && !tree.timedOut) {
|
|
64
|
+
store.raptorCache.set(cacheKey, { tree, builtAt: tree.builtAt ?? 0 });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
store.raptorCache.delete(cacheKey);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
42
70
|
if (!tree || !tree.rootId)
|
|
43
71
|
return [];
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// whose root is a budget-exhausted extractive fallback (level 99).
|
|
47
|
-
if (tree.timedOut)
|
|
48
|
-
return [];
|
|
72
|
+
// Freshness: skip persisted tree if the session has been compacted since
|
|
73
|
+
// the tree was built. The next runRaptor will rebuild.
|
|
49
74
|
const maxTs = maxCheckpointTimestamp(sid, stateDir);
|
|
50
75
|
if (tree.builtAt && tree.builtAt < maxTs)
|
|
51
76
|
return [];
|
|
77
|
+
// S25: skip timedOut extractive-fallback trees (level === 99).
|
|
78
|
+
if (tree.timedOut)
|
|
79
|
+
return [];
|
|
80
|
+
// Use the caller's already-loaded checkpoint list (no second scan).
|
|
81
|
+
const all = checkpoints;
|
|
82
|
+
const qv = embedder.embed(query);
|
|
83
|
+
const hits = [];
|
|
84
|
+
// S42B: multi-level retrieval — score all tree levels (leaves + internal
|
|
85
|
+
// clusters), expand cluster hits to their leaf descendants, dedup overlaps,
|
|
86
|
+
// and MMR-rerank. Default ON (RAPTOR_MULTILEVEL_ENABLED). When OFF, the
|
|
87
|
+
// leaf-only stagedExpansion path below is used (identical to pre-S42B).
|
|
88
|
+
if (cfg.RAPTOR_MULTILEVEL_ENABLED) {
|
|
89
|
+
const mlHits = multilevelRetrieval(query, tree, {
|
|
90
|
+
embedder,
|
|
91
|
+
levelWeights: cfg.RAPTOR_LEVEL_WEIGHTS,
|
|
92
|
+
leafExpansion: cfg.RAPTOR_LEAF_EXPANSION,
|
|
93
|
+
maxLeafExpansion: cfg.RAPTOR_MAX_LEAF_EXPANSION,
|
|
94
|
+
k,
|
|
95
|
+
mmrLambda: cfg.MMR_LAMBDA,
|
|
96
|
+
});
|
|
97
|
+
if (mlHits.length === 0)
|
|
98
|
+
return [];
|
|
99
|
+
for (const mh of mlHits) {
|
|
100
|
+
// Leaf hits: nodeId IS the checkpointId — hydrate from the stored list.
|
|
101
|
+
const cp = all.find((c) => c.checkpointId === mh.nodeId);
|
|
102
|
+
if (cp) {
|
|
103
|
+
hits.push({ checkpoint: cp, score: mh.score });
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Orphaned leaf (checkpoint missing from the stored list, e.g. SemDeDup
|
|
107
|
+
// removed) — skip; synthesizing it would yield an empty cluster block.
|
|
108
|
+
if (mh.isLeaf)
|
|
109
|
+
continue;
|
|
110
|
+
// Cluster hit: synthesize a SearchHit so the recall block can surface
|
|
111
|
+
// the hierarchical summary (not a stored checkpoint). The minimal
|
|
112
|
+
// StoredCheckpoint carries the node's centroid embedding for any
|
|
113
|
+
// downstream cosine / MMR the caller does.
|
|
114
|
+
hits.push({
|
|
115
|
+
checkpoint: {
|
|
116
|
+
checkpointId: mh.nodeId,
|
|
117
|
+
sessionId: sid,
|
|
118
|
+
summary: mh.summary,
|
|
119
|
+
keyDecisions: [],
|
|
120
|
+
nextSteps: [],
|
|
121
|
+
filesModified: [],
|
|
122
|
+
tokenEstimate: 0,
|
|
123
|
+
regionHash: `raptor:${mh.nodeId}`,
|
|
124
|
+
embedding: mh.embedding,
|
|
125
|
+
timestamp: tree.builtAt ?? Date.now(),
|
|
126
|
+
},
|
|
127
|
+
score: mh.score,
|
|
128
|
+
raptorSummary: mh.summary,
|
|
129
|
+
raptorLevel: mh.level,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
record("RAPTOR", hits.length > 0 ? "new" : "mark_only", `ml=${mlHits.length}`, Date.now() - t0);
|
|
133
|
+
return hits;
|
|
134
|
+
}
|
|
135
|
+
// S42B: flag OFF — leaf-only stagedExpansion (pre-S42B production path).
|
|
52
136
|
const leafIds = stagedExpansion(query, tree, {
|
|
53
137
|
embedder,
|
|
54
138
|
k,
|
|
@@ -57,20 +141,24 @@ function raptorSearchHits(store, sid, query, k) {
|
|
|
57
141
|
});
|
|
58
142
|
if (leafIds.length === 0)
|
|
59
143
|
return [];
|
|
60
|
-
const all = listCheckpoints(sid, stateDir).filter((cp) => cp.dedupStatus !== "removed");
|
|
61
|
-
const qv = embedder.embed(query);
|
|
62
|
-
const hits = [];
|
|
63
144
|
for (const id of leafIds) {
|
|
64
145
|
const cp = all.find((c) => c.checkpointId === id);
|
|
65
146
|
if (cp)
|
|
66
|
-
hits.push({
|
|
147
|
+
hits.push({
|
|
148
|
+
checkpoint: cp,
|
|
149
|
+
score: cosineSimilarity(qv, cp.embedding),
|
|
150
|
+
});
|
|
67
151
|
}
|
|
68
152
|
// S25 monitoring: emit a raptor_serve decision so canary.ts can track
|
|
69
153
|
// p95 latency + the tier's live traffic (non-fatal, best-effort).
|
|
70
154
|
record("RAPTOR", hits.length > 0 ? "new" : "mark_only", `leaves=${leafIds.length}`, Date.now() - t0);
|
|
71
155
|
return hits;
|
|
72
156
|
}
|
|
73
|
-
catch {
|
|
157
|
+
catch (e) {
|
|
158
|
+
logger.warn("raptor_search_error", {
|
|
159
|
+
error: String(e),
|
|
160
|
+
sessionId: sid,
|
|
161
|
+
});
|
|
74
162
|
return [];
|
|
75
163
|
}
|
|
76
164
|
}
|
|
@@ -109,7 +197,7 @@ export function vectorSearch(store, sessionId, query, k = 3) {
|
|
|
109
197
|
// RAPTOR returns fewer, broader hits (O(log n) high-level nodes) than the
|
|
110
198
|
// O(n) flat leaves, tightening the block at read time.
|
|
111
199
|
if (cfg.RAPTOR_ENABLED) {
|
|
112
|
-
const rh = raptorSearchHits(store, sid, query, k);
|
|
200
|
+
const rh = raptorSearchHits(store, sid, query, k, checkpoints);
|
|
113
201
|
if (rh.length > 0) {
|
|
114
202
|
const merged = [...window];
|
|
115
203
|
for (const h of rh) {
|
|
@@ -176,7 +264,11 @@ export async function vectorSearchAsync(store, sessionId, query, k = 3, opts = {
|
|
|
176
264
|
const cp = getCheckpoint(h.sessionId, h.checkpointId, h.repoId);
|
|
177
265
|
if (cp && cp.dedupStatus !== "removed") {
|
|
178
266
|
const crossRepo = opts.crossRepo && selfRepo && h.repoId && h.repoId !== selfRepo;
|
|
179
|
-
hydrated.push({
|
|
267
|
+
hydrated.push({
|
|
268
|
+
checkpoint: cp,
|
|
269
|
+
score: h.score,
|
|
270
|
+
repoId: crossRepo ? h.repoId : undefined,
|
|
271
|
+
});
|
|
180
272
|
}
|
|
181
273
|
}
|
|
182
274
|
if (hydrated.length === 0)
|
package/dist/src/vectorStore.js
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import { cosineSimilarity, defaultEmbedder } from "./embedder.js";
|
|
12
|
-
import { loadDedupConfig } from "./config/dedup.js";
|
|
12
|
+
import { loadDedupConfig, } from "./config/dedup.js";
|
|
13
13
|
import { logDecision } from "./monitoring.js";
|
|
14
14
|
import { getStateDir, normalizeSessionId, compressSmart } from "./store.js";
|
|
15
15
|
import { computeContentDigest } from "./dedup/digest.js";
|
|
16
|
-
import { minhashSignature, SIGNATURE_VERSION, NUM_HASHES } from "./dedup/l1-minhash.js";
|
|
16
|
+
import { minhashSignature, SIGNATURE_VERSION, NUM_HASHES, } from "./dedup/l1-minhash.js";
|
|
17
17
|
import { lshBands } from "./dedup/l1-lsh.js";
|
|
18
18
|
import { isNearDuplicate } from "./dedup/l1-verify.js";
|
|
19
19
|
import { openBloom, saveBloom } from "./store/bloom.js";
|
|
@@ -47,6 +47,10 @@ export class VectorStore {
|
|
|
47
47
|
* global index keys on repoId so recall can span repos.
|
|
48
48
|
*/
|
|
49
49
|
repoId;
|
|
50
|
+
/** S25: per-session cached rehydrated RaptorTree. Keyed by sessionId.
|
|
51
|
+
* Freshness-validated via maxRaptorNodeBuiltAt on each lookup — a cheap
|
|
52
|
+
* indexed MAX query replaces the O(n·leaves) rehydrate on every search. */
|
|
53
|
+
raptorCache = new Map();
|
|
50
54
|
constructor(opts = {}) {
|
|
51
55
|
this.embedder = opts.embedder ?? defaultEmbedder();
|
|
52
56
|
this.stateDir = opts.stateDir ?? getStateDir();
|
|
@@ -125,7 +129,11 @@ export class VectorStore {
|
|
|
125
129
|
bumpDedupStats(true, this.stateDir);
|
|
126
130
|
// Deduped: whole original region discarded, nothing new stored.
|
|
127
131
|
addTokensSaved(origTokens, this.stateDir);
|
|
128
|
-
const r = {
|
|
132
|
+
const r = {
|
|
133
|
+
checkpoint: contentMatch,
|
|
134
|
+
deduped: true,
|
|
135
|
+
reason: "contentHash",
|
|
136
|
+
};
|
|
129
137
|
this.record("L0", "deduped", "contentHash", Date.now() - t0);
|
|
130
138
|
onTier?.({ tier: "L0", status: "deduped", detail: "contentHash" });
|
|
131
139
|
return r;
|
|
@@ -143,7 +151,11 @@ export class VectorStore {
|
|
|
143
151
|
bumpDedupStats(true, this.stateDir);
|
|
144
152
|
// Deduped: whole original region discarded, nothing new stored.
|
|
145
153
|
addTokensSaved(origTokens, this.stateDir);
|
|
146
|
-
const r = {
|
|
154
|
+
const r = {
|
|
155
|
+
checkpoint: regionMatch,
|
|
156
|
+
deduped: true,
|
|
157
|
+
reason: "regionHash",
|
|
158
|
+
};
|
|
147
159
|
this.record("L0", "deduped", "regionHash", Date.now() - t0);
|
|
148
160
|
onTier?.({ tier: "L0", status: "deduped", detail: "regionHash" });
|
|
149
161
|
return r;
|
|
@@ -167,7 +179,11 @@ export class VectorStore {
|
|
|
167
179
|
bumpDedupStats(true, this.stateDir);
|
|
168
180
|
// Deduped: whole original region discarded, nothing new stored.
|
|
169
181
|
addTokensSaved(origTokens, this.stateDir);
|
|
170
|
-
const r = {
|
|
182
|
+
const r = {
|
|
183
|
+
checkpoint: summaryMatch,
|
|
184
|
+
deduped: true,
|
|
185
|
+
reason: "summaryHash",
|
|
186
|
+
};
|
|
171
187
|
this.record("L0", "deduped", "summaryHash", Date.now() - t0);
|
|
172
188
|
onTier?.({ tier: "L0", status: "deduped", detail: "summaryHash" });
|
|
173
189
|
return r;
|
|
@@ -226,14 +242,26 @@ export class VectorStore {
|
|
|
226
242
|
bumpDedupStats(true, this.stateDir);
|
|
227
243
|
// Deduped: whole original region discarded, nothing new stored.
|
|
228
244
|
addTokensSaved(origTokens, this.stateDir);
|
|
229
|
-
const r = {
|
|
245
|
+
const r = {
|
|
246
|
+
checkpoint: nearest.checkpoint,
|
|
247
|
+
deduped: true,
|
|
248
|
+
reason: "contentSimilarity",
|
|
249
|
+
};
|
|
230
250
|
this.record("L2", "deduped", "contentSimilarity", Date.now() - t0);
|
|
231
|
-
onTier?.({
|
|
251
|
+
onTier?.({
|
|
252
|
+
tier: "L2",
|
|
253
|
+
status: "deduped",
|
|
254
|
+
detail: nearest.sim.toFixed(2),
|
|
255
|
+
});
|
|
232
256
|
return r;
|
|
233
257
|
}
|
|
234
258
|
markOnly = "L2";
|
|
235
259
|
}
|
|
236
|
-
onTier?.({
|
|
260
|
+
onTier?.({
|
|
261
|
+
tier: "L2",
|
|
262
|
+
status: "passed",
|
|
263
|
+
detail: `best ${nearest.sim.toFixed(2)}`,
|
|
264
|
+
});
|
|
237
265
|
}
|
|
238
266
|
// 4. Genuinely new — create checkpoint
|
|
239
267
|
const checkpointId = nextCheckpointId(sessionId, this.stateDir);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wordpiece.ts — a self-contained WordPiece tokenizer for BERT/MiniLM.
|
|
3
|
+
*
|
|
4
|
+
* Loads the canonical `vocab.txt` (bert-base-uncased, ~30K tokens) from disk and
|
|
5
|
+
* implements the standard uncased BERT preprocessing + greedy longest-match
|
|
6
|
+
* WordPiece segmentation. No native dependency, no network — the vocab file is a
|
|
7
|
+
* local artifact fetched once by scripts/setup-minilm.mjs (PREVENT-PI-004).
|
|
8
|
+
*
|
|
9
|
+
* This mirrors HuggingFace `BertTokenizer` closely enough for sentence-embedding
|
|
10
|
+
* use: lowercase, strip accents, split on whitespace + punctuation, then
|
|
11
|
+
* WordPiece each token with the `##` continuation convention. Special tokens
|
|
12
|
+
* [CLS]/[SEP] are added by the caller's encode().
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
15
|
+
const UNK = "[UNK]";
|
|
16
|
+
const CLS = "[CLS]";
|
|
17
|
+
const SEP = "[SEP]";
|
|
18
|
+
const PAD = "[PAD]";
|
|
19
|
+
const MAX_INPUT_CHARS_PER_WORD = 200;
|
|
20
|
+
export class WordPieceTokenizer {
|
|
21
|
+
vocab;
|
|
22
|
+
clsId;
|
|
23
|
+
sepId;
|
|
24
|
+
padId;
|
|
25
|
+
unkId;
|
|
26
|
+
constructor(vocab) {
|
|
27
|
+
this.vocab = vocab;
|
|
28
|
+
this.clsId = vocab.get(CLS) ?? 101;
|
|
29
|
+
this.sepId = vocab.get(SEP) ?? 102;
|
|
30
|
+
this.padId = vocab.get(PAD) ?? 0;
|
|
31
|
+
this.unkId = vocab.get(UNK) ?? 100;
|
|
32
|
+
}
|
|
33
|
+
/** Build a tokenizer from a vocab.txt file (one token per line, index = line). */
|
|
34
|
+
static fromVocabFile(path) {
|
|
35
|
+
if (!existsSync(path)) {
|
|
36
|
+
throw new Error(`WordPiece vocab not found at ${path}. Run: node scripts/setup-minilm.mjs`);
|
|
37
|
+
}
|
|
38
|
+
const lines = readFileSync(path, "utf-8").split("\n");
|
|
39
|
+
const vocab = new Map();
|
|
40
|
+
for (let i = 0; i < lines.length; i++) {
|
|
41
|
+
const tok = lines[i].replace(/\r$/, "");
|
|
42
|
+
if (tok.length > 0 || i < lines.length - 1)
|
|
43
|
+
vocab.set(tok, i);
|
|
44
|
+
}
|
|
45
|
+
return new WordPieceTokenizer(vocab);
|
|
46
|
+
}
|
|
47
|
+
/** Uncased BERT basic tokenization: lowercase, strip accents, split on ws+punct. */
|
|
48
|
+
basicTokenize(text) {
|
|
49
|
+
// NFD + strip combining marks (accent removal), then lowercase.
|
|
50
|
+
const cleaned = text
|
|
51
|
+
.normalize("NFD")
|
|
52
|
+
.replace(/[̀-ͯ]/g, "")
|
|
53
|
+
.toLowerCase();
|
|
54
|
+
const tokens = [];
|
|
55
|
+
let buf = "";
|
|
56
|
+
const flush = () => {
|
|
57
|
+
if (buf.length > 0) {
|
|
58
|
+
tokens.push(buf);
|
|
59
|
+
buf = "";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
for (const ch of cleaned) {
|
|
63
|
+
if (/\s/.test(ch)) {
|
|
64
|
+
flush();
|
|
65
|
+
}
|
|
66
|
+
else if (/[!-/:-@[-`{-~¡-¿]/.test(ch)) {
|
|
67
|
+
// Punctuation becomes its own token.
|
|
68
|
+
flush();
|
|
69
|
+
tokens.push(ch);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
buf += ch;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
flush();
|
|
76
|
+
return tokens;
|
|
77
|
+
}
|
|
78
|
+
/** Greedy longest-match WordPiece for a single word. */
|
|
79
|
+
wordpiece(word) {
|
|
80
|
+
if (word.length > MAX_INPUT_CHARS_PER_WORD)
|
|
81
|
+
return [UNK];
|
|
82
|
+
const pieces = [];
|
|
83
|
+
let start = 0;
|
|
84
|
+
while (start < word.length) {
|
|
85
|
+
let end = word.length;
|
|
86
|
+
let cur = null;
|
|
87
|
+
while (start < end) {
|
|
88
|
+
let sub = word.slice(start, end);
|
|
89
|
+
if (start > 0)
|
|
90
|
+
sub = "##" + sub;
|
|
91
|
+
if (this.vocab.has(sub)) {
|
|
92
|
+
cur = sub;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
end--;
|
|
96
|
+
}
|
|
97
|
+
if (cur === null)
|
|
98
|
+
return [UNK]; // any unmatchable piece → whole word is UNK
|
|
99
|
+
pieces.push(cur);
|
|
100
|
+
start = end;
|
|
101
|
+
}
|
|
102
|
+
return pieces;
|
|
103
|
+
}
|
|
104
|
+
/** Tokenize text into WordPiece token strings (no special tokens). */
|
|
105
|
+
tokenize(text) {
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const word of this.basicTokenize(text)) {
|
|
108
|
+
for (const piece of this.wordpiece(word))
|
|
109
|
+
out.push(piece);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Encode text into model inputs with [CLS]…[SEP], truncated to `maxLen`.
|
|
115
|
+
* attention_mask is all 1s (no padding for single-sequence inference).
|
|
116
|
+
*/
|
|
117
|
+
encode(text, maxLen = 256) {
|
|
118
|
+
const pieces = this.tokenize(text).slice(0, Math.max(0, maxLen - 2));
|
|
119
|
+
const inputIds = [this.clsId];
|
|
120
|
+
for (const p of pieces)
|
|
121
|
+
inputIds.push(this.vocab.get(p) ?? this.unkId);
|
|
122
|
+
inputIds.push(this.sepId);
|
|
123
|
+
return {
|
|
124
|
+
inputIds,
|
|
125
|
+
attentionMask: inputIds.map(() => 1),
|
|
126
|
+
tokenTypeIds: inputIds.map(() => 0),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|