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.
- package/core/cli.js +43 -5
- package/core/embedding/embedding-cache.js +266 -18
- package/core/embedding/embedding-service.js +45 -9
- package/core/graph/graph-expansion.js +52 -12
- package/core/graph/graph-extractor.js +30 -1
- package/core/indexing/ast-chunker.js +331 -16
- package/core/indexing/chunking/chunk-builder.js +34 -1
- package/core/indexing/index-codebase-v21.js +31 -2
- package/core/indexing/index.js +6 -3
- package/core/indexing/indexer-ann.js +45 -6
- package/core/indexing/indexer-build.js +9 -1
- package/core/indexing/indexer-phases.js +6 -4
- package/core/indexing/indexing-file-policy.js +140 -0
- package/core/indexing/li-skip-policy.js +11 -220
- package/core/infrastructure/codebase-repository.js +21 -0
- package/core/infrastructure/config/embedding.js +20 -1
- package/core/infrastructure/config/graph.js +2 -2
- package/core/infrastructure/config/ranking.js +10 -0
- package/core/infrastructure/config/vector-store.js +1 -1
- package/core/infrastructure/coreml-cascade.js +236 -30
- package/core/infrastructure/coreml-cascade.json +25 -0
- package/core/infrastructure/index.js +17 -0
- package/core/infrastructure/init-config.js +216 -0
- package/core/infrastructure/language-patterns/registry-core.js +18 -0
- package/core/infrastructure/model-registry.js +12 -0
- package/core/infrastructure/native-inference.js +143 -51
- package/core/infrastructure/tree-sitter-provider.js +92 -2
- package/core/ranking/cascaded-scorer.js +6 -2
- package/core/ranking/file-kind-ranking.js +264 -0
- package/core/ranking/late-interaction-index.js +10 -4
- package/core/ranking/late-interaction-policy.js +304 -0
- package/core/search/context-expander.js +267 -28
- package/core/search/index.js +4 -0
- package/core/search/search-cli.js +3 -1
- package/core/search/search-pattern.js +4 -3
- package/core/search/search-postprocess.js +189 -8
- package/core/search/search-read-semantic.js +734 -0
- package/core/search/search-read.js +481 -0
- package/core/search/search-server.js +153 -5
- package/core/search/sweet-search.js +133 -16
- package/core/start-server.js +13 -2
- package/mcp/server.js +41 -0
- package/mcp/tool-handlers.js +117 -6
- package/package.json +9 -7
- package/scripts/init.js +386 -5
- 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
|
+
}
|