grepmax 0.26.5 → 0.26.6
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +6 -2
- package/dist/commands/doctor.js +20 -5
- package/dist/commands/mcp.js +60 -15
- package/dist/commands/search-run.js +4 -3
- package/dist/commands/similar.js +2 -2
- package/dist/eval-graph-recovery-probe.js +2 -2
- package/dist/eval-graph-sanity.js +2 -2
- package/dist/lib/analysis/surprising-connections.js +2 -2
- package/dist/lib/daemon/daemon.js +1 -0
- package/dist/lib/index/batch-processor.js +49 -22
- package/dist/lib/index/chunker.js +616 -602
- package/dist/lib/index/syncer.js +1 -1
- package/dist/lib/index/watcher.js +14 -3
- package/dist/lib/search/searcher.js +2 -2
- package/dist/lib/skeleton/skeletonizer.js +9 -1
- package/dist/lib/store/ann-config.js +34 -0
- package/dist/lib/store/vector-db.js +184 -33
- package/dist/lib/workers/orchestrator.js +5 -35
- package/dist/lib/workers/pool.js +15 -32
- package/package.json +3 -8
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "grepmax",
|
|
11
11
|
"source": "./plugins/grepmax",
|
|
12
12
|
"description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
|
|
13
|
-
"version": "0.26.
|
|
13
|
+
"version": "0.26.6",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ Plugins auto-update when you run `npm install -g grepmax@latest` — no need to
|
|
|
124
124
|
|
|
125
125
|
### MCP Server
|
|
126
126
|
|
|
127
|
-
`gmax mcp` starts a stdio-based MCP server for clients that support MCP but can't run shell commands (Cursor, Windsurf, custom agents).
|
|
127
|
+
`gmax mcp` starts a stdio-based MCP server for clients that support MCP but can't run shell commands (Cursor, Windsurf, custom agents). Search-backed tools use the singleton daemon when available, avoiding a separate embedding worker per MCP session, and fall back to an in-process worker only when the daemon is unavailable or does not support the request.
|
|
128
128
|
|
|
129
129
|
| Tool | Description |
|
|
130
130
|
| --- | --- |
|
|
@@ -215,7 +215,7 @@ gmax watch stop # Stop daemon
|
|
|
215
215
|
gmax status # See all projects + watcher status
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, or `gmax
|
|
218
|
+
The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, `gmax summarize`, or `gmax mcp`. It shuts down after 30 minutes of inactivity. File-change batches are processed concurrently while reserving worker capacity for searches; LanceDB compaction runs after writes rather than on every maintenance tick.
|
|
219
219
|
|
|
220
220
|
## Local LLM (optional)
|
|
221
221
|
|
|
@@ -374,9 +374,11 @@ fixtures/
|
|
|
374
374
|
| --- | --- | --- |
|
|
375
375
|
| `GMAX_EMBED_MODE` | Force `cpu` or `gpu` | Auto-detect |
|
|
376
376
|
| `GMAX_WORKER_THREADS` | Worker threads for embedding | 50% of cores |
|
|
377
|
+
| `GMAX_WORKER_RSS_RECYCLE_MB` | Recycle workers that remain above this RSS; `0` disables the check | `1536` |
|
|
377
378
|
| `GMAX_DEBUG` | Debug logging | Off |
|
|
378
379
|
| `GMAX_SUMMARIZER` | Enable summarizer auto-start (`1`) | Off |
|
|
379
380
|
| `GMAX_RERANK` | Enable ColBERT rerank (`1`) — off by default since v0.17.1 ([why](docs/known-limitations.md)) | Off |
|
|
381
|
+
| `GMAX_ANN` | Experimental IVF_FLAT vector index (`1`); leave off unless validating recall on your corpus | Off |
|
|
380
382
|
|
|
381
383
|
## Troubleshooting
|
|
382
384
|
|
|
@@ -389,6 +391,8 @@ gmax index --reset # Full reindex from scratch
|
|
|
389
391
|
gmax watch stop && gmax watch --daemon -b # Restart daemon
|
|
390
392
|
```
|
|
391
393
|
|
|
394
|
+
`gmax doctor` reports ANN index state. `ANN: vector index not built` is normal with the default exact-search configuration.
|
|
395
|
+
|
|
392
396
|
## Contributing
|
|
393
397
|
|
|
394
398
|
See [CLAUDE.md](CLAUDE.md) for development setup, commands, and architecture details.
|
package/dist/commands/doctor.js
CHANGED
|
@@ -88,7 +88,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
88
88
|
.option("--fix", "Auto-fix detected issues (compact, prune, remove stale locks)", false)
|
|
89
89
|
.option("--agent", "Compact output for AI agents", false)
|
|
90
90
|
.action((opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
91
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
92
92
|
if (!opts.agent)
|
|
93
93
|
console.log("gmax Doctor\n");
|
|
94
94
|
const root = config_1.PATHS.globalRoot;
|
|
@@ -228,6 +228,11 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
228
228
|
const logicalSize = tableStats.totalBytes;
|
|
229
229
|
const { numFragments, numSmallFragments } = tableStats.fragmentStats;
|
|
230
230
|
const versions = yield table.listVersions();
|
|
231
|
+
const indices = yield table.listIndices();
|
|
232
|
+
const vectorIndex = indices.find((index) => index.name === "vector_idx" || index.columns.includes("vector"));
|
|
233
|
+
const vectorIndexStats = vectorIndex
|
|
234
|
+
? yield table.indexStats(vectorIndex.name).catch(() => undefined)
|
|
235
|
+
: undefined;
|
|
231
236
|
// Lock status
|
|
232
237
|
const lockPath = path.join(config_1.PATHS.globalRoot, "LOCK");
|
|
233
238
|
let lockStatus = "none";
|
|
@@ -273,7 +278,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
273
278
|
? "LOW"
|
|
274
279
|
: "ok";
|
|
275
280
|
}
|
|
276
|
-
catch (
|
|
281
|
+
catch (_o) { }
|
|
277
282
|
const staleChunkerProjects = projects.filter((p) => {
|
|
278
283
|
var _a;
|
|
279
284
|
return p.status === "indexed" &&
|
|
@@ -301,6 +306,10 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
301
306
|
`legacy_embedding=${legacyEmbeddingCount}`,
|
|
302
307
|
`schema_dim=${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "none"}`,
|
|
303
308
|
`schema_dim_ok=${schemaGap ? "false" : "true"}`,
|
|
309
|
+
`ann_index=${vectorIndex ? "present" : "absent"}`,
|
|
310
|
+
`ann_indexed=${(_f = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numIndexedRows) !== null && _f !== void 0 ? _f : 0}`,
|
|
311
|
+
`ann_unindexed=${(_g = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numUnindexedRows) !== null && _g !== void 0 ? _g : 0}`,
|
|
312
|
+
`ann_metric=${(_h = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.distanceType) !== null && _h !== void 0 ? _h : "none"}`,
|
|
304
313
|
];
|
|
305
314
|
console.log(fields.join("\t"));
|
|
306
315
|
if (schemaGap) {
|
|
@@ -354,6 +363,12 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
354
363
|
else if (physicalDim) {
|
|
355
364
|
console.log(`ok Schema: vector table is ${physicalDim}d`);
|
|
356
365
|
}
|
|
366
|
+
if (vectorIndexStats) {
|
|
367
|
+
console.log(`ok ANN: ${vectorIndexStats.numIndexedRows.toLocaleString()} indexed, ${vectorIndexStats.numUnindexedRows.toLocaleString()} unindexed, metric=${(_j = vectorIndexStats.distanceType) !== null && _j !== void 0 ? _j : "unknown"}`);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
console.log("INFO ANN: vector index not built (set GMAX_ANN=1)");
|
|
371
|
+
}
|
|
357
372
|
// Disk space
|
|
358
373
|
if (diskLevel !== "ok") {
|
|
359
374
|
console.log(`WARN Disk: ${formatSize(availBytes)} available (${diskLevel})`);
|
|
@@ -468,7 +483,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
468
483
|
yield mc.close();
|
|
469
484
|
}
|
|
470
485
|
}
|
|
471
|
-
catch (
|
|
486
|
+
catch (_p) { }
|
|
472
487
|
}
|
|
473
488
|
}
|
|
474
489
|
// --fix auto-remediation
|
|
@@ -515,7 +530,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
515
530
|
const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
|
|
516
531
|
if (done.ok &&
|
|
517
532
|
(!("degraded" in done) || done.degraded !== true)) {
|
|
518
|
-
const chunks = (
|
|
533
|
+
const chunks = (_k = done.indexed) !== null && _k !== void 0 ? _k : 0;
|
|
519
534
|
if (opts.agent) {
|
|
520
535
|
console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
|
|
521
536
|
}
|
|
@@ -527,7 +542,7 @@ exports.doctor = new commander_1.Command("doctor")
|
|
|
527
542
|
else if (!opts.agent) {
|
|
528
543
|
const degraded = "degraded" in done && done.degraded === true;
|
|
529
544
|
const reason = degraded
|
|
530
|
-
? `${(
|
|
545
|
+
? `${(_l = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _l !== void 0 ? _l : 0} scan errors, ${(_m = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _m !== void 0 ? _m : 0} file failures`
|
|
531
546
|
: done.error;
|
|
532
547
|
console.log(`FAIL ${name}: reindex failed (${reason})`);
|
|
533
548
|
}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -53,6 +53,7 @@ exports.mcp = void 0;
|
|
|
53
53
|
exports.toStringArray = toStringArray;
|
|
54
54
|
exports.ok = ok;
|
|
55
55
|
exports.err = err;
|
|
56
|
+
exports.shouldFallbackMcpDaemonSearch = shouldFallbackMcpDaemonSearch;
|
|
56
57
|
exports.isExplicitCrossProjectSearch = isExplicitCrossProjectSearch;
|
|
57
58
|
exports.resolveMcpProject = resolveMcpProject;
|
|
58
59
|
exports.containMcpTarget = containMcpTarget;
|
|
@@ -84,6 +85,7 @@ const annotator_1 = require("../lib/skeleton/annotator");
|
|
|
84
85
|
const retriever_1 = require("../lib/skeleton/retriever");
|
|
85
86
|
const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
|
|
86
87
|
const symbol_extractor_1 = require("../lib/skeleton/symbol-extractor");
|
|
88
|
+
const ann_config_1 = require("../lib/store/ann-config");
|
|
87
89
|
const meta_cache_1 = require("../lib/store/meta-cache");
|
|
88
90
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
89
91
|
const cross_project_1 = require("../lib/utils/cross-project");
|
|
@@ -121,6 +123,15 @@ function ok(text) {
|
|
|
121
123
|
function err(text) {
|
|
122
124
|
return { content: [{ type: "text", text }], isError: true };
|
|
123
125
|
}
|
|
126
|
+
function shouldFallbackMcpDaemonSearch(error) {
|
|
127
|
+
const message = String(error !== null && error !== void 0 ? error : "").toLowerCase();
|
|
128
|
+
return (message === "enoent" ||
|
|
129
|
+
message === "econnrefused" ||
|
|
130
|
+
message.includes("project not watched") ||
|
|
131
|
+
message.includes("daemon not ready") ||
|
|
132
|
+
message.includes("oversize") ||
|
|
133
|
+
message.includes("unknown command"));
|
|
134
|
+
}
|
|
124
135
|
function isExplicitCrossProjectSearch(args, isSearchAll = false) {
|
|
125
136
|
return (isSearchAll ||
|
|
126
137
|
args.scope === "all" ||
|
|
@@ -339,6 +350,29 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
339
350
|
_searcher = new searcher_1.Searcher(getVectorDb());
|
|
340
351
|
return _searcher;
|
|
341
352
|
}
|
|
353
|
+
function daemonSearch(args) {
|
|
354
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
355
|
+
var _a;
|
|
356
|
+
const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
357
|
+
const response = yield sendDaemonCommand(Object.assign({ cmd: ((_a = args.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) ? "search-v2" : "search" }, args), { timeoutMs: 60000 });
|
|
358
|
+
if (!response.ok) {
|
|
359
|
+
if (shouldFallbackMcpDaemonSearch(response.error))
|
|
360
|
+
return null;
|
|
361
|
+
const detail = [response.error, response.hint]
|
|
362
|
+
.filter((value) => typeof value === "string" && value)
|
|
363
|
+
.join(": ");
|
|
364
|
+
throw new Error(detail || "daemon search failed");
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
data: Array.isArray(response.data)
|
|
368
|
+
? response.data
|
|
369
|
+
: [],
|
|
370
|
+
warnings: Array.isArray(response.warnings)
|
|
371
|
+
? response.warnings.filter((warning) => typeof warning === "string")
|
|
372
|
+
: undefined,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
}
|
|
342
376
|
function getSkeletonizer() {
|
|
343
377
|
return __awaiter(this, void 0, void 0, function* () {
|
|
344
378
|
if (!_skeletonizer) {
|
|
@@ -394,7 +428,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
394
428
|
// --- Tool handlers ---
|
|
395
429
|
function handleSemanticSearch(args_1) {
|
|
396
430
|
return __awaiter(this, arguments, void 0, function* (args, isSearchAll = false) {
|
|
397
|
-
var _a, _b;
|
|
431
|
+
var _a, _b, _c;
|
|
398
432
|
const query = String(args.query || "");
|
|
399
433
|
if (!query)
|
|
400
434
|
return err("Missing required parameter: query");
|
|
@@ -482,7 +516,6 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
482
516
|
? new Set((_a = filters.projectRoots) !== null && _a !== void 0 ? _a : [])
|
|
483
517
|
: new Set([resolvedRoot]);
|
|
484
518
|
(0, embedding_status_1.assertEmbeddingSearchCompatible)((0, project_registry_1.listProjects)().filter((project) => selectedRoots.has(project.root)), (0, index_config_1.readGlobalConfig)());
|
|
485
|
-
const searcher = getSearcher();
|
|
486
519
|
// Aider-style seeding: the agent passes its open files / discussed
|
|
487
520
|
// symbols; the searcher biases candidate generation toward them.
|
|
488
521
|
const parseSeedList = (v) => {
|
|
@@ -500,9 +533,15 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
500
533
|
const seeds = seedFiles || seedSymbols
|
|
501
534
|
? { files: seedFiles, symbols: seedSymbols }
|
|
502
535
|
: undefined;
|
|
503
|
-
const
|
|
536
|
+
const effectiveFilters = Object.keys(filters).length > 0 ? filters : undefined;
|
|
537
|
+
const searchOptions = {
|
|
538
|
+
rerank: process.env.GMAX_RERANK === "1",
|
|
539
|
+
seeds,
|
|
540
|
+
};
|
|
541
|
+
const result = (_b = (yield daemonSearch(Object.assign({ projectRoot: resolvedRoot, query,
|
|
542
|
+
limit, filters: effectiveFilters, pathPrefix }, searchOptions)))) !== null && _b !== void 0 ? _b : (yield getSearcher().search(query, limit, searchOptions, effectiveFilters, pathPrefix));
|
|
504
543
|
const allowedRoots = searchAll
|
|
505
|
-
? ((
|
|
544
|
+
? ((_c = filters.projectRoots) !== null && _c !== void 0 ? _c : [])
|
|
506
545
|
: [resolvedRoot];
|
|
507
546
|
result.data = result.data.filter((chunk) => {
|
|
508
547
|
const candidate = chunkAbsPath(chunk);
|
|
@@ -645,7 +684,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
645
684
|
const regex = new RegExp(namePattern, "i");
|
|
646
685
|
results = results.filter((r) => r.symbols.some((s) => regex.test(s)));
|
|
647
686
|
}
|
|
648
|
-
catch (
|
|
687
|
+
catch (_d) {
|
|
649
688
|
// Invalid regex — skip filter
|
|
650
689
|
}
|
|
651
690
|
}
|
|
@@ -688,7 +727,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
688
727
|
output += `\n${traceLines.join("\n")}`;
|
|
689
728
|
}
|
|
690
729
|
}
|
|
691
|
-
catch (
|
|
730
|
+
catch (_e) {
|
|
692
731
|
// Trace failed — return search results without trace
|
|
693
732
|
}
|
|
694
733
|
}
|
|
@@ -1891,6 +1930,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1891
1930
|
// --- New command handlers (Phase 4) ---
|
|
1892
1931
|
function handleDiffChanges(args) {
|
|
1893
1932
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1933
|
+
var _a;
|
|
1894
1934
|
ensureWatcher();
|
|
1895
1935
|
const { getChangedFiles } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/git")));
|
|
1896
1936
|
const ref = typeof args.ref === "string" ? args.ref : undefined;
|
|
@@ -1910,8 +1950,11 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1910
1950
|
}
|
|
1911
1951
|
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
1912
1952
|
if (query) {
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1953
|
+
const searchOptions = {
|
|
1954
|
+
rerank: process.env.GMAX_RERANK === "1",
|
|
1955
|
+
};
|
|
1956
|
+
const response = (_a = (yield daemonSearch(Object.assign({ projectRoot: root, query,
|
|
1957
|
+
limit, filters: {}, pathPrefix: root }, searchOptions)))) !== null && _a !== void 0 ? _a : (yield getSearcher().search(query, limit, searchOptions, {}, root));
|
|
1915
1958
|
const changedSet = new Set(changedFiles);
|
|
1916
1959
|
// searcher.search() returns mapped chunks (path under metadata.path);
|
|
1917
1960
|
// changedFiles are absolute, so match on the resolved absolute path.
|
|
@@ -2100,8 +2143,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2100
2143
|
const source = sourceRows[0];
|
|
2101
2144
|
if (!source.vector || source.vector.length === 0)
|
|
2102
2145
|
return ok("Source chunk has no embedding.");
|
|
2103
|
-
const results = yield table
|
|
2104
|
-
.vectorSearch(source.vector)
|
|
2146
|
+
const results = yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(source.vector))
|
|
2105
2147
|
.select([
|
|
2106
2148
|
"path",
|
|
2107
2149
|
"start_line",
|
|
@@ -2132,7 +2174,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2132
2174
|
}
|
|
2133
2175
|
function handleBuildContext(args) {
|
|
2134
2176
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2135
|
-
var _a, _b;
|
|
2177
|
+
var _a, _b, _c;
|
|
2136
2178
|
ensureWatcher();
|
|
2137
2179
|
const topic = String(args.topic || "");
|
|
2138
2180
|
if (!topic)
|
|
@@ -2144,8 +2186,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2144
2186
|
if (!proj)
|
|
2145
2187
|
return err("Project not added to gmax yet.");
|
|
2146
2188
|
(0, embedding_status_1.assertEmbeddingSearchCompatible)([proj], (0, index_config_1.readGlobalConfig)());
|
|
2147
|
-
const
|
|
2148
|
-
const response = yield
|
|
2189
|
+
const searchOptions = { rerank: process.env.GMAX_RERANK === "1" };
|
|
2190
|
+
const response = (_a = (yield daemonSearch(Object.assign({ projectRoot: root, query: topic, limit, filters: {}, pathPrefix: root }, searchOptions)))) !== null && _a !== void 0 ? _a : (yield getSearcher().search(topic, limit, searchOptions, {}, root));
|
|
2149
2191
|
if (response.data.length === 0)
|
|
2150
2192
|
return ok(`No results found for "${topic}".`);
|
|
2151
2193
|
const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
@@ -2167,7 +2209,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2167
2209
|
const absP = searchResultPath(r);
|
|
2168
2210
|
const startLine = searchResultStartLine(r);
|
|
2169
2211
|
const endLine = searchResultEndLine(r, startLine);
|
|
2170
|
-
const sym = (
|
|
2212
|
+
const sym = (_c = (_b = toStringArray(r.defined_symbols)) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : "";
|
|
2171
2213
|
try {
|
|
2172
2214
|
const sourcePath = (0, path_containment_1.resolveContainedFile)(root, absP);
|
|
2173
2215
|
const content = fs.readFileSync(sourcePath, "utf-8");
|
|
@@ -2181,7 +2223,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2181
2223
|
sections.push(blob);
|
|
2182
2224
|
tokensUsed += estTokens(blob);
|
|
2183
2225
|
}
|
|
2184
|
-
catch (
|
|
2226
|
+
catch (_d) {
|
|
2185
2227
|
/* skip unreadable */
|
|
2186
2228
|
}
|
|
2187
2229
|
}
|
|
@@ -2769,6 +2811,9 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
2769
2811
|
},
|
|
2770
2812
|
}, handleReviewRisk);
|
|
2771
2813
|
yield server.connect(transport);
|
|
2814
|
+
// Start the singleton daemon in the background without delaying MCP startup.
|
|
2815
|
+
void Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client"))).then(({ ensureDaemonRunning }) => ensureDaemonRunning())
|
|
2816
|
+
.catch((e) => console.error("[MCP] Daemon startup failed:", e));
|
|
2772
2817
|
// Kick off index readiness check and watcher in background
|
|
2773
2818
|
ensureIndexReady().catch((e) => console.error("[MCP] Index readiness check failed:", e));
|
|
2774
2819
|
ensureWatcher();
|
|
@@ -70,7 +70,7 @@ function shouldFallbackFromDaemonError(error) {
|
|
|
70
70
|
*/
|
|
71
71
|
function runSearch(params) {
|
|
72
72
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
var _a, _b;
|
|
73
|
+
var _a, _b, _c;
|
|
74
74
|
const { pattern, options, projectRoot, effectiveRoot, paths, projectStatus, searchFilters, pathFilter, seeds, } = params;
|
|
75
75
|
// Tracks a DB opened by the in-process path so it can be (a) returned to the
|
|
76
76
|
// caller for closing after render, or (b) closed here if indexing throws.
|
|
@@ -201,6 +201,7 @@ function runSearch(params) {
|
|
|
201
201
|
return { kind: "dry-run", vectorDb: openedDb };
|
|
202
202
|
}
|
|
203
203
|
yield vectorDb.createFTSIndex();
|
|
204
|
+
yield ((_b = vectorDb.createVectorIndex) === null || _b === void 0 ? void 0 : _b.call(vectorDb));
|
|
204
205
|
if (result.degraded) {
|
|
205
206
|
spinner.warn(`Indexing incomplete (${result.processed}/${result.total}) • ${result.scanErrors} scan errors • ${result.failedFiles} failed. Results may be partial.`);
|
|
206
207
|
}
|
|
@@ -228,7 +229,7 @@ function runSearch(params) {
|
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
// Ensure a watcher is running for live reindexing
|
|
231
|
-
if (!process.env.VITEST && !((
|
|
232
|
+
if (!process.env.VITEST && !((_c = process.env.NODE_ENV) === null || _c === void 0 ? void 0 : _c.includes("test"))) {
|
|
232
233
|
const { launchWatcher } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/watcher-launcher")));
|
|
233
234
|
const launched = yield launchWatcher(projectRoot);
|
|
234
235
|
if (!launched.ok && launched.reason === "spawn-failed") {
|
|
@@ -260,7 +261,7 @@ function runSearch(params) {
|
|
|
260
261
|
try {
|
|
261
262
|
yield openedDb.close();
|
|
262
263
|
}
|
|
263
|
-
catch (
|
|
264
|
+
catch (_d) { }
|
|
264
265
|
}
|
|
265
266
|
throw e;
|
|
266
267
|
}
|
package/dist/commands/similar.js
CHANGED
|
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.similar = void 0;
|
|
46
46
|
const commander_1 = require("commander");
|
|
47
|
+
const ann_config_1 = require("../lib/store/ann-config");
|
|
47
48
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
48
49
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
49
50
|
const arrow_1 = require("../lib/utils/arrow");
|
|
@@ -122,8 +123,7 @@ exports.similar = new commander_1.Command("similar")
|
|
|
122
123
|
exclude: opts.exclude,
|
|
123
124
|
});
|
|
124
125
|
const pathScope = buildScopeWhere(scope);
|
|
125
|
-
const results = yield table
|
|
126
|
-
.vectorSearch(sourceVector)
|
|
126
|
+
const results = yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(sourceVector))
|
|
127
127
|
.select([
|
|
128
128
|
"path",
|
|
129
129
|
"start_line",
|
|
@@ -58,6 +58,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
58
58
|
(_a = (_c = process.env).GMAX_WORKER_COUNT) !== null && _a !== void 0 ? _a : (_c.GMAX_WORKER_COUNT = "1");
|
|
59
59
|
const path = __importStar(require("node:path"));
|
|
60
60
|
const config_1 = require("./config");
|
|
61
|
+
const ann_config_1 = require("./lib/store/ann-config");
|
|
61
62
|
const vector_db_1 = require("./lib/store/vector-db");
|
|
62
63
|
const exit_1 = require("./lib/utils/exit");
|
|
63
64
|
const filter_builder_1 = require("./lib/utils/filter-builder");
|
|
@@ -114,8 +115,7 @@ function probe(table, sym, expectedFile) {
|
|
|
114
115
|
"defined_symbols",
|
|
115
116
|
"referenced_symbols",
|
|
116
117
|
];
|
|
117
|
-
const vectorRows = (yield table
|
|
118
|
-
.vectorSearch(dense)
|
|
118
|
+
const vectorRows = (yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(dense))
|
|
119
119
|
.select([...columns, "_distance"])
|
|
120
120
|
.where(where)
|
|
121
121
|
.limit(PRE_K)
|
|
@@ -75,6 +75,7 @@ const path = __importStar(require("node:path"));
|
|
|
75
75
|
const config_1 = require("./config");
|
|
76
76
|
const callsites_1 = require("./lib/graph/callsites");
|
|
77
77
|
const graph_builder_1 = require("./lib/graph/graph-builder");
|
|
78
|
+
const ann_config_1 = require("./lib/store/ann-config");
|
|
78
79
|
const vector_db_1 = require("./lib/store/vector-db");
|
|
79
80
|
const exit_1 = require("./lib/utils/exit");
|
|
80
81
|
const filter_builder_1 = require("./lib/utils/filter-builder");
|
|
@@ -128,8 +129,7 @@ function probe(target) {
|
|
|
128
129
|
"defined_symbols",
|
|
129
130
|
"referenced_symbols",
|
|
130
131
|
];
|
|
131
|
-
const vectorRows = (yield table
|
|
132
|
-
.vectorSearch(dense)
|
|
132
|
+
const vectorRows = (yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(dense))
|
|
133
133
|
.select([...columns, "_distance"])
|
|
134
134
|
.where(where)
|
|
135
135
|
.limit(PRE_K)
|
|
@@ -60,6 +60,7 @@ exports.buildFindings = buildFindings;
|
|
|
60
60
|
exports.analyzeSurprisingConnections = analyzeSurprisingConnections;
|
|
61
61
|
const path = __importStar(require("node:path"));
|
|
62
62
|
const callsites_1 = require("../graph/callsites");
|
|
63
|
+
const ann_config_1 = require("../store/ann-config");
|
|
63
64
|
const arrow_1 = require("../utils/arrow");
|
|
64
65
|
const scope_filter_1 = require("../utils/scope-filter");
|
|
65
66
|
exports.DEFAULT_SURPRISE_OPTIONS = {
|
|
@@ -520,8 +521,7 @@ function analyzeSurprisingConnections(table_1, projectRoot_1) {
|
|
|
520
521
|
};
|
|
521
522
|
const pairs = new Map();
|
|
522
523
|
for (const source of anchors) {
|
|
523
|
-
const neighbors = (yield table
|
|
524
|
-
.vectorSearch(source.vector)
|
|
524
|
+
const neighbors = (yield (0, ann_config_1.configureAnnVectorQuery)(table.vectorSearch(source.vector))
|
|
525
525
|
.select([...exports.SURPRISE_COLUMNS, "_distance"])
|
|
526
526
|
.where(where)
|
|
527
527
|
.limit(opts.neighbors + 8)
|
|
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.ProjectBatchProcessor = void 0;
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
|
+
const config_1 = require("../../config");
|
|
47
48
|
const vector_db_1 = require("../store/vector-db");
|
|
48
49
|
const cache_check_1 = require("../utils/cache-check");
|
|
49
50
|
const file_utils_1 = require("../utils/file-utils");
|
|
@@ -55,9 +56,12 @@ const watcher_batch_1 = require("./watcher-batch");
|
|
|
55
56
|
const DEBOUNCE_MS = 2000;
|
|
56
57
|
const MAX_RETRIES = 5;
|
|
57
58
|
const MAX_BATCH_SIZE = 50;
|
|
59
|
+
// Pool priority cannot preempt an active processFile, so keep one worker free
|
|
60
|
+
// for latency-sensitive encodeQuery/rerank work during indexing bursts.
|
|
61
|
+
const DEFAULT_BATCH_CONCURRENCY = Math.max(1, config_1.CONFIG.WORKER_THREADS - 1);
|
|
58
62
|
class ProjectBatchProcessor {
|
|
59
63
|
constructor(opts) {
|
|
60
|
-
var _a, _b;
|
|
64
|
+
var _a, _b, _c;
|
|
61
65
|
this.pending = new Map();
|
|
62
66
|
this.retryCount = new Map();
|
|
63
67
|
this.retryAt = new Map();
|
|
@@ -86,6 +90,7 @@ class ProjectBatchProcessor {
|
|
|
86
90
|
this.filePolicy =
|
|
87
91
|
(_b = opts.filePolicy) !== null && _b !== void 0 ? _b : new file_policy_1.ProjectFilePolicy(opts.projectRoot);
|
|
88
92
|
this.wtag = `watch:${path.basename(opts.projectRoot)}`;
|
|
93
|
+
this.concurrency = Math.max(1, Math.floor((_c = opts.concurrency) !== null && _c !== void 0 ? _c : DEFAULT_BATCH_CONCURRENCY));
|
|
89
94
|
const taskTimeoutMs = (() => {
|
|
90
95
|
var _a;
|
|
91
96
|
const fromEnv = Number.parseInt((_a = process.env.GMAX_WORKER_TASK_TIMEOUT_MS) !== null && _a !== void 0 ? _a : "", 10);
|
|
@@ -238,7 +243,7 @@ class ProjectBatchProcessor {
|
|
|
238
243
|
(0, logger_1.log)(this.wtag, `Processing ${batch.size} changed files: ${filenames.join(", ")}`);
|
|
239
244
|
const start = Date.now();
|
|
240
245
|
let reindexed = 0;
|
|
241
|
-
let
|
|
246
|
+
let settled = 0;
|
|
242
247
|
let backoffOverrideMs = 0;
|
|
243
248
|
try {
|
|
244
249
|
// No lock needed — daemon is the single writer to LanceDB/MetaCache
|
|
@@ -268,14 +273,8 @@ class ProjectBatchProcessor {
|
|
|
268
273
|
}
|
|
269
274
|
this.pending.set(absPath, event);
|
|
270
275
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
break;
|
|
274
|
-
processed++;
|
|
275
|
-
if (batch.size > 10 &&
|
|
276
|
-
(processed % 10 === 0 || processed === batch.size)) {
|
|
277
|
-
(0, logger_1.log)(this.wtag, `Progress: ${processed}/${batch.size} (${reindexed} reindexed)`);
|
|
278
|
-
}
|
|
276
|
+
let stopDispatch = false;
|
|
277
|
+
const processOne = (absPath, event) => __awaiter(this, void 0, void 0, function* () {
|
|
279
278
|
// Reclassify every event at apply time. A catchup-derived unlink may be
|
|
280
279
|
// stale if the file was recreated after its directory was scanned.
|
|
281
280
|
try {
|
|
@@ -283,7 +282,7 @@ class ProjectBatchProcessor {
|
|
|
283
282
|
if (classification.status === "error") {
|
|
284
283
|
console.error(`[${this.wtag}] Policy could not classify ${absPath}:`, classification.error);
|
|
285
284
|
retryFailures.add(absPath);
|
|
286
|
-
|
|
285
|
+
return;
|
|
287
286
|
}
|
|
288
287
|
if (classification.status === "excluded" ||
|
|
289
288
|
classification.status === "missing") {
|
|
@@ -293,11 +292,11 @@ class ProjectBatchProcessor {
|
|
|
293
292
|
reindexed++;
|
|
294
293
|
}
|
|
295
294
|
completed.add(absPath);
|
|
296
|
-
|
|
295
|
+
return;
|
|
297
296
|
}
|
|
298
297
|
const stats = classification.stat;
|
|
299
298
|
if (diskPressure === "critical") {
|
|
300
|
-
|
|
299
|
+
return;
|
|
301
300
|
}
|
|
302
301
|
const cached = this.metaCache.get(absPath);
|
|
303
302
|
const forceReprocess = batchForceGenerations.has(absPath);
|
|
@@ -305,7 +304,7 @@ class ProjectBatchProcessor {
|
|
|
305
304
|
(0, cache_coherence_1.isMetaEntryCacheCurrent)(cached, absPath) &&
|
|
306
305
|
(0, cache_check_1.isFileCached)(cached, stats)) {
|
|
307
306
|
completed.add(absPath);
|
|
308
|
-
|
|
307
|
+
return;
|
|
309
308
|
}
|
|
310
309
|
// Fast path: if only mtime changed but size matches and we have a hash,
|
|
311
310
|
// verify in-process instead of dispatching to a worker (~220ms saved).
|
|
@@ -321,7 +320,7 @@ class ProjectBatchProcessor {
|
|
|
321
320
|
if (hash === cached.hash) {
|
|
322
321
|
metaUpdates.set(absPath, Object.assign(Object.assign({}, cached), { mtimeMs: snapshot.mtimeMs }));
|
|
323
322
|
completed.add(absPath);
|
|
324
|
-
|
|
323
|
+
return;
|
|
325
324
|
}
|
|
326
325
|
}
|
|
327
326
|
const result = yield pool.processFile({
|
|
@@ -334,19 +333,19 @@ class ProjectBatchProcessor {
|
|
|
334
333
|
const current = yield this.filePolicy.classifyFile(absPath);
|
|
335
334
|
if (current.status === "error") {
|
|
336
335
|
retryFailures.add(absPath);
|
|
337
|
-
|
|
336
|
+
return;
|
|
338
337
|
}
|
|
339
338
|
if (current.status === "excluded" || current.status === "missing") {
|
|
340
339
|
deletes.push(absPath);
|
|
341
340
|
metaDeletes.push(absPath);
|
|
342
341
|
reindexed++;
|
|
343
342
|
completed.add(absPath);
|
|
344
|
-
|
|
343
|
+
return;
|
|
345
344
|
}
|
|
346
345
|
if (current.stat.mtimeMs !== result.mtimeMs ||
|
|
347
346
|
current.stat.size !== result.size) {
|
|
348
347
|
retryFailures.add(absPath);
|
|
349
|
-
|
|
348
|
+
return;
|
|
350
349
|
}
|
|
351
350
|
const metaEntry = {
|
|
352
351
|
hash: result.hash,
|
|
@@ -361,14 +360,14 @@ class ProjectBatchProcessor {
|
|
|
361
360
|
cached.hasVectors === result.vectors.length > 0) {
|
|
362
361
|
metaUpdates.set(absPath, metaEntry);
|
|
363
362
|
completed.add(absPath);
|
|
364
|
-
|
|
363
|
+
return;
|
|
365
364
|
}
|
|
366
365
|
if (result.shouldDelete) {
|
|
367
366
|
deletes.push(absPath);
|
|
368
367
|
metaUpdates.set(absPath, metaEntry);
|
|
369
368
|
reindexed++;
|
|
370
369
|
completed.add(absPath);
|
|
371
|
-
|
|
370
|
+
return;
|
|
372
371
|
}
|
|
373
372
|
deletes.push(absPath);
|
|
374
373
|
if (result.vectors.length > 0) {
|
|
@@ -380,7 +379,7 @@ class ProjectBatchProcessor {
|
|
|
380
379
|
}
|
|
381
380
|
catch (err) {
|
|
382
381
|
if (batchAc.signal.aborted)
|
|
383
|
-
|
|
382
|
+
return;
|
|
384
383
|
const code = err === null || err === void 0 ? void 0 : err.code;
|
|
385
384
|
if (code === "ENOENT") {
|
|
386
385
|
deletes.push(absPath);
|
|
@@ -392,12 +391,40 @@ class ProjectBatchProcessor {
|
|
|
392
391
|
console.error(`[${this.wtag}] Failed to process ${absPath}:`, err);
|
|
393
392
|
if (!pool.isHealthy()) {
|
|
394
393
|
console.error(`[${this.wtag}] Worker pool unhealthy, aborting batch`);
|
|
395
|
-
|
|
394
|
+
stopDispatch = true;
|
|
395
|
+
return;
|
|
396
396
|
}
|
|
397
397
|
retryFailures.add(absPath);
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
|
+
finally {
|
|
401
|
+
settled++;
|
|
402
|
+
if (batch.size > 10 &&
|
|
403
|
+
(settled % 10 === 0 || settled === batch.size)) {
|
|
404
|
+
(0, logger_1.log)(this.wtag, `Progress: ${settled}/${batch.size} (${reindexed} reindexed)`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
const activeTasks = [];
|
|
409
|
+
const schedule = (task) => __awaiter(this, void 0, void 0, function* () {
|
|
410
|
+
const taskPromise = task();
|
|
411
|
+
activeTasks.push(taskPromise);
|
|
412
|
+
const removeActiveTask = () => {
|
|
413
|
+
const index = activeTasks.indexOf(taskPromise);
|
|
414
|
+
if (index !== -1)
|
|
415
|
+
activeTasks.splice(index, 1);
|
|
416
|
+
};
|
|
417
|
+
void taskPromise.then(removeActiveTask, removeActiveTask);
|
|
418
|
+
if (activeTasks.length >= this.concurrency) {
|
|
419
|
+
yield Promise.race(activeTasks);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
for (const [absPath, event] of batch) {
|
|
423
|
+
if (batchAc.signal.aborted || stopDispatch)
|
|
424
|
+
break;
|
|
425
|
+
yield schedule(() => processOne(absPath, event));
|
|
400
426
|
}
|
|
427
|
+
yield Promise.allSettled(activeTasks);
|
|
401
428
|
const pureDeleteCandidates = new Set(metaDeletes);
|
|
402
429
|
const authoritativeMetaDeletes = new Set();
|
|
403
430
|
for (const absPath of pureDeleteCandidates) {
|