grepmax 0.26.40 → 0.26.41

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.40",
13
+ "version": "0.26.41",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -222,6 +222,10 @@ gmax status # See all projects + watcher status
222
222
 
223
223
  The daemon auto-starts when you run `gmax add`, `gmax index`, `gmax remove`, `gmax summarize`, or `gmax mcp`. It shuts down after 4 hours of inactivity, and hands off to a fresh daemon after 24 hours or once its memory footprint passes 2.5 GB. File-change batches are processed on at most four worker processes, added only when work backs up, with one kept free for searches; LanceDB compaction runs after writes rather than on every maintenance tick.
224
224
 
225
+ `gmax watch status` reports failed files, native watcher recovery or polling, dropped-event counts, and the last complete filesystem reconciliation. Polling scans run every five minutes, so recent edits can lag. Search and MCP responses retain health warnings even after the file queue drains. A complete reconciliation means the filesystem scan completed; queued embedding work can still be pending.
226
+
227
+ Temporary embedding backend outages preserve existing indexed rows and retry with backoff from five seconds up to one minute, without exhausting individual files' retry budgets. MLX failures report bounded transport/protocol details in the daemon log. `gmax status --json` includes the most recent resource snapshot when a recycle threshold has been reached; these counters help investigate memory growth, whose cause requires measurement over time.
228
+
225
229
  ## Running under the Claude Code sandbox
226
230
 
227
231
  Claude Code's Bash sandbox (the default on macOS) allows writes only under the working directory,
@@ -24,6 +24,10 @@ const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
24
24
  const AGENTS_PATH = node_path_1.default.join(node_os_1.default.homedir(), ".codex", "AGENTS.md");
25
25
  const SKILL_START = "<!-- gmax:start -->";
26
26
  const SKILL_END = "<!-- gmax:end -->";
27
+ function ownedSkillBlocks() {
28
+ // A new start marker cannot terminate an earlier incomplete block.
29
+ return new RegExp(`${SKILL_START}(?:(?!${SKILL_START})[\\s\\S])*?${SKILL_END}`, "g");
30
+ }
27
31
  function getPackageRoot() {
28
32
  return node_path_1.default.resolve(__dirname, "../..");
29
33
  }
@@ -51,20 +55,22 @@ function writeSkillToAgents(skill) {
51
55
  return;
52
56
  }
53
57
  const content = node_fs_1.default.readFileSync(AGENTS_PATH, "utf-8");
54
- // Check if file has any gmax content (markers or legacy)
55
- if (content.includes("gmax")) {
56
- // Remove all gmax content and rewrite with just our block
57
- const markerRe = new RegExp(`\n?${SKILL_START}[\\s\\S]*?${SKILL_END}\n?`, "g");
58
- const cleaned = content.replace(markerRe, "");
59
- // Remove legacy content (everything between --- blocks mentioning gmax)
60
- const withoutLegacy = cleaned
61
- .replace(/---[\s\S]*?(?:gmax|--compact)[\s\S]*?(?=\n<!-- |$)/, "")
62
- .trim();
63
- node_fs_1.default.writeFileSync(AGENTS_PATH, withoutLegacy ? `${withoutLegacy}\n\n${block}` : block);
64
- }
65
- else {
66
- node_fs_1.default.writeFileSync(AGENTS_PATH, `${content.trim()}\n\n${block}`);
67
- }
58
+ // Only complete marker pairs establish ownership. Unmarked prose, legacy
59
+ // instructions, and incomplete markers belong to the user and stay intact.
60
+ const markerRe = ownedSkillBlocks();
61
+ let replaced = false;
62
+ const updated = content.replace(markerRe, () => {
63
+ if (replaced)
64
+ return "";
65
+ replaced = true;
66
+ return block;
67
+ });
68
+ const separator = content.endsWith("\n\n")
69
+ ? ""
70
+ : content.endsWith("\n")
71
+ ? "\n"
72
+ : "\n\n";
73
+ node_fs_1.default.writeFileSync(AGENTS_PATH, replaced ? updated : `${content}${separator}${block}`);
68
74
  }
69
75
  function installPlugin() {
70
76
  return __awaiter(this, void 0, void 0, function* () {
@@ -101,9 +107,9 @@ function uninstallPlugin() {
101
107
  if (node_fs_1.default.existsSync(AGENTS_PATH)) {
102
108
  let content = node_fs_1.default.readFileSync(AGENTS_PATH, "utf-8");
103
109
  // Remove marked block
104
- const markerRe = new RegExp(`\n?${SKILL_START}[\\s\\S]*?${SKILL_END}\n?`, "g");
110
+ const markerRe = ownedSkillBlocks();
105
111
  if (markerRe.test(content)) {
106
- content = content.replace(markerRe, "").trim();
112
+ content = content.replace(ownedSkillBlocks(), "");
107
113
  node_fs_1.default.writeFileSync(AGENTS_PATH, content || "");
108
114
  console.log("✅ gmax instructions removed from AGENTS.md");
109
115
  }
@@ -203,7 +203,7 @@ exports.impact = new commander_1.Command("impact")
203
203
  console.log(`dep: ${rel(d.file)}\t${d.sharedSymbols}`);
204
204
  }
205
205
  for (const t of groupedTests) {
206
- console.log(`test: ${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
206
+ console.log(`test: ${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops, t.evidence)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
207
207
  }
208
208
  if (!nonTestDeps.length && !tests.length) {
209
209
  console.log("(no impact detected)");
@@ -225,7 +225,7 @@ exports.impact = new commander_1.Command("impact")
225
225
  if (groupedTests.length > 0) {
226
226
  console.log(`Affected tests (${groupedTests.length}):`);
227
227
  for (const t of groupedTests) {
228
- console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
228
+ console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops, t.evidence)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
229
229
  }
230
230
  }
231
231
  else {
@@ -78,10 +78,12 @@ const surprising_connections_1 = require("../lib/analysis/surprising-connections
78
78
  const graph_handler_1 = require("../lib/daemon/graph-handler");
79
79
  const rows_handler_1 = require("../lib/daemon/rows-handler");
80
80
  const vector_handler_1 = require("../lib/daemon/vector-handler");
81
+ const test_hits_1 = require("../lib/graph/test-hits");
81
82
  const embedding_status_1 = require("../lib/index/embedding-status");
82
83
  const index_config_1 = require("../lib/index/index-config");
83
84
  const syncer_1 = require("../lib/index/syncer");
84
85
  const agent_search_formatter_1 = require("../lib/output/agent-search-formatter");
86
+ const index_state_footer_1 = require("../lib/output/index-state-footer");
85
87
  const searcher_1 = require("../lib/search/searcher");
86
88
  const annotator_1 = require("../lib/skeleton/annotator");
87
89
  const skeletonizer_1 = require("../lib/skeleton/skeletonizer");
@@ -424,12 +426,18 @@ exports.mcp = new commander_1.Command("mcp")
424
426
  function searchStore(args) {
425
427
  return readStore("search", {
426
428
  daemon: () => { var _a; return (0, daemon_client_1.sendDaemonCommand)(Object.assign({ cmd: ((_a = args.filters) === null || _a === void 0 ? void 0 : _a.projectRoots) ? "search-v2" : "search" }, args), { timeoutMs: 60000 }); },
427
- render: (resp) => ({
428
- data: Array.isArray(resp.data) ? resp.data : [],
429
- warnings: Array.isArray(resp.warnings)
430
- ? resp.warnings.filter((warning) => typeof warning === "string")
431
- : undefined,
432
- }),
429
+ render: (resp) => {
430
+ const warnings = Array.isArray(resp.warnings)
431
+ ? resp.warnings.filter((w) => typeof w === "string")
432
+ : [];
433
+ const health = (0, index_state_footer_1.formatIndexStateFooter)(resp.indexState, { agent: true });
434
+ if (health)
435
+ warnings.push(health);
436
+ return {
437
+ data: Array.isArray(resp.data) ? resp.data : [],
438
+ warnings,
439
+ };
440
+ },
433
441
  inProcess: () => localStore((deps) => __awaiter(this, void 0, void 0, function* () {
434
442
  const searcher = new searcher_1.Searcher(deps.vectorDb);
435
443
  return searcher.search(args.query, args.limit, { rerank: args.rerank, seeds: args.seeds }, args.filters, args.pathPrefix);
@@ -907,7 +915,7 @@ exports.mcp = new commander_1.Command("mcp")
907
915
  traceLines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
908
916
  }
909
917
  else {
910
- traceLines.push(` -> ${callee.symbol} (not indexed)`);
918
+ traceLines.push(` -> ${callee.symbol} (${callee.resolution === "ambiguous-member" ? "receiver unresolved" : "not indexed"})`);
911
919
  }
912
920
  }
913
921
  }
@@ -1116,7 +1124,7 @@ exports.mcp = new commander_1.Command("mcp")
1116
1124
  lines.push(` -> ${callee.symbol} ${rel}:${callee.line + 1}`);
1117
1125
  }
1118
1126
  else {
1119
- lines.push(` -> ${callee.symbol} (not indexed)`);
1127
+ lines.push(` -> ${callee.symbol} (${callee.resolution === "ambiguous-member" ? "receiver unresolved" : "not indexed"})`);
1120
1128
  }
1121
1129
  }
1122
1130
  if (graph.callees.length > 15) {
@@ -1330,7 +1338,9 @@ exports.mcp = new commander_1.Command("mcp")
1330
1338
  for (const c of graph.callees.slice(0, maxCallees)) {
1331
1339
  const loc = c.file
1332
1340
  ? `${rel(c.file)}:${c.line + 1}`
1333
- : "(not indexed)";
1341
+ : c.resolution === "ambiguous-member"
1342
+ ? "(receiver unresolved)"
1343
+ : "(not indexed)";
1334
1344
  parts.push(` -> ${c.symbol} ${loc}`);
1335
1345
  }
1336
1346
  if (graph.callees.length > maxCallees) {
@@ -1729,15 +1739,20 @@ exports.mcp = new commander_1.Command("mcp")
1729
1739
  }
1730
1740
  function handleIndexStatus() {
1731
1741
  return __awaiter(this, void 0, void 0, function* () {
1732
- var _a;
1742
+ var _a, _b;
1733
1743
  try {
1734
1744
  const globalConfig = (0, index_config_1.readGlobalConfig)();
1735
1745
  const projects = (0, project_registry_1.listProjects)();
1736
1746
  const currentProject = resolveMcpProject(undefined, projectRoot);
1737
1747
  const identity = (0, embedding_status_1.projectEmbeddingStatus)(currentProject, globalConfig);
1738
1748
  const { chunks, files } = yield indexTotals(projects);
1739
- // Watcher status
1740
- const watcher = (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
1749
+ const daemonStatus = yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "status" });
1750
+ const watched = (Array.isArray(daemonStatus.projects) ? daemonStatus.projects : []);
1751
+ const daemonProject = watched.find((p) => p.root === (currentProject === null || currentProject === void 0 ? void 0 : currentProject.root));
1752
+ // Only use the registry when no daemon answered; sandboxed reads stay IPC-only.
1753
+ const watcher = daemonStatus.ok
1754
+ ? undefined
1755
+ : (0, watcher_store_1.getWatcherCoveringPath)(projectRoot);
1741
1756
  let watcherLine = "Watcher: not running";
1742
1757
  if (watcher) {
1743
1758
  const status = (_a = watcher.status) !== null && _a !== void 0 ? _a : "unknown";
@@ -1750,6 +1765,14 @@ exports.mcp = new commander_1.Command("mcp")
1750
1765
  watcherLine += " — search results may be incomplete";
1751
1766
  }
1752
1767
  }
1768
+ if (daemonProject) {
1769
+ watcherLine = `Watcher: ${(_b = daemonProject.status) !== null && _b !== void 0 ? _b : "unknown"} (${path.basename(daemonProject.root)}/)`;
1770
+ const health = (0, index_state_footer_1.formatIndexStateFooter)(daemonProject.indexState, {
1771
+ agent: true,
1772
+ });
1773
+ if (health)
1774
+ watcherLine += `\n${health}`;
1775
+ }
1753
1776
  // Health overview only. The per-project listing (names, roots,
1754
1777
  // per-project chunk counts) lives in `list_projects` — keep this tool
1755
1778
  // focused on index health and avoid the N-project LanceDB scans.
@@ -2125,7 +2148,7 @@ exports.mcp = new commander_1.Command("mcp")
2125
2148
  return ok(`No tests found for ${target}.`);
2126
2149
  const rel = (p) => p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
2127
2150
  const lines = tests.map((t) => {
2128
- const hop = t.hops === 0 ? "direct" : `${t.hops}-hop`;
2151
+ const hop = (0, test_hits_1.hopLabelAgent)(t.hops, t.evidence);
2129
2152
  return `${rel(t.file)}:${t.line + 1} ${t.symbol} (${hop})`;
2130
2153
  });
2131
2154
  return ok(`Tests for ${target}:\n${lines.join("\n")}`);
@@ -2218,7 +2241,7 @@ exports.mcp = new commander_1.Command("mcp")
2218
2241
  if (tests.length > 0) {
2219
2242
  sections.push(`Affected tests (${tests.length}):`);
2220
2243
  for (const t of tests) {
2221
- const hop = t.hops === 0 ? "direct" : `${t.hops}-hop`;
2244
+ const hop = (0, test_hits_1.hopLabelAgent)(t.hops, t.evidence);
2222
2245
  sections.push(` ${rel(t.file)}:${t.line + 1} ${t.symbol} (${hop})`);
2223
2246
  }
2224
2247
  }
@@ -269,7 +269,7 @@ exports.peek = new commander_1.Command("peek")
269
269
  // Dedupe by symbol — repeated references arrive once per chunk.
270
270
  const seenCallees = new Set();
271
271
  const calleeList = graph.callees
272
- .filter((c) => c.file || !(0, callsites_1.isBuiltinCallee)(c.symbol))
272
+ .filter((c) => c.file || c.resolution || !(0, callsites_1.isBuiltinCallee)(c.symbol))
273
273
  .filter((c) => {
274
274
  if (seenCallees.has(c.symbol))
275
275
  return false;
@@ -277,7 +277,7 @@ exports.peek = new commander_1.Command("peek")
277
277
  return true;
278
278
  })
279
279
  .map((c) => ({
280
- symbol: c.symbol,
280
+ symbol: c.resolution ? `${c.symbol} (receiver unresolved)` : c.symbol,
281
281
  file: c.file,
282
282
  line: c.line,
283
283
  }));
@@ -50,6 +50,7 @@ const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
51
  const embedding_status_1 = require("../lib/index/embedding-status");
52
52
  const index_config_1 = require("../lib/index/index-config");
53
+ const index_state_footer_1 = require("../lib/output/index-state-footer");
53
54
  const daemon_client_1 = require("../lib/utils/daemon-client");
54
55
  const exit_1 = require("../lib/utils/exit");
55
56
  const filter_builder_1 = require("../lib/utils/filter-builder");
@@ -74,11 +75,15 @@ function loadStatusView(projects) {
74
75
  return (0, store_access_1.withStoreRead)("status", {
75
76
  daemon: () => (0, daemon_client_1.sendDaemonCommand)({ cmd: "status" }),
76
77
  render: (resp) => __awaiter(this, void 0, void 0, function* () {
78
+ var _a;
77
79
  const watchers = new Map();
78
80
  const entries = Array.isArray(resp.projects) ? resp.projects : [];
81
+ const health = new Map();
79
82
  for (const entry of entries) {
80
83
  if (entry && typeof entry.root === "string") {
81
- watchers.set(entry.root, { status: "watching" });
84
+ watchers.set(entry.root, { status: (_a = entry.status) !== null && _a !== void 0 ? _a : "watching" });
85
+ if (entry.indexState)
86
+ health.set(entry.root, entry.indexState);
82
87
  }
83
88
  }
84
89
  // One project-stats call per project. A failure here is not fatal: the
@@ -91,17 +96,15 @@ function loadStatusView(projects) {
91
96
  chunkCounts.set(project.root, stats.chunks);
92
97
  }
93
98
  }
94
- const daemon = {
95
- pid: typeof resp.pid === "number" ? resp.pid : null,
96
- uptimeSec: typeof resp.uptime === "number" ? resp.uptime : null,
99
+ const daemon = Object.assign(Object.assign({}, (resp.resources !== undefined
100
+ ? { resources: resp.resources }
101
+ : {})), { pid: typeof resp.pid === "number" ? resp.pid : null, uptimeSec: typeof resp.uptime === "number" ? resp.uptime : null,
97
102
  // Older daemons do not report these.
98
- workers: typeof resp.workers === "number" ? resp.workers : null,
99
- workerThreads: resp.workerThreads &&
103
+ workers: typeof resp.workers === "number" ? resp.workers : null, workerThreads: resp.workerThreads &&
100
104
  typeof resp.workerThreads.value === "number"
101
105
  ? resp.workerThreads.value
102
- : null,
103
- };
104
- return { watchers, chunkCounts, daemon };
106
+ : null });
107
+ return { watchers, chunkCounts, daemon, health };
105
108
  }),
106
109
  inProcess: () => __awaiter(this, void 0, void 0, function* () {
107
110
  (0, watcher_store_1.listWatchers)(); // cleans stale entries as side effect
@@ -149,6 +152,8 @@ function projectState(project, watcher) {
149
152
  return "pending";
150
153
  if (projectStatus === "error")
151
154
  return "error";
155
+ if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "degraded")
156
+ return "degraded";
152
157
  if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "syncing")
153
158
  return "indexing";
154
159
  if (watcher)
@@ -195,12 +200,7 @@ function buildStatusJson(input) {
195
200
  : null);
196
201
  }
197
202
  return {
198
- daemon: {
199
- running: d !== undefined,
200
- pid: (_d = d === null || d === void 0 ? void 0 : d.pid) !== null && _d !== void 0 ? _d : null,
201
- since: (d === null || d === void 0 ? void 0 : d.uptimeSec) != null ? now - d.uptimeSec * 1000 : null,
202
- workerThreads: (_e = d === null || d === void 0 ? void 0 : d.workerThreads) !== null && _e !== void 0 ? _e : null,
203
- },
203
+ daemon: Object.assign({ running: d !== undefined, pid: (_d = d === null || d === void 0 ? void 0 : d.pid) !== null && _d !== void 0 ? _d : null, since: (d === null || d === void 0 ? void 0 : d.uptimeSec) != null ? now - d.uptimeSec * 1000 : null, workerThreads: (_e = d === null || d === void 0 ? void 0 : d.workerThreads) !== null && _e !== void 0 ? _e : null }, ((d === null || d === void 0 ? void 0 : d.resources) !== undefined ? { resources: d.resources } : {})),
204
204
  settings: {
205
205
  embedMode: globalConfig.embedMode,
206
206
  modelTier: globalConfig.modelTier,
@@ -210,14 +210,9 @@ function buildStatusJson(input) {
210
210
  },
211
211
  workersRunning,
212
212
  indexing: input.indexing,
213
- projects: input.projects.map((project) => { var _a, _b; return ({
214
- name: project.name,
215
- root: project.root,
216
- chunks: (_b = (_a = view.chunkCounts.get(project.root)) !== null && _a !== void 0 ? _a : project.chunkCount) !== null && _b !== void 0 ? _b : 0,
217
- indexedAt: toMs(project.lastIndexed),
218
- state: projectState(project, view.watchers.get(project.root)),
219
- embedding: (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state,
220
- }); }),
213
+ projects: input.projects.map((project) => { var _a; var _b, _c; return (Object.assign({ name: project.name, root: project.root, chunks: (_c = (_b = view.chunkCounts.get(project.root)) !== null && _b !== void 0 ? _b : project.chunkCount) !== null && _c !== void 0 ? _c : 0, indexedAt: toMs(project.lastIndexed), state: projectState(project, view.watchers.get(project.root)), embedding: (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig).state }, (((_a = view.health) === null || _a === void 0 ? void 0 : _a.has(project.root))
214
+ ? { health: view.health.get(project.root) }
215
+ : {}))); }),
221
216
  at: now,
222
217
  };
223
218
  }
@@ -268,7 +263,8 @@ Examples:
268
263
  gmax status --json The same, plus daemon and settings, as JSON
269
264
  `)
270
265
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
271
- var _a, _b, _c;
266
+ var _a, _b;
267
+ var _c, _d, _e, _f;
272
268
  const globalConfig = (0, index_config_1.readGlobalConfig)();
273
269
  const projects = (0, project_registry_1.listProjects)();
274
270
  const indexing = (0, lock_1.isLocked)(config_1.PATHS.globalRoot);
@@ -319,9 +315,9 @@ Examples:
319
315
  for (const project of projects) {
320
316
  const st = projectState(project, watchers.get(project.root));
321
317
  const isCurrent = project.root === currentRoot;
322
- const count = (_a = chunkCounts.get(project.root)) !== null && _a !== void 0 ? _a : project.chunkCount;
318
+ const count = (_c = chunkCounts.get(project.root)) !== null && _c !== void 0 ? _c : project.chunkCount;
323
319
  const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
324
- console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}`);
320
+ console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}${((_a = view.health) === null || _a === void 0 ? void 0 : _a.get(project.root)) ? `\t${(_d = (0, index_state_footer_1.formatIndexStateFooter)(view.health.get(project.root), { agent: true })) !== null && _d !== void 0 ? _d : ""}` : ""}`);
325
321
  }
326
322
  const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)((0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig), { agent: true });
327
323
  if (legacyNotice)
@@ -337,13 +333,16 @@ Examples:
337
333
  const watcher = watchers.get(project.root);
338
334
  // Status column
339
335
  let statusStr;
340
- const projectStatus = (_b = project.status) !== null && _b !== void 0 ? _b : "indexed";
336
+ const projectStatus = (_e = project.status) !== null && _e !== void 0 ? _e : "indexed";
341
337
  if (projectStatus === "pending") {
342
338
  statusStr = style.yellow("pending");
343
339
  }
344
340
  else if (projectStatus === "error") {
345
341
  statusStr = style.red("error");
346
342
  }
343
+ else if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "degraded") {
344
+ statusStr = style.red("degraded");
345
+ }
347
346
  else if ((watcher === null || watcher === void 0 ? void 0 : watcher.status) === "syncing") {
348
347
  statusStr = style.yellow("indexing");
349
348
  }
@@ -354,7 +353,7 @@ Examples:
354
353
  statusStr = style.dim("idle");
355
354
  }
356
355
  // Chunks column
357
- const count = (_c = chunkCounts.get(project.root)) !== null && _c !== void 0 ? _c : project.chunkCount;
356
+ const count = (_f = chunkCounts.get(project.root)) !== null && _f !== void 0 ? _f : project.chunkCount;
358
357
  const chunks = `${formatChunks(count)} chunks`;
359
358
  // Age column
360
359
  const age = formatAge(project.lastIndexed);
@@ -363,6 +362,11 @@ Examples:
363
362
  const marker = isCurrent ? style.cyan(" ←") : "";
364
363
  const name = project.name.padEnd(nameWidth);
365
364
  console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr} embedding:${embedding}${marker}`);
365
+ const health = (0, index_state_footer_1.formatIndexStateFooter)((_b = view.health) === null || _b === void 0 ? void 0 : _b.get(project.root), {
366
+ agent: false,
367
+ });
368
+ if (health)
369
+ console.log(` ${health}`);
366
370
  }
367
371
  const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)((0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig));
368
372
  if (legacyNotice)
@@ -127,13 +127,13 @@ exports.testFind = new commander_1.Command("test")
127
127
  const grouped = (0, test_hits_1.groupTestHitsByFile)(tests);
128
128
  if (opts.agent) {
129
129
  for (const t of grouped) {
130
- console.log(`${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
130
+ console.log(`${rel(t.file)}:${t.line + 1}\t${(0, test_hits_1.hopLabelAgent)(t.hops, t.evidence)}${(0, test_hits_1.formatViaAgent)(t.via)}`);
131
131
  }
132
132
  }
133
133
  else {
134
134
  console.log(`Tests for ${target}:\n`);
135
135
  for (const t of grouped) {
136
- console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
136
+ console.log(` ${rel(t.file)}:${t.line + 1} (${(0, test_hits_1.hopLabelHuman)(t.hops, t.evidence)}${(0, test_hits_1.formatViaHuman)(t.via)})`);
137
137
  }
138
138
  }
139
139
  }
@@ -121,7 +121,7 @@ function formatTraceAgent(graph, projectRoot, raw = false) {
121
121
  lines.push(`-> ${c.symbol}\t${rel(c.file)}:${c.line}`);
122
122
  }
123
123
  else {
124
- lines.push(`-> ${c.symbol}\t(not indexed)`);
124
+ lines.push(`-> ${c.symbol}\t(${c.resolution === "ambiguous-member" ? "receiver unresolved" : "not indexed"})`);
125
125
  }
126
126
  }
127
127
  return lines.join("\n");
@@ -50,6 +50,7 @@ const commander_1 = require("commander");
50
50
  const config_1 = require("../config");
51
51
  const syncer_1 = require("../lib/index/syncer");
52
52
  const watcher_1 = require("../lib/index/watcher");
53
+ const index_state_footer_1 = require("../lib/output/index-state-footer");
53
54
  const meta_cache_1 = require("../lib/store/meta-cache");
54
55
  const vector_db_1 = require("../lib/store/vector-db");
55
56
  const daemon_restart_1 = require("../lib/utils/daemon-restart");
@@ -366,6 +367,7 @@ exports.watch
366
367
  .command("status")
367
368
  .description("Show running watchers")
368
369
  .action(() => __awaiter(void 0, void 0, void 0, function* () {
370
+ var _a, _b, _c;
369
371
  const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
370
372
  const resp = yield sendDaemonCommand({ cmd: "status" });
371
373
  if (resp.ok) {
@@ -378,6 +380,11 @@ exports.watch
378
380
  }
379
381
  for (const p of projects) {
380
382
  console.log(` - ${p.root} [${p.status}]`);
383
+ const health = (0, index_state_footer_1.formatIndexStateFooter)(p.indexState, { agent: true });
384
+ if (health)
385
+ console.log(` ${health}`);
386
+ if (p.indexState)
387
+ console.log(` watcher=${(_a = p.indexState.watcherMode) !== null && _a !== void 0 ? _a : "unknown"} overflows=${(_b = p.indexState.overflowCount) !== null && _b !== void 0 ? _b : 0} lastReconciled=${p.indexState.lastReconciledAt ? new Date(p.indexState.lastReconciledAt).toISOString() : "unknown"} catchupMs=${(_c = p.indexState.catchupMs) !== null && _c !== void 0 ? _c : "unknown"}`);
381
388
  }
382
389
  if (projects.length === 0) {
383
390
  console.log(" (no projects)");
@@ -71,6 +71,7 @@ const operation_coordinator_1 = require("../utils/operation-coordinator");
71
71
  const process_1 = require("../utils/process");
72
72
  const process_footprint_1 = require("../utils/process-footprint");
73
73
  const project_registry_1 = require("../utils/project-registry");
74
+ const resource_snapshot_1 = require("../utils/resource-snapshot");
74
75
  const root_availability_1 = require("../utils/root-availability");
75
76
  const watcher_store_1 = require("../utils/watcher-store");
76
77
  const pool_1 = require("../workers/pool");
@@ -129,6 +130,7 @@ const RECYCLE_FORCE_AFTER_MS = envNum("GMAX_DAEMON_RECYCLE_FORCE_AFTER_MS", 60 *
129
130
  class Daemon {
130
131
  constructor() {
131
132
  this.processors = new Map();
133
+ this.lastResourceSnapshot = null;
132
134
  this.searchers = new Map();
133
135
  this.subscriptions = new Map();
134
136
  this.vectorDb = null;
@@ -767,13 +769,13 @@ class Daemon {
767
769
  * on every search to annotate partial-result responses (Phase 6).
768
770
  */
769
771
  indexState(root) {
770
- var _a;
771
- var _b, _c, _d;
772
+ var _a, _b, _c, _d;
773
+ var _e, _f, _g, _h, _j;
772
774
  const processor = this.processors.get(root);
773
- const batchPending = (_b = processor === null || processor === void 0 ? void 0 : processor.progress.pendingFiles) !== null && _b !== void 0 ? _b : 0;
774
- const processing = (_c = processor === null || processor === void 0 ? void 0 : processor.progress.processing) !== null && _c !== void 0 ? _c : false;
775
+ const batchPending = (_e = (_a = processor === null || processor === void 0 ? void 0 : processor.progress) === null || _a === void 0 ? void 0 : _a.pendingFiles) !== null && _e !== void 0 ? _e : 0;
776
+ const processing = (_f = (_b = processor === null || processor === void 0 ? void 0 : processor.progress) === null || _b === void 0 ? void 0 : _b.processing) !== null && _f !== void 0 ? _f : false;
775
777
  // status === "pending" means the initial full index hasn't completed.
776
- const initialPending = ((_a = (0, project_registry_1.getProject)(root)) === null || _a === void 0 ? void 0 : _a.status) === "pending";
778
+ const initialPending = ((_c = (0, project_registry_1.getProject)(root)) === null || _c === void 0 ? void 0 : _c.status) === "pending";
777
779
  // A full index (--reset / initial) bypasses the batch processor; its
778
780
  // onProgress feeds indexProgress, giving a real remaining count.
779
781
  const fullIdx = this.indexProgress.get(root);
@@ -785,7 +787,7 @@ class Daemon {
785
787
  // index genuinely has work outstanding; a post-FSEvents-drop catchup mostly
786
788
  // re-verifies unchanged files. Only claim "verifying" with a real sample and
787
789
  // no initial-index work pending.
788
- const { recentFiles, recentReindexed } = (_d = processor === null || processor === void 0 ? void 0 : processor.progress) !== null && _d !== void 0 ? _d : {
790
+ const { recentFiles, recentReindexed } = (_g = processor === null || processor === void 0 ? void 0 : processor.progress) !== null && _g !== void 0 ? _g : {
789
791
  recentFiles: 0,
790
792
  recentReindexed: 0,
791
793
  };
@@ -794,11 +796,9 @@ class Daemon {
794
796
  recentFiles >= 50 &&
795
797
  recentReindexed === 0 &&
796
798
  pendingFiles > 0;
797
- return {
798
- indexing: !!fullIdx || processing || batchPending > 0 || initialPending,
799
- pendingFiles,
800
- verifying,
801
- };
799
+ const health = this.watcherManager.health(root);
800
+ return Object.assign(Object.assign({ indexing: !!fullIdx || processing || batchPending > 0 || initialPending, pendingFiles,
801
+ verifying }, health), { failedFiles: Math.max((_h = (_d = processor === null || processor === void 0 ? void 0 : processor.progress) === null || _d === void 0 ? void 0 : _d.failedFiles) !== null && _h !== void 0 ? _h : 0, (_j = health.failedFiles) !== null && _j !== void 0 ? _j : 0) });
802
802
  }
803
803
  search(payload, signal) {
804
804
  return __awaiter(this, void 0, void 0, function* () {
@@ -836,10 +836,18 @@ class Daemon {
836
836
  };
837
837
  }
838
838
  listProjects() {
839
- return [...this.processors.keys()].map((root) => ({
840
- root,
841
- status: "watching",
842
- }));
839
+ return [...this.processors.keys()].map((root) => {
840
+ const indexState = this.indexState(root);
841
+ return {
842
+ root,
843
+ status: indexState.failedFiles || indexState.degraded
844
+ ? "degraded"
845
+ : indexState.indexing
846
+ ? "syncing"
847
+ : "watching",
848
+ indexState,
849
+ };
850
+ });
843
851
  }
844
852
  uptime() {
845
853
  return Math.floor((Date.now() - this.startTime) / 1000);
@@ -1810,6 +1818,28 @@ class Daemon {
1810
1818
  }));
1811
1819
  });
1812
1820
  }
1821
+ resourceSnapshot() {
1822
+ return this.lastResourceSnapshot;
1823
+ }
1824
+ logResourceSnapshot(reason, footprintMb) {
1825
+ var _a, _b;
1826
+ var _c, _d;
1827
+ let lanceCacheBytes = null;
1828
+ try {
1829
+ lanceCacheBytes = (_c = (_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.cacheSizeBytes()) !== null && _c !== void 0 ? _c : null;
1830
+ }
1831
+ catch (_e) { }
1832
+ this.lastResourceSnapshot = (0, resource_snapshot_1.buildResourceSnapshot)({
1833
+ reason,
1834
+ footprintMb,
1835
+ lanceCacheBytes,
1836
+ workers: this.workerCount(),
1837
+ pendingFiles: [...this.processors.values()].reduce((sum, processor) => sum + processor.progress.pendingFiles, 0),
1838
+ operations: this.operations.activeCount,
1839
+ maintenance: (_d = (_b = this.vectorDb) === null || _b === void 0 ? void 0 : _b.isMaintenanceActive()) !== null && _d !== void 0 ? _d : false,
1840
+ });
1841
+ console.log(`[daemon] Resource snapshot: ${JSON.stringify(this.lastResourceSnapshot)}`);
1842
+ }
1813
1843
  /**
1814
1844
  * Gracefully hand off to a fresh daemon when this one has grown too old or
1815
1845
  * too large. Only fires when quiet — no active compaction and no in-flight
@@ -1851,6 +1881,7 @@ class Daemon {
1851
1881
  this.recycleReason = ageExceeded
1852
1882
  ? `age ${(ageMs / 3600000).toFixed(1)}h > ${(MAX_LIFETIME_MS / 3600000).toFixed(1)}h`
1853
1883
  : `footprint ${Math.round(footprintMb !== null && footprintMb !== void 0 ? footprintMb : 0)}MB > ${RSS_WATERMARK_MB}MB`;
1884
+ this.logResourceSnapshot("recycle-due", footprintMb);
1854
1885
  }
1855
1886
  // Defer while busy; re-checked on every heartbeat until it goes through.
1856
1887
  if ((_a = this.vectorDb) === null || _a === void 0 ? void 0 : _a.isMaintenanceActive())
@@ -1868,6 +1899,7 @@ class Daemon {
1868
1899
  }
1869
1900
  return;
1870
1901
  }
1902
+ this.logResourceSnapshot("recycling", footprintMb);
1871
1903
  const forced = busy ? " (forced over in-flight work)" : "";
1872
1904
  console.log(`[daemon] Recycling (${this.recycleReason})${forced} — handing off to a fresh daemon`);
1873
1905
  this.recycling = true;
@@ -451,7 +451,7 @@ function runGraphRisk(db, args, signal) {
451
451
  file: (_a = loc === null || loc === void 0 ? void 0 : loc.file) !== null && _a !== void 0 ? _a : "",
452
452
  line: (_b = loc === null || loc === void 0 ? void 0 : loc.line) !== null && _b !== void 0 ? _b : 0,
453
453
  callerCount: callers.length,
454
- hasTests: tests.length > 0,
454
+ hasTests: tests.some((test) => !test.evidence),
455
455
  };
456
456
  }), signal);
457
457
  });
@@ -122,7 +122,8 @@ function startHeartbeat(conn, intervalMs = HEARTBEAT_INTERVAL_MS) {
122
122
  */
123
123
  function handleCommand(daemon, cmd, conn) {
124
124
  return __awaiter(this, void 0, void 0, function* () {
125
- var _a, _b, _c, _d, _e, _f, _g;
125
+ var _a;
126
+ var _b, _c, _d, _e, _f, _g, _h, _j;
126
127
  try {
127
128
  (0, logger_1.debug)("daemon", `ipc cmd=${cmd.cmd}${cmd.root ? ` root=${cmd.root}` : ""}`);
128
129
  if (cmd.cmd !== "ping" &&
@@ -185,6 +186,7 @@ function handleCommand(daemon, cmd, conn) {
185
186
  diskPressure: daemon.getDiskPressure(),
186
187
  mlx: daemon.getMlxStatus(),
187
188
  workers: daemon.workerCount(),
189
+ resources: (_b = (_a = daemon.resourceSnapshot) === null || _a === void 0 ? void 0 : _a.call(daemon)) !== null && _b !== void 0 ? _b : null,
188
190
  workerThreads: config_1.WORKER_THREADS_SETTING,
189
191
  };
190
192
  case "optimize":
@@ -200,14 +202,14 @@ function handleCommand(daemon, cmd, conn) {
200
202
  return Object.assign({ ok: true }, (yield daemon.projectStats(root)));
201
203
  }
202
204
  case "shutdown": {
203
- const reason = String((_a = cmd.reason) !== null && _a !== void 0 ? _a : "unknown");
204
- const fromPid = (_b = cmd.from_pid) !== null && _b !== void 0 ? _b : "?";
205
- const fromPpid = (_c = cmd.from_ppid) !== null && _c !== void 0 ? _c : "?";
206
- const fromVer = (_d = cmd.from_version) !== null && _d !== void 0 ? _d : "?";
205
+ const reason = String((_c = cmd.reason) !== null && _c !== void 0 ? _c : "unknown");
206
+ const fromPid = (_d = cmd.from_pid) !== null && _d !== void 0 ? _d : "?";
207
+ const fromPpid = (_e = cmd.from_ppid) !== null && _e !== void 0 ? _e : "?";
208
+ const fromVer = (_f = cmd.from_version) !== null && _f !== void 0 ? _f : "?";
207
209
  const fromArgv = Array.isArray(cmd.from_argv)
208
210
  ? cmd.from_argv.join(" ")
209
211
  : "?";
210
- const fromParentCmd = (_e = cmd.from_parent_cmd) !== null && _e !== void 0 ? _e : "?";
212
+ const fromParentCmd = (_g = cmd.from_parent_cmd) !== null && _g !== void 0 ? _g : "?";
211
213
  console.log(`[daemon] shutdown command received via IPC: reason=${reason} from_pid=${fromPid} from_ppid=${fromPpid} from_version=${fromVer} from_argv=[${fromArgv}] from_parent_cmd=[${fromParentCmd}]`);
212
214
  setImmediate(() => daemon.shutdown());
213
215
  return { ok: true };
@@ -272,8 +274,8 @@ function handleCommand(daemon, cmd, conn) {
272
274
  return { ok: false, error: "invalid project_roots" };
273
275
  }
274
276
  for (const prefix of [
275
- ...((_f = filters === null || filters === void 0 ? void 0 : filters.inPrefixes) !== null && _f !== void 0 ? _f : []),
276
- ...((_g = filters === null || filters === void 0 ? void 0 : filters.excludePrefixes) !== null && _g !== void 0 ? _g : []),
277
+ ...((_h = filters === null || filters === void 0 ? void 0 : filters.inPrefixes) !== null && _h !== void 0 ? _h : []),
278
+ ...((_j = filters === null || filters === void 0 ? void 0 : filters.excludePrefixes) !== null && _j !== void 0 ? _j : []),
277
279
  ]) {
278
280
  (0, path_containment_1.resolveContainedPath)(projectRoot, prefix);
279
281
  }