grepmax 0.14.5 → 0.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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(() => {
@@ -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
- for (const symbol of symbols) {
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));
@@ -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
- for (const child of (_d = n.namedChildren) !== null && _d !== void 0 ? _d : []) {
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
  };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",