grepmax 0.7.16 → 0.7.18

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.
@@ -41,6 +41,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
41
41
  step((generator = generator.apply(thisArg, _arguments || [])).next());
42
42
  });
43
43
  };
44
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
45
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
46
+ var m = o[Symbol.asyncIterator], i;
47
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
48
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
49
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
50
+ };
44
51
  Object.defineProperty(exports, "__esModule", { value: true });
45
52
  exports.mcp = void 0;
46
53
  exports.toStringArray = toStringArray;
@@ -57,6 +64,7 @@ const config_1 = require("../config");
57
64
  const graph_builder_1 = require("../lib/graph/graph-builder");
58
65
  const index_config_1 = require("../lib/index/index-config");
59
66
  const syncer_1 = require("../lib/index/syncer");
67
+ const meta_cache_1 = require("../lib/store/meta-cache");
60
68
  const searcher_1 = require("../lib/search/searcher");
61
69
  const retriever_1 = require("../lib/skeleton/retriever");
62
70
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
@@ -212,6 +220,10 @@ const TOOLS = [
212
220
  type: "number",
213
221
  description: "Max files for directory mode (default 10, max 20). Ignored for single files.",
214
222
  },
223
+ format: {
224
+ type: "string",
225
+ description: "Output format: 'text' (default) or 'json' (structured symbol list with names, lines, signatures).",
226
+ },
215
227
  },
216
228
  required: ["target"],
217
229
  },
@@ -311,6 +323,23 @@ const TOOLS = [
311
323
  required: ["file"],
312
324
  },
313
325
  },
326
+ {
327
+ name: "recent_changes",
328
+ description: "Show recently modified files in the index. Useful after pulls or merges to see what changed.",
329
+ inputSchema: {
330
+ type: "object",
331
+ properties: {
332
+ limit: {
333
+ type: "number",
334
+ description: "Max files to return (default 20)",
335
+ },
336
+ root: {
337
+ type: "string",
338
+ description: "Project root (defaults to current project)",
339
+ },
340
+ },
341
+ },
342
+ },
314
343
  ];
315
344
  // ---------------------------------------------------------------------------
316
345
  // Helpers
@@ -821,13 +850,16 @@ exports.mcp = new commander_1.Command("mcp")
821
850
  targets = [target];
822
851
  }
823
852
  }
853
+ const fmt = typeof args.format === "string" ? args.format : "text";
824
854
  // Generate skeletons for all targets
825
855
  const parts = [];
856
+ const jsonFiles = [];
826
857
  const skel = yield getSkeletonizer();
827
858
  for (const t of targets) {
828
859
  const absPath = path.resolve(projectRoot, t);
829
860
  if (!fs.existsSync(absPath)) {
830
- parts.push(`// ${t} — file not found`);
861
+ if (fmt !== "json")
862
+ parts.push(`// ${t} — file not found`);
831
863
  continue;
832
864
  }
833
865
  // Read source for line annotations
@@ -836,38 +868,92 @@ exports.mcp = new commander_1.Command("mcp")
836
868
  sourceContent = fs.readFileSync(absPath, "utf-8");
837
869
  }
838
870
  catch (_a) { }
871
+ let skeleton = "";
872
+ let language = "";
873
+ let tokenEstimate = 0;
839
874
  // Try cached skeleton first
840
875
  try {
841
876
  const db = getVectorDb();
842
877
  const cached = yield (0, retriever_1.getStoredSkeleton)(db, absPath);
843
878
  if (cached) {
844
- const tokens = Math.ceil(cached.length / 4);
845
- const annotated = sourceContent
846
- ? annotateSkeletonLines(cached, sourceContent)
847
- : cached;
848
- parts.push(`// ${t} (~${tokens} tokens)\n\n${annotated}`);
849
- continue;
879
+ skeleton = cached;
880
+ tokenEstimate = Math.ceil(cached.length / 4);
850
881
  }
851
882
  }
852
- catch (_b) {
853
- // Index may not exist yet — fall through to live generation
854
- }
855
- // Generate live
856
- try {
857
- const content = sourceContent || fs.readFileSync(absPath, "utf-8");
858
- const result = yield skel.skeletonizeFile(absPath, content);
859
- if (result.success) {
860
- const annotated = annotateSkeletonLines(result.skeleton, content);
861
- parts.push(`// ${t} (~${result.tokenEstimate} tokens)\n\n${annotated}`);
883
+ catch (_b) { }
884
+ // Generate live if no cache
885
+ if (!skeleton) {
886
+ try {
887
+ const content = sourceContent || fs.readFileSync(absPath, "utf-8");
888
+ const result = yield skel.skeletonizeFile(absPath, content);
889
+ if (result.success) {
890
+ skeleton = result.skeleton;
891
+ tokenEstimate = result.tokenEstimate;
892
+ language = result.language;
893
+ }
894
+ else {
895
+ if (fmt !== "json")
896
+ parts.push(`// ${t} — skeleton failed: ${result.error}`);
897
+ continue;
898
+ }
862
899
  }
863
- else {
864
- parts.push(`// ${t} skeleton failed: ${result.error}`);
900
+ catch (e) {
901
+ const msg = e instanceof Error ? e.message : String(e);
902
+ if (fmt !== "json")
903
+ parts.push(`// ${t} — error: ${msg}`);
904
+ continue;
865
905
  }
866
906
  }
867
- catch (e) {
868
- const msg = e instanceof Error ? e.message : String(e);
869
- parts.push(`// ${t} error: ${msg}`);
907
+ if (fmt === "json") {
908
+ // Extract structured symbols from annotated skeleton
909
+ const annotated = sourceContent
910
+ ? annotateSkeletonLines(skeleton, sourceContent)
911
+ : skeleton;
912
+ const symbols = annotated
913
+ .split("\n")
914
+ .filter((l) => /^\s*\d+│/.test(l))
915
+ .map((l) => {
916
+ var _a, _b, _c;
917
+ const m = l.match(/^\s*(\d+)│(.+)/);
918
+ if (!m)
919
+ return null;
920
+ const line = Number.parseInt(m[1], 10);
921
+ const sig = m[2].trim();
922
+ const exported = sig.includes("export ");
923
+ const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) || "other";
924
+ const name = ((_b = sig.match(/(?:function|class|interface|type|def|fn|func)\s+(\w+)/)) === null || _b === void 0 ? void 0 : _b[1]) ||
925
+ ((_c = sig.match(/^(?:async\s+)?(\w+)\s*[(<]/)) === null || _c === void 0 ? void 0 : _c[1]) ||
926
+ "unknown";
927
+ return {
928
+ name,
929
+ line,
930
+ signature: sig
931
+ .replace(/\s*\{?\s*\/\/.*$/, "")
932
+ .trim(),
933
+ type,
934
+ exported,
935
+ };
936
+ })
937
+ .filter((s) => s !== null && s.name !== "unknown");
938
+ jsonFiles.push({
939
+ file: t,
940
+ language: language || path.extname(t).slice(1),
941
+ tokenEstimate,
942
+ symbols,
943
+ });
870
944
  }
945
+ else {
946
+ const annotated = sourceContent
947
+ ? annotateSkeletonLines(skeleton, sourceContent)
948
+ : skeleton;
949
+ parts.push(`// ${t} (~${tokenEstimate} tokens)\n\n${annotated}`);
950
+ }
951
+ }
952
+ if (fmt === "json") {
953
+ const output = jsonFiles.length === 1
954
+ ? jsonFiles[0]
955
+ : { files: jsonFiles };
956
+ return ok(JSON.stringify(output, null, 2));
871
957
  }
872
958
  return ok(parts.join("\n\n---\n\n"));
873
959
  });
@@ -1344,6 +1430,76 @@ exports.mcp = new commander_1.Command("mcp")
1344
1430
  }
1345
1431
  });
1346
1432
  }
1433
+ function formatTimeAgo(ms) {
1434
+ const sec = Math.floor(ms / 1000);
1435
+ if (sec < 60)
1436
+ return `${sec}s ago`;
1437
+ const min = Math.floor(sec / 60);
1438
+ if (min < 60)
1439
+ return `${min}m ago`;
1440
+ const hr = Math.floor(min / 60);
1441
+ if (hr < 24)
1442
+ return `${hr}h ago`;
1443
+ const days = Math.floor(hr / 24);
1444
+ return `${days}d ago`;
1445
+ }
1446
+ function handleRecentChanges(args) {
1447
+ return __awaiter(this, void 0, void 0, function* () {
1448
+ var _a, e_1, _b, _c;
1449
+ const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 50);
1450
+ const root = typeof args.root === "string"
1451
+ ? path.resolve(args.root)
1452
+ : projectRoot;
1453
+ const prefix = root.endsWith("/") ? root : `${root}/`;
1454
+ try {
1455
+ const metaCache = new meta_cache_1.MetaCache(config_1.PATHS.lmdbPath);
1456
+ try {
1457
+ const files = [];
1458
+ try {
1459
+ for (var _d = true, _e = __asyncValues(metaCache.entries()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
1460
+ _c = _f.value;
1461
+ _d = false;
1462
+ const { path: p, entry, } = _c;
1463
+ if (p.startsWith(prefix)) {
1464
+ files.push({ path: p, mtimeMs: entry.mtimeMs });
1465
+ }
1466
+ }
1467
+ }
1468
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1469
+ finally {
1470
+ try {
1471
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
1472
+ }
1473
+ finally { if (e_1) throw e_1.error; }
1474
+ }
1475
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs);
1476
+ const top = files.slice(0, limit);
1477
+ if (top.length === 0) {
1478
+ return ok(`No indexed files found for ${root}`);
1479
+ }
1480
+ const now = Date.now();
1481
+ const lines = [
1482
+ `Recent changes in ${path.basename(root)} (${top.length} most recent):\n`,
1483
+ ];
1484
+ for (const f of top) {
1485
+ const rel = f.path.startsWith(prefix)
1486
+ ? f.path.slice(prefix.length)
1487
+ : f.path;
1488
+ const ago = formatTimeAgo(now - f.mtimeMs);
1489
+ lines.push(` ${ago.padEnd(10)} ${rel}`);
1490
+ }
1491
+ return ok(lines.join("\n"));
1492
+ }
1493
+ finally {
1494
+ yield metaCache.close();
1495
+ }
1496
+ }
1497
+ catch (e) {
1498
+ const msg = e instanceof Error ? e.message : String(e);
1499
+ return err(`Recent changes failed: ${msg}`);
1500
+ }
1501
+ });
1502
+ }
1347
1503
  // --- MCP server setup ---
1348
1504
  const transport = new stdio_js_1.StdioServerTransport();
1349
1505
  const server = new index_js_1.Server({
@@ -1381,6 +1537,8 @@ exports.mcp = new commander_1.Command("mcp")
1381
1537
  return handleSummarizeProject(toolArgs);
1382
1538
  case "related_files":
1383
1539
  return handleRelatedFiles(toolArgs);
1540
+ case "recent_changes":
1541
+ return handleRecentChanges(toolArgs);
1384
1542
  default:
1385
1543
  return err(`Unknown tool: ${name}`);
1386
1544
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "author": "Robert Owens <robowens@me.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.7.16",
3
+ "version": "0.7.18",
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",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: grepmax
3
3
  description: Semantic code search. Use alongside grep - grep for exact strings, gmax for concepts.
4
- allowed-tools: "mcp__grepmax__semantic_search, mcp__grepmax__search_all, mcp__grepmax__code_skeleton, mcp__grepmax__trace_calls, mcp__grepmax__list_symbols, mcp__grepmax__index_status, mcp__grepmax__summarize_directory, mcp__grepmax__summarize_project, mcp__grepmax__related_files, Bash(gmax:*), Read"
4
+ allowed-tools: "mcp__grepmax__semantic_search, mcp__grepmax__search_all, mcp__grepmax__code_skeleton, mcp__grepmax__trace_calls, mcp__grepmax__list_symbols, mcp__grepmax__index_status, mcp__grepmax__summarize_directory, mcp__grepmax__summarize_project, mcp__grepmax__related_files, mcp__grepmax__recent_changes, Bash(gmax:*), Read"
5
5
  ---
6
6
 
7
7
  ## What gmax does
@@ -67,6 +67,7 @@ Use sparingly. Prefer `semantic_search` when you know which directory to search.
67
67
  File or directory structure — signatures with bodies collapsed (~4x fewer tokens).
68
68
  - `target` (required): File path, directory path (e.g. "src/lib/search/"), or comma-separated files
69
69
  - `limit` (optional): Max files for directory mode (default 10, max 20)
70
+ - `format` (optional): `"text"` (default) or `"json"` (structured symbol list with name, line, signature, type, exported)
70
71
 
71
72
  ### trace_calls
72
73
  Call graph — who imports a symbol, who calls it, and what it calls. Includes file:line locations. Unscoped — follows calls across all indexed directories.
@@ -92,6 +93,11 @@ Find files related to a given file by shared symbol references. Shows dependenci
92
93
  - `file` (required): File path relative to project root
93
94
  - `limit` (optional): Max results per direction (default 10)
94
95
 
96
+ ### recent_changes
97
+ Show recently modified files in the index. Useful after pulls or merges to see what changed.
98
+ - `limit` (optional): Max files (default 20)
99
+ - `root` (optional): Project root (defaults to current project)
100
+
95
101
  ### index_status
96
102
  Check centralized index health — chunks, files, indexed directories, model info, watcher status.
97
103