@inerrata-corporation/errata 2.0.2-dev.685 → 2.0.2-dev.688

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/errata.mjs +137 -32
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -52221,23 +52221,38 @@ init_review2();
52221
52221
  import { closeSync, existsSync as existsSync12, fstatSync, openSync, readdirSync as readdirSync5, readSync, statSync as statSync3 } from "node:fs";
52222
52222
  import { basename as basename3, dirname as dirname8, join as join15 } from "node:path";
52223
52223
  import { homedir as homedir3 } from "node:os";
52224
- function readFrom(path2, fromByte) {
52224
+ function readFrom(path2, fromByte, maxBytes) {
52225
52225
  let fd;
52226
52226
  try {
52227
52227
  fd = openSync(path2, "r");
52228
52228
  } catch {
52229
- return { text: "", nextOffset: fromByte };
52229
+ return { text: "", nextOffset: fromByte, more: false };
52230
52230
  }
52231
52231
  try {
52232
52232
  const size = fstatSync(fd).size;
52233
52233
  const start2 = fromByte > 0 && fromByte <= size ? fromByte : 0;
52234
- const len = size - start2;
52235
- if (len <= 0) return { text: "", nextOffset: size };
52236
- const buf = Buffer.allocUnsafe(len);
52237
- readSync(fd, buf, 0, len, start2);
52238
- return { text: buf.toString("utf8"), nextOffset: size };
52234
+ const remaining = size - start2;
52235
+ if (remaining <= 0) return { text: "", nextOffset: size, more: false };
52236
+ let cap = maxBytes && maxBytes > 0 ? maxBytes : remaining;
52237
+ for (; ; ) {
52238
+ const len = Math.min(cap, remaining);
52239
+ const buf = Buffer.allocUnsafe(len);
52240
+ readSync(fd, buf, 0, len, start2);
52241
+ if (len >= remaining) {
52242
+ return { text: buf.toString("utf8"), nextOffset: size, more: false };
52243
+ }
52244
+ const nl = buf.lastIndexOf(10);
52245
+ if (nl >= 0) {
52246
+ return {
52247
+ text: buf.subarray(0, nl + 1).toString("utf8"),
52248
+ nextOffset: start2 + nl + 1,
52249
+ more: true
52250
+ };
52251
+ }
52252
+ cap *= 2;
52253
+ }
52239
52254
  } catch {
52240
- return { text: "", nextOffset: fromByte };
52255
+ return { text: "", nextOffset: fromByte, more: false };
52241
52256
  } finally {
52242
52257
  closeSync(fd);
52243
52258
  }
@@ -52333,9 +52348,9 @@ function isUserTurnBoundary(obj) {
52333
52348
  function readAssistantTurns(transcriptPath, includeThinking = true, maxBytes = 2e6) {
52334
52349
  return parseAssistantTurns(readTail(transcriptPath, maxBytes), includeThinking);
52335
52350
  }
52336
- function readAssistantTurnsFrom(transcriptPath, fromByte, includeThinking = true) {
52337
- const { text, nextOffset } = readFrom(transcriptPath, fromByte);
52338
- return { turns: parseAssistantTurns(text, includeThinking), nextOffset };
52351
+ function readAssistantTurnsFrom(transcriptPath, fromByte, includeThinking = true, maxBytes) {
52352
+ const { text, nextOffset, more } = readFrom(transcriptPath, fromByte, maxBytes);
52353
+ return { turns: parseAssistantTurns(text, includeThinking), nextOffset, more };
52339
52354
  }
52340
52355
  function parseAssistantTurns(raw2, includeThinking) {
52341
52356
  if (!raw2) return [];
@@ -53749,18 +53764,38 @@ function recordEpisode(store, workspaceId2, t, delta, causal) {
53749
53764
  return id;
53750
53765
  }
53751
53766
 
53767
+ // src/footprint.ts
53768
+ function numEnv(key, fallback) {
53769
+ const raw2 = process.env[key];
53770
+ if (raw2 == null || raw2.trim() === "") return fallback;
53771
+ const n = Number(raw2);
53772
+ return Number.isFinite(n) && n > 0 ? n : fallback;
53773
+ }
53774
+ var DAEMON_MAX_HEAP_MB = numEnv("ERRATA_MAX_HEAP_MB", 2048);
53775
+ var WORKER_MAX_HEAP_MB = numEnv("ERRATA_WORKER_MAX_HEAP_MB", 1024);
53776
+ var HARVEST_SLICE_BYTES = numEnv("ERRATA_HARVEST_SLICE_BYTES", 4e6);
53777
+ function workerResourceLimits() {
53778
+ return { maxOldGenerationSizeMb: WORKER_MAX_HEAP_MB };
53779
+ }
53780
+ function isWorkerOom(err2) {
53781
+ const e = err2;
53782
+ if (!e) return false;
53783
+ if (e.code === "ERR_WORKER_OUT_OF_MEMORY") return true;
53784
+ return /out of memory|ERR_WORKER_OUT_OF_MEMORY|heap limit/i.test(e.message ?? "");
53785
+ }
53786
+
53752
53787
  // src/episode-retention.ts
53753
53788
  var EPISODE_RETAIN_DAYS_DEFAULT = 90;
53754
53789
  var EPISODE_RETIRE_BATCH_DEFAULT = 300;
53755
53790
  var DAY_MS = 864e5;
53756
- function numEnv(key, fallback) {
53791
+ function numEnv2(key, fallback) {
53757
53792
  const raw2 = process.env[key];
53758
53793
  if (raw2 == null || raw2.trim() === "") return fallback;
53759
53794
  const n = Number(raw2);
53760
53795
  return Number.isFinite(n) && n > 0 ? n : fallback;
53761
53796
  }
53762
53797
  function retireEpisodeProvenance(store, now, opts = {}) {
53763
- const retainDays = opts.retainDays ?? numEnv("ERRATA_EPISODE_RETAIN_DAYS", EPISODE_RETAIN_DAYS_DEFAULT);
53798
+ const retainDays = opts.retainDays ?? numEnv2("ERRATA_EPISODE_RETAIN_DAYS", EPISODE_RETAIN_DAYS_DEFAULT);
53764
53799
  const batch = opts.batch ?? EPISODE_RETIRE_BATCH_DEFAULT;
53765
53800
  const cutoff = now - retainDays * DAY_MS;
53766
53801
  const due = store.findNodesByLabel("Episode").filter((ep) => {
@@ -53836,7 +53871,15 @@ var PassWorker = class {
53836
53871
  ensureWorker() {
53837
53872
  if (this.worker) return;
53838
53873
  const workerUrl = new URL("./pass-worker.mjs", import.meta.url);
53839
- const worker = new Worker(workerUrl, { workerData: this.init, execArgv: process.execArgv });
53874
+ const worker = new Worker(workerUrl, {
53875
+ workerData: this.init,
53876
+ execArgv: process.execArgv,
53877
+ // HZ-footprint-cap: hard heap ceiling per isolate. A pass that would
53878
+ // exceed it dies HERE, in the isolate built to be expendable — the
53879
+ // engine's fallback must then SKIP the pass (isWorkerOom), never re-run
53880
+ // it inline where the same allocation would take down the hook server.
53881
+ resourceLimits: workerResourceLimits()
53882
+ });
53840
53883
  this.worker = worker;
53841
53884
  this.ready = new Promise((resolve6, reject) => {
53842
53885
  const onReady = (m) => {
@@ -55412,7 +55455,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
55412
55455
  }
55413
55456
 
55414
55457
  // src/engine.ts
55415
- var DAEMON_VERSION = true ? "2.0.2-dev.685" : "2.0.0-alpha.0";
55458
+ var DAEMON_VERSION = true ? "2.0.2-dev.688" : "2.0.0-alpha.0";
55416
55459
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
55417
55460
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
55418
55461
  var GIT_OP_MUTE_MS = 4e3;
@@ -55958,6 +56001,36 @@ function createWorkspaceEngine(opts) {
55958
56001
  }
55959
56002
  return { report: r.report, localSkills: r.localSkills };
55960
56003
  } catch (err2) {
56004
+ if (isWorkerOom(err2)) {
56005
+ console.warn("[errata] nightly worker hit its heap ceiling \u2014 heavy pass skipped this fire");
56006
+ appendPassLedger(paths.configDir, "graph-rescore", 0, { oomSkipped: 1 });
56007
+ const sem = runSemanticMaintenance(store);
56008
+ return {
56009
+ report: {
56010
+ scoredNodes: 0,
56011
+ iterations: 0,
56012
+ converged: true,
56013
+ landmarks: 0,
56014
+ communities: 0,
56015
+ motifsPromoted: 0,
56016
+ techniques: 0,
56017
+ antipatterns: 0,
56018
+ skillsInduced: 0,
56019
+ skillsRefreshed: 0,
56020
+ pageRankWritten: 0,
56021
+ landmarkFlips: 0,
56022
+ mode: "incremental",
56023
+ regionSize: 0,
56024
+ designResolved: sem.designResolved,
56025
+ revisitsCleared: sem.revisitsCleared,
56026
+ fixCandidatesSettled: sem.fixCandidatesSettled,
56027
+ claimsEvaluated: sem.claimsEvaluated,
56028
+ claimsDerived: sem.claimsDerived,
56029
+ durationMs: sem.durationMs
56030
+ },
56031
+ localSkills: 0
56032
+ };
56033
+ }
55961
56034
  console.warn(
55962
56035
  "[errata] nightly worker unavailable, running inline:",
55963
56036
  err2 instanceof Error ? err2.message : err2
@@ -56717,20 +56790,38 @@ function createWorkspaceEngine(opts) {
56717
56790
  turns = readAssistantTurns(transcriptPath);
56718
56791
  nextOffset = transcriptSize(transcriptPath);
56719
56792
  } else {
56720
- ({ turns, nextOffset } = readAssistantTurnsFrom(transcriptPath, known));
56793
+ ({ turns, nextOffset } = readAssistantTurnsFrom(transcriptPath, known, true, HARVEST_SLICE_BYTES));
56721
56794
  }
56722
56795
  } catch {
56723
56796
  return;
56724
56797
  }
56725
- const fresh = turnsSince(turns, lastTurnUuid.get(sessionId));
56726
- turnOffset.set(sessionId, nextOffset);
56727
- if (fresh.length === 0) {
56798
+ for (let slice = 0; ; slice++) {
56799
+ const fresh = turnsSince(turns, lastTurnUuid.get(sessionId));
56800
+ turnOffset.set(sessionId, nextOffset);
56801
+ if (fresh.length > 0) {
56802
+ lastTurnUuid.set(sessionId, turns[turns.length - 1].uuid);
56803
+ await harvestTexts(sessionId, fresh);
56804
+ }
56728
56805
  saveTurnCursors(turnCursorPath, lastTurnUuid, turnOffset);
56729
- return;
56806
+ if (slice >= 256) {
56807
+ console.warn("[errata] harvest slice guard hit \u2014 backlog resumes next tick");
56808
+ break;
56809
+ }
56810
+ let next;
56811
+ try {
56812
+ next = readAssistantTurnsFrom(transcriptPath, nextOffset, true, HARVEST_SLICE_BYTES);
56813
+ } catch {
56814
+ break;
56815
+ }
56816
+ if (next.turns.length === 0 && !next.more) {
56817
+ turnOffset.set(sessionId, next.nextOffset);
56818
+ saveTurnCursors(turnCursorPath, lastTurnUuid, turnOffset);
56819
+ break;
56820
+ }
56821
+ turns = next.turns;
56822
+ nextOffset = next.nextOffset;
56823
+ await new Promise((resolve6) => setImmediate(resolve6));
56730
56824
  }
56731
- lastTurnUuid.set(sessionId, turns[turns.length - 1].uuid);
56732
- await harvestTexts(sessionId, fresh);
56733
- saveTurnCursors(turnCursorPath, lastTurnUuid, turnOffset);
56734
56825
  };
56735
56826
  const seenMessageIds = /* @__PURE__ */ new Set();
56736
56827
  const onMessage = (e) => {
@@ -58124,7 +58215,12 @@ var ConsolidateWorker = class {
58124
58215
  ensureWorker() {
58125
58216
  if (this.worker) return;
58126
58217
  const workerUrl = new URL("./consolidate-worker.mjs", import.meta.url);
58127
- const worker = new Worker2(workerUrl, { workerData: {}, execArgv: process.execArgv });
58218
+ const worker = new Worker2(workerUrl, {
58219
+ workerData: {},
58220
+ execArgv: process.execArgv,
58221
+ // HZ-footprint-cap — see pass-worker-client.ts for the contract.
58222
+ resourceLimits: workerResourceLimits()
58223
+ });
58128
58224
  this.worker = worker;
58129
58225
  this.ready = new Promise((resolve6, reject) => {
58130
58226
  const onReady = (m) => {
@@ -60253,7 +60349,7 @@ var DEFAULT_CONSOLIDATION_POLICY = {
60253
60349
  quiescenceMs: 6e4
60254
60350
  // sim preferred 60s over 90s (fresher, esp. heavy-code)
60255
60351
  };
60256
- function numEnv2(env2, key, fallback) {
60352
+ function numEnv3(env2, key, fallback) {
60257
60353
  const raw2 = env2[key];
60258
60354
  if (raw2 == null || raw2.trim() === "") return fallback;
60259
60355
  const n = Number(raw2);
@@ -60261,12 +60357,12 @@ function numEnv2(env2, key, fallback) {
60261
60357
  }
60262
60358
  function consolidationPolicyFromEnv(env2 = process.env) {
60263
60359
  return {
60264
- baseFloorMs: numEnv2(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
60265
- dutyFactor: numEnv2(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
60266
- momentumThreshold: numEnv2(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
60267
- momentumRatio: numEnv2(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
60268
- momentumRatioCap: numEnv2(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
60269
- quiescenceMs: numEnv2(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
60360
+ baseFloorMs: numEnv3(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
60361
+ dutyFactor: numEnv3(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
60362
+ momentumThreshold: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
60363
+ momentumRatio: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
60364
+ momentumRatioCap: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
60365
+ quiescenceMs: numEnv3(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
60270
60366
  };
60271
60367
  }
60272
60368
  function consolidationGapMs(lastPassMs, policy) {
@@ -62165,7 +62261,16 @@ function spawnDaemonDetached() {
62165
62261
  detached: true,
62166
62262
  stdio: ["ignore", out2, out2],
62167
62263
  windowsHide: true,
62168
- cwd: ROOT
62264
+ cwd: ROOT,
62265
+ // HZ-footprint-cap: hard V8 old-space ceiling on the daemon process. The
62266
+ // workers carry their own resourceLimits; this bounds the main isolate
62267
+ // (hook server + capture), whose bursts are slice-capped so the ceiling is
62268
+ // a backstop, not a working limit. NODE_OPTIONS so it applies however the
62269
+ // bundle re-executes.
62270
+ env: {
62271
+ ...process.env,
62272
+ NODE_OPTIONS: `${process.env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${DAEMON_MAX_HEAP_MB}`.trim()
62273
+ }
62169
62274
  }).unref();
62170
62275
  if (out2 !== "ignore") closeSync2(out2);
62171
62276
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.685",
3
+ "version": "2.0.2-dev.688",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {