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
package/scripts/init.js
CHANGED
|
@@ -23,6 +23,16 @@ import {
|
|
|
23
23
|
isDedupAvailable, getDedupLoadError, computeFingerprints, clusterFingerprints,
|
|
24
24
|
DEDUP_CONFIG,
|
|
25
25
|
} from '../core/infrastructure/index.js';
|
|
26
|
+
import {
|
|
27
|
+
LI_MODEL_EDGE,
|
|
28
|
+
LI_MODEL_NONE,
|
|
29
|
+
LI_MODEL_STANDARD,
|
|
30
|
+
VALID_LI_MODELS,
|
|
31
|
+
VALID_RERANK_POLICIES,
|
|
32
|
+
normalizeLiModel,
|
|
33
|
+
normalizePolicy,
|
|
34
|
+
recommendInitDefaults,
|
|
35
|
+
} from '../core/ranking/late-interaction-policy.js';
|
|
26
36
|
import { describeDedupConfig } from '../core/infrastructure/index.js';
|
|
27
37
|
import { verifyRuntime, getMaxsimTier, getRouterType } from './verify-runtime.js';
|
|
28
38
|
|
|
@@ -48,6 +58,9 @@ export function parseInitArgs(args) {
|
|
|
48
58
|
skipDedup: false,
|
|
49
59
|
skipPrewarmHook: false,
|
|
50
60
|
skipCuda: false,
|
|
61
|
+
liModel: null, // Phase 4: --li-model standard|edge|none (raw user input; 'standard' aliased to 'lateon-code')
|
|
62
|
+
searchReranking: null, // Phase 4: --search-reranking auto|on|off
|
|
63
|
+
wizard: false, // Phase 4: --wizard runs interactive prompts
|
|
51
64
|
};
|
|
52
65
|
|
|
53
66
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -58,6 +71,16 @@ export function parseInitArgs(args) {
|
|
|
58
71
|
result.profile = args[++i] || null;
|
|
59
72
|
} else if (arg.startsWith('--profile=')) {
|
|
60
73
|
result.profile = arg.split('=')[1];
|
|
74
|
+
} else if (arg === '--li-model') {
|
|
75
|
+
result.liModel = args[++i] || null;
|
|
76
|
+
} else if (arg.startsWith('--li-model=')) {
|
|
77
|
+
result.liModel = arg.split('=')[1];
|
|
78
|
+
} else if (arg === '--search-reranking') {
|
|
79
|
+
result.searchReranking = args[++i] || null;
|
|
80
|
+
} else if (arg.startsWith('--search-reranking=')) {
|
|
81
|
+
result.searchReranking = arg.split('=')[1];
|
|
82
|
+
} else if (arg === '--wizard') {
|
|
83
|
+
result.wizard = true;
|
|
61
84
|
} else if (arg === '--verify-deep') {
|
|
62
85
|
result.verifyDeep = true;
|
|
63
86
|
} else if (arg === '--force') {
|
|
@@ -157,6 +180,234 @@ export function writeInitConfig(dataDir, config) {
|
|
|
157
180
|
renameSync(tmpPath, configPath);
|
|
158
181
|
}
|
|
159
182
|
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// LI policy resolution (Phase 4)
|
|
185
|
+
//
|
|
186
|
+
// Two persisted choices land in `.sweet-search/config.json::runtime.li`:
|
|
187
|
+
// - `model` : 'lateon-code' | 'lateon-code-edge' | 'none'
|
|
188
|
+
// - `searchReranking` : 'auto' | 'on' | 'off'
|
|
189
|
+
//
|
|
190
|
+
// Precedence (most-specific wins):
|
|
191
|
+
// 1. CLI flag (--li-model, --search-reranking) — explicit, persisted
|
|
192
|
+
// 2. --wizard interactive prompt (when TTY); non-TTY falls back to (3)/(4)
|
|
193
|
+
// 3. Existing persisted config (re-uses prior choice across re-runs)
|
|
194
|
+
// 4. Hardware-aware recommendation (recommendInitDefaults)
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
const LI_MODEL_ALIASES = {
|
|
198
|
+
standard: LI_MODEL_STANDARD,
|
|
199
|
+
full: LI_MODEL_STANDARD,
|
|
200
|
+
edge: LI_MODEL_EDGE,
|
|
201
|
+
small: LI_MODEL_EDGE,
|
|
202
|
+
none: LI_MODEL_NONE,
|
|
203
|
+
off: LI_MODEL_NONE,
|
|
204
|
+
disable: LI_MODEL_NONE,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Coerce a user-facing model alias to a canonical id. Returns null when
|
|
209
|
+
* the input is unrecognized so the caller can produce a clear error.
|
|
210
|
+
*/
|
|
211
|
+
export function coerceLiModelChoice(value) {
|
|
212
|
+
if (typeof value !== 'string') return null;
|
|
213
|
+
const v = value.trim().toLowerCase();
|
|
214
|
+
if (LI_MODEL_ALIASES[v]) return LI_MODEL_ALIASES[v];
|
|
215
|
+
return normalizeLiModel(v);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Read the LI policy section from a parsed init config (the format written
|
|
220
|
+
* by `buildConfig`). Defensive — returns an empty object when the section
|
|
221
|
+
* is missing or malformed.
|
|
222
|
+
*/
|
|
223
|
+
function readPersistedLi(existingConfig) {
|
|
224
|
+
const li = existingConfig?.runtime?.li;
|
|
225
|
+
if (!li || typeof li !== 'object') return {};
|
|
226
|
+
const out = {};
|
|
227
|
+
if (typeof li.model === 'string' && VALID_LI_MODELS.includes(li.model)) {
|
|
228
|
+
out.liModel = li.model;
|
|
229
|
+
}
|
|
230
|
+
if (typeof li.searchReranking === 'string' && VALID_RERANK_POLICIES.includes(li.searchReranking)) {
|
|
231
|
+
out.searchReranking = li.searchReranking;
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Resolve the LI model + rerank policy from CLI args, persisted config,
|
|
238
|
+
* and hardware capability. Pure (modulo `wizardFn` which performs I/O when
|
|
239
|
+
* --wizard is set). Suitable for unit testing — pass a fake `wizardFn` and
|
|
240
|
+
* a fixed capability snapshot.
|
|
241
|
+
*
|
|
242
|
+
* @param {object} input
|
|
243
|
+
* @param {object} input.parsed - parseInitArgs result
|
|
244
|
+
* @param {object|null} input.existingConfig - prior `.sweet-search/config.json`
|
|
245
|
+
* @param {object} input.capability - detectHardwareCapability snapshot
|
|
246
|
+
* @param {boolean} input.isTTY - whether stdin is a TTY (for wizard fallback)
|
|
247
|
+
* @param {Function} [input.wizardFn] - async ({existingConfig, capability, recommendation, defaults}) => {liModel, searchReranking}
|
|
248
|
+
*
|
|
249
|
+
* @returns {Promise<{liModel: string, searchReranking: string, source: string, recommendation: object}>}
|
|
250
|
+
*/
|
|
251
|
+
export async function resolveLiPolicyChoices({
|
|
252
|
+
parsed,
|
|
253
|
+
existingConfig,
|
|
254
|
+
capability,
|
|
255
|
+
isTTY,
|
|
256
|
+
wizardFn,
|
|
257
|
+
}) {
|
|
258
|
+
const recommendation = recommendInitDefaults(capability ?? {});
|
|
259
|
+
const persisted = readPersistedLi(existingConfig);
|
|
260
|
+
|
|
261
|
+
// (1) CLI flags — validate now, fail fast with a clear message so users
|
|
262
|
+
// don't need to dig into the resolver. Wizard runs after CLI flags
|
|
263
|
+
// are validated so a bad --li-model never reaches the prompt.
|
|
264
|
+
let cliModel = null;
|
|
265
|
+
if (parsed.liModel) {
|
|
266
|
+
cliModel = coerceLiModelChoice(parsed.liModel);
|
|
267
|
+
if (cliModel == null) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Invalid --li-model "${parsed.liModel}". Valid choices: ${VALID_LI_MODELS.join(', ')} (aliases: standard, edge, none).`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
let cliReranking = null;
|
|
274
|
+
if (parsed.searchReranking) {
|
|
275
|
+
const v = String(parsed.searchReranking).trim().toLowerCase();
|
|
276
|
+
if (!VALID_RERANK_POLICIES.includes(v)) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Invalid --search-reranking "${parsed.searchReranking}". Valid choices: ${VALID_RERANK_POLICIES.join(', ')}.`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
cliReranking = v;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// (2) --wizard. Only meaningful on a TTY; on a non-TTY, fall through
|
|
285
|
+
// silently to (3)/(4) — wizard exists for humans, not CI.
|
|
286
|
+
let wizardChoice = null;
|
|
287
|
+
if (parsed.wizard && isTTY && typeof wizardFn === 'function') {
|
|
288
|
+
wizardChoice = await wizardFn({
|
|
289
|
+
existingConfig,
|
|
290
|
+
capability,
|
|
291
|
+
recommendation,
|
|
292
|
+
defaults: {
|
|
293
|
+
liModel: persisted.liModel ?? recommendation.liModel,
|
|
294
|
+
searchReranking: persisted.searchReranking ?? recommendation.searchReranking,
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
} else if (parsed.wizard && !isTTY) {
|
|
298
|
+
process.stderr.write(
|
|
299
|
+
'[init] --wizard requested on a non-TTY stdin; falling back to persisted config / hardware recommendation.\n',
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Final selection — first non-null source for each field wins.
|
|
304
|
+
const liModel =
|
|
305
|
+
cliModel
|
|
306
|
+
?? wizardChoice?.liModel
|
|
307
|
+
?? persisted.liModel
|
|
308
|
+
?? recommendation.liModel;
|
|
309
|
+
|
|
310
|
+
const searchReranking =
|
|
311
|
+
cliReranking
|
|
312
|
+
?? wizardChoice?.searchReranking
|
|
313
|
+
?? persisted.searchReranking
|
|
314
|
+
?? recommendation.searchReranking;
|
|
315
|
+
|
|
316
|
+
// Source label for logging — "where did each field actually come from".
|
|
317
|
+
const sourceFor = (cli, wiz, per) => {
|
|
318
|
+
if (cli) return 'cli';
|
|
319
|
+
if (wiz) return 'wizard';
|
|
320
|
+
if (per) return 'persisted';
|
|
321
|
+
return 'recommendation';
|
|
322
|
+
};
|
|
323
|
+
const source = {
|
|
324
|
+
liModel: sourceFor(cliModel, wizardChoice?.liModel, persisted.liModel),
|
|
325
|
+
searchReranking: sourceFor(cliReranking, wizardChoice?.searchReranking, persisted.searchReranking),
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
return { liModel, searchReranking, source, recommendation };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Interactive prompt — readline/promises. Caller has already verified
|
|
333
|
+
* `process.stdin.isTTY`. Echoes the recommendation, accepts blank input
|
|
334
|
+
* as the default, and validates each prompt before persisting.
|
|
335
|
+
*
|
|
336
|
+
* Exit ergonomics: blank input always defaults; ^C is allowed to throw,
|
|
337
|
+
* caller treats it as "no choice made" and re-runs without --wizard.
|
|
338
|
+
*/
|
|
339
|
+
export async function runInitWizard({ existingConfig, capability, recommendation, defaults }) {
|
|
340
|
+
const { createInterface } = await import('node:readline/promises');
|
|
341
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
342
|
+
try {
|
|
343
|
+
process.stdout.write('\n');
|
|
344
|
+
process.stdout.write('Sweet Search init — interactive setup\n');
|
|
345
|
+
process.stdout.write('---------------------------------------\n');
|
|
346
|
+
if (capability?.brandString) {
|
|
347
|
+
process.stdout.write(`Hardware: ${capability.brandString} (${capability.totalMemGB?.toFixed?.(0) ?? '?'} GB RAM)\n`);
|
|
348
|
+
}
|
|
349
|
+
if (recommendation?.reason) {
|
|
350
|
+
process.stdout.write(`Recommendation: ${recommendation.reason}\n`);
|
|
351
|
+
}
|
|
352
|
+
if (existingConfig?.runtime?.li) {
|
|
353
|
+
process.stdout.write(
|
|
354
|
+
`Currently persisted: liModel=${existingConfig.runtime.li.model ?? '?'}, `
|
|
355
|
+
+ `searchReranking=${existingConfig.runtime.li.searchReranking ?? '?'}\n`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
process.stdout.write('\n');
|
|
359
|
+
|
|
360
|
+
// ---- LI model prompt ----
|
|
361
|
+
const modelPrompt =
|
|
362
|
+
`Late-interaction model? [standard / edge / none] (default: ${defaults.liModel}): `;
|
|
363
|
+
const liModelRaw = (await rl.question(modelPrompt)).trim() || defaults.liModel;
|
|
364
|
+
const liModel = coerceLiModelChoice(liModelRaw);
|
|
365
|
+
if (!liModel) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Invalid model choice "${liModelRaw}". Valid: ${VALID_LI_MODELS.join(', ')} (aliases: standard, edge, none).`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ---- Search reranking prompt ----
|
|
372
|
+
// Suppress when liModel === 'none' — without an LI index there's
|
|
373
|
+
// nothing to rerank, so persist 'off' explicitly.
|
|
374
|
+
let searchReranking;
|
|
375
|
+
if (liModel === LI_MODEL_NONE) {
|
|
376
|
+
process.stdout.write(' (skipping search-reranking prompt — liModel=none implies no rerank)\n');
|
|
377
|
+
searchReranking = 'off';
|
|
378
|
+
} else {
|
|
379
|
+
const rerankPrompt =
|
|
380
|
+
`Search-time LI reranking? [auto / on / off] (default: ${defaults.searchReranking}): `;
|
|
381
|
+
const raw = (await rl.question(rerankPrompt)).trim() || defaults.searchReranking;
|
|
382
|
+
const v = raw.toLowerCase();
|
|
383
|
+
if (!VALID_RERANK_POLICIES.includes(v)) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Invalid rerank choice "${raw}". Valid: ${VALID_RERANK_POLICIES.join(', ')}.`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
searchReranking = v;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ---- Echo + confirm ----
|
|
392
|
+
process.stdout.write('\n');
|
|
393
|
+
process.stdout.write(`Plan: liModel=${liModel}, searchReranking=${searchReranking}\n`);
|
|
394
|
+
if (liModel === LI_MODEL_EDGE && searchReranking === 'on') {
|
|
395
|
+
process.stdout.write(
|
|
396
|
+
'Note: edge LI rerank benchmarked below no-rerank on gencodesearchnet '
|
|
397
|
+
+ '(80.65% vs 82.91% MRR). "auto" is recommended.\n',
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
const confirm = (await rl.question('Proceed? [Y/n]: ')).trim().toLowerCase();
|
|
401
|
+
if (confirm === 'n' || confirm === 'no') {
|
|
402
|
+
throw new Error('Wizard cancelled by user.');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return { liModel, searchReranking };
|
|
406
|
+
} finally {
|
|
407
|
+
rl.close();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
160
411
|
// ---------------------------------------------------------------------------
|
|
161
412
|
// Profile resolution
|
|
162
413
|
// ---------------------------------------------------------------------------
|
|
@@ -219,8 +470,36 @@ export function checkNativeStatus() {
|
|
|
219
470
|
// Model download
|
|
220
471
|
// ---------------------------------------------------------------------------
|
|
221
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Filter the profile's model keys based on the resolved `liModel` choice.
|
|
475
|
+
* - 'lateon-code' → exclude all lateon-code-edge* keys (don't fetch the edge variant)
|
|
476
|
+
* - 'lateon-code-edge' → exclude all standard lateon-code* keys (don't fetch the standard 768d backbone)
|
|
477
|
+
* - 'none' → exclude every lateon-* key (search rerank disabled product-wide)
|
|
478
|
+
*
|
|
479
|
+
* Order-preserving + non-mutating: returns a new array.
|
|
480
|
+
*/
|
|
481
|
+
export function filterModelKeysForLiChoice(modelKeys, liModel) {
|
|
482
|
+
if (liModel === LI_MODEL_NONE) {
|
|
483
|
+
return modelKeys.filter((k) => !k.startsWith('lateon-code'));
|
|
484
|
+
}
|
|
485
|
+
if (liModel === LI_MODEL_STANDARD) {
|
|
486
|
+
return modelKeys.filter((k) => !k.startsWith('lateon-code-edge'));
|
|
487
|
+
}
|
|
488
|
+
if (liModel === LI_MODEL_EDGE) {
|
|
489
|
+
// Keep only edge-flavoured keys; everything matching `lateon-code` but
|
|
490
|
+
// not `lateon-code-edge` is a standard variant.
|
|
491
|
+
return modelKeys.filter(
|
|
492
|
+
(k) => !k.startsWith('lateon-code') || k.startsWith('lateon-code-edge'),
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
return modelKeys;
|
|
496
|
+
}
|
|
497
|
+
|
|
222
498
|
export async function downloadModelsForProfile(profile, options = {}) {
|
|
223
|
-
|
|
499
|
+
let modelKeys = getModelsForProfile(profile);
|
|
500
|
+
if (options.liModel) {
|
|
501
|
+
modelKeys = filterModelKeysForLiChoice(modelKeys, options.liModel);
|
|
502
|
+
}
|
|
224
503
|
if (modelKeys.length === 0) {
|
|
225
504
|
return { results: new Map(), totalDownloaded: 0, totalCached: 0, failures: [] };
|
|
226
505
|
}
|
|
@@ -278,6 +557,7 @@ function printReport(report) {
|
|
|
278
557
|
const {
|
|
279
558
|
profile, maxsimTier, routerType, models, verification, runtimeDownloads,
|
|
280
559
|
capability, cascadeReport, dedupReport, prewarmHookReport, skillReport,
|
|
560
|
+
liChoices,
|
|
281
561
|
} = report;
|
|
282
562
|
|
|
283
563
|
console.log('');
|
|
@@ -292,6 +572,20 @@ function printReport(report) {
|
|
|
292
572
|
console.log(` MaxSim: ${maxsimTier}`);
|
|
293
573
|
console.log(` Router: ${routerType}`);
|
|
294
574
|
|
|
575
|
+
if (liChoices) {
|
|
576
|
+
const { liModel, searchReranking, source } = liChoices;
|
|
577
|
+
const srcStr = source
|
|
578
|
+
? ` (model: ${source.liModel}, rerank: ${source.searchReranking})`
|
|
579
|
+
: '';
|
|
580
|
+
console.log(` LI model: ${liModel}${srcStr}`);
|
|
581
|
+
console.log(` LI search rerank: ${searchReranking}`);
|
|
582
|
+
if (liModel === LI_MODEL_NONE) {
|
|
583
|
+
console.log(' ↳ rerank, read-semantic, ColGrep all disabled');
|
|
584
|
+
} else if (liModel === LI_MODEL_EDGE && searchReranking === 'on') {
|
|
585
|
+
console.log(' ↳ NOTE: edge LI rerank benchmarked below no-rerank — consider "auto"');
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
295
589
|
// NVIDIA / CUDA status line. Shown only when it's actionable:
|
|
296
590
|
// cudaAvailable=true → confirm GPU detection (user-visible win)
|
|
297
591
|
// nvidiaGpu present but cudaAddonEnabled=false → warn that the
|
|
@@ -606,6 +900,18 @@ Usage:
|
|
|
606
900
|
|
|
607
901
|
Options:
|
|
608
902
|
--profile <profile> Install profile: core, full (default: full)
|
|
903
|
+
--li-model <choice> Late-interaction model: standard | edge | none.
|
|
904
|
+
Aliases: 'standard' = lateon-code (149M, 128d, accuracy
|
|
905
|
+
default), 'edge' = lateon-code-edge (17M, 48d, smaller +
|
|
906
|
+
faster), 'none' = disable LI rerank, read-semantic and
|
|
907
|
+
ColGrep entirely. Persisted to .sweet-search/config.json.
|
|
908
|
+
--search-reranking <p> Search-time LI rerank policy: auto | on | off.
|
|
909
|
+
'auto' resolves from the LI index manifest at search time
|
|
910
|
+
(standard index → on, edge index → off, missing/mismatched
|
|
911
|
+
→ off). 'on' / 'off' are explicit overrides. Persisted.
|
|
912
|
+
--wizard Run interactive setup prompts for --li-model and
|
|
913
|
+
--search-reranking. Falls back to persisted/recommended
|
|
914
|
+
defaults on a non-TTY stdin (CI-safe).
|
|
609
915
|
--verify-deep Run deep verification (load modules, verify checksums)
|
|
610
916
|
--force Re-download all models even if cached
|
|
611
917
|
--build-coreml-cascade (M3+ Apple Silicon, local builds only) Trace the
|
|
@@ -688,6 +994,39 @@ export async function runInit(args) {
|
|
|
688
994
|
const profile = resolveProfile(parsed.profile, existingConfig);
|
|
689
995
|
process.stderr.write(`[init] Profile: ${profile}\n`);
|
|
690
996
|
|
|
997
|
+
// 4.5. Resolve LI policy (Phase 4 — `--li-model`, `--search-reranking`,
|
|
998
|
+
// `--wizard`). Done early so the model-fetch loop, cascade fetch,
|
|
999
|
+
// and final config write all see the same choices. Uses an early
|
|
1000
|
+
// hardware capability snapshot for the recommendation; the snapshot
|
|
1001
|
+
// is recomputed later for the runtime config (cheap + cacheable).
|
|
1002
|
+
const earlyCapability = detectHardwareCapability();
|
|
1003
|
+
let liChoices;
|
|
1004
|
+
try {
|
|
1005
|
+
liChoices = await resolveLiPolicyChoices({
|
|
1006
|
+
parsed,
|
|
1007
|
+
existingConfig,
|
|
1008
|
+
capability: earlyCapability,
|
|
1009
|
+
isTTY: process.stdin.isTTY === true,
|
|
1010
|
+
wizardFn: runInitWizard,
|
|
1011
|
+
});
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
process.stderr.write(`[init] LI policy resolution failed: ${err.message}\n`);
|
|
1014
|
+
process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
process.stderr.write(
|
|
1017
|
+
`[init] LI policy: model=${liChoices.liModel} (${liChoices.source.liModel}), `
|
|
1018
|
+
+ `searchReranking=${liChoices.searchReranking} (${liChoices.source.searchReranking})\n`,
|
|
1019
|
+
);
|
|
1020
|
+
if (liChoices.source.liModel === 'recommendation' && liChoices.recommendation?.reason) {
|
|
1021
|
+
process.stderr.write(`[init] recommendation reason: ${liChoices.recommendation.reason}\n`);
|
|
1022
|
+
}
|
|
1023
|
+
if (liChoices.liModel === LI_MODEL_NONE) {
|
|
1024
|
+
process.stderr.write(
|
|
1025
|
+
'[init] note: liModel=none disables LI rerank, read-semantic and ColGrep — '
|
|
1026
|
+
+ 're-run with --li-model standard|edge to re-enable.\n',
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
691
1030
|
// 5. Verify runtime assets (WASM, router, etc.)
|
|
692
1031
|
const assetCheck = verifyRuntimeAssets(PACKAGE_ROOT);
|
|
693
1032
|
if (!assetCheck.ok) {
|
|
@@ -709,8 +1048,18 @@ export async function runInit(args) {
|
|
|
709
1048
|
process.stderr.write(`[init] Router type: ${routerType}\n`);
|
|
710
1049
|
}
|
|
711
1050
|
|
|
712
|
-
// 7. Download models for profile
|
|
713
|
-
|
|
1051
|
+
// 7. Download models for profile (filtered by LI choice).
|
|
1052
|
+
// `liModel === 'none'` skips every lateon-* key; 'standard' skips edge
|
|
1053
|
+
// variants; 'edge' skips standard variants. Saves disk + bandwidth on
|
|
1054
|
+
// constrained installs.
|
|
1055
|
+
const allModelKeys = getModelsForProfile(profile);
|
|
1056
|
+
const modelKeys = filterModelKeysForLiChoice(allModelKeys, liChoices.liModel);
|
|
1057
|
+
const skippedByLiChoice = allModelKeys.filter((k) => !modelKeys.includes(k));
|
|
1058
|
+
if (skippedByLiChoice.length > 0 && parsed.verbose) {
|
|
1059
|
+
process.stderr.write(
|
|
1060
|
+
`[init] liModel=${liChoices.liModel} → skipping ${skippedByLiChoice.length} model key(s): ${skippedByLiChoice.join(', ')}\n`,
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
714
1063
|
const skippedOptIns = getSkippedOptInModels(profile);
|
|
715
1064
|
let modelResults = new Map();
|
|
716
1065
|
|
|
@@ -732,6 +1081,7 @@ export async function runInit(args) {
|
|
|
732
1081
|
process.stderr.write(`[init] Downloading models for profile "${profile}"...\n`);
|
|
733
1082
|
const downloadResult = await downloadModelsForProfile(profile, {
|
|
734
1083
|
force: parsed.force,
|
|
1084
|
+
liModel: liChoices.liModel,
|
|
735
1085
|
});
|
|
736
1086
|
|
|
737
1087
|
modelResults = downloadResult.results;
|
|
@@ -748,6 +1098,7 @@ export async function runInit(args) {
|
|
|
748
1098
|
profile, maxsimTier, routerType, nativeStatus,
|
|
749
1099
|
modelResults, verification: { type: 'none', timestamp: new Date().toISOString(), checks: [] },
|
|
750
1100
|
failed: true,
|
|
1101
|
+
liChoices,
|
|
751
1102
|
});
|
|
752
1103
|
writeInitConfig(dataDir, partialConfig);
|
|
753
1104
|
|
|
@@ -790,7 +1141,17 @@ export async function runInit(args) {
|
|
|
790
1141
|
// from the addon's point of view.
|
|
791
1142
|
const capability = detectHardwareCapability();
|
|
792
1143
|
let cascadeReport = { status: 'skipped', detail: 'Cascade inspection skipped' };
|
|
793
|
-
|
|
1144
|
+
// Phase 4: liModel === 'none' opts out of the cascade entirely (no LI rerank,
|
|
1145
|
+
// no read-semantic, no ColGrep — the only consumer of the LI cascade family).
|
|
1146
|
+
// The embed cascade is still useful for fast NomicBERT inference, but
|
|
1147
|
+
// without an LI consumer the "auto" family resolution would still pick
|
|
1148
|
+
// standard or edge LI tarballs we'll never use. Skipping outright is
|
|
1149
|
+
// cleaner and saves ~600 MB.
|
|
1150
|
+
const skipCascadeForLiNone = liChoices.liModel === LI_MODEL_NONE;
|
|
1151
|
+
if (skipCascadeForLiNone && !parsed.skipCoremlCascade && profile !== 'core' && parsed.verbose) {
|
|
1152
|
+
process.stderr.write('[init] liModel=none → skipping CoreML cascade fetch\n');
|
|
1153
|
+
}
|
|
1154
|
+
if (!parsed.skipCoremlCascade && !skipCascadeForLiNone && profile !== 'core') {
|
|
794
1155
|
cascadeReport = getCoremlCascadeReport();
|
|
795
1156
|
|
|
796
1157
|
if (cascadeReport.applicable && cascadeReport.status !== 'present') {
|
|
@@ -818,6 +1179,11 @@ export async function runInit(args) {
|
|
|
818
1179
|
const fetchResult = await fetchCoremlCascade({
|
|
819
1180
|
force: parsed.force,
|
|
820
1181
|
allowDownload: true, // init-time bypass — see downloadModelsForProfile comment
|
|
1182
|
+
// Phase 4: pass the user's persisted choice as the cascade variant
|
|
1183
|
+
// selector. Without this the cascade would default to whatever
|
|
1184
|
+
// SWEET_SEARCH_LATE_INTERACTION_MODEL says, which may diverge
|
|
1185
|
+
// from what the user just chose interactively or via --li-model.
|
|
1186
|
+
liVariantKey: liChoices.liModel,
|
|
821
1187
|
});
|
|
822
1188
|
if (fetchResult.status === 'fetched' || fetchResult.status === 'cached' || fetchResult.status === 'partial') {
|
|
823
1189
|
const total = fetchResult.fetched + fetchResult.cached;
|
|
@@ -884,6 +1250,7 @@ export async function runInit(args) {
|
|
|
884
1250
|
capability,
|
|
885
1251
|
cascadeReport,
|
|
886
1252
|
dedupReport,
|
|
1253
|
+
liChoices,
|
|
887
1254
|
});
|
|
888
1255
|
writeInitConfig(dataDir, preVerifyConfig);
|
|
889
1256
|
|
|
@@ -905,6 +1272,7 @@ export async function runInit(args) {
|
|
|
905
1272
|
capability,
|
|
906
1273
|
cascadeReport,
|
|
907
1274
|
dedupReport,
|
|
1275
|
+
liChoices,
|
|
908
1276
|
});
|
|
909
1277
|
writeInitConfig(dataDir, finalConfig);
|
|
910
1278
|
|
|
@@ -979,6 +1347,7 @@ export async function runInit(args) {
|
|
|
979
1347
|
dedupReport,
|
|
980
1348
|
prewarmHookReport,
|
|
981
1349
|
skillReport,
|
|
1350
|
+
liChoices,
|
|
982
1351
|
});
|
|
983
1352
|
}
|
|
984
1353
|
|
|
@@ -1031,7 +1400,7 @@ function runCoremlCascadeBuild(options = {}) {
|
|
|
1031
1400
|
function buildConfig({
|
|
1032
1401
|
profile, maxsimTier, routerType, nativeStatus, modelResults,
|
|
1033
1402
|
allowRuntimeModelDownload, verification, failed,
|
|
1034
|
-
capability, cascadeReport, dedupReport,
|
|
1403
|
+
capability, cascadeReport, dedupReport, liChoices,
|
|
1035
1404
|
}) {
|
|
1036
1405
|
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
1037
1406
|
|
|
@@ -1083,6 +1452,18 @@ function buildConfig({
|
|
|
1083
1452
|
smokeTest: dedupReport.smokeTest ?? null,
|
|
1084
1453
|
};
|
|
1085
1454
|
}
|
|
1455
|
+
if (liChoices) {
|
|
1456
|
+
// Phase 4: persisted LI policy. Read at runtime by SweetSearch via
|
|
1457
|
+
// `core/infrastructure/init-config.js::readPersistedLiPolicy`. The
|
|
1458
|
+
// `source` block is diagnostic only — it tells us which input wins
|
|
1459
|
+
// on the next re-run.
|
|
1460
|
+
runtime.li = {
|
|
1461
|
+
model: liChoices.liModel,
|
|
1462
|
+
searchReranking: liChoices.searchReranking,
|
|
1463
|
+
source: liChoices.source ?? null,
|
|
1464
|
+
recommendation: liChoices.recommendation ?? null,
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1086
1467
|
|
|
1087
1468
|
return {
|
|
1088
1469
|
version: 1,
|