sweet-search 2.5.2 → 2.5.3
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 +24 -3
- package/core/graph/graph-expansion.js +215 -36
- package/core/graph/graph-extractor.js +196 -11
- package/core/graph/graph-search.js +395 -92
- package/core/graph/hcgs-generator.js +2 -1
- package/core/graph/index.js +2 -0
- package/core/graph/repo-map.js +28 -6
- package/core/graph/structural-answer-cues.js +168 -0
- package/core/graph/structural-callsite-hints.js +40 -0
- package/core/graph/structural-context-format.js +40 -0
- package/core/graph/structural-context.js +450 -0
- package/core/graph/structural-forward-push.js +156 -0
- package/core/graph/structural-header-context.js +19 -0
- package/core/graph/structural-importance.js +148 -0
- package/core/graph/structural-pagerank.js +197 -0
- package/core/graph/summary-manager.js +13 -9
- package/core/incremental-indexing/application/dirty-scan.mjs +236 -0
- package/core/incremental-indexing/application/file-watcher.mjs +197 -0
- package/core/incremental-indexing/application/maintenance-handlers.mjs +519 -0
- package/core/incremental-indexing/application/maintenance-worker.mjs +380 -0
- package/core/incremental-indexing/application/operator-cli.mjs +554 -0
- package/core/incremental-indexing/application/production-li-delta.mjs +192 -0
- package/core/incremental-indexing/application/production-reconciler-helpers.mjs +107 -0
- package/core/incremental-indexing/application/production-reconciler.mjs +583 -0
- package/core/incremental-indexing/application/reconciler.mjs +477 -0
- package/core/incremental-indexing/application/tombstone-injector.mjs +148 -0
- package/core/incremental-indexing/domain/chunk-identity.mjs +260 -0
- package/core/incremental-indexing/domain/encoder-deps.mjs +193 -0
- package/core/incremental-indexing/domain/encoder-input.mjs +225 -0
- package/core/incremental-indexing/domain/interval-autotune.mjs +255 -0
- package/core/incremental-indexing/domain/reconcile-counters.mjs +149 -0
- package/core/incremental-indexing/domain/watermark-scheduler.mjs +239 -0
- package/core/incremental-indexing/infrastructure/artifact-temp-sweep.mjs +163 -0
- package/core/incremental-indexing/infrastructure/baseline-readiness.mjs +121 -0
- package/core/incremental-indexing/infrastructure/dirty-set.mjs +233 -0
- package/core/incremental-indexing/infrastructure/graph-gc.mjs +314 -0
- package/core/incremental-indexing/infrastructure/hashing.mjs +298 -0
- package/core/incremental-indexing/infrastructure/hcgs-invalidation.mjs +182 -0
- package/core/incremental-indexing/infrastructure/li-segment-merge.mjs +278 -0
- package/core/incremental-indexing/infrastructure/li-segment-state.mjs +173 -0
- package/core/incremental-indexing/infrastructure/lockfile.mjs +119 -0
- package/core/incremental-indexing/infrastructure/maintenance-state-reader.mjs +283 -0
- package/core/incremental-indexing/infrastructure/manifest.mjs +194 -0
- package/core/incremental-indexing/infrastructure/path-filter.mjs +190 -0
- package/core/incremental-indexing/infrastructure/reader-heartbeat.mjs +201 -0
- package/core/incremental-indexing/infrastructure/schema-migrations.mjs +257 -0
- package/core/incremental-indexing/infrastructure/sparse-gram-delta.mjs +335 -0
- package/core/incremental-indexing/infrastructure/sqlite-fts5.mjs +176 -0
- package/core/incremental-indexing/infrastructure/staleness-display.mjs +105 -0
- package/core/incremental-indexing/infrastructure/tombstone-bitmap.mjs +234 -0
- package/core/incremental-indexing/infrastructure/vector-delta-writer.mjs +359 -0
- package/core/incremental-indexing/infrastructure/vector-gc.mjs +133 -0
- package/core/incremental-indexing/infrastructure/worktree-stamp.mjs +155 -0
- package/core/incremental-indexing/infrastructure/wsl2-detect.mjs +115 -0
- package/core/indexing/admission-policy.js +139 -0
- package/core/indexing/artifact-builder.js +29 -12
- package/core/indexing/ast-chunker.js +107 -30
- package/core/indexing/dedup/exemplar-selector.js +19 -1
- package/core/indexing/gitignore-filter.js +223 -0
- package/core/indexing/incremental-tracker.js +99 -30
- package/core/indexing/index-codebase-v21.js +6 -5
- package/core/indexing/index-maintainer.mjs +698 -6
- package/core/indexing/indexer-ann.js +99 -15
- package/core/indexing/indexer-build.js +158 -45
- package/core/indexing/indexer-empty-baseline.js +80 -0
- package/core/indexing/indexer-manifest.js +66 -0
- package/core/indexing/indexer-phases.js +56 -23
- package/core/indexing/indexer-sparse-gram.js +54 -13
- package/core/indexing/indexer-utils.js +26 -208
- package/core/indexing/indexing-file-policy.js +32 -7
- package/core/indexing/maintainer-launcher.mjs +137 -0
- package/core/indexing/merkle-tracker.js +251 -244
- package/core/indexing/model-pool.js +46 -5
- package/core/infrastructure/code-graph-repository.js +758 -6
- package/core/infrastructure/code-graph-visibility.js +157 -0
- package/core/infrastructure/codebase-repository.js +100 -13
- package/core/infrastructure/config/search.js +1 -1
- package/core/infrastructure/db-utils.js +118 -0
- package/core/infrastructure/dedup-hashing.js +10 -13
- package/core/infrastructure/hardware-capability.js +17 -7
- package/core/infrastructure/index.js +8 -2
- package/core/infrastructure/language-patterns/maps.js +4 -1
- package/core/infrastructure/language-patterns/registry-core.js +56 -17
- package/core/infrastructure/language-patterns/registry-object-oriented.js +12 -5
- package/core/infrastructure/language-patterns.js +69 -0
- package/core/infrastructure/model-registry.js +20 -0
- package/core/infrastructure/native-inference.js +7 -12
- package/core/infrastructure/native-resolver.js +52 -37
- package/core/infrastructure/native-sparse-gram.js +261 -20
- package/core/infrastructure/native-tokenizer.js +6 -15
- package/core/infrastructure/simd-distance.js +10 -16
- package/core/infrastructure/sparse-gram-delta-reader.js +76 -0
- package/core/infrastructure/structural-alias-resolver.js +122 -0
- package/core/infrastructure/structural-candidate-ranker.js +34 -0
- package/core/infrastructure/structural-context-repository.js +472 -0
- package/core/infrastructure/structural-context-utils.js +51 -0
- package/core/infrastructure/structural-graph-signals.js +121 -0
- package/core/infrastructure/structural-qualified-resolution.js +15 -0
- package/core/infrastructure/structural-source-definitions.js +100 -0
- package/core/infrastructure/tombstone-bitmap-reader.js +139 -0
- package/core/infrastructure/tree-sitter-provider.js +811 -37
- package/core/prompt-optimization/data/p7-final/sweet-search-system-prompt.md +50 -0
- package/core/query/query-router.js +55 -5
- package/core/ranking/file-kind-ranking.js +2192 -15
- package/core/ranking/late-interaction-index.js +87 -12
- package/core/search/cli-decoration.js +290 -0
- package/core/search/context-expander.js +988 -78
- package/core/search/index.js +1 -0
- package/core/search/output-policy.js +275 -0
- package/core/search/search-anchor.js +499 -0
- package/core/search/search-boost.js +93 -1
- package/core/search/search-cli.js +61 -204
- package/core/search/search-hybrid.js +250 -10
- package/core/search/search-pattern-chunks.js +57 -8
- package/core/search/search-pattern-planner.js +68 -9
- package/core/search/search-pattern-prefilter.js +30 -10
- package/core/search/search-pattern-ripgrep.js +40 -4
- package/core/search/search-pattern-sparse-overlay.js +256 -0
- package/core/search/search-pattern.js +117 -29
- package/core/search/search-postprocess.js +479 -5
- package/core/search/search-read-semantic.js +260 -23
- package/core/search/search-read.js +82 -64
- package/core/search/search-reader-pin.js +71 -0
- package/core/search/search-rrf.js +279 -0
- package/core/search/search-semantic.js +110 -5
- package/core/search/search-server.js +130 -57
- package/core/search/search-trace.js +107 -0
- package/core/search/server-identity.js +93 -0
- package/core/search/session-daemon-prewarm.mjs +33 -10
- package/core/search/sweet-search.js +399 -7
- package/core/skills/sweet-index/SKILL.md +8 -6
- package/core/vector-store/binary-hnsw-index.js +194 -30
- package/core/vector-store/float-vector-store.js +96 -6
- package/core/vector-store/hnsw-index.js +220 -49
- package/eval/agent-read-workflows/bin/_ss-helpers.mjs +471 -0
- package/eval/agent-read-workflows/bin/ss-find +15 -0
- package/eval/agent-read-workflows/bin/ss-grep +12 -0
- package/eval/agent-read-workflows/bin/ss-read +14 -0
- package/eval/agent-read-workflows/bin/ss-search +18 -0
- package/eval/agent-read-workflows/bin/ss-semantic +12 -0
- package/eval/agent-read-workflows/bin/ss-trace +11 -0
- package/mcp/read-tool.js +109 -0
- package/mcp/server.js +55 -15
- package/mcp/tool-handlers.js +14 -124
- package/mcp/trace-tool.js +81 -0
- package/package.json +25 -10
- package/scripts/hooks/intercept-read.mjs +55 -0
- package/scripts/hooks/remind-tools.mjs +40 -0
- package/scripts/init.js +698 -54
- package/scripts/inject-agent-instructions.js +431 -0
- package/scripts/install-prompt-reminders.js +188 -0
- package/scripts/install-tool-enforcement.js +220 -0
- package/scripts/smoke-test.js +12 -9
- package/scripts/uninstall.js +276 -18
- package/scripts/write-claude-rules.js +110 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import fs from 'fs/promises';
|
|
13
|
-
import { existsSync } from 'fs';
|
|
13
|
+
import { existsSync, readFileSync } from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { DB_PATHS, PERFORMANCE_TARGETS, LOGGING, BINARY_HNSW_CONFIG, HCGS_CONFIG, LATE_INTERACTION_CONFIG, EMBEDDING_CONFIG, SEISMIC_CONFIG, CASCADE_CONFIG, loadProjectConfig, shouldUseLocalReranker } from '../infrastructure/config/index.js';
|
|
16
16
|
import { getGlobalLocalReranker } from '../ranking/local-reranker.js';
|
|
@@ -44,6 +44,7 @@ import * as hybrid from './search-hybrid.js';
|
|
|
44
44
|
import * as postprocess from './search-postprocess.js';
|
|
45
45
|
import * as pattern from './search-pattern.js';
|
|
46
46
|
import { packageForAgent } from './context-expander.js';
|
|
47
|
+
import { beginPinnedRead, endPinnedRead } from './search-reader-pin.js';
|
|
47
48
|
|
|
48
49
|
export { ROUTE_ALPHAS } from './search-fusion.js';
|
|
49
50
|
|
|
@@ -67,6 +68,18 @@ const STRUCTURAL_PATTERNS = {
|
|
|
67
68
|
* @param {string} query
|
|
68
69
|
* @returns {{ structuralType: string|null, targetEntity: string|null }}
|
|
69
70
|
*/
|
|
71
|
+
// Per-stage profiling hooks. No-op unless `globalThis.__stageTimings` is set
|
|
72
|
+
// by scripts/profile-search-stages.mjs.
|
|
73
|
+
function __ptStart() {
|
|
74
|
+
return globalThis.__stageTimings ? performance.now() : null;
|
|
75
|
+
}
|
|
76
|
+
function __ptEnd(stage, t0) {
|
|
77
|
+
if (t0 == null || !globalThis.__stageTimings) return;
|
|
78
|
+
const ms = performance.now() - t0;
|
|
79
|
+
const buf = globalThis.__stageTimings;
|
|
80
|
+
(buf[stage] = buf[stage] || []).push(ms);
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
function parseStructuralQuery(query) {
|
|
71
84
|
for (const [type, pattern] of Object.entries(STRUCTURAL_PATTERNS)) {
|
|
72
85
|
const match = query.match(pattern);
|
|
@@ -99,12 +112,25 @@ export class SweetSearch {
|
|
|
99
112
|
// standard model path on every search. Env var still wins; see
|
|
100
113
|
// applyPersistedLiModel for the full precedence ladder.
|
|
101
114
|
this._liModelApply = applyPersistedLiModel(projectRoot);
|
|
115
|
+
const explicitLiModel = options.lateInteractionOptions?.modelId;
|
|
116
|
+
if (typeof explicitLiModel === 'string' && LATE_INTERACTION_CONFIG.models[explicitLiModel]) {
|
|
117
|
+
const before = LATE_INTERACTION_CONFIG.model;
|
|
118
|
+
LATE_INTERACTION_CONFIG.model = explicitLiModel;
|
|
119
|
+
this._liModelApply = {
|
|
120
|
+
applied: explicitLiModel,
|
|
121
|
+
before,
|
|
122
|
+
source: 'option',
|
|
123
|
+
persistedModel: this._liModelApply.persistedModel,
|
|
124
|
+
changed: explicitLiModel !== before,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
102
127
|
const projectConfig = loadProjectConfig(projectRoot);
|
|
103
128
|
const projectCascade = projectConfig.cascade || {};
|
|
104
129
|
const envOrProject = (envKey, cascadeKey, configKey) =>
|
|
105
130
|
process.env[envKey] != null ? CASCADE_CONFIG[configKey] : projectCascade[cascadeKey];
|
|
106
131
|
|
|
107
132
|
this.graphDbPath = options.graphDbPath || DB_PATHS.codeGraph;
|
|
133
|
+
this._manifestGraphDbPath = this.graphDbPath;
|
|
108
134
|
this.graphSearch = new GraphSearch(this.graphDbPath);
|
|
109
135
|
this.codeGraphRepo = new CodeGraphRepository(this.graphDbPath);
|
|
110
136
|
this.hnswPath = options.hnswPath || DB_PATHS.hnswIndex;
|
|
@@ -115,6 +141,8 @@ export class SweetSearch {
|
|
|
115
141
|
this.lateInteractionIndex = new LateInteractionIndex(options.lateInteractionOptions || {});
|
|
116
142
|
this.router = new QueryRouter();
|
|
117
143
|
this.codebaseDbPath = options.codebaseDbPath || DB_PATHS.codebase;
|
|
144
|
+
this._manifestCodebaseDbPath = this.codebaseDbPath;
|
|
145
|
+
this._manifestStateDir = path.dirname(this._manifestCodebaseDbPath);
|
|
118
146
|
this.sparseGramIndexPath = options.sparseGramIndexPath || DB_PATHS.sparseGramIndex;
|
|
119
147
|
this.verbose = options.verbose ?? LOGGING.verbose;
|
|
120
148
|
this.timing = options.timing ?? LOGGING.timing;
|
|
@@ -167,10 +195,23 @@ export class SweetSearch {
|
|
|
167
195
|
?? CASCADE_CONFIG.shadowMode;
|
|
168
196
|
setRepoMapModule({ pageRank, loadGraph, buildAdjacency });
|
|
169
197
|
this._qualityScorer = null;
|
|
170
|
-
this.codebaseRepo = new CodebaseRepository(this.
|
|
198
|
+
this.codebaseRepo = new CodebaseRepository(this._manifestCodebaseDbPath);
|
|
171
199
|
this.sparseGramIndex = null;
|
|
200
|
+
this._sparseGramLoadedPath = null;
|
|
172
201
|
this.grepInitialized = false;
|
|
173
202
|
this.initialized = false;
|
|
203
|
+
this._lateInteractionOptions = { ...(options.lateInteractionOptions || {}) };
|
|
204
|
+
this._artifactManifestEpoch = null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_clearChunkLocationCache() {
|
|
208
|
+
this._chunkLocationMap = null;
|
|
209
|
+
this._chunkLocationMapSize = 0;
|
|
210
|
+
this._chunkLocationMapIndex = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_clearCodebaseChunkTypeCache() {
|
|
214
|
+
this._codebaseChunkTypeMap = null;
|
|
174
215
|
}
|
|
175
216
|
|
|
176
217
|
/** @deprecated Use codebaseRepo methods instead. Bridge for legacy callers. */
|
|
@@ -184,6 +225,8 @@ export class SweetSearch {
|
|
|
184
225
|
if (this.initialized) return;
|
|
185
226
|
const start = Date.now();
|
|
186
227
|
|
|
228
|
+
this._syncManifestPaths(this._readReconcileManifest());
|
|
229
|
+
|
|
187
230
|
this.hasGraphIndex = existsSync(this.graphDbPath);
|
|
188
231
|
this.hasHnswIndex = existsSync(this.hnswPath.replace('.idx', '.meta.json'));
|
|
189
232
|
this.hasBinaryHnswIndex = existsSync(this.binaryHnswPath.replace('.idx', '.meta.json'));
|
|
@@ -309,6 +352,7 @@ export class SweetSearch {
|
|
|
309
352
|
try {
|
|
310
353
|
this.sparseGramIndex = loadSparseGramIndex(this.sparseGramIndexPath);
|
|
311
354
|
if (this.sparseGramIndex) {
|
|
355
|
+
this._sparseGramLoadedPath = this.sparseGramIndexPath;
|
|
312
356
|
const stats = this.sparseGramIndex.getStats();
|
|
313
357
|
this.log(
|
|
314
358
|
`SparseGram: Loaded ${stats.grams} grams across ${stats.totalFiles} files ` +
|
|
@@ -321,11 +365,27 @@ export class SweetSearch {
|
|
|
321
365
|
this.log(`SparseGram: Failed to load: ${err.message}`);
|
|
322
366
|
this.hasSparseGramIndex = false;
|
|
323
367
|
this.sparseGramIndex = null;
|
|
368
|
+
this._sparseGramLoadedPath = null;
|
|
324
369
|
}
|
|
325
370
|
}
|
|
326
371
|
|
|
327
372
|
await warmupEmbedding({ initVocabulary: true, initSemanticCache: true });
|
|
328
373
|
|
|
374
|
+
// Pre-build the call-graph ref-count index so the first search query
|
|
375
|
+
// doesn't pay its 10-50 ms construction cost. This piggybacks on the
|
|
376
|
+
// existing warmup phase — model load already takes ~700 ms, so this
|
|
377
|
+
// adds at most a few ms to init and removes the cold-start spike from
|
|
378
|
+
// the search hot path. Skipped when the graph DB doesn't exist (e.g.
|
|
379
|
+
// grep-only mode).
|
|
380
|
+
if (this.hasGraphIndex && this.codeGraphRepo
|
|
381
|
+
&& typeof this.codeGraphRepo.prebuildRefCountIndex === 'function') {
|
|
382
|
+
try {
|
|
383
|
+
this.codeGraphRepo.prebuildRefCountIndex();
|
|
384
|
+
} catch {
|
|
385
|
+
// Index build is purely an optimisation; failure is non-fatal.
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
329
389
|
if (shouldUseLocalReranker()) {
|
|
330
390
|
try {
|
|
331
391
|
const localReranker = getGlobalLocalReranker();
|
|
@@ -337,19 +397,228 @@ export class SweetSearch {
|
|
|
337
397
|
}
|
|
338
398
|
|
|
339
399
|
this.initialized = true;
|
|
400
|
+
this._artifactManifestEpoch = this._readReconcileManifest()?.epoch ?? null;
|
|
340
401
|
this.log(`SweetSearch: Initialized in ${Date.now() - start}ms`);
|
|
341
402
|
}
|
|
342
403
|
|
|
404
|
+
_readReconcileManifest() {
|
|
405
|
+
try {
|
|
406
|
+
const manifestPath = path.join(this._manifestStateDir, 'reconcile-manifest.json');
|
|
407
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
408
|
+
return Number.isInteger(manifest?.epoch) ? manifest : null;
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
_resolveStatePath(filePath) {
|
|
415
|
+
if (!filePath) return null;
|
|
416
|
+
if (path.isAbsolute(filePath)) return filePath;
|
|
417
|
+
return path.join(this._manifestStateDir, filePath);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
_manifestArtifactPaths(manifest) {
|
|
421
|
+
if (!manifest) return {};
|
|
422
|
+
let liIndexPath = null;
|
|
423
|
+
const liDescriptor = manifest.lateInteraction?.path
|
|
424
|
+
|| manifest.lateInteraction?.indexPath
|
|
425
|
+
|| manifest.lateInteraction?.manifest;
|
|
426
|
+
if (liDescriptor) {
|
|
427
|
+
const resolved = this._resolveStatePath(liDescriptor);
|
|
428
|
+
const segmentDir = path.dirname(resolved);
|
|
429
|
+
liIndexPath = segmentDir.endsWith('.segments')
|
|
430
|
+
? segmentDir.slice(0, -'.segments'.length)
|
|
431
|
+
: resolved;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
codebaseDbPath: this._resolveStatePath(manifest.vectors?.path),
|
|
435
|
+
graphDbPath: this._resolveStatePath(manifest.codeGraph?.path),
|
|
436
|
+
hnswPath: this._resolveStatePath(manifest.hnsw?.path),
|
|
437
|
+
hnswStalePath: this._resolveStatePath(manifest.hnsw?.stale),
|
|
438
|
+
binaryHnswPath: this._resolveStatePath(manifest.binaryHnsw?.path),
|
|
439
|
+
binaryHnswStalePath: this._resolveStatePath(manifest.binaryHnsw?.stale),
|
|
440
|
+
lateInteractionIndexPath: liIndexPath,
|
|
441
|
+
sparseGramIndexPath: this._resolveStatePath(manifest.sparseGram?.base),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
_syncManifestPaths(manifest) {
|
|
446
|
+
const paths = this._manifestArtifactPaths(manifest);
|
|
447
|
+
if (!manifest) {
|
|
448
|
+
this.sparseGramDeltas = null;
|
|
449
|
+
this.sparseGramWeightsId = null;
|
|
450
|
+
}
|
|
451
|
+
if (paths.codebaseDbPath && paths.codebaseDbPath !== this.codebaseDbPath) {
|
|
452
|
+
this.codebaseRepo?.close?.();
|
|
453
|
+
this.codebaseDbPath = paths.codebaseDbPath;
|
|
454
|
+
this.codebaseRepo = new CodebaseRepository(this._manifestCodebaseDbPath);
|
|
455
|
+
this._clearCodebaseChunkTypeCache();
|
|
456
|
+
}
|
|
457
|
+
if (paths.graphDbPath && paths.graphDbPath !== this.graphDbPath) {
|
|
458
|
+
this.graphSearch?.close?.();
|
|
459
|
+
this.codeGraphRepo?.close?.();
|
|
460
|
+
this.graphDbPath = paths.graphDbPath;
|
|
461
|
+
this.graphSearch = new GraphSearch(this._manifestGraphDbPath);
|
|
462
|
+
this.codeGraphRepo = new CodeGraphRepository(this._manifestGraphDbPath);
|
|
463
|
+
}
|
|
464
|
+
if (paths.hnswPath && (paths.hnswPath !== this.hnswPath || paths.hnswStalePath !== this.hnswIndex?.stalePath)) {
|
|
465
|
+
this.hnswPath = paths.hnswPath;
|
|
466
|
+
this.hnswIndex = new HNSWIndex({ indexPath: this.hnswPath, stalePath: paths.hnswStalePath || `${this.hnswPath}.stale.bin` });
|
|
467
|
+
}
|
|
468
|
+
if (paths.binaryHnswPath && (paths.binaryHnswPath !== this.binaryHnswPath || paths.binaryHnswStalePath !== this.binaryHnswIndex?.stalePath)) {
|
|
469
|
+
this.binaryHnswPath = paths.binaryHnswPath;
|
|
470
|
+
this.binaryHnswIndex = new BinaryHNSWIndex({ indexPath: this.binaryHnswPath, stalePath: paths.binaryHnswStalePath || `${this.binaryHnswPath}.stale.bin` });
|
|
471
|
+
}
|
|
472
|
+
if (paths.lateInteractionIndexPath && paths.lateInteractionIndexPath !== this.lateInteractionIndex?.indexPath) {
|
|
473
|
+
this._lateInteractionOptions = { ...this._lateInteractionOptions, indexPath: paths.lateInteractionIndexPath };
|
|
474
|
+
this.lateInteractionIndex = new LateInteractionIndex(this._lateInteractionOptions);
|
|
475
|
+
this._clearChunkLocationCache();
|
|
476
|
+
}
|
|
477
|
+
if (paths.sparseGramIndexPath && paths.sparseGramIndexPath !== this.sparseGramIndexPath) {
|
|
478
|
+
this.sparseGramIndexPath = paths.sparseGramIndexPath;
|
|
479
|
+
this.sparseGramIndex = null;
|
|
480
|
+
this._sparseGramLoadedPath = null;
|
|
481
|
+
}
|
|
482
|
+
if (typeof manifest?.sparseGram?.weightsId === 'string') {
|
|
483
|
+
this.sparseGramWeightsId = manifest.sparseGram.weightsId;
|
|
484
|
+
}
|
|
485
|
+
if (Array.isArray(manifest?.sparseGram?.deltas)) {
|
|
486
|
+
this.sparseGramDeltas = manifest.sparseGram.deltas.filter((entry) => typeof entry === 'string');
|
|
487
|
+
} else if (manifest?.sparseGram) {
|
|
488
|
+
this.sparseGramDeltas = null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async _reloadManifestArtifacts(manifest, options = {}) {
|
|
493
|
+
this._syncManifestPaths(manifest);
|
|
494
|
+
const reloadScope = options.reloadScope || 'all';
|
|
495
|
+
const grepOnly = reloadScope === 'grep';
|
|
496
|
+
|
|
497
|
+
if (!grepOnly) {
|
|
498
|
+
this.hasBinaryHnswIndex = existsSync(this.binaryHnswPath.replace('.idx', '.meta.json'));
|
|
499
|
+
}
|
|
500
|
+
if (!grepOnly && this.hasBinaryHnswIndex && this.use3Stage) {
|
|
501
|
+
try {
|
|
502
|
+
const nextBinary = new BinaryHNSWIndex({
|
|
503
|
+
indexPath: this.binaryHnswPath,
|
|
504
|
+
stalePath: this.binaryHnswIndex?.stalePath || `${this.binaryHnswPath}.stale.bin`,
|
|
505
|
+
});
|
|
506
|
+
await nextBinary.load();
|
|
507
|
+
this.binaryHnswIndex = nextBinary;
|
|
508
|
+
this.floatVectorStore = new FloatVectorStore();
|
|
509
|
+
await this.floatVectorStore.load(getFloatStorePath(this.binaryHnswPath));
|
|
510
|
+
} catch (err) {
|
|
511
|
+
this.log(`BinaryHNSW: Failed to reload after manifest publish: ${err.message}`);
|
|
512
|
+
this.hasBinaryHnswIndex = false;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (!grepOnly) {
|
|
517
|
+
this.hasHnswIndex = existsSync(this.hnswPath.replace('.idx', '.meta.json'));
|
|
518
|
+
}
|
|
519
|
+
if (!grepOnly && this.hasHnswIndex) {
|
|
520
|
+
try {
|
|
521
|
+
const nextHnsw = new HNSWIndex({
|
|
522
|
+
indexPath: this.hnswPath,
|
|
523
|
+
stalePath: this.hnswIndex?.stalePath || `${this.hnswPath}.stale.bin`,
|
|
524
|
+
});
|
|
525
|
+
await nextHnsw.load(undefined, { mmap: true });
|
|
526
|
+
this.hnswIndex = nextHnsw;
|
|
527
|
+
} catch (err) {
|
|
528
|
+
this.log(`HNSW: Failed to reload after manifest publish: ${err.message}`);
|
|
529
|
+
this.hasHnswIndex = false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!grepOnly) {
|
|
534
|
+
this.hasLateInteractionIndex = existsSync(this.lateInteractionIndex.indexPath);
|
|
535
|
+
}
|
|
536
|
+
if (!grepOnly && this.hasLateInteractionIndex) {
|
|
537
|
+
try {
|
|
538
|
+
const nextLi = new LateInteractionIndex(this._lateInteractionOptions);
|
|
539
|
+
await nextLi.init();
|
|
540
|
+
this.lateInteractionIndex = nextLi;
|
|
541
|
+
this._clearChunkLocationCache();
|
|
542
|
+
const liManifest = {
|
|
543
|
+
modelId: this.lateInteractionIndex.modelId ?? null,
|
|
544
|
+
tokenDim: this.lateInteractionIndex.tokenDim ?? null,
|
|
545
|
+
modelMismatch: this.lateInteractionIndex.modelMismatch === true,
|
|
546
|
+
exists: true,
|
|
547
|
+
};
|
|
548
|
+
const resolved = resolveSearchRerankPolicy({
|
|
549
|
+
optionOverride: this._liPolicyOptionOverride,
|
|
550
|
+
env: process.env,
|
|
551
|
+
persisted: this._liPolicyPersisted,
|
|
552
|
+
indexManifest: liManifest,
|
|
553
|
+
activeConfigModel: LATE_INTERACTION_CONFIG.model,
|
|
554
|
+
});
|
|
555
|
+
this._liPolicyResolved = resolved;
|
|
556
|
+
this.useLateInteraction = LATE_INTERACTION_CONFIG.enabled && resolved.effective;
|
|
557
|
+
} catch (err) {
|
|
558
|
+
this.log(`LateInteraction: Failed to reload after manifest publish: ${err.message}`);
|
|
559
|
+
this.hasLateInteractionIndex = false;
|
|
560
|
+
this.useLateInteraction = false;
|
|
561
|
+
this._clearChunkLocationCache();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
this.hasSparseGramIndex = existsSync(this.sparseGramIndexPath);
|
|
566
|
+
if (this.hasSparseGramIndex) {
|
|
567
|
+
try {
|
|
568
|
+
this.sparseGramIndex = loadSparseGramIndex(this.sparseGramIndexPath);
|
|
569
|
+
this._sparseGramLoadedPath = this.sparseGramIndex ? this.sparseGramIndexPath : null;
|
|
570
|
+
} catch (err) {
|
|
571
|
+
this.log(`SparseGram: Failed to reload after manifest publish: ${err.message}`);
|
|
572
|
+
this.hasSparseGramIndex = false;
|
|
573
|
+
this.sparseGramIndex = null;
|
|
574
|
+
this._sparseGramLoadedPath = null;
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
this.sparseGramIndex = null;
|
|
578
|
+
this._sparseGramLoadedPath = null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
async _refreshManifestPins(options = {}) {
|
|
583
|
+
const manifest = this._readReconcileManifest();
|
|
584
|
+
const previousArtifactEpoch = this._artifactManifestEpoch;
|
|
585
|
+
const previousCodebaseEpoch = this.codebaseRepo?.getManifestEpoch?.();
|
|
586
|
+
const manifestEpoch = Number.isInteger(manifest?.epoch) ? manifest.epoch : null;
|
|
587
|
+
const shouldReloadArtifacts = manifestEpoch !== null
|
|
588
|
+
&& previousArtifactEpoch !== manifestEpoch
|
|
589
|
+
&& (this.initialized || this.grepInitialized);
|
|
590
|
+
this._syncManifestPaths(manifest);
|
|
591
|
+
const codebaseEpoch = this.codebaseRepo?.refreshManifestEpoch?.();
|
|
592
|
+
if (previousCodebaseEpoch !== codebaseEpoch) {
|
|
593
|
+
this._clearCodebaseChunkTypeCache();
|
|
594
|
+
}
|
|
595
|
+
const graphEpoch = this.graphSearch?.refreshManifestEpoch?.();
|
|
596
|
+
this.codeGraphRepo?.refreshManifestEpoch?.();
|
|
597
|
+
// Regex/sparse-gram helpers are not repository-backed, so expose the
|
|
598
|
+
// query-pinned epoch on the searcher for their delta overlay reader.
|
|
599
|
+
this.manifestEpoch = manifestEpoch !== null
|
|
600
|
+
? manifestEpoch
|
|
601
|
+
: (Number.isInteger(codebaseEpoch) ? codebaseEpoch : graphEpoch);
|
|
602
|
+
if (shouldReloadArtifacts) {
|
|
603
|
+
await this._reloadManifestArtifacts(manifest, options);
|
|
604
|
+
}
|
|
605
|
+
if (manifestEpoch !== null) {
|
|
606
|
+
this._artifactManifestEpoch = manifestEpoch;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
343
610
|
async initGrepOnly() {
|
|
344
611
|
if (this.grepInitialized || this.initialized) return;
|
|
345
612
|
const start = Date.now();
|
|
346
613
|
|
|
614
|
+
this._syncManifestPaths(this._readReconcileManifest());
|
|
347
615
|
this.hasCodebaseIndex = existsSync(this.codebaseDbPath);
|
|
348
616
|
this.hasSparseGramIndex = existsSync(this.sparseGramIndexPath);
|
|
349
617
|
if (this.hasSparseGramIndex) {
|
|
350
618
|
try {
|
|
351
619
|
this.sparseGramIndex = loadSparseGramIndex(this.sparseGramIndexPath);
|
|
352
620
|
if (this.sparseGramIndex) {
|
|
621
|
+
this._sparseGramLoadedPath = this.sparseGramIndexPath;
|
|
353
622
|
const stats = this.sparseGramIndex.getStats();
|
|
354
623
|
this.log(
|
|
355
624
|
`SparseGram: Loaded ${stats.grams} grams across ${stats.totalFiles} files ` +
|
|
@@ -362,10 +631,12 @@ export class SweetSearch {
|
|
|
362
631
|
this.log(`SparseGram: Failed to load: ${err.message}`);
|
|
363
632
|
this.hasSparseGramIndex = false;
|
|
364
633
|
this.sparseGramIndex = null;
|
|
634
|
+
this._sparseGramLoadedPath = null;
|
|
365
635
|
}
|
|
366
636
|
}
|
|
367
637
|
|
|
368
638
|
this.grepInitialized = true;
|
|
639
|
+
this._artifactManifestEpoch = this._readReconcileManifest()?.epoch ?? null;
|
|
369
640
|
this.log(`SweetSearch: Grep-only initialized in ${Date.now() - start}ms`);
|
|
370
641
|
}
|
|
371
642
|
|
|
@@ -384,7 +655,14 @@ export class SweetSearch {
|
|
|
384
655
|
} else {
|
|
385
656
|
await this.init();
|
|
386
657
|
}
|
|
658
|
+
await this._refreshManifestPins({ reloadScope: mode === 'grep' ? 'grep' : 'all' });
|
|
387
659
|
|
|
660
|
+
const readPin = beginPinnedRead({
|
|
661
|
+
stateDir: this._manifestStateDir,
|
|
662
|
+
epoch: this.manifestEpoch,
|
|
663
|
+
meta: { tool: 'search', mode, query: String(query).slice(0, 200) },
|
|
664
|
+
});
|
|
665
|
+
try {
|
|
388
666
|
const start = Date.now();
|
|
389
667
|
const stats = { query };
|
|
390
668
|
|
|
@@ -435,6 +713,27 @@ export class SweetSearch {
|
|
|
435
713
|
let results;
|
|
436
714
|
let semanticStats = null;
|
|
437
715
|
|
|
716
|
+
// Search-scoped caches for the per-result helpers in
|
|
717
|
+
// applyResultDemotions. Shared across the two demotion sites (one inside
|
|
718
|
+
// hybridSearchV2, one inside _applyPostRetrieval). Most top-K chunks
|
|
719
|
+
// appear in BOTH sites' input sets, so cross-call reuse stacks on top of
|
|
720
|
+
// the intra-call memoization in file-kind-ranking.js. Freshly allocated
|
|
721
|
+
// per search() call — never reused across queries.
|
|
722
|
+
// _entityKindCache : SQLite enclosing/contained entity lookup
|
|
723
|
+
// _entityNameCache : SQLite findEntityWithNameInRange (symbol-target)
|
|
724
|
+
// _resultTextCache : readFileSync source-span
|
|
725
|
+
// _fullFileTextCache : readFileSync FULL file (test-support detection)
|
|
726
|
+
// _isTestSupportCache : isTestSupportFile() per-file verdict
|
|
727
|
+
// _isTestChunkCache : isTestChunk() per-chunk verdict
|
|
728
|
+
// _fileKindCache : detectFileKind() per-file verdict
|
|
729
|
+
const _entityKindCache = new Map();
|
|
730
|
+
const _entityNameCache = new Map();
|
|
731
|
+
const _resultTextCache = new Map();
|
|
732
|
+
const _fullFileTextCache = new Map();
|
|
733
|
+
const _isTestSupportCache = new Map();
|
|
734
|
+
const _isTestChunkCache = new Map();
|
|
735
|
+
const _fileKindCache = new Map();
|
|
736
|
+
|
|
438
737
|
switch (searchMode) {
|
|
439
738
|
case 'grep': {
|
|
440
739
|
const grepResult = await this.bareGrep(query, routing, {
|
|
@@ -478,7 +777,13 @@ export class SweetSearch {
|
|
|
478
777
|
break;
|
|
479
778
|
}
|
|
480
779
|
case 'semantic': {
|
|
481
|
-
const semanticResult = await this.semanticSearch(query, {
|
|
780
|
+
const semanticResult = await this.semanticSearch(query, {
|
|
781
|
+
k,
|
|
782
|
+
rerank,
|
|
783
|
+
useLateInteraction,
|
|
784
|
+
format: options.format,
|
|
785
|
+
ablations: options.ablations,
|
|
786
|
+
});
|
|
482
787
|
results = semanticResult.results;
|
|
483
788
|
semanticStats = semanticResult.stats;
|
|
484
789
|
stats.path = 'semantic';
|
|
@@ -486,13 +791,40 @@ export class SweetSearch {
|
|
|
486
791
|
}
|
|
487
792
|
case 'hybrid':
|
|
488
793
|
default: {
|
|
489
|
-
const hybridResult = await this.hybridSearchV2(query, {
|
|
794
|
+
const hybridResult = await this.hybridSearchV2(query, {
|
|
795
|
+
k,
|
|
796
|
+
useLateInteraction,
|
|
797
|
+
format: options.format,
|
|
798
|
+
routing,
|
|
799
|
+
ablations: options.ablations,
|
|
800
|
+
useMMR: options.useMMR,
|
|
801
|
+
allowQueryRewrite: options.allowQueryRewrite,
|
|
802
|
+
allowKeywordFallback: options.allowKeywordFallback,
|
|
803
|
+
confidenceFloor: options.confidenceFloor,
|
|
804
|
+
fileKindWindow: options.fileKindWindow,
|
|
805
|
+
hybridDocFactor: options.hybridDocFactor,
|
|
806
|
+
hybridTestFactor: options.hybridTestFactor,
|
|
807
|
+
hybridTypeFactor: options.hybridTypeFactor,
|
|
808
|
+
hybridAncillaryFactor: options.hybridAncillaryFactor,
|
|
809
|
+
hybridTinyAncillaryFactor: options.hybridTinyAncillaryFactor,
|
|
810
|
+
resultDemotionWindow: options.resultDemotionWindow,
|
|
811
|
+
_entityKindCache,
|
|
812
|
+
_entityNameCache,
|
|
813
|
+
_resultTextCache,
|
|
814
|
+
_fullFileTextCache,
|
|
815
|
+
_isTestSupportCache,
|
|
816
|
+
_isTestChunkCache,
|
|
817
|
+
_fileKindCache,
|
|
818
|
+
});
|
|
490
819
|
results = hybridResult.results || hybridResult;
|
|
491
820
|
semanticStats = hybridResult.semanticStats || null;
|
|
492
821
|
stats.path = 'hybrid';
|
|
493
822
|
stats.fusion = hybridResult.fusionStats?.method || 'cc';
|
|
494
823
|
stats.fusionFallback = hybridResult.fusionStats?.fallbackReason || null;
|
|
495
824
|
stats.lexicalLatencyMs = hybridResult.fusionStats?.lexicalLatencyMs ?? null;
|
|
825
|
+
if (hybridResult.fusionStats?.queryRewrite) {
|
|
826
|
+
stats.queryRewrite = hybridResult.fusionStats.queryRewrite;
|
|
827
|
+
}
|
|
496
828
|
break;
|
|
497
829
|
}
|
|
498
830
|
}
|
|
@@ -505,9 +837,62 @@ export class SweetSearch {
|
|
|
505
837
|
effectiveGraphExpand = '2hop';
|
|
506
838
|
}
|
|
507
839
|
|
|
840
|
+
// Empty-result rescue (added 2026-05-07 — FreshStack uv diagnosis).
|
|
841
|
+
// The joint hybrid pipeline can return [] in two cascading-failure
|
|
842
|
+
// scenarios:
|
|
843
|
+
// (a) BM25 lexical returns nothing (FTS5 tokenization quirk on multi-
|
|
844
|
+
// word NL queries with stop-words like "trace how X uses Y"), AND
|
|
845
|
+
// (b) the dense path returns candidates that ALL trip post-fusion
|
|
846
|
+
// demotions to ≈0 score, AND
|
|
847
|
+
// (c) RRF fallback inside hybridSearchV2 also produces nothing because
|
|
848
|
+
// its keyword splitter sees the same FTS-empty result.
|
|
849
|
+
// Diagnosed on UV-FLOW-1 / UV-FLOW-4 (post-cutoff uv): both well-formed
|
|
850
|
+
// NL queries with concrete tokens (uv, add, dependency, pyproject, toml)
|
|
851
|
+
// returned in 3ms. That is a query-pipeline pathology, not a corpus gap.
|
|
852
|
+
// The principled fix (cascading retrieval — Thakur et al. BEIR 2024;
|
|
853
|
+
// Lin & Ma "Tiered Retrieval" 2025): when all upstream paths produce
|
|
854
|
+
// empty, fall back to ONE pure-dense call on the raw query string with
|
|
855
|
+
// no rerank, no graph, no fusion — this guarantees we always return
|
|
856
|
+
// something for any NL query the encoder can embed. Disable via
|
|
857
|
+
// `ablations: ['no-empty-rescue']`.
|
|
858
|
+
const emptyRescueAllowed = !(options.ablations && (
|
|
859
|
+
options.ablations instanceof Set
|
|
860
|
+
? options.ablations.has('no-empty-rescue')
|
|
861
|
+
: Array.isArray(options.ablations) && options.ablations.includes('no-empty-rescue')
|
|
862
|
+
));
|
|
863
|
+
if (emptyRescueAllowed
|
|
864
|
+
&& Array.isArray(results)
|
|
865
|
+
&& results.length === 0
|
|
866
|
+
&& (searchMode === 'hybrid' || searchMode === 'semantic' || searchMode === 'lexical')
|
|
867
|
+
&& expand) {
|
|
868
|
+
try {
|
|
869
|
+
const rescue = await this.semanticSearch(query, {
|
|
870
|
+
k: Math.max(k, 10),
|
|
871
|
+
rerank: false,
|
|
872
|
+
useLateInteraction: false,
|
|
873
|
+
});
|
|
874
|
+
const rescuedResults = rescue.results || [];
|
|
875
|
+
if (rescuedResults.length > 0) {
|
|
876
|
+
results = rescuedResults.map(r => ({ ...r, searchPath: 'empty-rescue-dense' }));
|
|
877
|
+
stats.emptyRescue = {
|
|
878
|
+
triggered: true,
|
|
879
|
+
recovered: rescuedResults.length,
|
|
880
|
+
mode: 'pure-dense-raw',
|
|
881
|
+
};
|
|
882
|
+
this.log(`Empty-rescue: hybrid returned 0; pure-dense recovered ${rescuedResults.length} candidates`);
|
|
883
|
+
} else {
|
|
884
|
+
stats.emptyRescue = { triggered: true, recovered: 0 };
|
|
885
|
+
}
|
|
886
|
+
} catch (err) {
|
|
887
|
+
stats.emptyRescue = { triggered: true, error: err.message };
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
508
891
|
// Step 3: Post-retrieval processing (delegated to extracted module)
|
|
509
892
|
const postRetrievalResult = await this._applyPostRetrieval(results, query, options, {
|
|
510
|
-
stats, semanticStats, searchMode, effectiveGraphExpand, intentPolicy, start,
|
|
893
|
+
stats, semanticStats, searchMode, effectiveGraphExpand, intentPolicy, start, fromSearch: true,
|
|
894
|
+
_entityKindCache, _entityNameCache, _resultTextCache, _fullFileTextCache,
|
|
895
|
+
_isTestSupportCache, _isTestChunkCache, _fileKindCache,
|
|
511
896
|
});
|
|
512
897
|
|
|
513
898
|
// Step 4: Agent packaging (lexical/semantic/hybrid/structural).
|
|
@@ -519,6 +904,7 @@ export class SweetSearch {
|
|
|
519
904
|
if (agentFormats.has(options.format)) {
|
|
520
905
|
const finalResults = postRetrievalResult.results || [];
|
|
521
906
|
const finalStats = postRetrievalResult.stats || {};
|
|
907
|
+
const __t_pkg = __ptStart();
|
|
522
908
|
const agentResponse = packageForAgent(finalResults, {
|
|
523
909
|
...finalStats,
|
|
524
910
|
candidatePoolSize: finalStats.results_count ?? finalResults.length,
|
|
@@ -533,12 +919,16 @@ export class SweetSearch {
|
|
|
533
919
|
projectRoot: this.projectRoot,
|
|
534
920
|
ablations: options.ablations,
|
|
535
921
|
});
|
|
922
|
+
__ptEnd('packageForAgent', __t_pkg);
|
|
536
923
|
// Preserve the underlying retrieval stats so callers can inspect both layers
|
|
537
924
|
agentResponse.stats = finalStats;
|
|
538
925
|
return agentResponse;
|
|
539
926
|
}
|
|
540
927
|
|
|
541
928
|
return postRetrievalResult;
|
|
929
|
+
} finally {
|
|
930
|
+
endPinnedRead(readPin);
|
|
931
|
+
}
|
|
542
932
|
}
|
|
543
933
|
|
|
544
934
|
/** Structural search path (GraphRAG structural queries — opt-in via explicit flag) */
|
|
@@ -588,10 +978,11 @@ export class SweetSearch {
|
|
|
588
978
|
/** Semantic search dispatcher. Delegates to 3Stage or Standard based on config. */
|
|
589
979
|
async semanticSearch(query, options = {}) {
|
|
590
980
|
const { k = 10, rerank = true, useLateInteraction = this.useLateInteraction } = options;
|
|
981
|
+
const semanticOptions = { ...options, k, rerank, useLateInteraction };
|
|
591
982
|
if (this.hasBinaryHnswIndex && this.use3Stage) {
|
|
592
|
-
return this.semanticSearch3Stage(query,
|
|
983
|
+
return this.semanticSearch3Stage(query, semanticOptions);
|
|
593
984
|
}
|
|
594
|
-
return this.semanticSearchStandard(query,
|
|
985
|
+
return this.semanticSearchStandard(query, semanticOptions);
|
|
595
986
|
}
|
|
596
987
|
|
|
597
988
|
/** O(N) vector scan fallback (when HNSW not available). Filters stale entities. */
|
|
@@ -694,6 +1085,7 @@ Object.assign(SweetSearch.prototype, {
|
|
|
694
1085
|
variance: fusion.variance,
|
|
695
1086
|
getBoostIntent: boost.getBoostIntent,
|
|
696
1087
|
applyPostFusionBoosts: boost.applyPostFusionBoosts,
|
|
1088
|
+
computeIdentifierAgreementBoost: boost.computeIdentifierAgreementBoost,
|
|
697
1089
|
computeDefinitionBoost: boost.computeDefinitionBoost,
|
|
698
1090
|
computeSyntaxBoost: boost.computeSyntaxBoost,
|
|
699
1091
|
computePositionBoost: boost.computePositionBoost,
|
|
@@ -32,18 +32,20 @@ lifecycle end-to-end:
|
|
|
32
32
|
fighting with the GPU models about to be loaded.
|
|
33
33
|
2. **Detect best backend** via `hardware-capability.js` —
|
|
34
34
|
`coreml-cascade` on M3+ Apple Silicon, `candle-metal` on M1/M2,
|
|
35
|
-
`candle-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
`candle-cuda` on Linux + NVIDIA, and the optimized **ORT INT8 CPU** path
|
|
36
|
+
on any host with no usable accelerator (no GPU models are loaded there).
|
|
37
|
+
3. **Load GPU models + warmup forward pass** (accelerator hosts only) —
|
|
38
|
+
compiles Metal pipelines, CoreML variant bundles, and BLAS thread pools
|
|
39
|
+
so the first indexing batch pays no cold-start cost.
|
|
39
40
|
4. **Index the codebase** — code graph, vector embeddings, HNSW,
|
|
40
41
|
late-interaction index, quantized artifacts, sparse-gram index.
|
|
41
42
|
5. **Kill GPU models** — releases Metal queues and Neural Engine.
|
|
42
43
|
6. **Load + warmup ORT CPU models** — both embedding and LI get one dummy
|
|
43
44
|
forward pass so the first query after indexing is warm.
|
|
44
45
|
|
|
45
|
-
On small-changeset incremental runs (under 20 files)
|
|
46
|
-
|
|
46
|
+
On small-changeset incremental runs (under 20 files) — and on any host with
|
|
47
|
+
no usable accelerator — the indexer skips the GPU swap entirely and indexes on
|
|
48
|
+
the optimized ORT INT8 CPU path.
|
|
47
49
|
|
|
48
50
|
## Usage
|
|
49
51
|
|