@inerrata-corporation/errata 2.0.18-dev.3597 → 2.0.18-dev.3612

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 +154 -54
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -33461,6 +33461,13 @@ function foreignDaemonNote(lock, selfEntry) {
33461
33461
  ];
33462
33462
  return runningInstalled ? [...head2, "this CLI is a different bundle from the daemon it is talking to \u2014 its view may not match what the daemon runs."] : [...head2, "`errata update` does not replace it. To move onto the installed package: errata stop, then errata dash."];
33463
33463
  }
33464
+ function daemonVersionNote(lock, selfVersion) {
33465
+ if (!lock.version || lock.version === selfVersion) return null;
33466
+ return [
33467
+ `the running daemon is ${lock.version}, but this CLI is ${selfVersion} \u2014 it was started before the update.`,
33468
+ `restart it onto ${selfVersion}: errata stop, then errata start`
33469
+ ];
33470
+ }
33464
33471
  function isProcessAlive(pid) {
33465
33472
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
33466
33473
  try {
@@ -33500,6 +33507,20 @@ function isDaemonAlive(lockPath) {
33500
33507
  function daemonLockBlocksStartup(lockPath) {
33501
33508
  return daemonLockState(readDaemonLock(lockPath), true) !== "stale";
33502
33509
  }
33510
+ async function waitForDaemonExit(lock, timeoutMs, pollMs = 200) {
33511
+ const deadline = Date.now() + timeoutMs;
33512
+ for (; ; ) {
33513
+ if (daemonLockState(lock, true) === "stale") return true;
33514
+ if (Date.now() >= deadline) return false;
33515
+ await new Promise((resolve10) => setTimeout(resolve10, pollMs));
33516
+ }
33517
+ }
33518
+ function releaseOwnLock(lockPath) {
33519
+ try {
33520
+ if (parseDaemonLock(readFileSync12(lockPath, "utf8"))?.pid === process.pid) unlinkSync2(lockPath);
33521
+ } catch {
33522
+ }
33523
+ }
33503
33524
  function stopLockedDaemon(lockPath) {
33504
33525
  let original;
33505
33526
  try {
@@ -73598,7 +73619,7 @@ var init_report_render = __esm({
73598
73619
 
73599
73620
  // src/cli.ts
73600
73621
  init_src6();
73601
- import { closeSync as closeSync2, existsSync as existsSync39, openSync as openSync2, readFileSync as readFileSync42, renameSync as renameSync6, statSync as statSync12 } from "node:fs";
73622
+ import { closeSync as closeSync2, existsSync as existsSync39, openSync as openSync2, readFileSync as readFileSync41, renameSync as renameSync6, statSync as statSync12 } from "node:fs";
73602
73623
  import { join as join48, resolve as pathResolve2 } from "node:path";
73603
73624
  import { spawn as spawn5 } from "node:child_process";
73604
73625
 
@@ -82685,7 +82706,7 @@ function createWatchBreaker(deps) {
82685
82706
  }
82686
82707
 
82687
82708
  // src/engine.ts
82688
- var DAEMON_VERSION = true ? "2.0.18-dev.3597" : "2.0.0-alpha.0";
82709
+ var DAEMON_VERSION = true ? "2.0.18-dev.3612" : "2.0.0-alpha.0";
82689
82710
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
82690
82711
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
82691
82712
  var GIT_OP_MUTE_MS = 4e3;
@@ -85407,10 +85428,20 @@ async function startDaemon(opts) {
85407
85428
  };
85408
85429
  }
85409
85430
  async function listenServer(fetchFn, port) {
85410
- return new Promise((resolve10) => {
85431
+ return new Promise((resolve10, reject) => {
85411
85432
  const server = serve({ fetch: fetchFn, hostname: "127.0.0.1", port }, (info2) => {
85412
85433
  resolve10({ server, port: info2.port });
85413
85434
  });
85435
+ server.once(
85436
+ "error",
85437
+ (err2) => {
85438
+ reject(
85439
+ err2.code === "EADDRINUSE" ? new Error(
85440
+ `port ${port} is still held by another process \u2014 usually a previous errata daemon that has not finished shutting down. Wait for it to exit (errata stop waits for it), then start again.`
85441
+ ) : err2
85442
+ );
85443
+ }
85444
+ );
85414
85445
  });
85415
85446
  }
85416
85447
 
@@ -85715,7 +85746,7 @@ function invalidateInstanceReceipt(current, destination, failedId, mirrorLegacy)
85715
85746
  // src/multi.ts
85716
85747
  init_dist();
85717
85748
  init_src5();
85718
- import { existsSync as existsSync34, readFileSync as readFileSync36, unlinkSync as unlinkSync5, writeFileSync as writeFileSync26 } from "node:fs";
85749
+ import { existsSync as existsSync34, writeFileSync as writeFileSync26 } from "node:fs";
85719
85750
  import { resolve as pathResolve } from "node:path";
85720
85751
 
85721
85752
  // src/principle-sync.ts
@@ -89657,6 +89688,7 @@ var SessionAffinity = class {
89657
89688
  // src/multi.ts
89658
89689
  init_checkout_root();
89659
89690
  var PROCESS_STARTED_AT = Date.now();
89691
+ var SHUTDOWN_CONNECTION_GRACE_MS = 2e3;
89660
89692
  var SELF_ENTRY = process.argv[1] ? pathResolve(process.argv[1]) : "";
89661
89693
  var LOCAL_BUILD_ENTRY = SELF_ENTRY && !isInstalledEntry(SELF_ENTRY) ? SELF_ENTRY : void 0;
89662
89694
  function withLaunchOrigin(pending) {
@@ -91479,21 +91511,23 @@ async function startMultiDaemon(opts = {}) {
91479
91511
  clearInterval(historyRetry);
91480
91512
  clearTimeout(codexWakeBoot);
91481
91513
  try {
91482
- const cur = readFileSync36(lockPath, "utf8");
91483
- if (JSON.parse(cur).pid === process.pid) unlinkSync5(lockPath);
91484
- } catch {
91485
- }
91486
- await new Promise(
91487
- (res) => server.close(res)
91488
- );
91489
- updatePoller?.stop();
91490
- healthSentinel.stop();
91491
- await Promise.allSettled(attaching.values());
91492
- for (const r of records) await r.engine.stop();
91493
- if (consolidateWorker) await consolidateWorker.stop();
91494
- try {
91495
- sharedStore.close();
91496
- } catch {
91514
+ const http = server;
91515
+ const closed = new Promise((res) => http.close(res));
91516
+ http.closeIdleConnections?.();
91517
+ const force = setTimeout(() => http.closeAllConnections?.(), SHUTDOWN_CONNECTION_GRACE_MS);
91518
+ await closed;
91519
+ clearTimeout(force);
91520
+ updatePoller?.stop();
91521
+ healthSentinel.stop();
91522
+ await Promise.allSettled(attaching.values());
91523
+ for (const r of records) await r.engine.stop();
91524
+ if (consolidateWorker) await consolidateWorker.stop();
91525
+ try {
91526
+ sharedStore.close();
91527
+ } catch {
91528
+ }
91529
+ } finally {
91530
+ releaseOwnLock(lockPath);
91497
91531
  }
91498
91532
  }
91499
91533
  };
@@ -91943,8 +91977,31 @@ function installedHarnessVersion(bin) {
91943
91977
  return null;
91944
91978
  }
91945
91979
  }
91980
+ function daemonDoctorCheck(input) {
91981
+ const { lock, state, selfVersion, lockPath } = input;
91982
+ if (!lock || state === "stale") {
91983
+ return { id: "daemon", status: "warn", detail: "Daemon is not running \u2014 start it: errata start" };
91984
+ }
91985
+ if (state === "unknown") {
91986
+ return {
91987
+ id: "daemon",
91988
+ status: "fail",
91989
+ detail: `${lockPath} names pid ${lock.pid}, but that process's identity cannot be verified. If \`ps -p ${lock.pid}\` shows no errata process, delete the lock; otherwise run errata stop.`
91990
+ };
91991
+ }
91992
+ const where = `pid ${lock.pid}${lock.webUiUrl ? ` \xB7 ${lock.webUiUrl}` : ""}`;
91993
+ if (lock.version && lock.version !== selfVersion) {
91994
+ return {
91995
+ id: "daemon",
91996
+ status: "warn",
91997
+ detail: `Running ${lock.version} (${where}), but this CLI is ${selfVersion} \u2014 restart it: errata stop, then errata start`
91998
+ };
91999
+ }
92000
+ return { id: "daemon", status: "pass", detail: `Running${lock.version ? ` ${lock.version}` : ""} \xB7 ${where}` };
92001
+ }
91946
92002
  async function diagnoseInstallation(cfg, dependencies) {
91947
92003
  const checks = [];
92004
+ if (dependencies.daemon) checks.push(dependencies.daemon());
91948
92005
  if (dependencies.codexHooks) checks.push(...await dependencies.codexHooks());
91949
92006
  const profileName = cfg.activeInstallationProfile;
91950
92007
  const profile = profileName ? cfg.installationProfiles[profileName] : void 0;
@@ -92041,12 +92098,12 @@ async function diagnoseInstallation(cfg, dependencies) {
92041
92098
 
92042
92099
  // src/codex-doctor.ts
92043
92100
  import { spawn as spawn4 } from "node:child_process";
92044
- import { readFileSync as readFileSync38, statSync as statSync10 } from "node:fs";
92101
+ import { readFileSync as readFileSync37, statSync as statSync10 } from "node:fs";
92045
92102
  import { join as join44 } from "node:path";
92046
92103
  import { createInterface } from "node:readline";
92047
92104
 
92048
92105
  // src/codex-hooks.ts
92049
- import { existsSync as existsSync35, mkdirSync as mkdirSync9, readFileSync as readFileSync37, renameSync as renameSync5, unlinkSync as unlinkSync6, writeFileSync as writeFileSync27 } from "node:fs";
92106
+ import { existsSync as existsSync35, mkdirSync as mkdirSync9, readFileSync as readFileSync36, renameSync as renameSync5, unlinkSync as unlinkSync5, writeFileSync as writeFileSync27 } from "node:fs";
92050
92107
  import { homedir as homedir7 } from "node:os";
92051
92108
  import { join as join43 } from "node:path";
92052
92109
 
@@ -92217,23 +92274,23 @@ function buildCodexRelaySource(workspaceRoot) {
92217
92274
  init_checkout_root();
92218
92275
  var CODEX_HOOK_REVIEW_HELP = "Codex desktop: Settings > Hooks, select this project, expand each event and Trust the Errata hook. CLI users can use /hooks.";
92219
92276
  function writeRelay(file2, source) {
92220
- if (existsSync35(file2) && readFileSync37(file2, "utf8") === source) return;
92277
+ if (existsSync35(file2) && readFileSync36(file2, "utf8") === source) return;
92221
92278
  const temp = `${file2}.${process.pid}.tmp`;
92222
92279
  try {
92223
92280
  writeFileSync27(temp, source, { encoding: "utf8", mode: 384 });
92224
92281
  renameSync5(temp, file2);
92225
92282
  } finally {
92226
- if (existsSync35(temp)) unlinkSync6(temp);
92283
+ if (existsSync35(temp)) unlinkSync5(temp);
92227
92284
  }
92228
92285
  }
92229
92286
  function refreshCodexRelay(root) {
92230
92287
  const config2 = join43(root, ".codex", "config.toml");
92231
92288
  if (!existsSync35(config2)) return "absent";
92232
- const existing = readFileSync37(config2, "utf8");
92289
+ const existing = readFileSync36(config2, "utf8");
92233
92290
  if (!existing.includes(CODEX_HOOKS_BEGIN) && !existing.includes(CODEX_HOOKS_END)) return "absent";
92234
92291
  replaceCodexHooks(existing, "");
92235
92292
  const relay = join43(root, ".codex", "hooks", "errata-relay.mjs");
92236
- const prior = existsSync35(relay) ? readFileSync37(relay, "utf8") : "";
92293
+ const prior = existsSync35(relay) ? readFileSync36(relay, "utf8") : "";
92237
92294
  if (prior && !prior.startsWith("// errata-managed: regenerated by errata install-hooks codex")) {
92238
92295
  throw new Error("Codex relay is not Errata-managed; preserved it. Run errata install-hooks codex to explicitly replace it.");
92239
92296
  }
@@ -92264,10 +92321,10 @@ function writeCodexHooks(root, port, nodePath = process.execPath) {
92264
92321
  const file2 = join43(dir, "config.toml");
92265
92322
  const relay = join43(dir, "hooks", "errata-relay.mjs");
92266
92323
  const block = buildCodexHooksToml(port, { nodePath, relayPath: relay });
92267
- const final = replaceCodexHooks(existsSync35(file2) ? readFileSync37(file2, "utf8") : "", block);
92324
+ const final = replaceCodexHooks(existsSync35(file2) ? readFileSync36(file2, "utf8") : "", block);
92268
92325
  mkdirSync9(join43(dir, "hooks"), { recursive: true });
92269
92326
  writeRelay(relay, buildCodexRelaySource(checkoutRootOf(root)));
92270
- if (!existsSync35(file2) || readFileSync37(file2, "utf8") !== final) writeFileSync27(file2, final, "utf8");
92327
+ if (!existsSync35(file2) || readFileSync36(file2, "utf8") !== final) writeFileSync27(file2, final, "utf8");
92271
92328
  return file2;
92272
92329
  }
92273
92330
 
@@ -92276,7 +92333,7 @@ init_checkout_root();
92276
92333
  function inspectCodexRelay(cwd) {
92277
92334
  const root = nearestGitRoot(cwd) ?? cwd;
92278
92335
  try {
92279
- const source = readFileSync38(join44(root, ".codex", "hooks", "errata-relay.mjs"), "utf8");
92336
+ const source = readFileSync37(join44(root, ".codex", "hooks", "errata-relay.mjs"), "utf8");
92280
92337
  if (source === buildCodexRelaySource(checkoutRootOf(root))) {
92281
92338
  return { id: "codex_relay", status: "pass", detail: "Installed Codex relay matches this Errata version" };
92282
92339
  }
@@ -92288,7 +92345,7 @@ function inspectCodexContext(cwd) {
92288
92345
  const root = checkoutRootOf(nearestGitRoot(cwd) ?? cwd);
92289
92346
  try {
92290
92347
  const file2 = join44(root, ".errata", "context.md");
92291
- if (statSync10(file2).size <= 128 * 1024 && readFileSync38(file2, "utf8").trim()) {
92348
+ if (statSync10(file2).size <= 128 * 1024 && readFileSync37(file2, "utf8").trim()) {
92292
92349
  return { id: "codex_context", status: "pass", detail: "Saved workspace context is readable by the relay; this does not prove live daemon freshness" };
92293
92350
  }
92294
92351
  } catch {
@@ -92464,7 +92521,7 @@ function consolidationGapMs(lastPassMs, policy) {
92464
92521
 
92465
92522
  // src/codex-hook-refresh.ts
92466
92523
  init_checkout_root();
92467
- import { existsSync as existsSync36, readdirSync as readdirSync13, readFileSync as readFileSync39 } from "node:fs";
92524
+ import { existsSync as existsSync36, readdirSync as readdirSync13, readFileSync as readFileSync38 } from "node:fs";
92468
92525
  import { dirname as dirname13, join as join45, resolve as resolve8 } from "node:path";
92469
92526
  init_registry();
92470
92527
  function codexHookRoots(roots) {
@@ -92482,7 +92539,7 @@ function codexHookRoots(roots) {
92482
92539
  }
92483
92540
  for (const name2 of names) {
92484
92541
  try {
92485
- const checkout = dirname13(readFileSync39(join45(dir, name2, "gitdir"), "utf8").trim());
92542
+ const checkout = dirname13(readFileSync38(join45(dir, name2, "gitdir"), "utf8").trim());
92486
92543
  if (mainCheckoutOfWorktree(checkout) === main2) found.add(checkout);
92487
92544
  } catch {
92488
92545
  }
@@ -92504,7 +92561,7 @@ function refreshInstalledCodexRelays(roots = listWorkspaceEntries().map((entry)
92504
92561
  }
92505
92562
 
92506
92563
  // src/claude-statusline.ts
92507
- import { existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync40, writeFileSync as writeFileSync28 } from "node:fs";
92564
+ import { existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync39, writeFileSync as writeFileSync28 } from "node:fs";
92508
92565
  import { homedir as homedir8 } from "node:os";
92509
92566
  import { dirname as dirname14, join as join46 } from "node:path";
92510
92567
  var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata init / install-hooks claude
@@ -92565,7 +92622,7 @@ function ensureUserStatusLine(opts) {
92565
92622
  let settings = {};
92566
92623
  if (existsSync37(settingsPath)) {
92567
92624
  try {
92568
- const parsed = JSON.parse(readFileSync40(settingsPath, "utf8"));
92625
+ const parsed = JSON.parse(readFileSync39(settingsPath, "utf8"));
92569
92626
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "unreadable";
92570
92627
  settings = parsed;
92571
92628
  } catch {
@@ -92578,7 +92635,7 @@ function ensureUserStatusLine(opts) {
92578
92635
  const ownRows = keep(settings["subagentStatusLine"]);
92579
92636
  if (ownMain && ownRows) return "kept-own";
92580
92637
  mkdirSync10(opts.relayDir, { recursive: true });
92581
- if (!existsSync37(relay) || readFileSync40(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
92638
+ if (!existsSync37(relay) || readFileSync39(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
92582
92639
  writeFileSync28(relay, STATUSLINE_RELAY_SOURCE, "utf8");
92583
92640
  }
92584
92641
  const nodePath = opts.nodePath ?? process.execPath;
@@ -92611,7 +92668,7 @@ function describeStatusLineOutcome(outcome) {
92611
92668
 
92612
92669
  // src/claude-mods.ts
92613
92670
  import { createHash as createHash20 } from "node:crypto";
92614
- import { cpSync as cpSync2, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync41, readdirSync as readdirSync14, rmSync as rmSync4, statSync as statSync11, writeFileSync as writeFileSync29 } from "node:fs";
92671
+ import { cpSync as cpSync2, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync40, readdirSync as readdirSync14, rmSync as rmSync4, statSync as statSync11, writeFileSync as writeFileSync29 } from "node:fs";
92615
92672
  import { delimiter as delimiter2, dirname as dirname15, join as join47, relative as relative7, resolve as resolve9, sep as sep5 } from "node:path";
92616
92673
  import { fileURLToPath as fileURLToPath2 } from "node:url";
92617
92674
  var CLAUDE_PLUGIN_DIRS_ENV = "CLAUDE_CODE_PLUGIN_DIRS";
@@ -92639,7 +92696,7 @@ function modHash(dir) {
92639
92696
  for (const abs of listFiles(dir).sort()) {
92640
92697
  h.update(relative7(dir, abs).split(sep5).join("/"));
92641
92698
  h.update("\0");
92642
- h.update(readFileSync41(abs));
92699
+ h.update(readFileSync40(abs));
92643
92700
  h.update("\0");
92644
92701
  }
92645
92702
  return h.digest("hex");
@@ -92663,7 +92720,7 @@ function off(env2) {
92663
92720
  function readSettings(path2) {
92664
92721
  if (!existsSync38(path2)) return {};
92665
92722
  try {
92666
- const parsed = JSON.parse(readFileSync41(path2, "utf8"));
92723
+ const parsed = JSON.parse(readFileSync40(path2, "utf8"));
92667
92724
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
92668
92725
  return parsed;
92669
92726
  } catch {
@@ -92714,7 +92771,7 @@ function ensureClaudeMods(opts) {
92714
92771
  const dest = join47(modsRoot, name2);
92715
92772
  const hash2 = modHash(src);
92716
92773
  const hashFile = join47(dest, HASH_FILE);
92717
- if (!existsSync38(hashFile) || readFileSync41(hashFile, "utf8") !== hash2) {
92774
+ if (!existsSync38(hashFile) || readFileSync40(hashFile, "utf8") !== hash2) {
92718
92775
  rmSync4(dest, { recursive: true, force: true });
92719
92776
  cpSync2(src, dest, { recursive: true, filter: (p) => !isEngineOwned(relative7(src, p)) });
92720
92777
  writeFileSync29(hashFile, hash2, "utf8");
@@ -92886,6 +92943,8 @@ export const InerrataPlugin = async (input) => {
92886
92943
  }
92887
92944
 
92888
92945
  // src/cli.ts
92946
+ var STOP_WAIT_MS = 3e4;
92947
+ var SHUTDOWN_DEADLINE_MS = 2e4;
92889
92948
  var exitCleanOnEpipe = (err2) => {
92890
92949
  if (err2.code === "EPIPE") process.exit(0);
92891
92950
  throw err2;
@@ -93138,7 +93197,7 @@ function resolveHookPort(explicit) {
93138
93197
  }
93139
93198
  const settingsPath = join48(ROOT, ".claude", "settings.json");
93140
93199
  try {
93141
- if (existsSync39(settingsPath)) return installedHookPort(readFileSync42(settingsPath, "utf8")) ?? 7891;
93200
+ if (existsSync39(settingsPath)) return installedHookPort(readFileSync41(settingsPath, "utf8")) ?? 7891;
93142
93201
  } catch {
93143
93202
  }
93144
93203
  return 7891;
@@ -93303,6 +93362,8 @@ async function cmdStart() {
93303
93362
  console.log(`errata: workspace registered; attached to the running daemon`);
93304
93363
  console.log(` workspace: ${where}`);
93305
93364
  console.log(` daemon: ${running.webUiUrl ?? "(running)"} \xB7 pid ${running.pid}`);
93365
+ const versionNote = daemonVersionNote(running, DAEMON_VERSION);
93366
+ if (versionNote) for (const l of versionNote) console.log(` \u26A0 ${l}`);
93306
93367
  let watching = "on the daemon's next restart";
93307
93368
  if (running.webUiUrl) {
93308
93369
  try {
@@ -93326,7 +93387,7 @@ async function cmdStart() {
93326
93387
  if (up) {
93327
93388
  console.log(` daemon: running \xB7 pid ${up.pid} \xB7 ${up.webUiUrl ?? "(running)"}`);
93328
93389
  console.log(` logs: ${daemonLogPath()}`);
93329
- console.log(` watch it: errata dash \xB7 check it: errata status`);
93390
+ console.log(` follow it: ${followLogHint()} \xB7 check it: errata status`);
93330
93391
  } else {
93331
93392
  console.log(` \u26A0 daemon did not come up within 12s.`);
93332
93393
  console.log(` run \`errata dash\` in the foreground to see the error.`);
@@ -93438,8 +93499,10 @@ async function cmdStatus() {
93438
93499
  const lockPath = globalDaemonLock();
93439
93500
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
93440
93501
  console.log(
93441
- ` daemon: ${running ? `running \xB7 pid ${running.pid} \xB7 ${running.webUiUrl ?? "(running)"}` : "not running (run: errata start)"}`
93502
+ ` daemon: ${running ? `running \xB7 pid ${running.pid}${running.version ? ` \xB7 ${running.version}` : ""} \xB7 ${running.webUiUrl ?? "(running)"}` : "not running (run: errata start)"}`
93442
93503
  );
93504
+ const versionNote = running ? daemonVersionNote(running, DAEMON_VERSION) : null;
93505
+ if (versionNote) for (const l of versionNote) console.log(` \u26A0 ${l}`);
93443
93506
  if (running && process.argv[1]) {
93444
93507
  const note = foreignDaemonNote(running, pathResolve2(process.argv[1]));
93445
93508
  if (note) for (const l of note) console.log(` \u26A0 ${l}`);
@@ -93656,6 +93719,11 @@ async function cmdDoctor(args2) {
93656
93719
  const cfg = loadConfig();
93657
93720
  const report = await diagnoseInstallation(cfg, {
93658
93721
  ...hasCodexConfig(process.cwd()) ? { codexHooks: () => diagnoseCodexHooks(process.cwd()) } : {},
93722
+ daemon: () => {
93723
+ const lockPath = globalDaemonLock();
93724
+ const lock = readDaemonLock(lockPath);
93725
+ return daemonDoctorCheck({ lock, state: daemonLockState(lock, true), selfVersion: DAEMON_VERSION, lockPath });
93726
+ },
93659
93727
  inspect: () => inspectCloudEndpoint(cfg.cloudUrl),
93660
93728
  authenticate: async () => {
93661
93729
  const me = await authedCloudClient(cfg).me();
@@ -93672,7 +93740,7 @@ async function cmdDoctor(args2) {
93672
93740
  const marker = check2.status === "pass" ? "\u2713" : check2.status === "warn" ? "!" : "\u2717";
93673
93741
  console.log(` ${marker} ${check2.id}: ${check2.detail}`);
93674
93742
  }
93675
- if (!report.ok) {
93743
+ if (report.checks.some((check2) => check2.status === "fail" && check2.id !== "daemon")) {
93676
93744
  console.log(
93677
93745
  " recovery: `errata profile list`, then `errata profile use <name>`; re-login if authentication still fails."
93678
93746
  );
@@ -93738,7 +93806,8 @@ async function cmdUpdate(args2) {
93738
93806
  }
93739
93807
  console.log(`
93740
93808
  [OK] updated to ${status.latest}. Restart the daemon to run it:`);
93741
- console.log(` errata stop (autostart relaunches it at next logon, or run 'errata dash')`);
93809
+ console.log(` errata stop (waits for the old daemon to exit)`);
93810
+ console.log(` errata start (starts the updated one; autostart also relaunches it at next logon)`);
93742
93811
  } else {
93743
93812
  console.log(`
93744
93813
  [X] npm install failed (exit ${code}). Try manually:`);
@@ -94375,7 +94444,7 @@ async function cmdReview() {
94375
94444
  console.log("(review queue empty)");
94376
94445
  return;
94377
94446
  }
94378
- const queue = JSON.parse(readFileSync42(paths.reviewQueue, "utf8"));
94447
+ const queue = JSON.parse(readFileSync41(paths.reviewQueue, "utf8"));
94379
94448
  if (queue.length === 0) {
94380
94449
  console.log("(review queue empty)");
94381
94450
  return;
@@ -95113,8 +95182,20 @@ async function cmdStop() {
95113
95182
  return;
95114
95183
  }
95115
95184
  try {
95185
+ const lock = readDaemonLock(lockPath);
95116
95186
  const result = stopLockedDaemon(lockPath);
95117
- if (result === "signaled") console.log(`sent SIGTERM to the verified daemon process`);
95187
+ if (result === "signaled" && lock) {
95188
+ console.log(`sent SIGTERM to the daemon (pid ${lock.pid}); waiting for it to exit\u2026`);
95189
+ if (await waitForDaemonExit(lock, STOP_WAIT_MS)) {
95190
+ stopLockedDaemon(lockPath);
95191
+ console.log(`daemon stopped \u2014 safe to start it again`);
95192
+ } else {
95193
+ console.warn(
95194
+ `daemon (pid ${lock.pid}) has not exited after ${STOP_WAIT_MS / 1e3}s; it may still hold port 7891 and its stores. Check it with \`ps -p ${lock.pid}\` (Windows: \`Get-Process -Id ${lock.pid}\`) before starting again.`
95195
+ );
95196
+ process.exitCode = 1;
95197
+ }
95198
+ } else if (result === "signaled") console.log(`sent SIGTERM to the verified daemon process`);
95118
95199
  else if (result === "stale") console.log(`removed stale daemon lock; no process was signaled`);
95119
95200
  else if (result === "missing") console.log(`no daemon lock found \u2014 nothing to stop`);
95120
95201
  else throw new Error(`daemon lock ownership is ${result}; no process was signaled and the lock was preserved`);
@@ -95212,13 +95293,13 @@ async function cmdInstallHooks(args2) {
95212
95293
  }
95213
95294
  }
95214
95295
  async function installOpencodePlugin(port) {
95215
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95296
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95216
95297
  const { join: join49 } = await import("node:path");
95217
95298
  const dir = join49(ROOT, ".opencode", "plugin");
95218
95299
  if (!existsSync40(dir)) mkdirSync12(dir, { recursive: true });
95219
95300
  const file2 = join49(dir, "inerrata.js");
95220
95301
  if (existsSync40(file2)) {
95221
- const head2 = readFileSync43(file2, "utf8").split("\n")[0] ?? "";
95302
+ const head2 = readFileSync42(file2, "utf8").split("\n")[0] ?? "";
95222
95303
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
95223
95304
  console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
95224
95305
  process.exit(2);
@@ -95229,7 +95310,7 @@ async function installOpencodePlugin(port) {
95229
95310
  let cfg = { $schema: "https://opencode.ai/config.json" };
95230
95311
  if (existsSync40(cfgFile)) {
95231
95312
  try {
95232
- cfg = JSON.parse(readFileSync43(cfgFile, "utf8"));
95313
+ cfg = JSON.parse(readFileSync42(cfgFile, "utf8"));
95233
95314
  } catch {
95234
95315
  console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
95235
95316
  process.exit(2);
@@ -95275,7 +95356,7 @@ function installClaudeMods() {
95275
95356
  }
95276
95357
  }
95277
95358
  async function installClaudeHooks(port) {
95278
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95359
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95279
95360
  const { join: join49 } = await import("node:path");
95280
95361
  const dir = join49(ROOT, ".claude");
95281
95362
  if (!existsSync40(dir)) mkdirSync12(dir, { recursive: true });
@@ -95283,7 +95364,7 @@ async function installClaudeHooks(port) {
95283
95364
  let settings = {};
95284
95365
  if (existsSync40(file2)) {
95285
95366
  try {
95286
- settings = JSON.parse(readFileSync43(file2, "utf8"));
95367
+ settings = JSON.parse(readFileSync42(file2, "utf8"));
95287
95368
  } catch {
95288
95369
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
95289
95370
  process.exit(2);
@@ -95349,7 +95430,7 @@ async function installClaudeHooks(port) {
95349
95430
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
95350
95431
  }
95351
95432
  async function installClaudeMcpConfig() {
95352
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95433
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95353
95434
  const { join: join49, dirname: dirname16 } = await import("node:path");
95354
95435
  const file2 = join49(ROOT, ".mcp.json");
95355
95436
  const dir = dirname16(file2);
@@ -95357,7 +95438,7 @@ async function installClaudeMcpConfig() {
95357
95438
  let cfg = {};
95358
95439
  if (existsSync40(file2)) {
95359
95440
  try {
95360
- cfg = JSON.parse(readFileSync43(file2, "utf8"));
95441
+ cfg = JSON.parse(readFileSync42(file2, "utf8"));
95361
95442
  } catch {
95362
95443
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
95363
95444
  process.exit(2);
@@ -95371,7 +95452,7 @@ async function installClaudeMcpConfig() {
95371
95452
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
95372
95453
  }
95373
95454
  async function installCursorMcpConfig() {
95374
- const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync43, writeFileSync: writeFileSync30 } = await import("node:fs");
95455
+ const { mkdirSync: mkdirSync12, existsSync: existsSync40, readFileSync: readFileSync42, writeFileSync: writeFileSync30 } = await import("node:fs");
95375
95456
  const { join: join49 } = await import("node:path");
95376
95457
  const dir = join49(ROOT, ".cursor");
95377
95458
  if (!existsSync40(dir)) mkdirSync12(dir, { recursive: true });
@@ -95379,7 +95460,7 @@ async function installCursorMcpConfig() {
95379
95460
  let cfg = {};
95380
95461
  if (existsSync40(file2)) {
95381
95462
  try {
95382
- cfg = JSON.parse(readFileSync43(file2, "utf8"));
95463
+ cfg = JSON.parse(readFileSync42(file2, "utf8"));
95383
95464
  } catch {
95384
95465
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
95385
95466
  process.exit(2);
@@ -95582,7 +95663,21 @@ function installLogTimestamps() {
95582
95663
  };
95583
95664
  }
95584
95665
  }
95666
+ function followLogHint() {
95667
+ const log = daemonLogPath();
95668
+ return process.platform === "win32" ? `Get-Content -Wait "${log}"` : `tail -f "${log}"`;
95669
+ }
95585
95670
  async function cmdDash(args2) {
95671
+ const running = readDaemonLock(globalDaemonLock());
95672
+ if (running && daemonLockState(running, true) === "running") {
95673
+ console.log(
95674
+ `errata daemon already running \xB7 pid ${running.pid}${running.version ? ` \xB7 ${running.version}` : ""}${running.webUiUrl ? ` \xB7 ${running.webUiUrl}` : ""}`
95675
+ );
95676
+ console.log(` follow it: ${followLogHint()}`);
95677
+ console.log(` check it: errata status`);
95678
+ console.log(` restart it: errata stop, then errata start`);
95679
+ return;
95680
+ }
95586
95681
  process.title = "errata-daemon";
95587
95682
  installLogTimestamps();
95588
95683
  refreshExistingCodexHooks();
@@ -95952,6 +96047,11 @@ async function cmdDash(args2) {
95952
96047
  const shutdown = async (signal) => {
95953
96048
  console.log(`
95954
96049
  shutting down\u2026 (${signal})`);
96050
+ setTimeout(() => {
96051
+ console.warn(`[errata] shutdown exceeded ${SHUTDOWN_DEADLINE_MS / 1e3}s \u2014 forcing exit`);
96052
+ releaseOwnLock(globalDaemonLock());
96053
+ process.exit(1);
96054
+ }, SHUTDOWN_DEADLINE_MS).unref();
95955
96055
  clearInterval(interval);
95956
96056
  clearInterval(nightlyInterval);
95957
96057
  if (quiesceTimer) clearTimeout(quiesceTimer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.18-dev.3597",
3
+ "version": "2.0.18-dev.3612",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {