grepmax 0.26.14 → 0.26.16

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.14",
13
+ "version": "0.26.16",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
@@ -61,6 +61,7 @@ const store_lease_1 = require("../store/store-lease");
61
61
  const vector_db_1 = require("../store/vector-db");
62
62
  const daemon_client_1 = require("../utils/daemon-client");
63
63
  const daemon_launcher_1 = require("../utils/daemon-launcher");
64
+ const kernel_zone_1 = require("../utils/kernel-zone");
64
65
  const keyed_mutex_1 = require("../utils/keyed-mutex");
65
66
  const log_rotate_1 = require("../utils/log-rotate");
66
67
  const logger_1 = require("../utils/logger");
@@ -127,6 +128,8 @@ class Daemon {
127
128
  this.heartbeatInterval = null;
128
129
  this.idleInterval = null;
129
130
  this.heartbeatTick = 0;
131
+ /** Rate-limits the kernel-zone warn log — see checkKernelZonePressure. */
132
+ this.lastZoneWarnMs = 0;
130
133
  this.shuttingDown = false;
131
134
  this.recycling = false;
132
135
  // False until LanceDB + MetaCache are open. The socket starts listening early
@@ -406,6 +409,7 @@ class Daemon {
406
409
  void this.mlxServerManager.checkMlxHealth();
407
410
  this.processManager.sweepOrphanWorkers();
408
411
  this.maybeRecycle();
412
+ this.checkKernelZonePressure();
409
413
  }
410
414
  }, HEARTBEAT_INTERVAL_MS);
411
415
  // 10. Idle timeout (skip when disabled via env)
@@ -1698,6 +1702,50 @@ class Daemon {
1698
1702
  this.recycling = true;
1699
1703
  void this.shutdown({ relaunch: true }).finally(() => process.exit(0));
1700
1704
  }
1705
+ /**
1706
+ * Stand down before our own write volume exhausts a leaking kernel zone.
1707
+ *
1708
+ * On macOS 26.5.2 sustained filesystem writes leak `data.kalloc.1024`
1709
+ * irreversibly, and exhausting it panics the host with no warning of any kind —
1710
+ * the daemon does not get to observe the failure, it just dies with the machine.
1711
+ * This host lost three sessions that way before the compactor was identified as
1712
+ * the largest write source on it.
1713
+ *
1714
+ * So the daemon watches the zone it is pressurizing. A warn-level reading is
1715
+ * logged and left alone; a critical reading exits without relaunch, because
1716
+ * relaunching would resume the writes that caused it. Nothing here can reclaim
1717
+ * the leaked memory — only a reboot does that — so the only useful move is to
1718
+ * stop adding to it while the user still has a working machine.
1719
+ *
1720
+ * Deliberately does nothing when the sample is unavailable or unparseable
1721
+ * (non-macOS, no `zprint`, changed output format): an unknown reading must never
1722
+ * shut the daemon down.
1723
+ */
1724
+ checkKernelZonePressure() {
1725
+ if (this.shuttingDown || this.recycling)
1726
+ return;
1727
+ const usage = (0, kernel_zone_1.readKernelZoneUsage)();
1728
+ if (!usage || usage.pressure === "ok")
1729
+ return;
1730
+ if (usage.pressure === "warn") {
1731
+ // Rate-limited to once an hour: at the 5-minute heartbeat this would
1732
+ // otherwise print 12 identical lines an hour for as long as the condition
1733
+ // holds, which on a leaking host is until reboot.
1734
+ const now = Date.now();
1735
+ if (now - this.lastZoneWarnMs < 60 * 60 * 1000)
1736
+ return;
1737
+ this.lastZoneWarnMs = now;
1738
+ console.log(`[daemon] WARNING: ${(0, kernel_zone_1.formatZoneUsage)(usage)} — macOS kernel zone is leaking ` +
1739
+ `under write pressure. The daemon will stop itself at ` +
1740
+ `${(kernel_zone_1.ZONE_THRESHOLDS.criticalBytes / Math.pow(1024, 3)).toFixed(0)}GiB. ` +
1741
+ `Only a reboot reclaims this memory.`);
1742
+ return;
1743
+ }
1744
+ console.log(`[daemon] CRITICAL: ${(0, kernel_zone_1.formatZoneUsage)(usage)} — stopping to avoid a kernel panic. ` +
1745
+ `Reboot to reclaim the zone; the index will catch up on next start.`);
1746
+ this.recycling = true;
1747
+ void this.shutdown().finally(() => process.exit(0));
1748
+ }
1701
1749
  shutdown(opts = {}) {
1702
1750
  if (!this.shutdownPromise) {
1703
1751
  this.shutdownPromise = this.performShutdown(opts);
@@ -324,9 +324,13 @@ function initialSync(options) {
324
324
  yield currentFlush;
325
325
  (0, logger_1.debug)("index", `flush done: ${Date.now() - flushStart}ms`);
326
326
  flushCount++;
327
- // Periodically compact during sync to prevent fragment accumulation
327
+ // Periodically compact during sync to prevent fragment accumulation.
328
+ // Uses the shared threshold deliberately: a hardcoded 30 here meant
329
+ // the full-index path kept compacting at the old rate long after the
330
+ // threshold was raised to 400, re-triggering the whole-table rewrite
331
+ // that raising it exists to avoid.
328
332
  if (flushCount % 10 === 0) {
329
- yield vectorDb.compactIfNeeded(30);
333
+ yield vectorDb.compactIfNeeded(config_1.FRAGMENT_COMPACT_THRESHOLD);
330
334
  }
331
335
  }
332
336
  catch (err) {
@@ -157,6 +157,36 @@ function isMissingTableError(err) {
157
157
  const TABLE_NAME = "chunks";
158
158
  const MAINTENANCE_INTERVAL_MS = 5 * 60 * 1000;
159
159
  const CLEAN_MAINTENANCE_INTERVAL_MS = 60 * 60 * 1000;
160
+ /**
161
+ * Floor between opportunistic full-table compactions.
162
+ *
163
+ * A compaction rewrites the whole table, so its cost scales with store size, not
164
+ * with how much changed. Neither opportunistic caller was rate-limited: the
165
+ * 5-minute maintenance tick re-optimizes whenever the write epoch moved, and
166
+ * `compactIfNeeded` fires whenever small fragments cross a threshold. On a store
167
+ * under continuous reindex pressure both are true essentially always, so the
168
+ * table gets rewritten every few minutes forever.
169
+ *
170
+ * That is not hypothetical. On 2026-08-16..17 this store (16 GB) took 43 full
171
+ * compactions in two days — an accelerating 6 → 30 → 63 → 54 per day — which a
172
+ * microstackshot measured as 549.76 GB of file-backed writes in 12.3 hours. On
173
+ * macOS 26.5.2 that write volume exhausted the `data.kalloc.1024` kernel zone and
174
+ * panicked the host. See docs/2026-08-04-macos-kernel-zone-panic-incident.md.
175
+ *
176
+ * The floor caps compaction at twice an hour regardless of how much churn arrives.
177
+ * Fragments accumulating between passes cost read performance, which is recoverable;
178
+ * unbounded rewrite volume was not.
179
+ */
180
+ const COMPACTION_MIN_INTERVAL_MS = 30 * 60 * 1000;
181
+ /**
182
+ * Ceiling for the unproductive-compaction backoff.
183
+ *
184
+ * A compaction that removes no fragments and frees no bytes rewrote the table for
185
+ * nothing, and the next one a few minutes later will almost certainly do the same.
186
+ * The interval doubles on each such pass and resets the moment one does real work,
187
+ * so a store that cannot be improved stops paying for the attempt.
188
+ */
189
+ const COMPACTION_MAX_INTERVAL_MS = 6 * 60 * 60 * 1000;
160
190
  class VectorDB {
161
191
  constructor(lancedbDir, vectorDim, suppliedLease) {
162
192
  this.lancedbDir = lancedbDir;
@@ -174,6 +204,9 @@ class VectorDB {
174
204
  /** Only an index owner may create/rebuild shared indexes — see markIndexOwner. */
175
205
  this.indexOwner = false;
176
206
  this.lastOptimizeDidWork = false;
207
+ /** Compaction rate limiter — see COMPACTION_MIN_INTERVAL_MS. */
208
+ this.lastCompactionMs = 0;
209
+ this.compactionIntervalMs = COMPACTION_MIN_INTERVAL_MS;
177
210
  this.maintenanceRunner = (fn) => fn();
178
211
  /** Latched when a from-scratch FTS rebuild failed to stop an optimize
179
212
  * panic. Stops the (expensive) rebuild from re-running every maintenance
@@ -1152,13 +1185,30 @@ class VectorDB {
1152
1185
  }
1153
1186
  yield this.createFTSIndex();
1154
1187
  yield this.createVectorIndex();
1155
- if (pressure === "low") {
1188
+ // Index creation above is idempotent and self-guarding, so it stays on the
1189
+ // 5-minute cadence. Compaction is the expensive half — it rewrites the whole
1190
+ // table — and gets rate-limited. Without this the tick compacts every 5
1191
+ // minutes for as long as writes keep arriving, which is how this store
1192
+ // reached 43 full rewrites in two days and panicked the host.
1193
+ const throttleMs = options.force
1194
+ ? 0
1195
+ : this.compactionThrottleRemainingMs();
1196
+ if (throttleMs > 0) {
1197
+ (0, logger_1.debug)("vectordb", `Compaction throttled — ${Math.ceil(throttleMs / 60000)}min until next full pass ` +
1198
+ `(interval ${Math.round(this.compactionIntervalMs / 60000)}min)`);
1199
+ }
1200
+ else if (pressure === "low") {
1156
1201
  (0, logger_1.log)("vectordb", `Low disk — single-pass optimize (no bloat retry)`);
1157
1202
  yield this.optimize(1, 0, true);
1203
+ this.noteCompaction(this.lastOptimizeDidWork);
1158
1204
  }
1159
1205
  else {
1160
1206
  // Normal maintenance: full optimize + bloat check
1161
1207
  yield this.optimize(5, 0, true);
1208
+ // Track across both passes: `lastOptimizeDidWork` is reset per optimize()
1209
+ // call, so a productive first pass followed by a barren bloat retry would
1210
+ // otherwise read as unproductive and trigger a spurious backoff.
1211
+ let didWork = this.lastOptimizeDidWork;
1162
1212
  if (this.lastOptimizeDidWork) {
1163
1213
  const table = yield this.openExistingTableUnsafe();
1164
1214
  if (table) {
@@ -1180,9 +1230,11 @@ class VectorDB {
1180
1230
  (0, logger_1.log)("vectordb", `Bloat detected after optimize: ${(diskSize / 1024 / 1024).toFixed(0)}MB disk vs ${(logicalSize / 1024 / 1024).toFixed(0)}MB logical (${bloatRatio.toFixed(1)}x) — retrying`);
1181
1231
  yield new Promise((r) => setTimeout(r, 2000));
1182
1232
  yield this.optimize(5, 0, true);
1233
+ didWork = didWork || this.lastOptimizeDidWork;
1183
1234
  }
1184
1235
  }
1185
1236
  }
1237
+ this.noteCompaction(didWork);
1186
1238
  }
1187
1239
  const maintainedTable = yield this.openExistingTableUnsafe();
1188
1240
  this.maintainedTableVersion =
@@ -1195,6 +1247,47 @@ class VectorDB {
1195
1247
  }
1196
1248
  });
1197
1249
  }
1250
+ /**
1251
+ * Milliseconds until an opportunistic full-table compaction is allowed again,
1252
+ * or 0 if one may run now. Forced callers (`gmax index`, `doctor --fix`) are
1253
+ * expected to bypass this rather than consult it.
1254
+ */
1255
+ compactionThrottleRemainingMs(now = Date.now()) {
1256
+ if (this.lastCompactionMs === 0)
1257
+ return 0;
1258
+ const elapsed = now - this.lastCompactionMs;
1259
+ // A clock that jumped backwards would otherwise wedge compaction shut.
1260
+ if (elapsed < 0)
1261
+ return 0;
1262
+ return Math.max(0, this.compactionIntervalMs - elapsed);
1263
+ }
1264
+ /**
1265
+ * Record a completed compaction and adjust the interval. Productive passes reset
1266
+ * to the floor; unproductive ones double up to the ceiling so a store that cannot
1267
+ * be improved stops being rewritten every few minutes.
1268
+ */
1269
+ noteCompaction(didWork) {
1270
+ this.lastCompactionMs = Date.now();
1271
+ if (didWork) {
1272
+ if (this.compactionIntervalMs !== COMPACTION_MIN_INTERVAL_MS) {
1273
+ (0, logger_1.debug)("vectordb", "Productive compaction — interval reset to floor");
1274
+ }
1275
+ this.compactionIntervalMs = COMPACTION_MIN_INTERVAL_MS;
1276
+ return;
1277
+ }
1278
+ const next = Math.min(this.compactionIntervalMs * 2, COMPACTION_MAX_INTERVAL_MS);
1279
+ if (next !== this.compactionIntervalMs) {
1280
+ (0, logger_1.log)("vectordb", `Compaction reclaimed nothing — backing off to ${Math.round(next / 60000)}min`);
1281
+ }
1282
+ this.compactionIntervalMs = next;
1283
+ }
1284
+ /** Test/diagnostic view of the rate limiter. */
1285
+ getCompactionThrottleState() {
1286
+ return {
1287
+ intervalMs: this.compactionIntervalMs,
1288
+ remainingMs: this.compactionThrottleRemainingMs(),
1289
+ };
1290
+ }
1198
1291
  compactIfNeeded() {
1199
1292
  return __awaiter(this, arguments, void 0, function* (threshold = config_1.FRAGMENT_COMPACT_THRESHOLD) {
1200
1293
  if (this.maintenanceRunning)
@@ -1206,8 +1299,14 @@ class VectorDB {
1206
1299
  const table = yield this.ensureTable();
1207
1300
  const stats = yield table.stats();
1208
1301
  if (stats.fragmentStats.numSmallFragments > threshold) {
1302
+ const throttleMs = this.compactionThrottleRemainingMs();
1303
+ if (throttleMs > 0) {
1304
+ (0, logger_1.debug)("vectordb", `Fragment threshold exceeded (${stats.fragmentStats.numSmallFragments} > ${threshold}) — throttled, ${Math.ceil(throttleMs / 60000)}min remaining`);
1305
+ return false;
1306
+ }
1209
1307
  (0, logger_1.log)("vectordb", `Fragment threshold exceeded (${stats.fragmentStats.numSmallFragments} > ${threshold}) — compacting`);
1210
1308
  yield this.optimize(2, 0, true);
1309
+ this.noteCompaction(this.lastOptimizeDidWork);
1211
1310
  return true;
1212
1311
  }
1213
1312
  }
@@ -187,22 +187,115 @@ function isIndexableFile(filePath, size) {
187
187
  function isIndexablePath(filePath) {
188
188
  return isIndexableFile(filePath);
189
189
  }
190
- // Well-known machine-generated markers emitted at the top of codegen output.
191
- // Tools (protoc, graphql-codegen, Apollo, Relay, Go `stringer`, OpenAPI, etc.)
192
- // stamp one of these in a banner comment. Matched against only the first few KB
193
- // because the banner is always at the very top — this keeps a hand-written
194
- // "do not edit this section" note deep in a real file from being misclassified.
195
- const GENERATED_MARKER = /@generated\b|\bDO NOT EDIT\b|Code generated by |auto-?generated by |This (?:file|code) (?:is|was) (?:\w+[ -])?generated|machine generated|@autogenerated\b/i;
190
+ // Markers that only ever appear in a codegen banner. Any one of these, on its
191
+ // own, is enough. Tools (protoc, graphql-codegen, Apollo, Relay, Go `stringer`,
192
+ // OpenAPI) stamp one of these; doc generators fence their output with the
193
+ // `generated-body` / `AUTO-GENERATED-CONTENT` style directives.
194
+ const STRONG_GENERATED_MARKER = /@generated\b|@autogenerated\b|\bCode generated by |\bauto-?generated by |\bThis (?:file|code) (?:is|was) (?:\w+[ -])?generated|\bmachine[ -]generated\b|\bauto-?generated[-_ ]?content\b|\bgenerated[-_](?:body|content|section|block)\b|\bBEGIN GENERATED\b/i;
195
+ // "DO NOT EDIT" is not proof of codegen on its own — humans write it on prose
196
+ // they want left alone (archived vendor snapshots, verbatim captures). Real
197
+ // codegen banners pair it with a generation word, so require that pairing.
198
+ const DO_NOT_EDIT_MARKER = /\bDO NOT EDIT\b/i;
199
+ const GENERATION_CONTEXT = /generat|codegen|autogen/i;
196
200
  const GENERATED_SNIFF_BYTES = 2048;
201
+ const DOC_LIKE_EXTENSIONS = /\.(?:md|mdx|markdown|txt|rst|adoc|asciidoc)$/i;
202
+ /**
203
+ * Extract the leading banner: the run of shebang, frontmatter, blank, and
204
+ * comment lines at the very top, stopping at the first line of actual code.
205
+ *
206
+ * Scanning raw head bytes instead was the bug this replaces. Two ways it
207
+ * misfired, both silently erasing hand-written files from the index:
208
+ *
209
+ * - A *generator* was classified as its own output, because the banner it
210
+ * emits appears as a string literal in its body
211
+ * (`lines.push('# Auto-generated by ...')`).
212
+ * - A comment quoting another file's rules ("its README says do not edit")
213
+ * counted as a banner.
214
+ *
215
+ * A real codegen banner precedes all code, so anchoring here removes both
216
+ * without weakening detection.
217
+ *
218
+ * In doc-like files `#` starts a heading, not a comment, so only frontmatter
219
+ * and HTML comments count — otherwise the whole document body would be read as
220
+ * banner.
221
+ */
222
+ function leadingBanner(head, filePath) {
223
+ var _a, _b;
224
+ const docLike = filePath ? DOC_LIKE_EXTENSIONS.test(filePath) : false;
225
+ const lines = head.split("\n");
226
+ const banner = [];
227
+ let i = 0;
228
+ if (!docLike && ((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.startsWith("#!"))) {
229
+ banner.push(lines[0]);
230
+ i = 1;
231
+ }
232
+ // YAML frontmatter is metadata, not code — a generated-doc marker often sits
233
+ // directly after it.
234
+ if (((_b = lines[i]) === null || _b === void 0 ? void 0 : _b.trim()) === "---") {
235
+ banner.push(lines[i]);
236
+ i++;
237
+ while (i < lines.length && lines[i].trim() !== "---")
238
+ banner.push(lines[i++]);
239
+ if (i < lines.length)
240
+ banner.push(lines[i++]);
241
+ }
242
+ let blockEnd;
243
+ for (; i < lines.length; i++) {
244
+ const line = lines[i];
245
+ const trimmed = line.trim();
246
+ if (blockEnd) {
247
+ banner.push(line);
248
+ if (trimmed.includes(blockEnd))
249
+ blockEnd = undefined;
250
+ continue;
251
+ }
252
+ if (trimmed === "") {
253
+ banner.push(line);
254
+ continue;
255
+ }
256
+ if (trimmed.startsWith("<!--")) {
257
+ banner.push(line);
258
+ if (!trimmed.includes("-->"))
259
+ blockEnd = "-->";
260
+ continue;
261
+ }
262
+ if (!docLike) {
263
+ if (trimmed.startsWith("/*")) {
264
+ banner.push(line);
265
+ if (!trimmed.includes("*/"))
266
+ blockEnd = "*/";
267
+ continue;
268
+ }
269
+ if (trimmed.startsWith("//") ||
270
+ trimmed.startsWith("#") ||
271
+ trimmed.startsWith("--") ||
272
+ trimmed.startsWith(";") ||
273
+ trimmed.startsWith("*")) {
274
+ banner.push(line);
275
+ continue;
276
+ }
277
+ }
278
+ break; // first real line of content — the banner is over
279
+ }
280
+ return banner.join("\n");
281
+ }
197
282
  /**
198
283
  * Detect machine-generated source by its banner. Generated files flood both the
199
284
  * vector index and the symbol graph (codegen types rank as god nodes), so they
200
285
  * are skipped at index time. Path globs in `ignore-patterns.ts` catch the
201
286
  * obvious filenames; this catches the rest by content.
287
+ *
288
+ * `filePath` is optional but worth passing: without it, doc-like files are
289
+ * scanned with code comment rules, so a markdown `# Heading` reads as a comment.
202
290
  */
203
- function isGeneratedContent(buffer) {
291
+ function isGeneratedContent(buffer, filePath) {
204
292
  const head = buffer.subarray(0, GENERATED_SNIFF_BYTES).toString("utf-8");
205
- return GENERATED_MARKER.test(head);
293
+ const banner = leadingBanner(head, filePath);
294
+ if (!banner.trim())
295
+ return false;
296
+ if (STRONG_GENERATED_MARKER.test(banner))
297
+ return true;
298
+ return DO_NOT_EDIT_MARKER.test(banner) && GENERATION_CONTEXT.test(banner);
206
299
  }
207
300
  function formatDenseSnippet(text, maxLength = 1500) {
208
301
  const clean = text !== null && text !== void 0 ? text : "";
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZONE_THRESHOLDS = void 0;
4
+ exports.classifyZonePressure = classifyZonePressure;
5
+ exports.parseZprintOutput = parseZprintOutput;
6
+ exports.readKernelZoneUsage = readKernelZoneUsage;
7
+ exports.formatZoneUsage = formatZoneUsage;
8
+ const node_child_process_1 = require("node:child_process");
9
+ /**
10
+ * macOS kernel-zone pressure guard.
11
+ *
12
+ * On macOS 26.5.2 (build 25F84) the `data.kalloc.1024` kernel zone leaks under
13
+ * sustained filesystem write pressure and is never reclaimed. Exhausting it panics
14
+ * the host outright — no jetsam, no swap warning, no chance to intervene, because
15
+ * the failing resource is wired kernel memory rather than anything the VM system
16
+ * manages.
17
+ *
18
+ * This host panicked three times that way. The third time the zone sat near 1.4 GB
19
+ * for twelve days, then climbed to 17.9 GB in eight hours while gmax's compactor
20
+ * dirtied 549.76 GB of file-backed memory. See
21
+ * docs/2026-08-04-macos-kernel-zone-panic-incident.md.
22
+ *
23
+ * gmax cannot fix the kernel defect, but it can decline to be the thing that
24
+ * detonates it. Sampling the zone is cheap (~0.2 s), so the daemon watches its own
25
+ * blast radius and stands down while there are still hours of headroom.
26
+ */
27
+ /** The zone that leaks. Named explicitly so `zprint` returns a single line. */
28
+ const ZONE_NAME = "data.kalloc.1024";
29
+ /**
30
+ * Warn threshold.
31
+ *
32
+ * Twelve days of ordinary use held this zone between 0.67 and 1.37 GiB, so 4 GiB is
33
+ * roughly 3x anything a healthy host has been observed to reach. During the
34
+ * August 17 burst the zone crossed this about 90 minutes in — early enough that
35
+ * stopping then would have avoided the panic with hours to spare.
36
+ */
37
+ const WARN_BYTES = 4 * Math.pow(1024, 3);
38
+ /**
39
+ * Critical threshold — the daemon stands down here.
40
+ *
41
+ * The observed zone map cap on this hardware is ~17.6 GiB. Eight GiB is under half
42
+ * of it, and at the burst rate actually measured (~2.0 GiB/hour) it still leaves
43
+ * roughly five hours before exhaustion. The margin is deliberately generous: the
44
+ * cost of standing down early is a stale index, and the cost of standing down late
45
+ * is an unplanned reboot.
46
+ */
47
+ const CRITICAL_BYTES = 8 * Math.pow(1024, 3);
48
+ function classifyZonePressure(bytes) {
49
+ if (bytes >= CRITICAL_BYTES)
50
+ return "critical";
51
+ if (bytes >= WARN_BYTES)
52
+ return "warn";
53
+ return "ok";
54
+ }
55
+ /**
56
+ * Parse one `zprint <zone>` report.
57
+ *
58
+ * Exported for tests; the column layout is a stable but undocumented text format,
59
+ * so this validates rather than trusting it:
60
+ *
61
+ * ```
62
+ * elem cur max cur max cur alloc alloc
63
+ * zone name size size size #elts #elts inuse size count
64
+ * data.kalloc.1024 1024 0K 0K 0 0 3416 0K 0
65
+ * ```
66
+ *
67
+ * The `cur size` column reads `0K` on this build, which is why the byte figure is
68
+ * derived from `cur inuse` x `elem size` rather than read directly.
69
+ */
70
+ function parseZprintOutput(output, zoneName = ZONE_NAME) {
71
+ for (const line of output.split("\n")) {
72
+ const fields = line.trim().split(/\s+/);
73
+ if (fields[0] !== zoneName)
74
+ continue;
75
+ const elementSize = Number(fields[1]);
76
+ const elements = Number(fields[6]);
77
+ if (!Number.isInteger(elementSize) || elementSize <= 0)
78
+ return null;
79
+ if (!Number.isInteger(elements) || elements < 0)
80
+ return null;
81
+ return { elements, elementSize };
82
+ }
83
+ return null;
84
+ }
85
+ /**
86
+ * Sample the zone. Returns null off macOS, or whenever the sample cannot be
87
+ * trusted — an unparseable report must read as "unknown", never as "healthy" and
88
+ * never as a reason to stop working.
89
+ */
90
+ function readKernelZoneUsage(zoneName = ZONE_NAME) {
91
+ if (process.platform !== "darwin")
92
+ return null;
93
+ try {
94
+ const output = (0, node_child_process_1.execFileSync)("zprint", [zoneName], {
95
+ encoding: "utf-8",
96
+ timeout: 5000,
97
+ stdio: ["ignore", "pipe", "ignore"],
98
+ });
99
+ const parsed = parseZprintOutput(output, zoneName);
100
+ if (!parsed)
101
+ return null;
102
+ const bytes = parsed.elements * parsed.elementSize;
103
+ return Object.assign(Object.assign({}, parsed), { bytes, pressure: classifyZonePressure(bytes) });
104
+ }
105
+ catch (_a) {
106
+ return null;
107
+ }
108
+ }
109
+ function formatZoneUsage(usage) {
110
+ const gib = (usage.bytes / Math.pow(1024, 3)).toFixed(2);
111
+ return `${ZONE_NAME} at ${gib}GiB (${usage.elements.toLocaleString()} elements)`;
112
+ }
113
+ exports.ZONE_THRESHOLDS = {
114
+ warnBytes: WARN_BYTES,
115
+ criticalBytes: CRITICAL_BYTES,
116
+ zoneName: ZONE_NAME,
117
+ };
@@ -256,7 +256,7 @@ class WorkerOrchestrator {
256
256
  (0, logger_1.debug)("orch", `skip ${input.path} (empty or binary)`);
257
257
  return { vectors: [], hash, mtimeMs, size, shouldDelete: true };
258
258
  }
259
- if ((0, file_utils_1.isGeneratedContent)(buffer)) {
259
+ if ((0, file_utils_1.isGeneratedContent)(buffer, absolutePath)) {
260
260
  (0, logger_1.debug)("orch", `skip ${input.path} (machine-generated header)`);
261
261
  return { vectors: [], hash, mtimeMs, size, shouldDelete: true };
262
262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.14",
3
+ "version": "0.26.16",
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.14",
3
+ "version": "0.26.16",
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",
@@ -78,7 +78,23 @@ function isProjectRegistered() {
78
78
  }
79
79
  }
80
80
 
81
+ // A kill switch for the auto-start. Create ~/.gmax/autostart-disabled (or export
82
+ // GMAX_NO_AUTOSTART=1) to keep sessions from reviving the daemon — needed when the
83
+ // host is quarantined after a kernel-zone incident and the daemon's write volume is
84
+ // the thing under investigation. Delete the file to restore normal behavior.
85
+ function isAutostartDisabled() {
86
+ if (process.env.GMAX_NO_AUTOSTART === "1") return true;
87
+ try {
88
+ return require("node:fs").existsSync(
89
+ _path.join(require("node:os").homedir(), ".gmax", "autostart-disabled"),
90
+ );
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+
81
96
  function startWatcher() {
97
+ if (isAutostartDisabled()) return;
82
98
  if (!isProjectRegistered()) return;
83
99
  try {
84
100
  execFileSync("gmax", ["watch", "--daemon", "-b"], {