grepmax 0.14.5 → 0.14.7
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/skeleton.js +1 -0
- package/dist/index.js +13 -0
- package/dist/lib/daemon/daemon.js +6 -1
- package/dist/lib/graph/impact.js +44 -2
- package/dist/lib/index/batch-processor.js +1 -1
- package/dist/lib/index/chunker.js +62 -3
- package/dist/lib/index/watcher.js +9 -0
- package/dist/lib/skeleton/skeletonizer.js +17 -1
- package/package.json +1 -1
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
|
@@ -126,6 +126,7 @@ exports.skeleton = new commander_1.Command("skeleton")
|
|
|
126
126
|
.option("--json", "Output as JSON", false)
|
|
127
127
|
.option("--no-summary", "Omit call/complexity summary in bodies", false)
|
|
128
128
|
.option("-s, --sync", "Sync index before searching", false)
|
|
129
|
+
.option("--agent", "Compact output for AI agents", false)
|
|
129
130
|
.addHelpText("after", `
|
|
130
131
|
Examples:
|
|
131
132
|
gmax skeleton src/lib/auth.ts Show file structure
|
package/dist/index.js
CHANGED
|
@@ -88,6 +88,19 @@ if (legacyProjectData) {
|
|
|
88
88
|
console.log(" gmax now uses a centralized index at ~/.gmax/lancedb/.");
|
|
89
89
|
console.log(" Run 'gmax index' to re-index into the centralized store.");
|
|
90
90
|
}
|
|
91
|
+
// Wire global --store to per-command --root so cross-project queries work
|
|
92
|
+
commander_1.program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
93
|
+
var _a, _b, _c;
|
|
94
|
+
const globals = (_b = (_a = actionCommand.optsWithGlobals) === null || _a === void 0 ? void 0 : _a.call(actionCommand)) !== null && _b !== void 0 ? _b : {};
|
|
95
|
+
if (globals.store && !((_c = actionCommand.getOptionValue) === null || _c === void 0 ? void 0 : _c.call(actionCommand, "root"))) {
|
|
96
|
+
try {
|
|
97
|
+
actionCommand.setOptionValue("root", globals.store);
|
|
98
|
+
}
|
|
99
|
+
catch (_d) {
|
|
100
|
+
// Command may not have --root; that's fine
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
91
104
|
// Core commands
|
|
92
105
|
commander_1.program.addCommand(search_1.search, { isDefault: true });
|
|
93
106
|
commander_1.program.addCommand(add_1.add);
|
|
@@ -203,9 +203,14 @@ class Daemon {
|
|
|
203
203
|
console.error(`[daemon] Failed to index pending ${path.basename(p.root)}:`, err);
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
-
// 9. Heartbeat
|
|
206
|
+
// 9. Heartbeat + refresh lockfile mtime to prevent stale detection
|
|
207
207
|
this.heartbeatInterval = setInterval(() => {
|
|
208
208
|
(0, watcher_store_1.heartbeat)(process.pid);
|
|
209
|
+
try {
|
|
210
|
+
const now = new Date();
|
|
211
|
+
fs.utimesSync(config_1.PATHS.daemonLockFile, now, now);
|
|
212
|
+
}
|
|
213
|
+
catch (_a) { }
|
|
209
214
|
}, HEARTBEAT_INTERVAL_MS);
|
|
210
215
|
// 10. Idle timeout
|
|
211
216
|
this.idleInterval = setInterval(() => {
|
package/dist/lib/graph/impact.js
CHANGED
|
@@ -17,8 +17,12 @@ const filter_builder_1 = require("../utils/filter-builder");
|
|
|
17
17
|
const graph_builder_1 = require("./graph-builder");
|
|
18
18
|
const TEST_DIR_RE = /(^|\/)(__tests__|tests?|specs?|benchmark)(\/|$)/i;
|
|
19
19
|
const TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
|
|
20
|
+
// Swift/Kotlin/Java: FooTests.swift, FooTest.kt, FooTest.java, or dirs like AppTests/
|
|
21
|
+
const NATIVE_TEST_DIR_RE = /(^|\/)\w+Tests?(\/|$)/;
|
|
22
|
+
const NATIVE_TEST_FILE_RE = /Tests?\.(swift|kt|java)$/;
|
|
20
23
|
function isTestPath(filePath) {
|
|
21
|
-
return TEST_DIR_RE.test(filePath) || TEST_FILE_RE.test(filePath)
|
|
24
|
+
return TEST_DIR_RE.test(filePath) || TEST_FILE_RE.test(filePath)
|
|
25
|
+
|| NATIVE_TEST_DIR_RE.test(filePath) || NATIVE_TEST_FILE_RE.test(filePath);
|
|
22
26
|
}
|
|
23
27
|
const arrow_1 = require("../utils/arrow");
|
|
24
28
|
/**
|
|
@@ -48,6 +52,42 @@ function resolveTargetSymbols(target, vectorDb, projectRoot) {
|
|
|
48
52
|
return { symbols: [target], resolvedAsFile: false };
|
|
49
53
|
});
|
|
50
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* For a single symbol, expand to include all symbols defined in the same file.
|
|
57
|
+
* This catches cases where tests call methods of a class rather than the class name itself
|
|
58
|
+
* (e.g., Swift tests call `handleNotification()` rather than referencing `DeepLinkRouter`).
|
|
59
|
+
*/
|
|
60
|
+
function expandFileSymbols(symbols, vectorDb, projectRoot) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
if (symbols.length !== 1)
|
|
63
|
+
return symbols;
|
|
64
|
+
const table = yield vectorDb.ensureTable();
|
|
65
|
+
const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
|
|
66
|
+
// Find the file that defines this symbol
|
|
67
|
+
const defRows = yield table
|
|
68
|
+
.query()
|
|
69
|
+
.select(["path"])
|
|
70
|
+
.where(`array_contains(defined_symbols, '${(0, filter_builder_1.escapeSqlString)(symbols[0])}') AND path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
|
|
71
|
+
.limit(1)
|
|
72
|
+
.toArray();
|
|
73
|
+
if (defRows.length === 0)
|
|
74
|
+
return symbols;
|
|
75
|
+
const filePath = String(defRows[0].path);
|
|
76
|
+
// Get ALL symbols defined in that file
|
|
77
|
+
const fileRows = yield table
|
|
78
|
+
.query()
|
|
79
|
+
.select(["defined_symbols"])
|
|
80
|
+
.where(`path = '${(0, filter_builder_1.escapeSqlString)(filePath)}'`)
|
|
81
|
+
.toArray();
|
|
82
|
+
const expanded = new Set(symbols);
|
|
83
|
+
for (const row of fileRows) {
|
|
84
|
+
for (const s of (0, arrow_1.toArr)(row.defined_symbols)) {
|
|
85
|
+
expanded.add(s);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [...expanded];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
51
91
|
/**
|
|
52
92
|
* Find test files that exercise a set of symbols, using reverse call graph traversal.
|
|
53
93
|
*/
|
|
@@ -55,7 +95,9 @@ function findTests(symbols_1, vectorDb_1, projectRoot_1) {
|
|
|
55
95
|
return __awaiter(this, arguments, void 0, function* (symbols, vectorDb, projectRoot, depth = 1) {
|
|
56
96
|
const graphBuilder = new graph_builder_1.GraphBuilder(vectorDb, projectRoot);
|
|
57
97
|
const testHits = new Map(); // key: file+symbol
|
|
58
|
-
|
|
98
|
+
// Expand single-symbol targets to include all symbols from the same file
|
|
99
|
+
const expanded = yield expandFileSymbols(symbols, vectorDb, projectRoot);
|
|
100
|
+
for (const symbol of expanded) {
|
|
59
101
|
yield walkCallers(symbol, graphBuilder, testHits, 0, depth, new Set());
|
|
60
102
|
}
|
|
61
103
|
return [...testHits.values()].sort((a, b) => a.hops - b.hops || a.file.localeCompare(b.file));
|
|
@@ -53,7 +53,7 @@ const pool_1 = require("../workers/pool");
|
|
|
53
53
|
const watcher_batch_1 = require("./watcher-batch");
|
|
54
54
|
// Fast path-segment check to reject events that leak through FSEvents overflow.
|
|
55
55
|
// Matches /node_modules/, /.git/, /dist/, /build/, /.next/, etc. anywhere in path.
|
|
56
|
-
const IGNORED_PATH_SEGMENTS_RE = /\/(?:node_modules|\.git|\.next|\.nuxt|__pycache__|coverage|\.gmax)\//;
|
|
56
|
+
const IGNORED_PATH_SEGMENTS_RE = /\/(?:node_modules|\.git|\.next|\.nuxt|__pycache__|coverage|\.gmax|\.venv|venv|site-packages|dist|build|out|target|vendor|\.tox|\.gradle|\.m2)\//;
|
|
57
57
|
const DEBOUNCE_MS = 2000;
|
|
58
58
|
const MAX_RETRIES = 5;
|
|
59
59
|
const MAX_BATCH_SIZE = 50;
|
|
@@ -249,6 +249,49 @@ class TreeSitterChunker {
|
|
|
249
249
|
}
|
|
250
250
|
// Split chunks if too big
|
|
251
251
|
result.chunks = result.chunks.flatMap((c) => this.splitIfTooBig(c));
|
|
252
|
+
// Post-process: extract property function names from chunks.
|
|
253
|
+
// Catches resolver patterns like `posTableTurnReport: withAuth(scope, async (args) => {`
|
|
254
|
+
const PROP_FN_RE = /^\s*(\w+)\s*:\s*(?:async\s+)?(?:function\b|\(|[A-Za-z_]\w*\()/gm;
|
|
255
|
+
for (const c of result.chunks) {
|
|
256
|
+
if (c.definedSymbols && c.content.length > 100) {
|
|
257
|
+
const extra = [];
|
|
258
|
+
let m;
|
|
259
|
+
while ((m = PROP_FN_RE.exec(c.content)) !== null) {
|
|
260
|
+
const name = m[1];
|
|
261
|
+
if (name.length > 1 && !c.definedSymbols.includes(name)) {
|
|
262
|
+
extra.push(name);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
PROP_FN_RE.lastIndex = 0;
|
|
266
|
+
if (extra.length > 0) {
|
|
267
|
+
// Create a new array to avoid shared references from splitIfTooBig
|
|
268
|
+
c.definedSymbols = [...c.definedSymbols, ...extra];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Post-process: extract JSX component references from all chunks (including blocks).
|
|
273
|
+
// Block chunks from test files miss JSX because extractRefs only runs on definition chunks.
|
|
274
|
+
const JSX_RE = /<([A-Z][A-Za-z0-9_.]*)/g;
|
|
275
|
+
for (const c of result.chunks) {
|
|
276
|
+
if (!c.referencedSymbols)
|
|
277
|
+
c.referencedSymbols = [];
|
|
278
|
+
const existing = new Set(c.referencedSymbols);
|
|
279
|
+
let jm;
|
|
280
|
+
const jsxRefs = [];
|
|
281
|
+
while ((jm = JSX_RE.exec(c.content)) !== null) {
|
|
282
|
+
// Extract base component name (e.g., "Foo" from "Foo.Bar")
|
|
283
|
+
const full = jm[1];
|
|
284
|
+
const base = full.split(".")[0];
|
|
285
|
+
if (!existing.has(base)) {
|
|
286
|
+
existing.add(base);
|
|
287
|
+
jsxRefs.push(base);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
JSX_RE.lastIndex = 0;
|
|
291
|
+
if (jsxRefs.length > 0) {
|
|
292
|
+
c.referencedSymbols = [...c.referencedSymbols, ...jsxRefs];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
252
295
|
return result;
|
|
253
296
|
});
|
|
254
297
|
}
|
|
@@ -316,7 +359,7 @@ class TreeSitterChunker {
|
|
|
316
359
|
if (t !== "lexical_declaration" && t !== "variable_declaration")
|
|
317
360
|
return false;
|
|
318
361
|
const parentType = ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) || "";
|
|
319
|
-
const allowedParents = ["program", "module", "source_file", "class_body"];
|
|
362
|
+
const allowedParents = ["program", "module", "source_file", "class_body", "export_statement"];
|
|
320
363
|
if (parentType && !allowedParents.includes(parentType))
|
|
321
364
|
return false;
|
|
322
365
|
const text = node.text || "";
|
|
@@ -483,7 +526,7 @@ class TreeSitterChunker {
|
|
|
483
526
|
definedSymbols.push(name);
|
|
484
527
|
const referencedSymbols = [];
|
|
485
528
|
const extractRefs = (n) => {
|
|
486
|
-
var _a, _b, _c, _d;
|
|
529
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
487
530
|
// Handle JS/TS (call_expression), Python (call), Lua (function_call)
|
|
488
531
|
if (n.type === "call_expression" ||
|
|
489
532
|
n.type === "call" ||
|
|
@@ -530,7 +573,23 @@ class TreeSitterChunker {
|
|
|
530
573
|
}
|
|
531
574
|
}
|
|
532
575
|
}
|
|
533
|
-
|
|
576
|
+
// JSX elements: <Component /> or <Component>
|
|
577
|
+
if (n.type === "jsx_self_closing_element" ||
|
|
578
|
+
n.type === "jsx_opening_element") {
|
|
579
|
+
const nameChild = ((_d = n.namedChildren) !== null && _d !== void 0 ? _d : []).find((c) => c.type === "identifier" ||
|
|
580
|
+
c.type === "member_expression" ||
|
|
581
|
+
c.type === "nested_identifier");
|
|
582
|
+
if (nameChild) {
|
|
583
|
+
// Only capture PascalCase components (skip HTML tags like div, span)
|
|
584
|
+
const name = nameChild.type === "member_expression"
|
|
585
|
+
? ((_g = (_f = (_e = nameChild.childForFieldName) === null || _e === void 0 ? void 0 : _e.call(nameChild, "property")) === null || _f === void 0 ? void 0 : _f.text) !== null && _g !== void 0 ? _g : nameChild.text)
|
|
586
|
+
: nameChild.text;
|
|
587
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
588
|
+
referencedSymbols.push(name);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
for (const child of (_h = n.namedChildren) !== null && _h !== void 0 ? _h : []) {
|
|
534
593
|
extractRefs(child);
|
|
535
594
|
}
|
|
536
595
|
};
|
|
@@ -59,7 +59,16 @@ exports.WATCHER_IGNORE_GLOBS = [
|
|
|
59
59
|
"__pycache__",
|
|
60
60
|
"coverage",
|
|
61
61
|
"venv",
|
|
62
|
+
".venv",
|
|
63
|
+
"site-packages",
|
|
64
|
+
".tox",
|
|
65
|
+
".mypy_cache",
|
|
66
|
+
".pytest_cache",
|
|
62
67
|
".next",
|
|
68
|
+
".nuxt",
|
|
69
|
+
".gradle",
|
|
70
|
+
".m2",
|
|
71
|
+
"vendor",
|
|
63
72
|
"lancedb",
|
|
64
73
|
".*", // dotfiles
|
|
65
74
|
"**/*.tmp.*", // editor atomic save artifacts
|
|
@@ -362,7 +362,7 @@ class Skeletonizer {
|
|
|
362
362
|
const refs = [];
|
|
363
363
|
const seen = new Set();
|
|
364
364
|
const extract = (n) => {
|
|
365
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
365
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
366
366
|
if (n.type === "call_expression" ||
|
|
367
367
|
n.type === "call" ||
|
|
368
368
|
n.type === "function_call") {
|
|
@@ -427,6 +427,22 @@ class Skeletonizer {
|
|
|
427
427
|
seen.add(nameNode.text);
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
|
+
// JSX elements: <Component /> or <Component>
|
|
431
|
+
if (n.type === "jsx_self_closing_element" ||
|
|
432
|
+
n.type === "jsx_opening_element") {
|
|
433
|
+
const nameChild = (n.namedChildren || []).find((c) => c.type === "identifier" ||
|
|
434
|
+
c.type === "member_expression" ||
|
|
435
|
+
c.type === "nested_identifier");
|
|
436
|
+
if (nameChild) {
|
|
437
|
+
const name = nameChild.type === "member_expression"
|
|
438
|
+
? ((_o = (_m = (_l = nameChild.childForFieldName) === null || _l === void 0 ? void 0 : _l.call(nameChild, "property")) === null || _m === void 0 ? void 0 : _m.text) !== null && _o !== void 0 ? _o : nameChild.text)
|
|
439
|
+
: nameChild.text;
|
|
440
|
+
if (name && /^[A-Z]/.test(name) && !seen.has(name)) {
|
|
441
|
+
seen.add(name);
|
|
442
|
+
refs.push(name);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
430
446
|
for (const child of n.namedChildren || []) {
|
|
431
447
|
extract(child);
|
|
432
448
|
}
|
package/package.json
CHANGED