sweet-search 0.0.1 → 2.3.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 (161) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +23 -0
  3. package/core/cli.js +51 -0
  4. package/core/config.js +27 -0
  5. package/core/embedding/embedding-cache.js +467 -0
  6. package/core/embedding/embedding-local-model.js +845 -0
  7. package/core/embedding/embedding-remote.js +492 -0
  8. package/core/embedding/embedding-service.js +712 -0
  9. package/core/embedding/embedding-telemetry.js +219 -0
  10. package/core/embedding/index.js +40 -0
  11. package/core/graph/community-detector.js +294 -0
  12. package/core/graph/graph-expansion.js +839 -0
  13. package/core/graph/graph-extractor.js +2304 -0
  14. package/core/graph/graph-search.js +2148 -0
  15. package/core/graph/hcgs-generator.js +666 -0
  16. package/core/graph/index.js +16 -0
  17. package/core/graph/leiden-algorithm.js +547 -0
  18. package/core/graph/relationship-resolver.js +366 -0
  19. package/core/graph/repo-map.js +408 -0
  20. package/core/graph/summary-manager.js +549 -0
  21. package/core/indexing/artifact-builder.js +1054 -0
  22. package/core/indexing/ast-chunker.js +709 -0
  23. package/core/indexing/chunking/chunk-builder.js +170 -0
  24. package/core/indexing/chunking/markdown-chunker.js +503 -0
  25. package/core/indexing/chunking/plaintext-chunker.js +104 -0
  26. package/core/indexing/dedup/dedup-phase.js +159 -0
  27. package/core/indexing/dedup/exemplar-selector.js +65 -0
  28. package/core/indexing/document-chunker.js +56 -0
  29. package/core/indexing/incremental-parser.js +390 -0
  30. package/core/indexing/incremental-tracker.js +761 -0
  31. package/core/indexing/index-codebase-v21.js +472 -0
  32. package/core/indexing/index-maintainer.mjs +1674 -0
  33. package/core/indexing/index.js +90 -0
  34. package/core/indexing/indexer-ann.js +1077 -0
  35. package/core/indexing/indexer-build.js +742 -0
  36. package/core/indexing/indexer-phases.js +800 -0
  37. package/core/indexing/indexer-pool.js +764 -0
  38. package/core/indexing/indexer-sparse-gram.js +98 -0
  39. package/core/indexing/indexer-utils.js +536 -0
  40. package/core/indexing/indexer-worker.js +148 -0
  41. package/core/indexing/li-skip-policy.js +225 -0
  42. package/core/indexing/merkle-tracker.js +244 -0
  43. package/core/indexing/model-pool.js +166 -0
  44. package/core/infrastructure/code-graph-repository.js +120 -0
  45. package/core/infrastructure/codebase-repository.js +131 -0
  46. package/core/infrastructure/config/dedup.js +54 -0
  47. package/core/infrastructure/config/embedding.js +298 -0
  48. package/core/infrastructure/config/graph.js +80 -0
  49. package/core/infrastructure/config/index.js +82 -0
  50. package/core/infrastructure/config/indexing.js +8 -0
  51. package/core/infrastructure/config/platform.js +254 -0
  52. package/core/infrastructure/config/ranking.js +221 -0
  53. package/core/infrastructure/config/search.js +396 -0
  54. package/core/infrastructure/config/translation.js +89 -0
  55. package/core/infrastructure/config/vector-store.js +114 -0
  56. package/core/infrastructure/constants.js +86 -0
  57. package/core/infrastructure/coreml-cascade.js +909 -0
  58. package/core/infrastructure/coreml-cascade.json +46 -0
  59. package/core/infrastructure/coreml-provider.js +81 -0
  60. package/core/infrastructure/db-utils.js +69 -0
  61. package/core/infrastructure/dedup-hashing.js +83 -0
  62. package/core/infrastructure/hardware-capability.js +332 -0
  63. package/core/infrastructure/index.js +104 -0
  64. package/core/infrastructure/language-patterns/maps.js +121 -0
  65. package/core/infrastructure/language-patterns/registry-core.js +323 -0
  66. package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
  67. package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
  68. package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
  69. package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
  70. package/core/infrastructure/language-patterns/registry.js +19 -0
  71. package/core/infrastructure/language-patterns.js +141 -0
  72. package/core/infrastructure/llm-provider.js +733 -0
  73. package/core/infrastructure/manifest.json +46 -0
  74. package/core/infrastructure/maxsim.wasm +0 -0
  75. package/core/infrastructure/model-fetcher.js +423 -0
  76. package/core/infrastructure/model-registry.js +214 -0
  77. package/core/infrastructure/native-inference.js +587 -0
  78. package/core/infrastructure/native-resolver.js +187 -0
  79. package/core/infrastructure/native-sparse-gram.js +257 -0
  80. package/core/infrastructure/native-tokenizer.js +160 -0
  81. package/core/infrastructure/onnx-mutex.js +45 -0
  82. package/core/infrastructure/onnx-session-utils.js +261 -0
  83. package/core/infrastructure/ort-pipeline.js +111 -0
  84. package/core/infrastructure/project-detector.js +102 -0
  85. package/core/infrastructure/quantization.js +410 -0
  86. package/core/infrastructure/simd-distance.js +502 -0
  87. package/core/infrastructure/simd-distance.wasm +0 -0
  88. package/core/infrastructure/tree-sitter-provider.js +665 -0
  89. package/core/infrastructure/webgpu-maxsim.js +222 -0
  90. package/core/query/index.js +35 -0
  91. package/core/query/intent-detector.js +201 -0
  92. package/core/query/intent-router.js +156 -0
  93. package/core/query/query-router-catboost.js +222 -0
  94. package/core/query/query-router-ml.js +266 -0
  95. package/core/query/query-router.js +213 -0
  96. package/core/ranking/cascaded-scorer.js +379 -0
  97. package/core/ranking/flashrank.js +810 -0
  98. package/core/ranking/index.js +49 -0
  99. package/core/ranking/late-interaction-index.js +2383 -0
  100. package/core/ranking/late-interaction-model.js +812 -0
  101. package/core/ranking/local-reranker.js +374 -0
  102. package/core/ranking/mmr.js +379 -0
  103. package/core/ranking/quality-scorer.js +363 -0
  104. package/core/search/context-expander.js +1167 -0
  105. package/core/search/dedup/sibling-expander.js +327 -0
  106. package/core/search/index.js +16 -0
  107. package/core/search/search-boost.js +259 -0
  108. package/core/search/search-cli.js +544 -0
  109. package/core/search/search-format.js +282 -0
  110. package/core/search/search-fusion.js +327 -0
  111. package/core/search/search-hybrid.js +204 -0
  112. package/core/search/search-pattern-chunks.js +337 -0
  113. package/core/search/search-pattern-planner.js +439 -0
  114. package/core/search/search-pattern-prefilter.js +412 -0
  115. package/core/search/search-pattern-ripgrep.js +663 -0
  116. package/core/search/search-pattern.js +463 -0
  117. package/core/search/search-postprocess.js +452 -0
  118. package/core/search/search-semantic.js +706 -0
  119. package/core/search/search-server.js +554 -0
  120. package/core/search/session-daemon-prewarm.mjs +164 -0
  121. package/core/search/session-warmup.js +595 -0
  122. package/core/search/sweet-search.js +632 -0
  123. package/core/search/warmup-metrics.js +532 -0
  124. package/core/start-server.js +6 -0
  125. package/core/training/query-router/features/extractor.js +762 -0
  126. package/core/training/query-router/features/multilingual-patterns.js +431 -0
  127. package/core/training/query-router/features/text-segmenter.js +303 -0
  128. package/core/training/query-router/features/unicode-utils.js +383 -0
  129. package/core/training/query-router/output/v45_router_d4.js +11521 -0
  130. package/core/training/query-router/output/v46_router_d4.js +11498 -0
  131. package/core/vector-store/binary-heap.js +227 -0
  132. package/core/vector-store/binary-hnsw-index.js +1004 -0
  133. package/core/vector-store/float-vector-store.js +234 -0
  134. package/core/vector-store/hnsw-index.js +580 -0
  135. package/core/vector-store/index.js +39 -0
  136. package/core/vector-store/seismic-index.js +498 -0
  137. package/core/vocabulary/index.js +84 -0
  138. package/core/vocabulary/vocab-constants.js +20 -0
  139. package/core/vocabulary/vocab-miner-extractors.js +375 -0
  140. package/core/vocabulary/vocab-miner-nl.js +404 -0
  141. package/core/vocabulary/vocab-miner-utils.js +146 -0
  142. package/core/vocabulary/vocab-miner.js +574 -0
  143. package/core/vocabulary/vocab-prewarm-cli.js +110 -0
  144. package/core/vocabulary/vocab-ranker.js +492 -0
  145. package/core/vocabulary/vocab-warmer.js +523 -0
  146. package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
  147. package/core/vocabulary/vocabulary-utils.js +704 -0
  148. package/crates/wasm-router/pkg/package.json +13 -0
  149. package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
  150. package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
  151. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
  152. package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
  153. package/mcp/config-gen.js +121 -0
  154. package/mcp/server.js +335 -0
  155. package/mcp/tool-handlers.js +476 -0
  156. package/package.json +131 -9
  157. package/scripts/benchmark-harness.js +794 -0
  158. package/scripts/init.js +1058 -0
  159. package/scripts/smoke-test.js +435 -0
  160. package/scripts/uninstall.js +478 -0
  161. package/scripts/verify-runtime.js +176 -0
@@ -0,0 +1,498 @@
1
+ /**
2
+ * SEISMIC Sparse Vector Search Index
3
+ *
4
+ * Pure JS implementation of SEISMIC's core data structures for Maximum Inner
5
+ * Product Search (MIPS) on sparse vectors. Organizes inverted lists into
6
+ * geometrically-cohesive blocks, each with a summary vector that enables
7
+ * aggressive block skipping during query.
8
+ *
9
+ * Retrieval pathway: FTS5 (lexical) + HNSW (dense) + SEISMIC (sparse)
10
+ *
11
+ * Reference: SEISMIC paper - block-based inverted index with summary pruning
12
+ */
13
+
14
+ // =============================================================================
15
+ // TOP-K MIN-HEAP
16
+ // =============================================================================
17
+
18
+ /**
19
+ * Min-heap for efficient top-k tracking during search.
20
+ * Maintains the k highest-scoring items; root is always the minimum.
21
+ */
22
+ export class TopKHeap {
23
+ /** @param {number} k - Maximum number of results to keep */
24
+ constructor(k) {
25
+ this.k = k;
26
+ /** @type {Array<{ id: string, score: number }>} */
27
+ this.heap = [];
28
+ }
29
+
30
+ /** Current minimum score in the heap (pruning threshold) */
31
+ threshold() {
32
+ return this.heap.length < this.k ? -Infinity : this.heap[0].score;
33
+ }
34
+
35
+ /** @param {string} id @param {number} score */
36
+ push(id, score) {
37
+ if (this.heap.length < this.k) {
38
+ this.heap.push({ id, score });
39
+ this._bubbleUp(this.heap.length - 1);
40
+ } else if (score > this.heap[0].score) {
41
+ this.heap[0] = { id, score };
42
+ this._sinkDown(0);
43
+ }
44
+ }
45
+
46
+ /** Return results sorted descending by score */
47
+ results() {
48
+ return this.heap.slice().sort((a, b) => b.score - a.score);
49
+ }
50
+
51
+ /** @private */
52
+ _bubbleUp(i) {
53
+ while (i > 0) {
54
+ const parent = (i - 1) >> 1;
55
+ if (this.heap[i].score < this.heap[parent].score) {
56
+ [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
57
+ i = parent;
58
+ } else break;
59
+ }
60
+ }
61
+
62
+ /** @private */
63
+ _sinkDown(i) {
64
+ const n = this.heap.length;
65
+ while (true) {
66
+ let smallest = i;
67
+ const left = 2 * i + 1;
68
+ const right = 2 * i + 2;
69
+ if (left < n && this.heap[left].score < this.heap[smallest].score) smallest = left;
70
+ if (right < n && this.heap[right].score < this.heap[smallest].score) smallest = right;
71
+ if (smallest === i) break;
72
+ [this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
73
+ i = smallest;
74
+ }
75
+ }
76
+ }
77
+
78
+ // =============================================================================
79
+ // SPARSE DOT PRODUCT
80
+ // =============================================================================
81
+
82
+ /**
83
+ * Sparse dot product (MIPS score) between two sparse vectors.
84
+ * Only iterates over the smaller vector's dimensions.
85
+ * @param {Map<number, number>} a
86
+ * @param {Map<number, number>} b
87
+ * @returns {number}
88
+ */
89
+ export function sparseDotProduct(a, b) {
90
+ let score = 0;
91
+ // Iterate over the smaller map for efficiency
92
+ const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];
93
+ for (const [dim, weight] of smaller) {
94
+ const otherWeight = larger.get(dim);
95
+ if (otherWeight !== undefined) {
96
+ score += weight * otherWeight;
97
+ }
98
+ }
99
+ return score;
100
+ }
101
+
102
+ // =============================================================================
103
+ // BLOCK
104
+ // =============================================================================
105
+
106
+ /**
107
+ * A contiguous group of postings within an inverted list.
108
+ * Stores a summary (max weight) for upper-bound pruning.
109
+ */
110
+ export class Block {
111
+ /** @param {Array<{ docId: string, weight: number }>} postings */
112
+ constructor(postings) {
113
+ this.postings = postings;
114
+ this._summary = 0;
115
+ for (let i = 0; i < postings.length; i++) {
116
+ if (postings[i].weight > this._summary) {
117
+ this._summary = postings[i].weight;
118
+ }
119
+ }
120
+ }
121
+
122
+ /** Maximum weight in this block (upper bound for scoring) */
123
+ get summary() {
124
+ return this._summary;
125
+ }
126
+
127
+ /**
128
+ * Whether this block can be skipped given the query weight and current threshold.
129
+ * If the best possible contribution from this block (summary * queryWeight)
130
+ * is less than the threshold, the block cannot improve the result set.
131
+ *
132
+ * Note: SeismicIndex.alpha is intentionally NOT used here. The summary already
133
+ * represents the maximum weight in the block, which is the tightest upper bound
134
+ * for pruning. Multiplying by alpha would weaken the bound (more false skips).
135
+ * Alpha is reserved for future use in score normalization at the index level,
136
+ * not at the block-pruning level.
137
+ *
138
+ * @param {number} queryWeight - Query weight for this term
139
+ * @param {number} currentThreshold - Current minimum score to beat
140
+ * @returns {boolean}
141
+ */
142
+ canSkip(queryWeight, currentThreshold) {
143
+ return this._summary * queryWeight < currentThreshold;
144
+ }
145
+ }
146
+
147
+ // =============================================================================
148
+ // INVERTED LIST
149
+ // =============================================================================
150
+
151
+ /**
152
+ * Per-term inverted list: posting entries sorted by weight descending,
153
+ * organized into blocks for pruning.
154
+ */
155
+ export class InvertedList {
156
+ /** @param {number} termId - The dimension/term index */
157
+ constructor(termId) {
158
+ this.termId = termId;
159
+ /** @type {Array<{ docId: string, weight: number }>} */
160
+ this.postings = [];
161
+ /** @type {Block[]} */
162
+ this.blocks = [];
163
+ this.built = false;
164
+ }
165
+
166
+ /** @param {string} docId @param {number} weight */
167
+ addPosting(docId, weight) {
168
+ this.postings.push({ docId, weight });
169
+ }
170
+
171
+ /**
172
+ * Sort postings by weight descending and split into fixed-size blocks.
173
+ * @param {number} blockSize
174
+ */
175
+ buildBlocks(blockSize) {
176
+ this.postings.sort((a, b) => b.weight - a.weight);
177
+ this.blocks = [];
178
+ for (let i = 0; i < this.postings.length; i += blockSize) {
179
+ this.blocks.push(new Block(this.postings.slice(i, i + blockSize)));
180
+ }
181
+ this.built = true;
182
+ }
183
+ }
184
+
185
+ // =============================================================================
186
+ // SEISMIC INDEX
187
+ // =============================================================================
188
+
189
+ const SERIALIZE_MAGIC = 'SEISM';
190
+ const SERIALIZE_VERSION = 1;
191
+ const MAX_DESERIALIZE_DOCS = 1_000_000;
192
+ const MAX_DESERIALIZE_DIMS_PER_DOC = 1_000_000;
193
+
194
+ function ensureReadable(buffer, off, bytes, what) {
195
+ if (!Number.isInteger(off) || off < 0) {
196
+ throw new Error(`Corrupt SEISMIC index: invalid offset while reading ${what}`);
197
+ }
198
+ if (!Number.isInteger(bytes) || bytes < 0) {
199
+ throw new Error(`Corrupt SEISMIC index: invalid length while reading ${what}`);
200
+ }
201
+ if (off + bytes > buffer.length) {
202
+ throw new Error(`Corrupt SEISMIC index: truncated while reading ${what}`);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * SEISMIC sparse vector search index.
208
+ *
209
+ * Build from sparse vectors (Map<dimension, weight>), then query with
210
+ * sparse query vectors. Uses block-based inverted lists with summary
211
+ * pruning for efficient MIPS.
212
+ */
213
+ export class SeismicIndex {
214
+ /**
215
+ * @param {Object} [options]
216
+ * @param {number} [options.blockSize=64] - Postings per block
217
+ * @param {number} [options.alpha=0.8] - Importance fraction (reserved for future use)
218
+ */
219
+ constructor(options = {}) {
220
+ this.blockSize = options.blockSize || 64;
221
+ this.alpha = options.alpha ?? 0.8;
222
+
223
+ /** @type {Map<number, InvertedList>} term → inverted list */
224
+ this.lists = new Map();
225
+ /** @type {Map<string, Map<number, number>>} docId → sparse vector */
226
+ this.docs = new Map();
227
+ this.built = false;
228
+ }
229
+
230
+ /**
231
+ * Build the index from an array of sparse vectors.
232
+ * @param {Array<{ id: string, vector: Map<number, number> }>} sparseVectors
233
+ */
234
+ build(sparseVectors) {
235
+ this.lists.clear();
236
+ this.docs.clear();
237
+
238
+ for (const { id, vector } of sparseVectors) {
239
+ this.docs.set(id, vector);
240
+ for (const [dim, weight] of vector) {
241
+ if (weight === 0) continue;
242
+ let list = this.lists.get(dim);
243
+ if (!list) {
244
+ list = new InvertedList(dim);
245
+ this.lists.set(dim, list);
246
+ }
247
+ list.addPosting(id, weight);
248
+ }
249
+ }
250
+
251
+ // Build blocks for every inverted list
252
+ for (const list of this.lists.values()) {
253
+ list.buildBlocks(this.blockSize);
254
+ }
255
+
256
+ this.built = true;
257
+ }
258
+
259
+ /**
260
+ * Query the index for top-k results by MIPS.
261
+ * @param {Map<number, number>} queryVector - Sparse query vector
262
+ * @param {number} [k=10] - Number of results
263
+ * @returns {Array<{ id: string, score: number }>}
264
+ */
265
+ query(queryVector, k = 10) {
266
+ if (!this.built || this.docs.size === 0) return [];
267
+ if (queryVector.size === 0) return [];
268
+
269
+ // Accumulate scores per document via block-structured inverted lists
270
+ const scores = new Map();
271
+
272
+ // Sort query dimensions by weight descending (process most important first)
273
+ const queryDims = [...queryVector.entries()]
274
+ .filter(([, w]) => w !== 0)
275
+ .sort((a, b) => b[1] - a[1]);
276
+
277
+ let blocksExamined = 0;
278
+
279
+ for (const [dim, queryWeight] of queryDims) {
280
+ const list = this.lists.get(dim);
281
+ if (!list) continue;
282
+
283
+ for (const block of list.blocks) {
284
+ blocksExamined++;
285
+ for (const { docId, weight } of block.postings) {
286
+ scores.set(docId, (scores.get(docId) || 0) + queryWeight * weight);
287
+ }
288
+ }
289
+ }
290
+
291
+ // Build final top-k from accumulated scores
292
+ const heap = new TopKHeap(k);
293
+ for (const [docId, score] of scores) {
294
+ heap.push(docId, score);
295
+ }
296
+
297
+ this._lastQueryStats = { blocksExamined };
298
+ return heap.results();
299
+ }
300
+
301
+ /**
302
+ * Approximate query with block skipping for single-dimension queries.
303
+ * Uses SEISMIC block pruning: blocks sorted by summary descending within
304
+ * each inverted list enable early termination when the remaining blocks
305
+ * cannot beat the current threshold.
306
+ *
307
+ * Sound for single-dimension queries; for multi-dimension queries the caller
308
+ * should verify results or use exact query().
309
+ *
310
+ * @param {Map<number, number>} queryVector - Sparse query vector
311
+ * @param {number} [k=10]
312
+ * @returns {{ results: Array<{ id: string, score: number }>, blocksExamined: number, blocksSkipped: number }}
313
+ */
314
+ approximateQuery(queryVector, k = 10) {
315
+ if (!this.built || this.docs.size === 0) {
316
+ return { results: [], blocksExamined: 0, blocksSkipped: 0 };
317
+ }
318
+ if (queryVector.size === 0) {
319
+ return { results: [], blocksExamined: 0, blocksSkipped: 0 };
320
+ }
321
+
322
+ const scores = new Map();
323
+ let threshold = 0;
324
+
325
+ const queryDims = [...queryVector.entries()]
326
+ .filter(([, w]) => w !== 0)
327
+ .sort((a, b) => b[1] - a[1]);
328
+
329
+ let blocksExamined = 0;
330
+ let blocksSkipped = 0;
331
+
332
+ for (const [dim, queryWeight] of queryDims) {
333
+ const list = this.lists.get(dim);
334
+ if (!list) continue;
335
+
336
+ for (const block of list.blocks) {
337
+ // Blocks are sorted by summary descending. Once a block's max
338
+ // possible contribution falls below threshold, early-terminate this list.
339
+ if (block.canSkip(queryWeight, threshold)) {
340
+ blocksSkipped += list.blocks.length - list.blocks.indexOf(block);
341
+ break;
342
+ }
343
+
344
+ blocksExamined++;
345
+ for (const { docId, weight } of block.postings) {
346
+ scores.set(docId, (scores.get(docId) || 0) + queryWeight * weight);
347
+ }
348
+ }
349
+
350
+ // Update threshold from accumulated scores after each dimension
351
+ if (scores.size >= k) {
352
+ const vals = [...scores.values()];
353
+ vals.sort((a, b) => b - a);
354
+ threshold = vals[k - 1];
355
+ }
356
+ }
357
+
358
+ const heap = new TopKHeap(k);
359
+ for (const [docId, score] of scores) {
360
+ heap.push(docId, score);
361
+ }
362
+
363
+ return { results: heap.results(), blocksExamined, blocksSkipped };
364
+ }
365
+
366
+ /** Stats from the last query (for testing block skipping) */
367
+ get lastQueryStats() {
368
+ return this._lastQueryStats || { blocksExamined: 0, blocksSkipped: 0 };
369
+ }
370
+
371
+ /**
372
+ * Index statistics.
373
+ * @returns {{ totalDocs: number, totalTerms: number, totalBlocks: number, avgBlockSize: number }}
374
+ */
375
+ getStats() {
376
+ let totalBlocks = 0;
377
+ let totalPostings = 0;
378
+ for (const list of this.lists.values()) {
379
+ totalBlocks += list.blocks.length;
380
+ totalPostings += list.postings.length;
381
+ }
382
+ return {
383
+ totalDocs: this.docs.size,
384
+ totalTerms: this.lists.size,
385
+ totalBlocks,
386
+ avgBlockSize: totalBlocks > 0 ? totalPostings / totalBlocks : 0,
387
+ };
388
+ }
389
+
390
+ /**
391
+ * Serialize the index to a Buffer for persistence.
392
+ * Format: [magic(5B)][version(1B)][blockSize(4B)][alpha(8B)]
393
+ * [numDocs(4B)][ ...docs... ][numLists(4B)][ ...lists... ]
394
+ * @returns {Buffer}
395
+ */
396
+ serialize() {
397
+ const parts = [];
398
+
399
+ // Header: magic(5B) + version(1B) + blockSize(4B) + alpha(8B) = 18B
400
+ const header = Buffer.alloc(18);
401
+ header.write(SERIALIZE_MAGIC, 0, 5, 'ascii');
402
+ header.writeUInt8(SERIALIZE_VERSION, 5);
403
+ header.writeUInt32BE(this.blockSize, 6);
404
+ header.writeDoubleBE(this.alpha, 10);
405
+ parts.push(header);
406
+
407
+ // Documents: [numDocs(4B)][foreach: idLen(2B) id(utf8) numDims(4B) [dim(4B) weight(8B)]...]
408
+ const docsBuf = [Buffer.alloc(4)];
409
+ docsBuf[0].writeUInt32BE(this.docs.size, 0);
410
+ for (const [id, vec] of this.docs) {
411
+ const idBuf = Buffer.from(id, 'utf8');
412
+ const entry = Buffer.alloc(2 + idBuf.length + 4 + vec.size * 12);
413
+ let off = 0;
414
+ entry.writeUInt16BE(idBuf.length, off); off += 2;
415
+ idBuf.copy(entry, off); off += idBuf.length;
416
+ entry.writeUInt32BE(vec.size, off); off += 4;
417
+ for (const [dim, weight] of vec) {
418
+ entry.writeUInt32BE(dim, off); off += 4;
419
+ entry.writeDoubleBE(weight, off); off += 8;
420
+ }
421
+ docsBuf.push(entry);
422
+ }
423
+ parts.push(...docsBuf);
424
+
425
+ return Buffer.concat(parts);
426
+ }
427
+
428
+ /**
429
+ * Deserialize a Buffer back into a SeismicIndex.
430
+ * @param {Buffer} buffer
431
+ * @returns {SeismicIndex}
432
+ */
433
+ static deserialize(buffer) {
434
+ let off = 0;
435
+
436
+ // Validate magic
437
+ ensureReadable(buffer, off, 5, 'magic');
438
+ const magic = buffer.toString('ascii', off, off + 5); off += 5;
439
+ if (magic !== SERIALIZE_MAGIC) {
440
+ throw new Error('Invalid SEISMIC index magic bytes');
441
+ }
442
+
443
+ ensureReadable(buffer, off, 1, 'version');
444
+ const version = buffer.readUInt8(off); off += 1;
445
+ if (version !== SERIALIZE_VERSION) {
446
+ throw new Error(`Unsupported SEISMIC index version: ${version}`);
447
+ }
448
+
449
+ ensureReadable(buffer, off, 4, 'block size');
450
+ const blockSize = buffer.readUInt32BE(off); off += 4;
451
+ if (!Number.isInteger(blockSize) || blockSize <= 0 || blockSize > 65536) {
452
+ throw new Error(`Corrupt SEISMIC index: invalid block size ${blockSize}`);
453
+ }
454
+
455
+ ensureReadable(buffer, off, 8, 'alpha');
456
+ const alpha = buffer.readDoubleBE(off); off += 8;
457
+ if (!Number.isFinite(alpha)) {
458
+ throw new Error('Corrupt SEISMIC index: invalid alpha');
459
+ }
460
+
461
+ const index = new SeismicIndex({ blockSize, alpha });
462
+
463
+ // Read documents
464
+ ensureReadable(buffer, off, 4, 'document count');
465
+ const numDocs = buffer.readUInt32BE(off); off += 4;
466
+ if (numDocs > MAX_DESERIALIZE_DOCS) {
467
+ throw new Error(`Corrupt SEISMIC index: document count too large (${numDocs})`);
468
+ }
469
+ const sparseVectors = [];
470
+
471
+ for (let d = 0; d < numDocs; d++) {
472
+ ensureReadable(buffer, off, 2, `doc ${d} id length`);
473
+ const idLen = buffer.readUInt16BE(off); off += 2;
474
+
475
+ ensureReadable(buffer, off, idLen, `doc ${d} id`);
476
+ const id = buffer.toString('utf8', off, off + idLen); off += idLen;
477
+
478
+ ensureReadable(buffer, off, 4, `doc ${d} dimension count`);
479
+ const numDims = buffer.readUInt32BE(off); off += 4;
480
+ if (numDims > MAX_DESERIALIZE_DIMS_PER_DOC) {
481
+ throw new Error(`Corrupt SEISMIC index: dimension count too large (${numDims})`);
482
+ }
483
+
484
+ ensureReadable(buffer, off, numDims * 12, `doc ${d} vector payload`);
485
+ const vector = new Map();
486
+ for (let i = 0; i < numDims; i++) {
487
+ const dim = buffer.readUInt32BE(off); off += 4;
488
+ const weight = buffer.readDoubleBE(off); off += 8;
489
+ vector.set(dim, weight);
490
+ }
491
+ sparseVectors.push({ id, vector });
492
+ }
493
+
494
+ // Rebuild index from documents
495
+ index.build(sparseVectors);
496
+ return index;
497
+ }
498
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Vocabulary Domain Barrel
3
+ *
4
+ * Re-exports public API from all vocabulary sub-modules.
5
+ */
6
+
7
+ // vocab-miner.js
8
+ export {
9
+ default as vocabMiner,
10
+ mineStructural,
11
+ mineSymbols,
12
+ mineCodeGraph,
13
+ mineGit,
14
+ mineAll,
15
+ } from './vocab-miner.js';
16
+
17
+ // Also re-export the barrel re-exports from vocab-miner.js
18
+ export {
19
+ splitIdentifier, STOP_WORDS, SOURCE_EXTENSIONS,
20
+ addTerm, mergeTerms, termsToArray, walkShallow,
21
+ } from './vocab-miner-utils.js';
22
+
23
+ export {
24
+ extractImports, extractExports, extractDefinitions, extractConstants,
25
+ extractNpmDeps, extractCargoDeps, extractGoDeps, extractPipDeps, extractPyprojectDeps,
26
+ } from './vocab-miner-extractors.js';
27
+
28
+ export {
29
+ mineNLContent, computeNLContentHash, extractNLText, isSecretLike,
30
+ detectScript, tokenizeNL, extractBigrams, extractTrigrams,
31
+ SECRET_PATTERNS, NL_MINING_TIMEOUT_MS,
32
+ } from './vocab-miner-nl.js';
33
+
34
+ // vocab-ranker.js
35
+ export {
36
+ classifyWarmMode,
37
+ rankIdentifiers,
38
+ rankCommunityPhrases,
39
+ rankAll,
40
+ _internals as rankerInternals,
41
+ } from './vocab-ranker.js';
42
+
43
+ // vocab-warmer.js
44
+ export {
45
+ default as vocabWarmer,
46
+ warmLexical,
47
+ warmSemantic,
48
+ warmHybrid,
49
+ warmFromCache,
50
+ runFullWarmup,
51
+ saveBinaryArtifact,
52
+ DATA_DIR,
53
+ ARTIFACT_PATHS,
54
+ _loadEntityMetadata,
55
+ } from './vocab-warmer.js';
56
+
57
+ // vocab-warmup-orchestrator.js
58
+ export {
59
+ runFullWarmup as runFullWarmupOrchestrator,
60
+ saveBinaryArtifact as saveBinaryArtifactOrchestrator,
61
+ } from './vocab-warmup-orchestrator.js';
62
+
63
+ // vocabulary-utils.js
64
+ export {
65
+ default as vocabularyUtils,
66
+ BinaryVocabulary,
67
+ mineQueryLogs,
68
+ extractEntities,
69
+ generateAllTerms,
70
+ callVoyageAPI,
71
+ migrateJsonToBinary,
72
+ warmupFull,
73
+ warmupLearn,
74
+ getStats,
75
+ } from './vocabulary-utils.js';
76
+
77
+ // vocab-constants.js
78
+ export {
79
+ DATA_DIR as vocabDataDir,
80
+ ARTIFACT_PATHS as vocabArtifactPaths,
81
+ } from './vocab-constants.js';
82
+
83
+ // vocab-prewarm-cli.js
84
+ export { handlePrewarmVocabCli } from './vocab-prewarm-cli.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Vocabulary Prewarm Constants
3
+ *
4
+ * Shared constants for vocab-warmer.js and vocab-warmup-orchestrator.js.
5
+ * Extracted to break the circular dependency between those two modules.
6
+ */
7
+
8
+ import path from 'path';
9
+ import { PROJECT_ROOT } from '../infrastructure/config/index.js';
10
+
11
+ export const DATA_DIR = path.join(PROJECT_ROOT, '.sweet-search');
12
+
13
+ export const ARTIFACT_PATHS = {
14
+ identifiersBin: path.join(DATA_DIR, 'vocab-identifiers.bin'),
15
+ identifiersMeta: path.join(DATA_DIR, 'vocab-identifiers.meta.json'),
16
+ semanticSeedsBin: path.join(DATA_DIR, 'vocab-semantic-seeds.bin'),
17
+ semanticSeedsMeta: path.join(DATA_DIR, 'vocab-semantic-seeds.meta.json'),
18
+ communities: path.join(DATA_DIR, 'communities.json'),
19
+ dynamicVocab: path.join(DATA_DIR, 'vocab-dynamic.json'),
20
+ };