grepmax 0.21.2 → 0.22.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 +2 -4
- package/dist/commands/dead.js +2 -1
- package/dist/commands/impact.js +3 -3
- package/dist/commands/mcp.js +14 -7
- package/dist/commands/peek.js +17 -4
- package/dist/commands/plugin.js +34 -24
- package/dist/commands/search-run.js +2 -0
- package/dist/commands/search.js +21 -11
- package/dist/commands/test-find.js +2 -2
- package/dist/commands/trace.js +13 -5
- package/dist/config.js +6 -1
- package/dist/lib/daemon/ipc-handler.js +1 -0
- package/dist/lib/daemon/mlx-server-manager.js +52 -8
- package/dist/lib/daemon/process-manager.js +16 -17
- package/dist/lib/graph/graph-builder.js +111 -12
- package/dist/lib/graph/impact.js +66 -16
- package/dist/lib/index/batch-processor.js +45 -9
- package/dist/lib/index/chunker.js +30 -3
- package/dist/lib/llm/tools.js +3 -3
- package/dist/lib/search/searcher.js +26 -21
- package/dist/lib/store/vector-db.js +52 -23
- package/dist/lib/utils/daemon-client.js +35 -9
- package/dist/lib/workers/orchestrator.js +1 -0
- package/dist/lib/workers/pool.js +62 -20
- package/package.json +8 -3
- package/plugins/grepmax/.claude-plugin/plugin.json +2 -8
- package/scripts/postinstall.js +8 -115
|
@@ -10,14 +10,12 @@
|
|
|
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.
|
|
13
|
+
"version": "0.22.0",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Robert Owens",
|
|
16
16
|
"email": "robowens@me.com"
|
|
17
17
|
},
|
|
18
|
-
"skills": [
|
|
19
|
-
"./skills/grepmax"
|
|
20
|
-
]
|
|
18
|
+
"skills": ["./skills/grepmax"]
|
|
21
19
|
}
|
|
22
20
|
]
|
|
23
21
|
}
|
package/dist/commands/dead.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.dead = void 0;
|
|
46
46
|
const commander_1 = require("commander");
|
|
47
|
+
const languages_1 = require("../lib/core/languages");
|
|
47
48
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
48
49
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
49
50
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
@@ -147,7 +148,7 @@ exports.dead = new commander_1.Command("dead")
|
|
|
147
148
|
const defLine = Number(defRow.start_line || 0);
|
|
148
149
|
const isExported = Boolean(defRow.is_exported);
|
|
149
150
|
const builder = new graph_builder_1.GraphBuilder(vectorDb, scope.pathPrefix, scope.excludePrefixes);
|
|
150
|
-
const callers = yield builder.getCallers(symbol);
|
|
151
|
+
const callers = yield builder.getCallers(symbol, (0, languages_1.languageFamilyForPath)(defPath));
|
|
151
152
|
const status = callers.length === 0 ? (isExported ? "PUBLIC_EXPORT" : "DEAD") : "LIVE";
|
|
152
153
|
const topCallers = callers
|
|
153
154
|
.slice(0, TOP_CALLERS)
|
package/dist/commands/impact.js
CHANGED
|
@@ -77,7 +77,7 @@ exports.impact = new commander_1.Command("impact")
|
|
|
77
77
|
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
78
78
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
79
79
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
80
|
-
const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
|
|
80
|
+
const { symbols, resolvedAsFile, symbolFamilies } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
|
|
81
81
|
if (symbols.length === 0) {
|
|
82
82
|
console.log(resolvedAsFile
|
|
83
83
|
? `No symbols found in file: ${target}`
|
|
@@ -109,9 +109,9 @@ exports.impact = new commander_1.Command("impact")
|
|
|
109
109
|
// traversal entirely so the affected-tests section is omitted (not just
|
|
110
110
|
// empty) below.
|
|
111
111
|
const [dependents, tests] = yield Promise.all([
|
|
112
|
-
(0, impact_1.findDependents)(symbols, vectorDb, queryRoot, excludePaths, undefined, scope.excludePrefixes),
|
|
112
|
+
(0, impact_1.findDependents)(symbols, vectorDb, queryRoot, excludePaths, undefined, scope.excludePrefixes, symbolFamilies),
|
|
113
113
|
includeTests
|
|
114
|
-
? (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes)
|
|
114
|
+
? (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes, symbolFamilies)
|
|
115
115
|
: Promise.resolve([]),
|
|
116
116
|
]);
|
|
117
117
|
// Separate test files from non-test dependents
|
package/dist/commands/mcp.js
CHANGED
|
@@ -65,6 +65,7 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
65
65
|
const commander_1 = require("commander");
|
|
66
66
|
const zod_1 = require("zod");
|
|
67
67
|
const config_1 = require("../config");
|
|
68
|
+
const languages_1 = require("../lib/core/languages");
|
|
68
69
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
69
70
|
const index_config_1 = require("../lib/index/index-config");
|
|
70
71
|
const syncer_1 = require("../lib/index/syncer");
|
|
@@ -906,6 +907,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
906
907
|
symbol: t.node.symbol,
|
|
907
908
|
file: t.node.file,
|
|
908
909
|
line: t.node.line,
|
|
910
|
+
edgeKind: t.node.edgeKind,
|
|
909
911
|
});
|
|
910
912
|
walkCallers(t.callers);
|
|
911
913
|
}
|
|
@@ -933,7 +935,12 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
933
935
|
const loc = c.file
|
|
934
936
|
? `${rel(c.file)}:${c.line + 1}`
|
|
935
937
|
: "(not indexed)";
|
|
936
|
-
|
|
938
|
+
// `(member)`/`(type)` flag inferred edges (receiver-unverified call
|
|
939
|
+
// or a type-position ref) so they don't read as confirmed callers.
|
|
940
|
+
const tag = c.edgeKind === "member" || c.edgeKind === "type"
|
|
941
|
+
? ` (${c.edgeKind})`
|
|
942
|
+
: "";
|
|
943
|
+
parts.push(` <- ${c.symbol} ${loc}${tag}`);
|
|
937
944
|
}
|
|
938
945
|
if (callerList.length > maxCallers) {
|
|
939
946
|
parts.push(` ... and ${callerList.length - maxCallers} more`);
|
|
@@ -993,7 +1000,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
993
1000
|
const defLine = Number(defRow.start_line || 0);
|
|
994
1001
|
const isExported = Boolean(defRow.is_exported);
|
|
995
1002
|
const builder = new graph_builder_1.GraphBuilder(db, root);
|
|
996
|
-
const callers = yield builder.getCallers(symbol);
|
|
1003
|
+
const callers = yield builder.getCallers(symbol, (0, languages_1.languageFamilyForPath)(defPath));
|
|
997
1004
|
const rel = (p) => p.startsWith(root) ? p.slice(root.length + 1) : p;
|
|
998
1005
|
const defLoc = `${rel(defPath)}:${defLine + 1}`;
|
|
999
1006
|
if (callers.length === 0) {
|
|
@@ -1714,10 +1721,10 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1714
1721
|
try {
|
|
1715
1722
|
const { resolveTargetSymbols, findTests } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact")));
|
|
1716
1723
|
const db = getVectorDb();
|
|
1717
|
-
const { symbols } = yield resolveTargetSymbols(target, db, projectRoot);
|
|
1724
|
+
const { symbols, symbolFamilies } = yield resolveTargetSymbols(target, db, projectRoot);
|
|
1718
1725
|
if (symbols.length === 0)
|
|
1719
1726
|
return ok(`No symbols found for: ${target}`);
|
|
1720
|
-
const tests = yield findTests(symbols, db, projectRoot, depth);
|
|
1727
|
+
const tests = yield findTests(symbols, db, projectRoot, depth, undefined, symbolFamilies);
|
|
1721
1728
|
if (tests.length === 0)
|
|
1722
1729
|
return ok(`No tests found for ${target}.`);
|
|
1723
1730
|
const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
|
|
@@ -1742,7 +1749,7 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1742
1749
|
try {
|
|
1743
1750
|
const { resolveTargetSymbols, findTests, findDependents, isTestPath } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/impact")));
|
|
1744
1751
|
const db = getVectorDb();
|
|
1745
|
-
const { symbols, resolvedAsFile } = yield resolveTargetSymbols(target, db, projectRoot);
|
|
1752
|
+
const { symbols, resolvedAsFile, symbolFamilies } = yield resolveTargetSymbols(target, db, projectRoot);
|
|
1746
1753
|
if (symbols.length === 0)
|
|
1747
1754
|
return ok(`No symbols found for: ${target}`);
|
|
1748
1755
|
const targetPath = resolvedAsFile
|
|
@@ -1750,8 +1757,8 @@ exports.mcp = new commander_1.Command("mcp")
|
|
|
1750
1757
|
: undefined;
|
|
1751
1758
|
const excludePaths = targetPath ? new Set([targetPath]) : undefined;
|
|
1752
1759
|
const [dependents, tests] = yield Promise.all([
|
|
1753
|
-
findDependents(symbols, db, projectRoot, excludePaths),
|
|
1754
|
-
findTests(symbols, db, projectRoot, depth),
|
|
1760
|
+
findDependents(symbols, db, projectRoot, excludePaths, undefined, undefined, symbolFamilies),
|
|
1761
|
+
findTests(symbols, db, projectRoot, depth, undefined, symbolFamilies),
|
|
1755
1762
|
]);
|
|
1756
1763
|
const nonTestDeps = dependents.filter((d) => !isTestPath(d.file));
|
|
1757
1764
|
const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
|
package/dist/commands/peek.js
CHANGED
|
@@ -212,7 +212,6 @@ exports.peek = new commander_1.Command("peek")
|
|
|
212
212
|
const endLine = metaRows.length > 0
|
|
213
213
|
? Number(metaRows[0].end_line || 0)
|
|
214
214
|
: center.line;
|
|
215
|
-
// Get multi-hop callers if depth > 1
|
|
216
215
|
let callerList;
|
|
217
216
|
if (depth > 1) {
|
|
218
217
|
const multiHop = yield graphBuilder.buildGraphMultiHop(symbol, depth);
|
|
@@ -224,6 +223,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
224
223
|
symbol: t.node.symbol,
|
|
225
224
|
file: t.node.file,
|
|
226
225
|
line: t.node.line,
|
|
226
|
+
edgeKind: t.node.edgeKind,
|
|
227
227
|
});
|
|
228
228
|
walkCallers(t.callers);
|
|
229
229
|
}
|
|
@@ -236,8 +236,16 @@ exports.peek = new commander_1.Command("peek")
|
|
|
236
236
|
symbol: c.symbol,
|
|
237
237
|
file: c.file,
|
|
238
238
|
line: c.line,
|
|
239
|
+
edgeKind: c.edgeKind,
|
|
239
240
|
}));
|
|
240
241
|
}
|
|
242
|
+
// Confidence per caller symbol: getCallers sorts free calls first, so the
|
|
243
|
+
// first edgeKind seen for a symbol is its highest-confidence one.
|
|
244
|
+
const edgeKindBySym = new Map();
|
|
245
|
+
for (const c of callerList) {
|
|
246
|
+
if (!edgeKindBySym.has(c.symbol))
|
|
247
|
+
edgeKindBySym.set(c.symbol, c.edgeKind);
|
|
248
|
+
}
|
|
241
249
|
// Re-anchor chunk-level caller rows to actual call sites and dedupe —
|
|
242
250
|
// getCallers() returns one row per chunk, which multiplies callers for
|
|
243
251
|
// classes split across many chunks (verified: 3 real call sites → 66).
|
|
@@ -247,6 +255,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
247
255
|
symbol: c.symbol,
|
|
248
256
|
file: c.file,
|
|
249
257
|
line: (_a = c.snippetLine) !== null && _a !== void 0 ? _a : c.line,
|
|
258
|
+
edgeKind: edgeKindBySym.get(c.symbol),
|
|
250
259
|
});
|
|
251
260
|
});
|
|
252
261
|
// Builtins listed as "(not indexed)" callees (trunc, now, filter, …)
|
|
@@ -266,6 +275,10 @@ exports.peek = new commander_1.Command("peek")
|
|
|
266
275
|
file: c.file,
|
|
267
276
|
line: c.line,
|
|
268
277
|
}));
|
|
278
|
+
// Inferred-caller marker: `member` = receiver-unverified `x.T()`, `type` =
|
|
279
|
+
// a type-position reference (not a call). Free calls render clean — the
|
|
280
|
+
// trustworthy default — so only guesses carry a tag.
|
|
281
|
+
const kindTag = (k) => k === "member" || k === "type" ? ` (${k})` : "";
|
|
269
282
|
if (opts.agent) {
|
|
270
283
|
// Compact TSV output
|
|
271
284
|
const exportedStr = exported ? "exported" : "";
|
|
@@ -290,7 +303,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
290
303
|
console.log(`sig: ${sigOnly}`);
|
|
291
304
|
// Callers
|
|
292
305
|
for (const c of resolvedCallers.slice(0, MAX_CALLERS)) {
|
|
293
|
-
console.log(`<- ${c.symbol}\t${c.file ? `${rel(c.file)}:${c.line + 1}` : "(not indexed)"}`);
|
|
306
|
+
console.log(`<- ${c.symbol}\t${c.file ? `${rel(c.file)}:${c.line + 1}` : "(not indexed)"}${kindTag(c.edgeKind)}`);
|
|
294
307
|
}
|
|
295
308
|
if (resolvedCallers.length > MAX_CALLERS) {
|
|
296
309
|
console.log(`<- ... ${resolvedCallers.length - MAX_CALLERS} more`);
|
|
@@ -338,10 +351,10 @@ exports.peek = new commander_1.Command("peek")
|
|
|
338
351
|
console.log(style.bold(`callers (${resolvedCallers.length}):`));
|
|
339
352
|
for (const c of shown) {
|
|
340
353
|
if (c.file) {
|
|
341
|
-
console.log(` ${style.blue("\u2190")} ${style.green(c.symbol.padEnd(25))} ${style.dim(`${rel(c.file)}:${c.line + 1}`)}`);
|
|
354
|
+
console.log(` ${style.blue("\u2190")} ${style.green(c.symbol.padEnd(25))} ${style.dim(`${rel(c.file)}:${c.line + 1}`)}${style.dim(kindTag(c.edgeKind))}`);
|
|
342
355
|
}
|
|
343
356
|
else {
|
|
344
|
-
console.log(` ${style.blue("\u2190")} ${c.symbol.padEnd(25)} ${style.dim("(not indexed)")}`);
|
|
357
|
+
console.log(` ${style.blue("\u2190")} ${c.symbol.padEnd(25)} ${style.dim("(not indexed)")}${style.dim(kindTag(c.edgeKind))}`);
|
|
345
358
|
}
|
|
346
359
|
}
|
|
347
360
|
if (resolvedCallers.length > MAX_CALLERS) {
|
package/dist/commands/plugin.js
CHANGED
|
@@ -174,33 +174,40 @@ function installAll() {
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
// --- Subcommands ---
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
177
|
+
function installAction(clientArg) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
const clients = getClients();
|
|
180
|
+
const onlyId = clientArg && clientArg !== "all" ? clientArg : undefined;
|
|
181
|
+
if (onlyId) {
|
|
182
|
+
const client = clients.find((c) => c.id === onlyId);
|
|
183
|
+
if (!client) {
|
|
184
|
+
console.error(`Unknown client: ${onlyId}`);
|
|
185
|
+
console.error(`Available: ${clients.map((c) => c.id).join(", ")}`);
|
|
186
|
+
yield (0, exit_1.gracefulExit)(1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!client.detect()) {
|
|
190
|
+
console.error(`${client.name} not found on this system`);
|
|
191
|
+
yield (0, exit_1.gracefulExit)(1);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
yield client.install();
|
|
195
|
+
yield (0, exit_1.gracefulExit)();
|
|
194
196
|
return;
|
|
195
197
|
}
|
|
196
|
-
|
|
198
|
+
// Install all detected
|
|
199
|
+
yield installAll();
|
|
197
200
|
yield (0, exit_1.gracefulExit)();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const addCmd = new commander_1.Command("add")
|
|
204
|
+
.description("Install or update gmax plugins")
|
|
205
|
+
.argument("[client]", "Client to install (claude, opencode, codex, droid, all)")
|
|
206
|
+
.action(installAction);
|
|
207
|
+
const updateCmd = new commander_1.Command("update")
|
|
208
|
+
.description("Update gmax plugins")
|
|
209
|
+
.argument("[client]", "Client to update (claude, opencode, codex, droid, all)")
|
|
210
|
+
.action(installAction);
|
|
204
211
|
const removeCmd = new commander_1.Command("remove")
|
|
205
212
|
.description("Remove gmax plugins")
|
|
206
213
|
.argument("[client]", "Client to remove (claude, opencode, codex, droid, all)")
|
|
@@ -261,6 +268,8 @@ function statusAction() {
|
|
|
261
268
|
console.log("\nCommands:");
|
|
262
269
|
console.log(" gmax plugin add Install all detected clients");
|
|
263
270
|
console.log(" gmax plugin add <client> Install a specific client");
|
|
271
|
+
console.log(" gmax plugin update Update all detected clients");
|
|
272
|
+
console.log(" gmax plugin update <client> Update a specific client");
|
|
264
273
|
console.log(" gmax plugin remove Remove all installed plugins");
|
|
265
274
|
console.log(" gmax plugin remove <client> Remove a specific plugin");
|
|
266
275
|
yield (0, exit_1.gracefulExit)();
|
|
@@ -270,4 +279,5 @@ exports.plugin = new commander_1.Command("plugin")
|
|
|
270
279
|
.description("Manage gmax plugins for AI coding clients")
|
|
271
280
|
.action(statusAction)
|
|
272
281
|
.addCommand(addCmd)
|
|
282
|
+
.addCommand(updateCmd)
|
|
273
283
|
.addCommand(removeCmd);
|
|
@@ -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.runSearch = runSearch;
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
|
+
const config_1 = require("../config");
|
|
47
48
|
const grammar_loader_1 = require("../lib/index/grammar-loader");
|
|
48
49
|
const sync_helpers_1 = require("../lib/index/sync-helpers");
|
|
49
50
|
const syncer_1 = require("../lib/index/syncer");
|
|
@@ -188,6 +189,7 @@ function runSearch(params) {
|
|
|
188
189
|
lastIndexed: new Date().toISOString(),
|
|
189
190
|
chunkCount: result.indexed,
|
|
190
191
|
status: "indexed",
|
|
192
|
+
chunkerVersion: config_1.CONFIG.CHUNKER_VERSION,
|
|
191
193
|
});
|
|
192
194
|
const failedSuffix = result.failedFiles > 0 ? ` • ${result.failedFiles} failed` : "";
|
|
193
195
|
spinner.succeed(`${options.sync ? "Indexing" : "Initial indexing"} complete (${result.processed}/${result.total}) • indexed ${result.indexed}${failedSuffix}`);
|
package/dist/commands/search.js
CHANGED
|
@@ -144,7 +144,17 @@ Examples:
|
|
|
144
144
|
// Check for running server. The per-project HTTP server can't answer
|
|
145
145
|
// cross-project queries, so cross-project mode skips it and uses the
|
|
146
146
|
// daemon-mediated / in-process path (both query the shared table).
|
|
147
|
-
|
|
147
|
+
let resolvedOptionRoot;
|
|
148
|
+
if (options.root && !crossProject.active) {
|
|
149
|
+
resolvedOptionRoot = (0, project_registry_1.resolveRootOrExit)(options.root);
|
|
150
|
+
if (resolvedOptionRoot === null)
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const execPathForServer = resolvedOptionRoot
|
|
154
|
+
? resolvedOptionRoot
|
|
155
|
+
: exec_path
|
|
156
|
+
? path.resolve(exec_path)
|
|
157
|
+
: root;
|
|
148
158
|
const projectRootForServer = (_a = (0, project_root_1.findProjectRoot)(execPathForServer)) !== null && _a !== void 0 ? _a : execPathForServer;
|
|
149
159
|
const server = crossProject.active
|
|
150
160
|
? null
|
|
@@ -167,21 +177,21 @@ Examples:
|
|
|
167
177
|
try {
|
|
168
178
|
yield (0, setup_helpers_1.ensureSetup)();
|
|
169
179
|
const searchRoot = exec_path ? path.resolve(exec_path) : root;
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
// Propagate project root to worker processes
|
|
173
|
-
process.env.GMAX_PROJECT_ROOT = projectRoot;
|
|
174
|
-
// Check if project is registered
|
|
175
|
-
let checkRoot;
|
|
180
|
+
const cwdProjectRoot = (_b = (0, project_root_1.findProjectRoot)(searchRoot)) !== null && _b !== void 0 ? _b : searchRoot;
|
|
181
|
+
let checkRoot = cwdProjectRoot;
|
|
176
182
|
if (options.root) {
|
|
177
|
-
const resolved =
|
|
183
|
+
const resolved = resolvedOptionRoot !== undefined
|
|
184
|
+
? resolvedOptionRoot
|
|
185
|
+
: (0, project_registry_1.resolveRootOrExit)(options.root);
|
|
178
186
|
if (resolved === null)
|
|
179
187
|
return;
|
|
180
188
|
checkRoot = (_c = (0, project_root_1.findProjectRoot)(resolved)) !== null && _c !== void 0 ? _c : resolved;
|
|
181
189
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
190
|
+
const projectRoot = crossProject.active ? cwdProjectRoot : checkRoot;
|
|
191
|
+
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
192
|
+
// Propagate project root to worker processes
|
|
193
|
+
process.env.GMAX_PROJECT_ROOT = projectRoot;
|
|
194
|
+
// Check if project is registered
|
|
185
195
|
const project = (0, project_registry_1.getProject)(checkRoot);
|
|
186
196
|
if (!project) {
|
|
187
197
|
console.error(`This project hasn't been added to gmax yet.\n\nRun: gmax add ${checkRoot}\n`);
|
|
@@ -72,7 +72,7 @@ exports.testFind = new commander_1.Command("test")
|
|
|
72
72
|
(0, stale_hint_1.maybeWarnStaleEmbedding)(projectRoot, { agent: opts.agent });
|
|
73
73
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
74
74
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
75
|
-
const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
|
|
75
|
+
const { symbols, resolvedAsFile, symbolFamilies } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
|
|
76
76
|
if (symbols.length === 0) {
|
|
77
77
|
console.log(resolvedAsFile
|
|
78
78
|
? `No symbols found in file: ${target}`
|
|
@@ -89,7 +89,7 @@ exports.testFind = new commander_1.Command("test")
|
|
|
89
89
|
const queryRoot = opts.in && opts.in.length > 0
|
|
90
90
|
? scope.pathPrefix.replace(/\/$/, "")
|
|
91
91
|
: projectRoot;
|
|
92
|
-
const tests = yield (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes);
|
|
92
|
+
const tests = yield (0, impact_1.findTests)(symbols, vectorDb, queryRoot, depth, scope.excludePrefixes, symbolFamilies);
|
|
93
93
|
if (tests.length === 0) {
|
|
94
94
|
console.log(`No tests found for ${target}.`);
|
|
95
95
|
return;
|
package/dist/commands/trace.js
CHANGED
|
@@ -56,6 +56,10 @@ const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
|
56
56
|
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
57
57
|
const dim = (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s);
|
|
58
58
|
const bold = (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s);
|
|
59
|
+
// Inferred-edge marker for caller rows: `member` = receiver-unverified `x.T()`,
|
|
60
|
+
// `type` = a type-position reference (not a call). Free calls stay unmarked —
|
|
61
|
+
// the trustworthy default — so only guesses carry a tag.
|
|
62
|
+
const kindTag = (k) => k === "member" || k === "type" ? ` (${k})` : "";
|
|
59
63
|
function formatTraceAgent(graph, projectRoot, raw = false) {
|
|
60
64
|
if (!graph.center)
|
|
61
65
|
return "(not found)";
|
|
@@ -71,7 +75,7 @@ function formatTraceAgent(graph, projectRoot, raw = false) {
|
|
|
71
75
|
function walkCallers(tree, depth) {
|
|
72
76
|
if (raw) {
|
|
73
77
|
for (const t of tree) {
|
|
74
|
-
lines.push(`${" ".repeat(depth)}<- ${t.node.symbol}\t${rel(t.node.file)}:${t.node.line}`);
|
|
78
|
+
lines.push(`${" ".repeat(depth)}<- ${t.node.symbol}\t${rel(t.node.file)}:${t.node.line}${kindTag(t.node.edgeKind)}`);
|
|
75
79
|
walkCallers(t.callers, depth + 1);
|
|
76
80
|
}
|
|
77
81
|
return;
|
|
@@ -92,6 +96,9 @@ function formatTraceAgent(graph, projectRoot, raw = false) {
|
|
|
92
96
|
file: t.node.file,
|
|
93
97
|
lineSet: new Set(),
|
|
94
98
|
sub: [],
|
|
99
|
+
// Callers arrive free-first (getCallers sorts), so the first edgeKind
|
|
100
|
+
// seen for this group is its highest-confidence one.
|
|
101
|
+
edgeKind: t.node.edgeKind,
|
|
95
102
|
};
|
|
96
103
|
groups.set(key, g);
|
|
97
104
|
order.push(key);
|
|
@@ -103,7 +110,7 @@ function formatTraceAgent(graph, projectRoot, raw = false) {
|
|
|
103
110
|
const g = groups.get(key);
|
|
104
111
|
const locs = [...g.lineSet].sort((a, b) => a - b).join(",");
|
|
105
112
|
const loc = g.file ? `${rel(g.file)}:${locs}` : "(not indexed)";
|
|
106
|
-
lines.push(`${" ".repeat(depth)}<- ${g.symbol}\t${loc}`);
|
|
113
|
+
lines.push(`${" ".repeat(depth)}<- ${g.symbol}\t${loc}${kindTag(g.edgeKind)}`);
|
|
107
114
|
walkCallers(g.sub, depth + 1);
|
|
108
115
|
}
|
|
109
116
|
}
|
|
@@ -139,6 +146,7 @@ function buildInboundTree(callerTree, targetSymbol, fileCache, withSnippets, lim
|
|
|
139
146
|
line: t.node.line,
|
|
140
147
|
snippet: (_b = snippet === null || snippet === void 0 ? void 0 : snippet.snippet) !== null && _b !== void 0 ? _b : null,
|
|
141
148
|
snippetLine: (_c = snippet === null || snippet === void 0 ? void 0 : snippet.snippetLine) !== null && _c !== void 0 ? _c : null,
|
|
149
|
+
edgeKind: t.node.edgeKind,
|
|
142
150
|
callers: buildInboundTree(t.callers, t.node.symbol, fileCache, withSnippets, limit),
|
|
143
151
|
});
|
|
144
152
|
if (out.length >= limit)
|
|
@@ -158,8 +166,8 @@ function formatInboundAgent(center, tree, projectRoot, withSnippets) {
|
|
|
158
166
|
? `${rel(n.file)}:${((_a = n.snippetLine) !== null && _a !== void 0 ? _a : n.line) + 1}`
|
|
159
167
|
: "(not indexed)";
|
|
160
168
|
const cols = withSnippets
|
|
161
|
-
? `${loc}\t${n.symbol}\t${(_b = n.snippet) !== null && _b !== void 0 ? _b : ""}`
|
|
162
|
-
: `${loc}\t${n.symbol}`;
|
|
169
|
+
? `${loc}\t${n.symbol}${kindTag(n.edgeKind)}\t${(_b = n.snippet) !== null && _b !== void 0 ? _b : ""}`
|
|
170
|
+
: `${loc}\t${n.symbol}${kindTag(n.edgeKind)}`;
|
|
163
171
|
lines.push(`${prefix}${cols}`);
|
|
164
172
|
walk(n.callers, depth + 1);
|
|
165
173
|
}
|
|
@@ -194,7 +202,7 @@ function formatInboundHuman(center, tree, projectRoot, withSnippets) {
|
|
|
194
202
|
const loc = n.file
|
|
195
203
|
? `${rel(n.file)}:${((_a = n.snippetLine) !== null && _a !== void 0 ? _a : n.line) + 1}`
|
|
196
204
|
: "(not indexed)";
|
|
197
|
-
lines.push(`${indent}${n.symbol} ${dim(loc)}`);
|
|
205
|
+
lines.push(`${indent}${n.symbol} ${dim(loc)}${dim(kindTag(n.edgeKind))}`);
|
|
198
206
|
if (withSnippets && n.snippet) {
|
|
199
207
|
lines.push(`${indent} ${dim(n.snippet)}`);
|
|
200
208
|
}
|
package/dist/config.js
CHANGED
|
@@ -84,7 +84,7 @@ exports.CONFIG = {
|
|
|
84
84
|
// reindex to take effect. Must equal the latest entry's `v` in
|
|
85
85
|
// CHUNKER_VERSION_HISTORY below — see that list for per-version severity and
|
|
86
86
|
// the user-facing note rendered by `gmax doctor` and the staleness hint.
|
|
87
|
-
CHUNKER_VERSION:
|
|
87
|
+
CHUNKER_VERSION: 4,
|
|
88
88
|
};
|
|
89
89
|
/**
|
|
90
90
|
* Per-version record of what changed in the chunker and how much it matters to
|
|
@@ -105,6 +105,11 @@ exports.CHUNKER_VERSION_HISTORY = [
|
|
|
105
105
|
severity: "additive",
|
|
106
106
|
note: "type-position edges; dead/trace miss type-only callers until reindex.",
|
|
107
107
|
},
|
|
108
|
+
{
|
|
109
|
+
v: 4,
|
|
110
|
+
severity: "additive",
|
|
111
|
+
note: "member-call edges recorded separately (obj.method()); substrate for receiver-aware call resolution, no query effect until reindex.",
|
|
112
|
+
},
|
|
108
113
|
];
|
|
109
114
|
/**
|
|
110
115
|
* Describe the gap between an index's stamped chunker version and the current
|
|
@@ -147,22 +147,66 @@ class MlxServerManager {
|
|
|
147
147
|
const env = Object.assign({}, process.env);
|
|
148
148
|
if (mlxModel)
|
|
149
149
|
env.MLX_EMBED_MODEL = mlxModel;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
const closeLogFd = () => {
|
|
151
|
+
try {
|
|
152
|
+
fs.closeSync(logFd);
|
|
153
|
+
}
|
|
154
|
+
catch (_a) { }
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
this.mlxChild = (0, node_child_process_1.spawn)("uv", ["run", "python", "server.py"], {
|
|
158
|
+
cwd: serverDir,
|
|
159
|
+
detached: true,
|
|
160
|
+
stdio: ["ignore", logFd, logFd],
|
|
161
|
+
env,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
closeLogFd();
|
|
166
|
+
console.error(`[daemon] MLX embed server failed to spawn: ${err instanceof Error ? err.message : String(err)} — falling back to CPU embeddings`);
|
|
167
|
+
this.mlxChild = null;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const child = this.mlxChild;
|
|
171
|
+
let startupSettled = false;
|
|
172
|
+
let resolveStartupError;
|
|
173
|
+
const startupError = new Promise((resolve) => {
|
|
174
|
+
resolveStartupError = resolve;
|
|
155
175
|
});
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
const onChildError = (err) => {
|
|
177
|
+
if (!startupSettled) {
|
|
178
|
+
resolveStartupError(err);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
console.error(`[daemon] MLX embed server process error: ${err.message}`);
|
|
182
|
+
if (this.mlxChild === child)
|
|
183
|
+
this.mlxChild = null;
|
|
184
|
+
};
|
|
185
|
+
child.on("error", onChildError);
|
|
186
|
+
child.unref();
|
|
187
|
+
console.log(`[daemon] Starting MLX embed server (PID: ${child.pid})`);
|
|
158
188
|
// Poll for readiness (up to 30s)
|
|
159
189
|
for (let i = 0; i < 30; i++) {
|
|
160
|
-
|
|
190
|
+
const spawnError = yield Promise.race([
|
|
191
|
+
startupError,
|
|
192
|
+
new Promise((r) => setTimeout(() => r(null), 1000)),
|
|
193
|
+
]);
|
|
194
|
+
if (spawnError) {
|
|
195
|
+
startupSettled = true;
|
|
196
|
+
child.off("error", onChildError);
|
|
197
|
+
closeLogFd();
|
|
198
|
+
console.error(`[daemon] MLX embed server failed to spawn: ${spawnError.message} — falling back to CPU embeddings`);
|
|
199
|
+
if (this.mlxChild === child)
|
|
200
|
+
this.mlxChild = null;
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
161
203
|
if (yield this.isMlxServerUp()) {
|
|
204
|
+
startupSettled = true;
|
|
162
205
|
console.log("[daemon] MLX embed server ready");
|
|
163
206
|
return;
|
|
164
207
|
}
|
|
165
208
|
}
|
|
209
|
+
startupSettled = true;
|
|
166
210
|
console.error("[daemon] MLX embed server failed to start within 30s — falling back to CPU embeddings");
|
|
167
211
|
this.mlxChild = null;
|
|
168
212
|
});
|
|
@@ -84,7 +84,7 @@ class ProcessManager {
|
|
|
84
84
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
85
|
// 1. Check for other daemon processes
|
|
86
86
|
const daemonPids = this.findProcessesByTitle("gmax-daemon").filter((pid) => pid !== process.pid);
|
|
87
|
-
|
|
87
|
+
let workerPids = this.findProcessesByTitle("gmax-worker");
|
|
88
88
|
if (daemonPids.length === 0 && workerPids.length === 0) {
|
|
89
89
|
(0, logger_1.log)("daemon", "No stale processes found");
|
|
90
90
|
return;
|
|
@@ -92,15 +92,21 @@ class ProcessManager {
|
|
|
92
92
|
// A peer that's gracefully shutting down has already dropped its
|
|
93
93
|
// socket/PID/lock (so the liveness probes below read "stale") yet is still
|
|
94
94
|
// mid-cleanup — destroying workers, closing LanceDB. Killing it now corrupts
|
|
95
|
-
// that teardown. Defer to its draining marker
|
|
96
|
-
//
|
|
97
|
-
let anyDraining = false;
|
|
95
|
+
// that teardown. Defer to its draining marker until the process exits; only
|
|
96
|
+
// after the marker grace elapses do we treat it like any other stale daemon.
|
|
98
97
|
for (const pid of daemonPids) {
|
|
99
98
|
(0, logger_1.log)("daemon", `found daemon PID:${pid}, checking liveness...`);
|
|
100
99
|
if ((0, daemon_client_1.isDaemonDraining)(pid)) {
|
|
101
|
-
|
|
102
|
-
(0,
|
|
103
|
-
|
|
100
|
+
(0, logger_1.log)("daemon", `daemon PID:${pid} is gracefully draining — waiting for it to exit`);
|
|
101
|
+
const exited = yield (0, daemon_client_1.waitForDaemonDrain)(pid);
|
|
102
|
+
if (exited) {
|
|
103
|
+
(0, logger_1.log)("daemon", `daemon PID:${pid} finished draining`);
|
|
104
|
+
// The peer may have reaped its workers while we waited. Rescan before
|
|
105
|
+
// sweeping so we don't act on a stale process snapshot.
|
|
106
|
+
workerPids = this.findProcessesByTitle("gmax-worker");
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
(0, logger_1.log)("daemon", `daemon PID:${pid} still exists after drain grace — checking stale probes`);
|
|
104
110
|
}
|
|
105
111
|
// A busy daemon (mid-index, compaction, big LMDB write) can block the
|
|
106
112
|
// event loop long enough to miss a ping. Two independent liveness
|
|
@@ -120,16 +126,9 @@ class ProcessManager {
|
|
|
120
126
|
}
|
|
121
127
|
// 2. Kill orphaned workers from previous daemon instances.
|
|
122
128
|
// Safe because this runs before the new daemon's worker pool is initialized.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
(0, logger_1.log)("daemon", "skipping orphan-worker sweep — a peer is draining its own workers");
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
for (const pid of workerPids) {
|
|
130
|
-
(0, logger_1.log)("daemon", `killing orphaned worker PID:${pid}`);
|
|
131
|
-
yield (0, process_1.killProcess)(pid);
|
|
132
|
-
}
|
|
129
|
+
for (const pid of workerPids) {
|
|
130
|
+
(0, logger_1.log)("daemon", `killing orphaned worker PID:${pid}`);
|
|
131
|
+
yield (0, process_1.killProcess)(pid);
|
|
133
132
|
}
|
|
134
133
|
(0, logger_1.log)("daemon", `Cleaned up ${daemonPids.length} stale daemon(s), ${workerPids.length} orphaned worker(s)`);
|
|
135
134
|
});
|