sweet-search 2.4.2 → 2.5.2

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 (46) hide show
  1. package/core/cli.js +43 -5
  2. package/core/embedding/embedding-cache.js +266 -18
  3. package/core/embedding/embedding-service.js +45 -9
  4. package/core/graph/graph-expansion.js +52 -12
  5. package/core/graph/graph-extractor.js +30 -1
  6. package/core/indexing/ast-chunker.js +331 -16
  7. package/core/indexing/chunking/chunk-builder.js +34 -1
  8. package/core/indexing/index-codebase-v21.js +31 -2
  9. package/core/indexing/index.js +6 -3
  10. package/core/indexing/indexer-ann.js +45 -6
  11. package/core/indexing/indexer-build.js +9 -1
  12. package/core/indexing/indexer-phases.js +6 -4
  13. package/core/indexing/indexing-file-policy.js +140 -0
  14. package/core/indexing/li-skip-policy.js +11 -220
  15. package/core/infrastructure/codebase-repository.js +21 -0
  16. package/core/infrastructure/config/embedding.js +20 -1
  17. package/core/infrastructure/config/graph.js +2 -2
  18. package/core/infrastructure/config/ranking.js +10 -0
  19. package/core/infrastructure/config/vector-store.js +1 -1
  20. package/core/infrastructure/coreml-cascade.js +236 -30
  21. package/core/infrastructure/coreml-cascade.json +25 -0
  22. package/core/infrastructure/index.js +17 -0
  23. package/core/infrastructure/init-config.js +216 -0
  24. package/core/infrastructure/language-patterns/registry-core.js +18 -0
  25. package/core/infrastructure/model-registry.js +12 -0
  26. package/core/infrastructure/native-inference.js +143 -51
  27. package/core/infrastructure/tree-sitter-provider.js +92 -2
  28. package/core/ranking/cascaded-scorer.js +6 -2
  29. package/core/ranking/file-kind-ranking.js +264 -0
  30. package/core/ranking/late-interaction-index.js +10 -4
  31. package/core/ranking/late-interaction-policy.js +304 -0
  32. package/core/search/context-expander.js +267 -28
  33. package/core/search/index.js +4 -0
  34. package/core/search/search-cli.js +3 -1
  35. package/core/search/search-pattern.js +4 -3
  36. package/core/search/search-postprocess.js +189 -8
  37. package/core/search/search-read-semantic.js +734 -0
  38. package/core/search/search-read.js +481 -0
  39. package/core/search/search-server.js +153 -5
  40. package/core/search/sweet-search.js +133 -16
  41. package/core/start-server.js +13 -2
  42. package/mcp/server.js +41 -0
  43. package/mcp/tool-handlers.js +117 -6
  44. package/package.json +9 -7
  45. package/scripts/init.js +386 -5
  46. package/scripts/uninstall.js +152 -6
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Late-interaction search-rerank policy resolver.
3
+ *
4
+ * Pure function — no I/O. The two product concepts (LI indexing model vs
5
+ * search-side LI rerank policy) are separate. This resolver computes the
6
+ * effective search-side state from explicit user choices, env vars,
7
+ * persisted init config, and the on-disk LI index manifest.
8
+ *
9
+ * The on-disk manifest is the source of truth: if the user (or auto)
10
+ * asks for rerank ON but the loaded index is missing, mismatched, or
11
+ * built with an edge model that's known to underperform as a reranker
12
+ * on benchmarked corpora, the resolver downgrades or warns accordingly.
13
+ *
14
+ * Bench evidence backing the auto rules (gencodesearchnet, 2026-05-03):
15
+ * standard `lateon-code` + LI on : 85.57 % MRR ← auto resolves ON
16
+ * edge `lateon-code-edge` + LI on : 80.65 % MRR ← auto resolves OFF
17
+ * edge + LI off : 82.91 % MRR (best edge config)
18
+ * standard + LI off : 82.91 % MRR (index-independent floor)
19
+ * See docs/BENCH_TODO.md "Phase 3 — Honest sweep before v2.5.0 (post-fix re-run)".
20
+ */
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Public model identifiers — kept in lockstep with
24
+ // `core/infrastructure/config/ranking.js::LATE_INTERACTION_CONFIG.models`.
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export const LI_MODEL_STANDARD = 'lateon-code';
28
+ export const LI_MODEL_EDGE = 'lateon-code-edge';
29
+ export const LI_MODEL_NONE = 'none';
30
+
31
+ export const VALID_LI_MODELS = Object.freeze([
32
+ LI_MODEL_STANDARD,
33
+ LI_MODEL_EDGE,
34
+ LI_MODEL_NONE,
35
+ ]);
36
+
37
+ export const VALID_RERANK_POLICIES = Object.freeze(['auto', 'on', 'off']);
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Env-var coercion (same shape as other env opt-outs in the repo —
41
+ // SWEET_SEARCH_COREML_CASCADE, SWEET_SEARCH_NATIVE_INFERENCE, etc.)
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function isEnvOff(value) {
45
+ if (value == null) return false;
46
+ const v = String(value).trim().toLowerCase();
47
+ return v === '0' || v === 'false' || v === 'off' || v === 'no';
48
+ }
49
+
50
+ function isEnvOn(value) {
51
+ if (value == null) return false;
52
+ const v = String(value).trim().toLowerCase();
53
+ return v === '1' || v === 'true' || v === 'on' || v === 'yes';
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Resolver
58
+ // ---------------------------------------------------------------------------
59
+
60
+ /**
61
+ * Resolve effective search-side LI rerank state.
62
+ *
63
+ * @param {object} input
64
+ * @param {object} [input.persisted] - parsed `.sweet-search/config.json`
65
+ * @param {string} [input.persisted.liModel] - 'lateon-code' | 'lateon-code-edge' | 'none'
66
+ * @param {string} [input.persisted.searchReranking] - 'auto' | 'on' | 'off'
67
+ * @param {object} [input.indexManifest] - loaded LI index header
68
+ * @param {string} [input.indexManifest.modelId] - id baked into the SSLX header
69
+ * @param {number} [input.indexManifest.tokenDim] - per-token dimension
70
+ * @param {boolean} [input.indexManifest.modelMismatch] - true when loaded
71
+ * modelId disagrees with the active config model
72
+ * @param {boolean} [input.indexManifest.exists] - false when no index file on disk
73
+ * @param {object} [input.env] - env-var snapshot (defaults to process.env)
74
+ * @param {boolean} [input.optionOverride] - explicit per-call override from caller
75
+ * (search-time options.useLateInteraction). If a boolean, it short-circuits the
76
+ * resolver and wins over everything else.
77
+ * @param {string} [input.activeConfigModel] - LATE_INTERACTION_CONFIG.model fallback
78
+ * (used when the index hasn't been loaded yet so we can still emit a sensible
79
+ * default — preserves back-compat with the pre-Phase-4 behaviour).
80
+ *
81
+ * @returns {{
82
+ * effective: boolean,
83
+ * policy: 'auto'|'on'|'off',
84
+ * reason: string,
85
+ * warning?: string
86
+ * }}
87
+ */
88
+ export function resolveSearchRerankPolicy(input = {}) {
89
+ const env = input.env ?? process.env;
90
+ const persisted = input.persisted ?? {};
91
+ const manifest = input.indexManifest ?? null;
92
+ const declaredPolicy = normalizePolicy(persisted.searchReranking);
93
+
94
+ // 1. Per-call explicit override (existing API, highest precedence).
95
+ if (typeof input.optionOverride === 'boolean') {
96
+ return {
97
+ effective: input.optionOverride,
98
+ policy: declaredPolicy,
99
+ reason: `per-call override (${input.optionOverride ? 'on' : 'off'})`,
100
+ };
101
+ }
102
+
103
+ // 2. Env-var hard kill switch — opt-out for benchmarks / scripts that
104
+ // must defeat any persisted policy. Mirrors SWEET_SEARCH_COREML_CASCADE=0.
105
+ if (isEnvOff(env.SWEET_SEARCH_LI_RERANK)) {
106
+ return {
107
+ effective: false,
108
+ policy: declaredPolicy,
109
+ reason: 'SWEET_SEARCH_LI_RERANK=0 (env opt-out)',
110
+ };
111
+ }
112
+ if (isEnvOn(env.SWEET_SEARCH_LI_RERANK)) {
113
+ // Env-on still goes through the safety check below — we never silently
114
+ // rerank with a missing or mismatched index.
115
+ if (!manifestUsable(manifest)) {
116
+ return {
117
+ effective: false,
118
+ policy: declaredPolicy,
119
+ reason: 'SWEET_SEARCH_LI_RERANK=1 but no usable LI index',
120
+ warning: manifestUnusableReason(manifest),
121
+ };
122
+ }
123
+ return {
124
+ effective: true,
125
+ policy: declaredPolicy,
126
+ reason: 'SWEET_SEARCH_LI_RERANK=1 (env opt-in)',
127
+ ...(isEdgeManifest(manifest) ? { warning: edgeOnWarning() } : {}),
128
+ };
129
+ }
130
+
131
+ // 3. Persisted explicit ON / OFF (init wizard / --search-reranking).
132
+ if (declaredPolicy === 'off') {
133
+ return {
134
+ effective: false,
135
+ policy: 'off',
136
+ reason: 'persisted searchReranking=off',
137
+ };
138
+ }
139
+ if (declaredPolicy === 'on') {
140
+ if (!manifestUsable(manifest)) {
141
+ return {
142
+ effective: false,
143
+ policy: 'on',
144
+ reason: 'persisted searchReranking=on but no usable LI index',
145
+ warning: manifestUnusableReason(manifest),
146
+ };
147
+ }
148
+ return {
149
+ effective: true,
150
+ policy: 'on',
151
+ reason: 'persisted searchReranking=on',
152
+ ...(isEdgeManifest(manifest) ? { warning: edgeOnWarning() } : {}),
153
+ };
154
+ }
155
+
156
+ // 4. Auto (the default): consult the index manifest.
157
+ // - missing/mismatched → off (with diagnostic)
158
+ // - edge modelId → off (Phase 3 shows edge LI rerank is net-negative)
159
+ // - any standard model → on
160
+ if (manifest != null && (manifest.exists === false)) {
161
+ return {
162
+ effective: false,
163
+ policy: 'auto',
164
+ reason: 'auto: no LI index on disk',
165
+ };
166
+ }
167
+ if (manifest != null && manifest.modelMismatch === true) {
168
+ return {
169
+ effective: false,
170
+ policy: 'auto',
171
+ reason: `auto: LI index model mismatch (header=${manifest.modelId ?? '?'})`,
172
+ };
173
+ }
174
+ if (manifest != null && isEdgeManifest(manifest)) {
175
+ return {
176
+ effective: false,
177
+ policy: 'auto',
178
+ reason: `auto: edge LI index (${manifest.modelId}) — search rerank disabled by default (see Phase 3 bench)`,
179
+ };
180
+ }
181
+ if (manifest != null && manifest.modelId) {
182
+ return {
183
+ effective: true,
184
+ policy: 'auto',
185
+ reason: `auto: standard LI index (${manifest.modelId})`,
186
+ };
187
+ }
188
+
189
+ // 5. Fallback — manifest not yet loaded (early call path) OR no manifest
190
+ // info at all. Defer to the active config model so existing flows that
191
+ // constructed SweetSearch before init() still get the historical
192
+ // behaviour. Edge model active → off; anything else → on.
193
+ if (input.activeConfigModel === LI_MODEL_EDGE) {
194
+ return {
195
+ effective: false,
196
+ policy: 'auto',
197
+ reason: 'auto: edge LI active in config (manifest not yet loaded)',
198
+ };
199
+ }
200
+ return {
201
+ effective: true,
202
+ policy: 'auto',
203
+ reason: 'auto: standard LI active in config (manifest not yet loaded)',
204
+ };
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // Helpers (also exported for test reuse)
209
+ // ---------------------------------------------------------------------------
210
+
211
+ export function normalizePolicy(value) {
212
+ if (typeof value !== 'string') return 'auto';
213
+ const v = value.trim().toLowerCase();
214
+ return VALID_RERANK_POLICIES.includes(v) ? v : 'auto';
215
+ }
216
+
217
+ export function normalizeLiModel(value) {
218
+ if (typeof value !== 'string') return null;
219
+ const v = value.trim();
220
+ return VALID_LI_MODELS.includes(v) ? v : null;
221
+ }
222
+
223
+ export function isEdgeManifest(manifest) {
224
+ return !!manifest && manifest.modelId === LI_MODEL_EDGE;
225
+ }
226
+
227
+ export function manifestUsable(manifest) {
228
+ if (manifest == null) return false;
229
+ if (manifest.exists === false) return false;
230
+ if (manifest.modelMismatch === true) return false;
231
+ if (!manifest.modelId) return false;
232
+ return true;
233
+ }
234
+
235
+ function manifestUnusableReason(manifest) {
236
+ if (manifest == null) return 'no LI index manifest';
237
+ if (manifest.exists === false) return 'no LI index file on disk';
238
+ if (manifest.modelMismatch === true) {
239
+ return `LI index built with ${manifest.modelId ?? '?'} but config says otherwise — re-index to fix`;
240
+ }
241
+ if (!manifest.modelId) return 'LI index manifest missing modelId';
242
+ return 'unusable LI index manifest';
243
+ }
244
+
245
+ function edgeOnWarning() {
246
+ return (
247
+ 'Edge LI search reranking benchmarked below no-rerank search on '
248
+ + 'gencodesearchnet (80.65% vs 82.91% MRR). The recommended edge '
249
+ + 'setup is search reranking off — edge LI tokens still power '
250
+ + 'read-semantic and ColGrep without participating in search rerank.'
251
+ );
252
+ }
253
+
254
+ // ---------------------------------------------------------------------------
255
+ // Init-side hardware-aware default
256
+ // ---------------------------------------------------------------------------
257
+
258
+ /**
259
+ * Recommend an LI model + rerank policy from a hardware capability snapshot.
260
+ * Pure function — caller passes in detectHardwareCapability() output.
261
+ *
262
+ * Conservative: accuracy-first by default. Only recommends edge when the
263
+ * machine is clearly RAM- or disk-constrained.
264
+ *
265
+ * @param {object} hw - shape returned by detectHardwareCapability()
266
+ * @returns {{ liModel: string, searchReranking: 'auto', reason: string }}
267
+ */
268
+ export function recommendInitDefaults(hw = {}) {
269
+ const ramGB = Number(hw.totalMemGB ?? 0);
270
+ // Constrained heuristic (intentionally narrow — "accuracy-first unless
271
+ // hardware/disk clearly indicates constrained mode" per Phase 4 brief):
272
+ // - RAM ≤ 8 GB → edge
273
+ // - Apple Silicon M1/M2 (older ANE) → edge candidate
274
+ // - everything else → standard
275
+ // The bench shows standard LI is the accuracy default; only flip when
276
+ // the constrained signal is unambiguous.
277
+ const isLowRam = ramGB > 0 && ramGB <= 8;
278
+ const isOlderApple =
279
+ hw.appleSilicon && typeof hw.appleSilicon === 'object'
280
+ ? Number(hw.appleSilicon.generation ?? hw.appleSilicon.gen ?? 99) <= 2
281
+ : false;
282
+
283
+ if (isLowRam) {
284
+ return {
285
+ liModel: LI_MODEL_EDGE,
286
+ searchReranking: 'auto',
287
+ reason: `constrained: RAM=${ramGB} GB (≤8) — edge LI for indexing, search rerank auto-disables on edge per Phase 3 bench`,
288
+ };
289
+ }
290
+ if (isOlderApple && ramGB > 0 && ramGB <= 16) {
291
+ return {
292
+ liModel: LI_MODEL_EDGE,
293
+ searchReranking: 'auto',
294
+ reason: `constrained: M1/M2 + RAM=${ramGB} GB — edge LI recommended for indexing speed + disk savings`,
295
+ };
296
+ }
297
+ return {
298
+ liModel: LI_MODEL_STANDARD,
299
+ searchReranking: 'auto',
300
+ reason: ramGB > 0
301
+ ? `capable: RAM=${ramGB} GB — standard LI (accuracy default at 85.57% MRR on gencodesearchnet)`
302
+ : 'capable (default): standard LI (accuracy default)',
303
+ };
304
+ }