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

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 +175 -46
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -32946,7 +32946,7 @@ async function Module2(moduleArg = {}) {
32946
32946
  }
32947
32947
  };
32948
32948
  if (flags.loadAsync) {
32949
- return metadata.neededDynlibs.reduce((chain, dynNeeded) => chain.then(() => loadDynamicLibrary(dynNeeded, flags, localScope)), Promise.resolve()).then(loadModule);
32949
+ return metadata.neededDynlibs.reduce((chain2, dynNeeded) => chain2.then(() => loadDynamicLibrary(dynNeeded, flags, localScope)), Promise.resolve()).then(loadModule);
32950
32950
  }
32951
32951
  metadata.neededDynlibs.forEach((needed) => loadDynamicLibrary(needed, flags, localScope));
32952
32952
  return loadModule();
@@ -49533,13 +49533,13 @@ function learnedCard(c, opts) {
49533
49533
  }
49534
49534
  if (c.chain.instanceOf)
49535
49535
  segs.push(`<span class="e">INSTANCE_OF</span> <span class="rc">${esc3(c.chain.instanceOf)}</span>`);
49536
- const chain = segs.length > 0 ? `\u2514\u2500 ${segs.join(" \u2500 ")}` : '\u2514\u2500 <span class="rc">(no causal chain yet)</span>';
49536
+ const chain2 = segs.length > 0 ? `\u2514\u2500 ${segs.join(" \u2500 ")}` : '\u2514\u2500 <span class="rc">(no causal chain yet)</span>';
49537
49537
  return `<div class="lcard">
49538
49538
  <div class="row1">
49539
49539
  <span class="prob">${esc3(c.problem)}</span>
49540
49540
  <span class="badge ${PROV_CLASS[c.provenance]}">${BADGE2[c.provenance]} ${PROV_LABEL[c.provenance]}</span>
49541
49541
  </div>
49542
- <div class="chain">${chain}</div>
49542
+ <div class="chain">${chain2}</div>
49543
49543
  <div class="lfoot">${trustHtml(c.trust, c.trustNote)}${nextHtml(c.next, opts)}</div>
49544
49544
  </div>`;
49545
49545
  }
@@ -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) => {
@@ -53794,6 +53829,27 @@ function retireEpisodeProvenance(store, now, opts = {}) {
53794
53829
 
53795
53830
  // src/pass-worker-client.ts
53796
53831
  import { Worker } from "node:worker_threads";
53832
+
53833
+ // src/pass-gate.ts
53834
+ var QUEUE_WARN_MS = 12e4;
53835
+ var chain = Promise.resolve();
53836
+ function withHeavyPassSlot(label, fn) {
53837
+ const queuedAt = Date.now();
53838
+ const run3 = chain.then(() => {
53839
+ const waited = Date.now() - queuedAt;
53840
+ if (waited > QUEUE_WARN_MS) {
53841
+ console.warn(`[errata] heavy-pass slot: ${label} waited ${Math.round(waited / 1e3)}s`);
53842
+ }
53843
+ return fn();
53844
+ });
53845
+ chain = run3.then(
53846
+ () => void 0,
53847
+ () => void 0
53848
+ );
53849
+ return run3;
53850
+ }
53851
+
53852
+ // src/pass-worker-client.ts
53797
53853
  var PassWorker = class {
53798
53854
  constructor(init2) {
53799
53855
  this.init = init2;
@@ -53824,19 +53880,30 @@ var PassWorker = class {
53824
53880
  }
53825
53881
  call(kind, payload) {
53826
53882
  if (this.stopped) return Promise.reject(new Error("pass worker stopped"));
53827
- this.ensureWorker();
53828
- const id = this.nextId++;
53829
- return this.ready.then(
53830
- () => new Promise((resolve6, reject) => {
53831
- this.pending.set(id, { resolve: resolve6, reject });
53832
- this.worker.postMessage({ id, kind, ...payload !== void 0 ? { payload } : {} });
53833
- })
53834
- );
53883
+ return withHeavyPassSlot(`${kind}:${this.init.workspaceId}`, () => {
53884
+ if (this.stopped) return Promise.reject(new Error("pass worker stopped"));
53885
+ this.ensureWorker();
53886
+ const id = this.nextId++;
53887
+ return this.ready.then(
53888
+ () => new Promise((resolve6, reject) => {
53889
+ this.pending.set(id, { resolve: resolve6, reject });
53890
+ this.worker.postMessage({ id, kind, ...payload !== void 0 ? { payload } : {} });
53891
+ })
53892
+ );
53893
+ });
53835
53894
  }
53836
53895
  ensureWorker() {
53837
53896
  if (this.worker) return;
53838
53897
  const workerUrl = new URL("./pass-worker.mjs", import.meta.url);
53839
- const worker = new Worker(workerUrl, { workerData: this.init, execArgv: process.execArgv });
53898
+ const worker = new Worker(workerUrl, {
53899
+ workerData: this.init,
53900
+ execArgv: process.execArgv,
53901
+ // HZ-footprint-cap: hard heap ceiling per isolate. A pass that would
53902
+ // exceed it dies HERE, in the isolate built to be expendable — the
53903
+ // engine's fallback must then SKIP the pass (isWorkerOom), never re-run
53904
+ // it inline where the same allocation would take down the hook server.
53905
+ resourceLimits: workerResourceLimits()
53906
+ });
53840
53907
  this.worker = worker;
53841
53908
  this.ready = new Promise((resolve6, reject) => {
53842
53909
  const onReady = (m) => {
@@ -55412,7 +55479,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
55412
55479
  }
55413
55480
 
55414
55481
  // src/engine.ts
55415
- var DAEMON_VERSION = true ? "2.0.2-dev.685" : "2.0.0-alpha.0";
55482
+ var DAEMON_VERSION = true ? "2.0.2-dev.690" : "2.0.0-alpha.0";
55416
55483
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
55417
55484
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
55418
55485
  var GIT_OP_MUTE_MS = 4e3;
@@ -55958,6 +56025,36 @@ function createWorkspaceEngine(opts) {
55958
56025
  }
55959
56026
  return { report: r.report, localSkills: r.localSkills };
55960
56027
  } catch (err2) {
56028
+ if (isWorkerOom(err2)) {
56029
+ console.warn("[errata] nightly worker hit its heap ceiling \u2014 heavy pass skipped this fire");
56030
+ appendPassLedger(paths.configDir, "graph-rescore", 0, { oomSkipped: 1 });
56031
+ const sem = runSemanticMaintenance(store);
56032
+ return {
56033
+ report: {
56034
+ scoredNodes: 0,
56035
+ iterations: 0,
56036
+ converged: true,
56037
+ landmarks: 0,
56038
+ communities: 0,
56039
+ motifsPromoted: 0,
56040
+ techniques: 0,
56041
+ antipatterns: 0,
56042
+ skillsInduced: 0,
56043
+ skillsRefreshed: 0,
56044
+ pageRankWritten: 0,
56045
+ landmarkFlips: 0,
56046
+ mode: "incremental",
56047
+ regionSize: 0,
56048
+ designResolved: sem.designResolved,
56049
+ revisitsCleared: sem.revisitsCleared,
56050
+ fixCandidatesSettled: sem.fixCandidatesSettled,
56051
+ claimsEvaluated: sem.claimsEvaluated,
56052
+ claimsDerived: sem.claimsDerived,
56053
+ durationMs: sem.durationMs
56054
+ },
56055
+ localSkills: 0
56056
+ };
56057
+ }
55961
56058
  console.warn(
55962
56059
  "[errata] nightly worker unavailable, running inline:",
55963
56060
  err2 instanceof Error ? err2.message : err2
@@ -56717,20 +56814,38 @@ function createWorkspaceEngine(opts) {
56717
56814
  turns = readAssistantTurns(transcriptPath);
56718
56815
  nextOffset = transcriptSize(transcriptPath);
56719
56816
  } else {
56720
- ({ turns, nextOffset } = readAssistantTurnsFrom(transcriptPath, known));
56817
+ ({ turns, nextOffset } = readAssistantTurnsFrom(transcriptPath, known, true, HARVEST_SLICE_BYTES));
56721
56818
  }
56722
56819
  } catch {
56723
56820
  return;
56724
56821
  }
56725
- const fresh = turnsSince(turns, lastTurnUuid.get(sessionId));
56726
- turnOffset.set(sessionId, nextOffset);
56727
- if (fresh.length === 0) {
56822
+ for (let slice = 0; ; slice++) {
56823
+ const fresh = turnsSince(turns, lastTurnUuid.get(sessionId));
56824
+ turnOffset.set(sessionId, nextOffset);
56825
+ if (fresh.length > 0) {
56826
+ lastTurnUuid.set(sessionId, turns[turns.length - 1].uuid);
56827
+ await harvestTexts(sessionId, fresh);
56828
+ }
56728
56829
  saveTurnCursors(turnCursorPath, lastTurnUuid, turnOffset);
56729
- return;
56830
+ if (slice >= 256) {
56831
+ console.warn("[errata] harvest slice guard hit \u2014 backlog resumes next tick");
56832
+ break;
56833
+ }
56834
+ let next;
56835
+ try {
56836
+ next = readAssistantTurnsFrom(transcriptPath, nextOffset, true, HARVEST_SLICE_BYTES);
56837
+ } catch {
56838
+ break;
56839
+ }
56840
+ if (next.turns.length === 0 && !next.more) {
56841
+ turnOffset.set(sessionId, next.nextOffset);
56842
+ saveTurnCursors(turnCursorPath, lastTurnUuid, turnOffset);
56843
+ break;
56844
+ }
56845
+ turns = next.turns;
56846
+ nextOffset = next.nextOffset;
56847
+ await new Promise((resolve6) => setImmediate(resolve6));
56730
56848
  }
56731
- lastTurnUuid.set(sessionId, turns[turns.length - 1].uuid);
56732
- await harvestTexts(sessionId, fresh);
56733
- saveTurnCursors(turnCursorPath, lastTurnUuid, turnOffset);
56734
56849
  };
56735
56850
  const seenMessageIds = /* @__PURE__ */ new Set();
56736
56851
  const onMessage = (e) => {
@@ -58124,7 +58239,12 @@ var ConsolidateWorker = class {
58124
58239
  ensureWorker() {
58125
58240
  if (this.worker) return;
58126
58241
  const workerUrl = new URL("./consolidate-worker.mjs", import.meta.url);
58127
- const worker = new Worker2(workerUrl, { workerData: {}, execArgv: process.execArgv });
58242
+ const worker = new Worker2(workerUrl, {
58243
+ workerData: {},
58244
+ execArgv: process.execArgv,
58245
+ // HZ-footprint-cap — see pass-worker-client.ts for the contract.
58246
+ resourceLimits: workerResourceLimits()
58247
+ });
58128
58248
  this.worker = worker;
58129
58249
  this.ready = new Promise((resolve6, reject) => {
58130
58250
  const onReady = (m) => {
@@ -58485,7 +58605,7 @@ function findGitRoot(absPath) {
58485
58605
  var RETRY_COOLDOWN_MS = 15 * 6e4;
58486
58606
  function createRootAdopter(deps) {
58487
58607
  const attempted = /* @__PURE__ */ new Map();
58488
- let chain = Promise.resolve();
58608
+ let chain2 = Promise.resolve();
58489
58609
  const tryAdopt = async (path2) => {
58490
58610
  if (!deps.linkingAllowed()) return;
58491
58611
  const root = findGitRoot(path2);
@@ -58505,9 +58625,9 @@ function createRootAdopter(deps) {
58505
58625
  return {
58506
58626
  noteMiss(path2) {
58507
58627
  if (!path2) return;
58508
- chain = chain.then(() => tryAdopt(path2)).catch(() => void 0);
58628
+ chain2 = chain2.then(() => tryAdopt(path2)).catch(() => void 0);
58509
58629
  },
58510
- idle: () => chain
58630
+ idle: () => chain2
58511
58631
  };
58512
58632
  }
58513
58633
 
@@ -60253,7 +60373,7 @@ var DEFAULT_CONSOLIDATION_POLICY = {
60253
60373
  quiescenceMs: 6e4
60254
60374
  // sim preferred 60s over 90s (fresher, esp. heavy-code)
60255
60375
  };
60256
- function numEnv2(env2, key, fallback) {
60376
+ function numEnv3(env2, key, fallback) {
60257
60377
  const raw2 = env2[key];
60258
60378
  if (raw2 == null || raw2.trim() === "") return fallback;
60259
60379
  const n = Number(raw2);
@@ -60261,12 +60381,12 @@ function numEnv2(env2, key, fallback) {
60261
60381
  }
60262
60382
  function consolidationPolicyFromEnv(env2 = process.env) {
60263
60383
  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)
60384
+ baseFloorMs: numEnv3(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
60385
+ dutyFactor: numEnv3(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
60386
+ momentumThreshold: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
60387
+ momentumRatio: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
60388
+ momentumRatioCap: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
60389
+ quiescenceMs: numEnv3(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
60270
60390
  };
60271
60391
  }
60272
60392
  function consolidationGapMs(lastPassMs, policy) {
@@ -62165,7 +62285,16 @@ function spawnDaemonDetached() {
62165
62285
  detached: true,
62166
62286
  stdio: ["ignore", out2, out2],
62167
62287
  windowsHide: true,
62168
- cwd: ROOT
62288
+ cwd: ROOT,
62289
+ // HZ-footprint-cap: hard V8 old-space ceiling on the daemon process. The
62290
+ // workers carry their own resourceLimits; this bounds the main isolate
62291
+ // (hook server + capture), whose bursts are slice-capped so the ceiling is
62292
+ // a backstop, not a working limit. NODE_OPTIONS so it applies however the
62293
+ // bundle re-executes.
62294
+ env: {
62295
+ ...process.env,
62296
+ NODE_OPTIONS: `${process.env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${DAEMON_MAX_HEAP_MB}`.trim()
62297
+ }
62169
62298
  }).unref();
62170
62299
  if (out2 !== "ignore") closeSync2(out2);
62171
62300
  }
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.690",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {