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/uninstall.js
CHANGED
|
@@ -132,15 +132,23 @@ function getModelCacheDirs(initConfig) {
|
|
|
132
132
|
* return an empty array — uninstall doesn't print a "removing 0 B"
|
|
133
133
|
* line.
|
|
134
134
|
*/
|
|
135
|
-
function getCoremlCascadeRemovals() {
|
|
135
|
+
export function getCoremlCascadeRemovals() {
|
|
136
136
|
const removals = [];
|
|
137
137
|
try {
|
|
138
138
|
const root = getCoremlCascadeRoot();
|
|
139
139
|
if (existsSync(root)) {
|
|
140
140
|
const state = getCoremlCascadeState();
|
|
141
|
+
// Sum across all advertised families — embed + standard LI + LI-edge.
|
|
142
|
+
// The earlier label only counted embed + standard LI (12 on the
|
|
143
|
+
// shipping spec) which contradicted init's "18 variants ready"
|
|
144
|
+
// (6 embed + 6 LI + 6 LI-edge). `liEdgeTotal` is 0 on hosts whose
|
|
145
|
+
// spec doesn't advertise the edge family, so older specs still
|
|
146
|
+
// collapse to the prior 12-count behaviour without ceremony.
|
|
147
|
+
const totalAll = state.embedTotal + state.liTotal + state.liEdgeTotal;
|
|
148
|
+
const presentAll = state.embedPresent + state.liPresent + state.liEdgePresent;
|
|
141
149
|
const label = state.complete
|
|
142
|
-
? `coreml cascade (${
|
|
143
|
-
: `coreml cascade (${
|
|
150
|
+
? `coreml cascade (${totalAll} variants complete)`
|
|
151
|
+
: `coreml cascade (${presentAll}/${totalAll} variants partial)`;
|
|
144
152
|
removals.push({ label, path: root, size: dirSize(root), type: 'coreml-cascade' });
|
|
145
153
|
}
|
|
146
154
|
} catch {
|
|
@@ -213,6 +221,70 @@ export function stopRunningDaemon({
|
|
|
213
221
|
return result;
|
|
214
222
|
}
|
|
215
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Remove the index-maintainer daemon hook init copied into
|
|
226
|
+
* `.claude/hooks/index-maintainer.mjs`. Only removes the file when it
|
|
227
|
+
* matches the bytes init shipped — never deletes a user-modified file
|
|
228
|
+
* we don't own. The marker is the source path: init does
|
|
229
|
+
* `copyFileSync(<pkg>/core/indexing/index-maintainer.mjs, dest)`, so
|
|
230
|
+
* we compare destination bytes to the package source.
|
|
231
|
+
*
|
|
232
|
+
* Returns `{ status, detail }`:
|
|
233
|
+
* not-found — file absent (nothing to do)
|
|
234
|
+
* removed — file removed (matched shipped bytes)
|
|
235
|
+
* skipped — file present but contents differ (user-modified) — left intact
|
|
236
|
+
* dry-run — found the file but skipped the delete
|
|
237
|
+
* error — rm or read failed; uninstall continues
|
|
238
|
+
*/
|
|
239
|
+
export function removeIndexMaintainerHook(projectRoot, { dryRun = false } = {}) {
|
|
240
|
+
const hookPath = join(projectRoot, '.claude', 'hooks', 'index-maintainer.mjs');
|
|
241
|
+
if (!existsSync(hookPath)) {
|
|
242
|
+
return { status: 'not-found', detail: 'no .claude/hooks/index-maintainer.mjs' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Only remove when the bytes match the version init shipped — refuses to
|
|
246
|
+
// delete a hook the user has customized. Failing the byte compare is a
|
|
247
|
+
// soft skip, not an error.
|
|
248
|
+
const shippedPath = join(PACKAGE_ROOT, 'core', 'indexing', 'index-maintainer.mjs');
|
|
249
|
+
let bytesMatch = false;
|
|
250
|
+
try {
|
|
251
|
+
if (existsSync(shippedPath)) {
|
|
252
|
+
const a = readFileSync(hookPath);
|
|
253
|
+
const b = readFileSync(shippedPath);
|
|
254
|
+
bytesMatch = a.length === b.length && a.equals(b);
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// Read errored on either side — treat as "don't remove, surface the
|
|
258
|
+
// file path so the user can clean up manually if they want to".
|
|
259
|
+
return { status: 'skipped', detail: `cannot compare bytes (${hookPath})` };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!bytesMatch) {
|
|
263
|
+
return {
|
|
264
|
+
status: 'skipped',
|
|
265
|
+
detail: `${hookPath} differs from shipped version — leaving in place (delete manually if intended)`,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (dryRun) {
|
|
270
|
+
return { status: 'dry-run', detail: hookPath };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
unlinkSync(hookPath);
|
|
275
|
+
// Best-effort: prune the parent .claude/hooks/ if it's now empty (we
|
|
276
|
+
// own the file, not the directory; only delete if WE made it empty).
|
|
277
|
+
try {
|
|
278
|
+
const parent = dirname(hookPath);
|
|
279
|
+
const entries = readdirSync(parent);
|
|
280
|
+
if (entries.length === 0) rmdirSync(parent);
|
|
281
|
+
} catch { /* ignore — sibling files exist or rmdir failed */ }
|
|
282
|
+
return { status: 'removed', detail: hookPath };
|
|
283
|
+
} catch (err) {
|
|
284
|
+
return { status: 'error', detail: err.message };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
216
288
|
/**
|
|
217
289
|
* Remove the sweet-search /sweet-index skill from `.claude/skills/sweet-index/`.
|
|
218
290
|
* Only removes the directory we created — leaves `.claude/skills/` and `.claude/`
|
|
@@ -342,6 +414,39 @@ export function removePrewarmSessionStartHook(projectRoot, { dryRun = false } =
|
|
|
342
414
|
return { status: 'removed', detail: `spliced out ${sessionStart.length - filtered.length} entry` };
|
|
343
415
|
}
|
|
344
416
|
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Optional native package list (derived from package.json)
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Return the list of `@sweet-search/native-*` packages declared as
|
|
423
|
+
* `optionalDependencies` in package.json. `--purge` walks this list so
|
|
424
|
+
* additions (e.g. CUDA variants) are picked up automatically without
|
|
425
|
+
* having to keep two hand-maintained lists in sync.
|
|
426
|
+
*
|
|
427
|
+
* Falls back to a hard-coded list if package.json is unreadable, so a
|
|
428
|
+
* partial install still gets best-effort purge coverage.
|
|
429
|
+
*/
|
|
430
|
+
export function getOptionalNativePackageNames() {
|
|
431
|
+
try {
|
|
432
|
+
const pkgPath = join(PACKAGE_ROOT, 'package.json');
|
|
433
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
434
|
+
const deps = pkg.optionalDependencies || {};
|
|
435
|
+
const out = Object.keys(deps).filter((n) => n.startsWith('@sweet-search/'));
|
|
436
|
+
if (out.length > 0) return out;
|
|
437
|
+
} catch { /* fall through to baseline */ }
|
|
438
|
+
// Baseline keeps the prior behaviour PLUS the CUDA variants that were
|
|
439
|
+
// missing from the pre-Phase-7 hand-maintained list.
|
|
440
|
+
return [
|
|
441
|
+
'@sweet-search/native-darwin-arm64',
|
|
442
|
+
'@sweet-search/native-darwin-x64',
|
|
443
|
+
'@sweet-search/native-linux-arm64-gnu',
|
|
444
|
+
'@sweet-search/native-linux-arm64-gnu-cuda',
|
|
445
|
+
'@sweet-search/native-linux-x64-gnu',
|
|
446
|
+
'@sweet-search/native-linux-x64-gnu-cuda',
|
|
447
|
+
];
|
|
448
|
+
}
|
|
449
|
+
|
|
345
450
|
// ---------------------------------------------------------------------------
|
|
346
451
|
// Help text
|
|
347
452
|
// ---------------------------------------------------------------------------
|
|
@@ -369,6 +474,8 @@ What gets removed:
|
|
|
369
474
|
artifacts AND the sibling .mlmodelc compiled cache files next to
|
|
370
475
|
each variant. Skipped by --keep-models.
|
|
371
476
|
- .claude/skills/sweet-index/ (the per-project /sweet-index skill copy)
|
|
477
|
+
- .claude/hooks/index-maintainer.mjs (init-installed). User-modified
|
|
478
|
+
copies are detected via a byte-compare and left in place.
|
|
372
479
|
- daemon-prewarm SessionStart entry inside .claude/settings.json
|
|
373
480
|
|
|
374
481
|
What is NOT removed:
|
|
@@ -436,8 +543,15 @@ export async function runUninstall(args) {
|
|
|
436
543
|
const skillPreview = removeSweetIndexSkill(projectRoot, { dryRun: true });
|
|
437
544
|
const hasSkillEntry = skillPreview.status === 'dry-run';
|
|
438
545
|
|
|
546
|
+
// Check for the index-maintainer daemon hook init copies into
|
|
547
|
+
// `.claude/hooks/index-maintainer.mjs`. Same dry-run pattern.
|
|
548
|
+
const indexMaintainerPreview = removeIndexMaintainerHook(projectRoot, { dryRun: true });
|
|
549
|
+
const hasIndexMaintainerHook = indexMaintainerPreview.status === 'dry-run';
|
|
550
|
+
const indexMaintainerSkippedReason =
|
|
551
|
+
indexMaintainerPreview.status === 'skipped' ? indexMaintainerPreview.detail : null;
|
|
552
|
+
|
|
439
553
|
// Nothing to remove?
|
|
440
|
-
if (removals.length === 0 && !hasHookEntry && !hasSkillEntry) {
|
|
554
|
+
if (removals.length === 0 && !hasHookEntry && !hasSkillEntry && !hasIndexMaintainerHook) {
|
|
441
555
|
console.log('Nothing to remove — Sweet Search is not initialized in this project.');
|
|
442
556
|
return;
|
|
443
557
|
}
|
|
@@ -457,6 +571,11 @@ export async function runUninstall(args) {
|
|
|
457
571
|
if (hasSkillEntry) {
|
|
458
572
|
console.log(` /sweet-index skill (.claude/skills/sweet-index/)`);
|
|
459
573
|
}
|
|
574
|
+
if (hasIndexMaintainerHook) {
|
|
575
|
+
console.log(` index-maintainer hook (.claude/hooks/index-maintainer.mjs)`);
|
|
576
|
+
} else if (indexMaintainerSkippedReason) {
|
|
577
|
+
console.log(` [skipped] ${indexMaintainerSkippedReason}`);
|
|
578
|
+
}
|
|
460
579
|
console.log(` Total: ${formatBytes(totalBytes)}`);
|
|
461
580
|
if (parsed.keepModels) {
|
|
462
581
|
console.log(' Model cache: kept (--keep-models)');
|
|
@@ -472,6 +591,12 @@ export async function runUninstall(args) {
|
|
|
472
591
|
if (drySkill.status === 'dry-run') {
|
|
473
592
|
console.log(` Would also remove: /sweet-index skill (${drySkill.detail})`);
|
|
474
593
|
}
|
|
594
|
+
const dryMaintainer = removeIndexMaintainerHook(projectRoot, { dryRun: true });
|
|
595
|
+
if (dryMaintainer.status === 'dry-run') {
|
|
596
|
+
console.log(` Would also remove: index-maintainer hook (${dryMaintainer.detail})`);
|
|
597
|
+
} else if (dryMaintainer.status === 'skipped') {
|
|
598
|
+
console.log(` Would skip: index-maintainer hook — ${dryMaintainer.detail}`);
|
|
599
|
+
}
|
|
475
600
|
console.log('Dry run — nothing was removed.');
|
|
476
601
|
return;
|
|
477
602
|
}
|
|
@@ -540,6 +665,21 @@ export async function runUninstall(args) {
|
|
|
540
665
|
}
|
|
541
666
|
// 'not-found' and 'dry-run' are silent in the main output.
|
|
542
667
|
|
|
668
|
+
// Reverse the index-maintainer daemon hook init copied into
|
|
669
|
+
// .claude/hooks/index-maintainer.mjs. Bytes-match check inside the
|
|
670
|
+
// helper guarantees we never delete a user-customised file.
|
|
671
|
+
const indexMaintainerResult = removeIndexMaintainerHook(projectRoot, { dryRun: parsed.dryRun });
|
|
672
|
+
if (indexMaintainerResult.status === 'removed') {
|
|
673
|
+
console.log(` Removed: index-maintainer hook (${indexMaintainerResult.detail})`);
|
|
674
|
+
removed++;
|
|
675
|
+
} else if (indexMaintainerResult.status === 'skipped') {
|
|
676
|
+
console.log(` Kept: index-maintainer hook — ${indexMaintainerResult.detail}`);
|
|
677
|
+
kept++;
|
|
678
|
+
} else if (indexMaintainerResult.status === 'error') {
|
|
679
|
+
console.log(` Failed to remove index-maintainer hook: ${indexMaintainerResult.detail}`);
|
|
680
|
+
kept++;
|
|
681
|
+
}
|
|
682
|
+
|
|
543
683
|
// Stop any daemon that an earlier SessionStart hook spawned. Otherwise the
|
|
544
684
|
// old daemon keeps running and holding the socket after uninstall, which
|
|
545
685
|
// surprises users. Never throws — `stopRunningDaemon` swallows every error.
|
|
@@ -556,11 +696,17 @@ export async function runUninstall(args) {
|
|
|
556
696
|
console.log('');
|
|
557
697
|
console.log(' Purging npm packages...');
|
|
558
698
|
try {
|
|
559
|
-
|
|
699
|
+
const pkgs = ['sweet-search', ...getOptionalNativePackageNames()];
|
|
700
|
+
// Use shell-form so non-installed packages don't abort the whole
|
|
701
|
+
// command (npm exits non-zero per missing pkg). The OR-true keeps
|
|
702
|
+
// the script alive across npm exit codes from a partially-installed
|
|
703
|
+
// host (e.g. a Linux box without the darwin-* packages).
|
|
704
|
+
const cmd = `npm uninstall ${pkgs.join(' ')} 2>/dev/null || true`;
|
|
705
|
+
execSync(cmd, {
|
|
560
706
|
cwd: projectRoot,
|
|
561
707
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
562
708
|
});
|
|
563
|
-
console.log(
|
|
709
|
+
console.log(` npm packages removed (${pkgs.length} candidates).`);
|
|
564
710
|
} catch {
|
|
565
711
|
console.log(' npm uninstall failed (packages may not be installed).');
|
|
566
712
|
}
|