@inerrata-corporation/errata 2.0.2-dev.831 → 2.0.2-dev.835

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 +116 -33
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -22659,7 +22659,7 @@ function buildSnapshot(opts) {
22659
22659
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
22660
22660
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
22661
22661
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
22662
- const recent = allProblems.slice(0, 8).map((p) => ({
22662
+ const recent2 = allProblems.slice(0, 8).map((p) => ({
22663
22663
  node: p,
22664
22664
  anchors: opts.store.outEdges(p.id, ["MANIFESTED_IN", "EVIDENCED_BY"])
22665
22665
  }));
@@ -22719,10 +22719,10 @@ function buildSnapshot(opts) {
22719
22719
  packages: opts.profile.stack.map((name2) => ({ name: name2, node: matchNode(pkgNodes, pkgBase(name2)) })),
22720
22720
  tools: toolNodes.map((node2) => ({ name: String(node2.attrs["name"] ?? node2.description), node: node2 }))
22721
22721
  };
22722
- const causalNudge = recent.length >= 2 && recent.every(
22722
+ const causalNudge = recent2.length >= 2 && recent2.every(
22723
22723
  (r) => opts.store.outEdges(r.node.id, ["CAUSED_BY"]).length === 0 && opts.store.inEdges(r.node.id, ["CAUSED_BY"]).length === 0
22724
22724
  );
22725
- const abstractUndomained = recent.filter((r) => {
22725
+ const abstractUndomained = recent2.filter((r) => {
22726
22726
  const grounded = opts.store.outEdges(r.node.id, ["ANCHORED_AT", "OCCURS_IN", "DEPENDS_ON"]).length > 0;
22727
22727
  const hasDomain = opts.store.outEdges(r.node.id, ["PERTAIN_TO"]).length > 0;
22728
22728
  return !grounded && !hasDomain;
@@ -22731,7 +22731,7 @@ function buildSnapshot(opts) {
22731
22731
  return {
22732
22732
  profile: opts.profile,
22733
22733
  profileContext,
22734
- recentProblems: recent,
22734
+ recentProblems: recent2,
22735
22735
  recentResolved,
22736
22736
  recentConstraints,
22737
22737
  ...causalNudge ? { causalNudge } : {},
@@ -54145,7 +54145,7 @@ function appendPassLedger(configDir, kind, durationMs, counts) {
54145
54145
  const path2 = passLedgerPath(configDir);
54146
54146
  try {
54147
54147
  const mem = process.memoryUsage();
54148
- const census = isolateCensus();
54148
+ const census2 = isolateCensus();
54149
54149
  const entry = {
54150
54150
  ts: Date.now(),
54151
54151
  kind,
@@ -54156,9 +54156,9 @@ function appendPassLedger(configDir, kind, durationMs, counts) {
54156
54156
  heapUsedMB: Math.round(mem.heapUsed / 1048576),
54157
54157
  externalMB: Math.round(mem.external / 1048576),
54158
54158
  arrayBuffersMB: Math.round(mem.arrayBuffers / 1048576),
54159
- isolatesLive: census.live,
54160
- isolatesSpawned: census.spawnedTotal,
54161
- isolatesPeak: census.peak
54159
+ isolatesLive: census2.live,
54160
+ isolatesSpawned: census2.spawnedTotal,
54161
+ isolatesPeak: census2.peak
54162
54162
  }
54163
54163
  };
54164
54164
  appendFileSync2(path2, JSON.stringify(entry) + "\n");
@@ -54357,6 +54357,7 @@ var DAEMON_MAX_HEAP_MB = numEnv("ERRATA_MAX_HEAP_MB", 2048);
54357
54357
  var WORKER_MAX_HEAP_MB = numEnv("ERRATA_WORKER_MAX_HEAP_MB", 1024);
54358
54358
  var WORKSPACE_IDLE_TTL_MS = numEnv("ERRATA_WORKSPACE_IDLE_TTL_MS", 15 * 6e4);
54359
54359
  var WORKSPACE_SWEEP_INTERVAL_MS = numEnv("ERRATA_WORKSPACE_SWEEP_MS", 6e4);
54360
+ var EAGER_WORKSPACES_AT_BOOT = process.env["ERRATA_EAGER_WORKSPACES"] === "1";
54360
54361
  var HARVEST_SLICE_BYTES = numEnv("ERRATA_HARVEST_SLICE_BYTES", 4e6);
54361
54362
  function workerResourceLimits() {
54362
54363
  return { maxOldGenerationSizeMb: WORKER_MAX_HEAP_MB };
@@ -55486,7 +55487,7 @@ ${GITIGNORE_LINES.join("\n")}
55486
55487
  var INTENT_TTL_MS = 15 * 60 * 1e3;
55487
55488
  function createIntentState(ttlMs = INTENT_TTL_MS) {
55488
55489
  const bySession = /* @__PURE__ */ new Map();
55489
- let recent = null;
55490
+ let recent2 = null;
55490
55491
  const live2 = (s, nowMs) => s !== void 0 && nowMs - s.atMs < ttlMs;
55491
55492
  return {
55492
55493
  set(sessionId, text, nowMs) {
@@ -55494,8 +55495,8 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
55494
55495
  if (!trimmed) return false;
55495
55496
  const prev = bySession.get(sessionId);
55496
55497
  bySession.set(sessionId, { text: trimmed, atMs: nowMs });
55497
- const changed = !(recent && recent.text === trimmed);
55498
- recent = { sessionId, text: trimmed, atMs: nowMs };
55498
+ const changed = !(recent2 && recent2.text === trimmed);
55499
+ recent2 = { sessionId, text: trimmed, atMs: nowMs };
55499
55500
  if (!changed && prev && prev.text === trimmed) return false;
55500
55501
  return changed;
55501
55502
  },
@@ -55504,12 +55505,12 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
55504
55505
  return live2(s, nowMs) ? s.text : void 0;
55505
55506
  },
55506
55507
  mostRecent(nowMs) {
55507
- if (!recent) return void 0;
55508
- return nowMs - recent.atMs < ttlMs ? recent.text : void 0;
55508
+ if (!recent2) return void 0;
55509
+ return nowMs - recent2.atMs < ttlMs ? recent2.text : void 0;
55509
55510
  },
55510
55511
  clear(sessionId) {
55511
55512
  bySession.delete(sessionId);
55512
- if (recent?.sessionId === sessionId) recent = null;
55513
+ if (recent2?.sessionId === sessionId) recent2 = null;
55513
55514
  }
55514
55515
  };
55515
55516
  }
@@ -55517,19 +55518,19 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
55517
55518
  // src/working-file-state.ts
55518
55519
  function createWorkingFileState() {
55519
55520
  const bySession = /* @__PURE__ */ new Map();
55520
- let recent = null;
55521
+ let recent2 = null;
55521
55522
  return {
55522
55523
  set(sessionId, rel) {
55523
55524
  bySession.set(sessionId, rel);
55524
- if (rel === recent) return false;
55525
- recent = rel;
55525
+ if (rel === recent2) return false;
55526
+ recent2 = rel;
55526
55527
  return true;
55527
55528
  },
55528
55529
  get(sessionId) {
55529
55530
  return bySession.get(sessionId);
55530
55531
  },
55531
55532
  mostRecent() {
55532
- return recent;
55533
+ return recent2;
55533
55534
  }
55534
55535
  };
55535
55536
  }
@@ -56072,13 +56073,32 @@ function createLivenessWatch(deps) {
56072
56073
 
56073
56074
  // src/loop-lag.ts
56074
56075
  var HEARTBEAT_MS = 500;
56076
+ var RECENT_PASS_LIMIT = 12;
56075
56077
  var currentPass = "idle";
56078
+ var currentPassStartedAt = null;
56076
56079
  var started = false;
56080
+ var recent = [];
56077
56081
  function markPass(name2) {
56078
56082
  const prev = currentPass;
56083
+ const prevStart = currentPassStartedAt;
56084
+ const startedAt = Date.now();
56079
56085
  currentPass = name2;
56086
+ currentPassStartedAt = startedAt;
56087
+ let restored = false;
56080
56088
  return () => {
56089
+ if (restored) return;
56090
+ restored = true;
56091
+ recent.unshift({ name: name2, ms: Date.now() - startedAt, endedAt: Date.now() });
56092
+ if (recent.length > RECENT_PASS_LIMIT) recent.pop();
56081
56093
  currentPass = prev;
56094
+ currentPassStartedAt = prevStart;
56095
+ };
56096
+ }
56097
+ function passState() {
56098
+ return {
56099
+ current: currentPass,
56100
+ runningMs: currentPassStartedAt === null ? null : Date.now() - currentPassStartedAt,
56101
+ recent: [...recent]
56082
56102
  };
56083
56103
  }
56084
56104
  function startLoopLagMonitor(thresholdMs = 1e3) {
@@ -56098,8 +56118,28 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
56098
56118
  timer.unref();
56099
56119
  }
56100
56120
 
56121
+ // src/watch-census.ts
56122
+ var census = { dispatched: 0, emitted: 0, logged: 0, rejected: 0, since: Date.now() };
56123
+ var noteWatchDispatched = () => void census.dispatched++;
56124
+ var noteWatchEmitted = () => void census.emitted++;
56125
+ var noteWatchLogged = () => void census.logged++;
56126
+ var noteWatchRejected = () => void census.rejected++;
56127
+ function watchCensus() {
56128
+ return { ...census };
56129
+ }
56130
+ function watchCensusLine(c = watchCensus()) {
56131
+ if (c.dispatched === 0 && c.emitted === 0) return null;
56132
+ const mins = Math.max(1, Math.round((Date.now() - c.since) / 6e4));
56133
+ const perMin = Math.round(c.dispatched / mins);
56134
+ const base = `${c.dispatched} OS event(s) \u2192 ${c.logged} logged (${perMin}/min, ${c.rejected} rejected here)`;
56135
+ if (c.logged === 0 && c.dispatched > 500) {
56136
+ return `${base} \u26A0 watcher is doing work with NO output \u2014 a watch target likely contains an ignored tree`;
56137
+ }
56138
+ return base;
56139
+ }
56140
+
56101
56141
  // src/engine.ts
56102
- var DAEMON_VERSION = true ? "2.0.2-dev.831" : "2.0.0-alpha.0";
56142
+ var DAEMON_VERSION = true ? "2.0.2-dev.835" : "2.0.0-alpha.0";
56103
56143
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
56104
56144
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
56105
56145
  var GIT_OP_MUTE_MS = 4e3;
@@ -56242,8 +56282,13 @@ function createWorkspaceEngine(opts) {
56242
56282
  ignoreInitial: true,
56243
56283
  persistent: true
56244
56284
  });
56285
+ watcher.on("raw", noteWatchDispatched);
56245
56286
  const onFs = (kind) => (path2) => {
56246
- if (IGNORED_PATH.test(path2) || IGNORED_NOISE.test(path2)) return;
56287
+ noteWatchEmitted();
56288
+ if (IGNORED_PATH.test(path2) || IGNORED_NOISE.test(path2)) {
56289
+ noteWatchRejected();
56290
+ return;
56291
+ }
56247
56292
  let size = 0;
56248
56293
  try {
56249
56294
  size = statSync5(path2).size;
@@ -56253,6 +56298,7 @@ function createWorkspaceEngine(opts) {
56253
56298
  const base = { ts: Date.now(), kind, workspaceId: profile.id, path: path2, size };
56254
56299
  const ev = { ...base, digest: digest(base) };
56255
56300
  log.append(ev);
56301
+ noteWatchLogged();
56256
56302
  };
56257
56303
  watcher.on("add", onFs("fs.add"));
56258
56304
  watcher.on("change", onFs("fs.change"));
@@ -59608,17 +59654,23 @@ async function startMultiDaemon(opts = {}) {
59608
59654
  }
59609
59655
  }
59610
59656
  const entries = listWorkspaceEntries();
59611
- for (const entry of entries) {
59612
- try {
59613
- records.push(buildRecord(entry));
59614
- } catch (err2) {
59615
- console.warn(`[errata] skipping ${entry.name} (${entry.path}): ${err2 instanceof Error ? err2.message : err2}`);
59657
+ if (opts.eagerWorkspaces ?? EAGER_WORKSPACES_AT_BOOT) {
59658
+ for (const entry of entries) {
59659
+ try {
59660
+ records.push(buildRecord(entry));
59661
+ } catch (err2) {
59662
+ console.warn(`[errata] skipping ${entry.name} (${entry.path}): ${err2 instanceof Error ? err2.message : err2}`);
59663
+ }
59616
59664
  }
59617
- }
59618
- if (entries.length > 0 && records.length < entries.length) {
59619
- const failed = entries.length - records.length;
59620
- console.warn(
59621
- `[errata] \u26A0 ${failed} of ${entries.length} project(s) failed to start \u2014 likely a stale daemon holding DB locks. Run \`errata stop\` (or kill the old errata process) and retry.`
59665
+ if (entries.length > 0 && records.length < entries.length) {
59666
+ const failed = entries.length - records.length;
59667
+ console.warn(
59668
+ `[errata] \u26A0 ${failed} of ${entries.length} project(s) failed to start \u2014 likely a stale daemon holding DB locks. Run \`errata stop\` (or kill the old errata process) and retry.`
59669
+ );
59670
+ }
59671
+ } else if (entries.length > 0) {
59672
+ console.log(
59673
+ `[errata] ${entries.length} workspace(s) registered \xB7 cold \u2014 an engine spawns on the first hook from each`
59622
59674
  );
59623
59675
  }
59624
59676
  const app = new Hono2();
@@ -59733,6 +59785,16 @@ async function startMultiDaemon(opts = {}) {
59733
59785
  (c) => c.json({
59734
59786
  ok: true,
59735
59787
  daemonVersion: DAEMON_VERSION,
59788
+ // What the daemon is DOING, not just what it holds. Without these a
59789
+ // pegged daemon is only diagnosable by attaching a CPU profiler to the
59790
+ // live process — see watch-census.ts for the incident that proved it.
59791
+ pass: passState(),
59792
+ watch: watchCensus(),
59793
+ // Registered but with no engine running (WS-cold-start). Listed so a cold
59794
+ // daemon is legible as "nothing has needed waking yet" rather than as
59795
+ // "the registry lost my projects" — an invisible cold workspace is
59796
+ // indistinguishable from a missing one, and only one of those is fine.
59797
+ cold: listWorkspaceEntries().filter((e) => !records.some((r) => normPath(r.root) === normPath(e.path))).map((e) => ({ name: e.name, path: e.path })),
59736
59798
  projects: records.map((r) => ({
59737
59799
  id: r.id,
59738
59800
  name: r.entry.name,
@@ -59756,10 +59818,11 @@ async function startMultiDaemon(opts = {}) {
59756
59818
  let owner = ownerOf(records, pathInBody(body2));
59757
59819
  if (!owner) {
59758
59820
  const missPath = pathInBody(body2);
59759
- const gitRoot = missPath ? findGitRoot(missPath) : null;
59760
- if (gitRoot && attachWorkspace(gitRoot).attached) {
59821
+ const registeredRoot = missPath ? pickOwnerRoot(listWorkspaceEntries().map((e) => e.path), missPath) : null;
59822
+ const wakeRoot = registeredRoot ?? (missPath ? findGitRoot(missPath) : null);
59823
+ if (wakeRoot && attachWorkspace(wakeRoot).attached) {
59761
59824
  owner = ownerOf(records, missPath) ?? null;
59762
- if (owner) console.log(`[errata] woke idle workspace ${owner.entry.name}`);
59825
+ if (owner) console.log(`[errata] woke workspace ${owner.entry.name}`);
59763
59826
  }
59764
59827
  }
59765
59828
  if (!owner) {
@@ -61784,6 +61847,26 @@ async function cmdStatus() {
61784
61847
  console.log(
61785
61848
  ` daemon: ${running ? `running \xB7 pid ${running.pid} \xB7 ${running.webUiUrl ?? "(running)"}` : "not running (run: errata start)"}`
61786
61849
  );
61850
+ if (running?.webUiUrl) {
61851
+ try {
61852
+ const res = await fetch(`${running.webUiUrl}/health`, {
61853
+ signal: AbortSignal.timeout(3e3)
61854
+ });
61855
+ if (res.ok) {
61856
+ const h = await res.json();
61857
+ if (h.pass) {
61858
+ const held = h.pass.runningMs !== null && h.pass.current !== "idle" ? ` (${(h.pass.runningMs / 1e3).toFixed(1)}s)` : "";
61859
+ const last = (h.pass.recent ?? []).slice(0, 3).map((p) => `${p.name} ${(p.ms / 1e3).toFixed(1)}s`).join(", ");
61860
+ console.log(` pass: ${h.pass.current}${held}${last ? ` \xB7 recent: ${last}` : ""}`);
61861
+ }
61862
+ if (h.watch) {
61863
+ const line = watchCensusLine(h.watch);
61864
+ if (line) console.log(` watcher: ${line}`);
61865
+ }
61866
+ }
61867
+ } catch {
61868
+ }
61869
+ }
61787
61870
  const all = listWorkspaces();
61788
61871
  const missing = all.filter((w) => w.missing).length;
61789
61872
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.831",
3
+ "version": "2.0.2-dev.835",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {