grepmax 0.18.0 → 0.19.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.
Files changed (66) hide show
  1. package/README.md +27 -2
  2. package/dist/commands/add.js +4 -2
  3. package/dist/commands/claude-code.js +1 -1
  4. package/dist/commands/codex.js +3 -1
  5. package/dist/commands/dead.js +2 -6
  6. package/dist/commands/doctor.js +26 -7
  7. package/dist/commands/extract.js +3 -5
  8. package/dist/commands/impact.js +6 -4
  9. package/dist/commands/index.js +7 -2
  10. package/dist/commands/log.js +4 -2
  11. package/dist/commands/mcp.js +587 -715
  12. package/dist/commands/peek.js +15 -9
  13. package/dist/commands/plugin.js +1 -1
  14. package/dist/commands/project.js +15 -5
  15. package/dist/commands/related.js +8 -9
  16. package/dist/commands/review.js +1 -1
  17. package/dist/commands/search.js +1 -1
  18. package/dist/commands/similar.js +4 -4
  19. package/dist/commands/status.js +5 -3
  20. package/dist/commands/summarize.js +6 -4
  21. package/dist/commands/test-find.js +2 -2
  22. package/dist/commands/trace.js +51 -9
  23. package/dist/commands/watch.js +9 -5
  24. package/dist/eval-graph-nav.js +4 -1
  25. package/dist/eval-graph-recovery-probe.js +56 -14
  26. package/dist/eval-graph-spotcheck.js +10 -2
  27. package/dist/eval-graph-totals.js +5 -2
  28. package/dist/eval-oss.js +212 -37
  29. package/dist/eval-seed.js +13 -4
  30. package/dist/index.js +9 -9
  31. package/dist/lib/daemon/daemon.js +22 -13
  32. package/dist/lib/daemon/ipc-handler.js +18 -5
  33. package/dist/lib/daemon/mlx-server-manager.js +11 -3
  34. package/dist/lib/daemon/process-manager.js +1 -2
  35. package/dist/lib/daemon/watcher-manager.js +4 -4
  36. package/dist/lib/graph/callsites.js +151 -25
  37. package/dist/lib/help/agent-cheatsheet.js +1 -1
  38. package/dist/lib/index/batch-processor.js +6 -5
  39. package/dist/lib/index/chunker.js +2 -0
  40. package/dist/lib/index/syncer.js +7 -6
  41. package/dist/lib/llm/diff.js +56 -11
  42. package/dist/lib/llm/investigate.js +12 -5
  43. package/dist/lib/llm/review.js +21 -8
  44. package/dist/lib/llm/server.js +16 -7
  45. package/dist/lib/output/agent-search-formatter.js +25 -3
  46. package/dist/lib/output/index-state-footer.js +1 -1
  47. package/dist/lib/review/risk.js +2 -4
  48. package/dist/lib/search/searcher.js +37 -11
  49. package/dist/lib/search/seed-weight.js +4 -1
  50. package/dist/lib/skeleton/symbol-extractor.js +2 -1
  51. package/dist/lib/utils/cross-project.js +5 -1
  52. package/dist/lib/utils/daemon-client.js +5 -1
  53. package/dist/lib/utils/git.js +10 -2
  54. package/dist/lib/utils/project-registry.js +3 -2
  55. package/dist/lib/utils/scope-filter.js +3 -1
  56. package/dist/lib/utils/watcher-launcher.js +4 -1
  57. package/dist/lib/utils/watcher-store.js +2 -1
  58. package/dist/lib/workers/embeddings/mlx-client.js +7 -2
  59. package/dist/lib/workers/pool.js +9 -6
  60. package/dist/lib/workers/process-child.js +1 -1
  61. package/package.json +22 -19
  62. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  63. package/plugins/grepmax/hooks/cwd-changed.js +4 -1
  64. package/plugins/grepmax/hooks/pre-grep.js +2 -1
  65. package/plugins/grepmax/hooks/start.js +45 -10
  66. package/plugins/grepmax/hooks/subagent-start.js +1 -4
@@ -29,7 +29,9 @@ function readSymbolArray(val) {
29
29
  if (typeof maybe.toArray === "function") {
30
30
  try {
31
31
  const a = maybe.toArray();
32
- return Array.isArray(a) ? a.filter((v) => typeof v === "string") : [];
32
+ return Array.isArray(a)
33
+ ? a.filter((v) => typeof v === "string")
34
+ : [];
33
35
  }
34
36
  catch (_a) {
35
37
  return [];
@@ -441,9 +443,21 @@ class Searcher {
441
443
  // loaded), so pull it into the lightweight path when symbols were seeded.
442
444
  const needDefinedSymbols = pagerankEnabled || symbolQuery !== null || seedCtx.symbols.size > 0;
443
445
  const LIGHTWEIGHT_COLUMNS = [
444
- "id", "path", "hash", "chunk_index", "start_line", "end_line",
445
- "is_anchor", "chunk_type", "role", "complexity", "is_exported",
446
- "content", "parent_symbol", "referenced_symbols", "pooled_colbert_48d",
446
+ "id",
447
+ "path",
448
+ "hash",
449
+ "chunk_index",
450
+ "start_line",
451
+ "end_line",
452
+ "is_anchor",
453
+ "chunk_type",
454
+ "role",
455
+ "complexity",
456
+ "is_exported",
457
+ "content",
458
+ "parent_symbol",
459
+ "referenced_symbols",
460
+ "pooled_colbert_48d",
447
461
  ...(needDefinedSymbols ? ["defined_symbols"] : []),
448
462
  ];
449
463
  // _distance is auto-added by vectorSearch, _score by FTS — include each
@@ -457,7 +471,7 @@ class Searcher {
457
471
  if (whereClause) {
458
472
  vectorQuery = vectorQuery.where(whereClause);
459
473
  }
460
- let vectorResults = (yield vectorQuery.toArray()).map((r) => (Object.assign({}, r)));
474
+ const vectorResults = (yield vectorQuery.toArray()).map((r) => (Object.assign({}, r)));
461
475
  let ftsResults = [];
462
476
  let ftsSearchFailed = false;
463
477
  if (this.ftsAvailable) {
@@ -566,7 +580,7 @@ class Searcher {
566
580
  var _a, _b;
567
581
  const ka = a.id || `${a.path}:${a.chunk_index}`;
568
582
  const kb = b.id || `${b.path}:${b.chunk_index}`;
569
- return ((_a = candidateScores.get(kb)) !== null && _a !== void 0 ? _a : 0) - ((_b = candidateScores.get(ka)) !== null && _b !== void 0 ? _b : 0);
583
+ return (((_a = candidateScores.get(kb)) !== null && _a !== void 0 ? _a : 0) - ((_b = candidateScores.get(ka)) !== null && _b !== void 0 ? _b : 0));
570
584
  });
571
585
  }
572
586
  }
@@ -751,7 +765,12 @@ class Searcher {
751
765
  record: doc,
752
766
  score: boosted,
753
767
  breakdown: explain
754
- ? { rerank: base, fused: fusedScore, boost: blended > 0 ? boosted / blended : 1, normalized: 0 }
768
+ ? {
769
+ rerank: base,
770
+ fused: fusedScore,
771
+ boost: blended > 0 ? boosted / blended : 1,
772
+ normalized: 0,
773
+ }
755
774
  : undefined,
756
775
  };
757
776
  });
@@ -766,12 +785,14 @@ class Searcher {
766
785
  const envWeight = Number.parseFloat((_o = process.env.GMAX_PR_WEIGHT) !== null && _o !== void 0 ? _o : "");
767
786
  const PR_WEIGHT = Number.isFinite(envWeight) && envWeight >= 0 ? envWeight : 0.05;
768
787
  for (const item of scored) {
769
- const raw = item.record.defined_symbols;
788
+ const raw = item.record
789
+ .defined_symbols;
770
790
  let defs = [];
771
791
  if (Array.isArray(raw)) {
772
792
  defs = raw.filter((v) => typeof v === "string");
773
793
  }
774
- else if (raw && typeof raw.toArray === "function") {
794
+ else if (raw &&
795
+ typeof raw.toArray === "function") {
775
796
  try {
776
797
  const arr = raw.toArray();
777
798
  if (Array.isArray(arr)) {
@@ -817,8 +838,13 @@ class Searcher {
817
838
  const displayRows = yield table
818
839
  .query()
819
840
  .select([
820
- "id", "defined_symbols", "parent_symbol", "imports", "exports",
821
- "summary", "file_skeleton",
841
+ "id",
842
+ "defined_symbols",
843
+ "parent_symbol",
844
+ "imports",
845
+ "exports",
846
+ "summary",
847
+ "file_skeleton",
822
848
  ])
823
849
  .where(`id IN (${finalIds.join(",")})`)
824
850
  .limit(finalIds.length)
@@ -64,7 +64,10 @@ function seedParamsFromEnv(env = process.env) {
64
64
  function buildSeedContext(spec) {
65
65
  var _a, _b;
66
66
  const fileSuffixes = ((_a = spec === null || spec === void 0 ? void 0 : spec.files) !== null && _a !== void 0 ? _a : [])
67
- .map((f) => f.trim().toLowerCase().replace(/^\.?\//, ""))
67
+ .map((f) => f
68
+ .trim()
69
+ .toLowerCase()
70
+ .replace(/^\.?\//, ""))
68
71
  .filter((f) => f.length > 0);
69
72
  const symbols = new Set(((_b = spec === null || spec === void 0 ? void 0 : spec.symbols) !== null && _b !== void 0 ? _b : []).map((s) => s.trim()).filter((s) => s.length > 0));
70
73
  return {
@@ -13,7 +13,8 @@ function extractSymbolsFromSkeleton(annotatedSkeleton) {
13
13
  const line = Number.parseInt(m[1], 10);
14
14
  const sig = m[2].trim();
15
15
  const exported = sig.includes("export ");
16
- const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) || "other";
16
+ const type = ((_a = sig.match(/\b(class|interface|type|function|def|fn|func)\b/)) === null || _a === void 0 ? void 0 : _a[1]) ||
17
+ "other";
17
18
  const name = ((_b = sig.match(/(?:function|class|interface|type|def|fn|func)\s+(\w+)/)) === null || _b === void 0 ? void 0 : _b[1]) ||
18
19
  ((_c = sig.match(/^(?:async\s+)?(\w+)\s*[(<]/)) === null || _c === void 0 ? void 0 : _c[1]) ||
19
20
  "unknown";
@@ -105,7 +105,11 @@ function groupResultsByProject(results, roots, getPath) {
105
105
  const key = (_a = owner === null || owner === void 0 ? void 0 : owner.root) !== null && _a !== void 0 ? _a : "(unknown)";
106
106
  let bucket = buckets.get(key);
107
107
  if (!bucket) {
108
- bucket = { name: (_b = owner === null || owner === void 0 ? void 0 : owner.name) !== null && _b !== void 0 ? _b : "(unknown)", root: (_c = owner === null || owner === void 0 ? void 0 : owner.root) !== null && _c !== void 0 ? _c : "", items: [] };
108
+ bucket = {
109
+ name: (_b = owner === null || owner === void 0 ? void 0 : owner.name) !== null && _b !== void 0 ? _b : "(unknown)",
110
+ root: (_c = owner === null || owner === void 0 ? void 0 : owner.root) !== null && _c !== void 0 ? _c : "",
111
+ items: [],
112
+ };
109
113
  buckets.set(key, bucket);
110
114
  order.push(key);
111
115
  }
@@ -95,7 +95,10 @@ function sendDaemonCommand(cmd, opts) {
95
95
  socket.on("error", (err) => {
96
96
  var _a;
97
97
  clearTimeout(timer);
98
- finish({ ok: false, error: (_a = err.code) !== null && _a !== void 0 ? _a : err.message });
98
+ finish({
99
+ ok: false,
100
+ error: (_a = err.code) !== null && _a !== void 0 ? _a : err.message,
101
+ });
99
102
  });
100
103
  socket.on("close", () => {
101
104
  clearTimeout(timer);
@@ -207,6 +210,7 @@ function sendStreamingCommand(cmd, onProgress, opts) {
207
210
  socket.on("data", (chunk) => {
208
211
  buf += chunk.toString();
209
212
  let nl;
213
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic buffer-split loop
210
214
  while ((nl = buf.indexOf("\n")) !== -1) {
211
215
  const line = buf.slice(0, nl);
212
216
  buf = buf.slice(nl + 1);
@@ -90,7 +90,11 @@ function getMainRepoRoot(worktreeRoot) {
90
90
  * Returns absolute paths. Includes both staged and unstaged changes.
91
91
  */
92
92
  function getChangedFiles(ref, cwd) {
93
- const opts = { cwd: cwd !== null && cwd !== void 0 ? cwd : process.cwd(), encoding: "utf-8", timeout: 10000 };
93
+ const opts = {
94
+ cwd: cwd !== null && cwd !== void 0 ? cwd : process.cwd(),
95
+ encoding: "utf-8",
96
+ timeout: 10000,
97
+ };
94
98
  try {
95
99
  let output;
96
100
  if (ref) {
@@ -210,7 +214,11 @@ function getCommitHistory(opts) {
210
214
  * Returns absolute paths.
211
215
  */
212
216
  function getUntrackedFiles(cwd) {
213
- const opts = { cwd: cwd !== null && cwd !== void 0 ? cwd : process.cwd(), encoding: "utf-8", timeout: 10000 };
217
+ const opts = {
218
+ cwd: cwd !== null && cwd !== void 0 ? cwd : process.cwd(),
219
+ encoding: "utf-8",
220
+ timeout: 10000,
221
+ };
214
222
  try {
215
223
  const output = (0, node_child_process_1.execFileSync)("git", ["ls-files", "--others", "--exclude-standard"], opts);
216
224
  const root = (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--show-toplevel"], opts).trim();
@@ -66,7 +66,7 @@ function loadRegistry() {
66
66
  }
67
67
  function saveRegistry(entries) {
68
68
  fs.mkdirSync(path.dirname(REGISTRY_PATH), { recursive: true });
69
- const tmp = REGISTRY_PATH + ".tmp";
69
+ const tmp = `${REGISTRY_PATH}.tmp`;
70
70
  fs.writeFileSync(tmp, `${JSON.stringify(entries, null, 2)}\n`);
71
71
  fs.renameSync(tmp, REGISTRY_PATH);
72
72
  }
@@ -119,7 +119,8 @@ function removeProject(root) {
119
119
  */
120
120
  function getParentProject(root) {
121
121
  const resolved = root.endsWith("/") ? root : `${root}/`;
122
- return loadRegistry().find((e) => e.root !== root && resolved.startsWith(e.root.endsWith("/") ? e.root : `${e.root}/`));
122
+ return loadRegistry().find((e) => e.root !== root &&
123
+ resolved.startsWith(e.root.endsWith("/") ? e.root : `${e.root}/`));
123
124
  }
124
125
  /**
125
126
  * Find registered projects that are children of this path.
@@ -49,7 +49,9 @@ function toArray(value) {
49
49
  .filter(Boolean);
50
50
  }
51
51
  function joinSubpath(projectRoot, sub) {
52
- const rootWithSlash = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
52
+ const rootWithSlash = projectRoot.endsWith("/")
53
+ ? projectRoot
54
+ : `${projectRoot}/`;
53
55
  if (path.isAbsolute(sub))
54
56
  return sub.endsWith("/") ? sub : `${sub}/`;
55
57
  if (sub.startsWith(rootWithSlash))
@@ -56,7 +56,10 @@ function launchWatcher(projectRoot) {
56
56
  for (let i = 0; i < 25; i++) {
57
57
  yield new Promise((r) => setTimeout(r, 200));
58
58
  try {
59
- const retry = yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "watch", root: projectRoot });
59
+ const retry = yield (0, daemon_client_1.sendDaemonCommand)({
60
+ cmd: "watch",
61
+ root: projectRoot,
62
+ });
60
63
  if (retry.ok && typeof retry.pid === "number") {
61
64
  return { ok: true, pid: retry.pid, reused: false };
62
65
  }
@@ -84,7 +84,8 @@ function isAlive(info) {
84
84
  if (!isProcessRunning(info.pid))
85
85
  return false;
86
86
  // If heartbeat exists and is stale, treat as dead (possibly deadlocked)
87
- if (info.lastHeartbeat && Date.now() - info.lastHeartbeat > HEARTBEAT_STALE_MS) {
87
+ if (info.lastHeartbeat &&
88
+ Date.now() - info.lastHeartbeat > HEARTBEAT_STALE_MS) {
88
89
  return false;
89
90
  }
90
91
  return true;
@@ -182,8 +182,13 @@ function mlxEmbed(texts) {
182
182
  const wasPreviouslyAvailable = mlxAvailable !== false;
183
183
  mlxAvailable = false;
184
184
  const now = Date.now();
185
- if (wasPreviouslyAvailable || now - lastMlxWarning >= MLX_WARNING_INTERVAL_MS) {
186
- console.error("[mlx] Embed server failed: bad response (ok=" + ok + ", hasVectors=" + !!(data === null || data === void 0 ? void 0 : data.vectors) + ")");
185
+ if (wasPreviouslyAvailable ||
186
+ now - lastMlxWarning >= MLX_WARNING_INTERVAL_MS) {
187
+ console.error("[mlx] Embed server failed: bad response (ok=" +
188
+ ok +
189
+ ", hasVectors=" +
190
+ !!(data === null || data === void 0 ? void 0 : data.vectors) +
191
+ ")");
187
192
  lastMlxWarning = now;
188
193
  }
189
194
  return null;
@@ -51,10 +51,10 @@ exports.isWorkerPoolInitialized = isWorkerPoolInitialized;
51
51
  * to ensure the ONNX Runtime segfaults do not crash the main process.
52
52
  */
53
53
  const childProcess = __importStar(require("node:child_process"));
54
- const logger_1 = require("../utils/logger");
55
54
  const fs = __importStar(require("node:fs"));
56
55
  const path = __importStar(require("node:path"));
57
56
  const config_1 = require("../../config");
57
+ const logger_1 = require("../utils/logger");
58
58
  function reviveBufferLike(input) {
59
59
  if (input &&
60
60
  typeof input === "object" &&
@@ -126,9 +126,7 @@ class ProcessWorker {
126
126
  // event). Guards against handleWorkerExit running twice when both events
127
127
  // fire for the same crash.
128
128
  this.cleanedUp = false;
129
- const memArgs = maxMemoryMb
130
- ? [`--max-old-space-size=${maxMemoryMb}`]
131
- : [];
129
+ const memArgs = maxMemoryMb ? [`--max-old-space-size=${maxMemoryMb}`] : [];
132
130
  this.child = childProcess.fork(modulePath, {
133
131
  execArgv: [...memArgs, ...execArgv],
134
132
  env: Object.assign({}, process.env),
@@ -167,7 +165,10 @@ const WORKER_RSS_RECYCLE_MB = (() => {
167
165
  // Methods that must skip the indexing backlog. encodeQuery is the search hot
168
166
  // path: a single query is ~17ms but waits behind every queued processFile.
169
167
  // rerank is similarly small and latency-sensitive.
170
- const PRIORITY_METHODS = new Set(["encodeQuery", "rerank"]);
168
+ const PRIORITY_METHODS = new Set([
169
+ "encodeQuery",
170
+ "rerank",
171
+ ]);
171
172
  class WorkerPool {
172
173
  constructor() {
173
174
  this.workers = [];
@@ -320,7 +321,9 @@ class WorkerPool {
320
321
  if (task.method === "processFile") {
321
322
  result = reviveProcessFileResult(result);
322
323
  }
323
- const elapsed = task.startTime ? `${Date.now() - task.startTime}ms` : "?ms";
324
+ const elapsed = task.startTime
325
+ ? `${Date.now() - task.startTime}ms`
326
+ : "?ms";
324
327
  const filePath = (_h = (_f = (_e = task.payload) === null || _e === void 0 ? void 0 : _e.path) !== null && _f !== void 0 ? _f : (_g = task.payload) === null || _g === void 0 ? void 0 : _g.absolutePath) !== null && _h !== void 0 ? _h : "";
325
328
  (0, logger_1.debug)("pool", `complete task=${task.id} method=${task.method} ${elapsed}${filePath ? ` file=${filePath}` : ""}`);
326
329
  task.resolve(result);
@@ -47,8 +47,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
48
  const node_process_1 = __importDefault(require("node:process"));
49
49
  node_process_1.default.title = "gmax-worker";
50
- const worker_1 = __importStar(require("./worker"));
51
50
  const logger_1 = require("../utils/logger");
51
+ const worker_1 = __importStar(require("./worker"));
52
52
  // Every outgoing message also carries `rss` (see send()).
53
53
  const send = (msg) => {
54
54
  if (node_process_1.default.send) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -14,6 +14,9 @@
14
14
  "bin": {
15
15
  "gmax": "dist/index.js"
16
16
  },
17
+ "engines": {
18
+ "node": ">=22.12.0"
19
+ },
17
20
  "scripts": {
18
21
  "postinstall": "node scripts/postinstall.js",
19
22
  "prebuild": "mkdir -p dist",
@@ -30,9 +33,10 @@
30
33
  "bench:recall:json": "GMAX_EVAL_JSON=1 npx tsx src/eval.ts",
31
34
  "bench:oss": "npx tsx src/eval-oss.ts all",
32
35
  "bench:oss:json": "GMAX_EVAL_JSON=1 npx tsx src/eval-oss.ts all",
33
- "format": "biome check --write .",
36
+ "format": "biome format --write .",
34
37
  "format:check": "biome check .",
35
38
  "lint": "biome lint .",
39
+ "lint:fix": "biome check --write .",
36
40
  "typecheck": "tsc --noEmit",
37
41
  "prepublishOnly": "pnpm build",
38
42
  "preversion": "pnpm test && pnpm typecheck",
@@ -71,38 +75,37 @@
71
75
  "license": "Apache-2.0",
72
76
  "description": "Semantic code search for coding agents. Local embeddings, LLM summaries, call graph tracing.",
73
77
  "dependencies": {
74
- "@clack/prompts": "^1.1.0",
75
- "@huggingface/transformers": "^4.0.0",
78
+ "@clack/prompts": "^1.6.0",
79
+ "@huggingface/transformers": "^4.2.0",
76
80
  "@lancedb/lancedb": "^0.30.0",
77
81
  "@modelcontextprotocol/sdk": "^1.29.0",
78
82
  "@parcel/watcher": "^2.5.6",
79
83
  "apache-arrow": "^18.1.0",
80
84
  "chalk": "^5.6.2",
81
85
  "cli-highlight": "^2.1.11",
82
- "commander": "^14.0.2",
83
- "dotenv": "^17.2.3",
86
+ "commander": "^15.0.0",
87
+ "dotenv": "^17.4.2",
84
88
  "fast-glob": "^3.3.3",
85
89
  "ignore": "^7.0.5",
86
- "lmdb": "^3.5.2",
90
+ "lmdb": "^3.5.6",
87
91
  "onnxruntime-node": "1.24.3",
88
- "openai": "^6.33.0",
89
- "ora": "^9.3.0",
90
- "piscina": "^5.1.4",
92
+ "openai": "^6.45.0",
93
+ "ora": "^9.4.1",
91
94
  "proper-lockfile": "^4.1.2",
92
95
  "simsimd": "^6.5.5",
93
- "uuid": "^13.0.0",
94
- "web-tree-sitter": "^0.26.7",
95
- "zod": "^4.1.12"
96
+ "uuid": "^14.0.1",
97
+ "web-tree-sitter": "^0.26.9",
98
+ "zod": "^4.4.3"
96
99
  },
97
100
  "devDependencies": {
98
- "@anthropic-ai/claude-agent-sdk": "^0.2.87",
99
101
  "@biomejs/biome": "2.4.10",
100
- "@types/node": "^25.5.0",
102
+ "@types/node": "^26.0.1",
101
103
  "@types/proper-lockfile": "^4.1.4",
102
- "node-gyp": "^12.1.0",
104
+ "node-gyp": "^13.0.0",
103
105
  "ts-node": "^10.9.2",
104
- "typescript": "^6.0.2",
105
- "vite": "^8.0.3",
106
- "vitest": "^4.1.2"
106
+ "tsx": "^4.22.4",
107
+ "typescript": "^6.0.3",
108
+ "vite": "^8.1.0",
109
+ "vitest": "^4.1.9"
107
110
  }
108
111
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
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",
@@ -41,7 +41,10 @@ async function main() {
41
41
  if (!isProjectRegistered(newCwd)) return;
42
42
 
43
43
  try {
44
- execFileSync("gmax", ["watch", "--daemon", "-b"], { timeout: 5000, stdio: "ignore" });
44
+ execFileSync("gmax", ["watch", "--daemon", "-b"], {
45
+ timeout: 5000,
46
+ stdio: "ignore",
47
+ });
45
48
  } catch {
46
49
  try {
47
50
  execFileSync("gmax", ["watch", "-b"], { timeout: 5000, stdio: "ignore" });
@@ -28,7 +28,8 @@ const SNAKE_CASE = /_[a-z]/;
28
28
  const ALL_CAPS = /^[A-Z_]+$/;
29
29
  const HAS_DOT = /\./;
30
30
  const HAS_QUOTE = /['"]/;
31
- const CODE_KEYWORDS = /\b(class|function|const|let|var|import|export|return|extends|implements|interface|type|enum|struct|def|async|await)\b/;
31
+ const CODE_KEYWORDS =
32
+ /\b(class|function|const|let|var|import|export|return|extends|implements|interface|type|enum|struct|def|async|await)\b/;
32
33
 
33
34
  /**
34
35
  * Conservative heuristic: returns true only when the pattern is very
@@ -127,21 +127,35 @@ function startPythonServer(serverDir, scriptName, logName, processName) {
127
127
  VIRTUAL_ENV: "",
128
128
  CONDA_DEFAULT_ENV: "",
129
129
  GMAX_PROCESS_NAME: processName || logName,
130
- HF_TOKEN_PATH: process.env.HF_TOKEN_PATH || _path.join(require("node:os").homedir(), ".cache", "huggingface", "token"),
130
+ HF_TOKEN_PATH:
131
+ process.env.HF_TOKEN_PATH ||
132
+ _path.join(
133
+ require("node:os").homedir(),
134
+ ".cache",
135
+ "huggingface",
136
+ "token",
137
+ ),
131
138
  },
132
139
  });
133
140
  child.unref();
134
141
  }
135
142
 
136
143
  // --- Crash counter (Item 14) ---
137
- const CRASH_FILE = _path.join(require("node:os").homedir(), ".gmax", "mlx-embed-crashes.json");
144
+ const CRASH_FILE = _path.join(
145
+ require("node:os").homedir(),
146
+ ".gmax",
147
+ "mlx-embed-crashes.json",
148
+ );
138
149
  const MAX_CRASHES = 3;
139
150
  const CRASH_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
140
151
 
141
152
  function readCrashCount() {
142
153
  try {
143
154
  const data = JSON.parse(fs.readFileSync(CRASH_FILE, "utf-8"));
144
- if (data.lastCrash && Date.now() - new Date(data.lastCrash).getTime() > CRASH_WINDOW_MS) {
155
+ if (
156
+ data.lastCrash &&
157
+ Date.now() - new Date(data.lastCrash).getTime() > CRASH_WINDOW_MS
158
+ ) {
145
159
  return { count: 0, lastCrash: null }; // Window expired, reset
146
160
  }
147
161
  return { count: data.count || 0, lastCrash: data.lastCrash };
@@ -157,7 +171,9 @@ function writeCrashCount(count, lastCrash) {
157
171
  }
158
172
 
159
173
  function resetCrashCount() {
160
- try { fs.unlinkSync(CRASH_FILE); } catch {}
174
+ try {
175
+ fs.unlinkSync(CRASH_FILE);
176
+ } catch {}
161
177
  }
162
178
 
163
179
  function isProjectRegistered() {
@@ -167,7 +183,9 @@ function isProjectRegistered() {
167
183
  ".gmax",
168
184
  "projects.json",
169
185
  );
170
- const projects = JSON.parse(require("node:fs").readFileSync(projectsPath, "utf-8"));
186
+ const projects = JSON.parse(
187
+ require("node:fs").readFileSync(projectsPath, "utf-8"),
188
+ );
171
189
  const cwd = process.cwd();
172
190
  return projects.some((p) => cwd.startsWith(p.root));
173
191
  } catch {
@@ -178,7 +196,10 @@ function isProjectRegistered() {
178
196
  function startWatcher() {
179
197
  if (!isProjectRegistered()) return;
180
198
  try {
181
- execFileSync("gmax", ["watch", "--daemon", "-b"], { timeout: 5000, stdio: "ignore" });
199
+ execFileSync("gmax", ["watch", "--daemon", "-b"], {
200
+ timeout: 5000,
201
+ stdio: "ignore",
202
+ });
182
203
  } catch {
183
204
  // Fallback to per-project mode (older gmax without --daemon)
184
205
  try {
@@ -200,14 +221,19 @@ async function main() {
200
221
  if (serverDir && !embedRunning) {
201
222
  const crashes = readCrashCount();
202
223
  if (crashes.count < MAX_CRASHES) {
203
- startPythonServer(serverDir, "server.py", "mlx-embed-server", "gmax-embed");
224
+ startPythonServer(
225
+ serverDir,
226
+ "server.py",
227
+ "mlx-embed-server",
228
+ "gmax-embed",
229
+ );
204
230
 
205
231
  // Fire-and-forget health verification (Item 13)
206
232
  (async () => {
207
233
  const maxAttempts = 5;
208
234
  const delayMs = 2000;
209
235
  for (let i = 0; i < maxAttempts; i++) {
210
- await new Promise(r => setTimeout(r, delayMs));
236
+ await new Promise((r) => setTimeout(r, delayMs));
211
237
  if (await isServerRunning(8100)) {
212
238
  resetCrashCount();
213
239
  return;
@@ -223,8 +249,17 @@ async function main() {
223
249
  }
224
250
 
225
251
  // Start LLM summarizer server (port 8101) — opt-in only
226
- if (process.env.GMAX_SUMMARIZER === "1" && serverDir && !(await isServerRunning(8101))) {
227
- startPythonServer(serverDir, "summarizer.py", "mlx-summarizer", "gmax-summarizer");
252
+ if (
253
+ process.env.GMAX_SUMMARIZER === "1" &&
254
+ serverDir &&
255
+ !(await isServerRunning(8101))
256
+ ) {
257
+ startPythonServer(
258
+ serverDir,
259
+ "summarizer.py",
260
+ "mlx-summarizer",
261
+ "gmax-summarizer",
262
+ );
228
263
  }
229
264
  }
230
265
 
@@ -33,10 +33,7 @@ function isProjectRegistered() {
33
33
  }
34
34
 
35
35
  // Agents that already know about gmax or can't use Bash
36
- const SKIP_AGENT_TYPES = [
37
- "grepmax:semantic-explore",
38
- "statusline-setup",
39
- ];
36
+ const SKIP_AGENT_TYPES = ["grepmax:semantic-explore", "statusline-setup"];
40
37
 
41
38
  async function main() {
42
39
  const input = await readStdin();