grepmax 0.26.57 → 0.26.59

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.57",
13
+ "version": "0.26.59",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -57,7 +57,7 @@ gmax doctor # Health check
57
57
  gmax doctor --fix # Repair checks; full-table maintenance is disabled
58
58
  ```
59
59
 
60
- **Storage containment:** full-table compaction is disabled, including forced maintenance and doctor repair. Existing retained index copies are not reclaimed by this release. The verified prune-only recovery path is separate work; normal incremental indexing remains available when the host is healthy and not quarantined. A persistent host safety stop blocks mutation and daemon restart. Installing an update preserves existing quarantine. Speculative embedding warmup and maintenance timers are disabled. On macOS, startup, heavy queries, worker forks and MLX launches require known normal OS memory pressure and a healthy kernel-zone sample; unavailable probes report bounded diagnostic details and refuse work.
60
+ **Storage containment:** full-table compaction is disabled, including forced maintenance and doctor repair. Existing retained index copies are not reclaimed by this release. The verified prune-only recovery path is separate work; normal incremental indexing remains available when the host is healthy and not quarantined. A persistent host safety stop blocks mutation and normal daemon restart; an explicit read-only start can serve the retained index. Installing an update preserves existing quarantine. Speculative embedding warmup and maintenance timers are disabled. On macOS, startup, heavy queries, worker forks and MLX launches require known normal OS memory pressure and a healthy kernel-zone sample; warning or unavailable probes pause heavy work while the daemon serves bounded reads. Known critical pressure still stops the daemon.
61
61
 
62
62
  ### Core Commands
63
63
 
@@ -474,6 +474,10 @@ gmax watch restart # Restart daemon
474
474
 
475
475
  `gmax doctor` and `gmax status` report persistent safety stops and daemon startup quarantine. `doctor` exits with code 2 when startup is blocked; `status --json` includes `daemon.startupBlockedReason`. Installing an update preserves these stops. macOS kernel-zone probes use `zprint -L` to omit the wired-memory report and kernel symbolication while retaining the same zone counters and pressure thresholds.
476
476
 
477
+ When heavy work is paused, the daemon keeps metadata, bounded symbol/file lookups, stored skeletons by exact file path, and keyword search available. Search uses the existing FTS index and excludes unindexed rows, loads no embedding model, returns at most 20 results, and reports keyword-only scores and retained-index freshness. CLI agent output preserves the keyword-only warning, and daemon reads skip client-side model setup. Native queries have a two-second deadline and run one at a time with 48 MiB of combined Lance cache capacity. Indexing, watchers, vector/graph scans, and model launches remain paused. Missing FTS indexes return an error; paused search never rebuilds an index or falls back to a corpus scan.
478
+
479
+ To serve bounded reads under an existing startup quarantine, run `gmax watch --daemon --read-only -b`. This preserves the safety markers. `status --json` reports `daemon.service.mode` and its reason; `doctor` reports paused availability and skips native corpus diagnostics. Pausing is sticky for that daemon process. Full service requires a deliberate restart after host recovery and quarantine review. Known critical OS or kernel pressure still stops even the read-only service.
480
+
477
481
  Full-table compaction and new FTS/ANN index builds are disabled in the containment release, including forced maintenance and doctor repair. Existing indexes remain readable; missing lexical indexes fall back to available retrieval. Retained copies are not recovered by this release. A separate exclusive prune-only path requires validation before deployment. Do not repeatedly force repair, use future cutoffs or manually remove index fragments.
478
482
 
479
483
  `~/.gmax/logs/daemon.log` records containment skips and retained historical compaction outcomes, including duration, logical size, disk size before/after, free space, and bytes reclaimed. `gmax status --json` exposes the last outcome under `daemon.compaction`; its `at` timestamp identifies when it was recorded. Five-minute resource snapshots track footprint, RSS, heap, external/ArrayBuffer memory, Lance cache, workers and pending work for memory investigations. Reading status uses retained snapshots and does not scan the index directory. Logs rotate to `daemon.log.prev`.
@@ -347,7 +347,7 @@ exports.doctor = new commander_1.Command("doctor")
347
347
  .option("--agent", "Compact output for AI agents", false)
348
348
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
349
349
  var _a, _b;
350
- var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
350
+ var _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
351
351
  if (opts.fix) {
352
352
  const denied = (0, maintenance_policy_1.storeMutationDeniedReason)();
353
353
  if (denied !== null) {
@@ -358,6 +358,22 @@ exports.doctor = new commander_1.Command("doctor")
358
358
  }
359
359
  if (!opts.agent)
360
360
  console.log("gmax Doctor\n");
361
+ // Metadata-only diagnostics while paused: never open a second native
362
+ // reader or scan the retained corpus on a pressured host.
363
+ const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
364
+ const daemonStatus = yield sendDaemonCommand({ cmd: "status" });
365
+ const service = daemonStatus.service;
366
+ if (daemonStatus.ok && (service === null || service === void 0 ? void 0 : service.mode) === "paused") {
367
+ const reason = String((_c = service.reason) !== null && _c !== void 0 ? _c : "read-only service requested").replace(/[\r\n\t]/g, " ");
368
+ console.log(opts.agent
369
+ ? `daemon_service\tmode=paused\treads=bounded\treason=${reason}`
370
+ : `WARN Daemon available for bounded reads; indexing and embeddings paused: ${reason}`);
371
+ console.log(opts.agent
372
+ ? "index_diagnostics\tskipped=paused_service"
373
+ : "INFO Native index diagnostics skipped while heavy work is paused");
374
+ process.exitCode = 2;
375
+ return;
376
+ }
361
377
  const startupBlockedReason = (0, autostart_1.daemonStartDeniedReason)();
362
378
  if (startupBlockedReason !== null) {
363
379
  const reason = startupBlockedReason.replace(/[\r\n\t]/g, " ");
@@ -382,12 +398,12 @@ exports.doctor = new commander_1.Command("doctor")
382
398
  checkDir("Grammars", grammars);
383
399
  }
384
400
  const globalConfig = (0, index_config_1.readGlobalConfig)();
385
- const tier = (_c = config_1.MODEL_TIERS[globalConfig.modelTier]) !== null && _c !== void 0 ? _c : config_1.MODEL_TIERS.small;
401
+ const tier = (_d = config_1.MODEL_TIERS[globalConfig.modelTier]) !== null && _d !== void 0 ? _d : config_1.MODEL_TIERS.small;
386
402
  if (!config_1.MODEL_TIERS[globalConfig.modelTier]) {
387
403
  console.log(`WARN Unknown model tier '${globalConfig.modelTier}', falling back to 'small'`);
388
404
  }
389
405
  const embedModel = globalConfig.embedMode === "gpu"
390
- ? ((_d = globalConfig.mlxModel) !== null && _d !== void 0 ? _d : tier.mlxModel)
406
+ ? ((_e = globalConfig.mlxModel) !== null && _e !== void 0 ? _e : tier.mlxModel)
391
407
  : tier.onnxModel;
392
408
  if (!opts.agent) {
393
409
  console.log(`\nEmbed mode: ${globalConfig.embedMode} | Model tier: ${globalConfig.modelTier} (${tier.vectorDim}d)`);
@@ -448,7 +464,7 @@ exports.doctor = new commander_1.Command("doctor")
448
464
  body: JSON.stringify({ texts: ["gmax health check"] }),
449
465
  });
450
466
  const embedData = yield embedRes.json();
451
- const dim = (_e = (_b = (_a = embedData === null || embedData === void 0 ? void 0 : embedData.vectors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.length) !== null && _e !== void 0 ? _e : 0;
467
+ const dim = (_f = (_b = (_a = embedData === null || embedData === void 0 ? void 0 : embedData.vectors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.length) !== null && _f !== void 0 ? _f : 0;
452
468
  const ms = Date.now() - start;
453
469
  const expectedDim = tier.vectorDim || 384;
454
470
  if (dim === expectedDim) {
@@ -475,7 +491,7 @@ exports.doctor = new commander_1.Command("doctor")
475
491
  // caller most likely to be inside the sandbox this check is about.
476
492
  const sandbox = checkClaudeSandbox({
477
493
  home: os.homedir(),
478
- projectRoot: (_f = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _f !== void 0 ? _f : process.cwd(),
494
+ projectRoot: (_g = (0, project_root_1.findProjectRoot)(process.cwd())) !== null && _g !== void 0 ? _g : process.cwd(),
479
495
  });
480
496
  if (sandbox) {
481
497
  if (opts.agent) {
@@ -594,7 +610,7 @@ exports.doctor = new commander_1.Command("doctor")
594
610
  ? "LOW"
595
611
  : "ok";
596
612
  }
597
- catch (_p) { }
613
+ catch (_q) { }
598
614
  const staleChunkerProjects = projects.filter((p) => { var _a; return p.status === "indexed" &&
599
615
  ((_a = p.chunkerVersion) !== null && _a !== void 0 ? _a : 1) < config_1.CONFIG.CHUNKER_VERSION; });
600
616
  const staleEmbeddingProjects = projects.filter((p) => p.status === "indexed" &&
@@ -623,9 +639,9 @@ exports.doctor = new commander_1.Command("doctor")
623
639
  `schema_dim=${physicalDim !== null && physicalDim !== void 0 ? physicalDim : "none"}`,
624
640
  `schema_dim_ok=${schemaGap ? "false" : "true"}`,
625
641
  `ann_index=${vectorIndex ? "present" : "absent"}`,
626
- `ann_indexed=${(_g = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numIndexedRows) !== null && _g !== void 0 ? _g : 0}`,
627
- `ann_unindexed=${(_h = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numUnindexedRows) !== null && _h !== void 0 ? _h : 0}`,
628
- `ann_metric=${(_j = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.distanceType) !== null && _j !== void 0 ? _j : "none"}`,
642
+ `ann_indexed=${(_h = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numIndexedRows) !== null && _h !== void 0 ? _h : 0}`,
643
+ `ann_unindexed=${(_j = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.numUnindexedRows) !== null && _j !== void 0 ? _j : 0}`,
644
+ `ann_metric=${(_k = vectorIndexStats === null || vectorIndexStats === void 0 ? void 0 : vectorIndexStats.distanceType) !== null && _k !== void 0 ? _k : "none"}`,
629
645
  ];
630
646
  console.log(fields.join("\t"));
631
647
  if (schemaGap) {
@@ -680,7 +696,7 @@ exports.doctor = new commander_1.Command("doctor")
680
696
  console.log(`ok Schema: vector table is ${physicalDim}d`);
681
697
  }
682
698
  if (vectorIndexStats) {
683
- console.log(`ok ANN: ${vectorIndexStats.numIndexedRows.toLocaleString()} indexed, ${vectorIndexStats.numUnindexedRows.toLocaleString()} unindexed, metric=${(_k = vectorIndexStats.distanceType) !== null && _k !== void 0 ? _k : "unknown"}`);
699
+ console.log(`ok ANN: ${vectorIndexStats.numIndexedRows.toLocaleString()} indexed, ${vectorIndexStats.numUnindexedRows.toLocaleString()} unindexed, metric=${(_l = vectorIndexStats.distanceType) !== null && _l !== void 0 ? _l : "unknown"}`);
684
700
  }
685
701
  else {
686
702
  console.log("INFO ANN: vector index not built (set GMAX_ANN=1)");
@@ -809,7 +825,7 @@ exports.doctor = new commander_1.Command("doctor")
809
825
  yield mc.close();
810
826
  }
811
827
  }
812
- catch (_q) { }
828
+ catch (_r) { }
813
829
  }
814
830
  }
815
831
  // --fix auto-remediation
@@ -887,7 +903,7 @@ exports.doctor = new commander_1.Command("doctor")
887
903
  const done = yield sendStreamingCommand({ cmd: "index", root: p.root, reset: true }, () => { }).catch((e) => ({ ok: false, error: String(e) }));
888
904
  if (done.ok &&
889
905
  (!("degraded" in done) || done.degraded !== true)) {
890
- const chunks = (_l = done.indexed) !== null && _l !== void 0 ? _l : 0;
906
+ const chunks = (_m = done.indexed) !== null && _m !== void 0 ? _m : 0;
891
907
  if (opts.agent) {
892
908
  console.log(`stale_chunker_reindexed\tname=${name}\tchunks=${chunks}`);
893
909
  }
@@ -899,7 +915,7 @@ exports.doctor = new commander_1.Command("doctor")
899
915
  else if (!opts.agent) {
900
916
  const degraded = "degraded" in done && done.degraded === true;
901
917
  const reason = degraded
902
- ? `${(_m = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _m !== void 0 ? _m : 0} scan errors, ${(_o = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _o !== void 0 ? _o : 0} file failures`
918
+ ? `${(_o = ("scanErrors" in done ? done.scanErrors : 0)) !== null && _o !== void 0 ? _o : 0} scan errors, ${(_p = ("failedFiles" in done ? done.failedFiles : 0)) !== null && _p !== void 0 ? _p : 0} file failures`
903
919
  : done.error;
904
920
  console.log(`FAIL ${name}: reindex failed (${reason})`);
905
921
  }
@@ -210,9 +210,11 @@ function renderSearchOutput(params) {
210
210
  var _a, _b, _c;
211
211
  var _d, _e, _f, _g, _h, _j;
212
212
  const { searchResult, options, minScore, crossProject, pattern, effectiveRoot, projectRoot, vectorDb, precomputedSkeletons, precomputedGraph, indexState, } = params;
213
- if (!options.agent && ((_a = searchResult.warnings) === null || _a === void 0 ? void 0 : _a.length)) {
213
+ if ((_a = searchResult.warnings) === null || _a === void 0 ? void 0 : _a.length) {
214
214
  for (const w of searchResult.warnings) {
215
- console.warn(`Warning: ${w}`);
215
+ console.warn(options.agent
216
+ ? `warn\t${w.replace(/[\r\n\t]/g, " ")}`
217
+ : `Warning: ${w}`);
216
218
  }
217
219
  }
218
220
  // Partial-index signal (Phase 6): when the index is mid-catchup, results
@@ -53,6 +53,7 @@ const sync_helpers_1 = require("../lib/index/sync-helpers");
53
53
  const syncer_1 = require("../lib/index/syncer");
54
54
  const per_file_1 = require("../lib/search/per-file");
55
55
  const searcher_1 = require("../lib/search/searcher");
56
+ const setup_helpers_1 = require("../lib/setup/setup-helpers");
56
57
  const vector_db_1 = require("../lib/store/vector-db");
57
58
  const lock_1 = require("../lib/utils/lock");
58
59
  const project_registry_1 = require("../lib/utils/project-registry");
@@ -160,6 +161,8 @@ function runSearch(params) {
160
161
  // In-process fallback: open VectorDB, ensure index, run Searcher.
161
162
  // Only entered when the daemon path didn't produce results.
162
163
  if (!searchResult) {
164
+ // A daemon read needs no client-side model download or loading.
165
+ yield (0, setup_helpers_1.ensureSetup)();
163
166
  const vectorDb = new vector_db_1.VectorDB(paths.lancedbDir);
164
167
  openedDb = vectorDb;
165
168
  // Check for active indexing lock and warn if present
@@ -46,7 +46,6 @@ exports.search = void 0;
46
46
  const path = __importStar(require("node:path"));
47
47
  const commander_1 = require("commander");
48
48
  const per_file_1 = require("../lib/search/per-file");
49
- const setup_helpers_1 = require("../lib/setup/setup-helpers");
50
49
  const cross_project_1 = require("../lib/utils/cross-project");
51
50
  const exit_1 = require("../lib/utils/exit");
52
51
  const path_containment_1 = require("../lib/utils/path-containment");
@@ -238,7 +237,6 @@ Examples:
238
237
  return;
239
238
  }
240
239
  try {
241
- yield (0, setup_helpers_1.ensureSetup)();
242
240
  let checkRoot = cwdProjectRoot;
243
241
  if (options.root) {
244
242
  const resolved = resolvedOptionRoot !== undefined
@@ -79,12 +79,13 @@ function loadStatusView(projects) {
79
79
  daemon: () => (0, daemon_client_1.sendDaemonCommand)({ cmd: "status" }),
80
80
  render: (resp) => __awaiter(this, void 0, void 0, function* () {
81
81
  var _a;
82
+ var _b;
82
83
  const watchers = new Map();
83
84
  const entries = Array.isArray(resp.projects) ? resp.projects : [];
84
85
  const health = new Map();
85
86
  for (const entry of entries) {
86
87
  if (entry && typeof entry.root === "string") {
87
- watchers.set(entry.root, { status: (_a = entry.status) !== null && _a !== void 0 ? _a : "watching" });
88
+ watchers.set(entry.root, { status: (_b = entry.status) !== null && _b !== void 0 ? _b : "watching" });
88
89
  if (entry.indexState)
89
90
  health.set(entry.root, entry.indexState);
90
91
  }
@@ -94,15 +95,21 @@ function loadStatusView(projects) {
94
95
  // the in-process path does when the LanceDB query fails.
95
96
  const chunkCounts = new Map();
96
97
  for (const project of projects) {
98
+ if (((_a = resp.service) === null || _a === void 0 ? void 0 : _a.mode) === "paused")
99
+ break;
97
100
  const stats = yield (0, daemon_client_1.sendDaemonCommand)({ cmd: "project-stats", root: project.root }, { timeoutMs: 30000 });
98
101
  if (stats.ok && typeof stats.chunks === "number") {
99
102
  chunkCounts.set(project.root, stats.chunks);
100
103
  }
101
104
  }
102
- const daemon = Object.assign(Object.assign(Object.assign({}, (resp.compaction !== undefined
105
+ const daemon = Object.assign(Object.assign(Object.assign(Object.assign({}, (resp.compaction !== undefined
103
106
  ? { compaction: resp.compaction }
104
107
  : {})), (resp.resources !== undefined
105
108
  ? { resources: resp.resources }
109
+ : {})), (resp.service
110
+ ? {
111
+ service: resp.service,
112
+ }
106
113
  : {})), { pid: typeof resp.pid === "number" ? resp.pid : null, uptimeSec: typeof resp.uptime === "number" ? resp.uptime : null,
107
114
  // Older daemons do not report these.
108
115
  workers: typeof resp.workers === "number" ? resp.workers : null, workerThreads: resp.workerThreads &&
@@ -207,9 +214,9 @@ function buildStatusJson(input) {
207
214
  ? ((_c = input.countWorkers) !== null && _c !== void 0 ? _c : countWorkerProcesses)(d.pid)
208
215
  : null);
209
216
  }
210
- return Object.assign(Object.assign({}, (input.stores ? { stores: input.stores } : {})), { daemon: Object.assign(Object.assign(Object.assign(Object.assign({ running: d !== undefined }, (input.startupBlockedReason != null
217
+ return Object.assign(Object.assign({}, (input.stores ? { stores: input.stores } : {})), { daemon: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ running: d !== undefined }, (input.startupBlockedReason != null
211
218
  ? { startupBlockedReason: input.startupBlockedReason }
212
- : {})), { 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 } : {})), ((d === null || d === void 0 ? void 0 : d.compaction) !== undefined ? { compaction: d.compaction } : {})), settings: {
219
+ : {})), { 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.service) ? { service: d.service } : {})), ((d === null || d === void 0 ? void 0 : d.resources) !== undefined ? { resources: d.resources } : {})), ((d === null || d === void 0 ? void 0 : d.compaction) !== undefined ? { compaction: d.compaction } : {})), settings: {
213
220
  embedMode: globalConfig.embedMode,
214
221
  modelTier: globalConfig.modelTier,
215
222
  vectorDim: globalConfig.vectorDim,
@@ -266,8 +273,8 @@ Examples:
266
273
  gmax status --json The same, plus daemon and settings, as JSON
267
274
  `)
268
275
  .action((opts) => __awaiter(void 0, void 0, void 0, function* () {
269
- var _a, _b;
270
- var _c, _d, _e, _f, _g, _h, _j;
276
+ var _a, _b, _c, _d;
277
+ var _e, _f, _g, _h, _j, _k, _l, _m;
271
278
  const globalConfig = (0, index_config_1.readGlobalConfig)();
272
279
  const projects = (0, project_registry_1.listProjects)();
273
280
  const indexing = (0, lock_1.isLocked)(config_1.PATHS.globalRoot);
@@ -308,7 +315,13 @@ Examples:
308
315
  // Header
309
316
  console.log(`\n${style.bold("gmax")} · ${globalConfig.modelTier} (${globalConfig.vectorDim}d, ${globalConfig.embedMode})${indexing ? style.yellow(" · indexing...") : ""}`);
310
317
  }
311
- if (startupBlockedReason !== null) {
318
+ if (((_b = (_a = view.daemon) === null || _a === void 0 ? void 0 : _a.service) === null || _b === void 0 ? void 0 : _b.mode) === "paused") {
319
+ const reason = ((_e = view.daemon.service.reason) !== null && _e !== void 0 ? _e : "host pressure").replace(/[\r\n\t]/g, " ");
320
+ console.log(opts.agent
321
+ ? `daemon_service\tmode=paused\treason=${reason}`
322
+ : style.yellow(`Bounded reads available; indexing and embeddings paused: ${reason}`));
323
+ }
324
+ else if (startupBlockedReason !== null) {
312
325
  const reason = startupBlockedReason.replace(/[\r\n\t]/g, " ");
313
326
  console.log(opts.agent
314
327
  ? `daemon_startup\tblocked=true\treason=${reason}`
@@ -318,14 +331,14 @@ Examples:
318
331
  if (opts.agent) {
319
332
  console.log(`store\t${store.home}\t${store.state}\tprefixes=${store.prefixes.join(",")}${store.error ? `\terror=${store.error}` : ""}`);
320
333
  for (const project of store.projects)
321
- console.log(`store_project\t${store.home}\t${project.name}\t${(_c = project.chunkCount) !== null && _c !== void 0 ? _c : 0}\t${(_d = project.status) !== null && _d !== void 0 ? _d : "indexed"}\tcounts=cached`);
334
+ console.log(`store_project\t${store.home}\t${project.name}\t${(_f = project.chunkCount) !== null && _f !== void 0 ? _f : 0}\t${(_g = project.status) !== null && _g !== void 0 ? _g : "indexed"}\tcounts=cached`);
322
335
  }
323
336
  else {
324
337
  console.log(`\nStore: ${shortenPath(store.home)} — ${store.state}${store.error ? ` (${store.error})` : ""}`);
325
338
  if (store.state === "offline")
326
339
  console.log(" Connect the drive to list its projects.");
327
340
  for (const project of store.projects)
328
- console.log(` ${project.name} ${formatChunks(project.chunkCount)} chunks (cached) ${(_e = project.status) !== null && _e !== void 0 ? _e : "indexed"}`);
341
+ console.log(` ${project.name} ${formatChunks(project.chunkCount)} chunks (cached) ${(_h = project.status) !== null && _h !== void 0 ? _h : "indexed"}`);
329
342
  }
330
343
  }
331
344
  if (projects.length === 0) {
@@ -342,9 +355,9 @@ Examples:
342
355
  for (const project of projects) {
343
356
  const st = projectState(project, watchers.get(project.root));
344
357
  const isCurrent = project.root === currentRoot;
345
- const count = (_f = chunkCounts.get(project.root)) !== null && _f !== void 0 ? _f : project.chunkCount;
358
+ const count = (_j = chunkCounts.get(project.root)) !== null && _j !== void 0 ? _j : project.chunkCount;
346
359
  const identity = (0, embedding_status_1.projectEmbeddingStatus)(project, globalConfig);
347
- 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${(_g = (0, index_state_footer_1.formatIndexStateFooter)(view.health.get(project.root), { agent: true })) !== null && _g !== void 0 ? _g : ""}` : ""}`);
360
+ console.log(`${project.name}\t${formatChunks(count)}\t${formatAge(project.lastIndexed)}\t${st}\tembedding=${identity.state}${isCurrent ? "\tcurrent" : ""}${((_c = view.health) === null || _c === void 0 ? void 0 : _c.get(project.root)) ? `\t${(_k = (0, index_state_footer_1.formatIndexStateFooter)(view.health.get(project.root), { agent: true })) !== null && _k !== void 0 ? _k : ""}` : ""}`);
348
361
  }
349
362
  const legacyNotice = (0, embedding_status_1.formatLegacyEmbeddingNotice)((0, embedding_status_1.countLegacyEmbeddingProjects)(projects, globalConfig), { agent: true });
350
363
  if (legacyNotice)
@@ -360,7 +373,7 @@ Examples:
360
373
  const watcher = watchers.get(project.root);
361
374
  // Status column
362
375
  let statusStr;
363
- const projectStatus = (_h = project.status) !== null && _h !== void 0 ? _h : "indexed";
376
+ const projectStatus = (_l = project.status) !== null && _l !== void 0 ? _l : "indexed";
364
377
  if (projectStatus === "pending") {
365
378
  statusStr = style.yellow("pending");
366
379
  }
@@ -380,7 +393,7 @@ Examples:
380
393
  statusStr = style.dim("idle");
381
394
  }
382
395
  // Chunks column
383
- const count = (_j = chunkCounts.get(project.root)) !== null && _j !== void 0 ? _j : project.chunkCount;
396
+ const count = (_m = chunkCounts.get(project.root)) !== null && _m !== void 0 ? _m : project.chunkCount;
384
397
  const chunks = `${formatChunks(count)} chunks`;
385
398
  // Age column
386
399
  const age = formatAge(project.lastIndexed);
@@ -389,7 +402,7 @@ Examples:
389
402
  const marker = isCurrent ? style.cyan(" ←") : "";
390
403
  const name = project.name.padEnd(nameWidth);
391
404
  console.log(` ${name} ${chunks.padEnd(12)} ${age.padEnd(10)} ${statusStr} embedding:${embedding}${marker}`);
392
- const health = (0, index_state_footer_1.formatIndexStateFooter)((_b = view.health) === null || _b === void 0 ? void 0 : _b.get(project.root), {
405
+ const health = (0, index_state_footer_1.formatIndexStateFooter)((_d = view.health) === null || _d === void 0 ? void 0 : _d.get(project.root), {
393
406
  agent: false,
394
407
  });
395
408
  if (health)
@@ -72,12 +72,18 @@ exports.watch = new commander_1.Command("watch")
72
72
  .description("Start background file watcher for live reindexing")
73
73
  .option("-b, --background", "Run watcher in background and exit")
74
74
  .option("-d, --daemon", "Run as centralized daemon watching projects that active sessions hold")
75
+ .option("--read-only", "Serve bounded reads without indexing, models, or clearing quarantine")
75
76
  .option("-p, --path <dir>", "Directory to watch (defaults to project root)")
76
77
  .option("--no-idle-timeout", "Disable the 30-minute idle shutdown")
77
78
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
78
79
  var _a, _b;
80
+ if (options.readOnly && !options.daemon) {
81
+ console.error("gmax: --read-only requires --daemon");
82
+ process.exitCode = 2;
83
+ return;
84
+ }
79
85
  const safetyDenied = (0, autostart_1.daemonStartDeniedReason)();
80
- if (safetyDenied !== null) {
86
+ if (safetyDenied !== null && !options.readOnly) {
81
87
  console.error(`gmax: ${safetyDenied}; preserving containment`);
82
88
  process.exitCode = 2;
83
89
  return;
@@ -114,6 +120,14 @@ exports.watch = new commander_1.Command("watch")
114
120
  const cliVersion = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf-8")).version;
115
121
  const resp = yield sendDaemonCommand({ cmd: "ping" });
116
122
  if (resp.version && resp.version === cliVersion) {
123
+ if (options.readOnly) {
124
+ const paused = yield sendDaemonCommand({
125
+ cmd: "pause",
126
+ reason: "read-only service requested",
127
+ });
128
+ if (!paused.ok)
129
+ throw new Error(String(paused.error));
130
+ }
117
131
  process.exit(0);
118
132
  }
119
133
  console.log(`Daemon version mismatch (${resp.version} → ${cliVersion}), restarting...`);
@@ -151,7 +165,9 @@ exports.watch = new commander_1.Command("watch")
151
165
  const { spawnDaemonProcess } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-launcher")));
152
166
  let started;
153
167
  try {
154
- started = yield spawnDaemonProcess();
168
+ started = options.readOnly
169
+ ? yield spawnDaemonProcess({ readOnly: true })
170
+ : yield spawnDaemonProcess();
155
171
  }
156
172
  catch (error) {
157
173
  const message = error instanceof Error ? error.message : String(error);
@@ -195,7 +211,10 @@ exports.watch = new commander_1.Command("watch")
195
211
  console.error("[daemon] unhandledRejection:", reason);
196
212
  });
197
213
  try {
198
- yield daemon.start();
214
+ if (options.readOnly)
215
+ yield daemon.start({ readOnly: true });
216
+ else
217
+ yield daemon.start();
199
218
  }
200
219
  catch (err) {
201
220
  const code = err === null || err === void 0 ? void 0 : err.code;