grepmax 0.18.1 → 0.20.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 (78) hide show
  1. package/.claude-plugin/marketplace.json +23 -0
  2. package/README.md +28 -3
  3. package/dist/commands/add.js +4 -2
  4. package/dist/commands/claude-code.js +1 -1
  5. package/dist/commands/codex.js +3 -1
  6. package/dist/commands/context.js +1 -1
  7. package/dist/commands/dead.js +2 -6
  8. package/dist/commands/doctor.js +26 -7
  9. package/dist/commands/extract.js +3 -5
  10. package/dist/commands/impact.js +6 -4
  11. package/dist/commands/index.js +7 -2
  12. package/dist/commands/log.js +4 -2
  13. package/dist/commands/mcp.js +594 -722
  14. package/dist/commands/peek.js +15 -9
  15. package/dist/commands/plugin.js +36 -22
  16. package/dist/commands/project.js +16 -6
  17. package/dist/commands/related.js +8 -9
  18. package/dist/commands/remove.js +9 -2
  19. package/dist/commands/review.js +1 -1
  20. package/dist/commands/setup.js +4 -2
  21. package/dist/commands/similar.js +4 -4
  22. package/dist/commands/status.js +6 -4
  23. package/dist/commands/summarize.js +6 -4
  24. package/dist/commands/symbols.js +1 -1
  25. package/dist/commands/test-find.js +2 -2
  26. package/dist/commands/trace.js +12 -5
  27. package/dist/commands/watch.js +33 -9
  28. package/dist/eval-graph-nav.js +4 -1
  29. package/dist/eval-graph-recovery-probe.js +56 -14
  30. package/dist/eval-graph-spotcheck.js +10 -2
  31. package/dist/eval-graph-totals.js +5 -2
  32. package/dist/eval-oss.js +212 -37
  33. package/dist/eval-seed.js +13 -4
  34. package/dist/index.js +9 -9
  35. package/dist/lib/daemon/daemon.js +32 -13
  36. package/dist/lib/daemon/ipc-handler.js +24 -5
  37. package/dist/lib/daemon/mlx-server-manager.js +11 -3
  38. package/dist/lib/daemon/process-manager.js +1 -2
  39. package/dist/lib/daemon/watcher-manager.js +22 -6
  40. package/dist/lib/graph/callsites.js +151 -25
  41. package/dist/lib/graph/graph-builder.js +2 -2
  42. package/dist/lib/graph/impact.js +6 -6
  43. package/dist/lib/index/batch-processor.js +16 -6
  44. package/dist/lib/index/chunker.js +2 -0
  45. package/dist/lib/index/syncer.js +22 -10
  46. package/dist/lib/index/watcher-batch.js +10 -1
  47. package/dist/lib/llm/config.js +2 -1
  48. package/dist/lib/llm/diff.js +56 -11
  49. package/dist/lib/llm/investigate.js +52 -11
  50. package/dist/lib/llm/review.js +21 -8
  51. package/dist/lib/llm/server.js +25 -9
  52. package/dist/lib/llm/tools.js +1 -1
  53. package/dist/lib/output/agent-search-formatter.js +25 -3
  54. package/dist/lib/output/index-state-footer.js +1 -1
  55. package/dist/lib/review/risk.js +2 -4
  56. package/dist/lib/search/pagerank.js +1 -1
  57. package/dist/lib/search/searcher.js +43 -17
  58. package/dist/lib/search/seed-weight.js +4 -1
  59. package/dist/lib/skeleton/symbol-extractor.js +2 -1
  60. package/dist/lib/store/vector-db.js +21 -10
  61. package/dist/lib/utils/cross-project.js +5 -1
  62. package/dist/lib/utils/daemon-client.js +39 -1
  63. package/dist/lib/utils/filter-builder.js +22 -0
  64. package/dist/lib/utils/git.js +10 -2
  65. package/dist/lib/utils/project-registry.js +3 -2
  66. package/dist/lib/utils/scope-filter.js +6 -6
  67. package/dist/lib/utils/watcher-launcher.js +4 -1
  68. package/dist/lib/utils/watcher-store.js +2 -1
  69. package/dist/lib/workers/embeddings/granite.js +4 -1
  70. package/dist/lib/workers/embeddings/mlx-client.js +7 -2
  71. package/dist/lib/workers/pool.js +30 -7
  72. package/dist/lib/workers/process-child.js +1 -1
  73. package/package.json +23 -19
  74. package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
  75. package/plugins/grepmax/hooks/cwd-changed.js +4 -1
  76. package/plugins/grepmax/hooks/pre-grep.js +2 -1
  77. package/plugins/grepmax/hooks/start.js +45 -10
  78. package/plugins/grepmax/hooks/subagent-start.js +1 -4
@@ -53,8 +53,8 @@ const exit_1 = require("../lib/utils/exit");
53
53
  const filter_builder_1 = require("../lib/utils/filter-builder");
54
54
  const language_1 = require("../lib/utils/language");
55
55
  const project_registry_1 = require("../lib/utils/project-registry");
56
- const stale_hint_1 = require("../lib/utils/stale-hint");
57
56
  const project_root_1 = require("../lib/utils/project-root");
57
+ const stale_hint_1 = require("../lib/utils/stale-hint");
58
58
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
59
59
  const style = {
60
60
  bold: (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s),
@@ -117,8 +117,8 @@ exports.peek = new commander_1.Command("peek")
117
117
  .argument("<symbol>", "The symbol to peek at")
118
118
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
119
119
  .option("--root <dir>", "Project root directory")
120
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
121
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
120
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
121
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
122
122
  .option("--agent", "Compact output for AI agents", false)
123
123
  .option("--no-tests", "Suppress the tests footer")
124
124
  .action((symbol, opts) => __awaiter(void 0, void 0, void 0, function* () {
@@ -196,9 +196,7 @@ exports.peek = new commander_1.Command("peek")
196
196
  return;
197
197
  }
198
198
  const center = graph.center;
199
- const rel = (p) => p.startsWith(projectRoot)
200
- ? p.slice(projectRoot.length + 1)
201
- : p;
199
+ const rel = (p) => p.startsWith(projectRoot) ? p.slice(projectRoot.length + 1) : p;
202
200
  // Get chunk metadata for is_exported and end_line
203
201
  const table = yield vectorDb.ensureTable();
204
202
  const metaRows = yield table
@@ -208,8 +206,12 @@ exports.peek = new commander_1.Command("peek")
208
206
  .limit(1)
209
207
  .toArray();
210
208
  const exported = metaRows.length > 0 && Boolean(metaRows[0].is_exported);
211
- const startLine = metaRows.length > 0 ? Number(metaRows[0].start_line || 0) : center.line;
212
- const endLine = metaRows.length > 0 ? Number(metaRows[0].end_line || 0) : center.line;
209
+ const startLine = metaRows.length > 0
210
+ ? Number(metaRows[0].start_line || 0)
211
+ : center.line;
212
+ const endLine = metaRows.length > 0
213
+ ? Number(metaRows[0].end_line || 0)
214
+ : center.line;
213
215
  // Get multi-hop callers if depth > 1
214
216
  let callerList;
215
217
  if (depth > 1) {
@@ -218,7 +220,11 @@ exports.peek = new commander_1.Command("peek")
218
220
  const flat = [];
219
221
  function walkCallers(tree) {
220
222
  for (const t of tree) {
221
- flat.push({ symbol: t.node.symbol, file: t.node.file, line: t.node.line });
223
+ flat.push({
224
+ symbol: t.node.symbol,
225
+ file: t.node.file,
226
+ line: t.node.line,
227
+ });
222
228
  walkCallers(t.callers);
223
229
  }
224
230
  }
@@ -43,6 +43,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.plugin = void 0;
46
+ exports.installAll = installAll;
46
47
  const node_child_process_1 = require("node:child_process");
47
48
  const fs = __importStar(require("node:fs"));
48
49
  const os = __importStar(require("node:os"));
@@ -107,7 +108,7 @@ function getClients() {
107
108
  isInstalled: () => {
108
109
  const p = path.join(os.homedir(), ".codex", "AGENTS.md");
109
110
  try {
110
- return fs.existsSync(p) && fs.readFileSync(p, "utf-8").includes("gmax");
111
+ return (fs.existsSync(p) && fs.readFileSync(p, "utf-8").includes("gmax"));
111
112
  }
112
113
  catch (_a) {
113
114
  return false;
@@ -139,6 +140,39 @@ function getClients() {
139
140
  },
140
141
  ];
141
142
  }
143
+ /**
144
+ * Install gmax plugins for every detected client; returns the number installed.
145
+ * Deliberately does NOT call gracefulExit — callers decide how to finish. This
146
+ * lets `gmax setup` keep running (and print its outro) after installing, while
147
+ * the `add` subcommand still exits the process itself.
148
+ */
149
+ function installAll() {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ const clients = getClients();
152
+ console.log("gmax plugin add — detecting clients...\n");
153
+ let installed = 0;
154
+ for (const client of clients) {
155
+ if (!client.detect()) {
156
+ console.log(` skip ${client.name} — not found`);
157
+ continue;
158
+ }
159
+ try {
160
+ yield client.install();
161
+ installed++;
162
+ }
163
+ catch (err) {
164
+ console.error(` FAIL ${client.name} — ${err instanceof Error ? err.message : String(err)}`);
165
+ }
166
+ }
167
+ if (installed === 0) {
168
+ console.log("\nNo supported clients found. Install one of: claude, opencode, codex, droid");
169
+ }
170
+ else {
171
+ console.log(`\n${installed} plugin(s) installed.`);
172
+ }
173
+ return installed;
174
+ });
175
+ }
142
176
  // --- Subcommands ---
143
177
  const addCmd = new commander_1.Command("add")
144
178
  .description("Install or update gmax plugins")
@@ -164,27 +198,7 @@ const addCmd = new commander_1.Command("add")
164
198
  return;
165
199
  }
166
200
  // Install all detected
167
- console.log("gmax plugin add — detecting clients...\n");
168
- let installed = 0;
169
- for (const client of clients) {
170
- if (!client.detect()) {
171
- console.log(` skip ${client.name} — not found`);
172
- continue;
173
- }
174
- try {
175
- yield client.install();
176
- installed++;
177
- }
178
- catch (err) {
179
- console.error(` FAIL ${client.name} — ${err instanceof Error ? err.message : String(err)}`);
180
- }
181
- }
182
- if (installed === 0) {
183
- console.log("\nNo supported clients found. Install one of: claude, opencode, codex, droid");
184
- }
185
- else {
186
- console.log(`\n${installed} plugin(s) installed.`);
187
- }
201
+ yield installAll();
188
202
  yield (0, exit_1.gracefulExit)();
189
203
  }));
190
204
  const removeCmd = new commander_1.Command("remove")
@@ -47,11 +47,11 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const callsites_1 = require("../lib/graph/callsites");
49
49
  const vector_db_1 = require("../lib/store/vector-db");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
50
+ const arrow_1 = require("../lib/utils/arrow");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
- const arrow_1 = require("../lib/utils/arrow");
55
55
  exports.project = new commander_1.Command("project")
56
56
  .description("Show project overview — languages, structure, key symbols")
57
57
  .option("--root <dir>", "Project root (defaults to current directory)")
@@ -79,7 +79,7 @@ exports.project = new commander_1.Command("project")
79
79
  "defined_symbols",
80
80
  "referenced_symbols",
81
81
  ])
82
- .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
82
+ .where((0, filter_builder_1.pathStartsWith)(prefix))
83
83
  .limit(200000)
84
84
  .toArray();
85
85
  if (rows.length === 0) {
@@ -122,7 +122,10 @@ exports.project = new commander_1.Command("project")
122
122
  definedInProject.add(d);
123
123
  for (const ref of refs)
124
124
  symbolRefs.set(ref, (symbolRefs.get(ref) || 0) + 1);
125
- if (exported && role === "ORCHESTRATION" && complexity >= 5 && defs.length > 0) {
125
+ if (exported &&
126
+ role === "ORCHESTRATION" &&
127
+ complexity >= 5 &&
128
+ defs.length > 0) {
126
129
  const epKey = `${defs[0]}:${p}`;
127
130
  if (!seenEntryPoints.has(epKey)) {
128
131
  seenEntryPoints.add(epKey);
@@ -152,12 +155,19 @@ exports.project = new commander_1.Command("project")
152
155
  console.log(`files\t${files.size}`);
153
156
  console.log(`last_indexed\t${(_b = proj === null || proj === void 0 ? void 0 : proj.lastIndexed) !== null && _b !== void 0 ? _b : "unknown"}`);
154
157
  console.log(`languages\t${extEntries.map(([ext]) => ext).join(",")}`);
155
- console.log(`top_dirs\t${Array.from(dirCounts.entries()).sort((a, b) => b[1].chunks - a[1].chunks).slice(0, 8).map(([d]) => d).join(",")}`);
158
+ console.log(`top_dirs\t${Array.from(dirCounts.entries())
159
+ .sort((a, b) => b[1].chunks - a[1].chunks)
160
+ .slice(0, 8)
161
+ .map(([d]) => d)
162
+ .join(",")}`);
156
163
  if (topSymbols.length > 0) {
157
164
  console.log(`key_symbols\t${topSymbols.map(([s]) => s).join(",")}`);
158
165
  }
159
166
  if (entryPoints.length > 0) {
160
- console.log(`entry_points\t${entryPoints.slice(0, 10).map((e) => e.symbol).join(",")}`);
167
+ console.log(`entry_points\t${entryPoints
168
+ .slice(0, 10)
169
+ .map((e) => e.symbol)
170
+ .join(",")}`);
161
171
  }
162
172
  }
163
173
  else {
@@ -47,20 +47,20 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const vector_db_1 = require("../lib/store/vector-db");
49
49
  const agent_errors_1 = require("../lib/utils/agent-errors");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
50
+ const arrow_1 = require("../lib/utils/arrow");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const query_timeout_1 = require("../lib/utils/query-timeout");
55
56
  const stale_hint_1 = require("../lib/utils/stale-hint");
56
- const arrow_1 = require("../lib/utils/arrow");
57
57
  exports.related = new commander_1.Command("related")
58
58
  .description("Find files related by shared symbol references")
59
59
  .argument("<file>", "File path relative to project root")
60
60
  .option("-l, --limit <n>", "Max results per direction (default 10)", "10")
61
61
  .option("--root <dir>", "Project root directory")
62
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
63
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
62
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
63
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
64
64
  .option("--agent", "Compact output for AI agents", false)
65
65
  .action((file, opts) => __awaiter(void 0, void 0, void 0, function* () {
66
66
  var _a;
@@ -158,13 +158,14 @@ exports.related = new commander_1.Command("related")
158
158
  "common",
159
159
  "shared",
160
160
  ]);
161
- let mentions = [];
161
+ const mentions = [];
162
162
  let basename = "";
163
163
  let basenameRejected = false;
164
164
  if (topDeps.length === 0 && topRevs.length === 0) {
165
165
  const ext = path.extname(absPath);
166
166
  basename = path.basename(absPath, ext);
167
- if (basename.length < 4 || GENERIC_BASENAMES.has(basename.toLowerCase())) {
167
+ if (basename.length < 4 ||
168
+ GENERIC_BASENAMES.has(basename.toLowerCase())) {
168
169
  basenameRejected = true;
169
170
  }
170
171
  else {
@@ -190,9 +191,7 @@ exports.related = new commander_1.Command("related")
190
191
  }
191
192
  }
192
193
  if (opts.agent) {
193
- const rel = (p) => p.startsWith(`${projectRoot}/`)
194
- ? p.slice(projectRoot.length + 1)
195
- : p;
194
+ const rel = (p) => p.startsWith(`${projectRoot}/`) ? p.slice(projectRoot.length + 1) : p;
196
195
  for (const [p, count] of topDeps) {
197
196
  console.log(`dep: ${rel(p)}\t${count}`);
198
197
  }
@@ -133,10 +133,17 @@ Examples:
133
133
  (0, watcher_store_1.unregisterWatcher)(watcher.pid);
134
134
  }
135
135
  const paths = (0, project_root_1.ensureProjectPaths)(projectRoot);
136
+ // Slash-terminate so removing /repo/app can't also delete the sibling
137
+ // /repo/app2 — VectorDB matches `path LIKE prefix%` and MetaCache
138
+ // matches `key.startsWith(prefix)`, both of which bleed across siblings
139
+ // without the trailing slash. Mirrors the daemon remove path.
140
+ const rootPrefix = projectRoot.endsWith("/")
141
+ ? projectRoot
142
+ : `${projectRoot}/`;
136
143
  vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
137
- yield vectorDb.deletePathsWithPrefix(projectRoot);
144
+ yield vectorDb.deletePathsWithPrefix(rootPrefix);
138
145
  metaCache = new meta_cache_1.MetaCache(paths.lmdbPath);
139
- const keys = yield metaCache.getKeysWithPrefix(projectRoot);
146
+ const keys = yield metaCache.getKeysWithPrefix(rootPrefix);
140
147
  for (const key of keys) {
141
148
  metaCache.delete(key);
142
149
  }
@@ -85,7 +85,7 @@ Subcommands:
85
85
  const { VectorDB } = yield Promise.resolve().then(() => __importStar(require("../lib/store/vector-db")));
86
86
  const { GraphBuilder } = yield Promise.resolve().then(() => __importStar(require("../lib/graph/graph-builder")));
87
87
  const { ensureProjectPaths } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/project-root")));
88
- const { gatherRiskInputs, computeRiskTable, formatRiskTable, } = yield Promise.resolve().then(() => __importStar(require("../lib/review/risk")));
88
+ const { gatherRiskInputs, computeRiskTable, formatRiskTable } = yield Promise.resolve().then(() => __importStar(require("../lib/review/risk")));
89
89
  const paths = ensureProjectPaths(projectRoot);
90
90
  const vectorDb = new VectorDB(paths.lancedbDir);
91
91
  try {
@@ -154,8 +154,10 @@ exports.setup = new commander_1.Command("setup")
154
154
  initialValue: true,
155
155
  });
156
156
  if (!p.isCancel(installPlugins) && installPlugins) {
157
- const { plugin: pluginCmd } = yield Promise.resolve().then(() => __importStar(require("./plugin")));
158
- yield pluginCmd.parseAsync(["node", "gmax"]);
157
+ // Call installAll() directly, not `plugin` (bare status) or the `add`
158
+ // subcommand — both gracefulExit() before setup's outro can run.
159
+ const { installAll } = yield Promise.resolve().then(() => __importStar(require("./plugin")));
160
+ yield installAll();
159
161
  }
160
162
  p.outro(`Ready — ${selectedTier.label}, ${embedMode === "gpu" ? "GPU" : "CPU"} mode`);
161
163
  yield (0, exit_1.gracefulExit)();
@@ -47,20 +47,20 @@ const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const vector_db_1 = require("../lib/store/vector-db");
49
49
  const agent_errors_1 = require("../lib/utils/agent-errors");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
50
+ const arrow_1 = require("../lib/utils/arrow");
51
51
  const exit_1 = require("../lib/utils/exit");
52
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
53
  const project_registry_1 = require("../lib/utils/project-registry");
53
54
  const project_root_1 = require("../lib/utils/project-root");
54
55
  const stale_hint_1 = require("../lib/utils/stale-hint");
55
- const arrow_1 = require("../lib/utils/arrow");
56
56
  exports.similar = new commander_1.Command("similar")
57
57
  .description("Find semantically similar code to a symbol or file")
58
58
  .argument("<target>", "Symbol name or file path")
59
59
  .option("-m, --max-count <n>", "Max results (default 5)", "5")
60
60
  .option("--threshold <score>", "Min similarity 0-1 (default 0)")
61
61
  .option("--root <dir>", "Project root directory")
62
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
63
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
62
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
63
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
64
64
  .option("--agent", "Compact output for AI agents", false)
65
65
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
66
66
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
@@ -47,8 +47,8 @@ const os = __importStar(require("node:os"));
47
47
  const commander_1 = require("commander");
48
48
  const config_1 = require("../config");
49
49
  const index_config_1 = require("../lib/index/index-config");
50
- const filter_builder_1 = require("../lib/utils/filter-builder");
51
50
  const exit_1 = require("../lib/utils/exit");
51
+ const filter_builder_1 = require("../lib/utils/filter-builder");
52
52
  const lock_1 = require("../lib/utils/lock");
53
53
  const project_registry_1 = require("../lib/utils/project-registry");
54
54
  const project_root_1 = require("../lib/utils/project-root");
@@ -115,17 +115,19 @@ Examples:
115
115
  const db = new VectorDB(config_1.PATHS.lancedbDir);
116
116
  const table = yield db.ensureTable();
117
117
  for (const project of projects) {
118
- const prefix = project.root.endsWith("/") ? project.root : `${project.root}/`;
118
+ const prefix = project.root.endsWith("/")
119
+ ? project.root
120
+ : `${project.root}/`;
119
121
  const rows = yield table
120
122
  .query()
121
123
  .select(["id"])
122
- .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
124
+ .where((0, filter_builder_1.pathStartsWith)(prefix))
123
125
  .toArray();
124
126
  chunkCounts.set(project.root, rows.length);
125
127
  }
126
128
  yield db.close();
127
129
  }
128
- catch (err) {
130
+ catch (_e) {
129
131
  console.warn(`[status] Failed to query LanceDB for live chunk counts, using cached counts`);
130
132
  }
131
133
  if (projects.length === 0) {
@@ -56,15 +56,17 @@ exports.summarize = new commander_1.Command("summarize")
56
56
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
57
57
  var _a, _b, _c;
58
58
  const paths = (0, project_root_1.ensureProjectPaths)(process.cwd());
59
- const rootPrefix = options.path
60
- ? `${path.resolve(options.path)}/`
61
- : "";
59
+ const rootPrefix = options.path ? `${path.resolve(options.path)}/` : "";
62
60
  const { spinner } = (0, sync_helpers_1.createIndexingSpinner)("", "Summarizing...");
63
61
  const { isDaemonRunning, sendStreamingCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
64
62
  if (yield isDaemonRunning()) {
65
63
  // Daemon mode: IPC streaming
66
64
  try {
67
- const done = yield sendStreamingCommand({ cmd: "summarize", root: paths.root, pathPrefix: rootPrefix || undefined }, (msg) => {
65
+ const done = yield sendStreamingCommand({
66
+ cmd: "summarize",
67
+ root: paths.root,
68
+ pathPrefix: rootPrefix || undefined,
69
+ }, (msg) => {
68
70
  var _a, _b;
69
71
  spinner.text = `Summarizing... (${(_a = msg.summarized) !== null && _a !== void 0 ? _a : 0}/${(_b = msg.total) !== null && _b !== void 0 ? _b : 0})`;
70
72
  });
@@ -86,7 +86,7 @@ function collectSymbols(options) {
86
86
  const absPrefix = path.isAbsolute(options.pathPrefix)
87
87
  ? options.pathPrefix
88
88
  : path.resolve(options.projectRoot, options.pathPrefix);
89
- query = query.where(`path LIKE '${(0, filter_builder_1.escapeSqlString)((0, filter_builder_1.normalizePath)(absPrefix))}%'`);
89
+ query = query.where((0, filter_builder_1.pathStartsWith)((0, filter_builder_1.normalizePath)(absPrefix)));
90
90
  }
91
91
  const rows = yield query.toArray();
92
92
  const map = new Map();
@@ -56,8 +56,8 @@ exports.testFind = new commander_1.Command("test")
56
56
  .argument("<target>", "Symbol name or file path")
57
57
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
58
58
  .option("--root <dir>", "Project root directory")
59
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
60
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
59
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
60
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
61
61
  .option("--agent", "Compact output for AI agents", false)
62
62
  .action((target, opts) => __awaiter(void 0, void 0, void 0, function* () {
63
63
  var _a;
@@ -51,8 +51,8 @@ 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");
55
54
  const project_root_1 = require("../lib/utils/project-root");
55
+ const stale_hint_1 = require("../lib/utils/stale-hint");
56
56
  const useColors = process.stdout.isTTY && !process.env.NO_COLOR;
57
57
  const dim = (s) => (useColors ? `\x1b[2m${s}\x1b[22m` : s);
58
58
  const bold = (s) => (useColors ? `\x1b[1m${s}\x1b[22m` : s);
@@ -87,7 +87,12 @@ function formatTraceAgent(graph, projectRoot, raw = false) {
87
87
  const key = `${t.node.symbol}\t${t.node.file}`;
88
88
  let g = groups.get(key);
89
89
  if (!g) {
90
- g = { symbol: t.node.symbol, file: t.node.file, lineSet: new Set(), sub: [] };
90
+ g = {
91
+ symbol: t.node.symbol,
92
+ file: t.node.file,
93
+ lineSet: new Set(),
94
+ sub: [],
95
+ };
91
96
  groups.set(key, g);
92
97
  order.push(key);
93
98
  }
@@ -149,7 +154,9 @@ function formatInboundAgent(center, tree, projectRoot, withSnippets) {
149
154
  var _a, _b;
150
155
  for (const n of nodes) {
151
156
  const prefix = " ".repeat(depth);
152
- const loc = n.file ? `${rel(n.file)}:${((_a = n.snippetLine) !== null && _a !== void 0 ? _a : n.line) + 1}` : "(not indexed)";
157
+ const loc = n.file
158
+ ? `${rel(n.file)}:${((_a = n.snippetLine) !== null && _a !== void 0 ? _a : n.line) + 1}`
159
+ : "(not indexed)";
153
160
  const cols = withSnippets
154
161
  ? `${loc}\t${n.symbol}\t${(_b = n.snippet) !== null && _b !== void 0 ? _b : ""}`
155
162
  : `${loc}\t${n.symbol}`;
@@ -202,8 +209,8 @@ exports.trace = new commander_1.Command("trace")
202
209
  .argument("<symbol>", "The symbol to trace")
203
210
  .option("-d, --depth <n>", "Caller traversal depth (default 1, max 3)", "1")
204
211
  .option("--root <dir>", "Project root directory")
205
- .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
206
- .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => (prev ? [...prev, value] : [value]))
212
+ .option("--in <subpath>", "Restrict to a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
213
+ .option("--exclude <subpath>", "Exclude a sub-path of the project (repeatable)", (value, prev) => prev ? [...prev, value] : [value])
207
214
  .option("--agent", "Compact output for AI agents", false)
208
215
  .option("--inbound", "Show only callers, with call-site snippets", false)
209
216
  .option("--no-snippets", "Suppress call-site snippets in --inbound output")
@@ -43,18 +43,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.watch = void 0;
46
- const fs = __importStar(require("node:fs"));
47
46
  const node_child_process_1 = require("node:child_process");
47
+ const fs = __importStar(require("node:fs"));
48
48
  const path = __importStar(require("node:path"));
49
49
  const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
51
  const index_config_1 = require("../lib/index/index-config");
52
- const filter_builder_1 = require("../lib/utils/filter-builder");
53
52
  const syncer_1 = require("../lib/index/syncer");
54
53
  const watcher_1 = require("../lib/index/watcher");
55
54
  const meta_cache_1 = require("../lib/store/meta-cache");
56
55
  const vector_db_1 = require("../lib/store/vector-db");
57
56
  const exit_1 = require("../lib/utils/exit");
57
+ const filter_builder_1 = require("../lib/utils/filter-builder");
58
58
  const log_rotate_1 = require("../lib/utils/log-rotate");
59
59
  const process_1 = require("../lib/utils/process");
60
60
  const project_registry_1 = require("../lib/utils/project-registry");
@@ -81,7 +81,7 @@ exports.watch = new commander_1.Command("watch")
81
81
  // Skip spawn if daemon already running at the same version.
82
82
  // If version mismatches (e.g. after npm install -g), shut down the old
83
83
  // daemon so we can start a fresh one with the new code.
84
- const { isDaemonRunning, isDaemonHeartbeatFresh, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
84
+ const { isDaemonRunning, isDaemonHeartbeatFresh, sendDaemonCommand, readDaemonPid, waitForProcessExit, } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
85
85
  if (yield isDaemonRunning()) {
86
86
  const cliVersion = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf-8")).version;
87
87
  const resp = yield sendDaemonCommand({ cmd: "ping" });
@@ -89,6 +89,12 @@ exports.watch = new commander_1.Command("watch")
89
89
  process.exit(0);
90
90
  }
91
91
  console.log(`Daemon version mismatch (${resp.version} → ${cliVersion}), restarting...`);
92
+ // Capture the old PID before shutdown removes the PID file, so we
93
+ // can wait for the process to fully EXIT rather than guessing with a
94
+ // fixed sleep. Spawning the successor while the old daemon is still
95
+ // draining lets the successor's killStaleProcesses() classify it as
96
+ // stale and SIGKILL it mid-cleanup.
97
+ const oldPid = readDaemonPid();
92
98
  yield sendDaemonCommand({
93
99
  cmd: "shutdown",
94
100
  reason: "version-mismatch",
@@ -97,8 +103,15 @@ exports.watch = new commander_1.Command("watch")
97
103
  from_version: cliVersion,
98
104
  from_argv: process.argv.slice(0, 4),
99
105
  });
100
- // Brief wait for old daemon to release socket/lock
101
- yield new Promise((r) => setTimeout(r, 2000));
106
+ if (oldPid) {
107
+ const exited = yield waitForProcessExit(oldPid, 20000);
108
+ if (!exited) {
109
+ console.log(`Old daemon (PID ${oldPid}) still draining after 20s — starting anyway`);
110
+ }
111
+ }
112
+ else {
113
+ yield new Promise((r) => setTimeout(r, 2000));
114
+ }
102
115
  }
103
116
  else if (isDaemonHeartbeatFresh()) {
104
117
  // Ping failed but daemon.lock mtime is fresh — another daemon is
@@ -131,6 +144,13 @@ exports.watch = new commander_1.Command("watch")
131
144
  process.exit(0);
132
145
  }
133
146
  console.error("[daemon] Failed to start:", err);
147
+ // Tear down any half-opened state — the listening socket, PID, and
148
+ // lock — so we don't leave a zombie that answers pings but has no
149
+ // resources. The socket also keeps the event loop alive otherwise.
150
+ try {
151
+ yield daemon.shutdown();
152
+ }
153
+ catch (_c) { }
134
154
  process.exitCode = 1;
135
155
  return;
136
156
  }
@@ -148,7 +168,7 @@ exports.watch = new commander_1.Command("watch")
148
168
  // --- Per-project mode ---
149
169
  const projectRoot = options.path
150
170
  ? path.resolve(options.path)
151
- : (_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd();
171
+ : ((_a = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _a !== void 0 ? _a : process.cwd());
152
172
  const projectName = path.basename(projectRoot);
153
173
  // Check if watcher already running (exact match or parent covering this dir)
154
174
  const existing = (_b = (0, watcher_store_1.getWatcherForProject)(projectRoot)) !== null && _b !== void 0 ? _b : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
@@ -195,11 +215,13 @@ exports.watch = new commander_1.Command("watch")
195
215
  // Initial sync if this directory isn't indexed yet
196
216
  const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
197
217
  const table = yield vectorDb.ensureTable();
198
- const prefix = projectRoot.endsWith("/") ? projectRoot : `${projectRoot}/`;
218
+ const prefix = projectRoot.endsWith("/")
219
+ ? projectRoot
220
+ : `${projectRoot}/`;
199
221
  const indexed = yield table
200
222
  .query()
201
223
  .select(["id"])
202
- .where(`path LIKE '${(0, filter_builder_1.escapeSqlString)(prefix)}%'`)
224
+ .where((0, filter_builder_1.pathStartsWith)(prefix))
203
225
  .limit(1)
204
226
  .toArray();
205
227
  if (indexed.length === 0) {
@@ -325,7 +347,9 @@ exports.watch
325
347
  let parentCmd = "?";
326
348
  try {
327
349
  const { execSync } = yield Promise.resolve().then(() => __importStar(require("node:child_process")));
328
- parentCmd = execSync(`ps -o command= -p ${process.ppid}`, { encoding: "utf8" }).trim();
350
+ parentCmd = execSync(`ps -o command= -p ${process.ppid}`, {
351
+ encoding: "utf8",
352
+ }).trim();
329
353
  }
330
354
  catch (_c) { }
331
355
  yield sendDaemonCommand({
@@ -93,7 +93,10 @@ const SELF = "src/eval-graph-nav.ts"; // exclude from grep + graph (self-referen
93
93
  // grep can't tell those apart from genuine uses, so they would manufacture
94
94
  // phantom "expected callers" that no correct graph ever surfaces. Excluded from
95
95
  // both the grep truth and the getCallers set so the exclusion stays symmetric.
96
- const FIXTURE_FILES = new Set([SELF, "tests/graph-edges.type-position.test.ts"]);
96
+ const FIXTURE_FILES = new Set([
97
+ SELF,
98
+ "tests/graph-edges.type-position.test.ts",
99
+ ]);
97
100
  const rel = (p) => p.replace(`${GMAX_ROOT}/`, "");
98
101
  const INBOUND_SYMBOLS = [
99
102
  // ── Baselines: called/constructed, so the graph captures them. ──────────────