grepmax 0.18.1 → 0.20.0
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 +23 -0
- package/README.md +28 -3
- package/dist/commands/add.js +4 -2
- package/dist/commands/claude-code.js +1 -1
- package/dist/commands/codex.js +3 -1
- package/dist/commands/context.js +1 -1
- package/dist/commands/dead.js +2 -6
- package/dist/commands/doctor.js +26 -7
- package/dist/commands/extract.js +3 -5
- package/dist/commands/impact.js +6 -4
- package/dist/commands/index.js +7 -2
- package/dist/commands/log.js +4 -2
- package/dist/commands/mcp.js +594 -722
- package/dist/commands/peek.js +15 -9
- package/dist/commands/plugin.js +36 -22
- package/dist/commands/project.js +16 -6
- package/dist/commands/related.js +8 -9
- package/dist/commands/remove.js +9 -2
- package/dist/commands/review.js +1 -1
- package/dist/commands/setup.js +4 -2
- package/dist/commands/similar.js +4 -4
- package/dist/commands/status.js +6 -4
- package/dist/commands/summarize.js +6 -4
- package/dist/commands/symbols.js +1 -1
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +12 -5
- package/dist/commands/watch.js +33 -9
- package/dist/eval-graph-nav.js +4 -1
- package/dist/eval-graph-recovery-probe.js +56 -14
- package/dist/eval-graph-spotcheck.js +10 -2
- package/dist/eval-graph-totals.js +5 -2
- package/dist/eval-oss.js +212 -37
- package/dist/eval-seed.js +13 -4
- package/dist/index.js +9 -9
- package/dist/lib/daemon/daemon.js +32 -13
- package/dist/lib/daemon/ipc-handler.js +24 -5
- package/dist/lib/daemon/mlx-server-manager.js +11 -3
- package/dist/lib/daemon/process-manager.js +1 -2
- package/dist/lib/daemon/watcher-manager.js +22 -6
- package/dist/lib/graph/callsites.js +151 -25
- package/dist/lib/graph/graph-builder.js +2 -2
- package/dist/lib/graph/impact.js +6 -6
- package/dist/lib/index/batch-processor.js +16 -6
- package/dist/lib/index/chunker.js +2 -0
- package/dist/lib/index/syncer.js +22 -10
- package/dist/lib/index/watcher-batch.js +10 -1
- package/dist/lib/llm/config.js +2 -1
- package/dist/lib/llm/diff.js +56 -11
- package/dist/lib/llm/investigate.js +52 -11
- package/dist/lib/llm/review.js +21 -8
- package/dist/lib/llm/server.js +25 -9
- package/dist/lib/llm/tools.js +1 -1
- package/dist/lib/output/agent-search-formatter.js +25 -3
- package/dist/lib/output/index-state-footer.js +1 -1
- package/dist/lib/review/risk.js +2 -4
- package/dist/lib/search/pagerank.js +1 -1
- package/dist/lib/search/searcher.js +43 -17
- package/dist/lib/search/seed-weight.js +4 -1
- package/dist/lib/skeleton/symbol-extractor.js +2 -1
- package/dist/lib/store/vector-db.js +21 -10
- package/dist/lib/utils/cross-project.js +5 -1
- package/dist/lib/utils/daemon-client.js +39 -1
- package/dist/lib/utils/filter-builder.js +22 -0
- package/dist/lib/utils/git.js +10 -2
- package/dist/lib/utils/project-registry.js +3 -2
- package/dist/lib/utils/scope-filter.js +6 -6
- package/dist/lib/utils/watcher-launcher.js +4 -1
- package/dist/lib/utils/watcher-store.js +2 -1
- package/dist/lib/workers/embeddings/granite.js +4 -1
- package/dist/lib/workers/embeddings/mlx-client.js +7 -2
- package/dist/lib/workers/pool.js +30 -7
- package/dist/lib/workers/process-child.js +1 -1
- package/package.json +23 -19
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/plugins/grepmax/hooks/cwd-changed.js +4 -1
- package/plugins/grepmax/hooks/pre-grep.js +2 -1
- package/plugins/grepmax/hooks/start.js +45 -10
- package/plugins/grepmax/hooks/subagent-start.js +1 -4
|
@@ -105,9 +105,20 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
105
105
|
var _a, _b, _c, _d, _e;
|
|
106
106
|
try {
|
|
107
107
|
(0, logger_1.debug)("daemon", `ipc cmd=${cmd.cmd}${cmd.root ? ` root=${cmd.root}` : ""}`);
|
|
108
|
+
// The socket listens before LanceDB/MetaCache are open so liveness probes
|
|
109
|
+
// succeed during slow startup. Gate resource-dependent commands until those
|
|
110
|
+
// stores exist; ping/shutdown must always work (probes + restart).
|
|
111
|
+
if (cmd.cmd !== "ping" && cmd.cmd !== "shutdown" && !daemon.isReady()) {
|
|
112
|
+
return { ok: false, error: "daemon initializing" };
|
|
113
|
+
}
|
|
108
114
|
switch (cmd.cmd) {
|
|
109
115
|
case "ping":
|
|
110
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
pid: process.pid,
|
|
119
|
+
uptime: daemon.uptime(),
|
|
120
|
+
version: DAEMON_VERSION,
|
|
121
|
+
};
|
|
111
122
|
case "watch": {
|
|
112
123
|
const root = String(cmd.root || "");
|
|
113
124
|
if (!root)
|
|
@@ -135,7 +146,9 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
135
146
|
const fromPid = (_b = cmd.from_pid) !== null && _b !== void 0 ? _b : "?";
|
|
136
147
|
const fromPpid = (_c = cmd.from_ppid) !== null && _c !== void 0 ? _c : "?";
|
|
137
148
|
const fromVer = (_d = cmd.from_version) !== null && _d !== void 0 ? _d : "?";
|
|
138
|
-
const fromArgv = Array.isArray(cmd.from_argv)
|
|
149
|
+
const fromArgv = Array.isArray(cmd.from_argv)
|
|
150
|
+
? cmd.from_argv.join(" ")
|
|
151
|
+
: "?";
|
|
139
152
|
const fromParentCmd = (_e = cmd.from_parent_cmd) !== null && _e !== void 0 ? _e : "?";
|
|
140
153
|
console.log(`[daemon] shutdown command received via IPC: reason=${reason} from_pid=${fromPid} from_ppid=${fromPpid} from_version=${fromVer} from_argv=[${fromArgv}] from_parent_cmd=[${fromParentCmd}]`);
|
|
141
154
|
setImmediate(() => daemon.shutdown());
|
|
@@ -155,12 +168,16 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
155
168
|
conn.on("close", onClose);
|
|
156
169
|
try {
|
|
157
170
|
const limitRaw = typeof cmd.limit === "number" ? cmd.limit : 10;
|
|
158
|
-
const skeletonLimitRaw = typeof cmd.skeletonLimit === "number"
|
|
171
|
+
const skeletonLimitRaw = typeof cmd.skeletonLimit === "number"
|
|
172
|
+
? cmd.skeletonLimit
|
|
173
|
+
: undefined;
|
|
159
174
|
// Accept both the legacy single-string `exclude` and the new
|
|
160
175
|
// `excludePrefixes`/`inPrefixes` arrays (--in/--exclude on the CLI).
|
|
161
176
|
// Daemon may outlive a CLI restart, so keep both wire shapes for
|
|
162
177
|
// one release; drop the single-string form in v0.17.x.
|
|
163
|
-
const filters = cmd.filters &&
|
|
178
|
+
const filters = cmd.filters &&
|
|
179
|
+
typeof cmd.filters === "object" &&
|
|
180
|
+
!Array.isArray(cmd.filters)
|
|
164
181
|
? cmd.filters
|
|
165
182
|
: undefined;
|
|
166
183
|
const resp = yield daemon.search({
|
|
@@ -171,7 +188,9 @@ function handleCommand(daemon, cmd, conn) {
|
|
|
171
188
|
pathPrefix: typeof cmd.pathPrefix === "string" ? cmd.pathPrefix : undefined,
|
|
172
189
|
rerank: cmd.rerank === true,
|
|
173
190
|
explain: cmd.explain === true,
|
|
174
|
-
seeds: cmd.seeds &&
|
|
191
|
+
seeds: cmd.seeds &&
|
|
192
|
+
typeof cmd.seeds === "object" &&
|
|
193
|
+
!Array.isArray(cmd.seeds)
|
|
175
194
|
? cmd.seeds
|
|
176
195
|
: undefined,
|
|
177
196
|
includeSkeletons: cmd.includeSkeletons === true,
|
|
@@ -69,15 +69,23 @@ class MlxServerManager {
|
|
|
69
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
70
70
|
const port = parseInt(process.env.MLX_EMBED_PORT || "8100", 10);
|
|
71
71
|
return new Promise((resolve) => {
|
|
72
|
-
const req = http.get({ hostname: "127.0.0.1", port, path: "/health", timeout: 2000 }, (res) => {
|
|
72
|
+
const req = http.get({ hostname: "127.0.0.1", port, path: "/health", timeout: 2000 }, (res) => {
|
|
73
|
+
res.resume();
|
|
74
|
+
resolve(res.statusCode === 200);
|
|
75
|
+
});
|
|
73
76
|
req.on("error", () => resolve(false));
|
|
74
|
-
req.on("timeout", () => {
|
|
77
|
+
req.on("timeout", () => {
|
|
78
|
+
req.destroy();
|
|
79
|
+
resolve(false);
|
|
80
|
+
});
|
|
75
81
|
});
|
|
76
82
|
});
|
|
77
83
|
}
|
|
78
84
|
getPortPid(port) {
|
|
79
85
|
try {
|
|
80
|
-
const out = (0, node_child_process_1.execSync)(`lsof -ti :${port}`, { timeout: 5000 })
|
|
86
|
+
const out = (0, node_child_process_1.execSync)(`lsof -ti :${port}`, { timeout: 5000 })
|
|
87
|
+
.toString()
|
|
88
|
+
.trim();
|
|
81
89
|
const pid = parseInt(out.split("\n")[0], 10);
|
|
82
90
|
return Number.isFinite(pid) ? pid : null;
|
|
83
91
|
}
|
|
@@ -83,8 +83,7 @@ class ProcessManager {
|
|
|
83
83
|
killStaleProcesses() {
|
|
84
84
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
85
|
// 1. Check for other daemon processes
|
|
86
|
-
const daemonPids = this.findProcessesByTitle("gmax-daemon")
|
|
87
|
-
.filter((pid) => pid !== process.pid);
|
|
86
|
+
const daemonPids = this.findProcessesByTitle("gmax-daemon").filter((pid) => pid !== process.pid);
|
|
88
87
|
const workerPids = this.findProcessesByTitle("gmax-worker");
|
|
89
88
|
if (daemonPids.length === 0 && workerPids.length === 0) {
|
|
90
89
|
(0, logger_1.log)("daemon", "No stale processes found");
|
|
@@ -50,9 +50,9 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
50
50
|
};
|
|
51
51
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
52
|
exports.WatcherManager = void 0;
|
|
53
|
-
const watcher = __importStar(require("@parcel/watcher"));
|
|
54
53
|
const fs = __importStar(require("node:fs"));
|
|
55
54
|
const path = __importStar(require("node:path"));
|
|
55
|
+
const watcher = __importStar(require("@parcel/watcher"));
|
|
56
56
|
const batch_processor_1 = require("../index/batch-processor");
|
|
57
57
|
const watcher_1 = require("../index/watcher");
|
|
58
58
|
const logger_1 = require("../utils/logger");
|
|
@@ -250,7 +250,7 @@ class WatcherManager {
|
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
// Backoff: wait before re-subscribing (3s, 6s, 12s)
|
|
253
|
-
const delayMs = 3000 * Math.pow(2, fails - 1);
|
|
253
|
+
const delayMs = 3000 * Math.pow(2, (fails - 1));
|
|
254
254
|
console.error(`[daemon:${name}] Recovering watcher (attempt ${fails}/${MAX_WATCHER_RETRIES}, backoff ${delayMs}ms)...`);
|
|
255
255
|
setTimeout(() => {
|
|
256
256
|
if (this.deps.getShuttingDown()) {
|
|
@@ -368,17 +368,20 @@ class WatcherManager {
|
|
|
368
368
|
const bn = path.basename(absPath).toLowerCase();
|
|
369
369
|
if (!INDEXABLE_EXTENSIONS.has(ext) && !INDEXABLE_EXTENSIONS.has(bn))
|
|
370
370
|
continue;
|
|
371
|
-
seenPaths.add(absPath);
|
|
372
371
|
try {
|
|
373
372
|
const stats = yield fs.promises.stat(absPath);
|
|
374
|
-
// Skip files that are too large or empty — they'll never be indexed
|
|
373
|
+
// Skip files that are too large or empty — they'll never be indexed.
|
|
374
|
+
// Leave them OUT of seenPaths so a file that became empty/oversized is
|
|
375
|
+
// treated as deleted by the purge sweep below and its now-stale chunks
|
|
376
|
+
// are removed, rather than lingering as unsearchable-but-present rows.
|
|
375
377
|
if (stats.size === 0 || stats.size > MAX_FILE_SIZE_BYTES)
|
|
376
378
|
continue;
|
|
379
|
+
seenPaths.add(absPath);
|
|
377
380
|
const cached = metaCache.get(absPath);
|
|
378
381
|
if (!isFileCached(cached, stats)) {
|
|
379
382
|
// Fast path: if only mtime changed but size is identical and we have a hash,
|
|
380
383
|
// just verify the hash in-process instead of sending to a worker.
|
|
381
|
-
if (cached
|
|
384
|
+
if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
|
|
382
385
|
const { computeBufferHash } = yield Promise.resolve().then(() => __importStar(require("../utils/file-utils")));
|
|
383
386
|
const buf = yield fs.promises.readFile(absPath);
|
|
384
387
|
const hash = computeBufferHash(buf);
|
|
@@ -400,7 +403,7 @@ class WatcherManager {
|
|
|
400
403
|
// batch processor drain and compaction run between bursts.
|
|
401
404
|
if (queued % 500 === 0) {
|
|
402
405
|
(0, logger_1.debug)("catchup", `${path.basename(root)}: throttle pause at ${queued} queued`);
|
|
403
|
-
yield new Promise(r => setTimeout(r, 5000));
|
|
406
|
+
yield new Promise((r) => setTimeout(r, 5000));
|
|
404
407
|
}
|
|
405
408
|
}
|
|
406
409
|
else {
|
|
@@ -439,6 +442,19 @@ class WatcherManager {
|
|
|
439
442
|
}
|
|
440
443
|
unwatchProject(root) {
|
|
441
444
|
return __awaiter(this, void 0, void 0, function* () {
|
|
445
|
+
// Stop poll-mode timers + their FSEvents recovery probe first, so a removed
|
|
446
|
+
// project can't keep scanning until full daemon shutdown. These live
|
|
447
|
+
// independently of the processor, so clear them even on the early return.
|
|
448
|
+
const pollInterval = this.pollIntervals.get(root);
|
|
449
|
+
if (pollInterval) {
|
|
450
|
+
clearInterval(pollInterval);
|
|
451
|
+
this.pollIntervals.delete(root);
|
|
452
|
+
}
|
|
453
|
+
const recoveryTimer = this.pollRecoveryTimers.get(root);
|
|
454
|
+
if (recoveryTimer) {
|
|
455
|
+
clearInterval(recoveryTimer);
|
|
456
|
+
this.pollRecoveryTimers.delete(root);
|
|
457
|
+
}
|
|
442
458
|
const processor = this.deps.processors.get(root);
|
|
443
459
|
if (!processor)
|
|
444
460
|
return;
|
|
@@ -110,37 +110,163 @@ function resolveCallSites(callers, targetSymbol, fileCache = new Map()) {
|
|
|
110
110
|
*/
|
|
111
111
|
const BUILTIN_CALLEES = new Set([
|
|
112
112
|
// Object/primitive methods
|
|
113
|
-
"toString",
|
|
113
|
+
"toString",
|
|
114
|
+
"valueOf",
|
|
115
|
+
"hasOwnProperty",
|
|
114
116
|
// Array/String methods
|
|
115
|
-
"push",
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
117
|
+
"push",
|
|
118
|
+
"pop",
|
|
119
|
+
"shift",
|
|
120
|
+
"unshift",
|
|
121
|
+
"slice",
|
|
122
|
+
"splice",
|
|
123
|
+
"concat",
|
|
124
|
+
"join",
|
|
125
|
+
"map",
|
|
126
|
+
"filter",
|
|
127
|
+
"reduce",
|
|
128
|
+
"forEach",
|
|
129
|
+
"find",
|
|
130
|
+
"findIndex",
|
|
131
|
+
"some",
|
|
132
|
+
"every",
|
|
133
|
+
"includes",
|
|
134
|
+
"indexOf",
|
|
135
|
+
"lastIndexOf",
|
|
136
|
+
"sort",
|
|
137
|
+
"reverse",
|
|
138
|
+
"flat",
|
|
139
|
+
"flatMap",
|
|
140
|
+
"keys",
|
|
141
|
+
"values",
|
|
142
|
+
"entries",
|
|
143
|
+
"fill",
|
|
144
|
+
"split",
|
|
145
|
+
"trim",
|
|
146
|
+
"trimStart",
|
|
147
|
+
"trimEnd",
|
|
148
|
+
"replace",
|
|
149
|
+
"replaceAll",
|
|
150
|
+
"match",
|
|
151
|
+
"matchAll",
|
|
152
|
+
"test",
|
|
153
|
+
"exec",
|
|
154
|
+
"padStart",
|
|
155
|
+
"padEnd",
|
|
156
|
+
"repeat",
|
|
157
|
+
"charAt",
|
|
158
|
+
"charCodeAt",
|
|
159
|
+
"codePointAt",
|
|
160
|
+
"startsWith",
|
|
161
|
+
"endsWith",
|
|
162
|
+
"toLowerCase",
|
|
163
|
+
"toUpperCase",
|
|
164
|
+
"substring",
|
|
165
|
+
"substr",
|
|
166
|
+
"localeCompare",
|
|
167
|
+
"normalize",
|
|
123
168
|
// Map/Set methods
|
|
124
|
-
"get",
|
|
169
|
+
"get",
|
|
170
|
+
"set",
|
|
171
|
+
"has",
|
|
172
|
+
"add",
|
|
173
|
+
"delete",
|
|
174
|
+
"clear",
|
|
125
175
|
// Math/Date/Number members
|
|
126
|
-
"trunc",
|
|
127
|
-
"
|
|
128
|
-
"
|
|
176
|
+
"trunc",
|
|
177
|
+
"floor",
|
|
178
|
+
"ceil",
|
|
179
|
+
"round",
|
|
180
|
+
"abs",
|
|
181
|
+
"min",
|
|
182
|
+
"max",
|
|
183
|
+
"random",
|
|
184
|
+
"pow",
|
|
185
|
+
"sqrt",
|
|
186
|
+
"log2",
|
|
187
|
+
"log10",
|
|
188
|
+
"sign",
|
|
189
|
+
"now",
|
|
190
|
+
"parse",
|
|
191
|
+
"parseInt",
|
|
192
|
+
"parseFloat",
|
|
193
|
+
"isInteger",
|
|
194
|
+
"isFinite",
|
|
195
|
+
"isNaN",
|
|
196
|
+
"toFixed",
|
|
197
|
+
"toISOString",
|
|
129
198
|
// Promise/Function members
|
|
130
|
-
"then",
|
|
131
|
-
"
|
|
199
|
+
"then",
|
|
200
|
+
"catch",
|
|
201
|
+
"finally",
|
|
202
|
+
"resolve",
|
|
203
|
+
"reject",
|
|
204
|
+
"all",
|
|
205
|
+
"allSettled",
|
|
206
|
+
"race",
|
|
207
|
+
"any",
|
|
208
|
+
"call",
|
|
209
|
+
"apply",
|
|
210
|
+
"bind",
|
|
132
211
|
// JSON / global constructors & functions
|
|
133
|
-
"JSON",
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
212
|
+
"JSON",
|
|
213
|
+
"stringify",
|
|
214
|
+
"Math",
|
|
215
|
+
"Date",
|
|
216
|
+
"Promise",
|
|
217
|
+
"Array",
|
|
218
|
+
"Object",
|
|
219
|
+
"String",
|
|
220
|
+
"Number",
|
|
221
|
+
"Boolean",
|
|
222
|
+
"Symbol",
|
|
223
|
+
"RegExp",
|
|
224
|
+
"Error",
|
|
225
|
+
"TypeError",
|
|
226
|
+
"RangeError",
|
|
227
|
+
"Map",
|
|
228
|
+
"Set",
|
|
229
|
+
"WeakMap",
|
|
230
|
+
"WeakSet",
|
|
231
|
+
"Buffer",
|
|
232
|
+
"URL",
|
|
233
|
+
"URLSearchParams",
|
|
234
|
+
"isArray",
|
|
235
|
+
"from",
|
|
236
|
+
"of",
|
|
237
|
+
"assign",
|
|
238
|
+
"freeze",
|
|
239
|
+
"create",
|
|
240
|
+
"getOwnPropertyNames",
|
|
241
|
+
"fromEntries",
|
|
242
|
+
"setTimeout",
|
|
243
|
+
"setInterval",
|
|
244
|
+
"clearTimeout",
|
|
245
|
+
"clearInterval",
|
|
246
|
+
"queueMicrotask",
|
|
247
|
+
"structuredClone",
|
|
248
|
+
"fetch",
|
|
249
|
+
"console",
|
|
250
|
+
"process",
|
|
251
|
+
"require",
|
|
252
|
+
"encodeURIComponent",
|
|
253
|
+
"decodeURIComponent",
|
|
254
|
+
"encodeURI",
|
|
255
|
+
"decodeURI",
|
|
256
|
+
"atob",
|
|
257
|
+
"btoa",
|
|
141
258
|
// console members
|
|
142
|
-
"warn",
|
|
143
|
-
"
|
|
259
|
+
"warn",
|
|
260
|
+
"info",
|
|
261
|
+
"debug",
|
|
262
|
+
"trace",
|
|
263
|
+
"table",
|
|
264
|
+
"dir",
|
|
265
|
+
"group",
|
|
266
|
+
"groupEnd",
|
|
267
|
+
"assert",
|
|
268
|
+
"time",
|
|
269
|
+
"timeEnd",
|
|
144
270
|
]);
|
|
145
271
|
/** True when an UNRESOLVED callee name is a known JS/TS builtin. */
|
|
146
272
|
function isBuiltinCallee(name) {
|
|
@@ -27,10 +27,10 @@ class GraphBuilder {
|
|
|
27
27
|
scopeWhere(condition) {
|
|
28
28
|
let result = condition;
|
|
29
29
|
if (this.pathPrefix) {
|
|
30
|
-
result = `${result} AND
|
|
30
|
+
result = `${result} AND ${(0, filter_builder_1.pathStartsWith)(this.pathPrefix)}`;
|
|
31
31
|
}
|
|
32
32
|
for (const ex of this.excludePrefixes) {
|
|
33
|
-
result = `${result} AND
|
|
33
|
+
result = `${result} AND ${(0, filter_builder_1.pathNotStartsWith)(ex)}`;
|
|
34
34
|
}
|
|
35
35
|
return result;
|
|
36
36
|
}
|
package/dist/lib/graph/impact.js
CHANGED
|
@@ -66,10 +66,10 @@ function expandFileSymbols(symbols, vectorDb, projectRoot, excludePrefixes) {
|
|
|
66
66
|
return symbols;
|
|
67
67
|
const table = yield vectorDb.ensureTable();
|
|
68
68
|
const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
|
|
69
|
-
let where = `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND
|
|
69
|
+
let where = `array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND ${(0, filter_builder_1.pathStartsWith)(prefix)}`;
|
|
70
70
|
for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
|
|
71
71
|
const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
|
|
72
|
-
where += ` AND
|
|
72
|
+
where += ` AND ${(0, filter_builder_1.pathNotStartsWith)(exNorm)}`;
|
|
73
73
|
}
|
|
74
74
|
// Find the file that defines this symbol
|
|
75
75
|
const defRows = yield table
|
|
@@ -146,10 +146,10 @@ function findImportFallbackTests(expandedSymbols, originalSymbols, vectorDb, pro
|
|
|
146
146
|
// test body's referenced_symbols ends up empty).
|
|
147
147
|
const table = yield vectorDb.ensureTable();
|
|
148
148
|
const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
|
|
149
|
-
let pathScope =
|
|
149
|
+
let pathScope = (0, filter_builder_1.pathStartsWith)(prefix);
|
|
150
150
|
for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
|
|
151
151
|
const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
|
|
152
|
-
pathScope += ` AND
|
|
152
|
+
pathScope += ` AND ${(0, filter_builder_1.pathNotStartsWith)(exNorm)}`;
|
|
153
153
|
}
|
|
154
154
|
// Textual matching runs on the ORIGINAL targets only: matching co-defined
|
|
155
155
|
// file symbols (helpers like `log`) textually drags in every test file
|
|
@@ -203,10 +203,10 @@ function walkCallers(symbol, graphBuilder, testHits, currentHop, maxDepth, visit
|
|
|
203
203
|
function findDependents(symbols_1, vectorDb_1, projectRoot_1, excludePaths_1) {
|
|
204
204
|
return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, excludePaths, limit = 10, excludePrefixes) {
|
|
205
205
|
const table = yield vectorDb.ensureTable();
|
|
206
|
-
let pathScope =
|
|
206
|
+
let pathScope = (0, filter_builder_1.pathStartsWith)(`${projectRoot}/`);
|
|
207
207
|
for (const ex of excludePrefixes !== null && excludePrefixes !== void 0 ? excludePrefixes : []) {
|
|
208
208
|
const exNorm = ex.endsWith("/") ? ex : `${ex}/`;
|
|
209
|
-
pathScope += ` AND
|
|
209
|
+
pathScope += ` AND ${(0, filter_builder_1.pathNotStartsWith)(exNorm)}`;
|
|
210
210
|
}
|
|
211
211
|
const counts = new Map();
|
|
212
212
|
for (const sym of symbols) {
|
|
@@ -46,10 +46,10 @@ exports.ProjectBatchProcessor = void 0;
|
|
|
46
46
|
const fs = __importStar(require("node:fs"));
|
|
47
47
|
const path = __importStar(require("node:path"));
|
|
48
48
|
const config_1 = require("../../config");
|
|
49
|
+
const vector_db_1 = require("../store/vector-db");
|
|
49
50
|
const cache_check_1 = require("../utils/cache-check");
|
|
50
51
|
const file_utils_1 = require("../utils/file-utils");
|
|
51
52
|
const logger_1 = require("../utils/logger");
|
|
52
|
-
const vector_db_1 = require("../store/vector-db");
|
|
53
53
|
const pool_1 = require("../workers/pool");
|
|
54
54
|
const watcher_batch_1 = require("./watcher-batch");
|
|
55
55
|
// Fast path-segment check to reject events that leak through FSEvents overflow.
|
|
@@ -164,7 +164,8 @@ class ProjectBatchProcessor {
|
|
|
164
164
|
break;
|
|
165
165
|
attempted.add(absPath);
|
|
166
166
|
processed++;
|
|
167
|
-
if (batch.size > 10 &&
|
|
167
|
+
if (batch.size > 10 &&
|
|
168
|
+
(processed % 10 === 0 || processed === batch.size)) {
|
|
168
169
|
(0, logger_1.log)(this.wtag, `Progress: ${processed}/${batch.size} (${reindexed} reindexed)`);
|
|
169
170
|
}
|
|
170
171
|
if (event === "unlink") {
|
|
@@ -176,15 +177,24 @@ class ProjectBatchProcessor {
|
|
|
176
177
|
// change or add
|
|
177
178
|
try {
|
|
178
179
|
const stats = yield fs.promises.stat(absPath);
|
|
179
|
-
if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size))
|
|
180
|
+
if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size)) {
|
|
181
|
+
// File became non-indexable (emptied or now too large). If we
|
|
182
|
+
// indexed it before, drop its chunks + meta so search stops
|
|
183
|
+
// returning stale content; otherwise there's nothing to clean up.
|
|
184
|
+
if (this.metaCache.get(absPath)) {
|
|
185
|
+
deletes.push(absPath);
|
|
186
|
+
metaDeletes.push(absPath);
|
|
187
|
+
reindexed++;
|
|
188
|
+
}
|
|
180
189
|
continue;
|
|
190
|
+
}
|
|
181
191
|
const cached = this.metaCache.get(absPath);
|
|
182
192
|
if ((0, cache_check_1.isFileCached)(cached, stats)) {
|
|
183
193
|
continue;
|
|
184
194
|
}
|
|
185
195
|
// Fast path: if only mtime changed but size matches and we have a hash,
|
|
186
196
|
// verify in-process instead of dispatching to a worker (~220ms saved).
|
|
187
|
-
if (cached
|
|
197
|
+
if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
|
|
188
198
|
const buf = yield fs.promises.readFile(absPath);
|
|
189
199
|
const hash = (0, file_utils_1.computeBufferHash)(buf);
|
|
190
200
|
if (hash === cached.hash) {
|
|
@@ -319,8 +329,8 @@ class ProjectBatchProcessor {
|
|
|
319
329
|
}
|
|
320
330
|
}
|
|
321
331
|
if (dropped > 0) {
|
|
322
|
-
const droppedPaths = [...batch.keys()].filter(p => !requeued.has(p));
|
|
323
|
-
(0, logger_1.log)(this.wtag, `Dropped ${dropped} file(s) after ${MAX_RETRIES} retries: ${droppedPaths.map(p => path.basename(p)).join(", ")}`);
|
|
332
|
+
const droppedPaths = [...batch.keys()].filter((p) => !requeued.has(p));
|
|
333
|
+
(0, logger_1.log)(this.wtag, `Dropped ${dropped} file(s) after ${MAX_RETRIES} retries: ${droppedPaths.map((p) => path.basename(p)).join(", ")}`);
|
|
324
334
|
}
|
|
325
335
|
backoffOverrideMs = this.pending.size > 0 ? backoffMs : 0;
|
|
326
336
|
}
|
|
@@ -256,6 +256,7 @@ class TreeSitterChunker {
|
|
|
256
256
|
if (c.definedSymbols && c.content.length > 100) {
|
|
257
257
|
const extra = [];
|
|
258
258
|
let m;
|
|
259
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
|
|
259
260
|
while ((m = PROP_FN_RE.exec(c.content)) !== null) {
|
|
260
261
|
const name = m[1];
|
|
261
262
|
if (name.length > 1 && !c.definedSymbols.includes(name)) {
|
|
@@ -278,6 +279,7 @@ class TreeSitterChunker {
|
|
|
278
279
|
const existing = new Set(c.referencedSymbols);
|
|
279
280
|
let jm;
|
|
280
281
|
const jsxRefs = [];
|
|
282
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex exec loop
|
|
281
283
|
while ((jm = JSX_RE.exec(c.content)) !== null) {
|
|
282
284
|
// Extract base component name (e.g., "Foo" from "Foo.Bar")
|
|
283
285
|
const full = jm[1];
|
package/dist/lib/index/syncer.js
CHANGED
|
@@ -55,11 +55,11 @@ exports.initialSync = initialSync;
|
|
|
55
55
|
const fs = __importStar(require("node:fs"));
|
|
56
56
|
const path = __importStar(require("node:path"));
|
|
57
57
|
const config_1 = require("../../config");
|
|
58
|
-
const logger_1 = require("../utils/logger");
|
|
59
58
|
const meta_cache_1 = require("../store/meta-cache");
|
|
60
59
|
const vector_db_1 = require("../store/vector-db");
|
|
61
60
|
// isIndexableFile no longer used — extension check inlined for performance
|
|
62
61
|
const lock_1 = require("../utils/lock");
|
|
62
|
+
const logger_1 = require("../utils/logger");
|
|
63
63
|
const project_root_1 = require("../utils/project-root");
|
|
64
64
|
const pool_1 = require("../workers/pool");
|
|
65
65
|
const index_config_1 = require("./index-config");
|
|
@@ -74,13 +74,24 @@ function flushBatch(db, meta, vectors, pendingMeta, pendingDeletes, dryRun) {
|
|
|
74
74
|
return __awaiter(this, void 0, void 0, function* () {
|
|
75
75
|
if (dryRun)
|
|
76
76
|
return;
|
|
77
|
-
// 1.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
// 1. Insert the new vectors FIRST, then delete the old chunks for those paths
|
|
78
|
+
// (excluding the just-inserted ids). Deleting first would leave a file
|
|
79
|
+
// unsearchable if the insert then fails — the old, still-valid chunks would
|
|
80
|
+
// already be gone. Mirrors batch-processor's insert-first flush. Paths in
|
|
81
|
+
// pendingDeletes with no new vectors (emptied / non-indexable files) match
|
|
82
|
+
// no excluded id, so all their old chunks are removed.
|
|
83
|
+
const newIds = vectors.map((v) => v.id);
|
|
81
84
|
if (vectors.length > 0) {
|
|
82
85
|
yield db.insertBatch(vectors);
|
|
83
86
|
}
|
|
87
|
+
if (pendingDeletes.length > 0) {
|
|
88
|
+
if (newIds.length > 0) {
|
|
89
|
+
yield db.deletePathsExcludingIds(pendingDeletes, newIds);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
yield db.deletePaths(pendingDeletes);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
84
95
|
// 2. Update MetaCache only after VectorDB write succeeds
|
|
85
96
|
for (const [p, entry] of pendingMeta.entries()) {
|
|
86
97
|
meta.put(p, entry);
|
|
@@ -173,7 +184,8 @@ function initialSync(options) {
|
|
|
173
184
|
}
|
|
174
185
|
projectKeys.clear();
|
|
175
186
|
}
|
|
176
|
-
else if (projectKeys.size > 0 &&
|
|
187
|
+
else if (projectKeys.size > 0 &&
|
|
188
|
+
vectorFileCount < projectKeys.size * 0.8) {
|
|
177
189
|
(0, logger_1.log)("index", `Partial cache detected: ${vectorFileCount} files in vectors vs ${projectKeys.size} in cache — clearing cache to re-embed missing files`);
|
|
178
190
|
for (const key of projectKeys) {
|
|
179
191
|
mc.delete(key);
|
|
@@ -351,13 +363,13 @@ function initialSync(options) {
|
|
|
351
363
|
return; // Broken symlink
|
|
352
364
|
}
|
|
353
365
|
}
|
|
354
|
-
if (!stats.isFile() ||
|
|
366
|
+
if (!stats.isFile() ||
|
|
367
|
+
stats.size === 0 ||
|
|
368
|
+
stats.size > config_1.MAX_FILE_SIZE_BYTES) {
|
|
355
369
|
return;
|
|
356
370
|
}
|
|
357
371
|
// Use absolute path as the key for MetaCache
|
|
358
|
-
const cached = treatAsEmptyCache
|
|
359
|
-
? undefined
|
|
360
|
-
: mc.get(absPath);
|
|
372
|
+
const cached = treatAsEmptyCache ? undefined : mc.get(absPath);
|
|
361
373
|
if (cached &&
|
|
362
374
|
cached.mtimeMs === stats.mtimeMs &&
|
|
363
375
|
cached.size === stats.size) {
|
|
@@ -67,8 +67,17 @@ function processBatchCore(batch, metaCache, pool, signal) {
|
|
|
67
67
|
}
|
|
68
68
|
try {
|
|
69
69
|
const stats = yield fs.promises.stat(absPath);
|
|
70
|
-
if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size))
|
|
70
|
+
if (!(0, file_utils_1.isIndexableFile)(absPath, stats.size)) {
|
|
71
|
+
// File became non-indexable (emptied or now too large). If we indexed
|
|
72
|
+
// it before, drop its chunks + meta so search stops returning stale
|
|
73
|
+
// content; otherwise there's nothing to clean up.
|
|
74
|
+
if (metaCache.get(absPath)) {
|
|
75
|
+
deletes.push(absPath);
|
|
76
|
+
metaDeletes.push(absPath);
|
|
77
|
+
reindexed++;
|
|
78
|
+
}
|
|
71
79
|
continue;
|
|
80
|
+
}
|
|
72
81
|
const cached = metaCache.get(absPath);
|
|
73
82
|
if ((0, cache_check_1.isFileCached)(cached, stats)) {
|
|
74
83
|
continue;
|
package/dist/lib/llm/config.js
CHANGED
|
@@ -10,7 +10,7 @@ function envInt(key, fallback) {
|
|
|
10
10
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
11
11
|
}
|
|
12
12
|
function getLlmConfig() {
|
|
13
|
-
var _a, _b, _c;
|
|
13
|
+
var _a, _b, _c, _d;
|
|
14
14
|
return {
|
|
15
15
|
model: (_a = process.env.GMAX_LLM_MODEL) !== null && _a !== void 0 ? _a : DEFAULT_MODEL,
|
|
16
16
|
binary: (_b = process.env.GMAX_LLM_BINARY) !== null && _b !== void 0 ? _b : "llama-server",
|
|
@@ -21,5 +21,6 @@ function getLlmConfig() {
|
|
|
21
21
|
maxTokens: envInt("GMAX_LLM_MAX_TOKENS", 8192),
|
|
22
22
|
idleTimeoutMin: envInt("GMAX_LLM_IDLE_TIMEOUT", 30),
|
|
23
23
|
startupWaitSec: envInt("GMAX_LLM_STARTUP_WAIT", 60),
|
|
24
|
+
reasoningFormat: (_d = process.env.GMAX_LLM_REASONING_FORMAT) !== null && _d !== void 0 ? _d : "deepseek",
|
|
24
25
|
};
|
|
25
26
|
}
|