grepmax 0.26.73 → 0.26.75

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.73",
13
+ "version": "0.26.75",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/README.md CHANGED
@@ -528,7 +528,7 @@ The package includes a separate prune-only helper with pinned Lance 12 dependenc
528
528
 
529
529
  `gmax recover --table /path/to/lancedb/chunks.lance --json` reads the durable recovery receipt without starting a daemon or preparing Python. `--check` samples admission without pruning. `--prune --version <current-version> --cutoff <absolute-ISO-timestamp>` explicitly requests one offline prune. A prior uncertain attempt requires `--acknowledge-uncertain <attempt-id>` after inspection. Recovery never creates/clears quarantine, stops other owners, retries automatically or enables rewriting.
530
530
 
531
- Production recovery currently requires macOS, a persistent `autostart-disabled` file in the selected store home, and closed store owners. Recovery alone uses known healthy memory/kernel pressure, measured physical headroom and the shared aggregate resource budget; normal development retains its configured guard policy. The helper reserves 512 MiB of admission capacity; this is not an OS-enforced footprint cap. Recovery samples have an eight-second total ceiling, including the established five-second kernel probe allowance, with fresh sampling at deletion admission and five-second heartbeat scheduling. Unknown, stale, warning or critical measurements refuse/interrupt recovery. Pruning needs at least 1 MiB of measured metadata disk headroom, not full-table rewrite space. Initial wheel setup requires 512 MiB; an existing environment is checked against the lock offline without that download-space requirement.
531
+ Production recovery currently requires macOS, a persistent `autostart-disabled` file in the selected store home, and closed store owners. Recovery alone uses known healthy memory/kernel pressure and the shared aggregate resource budget; normal development retains its configured guard policy. Immediately free RAM is measured for diagnostics but has no fixed minimum: macOS can reclaim cached memory, so a low free-page count alone must not prevent cleanup. The helper reserves 512 MiB of admission capacity; this is not an OS-enforced footprint cap. Recovery samples have an eight-second total ceiling, including the established five-second kernel probe allowance, with fresh sampling at deletion admission and five-second heartbeat scheduling. Unknown, stale, warning or critical measurements refuse/interrupt recovery. Pruning needs at least 1 MiB of measured metadata disk headroom, not full-table rewrite space. Initial wheel setup requires 512 MiB; an existing environment is checked against the lock offline without that download-space requirement.
532
532
 
533
533
  The child waits for launch and deletion admission tokens, with verified helper PID/start identity recorded as the primary exclusive owner. Existing readers retain exclusion if the parent dies while the helper is alive. Current/tagged/post-cutoff metadata is verified after pruning; recovery refuses more than 256 protected versions before deletion to bound verification. Interrupted attempts require verification and explicit acknowledgement before retry. Linux/APFS fixtures exercise native retention and interruption; supported production host admission is tested separately and still runs freshly for each operation.
534
534
 
@@ -365,9 +365,10 @@ exports.watch = new commander_1.Command("watch")
365
365
  },
366
366
  });
367
367
  console.log(`[watch:${projectName}] File watcher active`);
368
+ (0, watcher_store_1.heartbeat)(process.pid, watcher.health);
368
369
  // Heartbeat — update LMDB every 60s so other processes can detect liveliness
369
370
  const heartbeatInterval = setInterval(() => {
370
- (0, watcher_store_1.heartbeat)(process.pid);
371
+ (0, watcher_store_1.heartbeat)(process.pid, watcher.health);
371
372
  }, IDLE_CHECK_INTERVAL_MS);
372
373
  // Idle timeout
373
374
  let lastActivity = Date.now();
@@ -401,8 +402,8 @@ exports.watch
401
402
  .command("status")
402
403
  .description("Show running watchers")
403
404
  .action(() => __awaiter(void 0, void 0, void 0, function* () {
404
- var _a;
405
- var _b, _c, _d;
405
+ var _a, _b, _c, _d;
406
+ var _e, _f, _g;
406
407
  const { sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
407
408
  const resp = yield sendDaemonCommand({ cmd: "status" });
408
409
  if (resp.ok) {
@@ -421,7 +422,10 @@ exports.watch
421
422
  if ((_a = p.indexState) === null || _a === void 0 ? void 0 : _a.queue)
422
423
  console.log(` queue: ${(0, index_state_footer_1.formatWatchQueue)(p.indexState.queue)}`);
423
424
  if (p.indexState)
424
- console.log(` watcher=${(_b = p.indexState.watcherMode) !== null && _b !== void 0 ? _b : "unknown"} overflows=${(_c = p.indexState.overflowCount) !== null && _c !== void 0 ? _c : 0} lastReconciled=${p.indexState.lastReconciledAt ? new Date(p.indexState.lastReconciledAt).toISOString() : "unknown"} catchupMs=${(_d = p.indexState.catchupMs) !== null && _d !== void 0 ? _d : "unknown"}`);
425
+ console.log(` watcher=${(_e = p.indexState.watcherMode) !== null && _e !== void 0 ? _e : "unknown"} overflows=${(_f = p.indexState.overflowCount) !== null && _f !== void 0 ? _f : 0} lastReconciled=${p.indexState.lastReconciledAt ? new Date(p.indexState.lastReconciledAt).toISOString() : "unknown"} catchupMs=${(_g = p.indexState.catchupMs) !== null && _g !== void 0 ? _g : "unknown"}`);
426
+ const recovery = (0, index_state_footer_1.formatWatcherRecovery)((_b = p.indexState) === null || _b === void 0 ? void 0 : _b.watcherRecovery);
427
+ if (recovery)
428
+ console.log(` recovery: ${recovery}`);
425
429
  }
426
430
  if (projects.length === 0) {
427
431
  console.log(" (no projects)");
@@ -434,6 +438,13 @@ exports.watch
434
438
  for (const w of watchers) {
435
439
  const age = Math.floor((Date.now() - w.startTime) / 60000);
436
440
  console.log(` - PID: ${w.pid} | Root: ${w.projectRoot} | Running: ${age}m`);
441
+ const recovery = (0, index_state_footer_1.formatWatcherRecovery)((_c = w.indexState) === null || _c === void 0 ? void 0 : _c.watcherRecovery);
442
+ if (recovery) {
443
+ console.log(` health sample: ${w.indexStateAt ? new Date(w.indexStateAt).toISOString() : "unknown"}`);
444
+ console.log(` recovery: ${recovery}`);
445
+ if ((_d = w.indexState) === null || _d === void 0 ? void 0 : _d.queue)
446
+ console.log(` queue: ${(0, index_state_footer_1.formatWatchQueue)(w.indexState.queue)}`);
447
+ }
437
448
  }
438
449
  }
439
450
  if (!resp.ok && watchers.length === 0) {
@@ -58,6 +58,7 @@ const walker_1 = require("../index/walker");
58
58
  const watcher_1 = require("../index/watcher");
59
59
  const watcher_errors_1 = require("../index/watcher-errors");
60
60
  const watcher_ignore_1 = require("../index/watcher-ignore");
61
+ const watcher_recovery_1 = require("../index/watcher-recovery");
61
62
  const file_utils_1 = require("../utils/file-utils");
62
63
  const logger_1 = require("../utils/logger");
63
64
  const project_registry_1 = require("../utils/project-registry");
@@ -97,6 +98,14 @@ function abortableDelay(ms, signal) {
97
98
  * thin watchProject/unwatchProject delegators over this manager.
98
99
  */
99
100
  class WatcherManager {
101
+ recovery(root) {
102
+ let tracker = this.recoveryStates.get(root);
103
+ if (!tracker) {
104
+ tracker = new watcher_recovery_1.WatcherRecoveryTracker();
105
+ this.recoveryStates.set(root, tracker);
106
+ }
107
+ return tracker;
108
+ }
100
109
  constructor(deps) {
101
110
  this.deps = deps;
102
111
  this.pendingOps = new Set();
@@ -117,14 +126,15 @@ class WatcherManager {
117
126
  this.degradedRoots = new Set();
118
127
  this.terminalFailures = new Map();
119
128
  this.watchLifecycles = new Map();
129
+ this.recoveryStates = new Map();
120
130
  this.subscriptionUpdates = new Map();
121
131
  this.watcherGenerations = new Map();
122
132
  }
123
133
  health(root) {
124
- var _a;
125
- var _b, _c;
134
+ var _a, _b;
135
+ var _c, _d;
126
136
  return {
127
- failedFiles: (_b = (_a = this.terminalFailures.get(root)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0,
137
+ failedFiles: (_c = (_a = this.terminalFailures.get(root)) === null || _a === void 0 ? void 0 : _a.size) !== null && _c !== void 0 ? _c : 0,
128
138
  degraded: this.isRootDegraded(root),
129
139
  watcherMode: this.pollIntervals.has(root)
130
140
  ? "polling"
@@ -135,8 +145,9 @@ class WatcherManager {
135
145
  : "native",
136
146
  catchupRunning: this.catchups.has(root),
137
147
  lastReconciledAt: this.reconciledAt.get(root),
138
- overflowCount: (_c = this.overflowCounts.get(root)) !== null && _c !== void 0 ? _c : 0,
148
+ overflowCount: (_d = this.overflowCounts.get(root)) !== null && _d !== void 0 ? _d : 0,
139
149
  catchupMs: this.catchupDurations.get(root),
150
+ watcherRecovery: (_b = this.recoveryStates.get(root)) === null || _b === void 0 ? void 0 : _b.snapshot(),
140
151
  };
141
152
  }
142
153
  watchProject(root_1) {
@@ -325,8 +336,9 @@ class WatcherManager {
325
336
  }
326
337
  catch (_c) { }
327
338
  this.deps.subscriptions.delete(root);
339
+ this.recovery(root).detached();
328
340
  }
329
- const { subscription: sub } = yield (0, watcher_ignore_1.subscribeWithNativeExclusions)(root, (err, events) => {
341
+ const { subscription: sub, policy } = yield (0, watcher_ignore_1.subscribeWithNativeExclusions)(root, (err, events) => {
330
342
  var _a, _b;
331
343
  var _c, _d;
332
344
  if (this.deps.processors.get(root) !== processor ||
@@ -334,6 +346,7 @@ class WatcherManager {
334
346
  ((_b = (_a = this.deps).getShuttingDown) === null || _b === void 0 ? void 0 : _b.call(_a)))
335
347
  return;
336
348
  if (err) {
349
+ this.recovery(root).error(err);
337
350
  this.overflowCounts.set(root, ((_c = this.overflowCounts.get(root)) !== null && _c !== void 0 ? _c : 0) + 1);
338
351
  const now = Date.now();
339
352
  if (!this.lastWatcherErrorLogMs.has(root) ||
@@ -366,6 +379,7 @@ class WatcherManager {
366
379
  return;
367
380
  }
368
381
  this.deps.subscriptions.set(root, sub);
382
+ this.recovery(root).attached(policy);
369
383
  });
370
384
  }
371
385
  requestOverflowCatchup(root, processor) {
@@ -409,6 +423,7 @@ class WatcherManager {
409
423
  if (sub) {
410
424
  sub.unsubscribe().catch(() => { });
411
425
  this.deps.subscriptions.delete(root);
426
+ this.recovery(root).detached();
412
427
  }
413
428
  if (!this.pollIntervals.has(root)) {
414
429
  console.error(`[daemon:${name}] Native watcher failed after ${fails} terminal errors — switching to poll mode (${POLL_INTERVAL_MS / 60000}min interval)`);
@@ -583,186 +598,199 @@ class WatcherManager {
583
598
  var _a, e_1, _b, _c;
584
599
  var _d;
585
600
  var _e, _f, _g;
586
- const scanStart = Date.now();
587
- const overflowCountAtStart = (_e = this.overflowCounts.get(root)) !== null && _e !== void 0 ? _e : 0;
588
- const { isFileCached } = yield Promise.resolve().then(() => __importStar(require("../utils/cache-check")));
589
- const metaCache = this.deps.getMetaCache();
590
- const vectorDb = this.deps.getVectorDb();
591
- processor.filePolicy.invalidateIgnoreCache();
592
- const rootPrefix = root.endsWith("/") ? root : `${root}/`;
593
- const cachedPaths = yield metaCache.getKeysWithPrefix(rootPrefix);
594
- const vectorPaths = yield vectorDb.getDistinctPathsForPrefix(rootPrefix);
595
- const knownPaths = new Set([...cachedPaths, ...vectorPaths]);
596
- if (signal.aborted)
597
- return false;
598
- const seenPaths = new Set();
599
- const walkState = (0, walker_1.createWalkState)();
600
- let queued = 0;
601
- let skipped = 0;
602
- let debugSamples = 0;
601
+ const tracker = this.recovery(root);
602
+ const scan = tracker.beginScan();
603
+ let outcome = "failed";
603
604
  try {
604
- for (var _h = true, _j = __asyncValues((0, walker_1.walk)(root, {
605
- policy: processor.filePolicy,
606
- state: walkState,
607
- })), _k; _k = yield _j.next(), _a = _k.done, !_a; _h = true) {
608
- _c = _k.value;
609
- _h = false;
610
- const relPath = _c;
611
- if (signal.aborted)
612
- return false;
613
- const absPath = path.join(root, relPath);
614
- try {
615
- const classification = yield processor.filePolicy.classifyFile(absPath);
616
- if (classification.status === "error") {
617
- walkState.protectedPaths.add(classification.protectedPath);
618
- walkState.errors.push({
619
- path: classification.protectedPath,
620
- error: classification.error,
621
- });
622
- continue;
623
- }
624
- if (classification.status !== "indexable")
625
- continue;
626
- const stats = classification.stat;
627
- seenPaths.add(absPath);
628
- let cached = metaCache.get(absPath);
629
- const reconciliation = (0, cache_coherence_1.reconcileMetaEntry)(absPath, cached, vectorPaths.has(absPath));
630
- if (reconciliation.action === "stamp") {
631
- cached = reconciliation.entry;
632
- metaCache.put(absPath, cached);
633
- }
634
- else if (reconciliation.action === "reprocess") {
635
- processor.handleFileEvent("change", absPath, {
636
- forceReprocess: true,
637
- workKind: "catchup",
638
- });
639
- queued++;
640
- if (queued % 500 === 0) {
641
- (0, logger_1.debug)("catchup", `${path.basename(root)}: throttle pause at ${queued} queued`);
642
- yield abortableDelay(5000, signal);
605
+ const scanStart = Date.now();
606
+ const overflowCountAtStart = (_e = this.overflowCounts.get(root)) !== null && _e !== void 0 ? _e : 0;
607
+ const { isFileCached } = yield Promise.resolve().then(() => __importStar(require("../utils/cache-check")));
608
+ const metaCache = this.deps.getMetaCache();
609
+ const vectorDb = this.deps.getVectorDb();
610
+ processor.filePolicy.invalidateIgnoreCache();
611
+ const rootPrefix = root.endsWith("/") ? root : `${root}/`;
612
+ const cachedPaths = yield metaCache.getKeysWithPrefix(rootPrefix);
613
+ const vectorPaths = yield vectorDb.getDistinctPathsForPrefix(rootPrefix);
614
+ const knownPaths = new Set([...cachedPaths, ...vectorPaths]);
615
+ if (signal.aborted)
616
+ return false;
617
+ const seenPaths = new Set();
618
+ const walkState = (0, walker_1.createWalkState)();
619
+ let queued = 0;
620
+ let skipped = 0;
621
+ let debugSamples = 0;
622
+ try {
623
+ for (var _h = true, _j = __asyncValues((0, walker_1.walk)(root, {
624
+ policy: processor.filePolicy,
625
+ state: walkState,
626
+ })), _k; _k = yield _j.next(), _a = _k.done, !_a; _h = true) {
627
+ _c = _k.value;
628
+ _h = false;
629
+ const relPath = _c;
630
+ if (signal.aborted)
631
+ return false;
632
+ const absPath = path.join(root, relPath);
633
+ try {
634
+ const classification = yield processor.filePolicy.classifyFile(absPath);
635
+ if (classification.status === "error") {
636
+ walkState.protectedPaths.add(classification.protectedPath);
637
+ walkState.errors.push({
638
+ path: classification.protectedPath,
639
+ error: classification.error,
640
+ });
641
+ continue;
643
642
  }
644
- continue;
645
- }
646
- if (!isFileCached(cached, stats)) {
647
- // Fast path: if only mtime changed but size is identical and we have a hash,
648
- // just verify the hash in-process instead of sending to a worker.
649
- if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
650
- const observed = Object.assign({}, cached);
651
- const snapshot = yield (0, file_utils_1.readFileSnapshot)(absPath, {
652
- projectRoot: root,
643
+ if (classification.status !== "indexable")
644
+ continue;
645
+ const stats = classification.stat;
646
+ seenPaths.add(absPath);
647
+ let cached = metaCache.get(absPath);
648
+ const reconciliation = (0, cache_coherence_1.reconcileMetaEntry)(absPath, cached, vectorPaths.has(absPath));
649
+ if (reconciliation.action === "stamp") {
650
+ cached = reconciliation.entry;
651
+ metaCache.put(absPath, cached);
652
+ }
653
+ else if (reconciliation.action === "reprocess") {
654
+ processor.handleFileEvent("change", absPath, {
655
+ forceReprocess: true,
656
+ workKind: "catchup",
653
657
  });
654
- if (snapshot.size !== stats.size ||
655
- snapshot.mtimeMs !== stats.mtimeMs) {
656
- processor.handleFileEvent("change", absPath, {
657
- workKind: "catchup",
658
- });
659
- continue;
658
+ queued++;
659
+ if (queued % 500 === 0) {
660
+ (0, logger_1.debug)("catchup", `${path.basename(root)}: throttle pause at ${queued} queued`);
661
+ yield abortableDelay(5000, signal);
660
662
  }
661
- const hash = (0, file_utils_1.computeContentHash)(snapshot.buffer, absPath);
662
- if (hash === observed.hash) {
663
- // Content unchanged — update mtime in cache and skip worker
664
- if (signal.aborted)
665
- return false;
666
- // A live batch can finish while this read yields. Never replace
667
- // newer metadata or resurrect a cache entry it removed.
668
- const current = metaCache.get(absPath);
669
- if (current &&
670
- current.hash === observed.hash &&
671
- current.mtimeMs === observed.mtimeMs &&
672
- current.size === observed.size &&
673
- current.hashVersion === observed.hashVersion &&
674
- current.hasVectors === observed.hasVectors) {
675
- metaCache.put(absPath, Object.assign(Object.assign({}, observed), { mtimeMs: stats.mtimeMs }));
663
+ continue;
664
+ }
665
+ if (!isFileCached(cached, stats)) {
666
+ // Fast path: if only mtime changed but size is identical and we have a hash,
667
+ // just verify the hash in-process instead of sending to a worker.
668
+ if ((cached === null || cached === void 0 ? void 0 : cached.hash) && cached.size === stats.size) {
669
+ const observed = Object.assign({}, cached);
670
+ const snapshot = yield (0, file_utils_1.readFileSnapshot)(absPath, {
671
+ projectRoot: root,
672
+ });
673
+ if (snapshot.size !== stats.size ||
674
+ snapshot.mtimeMs !== stats.mtimeMs) {
675
+ processor.handleFileEvent("change", absPath, {
676
+ workKind: "catchup",
677
+ });
678
+ continue;
679
+ }
680
+ const hash = (0, file_utils_1.computeContentHash)(snapshot.buffer, absPath);
681
+ if (hash === observed.hash) {
682
+ // Content unchanged — update mtime in cache and skip worker
683
+ if (signal.aborted)
684
+ return false;
685
+ // A live batch can finish while this read yields. Never replace
686
+ // newer metadata or resurrect a cache entry it removed.
687
+ const current = metaCache.get(absPath);
688
+ if (current &&
689
+ current.hash === observed.hash &&
690
+ current.mtimeMs === observed.mtimeMs &&
691
+ current.size === observed.size &&
692
+ current.hashVersion === observed.hashVersion &&
693
+ current.hasVectors === observed.hasVectors) {
694
+ metaCache.put(absPath, Object.assign(Object.assign({}, observed), { mtimeMs: stats.mtimeMs }));
695
+ }
696
+ skipped++;
697
+ continue;
676
698
  }
677
- skipped++;
678
- continue;
699
+ }
700
+ // Debug: log first few misses to diagnose re-queue loops
701
+ if (debugSamples < 5) {
702
+ (0, logger_1.debug)("catchup", `miss ${relPath}: cached=${cached ? `mtime=${Math.trunc(cached.mtimeMs)} size=${cached.size}` : "null"} stat=mtime=${Math.trunc(stats.mtimeMs)} size=${stats.size}`);
703
+ debugSamples++;
704
+ }
705
+ if (signal.aborted)
706
+ return false;
707
+ processor.handleFileEvent("change", absPath, {
708
+ workKind: "catchup",
709
+ });
710
+ queued++;
711
+ // Throttle: pause periodically during large catchup scans to let the
712
+ // batch processor drain and compaction run between bursts.
713
+ if (queued % 500 === 0) {
714
+ (0, logger_1.debug)("catchup", `${path.basename(root)}: throttle pause at ${queued} queued`);
715
+ yield abortableDelay(5000, signal);
679
716
  }
680
717
  }
681
- // Debug: log first few misses to diagnose re-queue loops
682
- if (debugSamples < 5) {
683
- (0, logger_1.debug)("catchup", `miss ${relPath}: cached=${cached ? `mtime=${Math.trunc(cached.mtimeMs)} size=${cached.size}` : "null"} stat=mtime=${Math.trunc(stats.mtimeMs)} size=${stats.size}`);
684
- debugSamples++;
685
- }
686
- if (signal.aborted)
687
- return false;
688
- processor.handleFileEvent("change", absPath, { workKind: "catchup" });
689
- queued++;
690
- // Throttle: pause periodically during large catchup scans to let the
691
- // batch processor drain and compaction run between bursts.
692
- if (queued % 500 === 0) {
693
- (0, logger_1.debug)("catchup", `${path.basename(root)}: throttle pause at ${queued} queued`);
694
- yield abortableDelay(5000, signal);
718
+ else {
719
+ skipped++;
695
720
  }
696
721
  }
697
- else {
698
- skipped++;
722
+ catch (error) {
723
+ walkState.protectedPaths.add(absPath);
724
+ walkState.errors.push({ path: absPath, error });
699
725
  }
700
726
  }
701
- catch (error) {
702
- walkState.protectedPaths.add(absPath);
703
- walkState.errors.push({ path: absPath, error });
727
+ }
728
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
729
+ finally {
730
+ try {
731
+ if (!_h && !_a && (_b = _j.return)) yield _b.call(_j);
704
732
  }
733
+ finally { if (e_1) throw e_1.error; }
705
734
  }
706
- }
707
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
708
- finally {
709
- try {
710
- if (!_h && !_a && (_b = _j.return)) yield _b.call(_j);
735
+ (0, logger_1.debug)("catchup", `${path.basename(root)}: ${queued} queued, ${skipped} skipped (cached ok), ${seenPaths.size} total`);
736
+ // Purge files deleted while daemon was offline
737
+ let purged = 0;
738
+ for (const cachedPath of knownPaths) {
739
+ if (signal.aborted)
740
+ return false;
741
+ if (!seenPaths.has(cachedPath) &&
742
+ !(0, walker_1.isPathProtectedByWalkState)(cachedPath, walkState)) {
743
+ processor.handleFileEvent("unlink", cachedPath, {
744
+ forceDelete: true,
745
+ workKind: "cleanup",
746
+ });
747
+ purged++;
748
+ }
711
749
  }
712
- finally { if (e_1) throw e_1.error; }
713
- }
714
- (0, logger_1.debug)("catchup", `${path.basename(root)}: ${queued} queued, ${skipped} skipped (cached ok), ${seenPaths.size} total`);
715
- // Purge files deleted while daemon was offline
716
- let purged = 0;
717
- for (const cachedPath of knownPaths) {
718
- if (signal.aborted)
719
- return false;
720
- if (!seenPaths.has(cachedPath) &&
721
- !(0, walker_1.isPathProtectedByWalkState)(cachedPath, walkState)) {
722
- processor.handleFileEvent("unlink", cachedPath, {
723
- forceDelete: true,
724
- workKind: "cleanup",
750
+ const complete = walkState.rootComplete && walkState.errors.length === 0;
751
+ if (!complete) {
752
+ this.degradedRoots.add(root);
753
+ (0, watcher_store_1.registerWatcher)({
754
+ pid: process.pid,
755
+ projectRoot: root,
756
+ startTime: Date.now(),
757
+ status: "degraded",
758
+ lastHeartbeat: Date.now(),
759
+ lastError: `${walkState.errors.length} scan path(s) incomplete`,
725
760
  });
726
- purged++;
727
761
  }
762
+ else {
763
+ this.degradedRoots.delete(root);
764
+ const failedFiles = (_f = (_d = this.terminalFailures.get(root)) === null || _d === void 0 ? void 0 : _d.size) !== null && _f !== void 0 ? _f : 0;
765
+ (0, watcher_store_1.registerWatcher)(Object.assign({ pid: process.pid, projectRoot: root, startTime: Date.now(), status: this.isRootDegraded(root) ? "degraded" : "watching", lastHeartbeat: Date.now() }, (failedFiles > 0
766
+ ? {
767
+ lastError: `${failedFiles} file(s) exhausted automatic retries`,
768
+ }
769
+ : {})));
770
+ }
771
+ if (queued > 0 || purged > 0) {
772
+ const parts = [];
773
+ if (queued > 0)
774
+ parts.push(`${queued} changed`);
775
+ if (purged > 0)
776
+ parts.push(`${purged} deleted`);
777
+ console.log(`[daemon:${path.basename(root)}] Catchup: ${parts.join(", ")} file(s) while offline`);
778
+ }
779
+ const ended = Date.now();
780
+ this.lastCatchupEndMs.set(root, ended);
781
+ this.catchupDurations.set(root, ended - scanStart);
782
+ if (complete) {
783
+ this.reconciledAt.set(root, ended);
784
+ // A later gap still needs its deferred scan, even if this walk succeeded.
785
+ if (((_g = this.overflowCounts.get(root)) !== null && _g !== void 0 ? _g : 0) === overflowCountAtStart)
786
+ this.overflowPendingRoots.delete(root);
787
+ }
788
+ outcome = complete ? "complete" : "incomplete";
789
+ return complete;
728
790
  }
729
- const complete = walkState.rootComplete && walkState.errors.length === 0;
730
- if (!complete) {
731
- this.degradedRoots.add(root);
732
- (0, watcher_store_1.registerWatcher)({
733
- pid: process.pid,
734
- projectRoot: root,
735
- startTime: Date.now(),
736
- status: "degraded",
737
- lastHeartbeat: Date.now(),
738
- lastError: `${walkState.errors.length} scan path(s) incomplete`,
739
- });
740
- }
741
- else {
742
- this.degradedRoots.delete(root);
743
- const failedFiles = (_f = (_d = this.terminalFailures.get(root)) === null || _d === void 0 ? void 0 : _d.size) !== null && _f !== void 0 ? _f : 0;
744
- (0, watcher_store_1.registerWatcher)(Object.assign({ pid: process.pid, projectRoot: root, startTime: Date.now(), status: this.isRootDegraded(root) ? "degraded" : "watching", lastHeartbeat: Date.now() }, (failedFiles > 0
745
- ? { lastError: `${failedFiles} file(s) exhausted automatic retries` }
746
- : {})));
747
- }
748
- if (queued > 0 || purged > 0) {
749
- const parts = [];
750
- if (queued > 0)
751
- parts.push(`${queued} changed`);
752
- if (purged > 0)
753
- parts.push(`${purged} deleted`);
754
- console.log(`[daemon:${path.basename(root)}] Catchup: ${parts.join(", ")} file(s) while offline`);
755
- }
756
- const ended = Date.now();
757
- this.lastCatchupEndMs.set(root, ended);
758
- this.catchupDurations.set(root, ended - scanStart);
759
- if (complete) {
760
- this.reconciledAt.set(root, ended);
761
- // A later gap still needs its deferred scan, even if this walk succeeded.
762
- if (((_g = this.overflowCounts.get(root)) !== null && _g !== void 0 ? _g : 0) === overflowCountAtStart)
763
- this.overflowPendingRoots.delete(root);
791
+ finally {
792
+ tracker.finishScan(scan, signal.aborted ? "aborted" : outcome);
764
793
  }
765
- return complete;
766
794
  });
767
795
  }
768
796
  runCatchup(root, processor) {
@@ -792,9 +820,19 @@ class WatcherManager {
792
820
  }
793
821
  } while (active.dirty && !signal.aborted);
794
822
  });
795
- const run = (this.deps.runProjectOperation
796
- ? this.deps.runProjectOperation(root, "watch-catchup", active.controller.signal, execute)
797
- : execute(active.controller.signal)).finally(() => {
823
+ const tracker = this.recovery(root);
824
+ const scansBeforeAttempt = tracker.snapshot().scanCount;
825
+ const run = (() => __awaiter(this, void 0, void 0, function* () {
826
+ try {
827
+ return yield (this.deps.runProjectOperation
828
+ ? this.deps.runProjectOperation(root, "watch-catchup", active.controller.signal, execute)
829
+ : execute(active.controller.signal));
830
+ }
831
+ catch (error) {
832
+ tracker.attemptFailed(tracker.snapshot().scanCount > scansBeforeAttempt, active.controller.signal.aborted);
833
+ throw error;
834
+ }
835
+ }))().finally(() => {
798
836
  if (this.catchups.get(root) === active)
799
837
  this.catchups.delete(root);
800
838
  });
@@ -855,6 +893,7 @@ class WatcherManager {
855
893
  this.overflowPendingRoots.delete(root);
856
894
  this.degradedRoots.delete(root);
857
895
  this.terminalFailures.delete(root);
896
+ this.recoveryStates.delete(root);
858
897
  (0, watcher_store_1.unregisterWatcherByRoot)(root);
859
898
  console.log(`[daemon] Unwatched ${root}`);
860
899
  });
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WatcherRecoveryTracker = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const watcher_errors_1 = require("./watcher-errors");
6
+ /** Fixed-size, process/lifecycle-local evidence. A completed walk covers only
7
+ * errors already observed when it began; queued ingestion is reported separately. */
8
+ class WatcherRecoveryTracker {
9
+ constructor() {
10
+ this.gapCount = 0;
11
+ this.terminalErrorCount = 0;
12
+ this.coveredGapCount = 0;
13
+ this.coveredTerminalErrorCount = 0;
14
+ this.scanCount = 0;
15
+ this.scanIncomplete = false;
16
+ }
17
+ error(error) {
18
+ if ((0, watcher_errors_1.isFSEventsGap)(error)) {
19
+ this.gapCount++;
20
+ this.lastGapAt = Date.now();
21
+ }
22
+ else {
23
+ this.terminalErrorCount++;
24
+ this.lastTerminalErrorAt = Date.now();
25
+ }
26
+ }
27
+ beginScan() {
28
+ const scan = {
29
+ id: ++this.scanCount,
30
+ startedAt: Date.now(),
31
+ gapCountAtStart: this.gapCount,
32
+ terminalErrorCountAtStart: this.terminalErrorCount,
33
+ outcome: "running",
34
+ };
35
+ this.lastScan = scan;
36
+ return scan;
37
+ }
38
+ finishScan(scan, outcome) {
39
+ scan.finishedAt = Date.now();
40
+ scan.outcome = outcome;
41
+ if (outcome === "complete") {
42
+ this.scanIncomplete = false;
43
+ this.coveredGapCount = Math.max(this.coveredGapCount, scan.gapCountAtStart);
44
+ this.coveredTerminalErrorCount = Math.max(this.coveredTerminalErrorCount, scan.terminalErrorCountAtStart);
45
+ this.lastCompleteScan = Object.assign({}, scan);
46
+ }
47
+ else
48
+ this.scanIncomplete = true;
49
+ }
50
+ attemptFailed(scanStarted, aborted) {
51
+ this.scanIncomplete = true;
52
+ this.lastAttemptFailure = { at: Date.now(), scanStarted, aborted };
53
+ }
54
+ attached(policy) {
55
+ // The subscription helper returns the exact accepted policy, including its
56
+ // fallback. Summarize that value; never rediscover directories for health.
57
+ const patterns = JSON.parse(policy);
58
+ const literalCount = patterns.filter((pattern) => !pattern.startsWith("/") && !pattern.includes("*")).length;
59
+ this.exclusions = {
60
+ attached: true,
61
+ literalCount,
62
+ filterCount: patterns.length - literalCount,
63
+ fingerprint: (0, node_crypto_1.createHash)("sha256").update(policy).digest("hex"),
64
+ };
65
+ }
66
+ detached() {
67
+ if (this.exclusions)
68
+ this.exclusions.attached = false;
69
+ }
70
+ snapshot() {
71
+ var _a;
72
+ return {
73
+ gapCount: this.gapCount,
74
+ terminalErrorCount: this.terminalErrorCount,
75
+ coveredGapCount: this.coveredGapCount,
76
+ outstandingGapCount: this.gapCount - this.coveredGapCount,
77
+ reconciliationNeeded: this.scanIncomplete ||
78
+ ((_a = this.lastScan) === null || _a === void 0 ? void 0 : _a.outcome) === "running" ||
79
+ this.gapCount > this.coveredGapCount ||
80
+ this.terminalErrorCount > this.coveredTerminalErrorCount,
81
+ lastGapAt: this.lastGapAt,
82
+ lastTerminalErrorAt: this.lastTerminalErrorAt,
83
+ scanCount: this.scanCount,
84
+ lastScan: this.lastScan && Object.assign({}, this.lastScan),
85
+ lastCompleteScan: this.lastCompleteScan && Object.assign({}, this.lastCompleteScan),
86
+ lastAttemptFailure: this.lastAttemptFailure && Object.assign({}, this.lastAttemptFailure),
87
+ exclusions: this.exclusions && Object.assign({}, this.exclusions),
88
+ };
89
+ }
90
+ }
91
+ exports.WatcherRecoveryTracker = WatcherRecoveryTracker;
@@ -59,6 +59,7 @@ const ignore_patterns_1 = require("./ignore-patterns");
59
59
  const walker_1 = require("./walker");
60
60
  const watcher_errors_1 = require("./watcher-errors");
61
61
  const watcher_ignore_1 = require("./watcher-ignore");
62
+ const watcher_recovery_1 = require("./watcher-recovery");
62
63
  // Directory names ignored at ANY depth. @parcel/watcher treats a non-glob
63
64
  // ignore entry as a literal path resolved against the watch root, so a bare
64
65
  // "dist" only covered <root>/dist — nested build dirs (packages/*/dist,
@@ -114,6 +115,7 @@ function startWatcher(opts) {
114
115
  const { projectRoot } = opts;
115
116
  const wtag = `watch:${projectRoot.split("/").pop()}`;
116
117
  const filePolicy = new file_policy_1.ProjectFilePolicy(projectRoot);
118
+ const recovery = new watcher_recovery_1.WatcherRecoveryTracker();
117
119
  let reconciliation = null;
118
120
  let reconcileRequested = false;
119
121
  let closing = false;
@@ -143,6 +145,7 @@ function startWatcher(opts) {
143
145
  subscriptionFailed = false;
144
146
  yield subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
145
147
  subscription = null;
148
+ recovery.detached();
146
149
  if (closing)
147
150
  return;
148
151
  const { subscription: next, policy: attachedPolicy } = yield (0, watcher_ignore_1.subscribeWithNativeExclusions)(projectRoot, (err, events) => {
@@ -150,6 +153,7 @@ function startWatcher(opts) {
150
153
  if (closing || generation !== subscriptionGeneration)
151
154
  return;
152
155
  if (err) {
156
+ recovery.error(err);
153
157
  console.error(`[${wtag}] Watcher error:`, err);
154
158
  scanHealthy = false;
155
159
  (_a = opts.onHealthChange) === null || _a === void 0 ? void 0 : _a.call(opts, false, 1);
@@ -171,6 +175,7 @@ function startWatcher(opts) {
171
175
  }
172
176
  subscription = next;
173
177
  subscriptionPolicy = attachedPolicy;
178
+ recovery.attached(attachedPolicy);
174
179
  }));
175
180
  subscriptionUpdate = update;
176
181
  return update;
@@ -199,76 +204,88 @@ function startWatcher(opts) {
199
204
  reconcileRequested = false;
200
205
  lastReconcileStart = Date.now();
201
206
  const observedGapGeneration = gapGeneration;
202
- filePolicy.invalidateIgnoreCache();
203
- yield updateSubscription();
204
- if (closing)
205
- return;
206
- const rootPrefix = projectRoot.endsWith("/")
207
- ? projectRoot
208
- : `${projectRoot}/`;
209
- const cached = yield opts.metaCache.getKeysWithPrefix(rootPrefix);
210
- const vectorPaths = yield opts.vectorDb.getDistinctPathsForPrefix(rootPrefix);
211
- const knownPaths = new Set([...cached, ...vectorPaths]);
212
- const seen = new Set();
213
- const state = (0, walker_1.createWalkState)();
207
+ const scan = recovery.beginScan();
208
+ let outcome = "failed";
209
+ let retryDelayMs = 0;
214
210
  try {
215
- for (var _e = true, _f = (e_1 = void 0, __asyncValues((0, walker_1.walk)(projectRoot, {
216
- policy: filePolicy,
217
- state,
218
- }))), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
219
- _c = _g.value;
220
- _e = false;
221
- const relative = _c;
211
+ filePolicy.invalidateIgnoreCache();
212
+ yield updateSubscription();
213
+ if (closing)
214
+ return;
215
+ const rootPrefix = projectRoot.endsWith("/")
216
+ ? projectRoot
217
+ : `${projectRoot}/`;
218
+ const cached = yield opts.metaCache.getKeysWithPrefix(rootPrefix);
219
+ const vectorPaths = yield opts.vectorDb.getDistinctPathsForPrefix(rootPrefix);
220
+ const knownPaths = new Set([...cached, ...vectorPaths]);
221
+ const seen = new Set();
222
+ const state = (0, walker_1.createWalkState)();
223
+ try {
224
+ for (var _e = true, _f = (e_1 = void 0, __asyncValues((0, walker_1.walk)(projectRoot, {
225
+ policy: filePolicy,
226
+ state,
227
+ }))), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
228
+ _c = _g.value;
229
+ _e = false;
230
+ const relative = _c;
231
+ if (closing)
232
+ return;
233
+ const absolute = path.join(projectRoot, relative);
234
+ seen.add(absolute);
235
+ const reconciliation = (0, cache_coherence_1.reconcileMetaEntry)(absolute, opts.metaCache.get(absolute), vectorPaths.has(absolute));
236
+ if (reconciliation.action === "stamp") {
237
+ opts.metaCache.put(absolute, reconciliation.entry);
238
+ }
239
+ processor.handleFileEvent("change", absolute, {
240
+ forceReprocess: reconciliation.action === "reprocess",
241
+ workKind: "catchup",
242
+ });
243
+ }
244
+ }
245
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
246
+ finally {
247
+ try {
248
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
249
+ }
250
+ finally { if (e_1) throw e_1.error; }
251
+ }
252
+ for (const cachedPath of knownPaths) {
222
253
  if (closing)
223
254
  return;
224
- const absolute = path.join(projectRoot, relative);
225
- seen.add(absolute);
226
- const reconciliation = (0, cache_coherence_1.reconcileMetaEntry)(absolute, opts.metaCache.get(absolute), vectorPaths.has(absolute));
227
- if (reconciliation.action === "stamp") {
228
- opts.metaCache.put(absolute, reconciliation.entry);
255
+ if (!seen.has(cachedPath) &&
256
+ !(0, walker_1.isPathProtectedByWalkState)(cachedPath, state)) {
257
+ processor.handleFileEvent("unlink", cachedPath, {
258
+ forceDelete: true,
259
+ workKind: "cleanup",
260
+ });
229
261
  }
230
- processor.handleFileEvent("change", absolute, {
231
- forceReprocess: reconciliation.action === "reprocess",
232
- workKind: "catchup",
233
- });
234
- }
235
- }
236
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
237
- finally {
238
- try {
239
- if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
240
262
  }
241
- finally { if (e_1) throw e_1.error; }
242
- }
243
- for (const cachedPath of knownPaths) {
244
- if (closing)
245
- return;
246
- if (!seen.has(cachedPath) &&
247
- !(0, walker_1.isPathProtectedByWalkState)(cachedPath, state)) {
248
- processor.handleFileEvent("unlink", cachedPath, {
249
- forceDelete: true,
250
- workKind: "cleanup",
251
- });
263
+ if (!state.rootComplete || state.errors.length > 0) {
264
+ outcome = "incomplete";
265
+ scanHealthy = false;
266
+ (_d = opts.onHealthChange) === null || _d === void 0 ? void 0 : _d.call(opts, false, state.errors.length);
267
+ console.error(`[${wtag}] Reconciliation incomplete at ${state.errors.length} path(s)`);
268
+ if (incompleteRetries < 3) {
269
+ incompleteRetries++;
270
+ reconcileRequested = true;
271
+ retryDelayMs = 1000 * Math.pow(2, (incompleteRetries - 1));
272
+ }
252
273
  }
253
- }
254
- if (!state.rootComplete || state.errors.length > 0) {
255
- scanHealthy = false;
256
- (_d = opts.onHealthChange) === null || _d === void 0 ? void 0 : _d.call(opts, false, state.errors.length);
257
- console.error(`[${wtag}] Reconciliation incomplete at ${state.errors.length} path(s)`);
258
- if (incompleteRetries < 3) {
259
- incompleteRetries++;
260
- reconcileRequested = true;
261
- yield new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, (incompleteRetries - 1))));
274
+ else {
275
+ outcome = "complete";
276
+ scanHealthy = observedGapGeneration === gapGeneration;
277
+ if (ingestionDegraded && processor.progress.pendingFiles > 0) {
278
+ degradedRepairQueued = true;
279
+ }
280
+ reportHealthyIfSettled();
281
+ incompleteRetries = 0;
262
282
  }
263
283
  }
264
- else {
265
- scanHealthy = observedGapGeneration === gapGeneration;
266
- if (ingestionDegraded && processor.progress.pendingFiles > 0) {
267
- degradedRepairQueued = true;
268
- }
269
- reportHealthyIfSettled();
270
- incompleteRetries = 0;
284
+ finally {
285
+ recovery.finishScan(scan, closing ? "aborted" : outcome);
271
286
  }
287
+ if (retryDelayMs > 0)
288
+ yield new Promise((resolve) => setTimeout(resolve, retryDelayMs));
272
289
  } while (reconcileRequested && !closing);
273
290
  }))()
274
291
  .catch((err) => console.error(`[${wtag}] Reconciliation failed:`, err))
@@ -313,6 +330,24 @@ function startWatcher(opts) {
313
330
  yield updateSubscription();
314
331
  reconcile();
315
332
  return {
333
+ get health() {
334
+ const progress = processor.progress;
335
+ const state = recovery.snapshot();
336
+ return {
337
+ indexing: progress.processing || progress.pendingFiles > 0,
338
+ pendingFiles: progress.pendingFiles,
339
+ queue: progress.queue,
340
+ failedFiles: Math.max(terminalFailures.size, progress.failedFiles),
341
+ degraded: !scanHealthy || ingestionDegraded || terminalFailures.size > 0,
342
+ watcherMode: closing
343
+ ? undefined
344
+ : subscriptionFailed || gapScanTimer || state.reconciliationNeeded
345
+ ? "recovering"
346
+ : "native",
347
+ catchupRunning: reconciliation !== null,
348
+ watcherRecovery: state,
349
+ };
350
+ },
316
351
  get progress() {
317
352
  return processor.progress;
318
353
  },
@@ -322,6 +357,7 @@ function startWatcher(opts) {
322
357
  clearTimeout(gapScanTimer);
323
358
  yield subscriptionUpdate.catch(() => { });
324
359
  yield subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
360
+ recovery.detached();
325
361
  yield reconciliation;
326
362
  yield processor.close();
327
363
  }),
@@ -7,6 +7,7 @@
7
7
  // agent can decide to caveat its answer or retry once indexing settles.
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.formatWatchQueue = formatWatchQueue;
10
+ exports.formatWatcherRecovery = formatWatcherRecovery;
10
11
  exports.formatIndexStateFooter = formatIndexStateFooter;
11
12
  function formatWatchQueue(queue) {
12
13
  const age = queue.oldestLiveEditAgeMs === null
@@ -14,6 +15,13 @@ function formatWatchQueue(queue) {
14
15
  : `${Math.ceil(queue.oldestLiveEditAgeMs / 1000)}s`;
15
16
  return `live=${queue.live} catchup=${queue.catchup} cleanup=${queue.cleanup} active=${queue.activeFiles} oldestLiveEdit=${age}`;
16
17
  }
18
+ function formatWatcherRecovery(state) {
19
+ var _a, _b;
20
+ var _c, _d;
21
+ if (!state)
22
+ return null;
23
+ return `gaps=${state.gapCount} covered=${state.coveredGapCount} outstanding=${state.outstandingGapCount} terminalErrors=${state.terminalErrorCount} reconciliationNeeded=${state.reconciliationNeeded} scan=${(_c = (_a = state.lastScan) === null || _a === void 0 ? void 0 : _a.id) !== null && _c !== void 0 ? _c : "none"}/${(_d = (_b = state.lastScan) === null || _b === void 0 ? void 0 : _b.outcome) !== null && _d !== void 0 ? _d : "none"} exclusions=${state.exclusions ? `${state.exclusions.attached ? "attached" : "detached"}:${state.exclusions.literalCount}+${state.exclusions.filterCount}:${state.exclusions.fingerprint}` : "unknown"}`;
24
+ }
17
25
  /**
18
26
  * One-line footer describing an in-progress index, or null when there's
19
27
  * nothing to say (no state, or the index is settled). Suppressing the
@@ -65,10 +65,15 @@ function assertRecoveryAdmission(storeDir, budget) {
65
65
  const snapshot = budget.check();
66
66
  if ((snapshot === null || snapshot === void 0 ? void 0 : snapshot.platform) !== "darwin")
67
67
  throw new Error("Production recovery admission currently requires macOS host measurements");
68
+ // os.freemem() reports immediately free pages, not all memory macOS can
69
+ // reclaim. Requiring an idle 512MiB here refused cleanup on a healthy host
70
+ // as the file cache changed. Strict pressure/aggregate checks above and the
71
+ // shared 512MiB reservation below govern helper admission; free pages remain
72
+ // measured diagnostics, never a substitute for those checks.
68
73
  if (snapshot.physicalFreeMb === null ||
69
74
  !Number.isFinite(snapshot.physicalFreeMb) ||
70
- snapshot.physicalFreeMb < resource_budget_1.HELPER_RESOURCE_RESERVE_MB)
71
- throw new Error("Insufficient measured physical headroom for prune helper");
75
+ snapshot.physicalFreeMb < 0)
76
+ throw new Error("Measured physical free memory is unavailable");
72
77
  return snapshot;
73
78
  }
74
79
  function admitPrune(storeDir, budget = createRecoveryBudget()) {
@@ -109,11 +109,12 @@ function updateWatcherStatus(pid, status, lastReindex, lastError) {
109
109
  }
110
110
  }
111
111
  }
112
- function heartbeat(pid) {
112
+ function heartbeat(pid, indexState) {
113
113
  const db = getDb();
114
114
  for (const { key, value } of db.getRange()) {
115
115
  if (value && value.pid === pid) {
116
- db.put(String(key), Object.assign(Object.assign({}, value), { lastHeartbeat: Date.now() }));
116
+ const now = Date.now();
117
+ db.put(String(key), Object.assign(Object.assign(Object.assign({}, value), { lastHeartbeat: now }), (indexState ? { indexState, indexStateAt: now } : {})));
117
118
  }
118
119
  }
119
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.73",
3
+ "version": "0.26.75",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.73",
3
+ "version": "0.26.75",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",