grepmax 0.17.22 → 0.17.24
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/dist/commands/add.js +3 -2
- package/dist/commands/audit.js +29 -4
- package/dist/commands/context.js +34 -2
- package/dist/commands/dead.js +2 -0
- package/dist/commands/doctor.js +70 -3
- package/dist/commands/impact.js +11 -8
- package/dist/commands/index.js +16 -6
- package/dist/commands/mcp.js +2 -0
- package/dist/commands/peek.js +96 -17
- package/dist/commands/project.js +17 -4
- package/dist/commands/related.js +7 -3
- package/dist/commands/search.js +5 -0
- package/dist/commands/similar.js +2 -0
- package/dist/commands/test-find.js +10 -14
- package/dist/commands/trace.js +4 -29
- package/dist/config.js +47 -1
- package/dist/eval-graph-nav.js +374 -0
- package/dist/eval-graph-sanity.js +138 -19
- package/dist/eval-like-limit-probe.js +62 -0
- package/dist/lib/daemon/daemon.js +1 -1
- package/dist/lib/graph/callsites.js +148 -0
- package/dist/lib/graph/graph-builder.js +37 -11
- package/dist/lib/graph/impact.js +29 -14
- package/dist/lib/graph/test-hits.js +54 -0
- package/dist/lib/help/agent-cheatsheet.js +5 -0
- package/dist/lib/index/chunker.js +185 -6
- package/dist/lib/llm/tools.js +58 -16
- package/dist/lib/output/agent-search-formatter.js +59 -3
- package/dist/lib/search/searcher.js +2 -1
- package/dist/lib/store/vector-db.js +43 -13
- package/dist/lib/utils/query-timeout.js +52 -0
- package/dist/lib/utils/stale-hint.js +62 -0
- package/dist/lib/workers/orchestrator.js +2 -1
- package/package.json +18 -4
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
package/dist/commands/peek.js
CHANGED
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.peek = void 0;
|
|
46
46
|
const fs = __importStar(require("node:fs"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
|
+
const callsites_1 = require("../lib/graph/callsites");
|
|
48
49
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
49
50
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
50
51
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
@@ -52,6 +53,7 @@ const exit_1 = require("../lib/utils/exit");
|
|
|
52
53
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
53
54
|
const language_1 = require("../lib/utils/language");
|
|
54
55
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
56
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
55
57
|
const project_root_1 = require("../lib/utils/project-root");
|
|
56
58
|
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
57
59
|
const style = {
|
|
@@ -69,26 +71,45 @@ function extractSignature(filePath, startLine, endLine) {
|
|
|
69
71
|
const lines = content.split("\n");
|
|
70
72
|
const chunk = lines.slice(startLine, endLine + 1);
|
|
71
73
|
const bodyLines = chunk.length;
|
|
72
|
-
// Find the signature: everything up to and including the opening brace
|
|
74
|
+
// Find the signature: everything up to and including the opening brace.
|
|
75
|
+
// Only treat `{` / `=>` as the body boundary once the parameter list's
|
|
76
|
+
// parens are balanced — object-literal param types (`cached: { … }`)
|
|
77
|
+
// contain braces mid-signature and must not end it.
|
|
73
78
|
const sigLines = [];
|
|
79
|
+
let parenDepth = 0;
|
|
74
80
|
for (const line of chunk) {
|
|
75
81
|
sigLines.push(line);
|
|
76
|
-
|
|
82
|
+
for (const ch of line) {
|
|
83
|
+
if (ch === "(")
|
|
84
|
+
parenDepth++;
|
|
85
|
+
else if (ch === ")")
|
|
86
|
+
parenDepth--;
|
|
87
|
+
}
|
|
88
|
+
if (parenDepth <= 0 && (line.includes("{") || line.includes("=>"))) {
|
|
77
89
|
break;
|
|
90
|
+
}
|
|
91
|
+
if (sigLines.length >= 12)
|
|
92
|
+
break; // degenerate input — bail
|
|
78
93
|
}
|
|
79
94
|
// If we only got one line and it's the whole function, collapse it
|
|
80
95
|
if (sigLines.length >= bodyLines) {
|
|
81
|
-
|
|
96
|
+
const whole = chunk.join("\n");
|
|
97
|
+
return { signature: whole, signatureOnly: whole, bodyLines: 0 };
|
|
82
98
|
}
|
|
83
99
|
const sig = sigLines.join("\n");
|
|
84
100
|
const remaining = bodyLines - sigLines.length;
|
|
85
101
|
return {
|
|
86
102
|
signature: `${sig}\n // ... (${remaining} lines)\n }`,
|
|
103
|
+
signatureOnly: sig,
|
|
87
104
|
bodyLines,
|
|
88
105
|
};
|
|
89
106
|
}
|
|
90
107
|
catch (_a) {
|
|
91
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
signature: "(source not available)",
|
|
110
|
+
signatureOnly: "(source not available)",
|
|
111
|
+
bodyLines: 0,
|
|
112
|
+
};
|
|
92
113
|
}
|
|
93
114
|
}
|
|
94
115
|
exports.peek = new commander_1.Command("peek")
|
|
@@ -109,6 +130,7 @@ exports.peek = new commander_1.Command("peek")
|
|
|
109
130
|
const depth = Math.min(Math.max(Number.parseInt(opts.depth || "1", 10), 1), 3);
|
|
110
131
|
try {
|
|
111
132
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
133
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
112
134
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
113
135
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
114
136
|
const { resolveScope, buildScopeWhere } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
|
@@ -122,6 +144,9 @@ exports.peek = new commander_1.Command("peek")
|
|
|
122
144
|
// languages, refuse to silently pick one. The graph builder otherwise
|
|
123
145
|
// picks one chunk arbitrarily and lists callers from a different
|
|
124
146
|
// language — verified failure mode.
|
|
147
|
+
// Same-language multi-definition is reported as a note instead (below):
|
|
148
|
+
// the first definition still wins, but the agent learns it guessed.
|
|
149
|
+
let otherDefs = [];
|
|
125
150
|
{
|
|
126
151
|
const tableForCheck = yield vectorDb.ensureTable();
|
|
127
152
|
const allDefs = yield tableForCheck
|
|
@@ -134,6 +159,14 @@ exports.peek = new commander_1.Command("peek")
|
|
|
134
159
|
path: String(row.path || ""),
|
|
135
160
|
startLine: Number(row.start_line || 0),
|
|
136
161
|
}));
|
|
162
|
+
// Dedupe by file: split sub-chunks of one definition share a path,
|
|
163
|
+
// while genuine ambiguity (same name defined elsewhere) crosses files.
|
|
164
|
+
const distinct = new Map();
|
|
165
|
+
for (const c of chunks) {
|
|
166
|
+
if (!distinct.has(c.path))
|
|
167
|
+
distinct.set(c.path, c);
|
|
168
|
+
}
|
|
169
|
+
otherDefs = [...distinct.values()];
|
|
137
170
|
const byLang = (0, language_1.groupByLanguage)(chunks);
|
|
138
171
|
if (byLang.size >= 2) {
|
|
139
172
|
const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
|
|
@@ -198,7 +231,30 @@ exports.peek = new commander_1.Command("peek")
|
|
|
198
231
|
line: c.line,
|
|
199
232
|
}));
|
|
200
233
|
}
|
|
201
|
-
|
|
234
|
+
// Re-anchor chunk-level caller rows to actual call sites and dedupe —
|
|
235
|
+
// getCallers() returns one row per chunk, which multiplies callers for
|
|
236
|
+
// classes split across many chunks (verified: 3 real call sites → 66).
|
|
237
|
+
const resolvedCallers = (0, callsites_1.resolveCallSites)(callerList, symbol).map((c) => {
|
|
238
|
+
var _a;
|
|
239
|
+
return ({
|
|
240
|
+
symbol: c.symbol,
|
|
241
|
+
file: c.file,
|
|
242
|
+
line: (_a = c.snippetLine) !== null && _a !== void 0 ? _a : c.line,
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
// Builtins listed as "(not indexed)" callees (trunc, now, filter, …)
|
|
246
|
+
// are noise; project symbols always resolve so they're unaffected.
|
|
247
|
+
// Dedupe by symbol — repeated references arrive once per chunk.
|
|
248
|
+
const seenCallees = new Set();
|
|
249
|
+
const calleeList = graph.callees
|
|
250
|
+
.filter((c) => c.file || !(0, callsites_1.isBuiltinCallee)(c.symbol))
|
|
251
|
+
.filter((c) => {
|
|
252
|
+
if (seenCallees.has(c.symbol))
|
|
253
|
+
return false;
|
|
254
|
+
seenCallees.add(c.symbol);
|
|
255
|
+
return true;
|
|
256
|
+
})
|
|
257
|
+
.map((c) => ({
|
|
202
258
|
symbol: c.symbol,
|
|
203
259
|
file: c.file,
|
|
204
260
|
line: c.line,
|
|
@@ -207,16 +263,30 @@ exports.peek = new commander_1.Command("peek")
|
|
|
207
263
|
// Compact TSV output
|
|
208
264
|
const exportedStr = exported ? "exported" : "";
|
|
209
265
|
console.log(`${center.symbol}\t${rel(center.file)}:${center.line + 1}\t${center.role}\t${exportedStr}`);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
266
|
+
if (otherDefs.length > 1) {
|
|
267
|
+
const others = otherDefs
|
|
268
|
+
.filter((d) => d.path !== center.file)
|
|
269
|
+
.slice(0, 4)
|
|
270
|
+
.map((d) => `${rel(d.path)}:${d.startLine + 1}`);
|
|
271
|
+
if (others.length > 0) {
|
|
272
|
+
console.log(`also-defined: ${others.join(", ")} — showing the first; pin with --in <subpath>`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Signature — all lines up to the opening brace, collapsed to one
|
|
276
|
+
// line so parameters survive (first-line-only loses them).
|
|
277
|
+
const { signatureOnly } = extractSignature(center.file, startLine, endLine);
|
|
278
|
+
const sigOnly = signatureOnly
|
|
279
|
+
.split("\n")
|
|
280
|
+
.map((l) => l.trim())
|
|
281
|
+
.join(" ")
|
|
282
|
+
.replace(/\s+/g, " ");
|
|
283
|
+
console.log(`sig: ${sigOnly}`);
|
|
214
284
|
// Callers
|
|
215
|
-
for (const c of
|
|
285
|
+
for (const c of resolvedCallers.slice(0, MAX_CALLERS)) {
|
|
216
286
|
console.log(`<- ${c.symbol}\t${c.file ? `${rel(c.file)}:${c.line + 1}` : "(not indexed)"}`);
|
|
217
287
|
}
|
|
218
|
-
if (
|
|
219
|
-
console.log(`<- ... ${
|
|
288
|
+
if (resolvedCallers.length > MAX_CALLERS) {
|
|
289
|
+
console.log(`<- ... ${resolvedCallers.length - MAX_CALLERS} more`);
|
|
220
290
|
}
|
|
221
291
|
// Callees
|
|
222
292
|
for (const c of calleeList.slice(0, MAX_CALLEES)) {
|
|
@@ -239,6 +309,15 @@ exports.peek = new commander_1.Command("peek")
|
|
|
239
309
|
// Rich output
|
|
240
310
|
const exportedStr = exported ? ", exported" : "";
|
|
241
311
|
console.log(`${style.bold(`peek: ${center.symbol}`)} ${style.dim(`${rel(center.file)}:${center.line + 1}`)} ${style.dim(`[${center.role}${exportedStr}]`)}`);
|
|
312
|
+
if (otherDefs.length > 1) {
|
|
313
|
+
const others = otherDefs
|
|
314
|
+
.filter((d) => d.path !== center.file)
|
|
315
|
+
.slice(0, 4)
|
|
316
|
+
.map((d) => `${rel(d.path)}:${d.startLine + 1}`);
|
|
317
|
+
if (others.length > 0) {
|
|
318
|
+
console.log(style.dim(` also defined in: ${others.join(", ")} — showing the first; pin with --in <subpath>`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
242
321
|
console.log();
|
|
243
322
|
// Signature with collapsed body
|
|
244
323
|
const { signature } = extractSignature(center.file, startLine, endLine);
|
|
@@ -247,9 +326,9 @@ exports.peek = new commander_1.Command("peek")
|
|
|
247
326
|
}
|
|
248
327
|
console.log();
|
|
249
328
|
// Callers
|
|
250
|
-
if (
|
|
251
|
-
const shown =
|
|
252
|
-
console.log(style.bold(`callers (${
|
|
329
|
+
if (resolvedCallers.length > 0) {
|
|
330
|
+
const shown = resolvedCallers.slice(0, MAX_CALLERS);
|
|
331
|
+
console.log(style.bold(`callers (${resolvedCallers.length}):`));
|
|
253
332
|
for (const c of shown) {
|
|
254
333
|
if (c.file) {
|
|
255
334
|
console.log(` ${style.blue("\u2190")} ${style.green(c.symbol.padEnd(25))} ${style.dim(`${rel(c.file)}:${c.line + 1}`)}`);
|
|
@@ -258,8 +337,8 @@ exports.peek = new commander_1.Command("peek")
|
|
|
258
337
|
console.log(` ${style.blue("\u2190")} ${c.symbol.padEnd(25)} ${style.dim("(not indexed)")}`);
|
|
259
338
|
}
|
|
260
339
|
}
|
|
261
|
-
if (
|
|
262
|
-
console.log(style.dim(` ... and ${
|
|
340
|
+
if (resolvedCallers.length > MAX_CALLERS) {
|
|
341
|
+
console.log(style.dim(` ... and ${resolvedCallers.length - MAX_CALLERS} more`));
|
|
263
342
|
}
|
|
264
343
|
}
|
|
265
344
|
else {
|
package/dist/commands/project.js
CHANGED
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.project = void 0;
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
47
|
const commander_1 = require("commander");
|
|
48
|
+
const callsites_1 = require("../lib/graph/callsites");
|
|
48
49
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
49
50
|
const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
50
51
|
const exit_1 = require("../lib/utils/exit");
|
|
@@ -91,7 +92,9 @@ exports.project = new commander_1.Command("project")
|
|
|
91
92
|
const dirCounts = new Map();
|
|
92
93
|
const roleCounts = new Map();
|
|
93
94
|
const symbolRefs = new Map();
|
|
95
|
+
const definedInProject = new Set();
|
|
94
96
|
const entryPoints = [];
|
|
97
|
+
const seenEntryPoints = new Set();
|
|
95
98
|
for (const row of rows) {
|
|
96
99
|
const p = String(row.path || "");
|
|
97
100
|
const role = String(row.role || "IMPLEMENTATION");
|
|
@@ -115,13 +118,19 @@ exports.project = new commander_1.Command("project")
|
|
|
115
118
|
dc.files.add(p);
|
|
116
119
|
dc.chunks++;
|
|
117
120
|
roleCounts.set(role, (roleCounts.get(role) || 0) + 1);
|
|
121
|
+
for (const d of defs)
|
|
122
|
+
definedInProject.add(d);
|
|
118
123
|
for (const ref of refs)
|
|
119
124
|
symbolRefs.set(ref, (symbolRefs.get(ref) || 0) + 1);
|
|
120
125
|
if (exported && role === "ORCHESTRATION" && complexity >= 5 && defs.length > 0) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
const epKey = `${defs[0]}:${p}`;
|
|
127
|
+
if (!seenEntryPoints.has(epKey)) {
|
|
128
|
+
seenEntryPoints.add(epKey);
|
|
129
|
+
entryPoints.push({
|
|
130
|
+
symbol: defs[0],
|
|
131
|
+
path: p.startsWith(prefix) ? p.slice(prefix.length) : p,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
const projects = (0, project_registry_1.listProjects)();
|
|
@@ -129,7 +138,11 @@ exports.project = new commander_1.Command("project")
|
|
|
129
138
|
const extEntries = Array.from(extCounts.entries())
|
|
130
139
|
.sort((a, b) => b[1] - a[1])
|
|
131
140
|
.slice(0, 8);
|
|
141
|
+
// Key symbols must be the project's own: raw referenced_symbols counts
|
|
142
|
+
// are dominated by JS builtins (push, slice, map, …) which say nothing
|
|
143
|
+
// about the codebase.
|
|
132
144
|
const topSymbols = Array.from(symbolRefs.entries())
|
|
145
|
+
.filter(([s]) => definedInProject.has(s) && !(0, callsites_1.isBuiltinCallee)(s))
|
|
133
146
|
.sort((a, b) => b[1] - a[1])
|
|
134
147
|
.slice(0, 8);
|
|
135
148
|
if (opts.agent) {
|
package/dist/commands/related.js
CHANGED
|
@@ -51,6 +51,8 @@ const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
|
51
51
|
const exit_1 = require("../lib/utils/exit");
|
|
52
52
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
53
53
|
const project_root_1 = require("../lib/utils/project-root");
|
|
54
|
+
const query_timeout_1 = require("../lib/utils/query-timeout");
|
|
55
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
54
56
|
const arrow_1 = require("../lib/utils/arrow");
|
|
55
57
|
exports.related = new commander_1.Command("related")
|
|
56
58
|
.description("Find files related by shared symbol references")
|
|
@@ -69,6 +71,7 @@ exports.related = new commander_1.Command("related")
|
|
|
69
71
|
if (root === null)
|
|
70
72
|
return;
|
|
71
73
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
74
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
72
75
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
73
76
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
74
77
|
const absPath = path.resolve(projectRoot, file);
|
|
@@ -164,12 +167,13 @@ exports.related = new commander_1.Command("related")
|
|
|
164
167
|
basenameRejected = true;
|
|
165
168
|
}
|
|
166
169
|
else {
|
|
167
|
-
|
|
170
|
+
// No .limit() here: LIKE + limit deadlocks in @lancedb 0.27.x when
|
|
171
|
+
// more rows match than the limit (verified). The loop below caps.
|
|
172
|
+
const rows = yield (0, query_timeout_1.withQueryTimeout)(table
|
|
168
173
|
.query()
|
|
169
174
|
.select(["path"])
|
|
170
175
|
.where(`content LIKE '%${(0, filter_builder_1.escapeSqlString)(basename)}%' AND ${pathScope}`)
|
|
171
|
-
.
|
|
172
|
-
.toArray();
|
|
176
|
+
.toArray(), `content LIKE %${basename}% (related mentions)`);
|
|
173
177
|
const seen = new Set();
|
|
174
178
|
for (const row of rows) {
|
|
175
179
|
const p = String(row.path || "");
|
package/dist/commands/search.js
CHANGED
|
@@ -56,6 +56,7 @@ const import_extractor_1 = require("../lib/utils/import-extractor");
|
|
|
56
56
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
57
57
|
const project_root_1 = require("../lib/utils/project-root");
|
|
58
58
|
const server_registry_1 = require("../lib/utils/server-registry");
|
|
59
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
59
60
|
const search_run_1 = require("./search-run");
|
|
60
61
|
const search_skeletons_1 = require("./search-skeletons");
|
|
61
62
|
exports.search = new commander_1.Command("search")
|
|
@@ -217,6 +218,7 @@ Examples:
|
|
|
217
218
|
};
|
|
218
219
|
console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, projectRootForServer, {
|
|
219
220
|
includeImports: options.imports,
|
|
221
|
+
query: pattern,
|
|
220
222
|
getImportsForFile,
|
|
221
223
|
explain: options.explain,
|
|
222
224
|
}));
|
|
@@ -284,6 +286,7 @@ Examples:
|
|
|
284
286
|
if (project.status === "pending") {
|
|
285
287
|
console.warn("This project is still being indexed. Results may be incomplete.\n");
|
|
286
288
|
}
|
|
289
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(checkRoot, { agent: options.agent });
|
|
287
290
|
// Compute effective paths + filters early — both the daemon-mediated
|
|
288
291
|
// and in-process search paths need them. Reuse the resolved checkRoot
|
|
289
292
|
// so --root <name> only resolves once per invocation.
|
|
@@ -444,6 +447,7 @@ Examples:
|
|
|
444
447
|
if (options.agent) {
|
|
445
448
|
body = (0, agent_search_formatter_1.formatAgentSearchResults)(g.items, g.root, {
|
|
446
449
|
includeImports: options.imports,
|
|
450
|
+
query: pattern,
|
|
447
451
|
getImportsForFile,
|
|
448
452
|
explain: options.explain,
|
|
449
453
|
});
|
|
@@ -487,6 +491,7 @@ Examples:
|
|
|
487
491
|
else {
|
|
488
492
|
console.log((0, agent_search_formatter_1.formatAgentSearchResults)(filteredData, effectiveRoot, {
|
|
489
493
|
includeImports: options.imports,
|
|
494
|
+
query: pattern,
|
|
490
495
|
getImportsForFile,
|
|
491
496
|
explain: options.explain,
|
|
492
497
|
}));
|
package/dist/commands/similar.js
CHANGED
|
@@ -51,6 +51,7 @@ const filter_builder_1 = require("../lib/utils/filter-builder");
|
|
|
51
51
|
const exit_1 = require("../lib/utils/exit");
|
|
52
52
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
53
53
|
const project_root_1 = require("../lib/utils/project-root");
|
|
54
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
54
55
|
const arrow_1 = require("../lib/utils/arrow");
|
|
55
56
|
exports.similar = new commander_1.Command("similar")
|
|
56
57
|
.description("Find semantically similar code to a symbol or file")
|
|
@@ -71,6 +72,7 @@ exports.similar = new commander_1.Command("similar")
|
|
|
71
72
|
if (root === null)
|
|
72
73
|
return;
|
|
73
74
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
75
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
74
76
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
75
77
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
76
78
|
const table = yield vectorDb.ensureTable();
|
|
@@ -45,10 +45,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.testFind = void 0;
|
|
46
46
|
const commander_1 = require("commander");
|
|
47
47
|
const impact_1 = require("../lib/graph/impact");
|
|
48
|
+
const test_hits_1 = require("../lib/graph/test-hits");
|
|
48
49
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
49
50
|
const exit_1 = require("../lib/utils/exit");
|
|
50
51
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
51
52
|
const project_root_1 = require("../lib/utils/project-root");
|
|
53
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
52
54
|
exports.testFind = new commander_1.Command("test")
|
|
53
55
|
.description("Find tests that exercise a symbol or file")
|
|
54
56
|
.argument("<target>", "Symbol name or file path")
|
|
@@ -66,6 +68,7 @@ exports.testFind = new commander_1.Command("test")
|
|
|
66
68
|
if (root === null)
|
|
67
69
|
return;
|
|
68
70
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
71
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
69
72
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
70
73
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
71
74
|
const { symbols, resolvedAsFile } = yield (0, impact_1.resolveTargetSymbols)(target, vectorDb, projectRoot);
|
|
@@ -91,25 +94,18 @@ exports.testFind = new commander_1.Command("test")
|
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
93
96
|
const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
|
|
97
|
+
// One line per test file: the file is what the reader runs; caller
|
|
98
|
+
// symbols inside it (often internal helpers) are detail, not the lead.
|
|
99
|
+
const grouped = (0, test_hits_1.groupTestHitsByFile)(tests);
|
|
94
100
|
if (opts.agent) {
|
|
95
|
-
for (const t of
|
|
96
|
-
|
|
97
|
-
? "via-import"
|
|
98
|
-
: t.hops === 0
|
|
99
|
-
? "direct"
|
|
100
|
-
: `${t.hops}-hop`;
|
|
101
|
-
console.log(`${rel(t.file)}:${t.line + 1}\t${t.symbol}\t${hopLabel}`);
|
|
101
|
+
for (const t of grouped) {
|
|
102
|
+
console.log(`${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
else {
|
|
105
106
|
console.log(`Tests for ${target}:\n`);
|
|
106
|
-
for (const t of
|
|
107
|
-
|
|
108
|
-
? "via import"
|
|
109
|
-
: t.hops === 0
|
|
110
|
-
? "calls directly"
|
|
111
|
-
: `${t.hops} hop${t.hops > 1 ? "s" : ""} away`;
|
|
112
|
-
console.log(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hopLabel})`);
|
|
107
|
+
for (const t of grouped) {
|
|
108
|
+
console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
|
|
113
109
|
}
|
|
114
110
|
}
|
|
115
111
|
}
|
package/dist/commands/trace.js
CHANGED
|
@@ -43,14 +43,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
43
43
|
};
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.trace = void 0;
|
|
46
|
-
const fs = __importStar(require("node:fs"));
|
|
47
46
|
const commander_1 = require("commander");
|
|
47
|
+
const callsites_1 = require("../lib/graph/callsites");
|
|
48
48
|
const graph_builder_1 = require("../lib/graph/graph-builder");
|
|
49
49
|
const formatter_1 = require("../lib/output/formatter");
|
|
50
50
|
const vector_db_1 = require("../lib/store/vector-db");
|
|
51
51
|
const agent_errors_1 = require("../lib/utils/agent-errors");
|
|
52
52
|
const exit_1 = require("../lib/utils/exit");
|
|
53
53
|
const project_registry_1 = require("../lib/utils/project-registry");
|
|
54
|
+
const stale_hint_1 = require("../lib/utils/stale-hint");
|
|
54
55
|
const project_root_1 = require("../lib/utils/project-root");
|
|
55
56
|
const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
56
57
|
const dim = (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s);
|
|
@@ -78,33 +79,6 @@ function formatTraceAgent(graph, projectRoot) {
|
|
|
78
79
|
}
|
|
79
80
|
return lines.join("\n");
|
|
80
81
|
}
|
|
81
|
-
function findCallSiteSnippet(fileCache, callerFile, callerLine, targetSymbol) {
|
|
82
|
-
if (!callerFile)
|
|
83
|
-
return null;
|
|
84
|
-
let lines = fileCache.get(callerFile);
|
|
85
|
-
if (!lines) {
|
|
86
|
-
try {
|
|
87
|
-
lines = fs.readFileSync(callerFile, "utf-8").split("\n");
|
|
88
|
-
}
|
|
89
|
-
catch (_a) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
fileCache.set(callerFile, lines);
|
|
93
|
-
}
|
|
94
|
-
// Search a bounded window starting at the caller's definition line.
|
|
95
|
-
const start = Math.max(0, callerLine);
|
|
96
|
-
const end = Math.min(lines.length, callerLine + 200);
|
|
97
|
-
const wordRe = new RegExp(`\\b${targetSymbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
98
|
-
for (let i = start; i < end; i++) {
|
|
99
|
-
if (wordRe.test(lines[i])) {
|
|
100
|
-
return { snippet: lines[i].trim(), snippetLine: i };
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// Window didn't contain the symbol — chunker rolled the reference up to a
|
|
104
|
-
// parent scope. Skip the snippet rather than showing a misleading default;
|
|
105
|
-
// the caller's file:line is still emitted.
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
82
|
function buildInboundTree(callerTree, targetSymbol, fileCache, withSnippets, limit) {
|
|
109
83
|
var _a, _b, _c;
|
|
110
84
|
const out = [];
|
|
@@ -114,7 +88,7 @@ function buildInboundTree(callerTree, targetSymbol, fileCache, withSnippets, lim
|
|
|
114
88
|
const seen = new Set();
|
|
115
89
|
for (const t of callerTree) {
|
|
116
90
|
const snippet = withSnippets
|
|
117
|
-
? findCallSiteSnippet(fileCache, t.node.file, t.node.line, targetSymbol)
|
|
91
|
+
? (0, callsites_1.findCallSiteSnippet)(fileCache, t.node.file, t.node.line, targetSymbol)
|
|
118
92
|
: null;
|
|
119
93
|
const dedupeKey = `${t.node.file}:${(_a = snippet === null || snippet === void 0 ? void 0 : snippet.snippetLine) !== null && _a !== void 0 ? _a : t.node.line}`;
|
|
120
94
|
if (seen.has(dedupeKey))
|
|
@@ -210,6 +184,7 @@ exports.trace = new commander_1.Command("trace")
|
|
|
210
184
|
let vectorDb = null;
|
|
211
185
|
try {
|
|
212
186
|
const projectRoot = (_a = (0, project_root_1.findProjectRoot)(root)) !== null && _a !== void 0 ? _a : root;
|
|
187
|
+
(0, stale_hint_1.maybeWarnStaleChunker)(projectRoot, { agent: opts.agent });
|
|
213
188
|
const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
|
|
214
189
|
vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
|
|
215
190
|
const { resolveScope } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/scope-filter")));
|
package/dist/config.js
CHANGED
|
@@ -33,7 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.INDEXABLE_EXTENSIONS = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
|
|
36
|
+
exports.INDEXABLE_EXTENSIONS = exports.FRAGMENT_COMPACT_THRESHOLD = exports.DISK_LOW_BYTES = exports.DISK_CRITICAL_BYTES = exports.MAX_FILE_SIZE_BYTES = exports.PATHS = exports.MAX_WORKER_MEMORY_MB = exports.WORKER_BOOT_TIMEOUT_MS = exports.WORKER_TIMEOUT_MS = exports.CHUNKER_VERSION_HISTORY = exports.CONFIG = exports.MODEL_IDS = exports.DEFAULT_MODEL_TIER = exports.MODEL_TIERS = void 0;
|
|
37
|
+
exports.describeChunkerGap = describeChunkerGap;
|
|
37
38
|
const os = __importStar(require("node:os"));
|
|
38
39
|
const path = __importStar(require("node:path"));
|
|
39
40
|
exports.MODEL_TIERS = {
|
|
@@ -76,7 +77,52 @@ exports.CONFIG = {
|
|
|
76
77
|
EMBED_BATCH_SIZE: 24,
|
|
77
78
|
WORKER_THREADS: DEFAULT_WORKER_THREADS,
|
|
78
79
|
QUERY_PREFIX: "",
|
|
80
|
+
// Bump when chunk metadata semantics change in a way that requires a full
|
|
81
|
+
// reindex to take effect. Must equal the latest entry's `v` in
|
|
82
|
+
// CHUNKER_VERSION_HISTORY below — see that list for per-version severity and
|
|
83
|
+
// the user-facing note rendered by `gmax doctor` and the staleness hint.
|
|
84
|
+
CHUNKER_VERSION: 3,
|
|
79
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Per-version record of what changed in the chunker and how much it matters to
|
|
88
|
+
* an already-built index. `severity` drives tone: an "additive" change only
|
|
89
|
+
* adds new metadata (older indexes under-cover but aren't wrong), while a
|
|
90
|
+
* "breaking" change means older indexes carry incorrect metadata until a
|
|
91
|
+
* reindex. The `note` is shown verbatim to the user for the versions their
|
|
92
|
+
* index is missing. CONFIG.CHUNKER_VERSION must equal the highest `v` here.
|
|
93
|
+
*/
|
|
94
|
+
exports.CHUNKER_VERSION_HISTORY = [
|
|
95
|
+
{
|
|
96
|
+
v: 2,
|
|
97
|
+
severity: "breaking",
|
|
98
|
+
note: "sub-chunk symbol scoping; graph overcounted callers before this.",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
v: 3,
|
|
102
|
+
severity: "additive",
|
|
103
|
+
note: "type-position edges; dead/trace miss type-only callers until reindex.",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
/**
|
|
107
|
+
* Describe the gap between an index's stamped chunker version and the current
|
|
108
|
+
* one, or null when the index is already current. Shared by `gmax doctor` and
|
|
109
|
+
* the query-time staleness hint so both render the same severity + notes.
|
|
110
|
+
*/
|
|
111
|
+
function describeChunkerGap(indexedVersion) {
|
|
112
|
+
const fromVersion = indexedVersion !== null && indexedVersion !== void 0 ? indexedVersion : 1;
|
|
113
|
+
if (fromVersion >= exports.CONFIG.CHUNKER_VERSION)
|
|
114
|
+
return null;
|
|
115
|
+
const missed = exports.CHUNKER_VERSION_HISTORY.filter((h) => h.v > fromVersion && h.v <= exports.CONFIG.CHUNKER_VERSION);
|
|
116
|
+
const severity = missed.some((h) => h.severity === "breaking")
|
|
117
|
+
? "breaking"
|
|
118
|
+
: "additive";
|
|
119
|
+
return {
|
|
120
|
+
fromVersion,
|
|
121
|
+
toVersion: exports.CONFIG.CHUNKER_VERSION,
|
|
122
|
+
severity,
|
|
123
|
+
notes: missed.map((h) => h.note),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
80
126
|
exports.WORKER_TIMEOUT_MS = Number.parseInt(process.env.GMAX_WORKER_TIMEOUT_MS || "60000", 10);
|
|
81
127
|
exports.WORKER_BOOT_TIMEOUT_MS = Number.parseInt(process.env.GMAX_WORKER_BOOT_TIMEOUT_MS || "300000", 10);
|
|
82
128
|
exports.MAX_WORKER_MEMORY_MB = (() => {
|