@inerrata-corporation/errata 2.0.6-dev.2759 → 2.0.6

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.
package/README.txt CHANGED
@@ -34,9 +34,12 @@ errata link flows also refresh detected Codex hooks; --skip-hooks opts out of
34
34
  either automatic install. The installer preserves other config and writes its
35
35
  Node relay into .codex/hooks/errata-relay.mjs. No curl or Bash dependency.
36
36
 
37
- Restart/resume Codex after installing, and review/trust the hooks with /hooks.
38
- If your Codex build gates hooks, enable [features] codex_hooks = true. Update
39
- older Codex builds if UserPromptSubmit or tool hooks are unsupported.
37
+ Codex desktop: open Settings > Hooks, select the project, expand each event,
38
+ and review/Trust its Errata hook. Use Reload hooks after installation. No
39
+ standalone Codex CLI is required. CLI users can use /hooks instead.
40
+ Restarting or reinstalling does not grant trust. Run errata doctor to check
41
+ activation; it discovers the Windows desktop runtime even without a CLI on PATH.
42
+ Respect explicitly disabled hooks ([features] hooks = false).
40
43
 
41
44
  SessionStart injects .errata/context.md, including startup/resume/compaction.
42
45
  UserPromptSubmit and PostToolUse inject it again only when it changes. Tool
package/errata.mjs CHANGED
@@ -17038,6 +17038,11 @@ var init_context_file = __esm({
17038
17038
  "Live agent context for this checkout \u2014 priors, open problems, the capture",
17039
17039
  "protocol \u2014 is maintained by the errata daemon at `.errata/context.md`.",
17040
17040
  "",
17041
+ "At the beginning of each task and each new user turn, read that file before",
17042
+ "substantive work. Use the current file in preference to an earlier snapshot.",
17043
+ "If it is absent, continue normally. In Codex, an `@` mention alone does not",
17044
+ "load the file; read it explicitly.",
17045
+ "",
17041
17046
  "It is gitignored ON PURPOSE: it is this machine's runtime state, not repo",
17042
17047
  "content. Committing it would prime your teammates with your graph.",
17043
17048
  "",
@@ -26301,8 +26306,8 @@ var init_config = __esm({
26301
26306
  "use strict";
26302
26307
  init_paths();
26303
26308
  LEGACY_CLOUD_URL = "https://inerrata-gateway.onrender.com";
26304
- DEFAULT_CLOUD_URL = true ? "https://dev.inerrata.dev" : DEV_CLOUD_URL;
26305
- DEFAULT_UPDATE_CHANNEL = false ? "latest" : "dev";
26309
+ DEFAULT_CLOUD_URL = true ? "https://inerrata.dev" : DEV_CLOUD_URL;
26310
+ DEFAULT_UPDATE_CHANNEL = true ? "latest" : "dev";
26306
26311
  }
26307
26312
  });
26308
26313
 
@@ -71187,8 +71192,8 @@ var init_report_render = __esm({
71187
71192
 
71188
71193
  // src/cli.ts
71189
71194
  init_src6();
71190
- import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync35, renameSync as renameSync4, statSync as statSync8 } from "node:fs";
71191
- import { join as join40, resolve as pathResolve2 } from "node:path";
71195
+ import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync35, renameSync as renameSync4, statSync as statSync9 } from "node:fs";
71196
+ import { join as join41, resolve as pathResolve2 } from "node:path";
71192
71197
  import { spawn as spawn5 } from "node:child_process";
71193
71198
 
71194
71199
  // src/daemon.ts
@@ -73721,6 +73726,7 @@ init_src10();
73721
73726
  init_review2();
73722
73727
 
73723
73728
  // src/turn.ts
73729
+ init_checkout_root();
73724
73730
  import { closeSync, existsSync as existsSync16, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync5 } from "node:fs";
73725
73731
  import { basename as basename5, delimiter, dirname as dirname10, join as join19, normalize as normalize3 } from "node:path";
73726
73732
  import { homedir as homedir4 } from "node:os";
@@ -74205,6 +74211,8 @@ function codexRolloutsForCwd(cwd, opts = {}) {
74205
74211
  const sinceMs = opts.sinceMs ?? 0;
74206
74212
  const limit = opts.limit ?? 25;
74207
74213
  const out2 = [];
74214
+ const owner = checkoutRootOf(cwd);
74215
+ const owners = /* @__PURE__ */ new Map();
74208
74216
  for (const root of codexSessionRoots(opts.env, opts.home)) {
74209
74217
  if (!existsSync16(root)) continue;
74210
74218
  const stack = [{ dir: root, depth: 0 }];
@@ -74231,7 +74239,14 @@ function codexRolloutsForCwd(cwd, opts = {}) {
74231
74239
  if (depth < 3) continue;
74232
74240
  if (!name2.startsWith("rollout-") || !name2.endsWith(".jsonl")) continue;
74233
74241
  if (st.mtimeMs < sinceMs) continue;
74234
- if (readCodexRolloutCwd(full) !== cwd) continue;
74242
+ const sessionCwd = readCodexRolloutCwd(full);
74243
+ if (sessionCwd === null) continue;
74244
+ let sessionOwner = owners.get(sessionCwd);
74245
+ if (sessionOwner === void 0) {
74246
+ sessionOwner = checkoutRootOf(sessionCwd);
74247
+ owners.set(sessionCwd, sessionOwner);
74248
+ }
74249
+ if (sessionOwner !== owner) continue;
74235
74250
  out2.push({ path: full, sessionId: `codex:${basename5(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
74236
74251
  }
74237
74252
  }
@@ -79907,7 +79922,7 @@ function createWatchBreaker(deps) {
79907
79922
  }
79908
79923
 
79909
79924
  // src/engine.ts
79910
- var DAEMON_VERSION = true ? "2.0.6-dev.2759" : "2.0.0-alpha.0";
79925
+ var DAEMON_VERSION = true ? "2.0.6" : "2.0.0-alpha.0";
79911
79926
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
79912
79927
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
79913
79928
  var GIT_OP_MUTE_MS = 4e3;
@@ -87636,9 +87651,41 @@ function shouldUseOAuthLogin(flags2, env2 = process.env) {
87636
87651
 
87637
87652
  // src/doctor.ts
87638
87653
  import { spawnSync } from "node:child_process";
87654
+
87655
+ // src/codex-runtime.ts
87656
+ import { readdirSync as readdirSync12, statSync as statSync8 } from "node:fs";
87657
+ import { join as join39 } from "node:path";
87658
+ function resolveCodexExecutable(options = {}) {
87659
+ const platform = options.platform ?? process.platform;
87660
+ const env2 = options.env ?? process.env;
87661
+ if (platform !== "win32") return "codex";
87662
+ const fileTime = (path2) => {
87663
+ try {
87664
+ const s = statSync8(path2);
87665
+ return s.isFile() ? s.mtimeMs : null;
87666
+ } catch {
87667
+ return null;
87668
+ }
87669
+ };
87670
+ for (const dir of (env2["PATH"] ?? env2["Path"] ?? "").split(";")) {
87671
+ if (!dir.trim()) continue;
87672
+ const path2 = join39(dir.trim().replace(/^"|"$/g, ""), "codex.exe");
87673
+ if (fileTime(path2) !== null) return path2;
87674
+ }
87675
+ if (!env2["LOCALAPPDATA"]) return "codex";
87676
+ const root = join39(env2["LOCALAPPDATA"], "OpenAI", "Codex", "bin");
87677
+ try {
87678
+ const candidates = readdirSync12(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join39(root, entry.name, "codex.exe")).map((path2) => ({ path: path2, mtime: fileTime(path2) })).filter((candidate) => candidate.mtime !== null).sort((a, b) => b.mtime - a.mtime || a.path.localeCompare(b.path));
87679
+ return candidates[0]?.path ?? "codex";
87680
+ } catch {
87681
+ return "codex";
87682
+ }
87683
+ }
87684
+
87685
+ // src/doctor.ts
87639
87686
  function installedHarnessVersion(bin) {
87640
87687
  try {
87641
- const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 3e3 });
87688
+ const r = spawnSync(bin === "codex" ? resolveCodexExecutable() : bin, ["--version"], { encoding: "utf8", timeout: 3e3, windowsHide: true });
87642
87689
  if (r.status !== 0 && !r.stdout) return null;
87643
87690
  const m = /(\d+\.\d+\.\d+)/.exec(`${r.stdout ?? ""}${r.stderr ?? ""}`);
87644
87691
  return m ? m[1] : null;
@@ -87745,9 +87792,112 @@ async function diagnoseInstallation(cfg, dependencies) {
87745
87792
  // src/codex-doctor.ts
87746
87793
  import { spawn as spawn4 } from "node:child_process";
87747
87794
  import { createInterface } from "node:readline";
87795
+
87796
+ // src/codex-hooks.ts
87797
+ import { existsSync as existsSync33, mkdirSync as mkdirSync9, readFileSync as readFileSync34, writeFileSync as writeFileSync25 } from "node:fs";
87798
+ import { homedir as homedir7 } from "node:os";
87799
+ import { join as join40 } from "node:path";
87800
+
87801
+ // src/hook-commands.ts
87802
+ function hookCurlCommand(port, path2 = "/api/hook") {
87803
+ const url2 = `http://127.0.0.1:${port}${path2}`;
87804
+ return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 3 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} >NUL 2>NUL || exit /b 0"` : `curl -s --connect-timeout 1 --max-time 3 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} >/dev/null 2>&1 || true`;
87805
+ }
87806
+ function hookRelayCommand(port, path2) {
87807
+ const url2 = `http://127.0.0.1:${port}${path2}`;
87808
+ return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
87809
+ }
87810
+ var CODEX_HOOKS_BEGIN = `# >>> errata hooks (errata-managed)`;
87811
+ var CODEX_HOOKS_END = `# <<< errata hooks`;
87812
+ function buildCodexHooksToml(port, options) {
87813
+ const platform = options.platform ?? process.platform;
87814
+ const quote = (value) => {
87815
+ if (platform === "win32") {
87816
+ if (/["%\r\n]/.test(value)) throw new Error("Unsupported character in Codex hook path");
87817
+ return '"' + value + '"';
87818
+ }
87819
+ return "'" + value.replace(/'/g, "'\\''") + "'";
87820
+ };
87821
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid hook port");
87822
+ const command = quote(options.nodePath) + " " + quote(options.relayPath) + " " + port;
87823
+ const handlers = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"].map(
87824
+ (event) => "[[hooks." + event + "]]\n" + (["UserPromptSubmit", "Stop"].includes(event) ? "" : 'matcher = ".*"\n') + "\n[[hooks." + event + '.hooks]]\ntype = "command"\ncommand = ' + JSON.stringify(command) + "\ntimeout = 10\nadditionalContextLimit = 5000\n"
87825
+ );
87826
+ return CODEX_HOOKS_BEGIN + "\n" + handlers.join("\n") + CODEX_HOOKS_END + "\n";
87827
+ }
87828
+
87829
+ // src/codex-hook-relay.ts
87830
+ var CODEX_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks codex
87831
+ import { readFileSync } from 'node:fs';
87832
+
87833
+ try {
87834
+ const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
87835
+ const port = Number(process.argv[2]);
87836
+ const event = payload.hook_event_name;
87837
+ if (!Number.isInteger(port) || port < 1 || port > 65535) process.exit(0);
87838
+ const contexts = [];
87839
+ const post = async (endpoint, relay = false) => {
87840
+ try {
87841
+ const response = await fetch('http://127.0.0.1:' + port + '/api/' + endpoint, {
87842
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
87843
+ body: JSON.stringify(payload), signal: AbortSignal.timeout(2000),
87844
+ });
87845
+ if (!response.ok || !relay) return;
87846
+ const result = await response.json();
87847
+ const context = result.hookSpecificOutput?.additionalContext;
87848
+ if (typeof context === 'string' && context.trim()) contexts.push(context);
87849
+ } catch { /* An unavailable daemon must never block the agent. */ }
87850
+ };
87851
+ if (event === 'PreToolUse' || event === 'PostToolUse') await post('hook', true);
87852
+ // Stop sees the final assistant message, including turns with no tools.
87853
+ if (event === 'PostToolUse' || event === 'Stop') await post('turn');
87854
+ if (['SessionStart', 'UserPromptSubmit', 'PostToolUse'].includes(event)) {
87855
+ // Run after capture, so changes already rendered by the daemon arrive now.
87856
+ // Background extraction finishing later arrives at the next tool boundary.
87857
+ await post('context-inject', true);
87858
+ }
87859
+ if (contexts.length) {
87860
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: {
87861
+ hookEventName: event, additionalContext: contexts.join('\n\n'),
87862
+ } }));
87863
+ }
87864
+ } catch { /* Invalid input is also fail-open. */ }
87865
+ `;
87866
+
87867
+ // src/codex-hooks.ts
87868
+ 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.";
87869
+ function hasCodexConfig(root, codexHome = process.env["CODEX_HOME"] || join40(homedir7(), ".codex")) {
87870
+ return existsSync33(join40(root, ".codex")) || existsSync33(codexHome);
87871
+ }
87872
+ function replaceCodexHooks(existing, block) {
87873
+ const begin = existing.indexOf(CODEX_HOOKS_BEGIN);
87874
+ const end = existing.indexOf(CODEX_HOOKS_END);
87875
+ if (begin >= 0 || end >= 0) {
87876
+ if (begin < 0 || end < begin || existing.indexOf(CODEX_HOOKS_BEGIN, begin + CODEX_HOOKS_BEGIN.length) >= 0) {
87877
+ throw new Error(
87878
+ "Incomplete or duplicate errata hooks markers in .codex/config.toml; repair them before reinstalling."
87879
+ );
87880
+ }
87881
+ return existing.slice(0, begin) + block.trimEnd() + existing.slice(end + CODEX_HOOKS_END.length);
87882
+ }
87883
+ return existing.trimEnd() + (existing.trim() ? "\n\n" : "") + block;
87884
+ }
87885
+ function writeCodexHooks(root, port, nodePath = process.execPath) {
87886
+ const dir = join40(root, ".codex");
87887
+ const file2 = join40(dir, "config.toml");
87888
+ const relay = join40(dir, "hooks", "errata-relay.mjs");
87889
+ const block = buildCodexHooksToml(port, { nodePath, relayPath: relay });
87890
+ const final = replaceCodexHooks(existsSync33(file2) ? readFileSync34(file2, "utf8") : "", block);
87891
+ mkdirSync9(join40(dir, "hooks"), { recursive: true });
87892
+ writeFileSync25(relay, CODEX_RELAY_SOURCE, "utf8");
87893
+ writeFileSync25(file2, final, "utf8");
87894
+ return file2;
87895
+ }
87896
+
87897
+ // src/codex-doctor.ts
87748
87898
  function readCodexHookState(cwd, options = {}) {
87749
87899
  return new Promise((resolve7, reject) => {
87750
- const child = spawn4(options.bin ?? "codex", options.args ?? ["app-server", "--stdio"], {
87900
+ const child = spawn4(options.bin ?? resolveCodexExecutable(), options.args ?? ["app-server", "--stdio"], {
87751
87901
  cwd,
87752
87902
  windowsHide: true,
87753
87903
  stdio: ["pipe", "pipe", "pipe"]
@@ -87770,7 +87920,7 @@ function readCodexHookState(cwd, options = {}) {
87770
87920
  const send = (id, method, params) => {
87771
87921
  child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
87772
87922
  };
87773
- child.on("error", () => finish(new Error("Codex could not be started for hook inspection")));
87923
+ child.on("error", () => finish(new Error(`Codex could not be started for hook inspection. ${CODEX_HOOK_REVIEW_HELP}`)));
87774
87924
  child.on("exit", () => finish(new Error("Codex exited before returning its hook state")));
87775
87925
  child.stdin.on("error", () => finish(new Error("Codex hook inspection connection closed")));
87776
87926
  child.stderr.resume();
@@ -87787,7 +87937,7 @@ function readCodexHookState(cwd, options = {}) {
87787
87937
  return;
87788
87938
  }
87789
87939
  if (![1, 2, 3].includes(message2.id)) return;
87790
- if (message2.error) return finish(new Error("Codex does not support hook inspection; update Codex and review /hooks"));
87940
+ if (message2.error) return finish(new Error(`Codex does not support hook inspection. ${CODEX_HOOK_REVIEW_HELP}`));
87791
87941
  if (message2.id === 1) {
87792
87942
  child.stdin.write(JSON.stringify({ method: "initialized" }) + "\n");
87793
87943
  send(2, "hooks/list", { cwds: [cwd] });
@@ -87810,7 +87960,7 @@ function readCodexHookState(cwd, options = {}) {
87810
87960
  }
87811
87961
  function assessCodexHooks(state) {
87812
87962
  const check2 = (status, detail) => [{ id: "codex_hooks", status, detail }];
87813
- if (state.errors?.length) return check2("fail", "Codex could not load hook configuration; inspect /hooks for errors");
87963
+ if (state.errors?.length) return check2("fail", `Codex could not load hook configuration. ${CODEX_HOOK_REVIEW_HELP}`);
87814
87964
  if ((state.features?.hooks ?? state.features?.codex_hooks) === false) {
87815
87965
  return check2("fail", "Codex hooks are explicitly disabled; left unchanged");
87816
87966
  }
@@ -87819,9 +87969,9 @@ function assessCodexHooks(state) {
87819
87969
  const missing = required2.filter((event) => !hooks.some((h) => h.eventName === event));
87820
87970
  const checks = [];
87821
87971
  if (missing.length) checks.push(...check2("fail", `Errata hooks missing or outdated in this checkout (${missing.join(", ")}); run errata install-hooks codex here`));
87822
- if (hooks.some((h) => !h.enabled)) checks.push(...check2("fail", "Errata hooks are disabled in Codex; review /hooks; left unchanged"));
87972
+ if (hooks.some((h) => !h.enabled)) checks.push(...check2("fail", `Errata hooks are disabled in Codex; left unchanged. ${CODEX_HOOK_REVIEW_HELP}`));
87823
87973
  if (hooks.some((h) => !["trusted", "managed"].includes(h.trustStatus))) {
87824
- checks.push(...check2("fail", "Errata hooks need review/trust in Codex /hooks before they can run; project trust is separate"));
87974
+ checks.push(...check2("fail", `Errata hooks need review/trust before they can run; project trust is separate. ${CODEX_HOOK_REVIEW_HELP}`));
87825
87975
  }
87826
87976
  return checks.length ? checks : check2("pass", "Codex discovers enabled, trusted Errata hooks; verify live context and capture in a session");
87827
87977
  }
@@ -87903,106 +88053,6 @@ function consolidationGapMs(lastPassMs, policy) {
87903
88053
  return Math.max(policy.baseFloorMs, lastPassMs * policy.dutyFactor);
87904
88054
  }
87905
88055
 
87906
- // src/hook-commands.ts
87907
- function hookCurlCommand(port, path2 = "/api/hook") {
87908
- const url2 = `http://127.0.0.1:${port}${path2}`;
87909
- return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 3 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} >NUL 2>NUL || exit /b 0"` : `curl -s --connect-timeout 1 --max-time 3 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} >/dev/null 2>&1 || true`;
87910
- }
87911
- function hookRelayCommand(port, path2) {
87912
- const url2 = `http://127.0.0.1:${port}${path2}`;
87913
- return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
87914
- }
87915
- var CODEX_HOOKS_BEGIN = `# >>> errata hooks (errata-managed)`;
87916
- var CODEX_HOOKS_END = `# <<< errata hooks`;
87917
- function buildCodexHooksToml(port, options) {
87918
- const platform = options.platform ?? process.platform;
87919
- const quote = (value) => {
87920
- if (platform === "win32") {
87921
- if (/["%\r\n]/.test(value)) throw new Error("Unsupported character in Codex hook path");
87922
- return '"' + value + '"';
87923
- }
87924
- return "'" + value.replace(/'/g, "'\\''") + "'";
87925
- };
87926
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid hook port");
87927
- const command = quote(options.nodePath) + " " + quote(options.relayPath) + " " + port;
87928
- const handlers = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"].map(
87929
- (event) => "[[hooks." + event + "]]\n" + (["UserPromptSubmit", "Stop"].includes(event) ? "" : 'matcher = ".*"\n') + "\n[[hooks." + event + '.hooks]]\ntype = "command"\ncommand = ' + JSON.stringify(command) + "\ntimeout = 10\nadditionalContextLimit = 5000\n"
87930
- );
87931
- return CODEX_HOOKS_BEGIN + "\n" + handlers.join("\n") + CODEX_HOOKS_END + "\n";
87932
- }
87933
-
87934
- // src/codex-hooks.ts
87935
- import { existsSync as existsSync33, mkdirSync as mkdirSync9, readFileSync as readFileSync34, writeFileSync as writeFileSync25 } from "node:fs";
87936
- import { homedir as homedir7 } from "node:os";
87937
- import { join as join39 } from "node:path";
87938
-
87939
- // src/codex-hook-relay.ts
87940
- var CODEX_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks codex
87941
- import { readFileSync } from 'node:fs';
87942
-
87943
- try {
87944
- const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
87945
- const port = Number(process.argv[2]);
87946
- const event = payload.hook_event_name;
87947
- if (!Number.isInteger(port) || port < 1 || port > 65535) process.exit(0);
87948
- const contexts = [];
87949
- const post = async (endpoint, relay = false) => {
87950
- try {
87951
- const response = await fetch('http://127.0.0.1:' + port + '/api/' + endpoint, {
87952
- method: 'POST', headers: { 'Content-Type': 'application/json' },
87953
- body: JSON.stringify(payload), signal: AbortSignal.timeout(2000),
87954
- });
87955
- if (!response.ok || !relay) return;
87956
- const result = await response.json();
87957
- const context = result.hookSpecificOutput?.additionalContext;
87958
- if (typeof context === 'string' && context.trim()) contexts.push(context);
87959
- } catch { /* An unavailable daemon must never block the agent. */ }
87960
- };
87961
- if (event === 'PreToolUse' || event === 'PostToolUse') await post('hook', true);
87962
- // Stop sees the final assistant message, including turns with no tools.
87963
- if (event === 'PostToolUse' || event === 'Stop') await post('turn');
87964
- if (['SessionStart', 'UserPromptSubmit', 'PostToolUse'].includes(event)) {
87965
- // Run after capture, so changes already rendered by the daemon arrive now.
87966
- // Background extraction finishing later arrives at the next tool boundary.
87967
- await post('context-inject', true);
87968
- }
87969
- if (contexts.length) {
87970
- process.stdout.write(JSON.stringify({ hookSpecificOutput: {
87971
- hookEventName: event, additionalContext: contexts.join('\n\n'),
87972
- } }));
87973
- }
87974
- } catch { /* Invalid input is also fail-open. */ }
87975
- `;
87976
-
87977
- // src/codex-hooks.ts
87978
- function hasCodexConfig(root, codexHome = process.env["CODEX_HOME"] || join39(homedir7(), ".codex")) {
87979
- return existsSync33(join39(root, ".codex")) || existsSync33(codexHome);
87980
- }
87981
- function replaceCodexHooks(existing, block) {
87982
- const begin = existing.indexOf(CODEX_HOOKS_BEGIN);
87983
- const end = existing.indexOf(CODEX_HOOKS_END);
87984
- if (begin >= 0 || end >= 0) {
87985
- if (begin < 0 || end < begin || existing.indexOf(CODEX_HOOKS_BEGIN, begin + CODEX_HOOKS_BEGIN.length) >= 0) {
87986
- throw new Error(
87987
- "Incomplete or duplicate errata hooks markers in .codex/config.toml; repair them before reinstalling."
87988
- );
87989
- }
87990
- return existing.slice(0, begin) + block.trimEnd() + existing.slice(end + CODEX_HOOKS_END.length);
87991
- }
87992
- return existing.trimEnd() + (existing.trim() ? "\n\n" : "") + block;
87993
- }
87994
- function writeCodexHooks(root, port, nodePath = process.execPath) {
87995
- const dir = join39(root, ".codex");
87996
- const file2 = join39(dir, "config.toml");
87997
- const relay = join39(dir, "hooks", "errata-relay.mjs");
87998
- const block = buildCodexHooksToml(port, { nodePath, relayPath: relay });
87999
- const final = replaceCodexHooks(existsSync33(file2) ? readFileSync34(file2, "utf8") : "", block);
88000
- mkdirSync9(join39(dir, "hooks"), { recursive: true });
88001
- writeFileSync25(relay, CODEX_RELAY_SOURCE, "utf8");
88002
- writeFileSync25(file2, final, "utf8");
88003
- return file2;
88004
- }
88005
-
88006
88056
  // src/opencode-plugin.ts
88007
88057
  var OPENCODE_PLUGIN_MARKER = "// @inerrata opencode plugin \u2014 errata-managed (errata install-hooks opencode)";
88008
88058
  var OPENCODE_D1_CONTRACT = "Behavioral contract: (1) On ANY error, call search() BEFORE debugging \u2014 costs ~400 tokens vs 5,000\u201350,000 cold. (2) After solving non-trivial problems, contribute back: if your harness emits typed nodes (canonicalId + label + attrs), use ingest \u2014 the typed door; otherwise call contribute(), the prose fallback. (3) Before web search, try search() first \u2014 graph is 2\u201315\xD7 cheaper. (4) At session end, review what you solved and contribute anything novel.";
@@ -88409,13 +88459,13 @@ async function cmdInit() {
88409
88459
  console.log("");
88410
88460
  console.log("installing harness hooks...");
88411
88461
  const { existsSync: existsSync35 } = await import("node:fs");
88412
- const { join: join41 } = await import("node:path");
88462
+ const { join: join42 } = await import("node:path");
88413
88463
  try {
88414
88464
  await installClaudeHooks(port);
88415
88465
  } catch (err2) {
88416
88466
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
88417
88467
  }
88418
- if (existsSync35(join41(ROOT, ".cursor"))) {
88468
+ if (existsSync35(join42(ROOT, ".cursor"))) {
88419
88469
  try {
88420
88470
  await installCursorMcpConfig();
88421
88471
  } catch (err2) {
@@ -90243,8 +90293,8 @@ async function cmdReport(args2) {
90243
90293
  const outDir = workspacePaths(ROOT).configDir;
90244
90294
  mkdirSync10(outDir, { recursive: true });
90245
90295
  const files = renderReport2(data, { includeFutureVerbs });
90246
- for (const f of files) writeFileSync26(join40(outDir, f.name), f.html, "utf8");
90247
- const indexPath = join40(outDir, "report.html");
90296
+ for (const f of files) writeFileSync26(join41(outDir, f.name), f.html, "utf8");
90297
+ const indexPath = join41(outDir, "report.html");
90248
90298
  console.log(`report \u2192 ${indexPath}`);
90249
90299
  console.log(
90250
90300
  ` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`
@@ -90299,7 +90349,7 @@ function spawnDaemonDetached(options = {}) {
90299
90349
  try {
90300
90350
  const logPath = daemonLogPath();
90301
90351
  try {
90302
- if (statSync8(logPath).size > 5 * 1024 * 1024) renameSync4(logPath, `${logPath}.1`);
90352
+ if (statSync9(logPath).size > 5 * 1024 * 1024) renameSync4(logPath, `${logPath}.1`);
90303
90353
  } catch {
90304
90354
  }
90305
90355
  out2 = openSync2(logPath, "a");
@@ -90365,10 +90415,10 @@ async function cmdInstallHooks(args2) {
90365
90415
  }
90366
90416
  async function installOpencodePlugin(port) {
90367
90417
  const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync26 } = await import("node:fs");
90368
- const { join: join41 } = await import("node:path");
90369
- const dir = join41(ROOT, ".opencode", "plugin");
90418
+ const { join: join42 } = await import("node:path");
90419
+ const dir = join42(ROOT, ".opencode", "plugin");
90370
90420
  if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
90371
- const file2 = join41(dir, "inerrata.js");
90421
+ const file2 = join42(dir, "inerrata.js");
90372
90422
  if (existsSync35(file2)) {
90373
90423
  const head2 = readFileSync36(file2, "utf8").split("\n")[0] ?? "";
90374
90424
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
@@ -90377,7 +90427,7 @@ async function installOpencodePlugin(port) {
90377
90427
  }
90378
90428
  }
90379
90429
  writeFileSync26(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
90380
- const cfgFile = join41(ROOT, "opencode.json");
90430
+ const cfgFile = join42(ROOT, "opencode.json");
90381
90431
  let cfg = { $schema: "https://opencode.ai/config.json" };
90382
90432
  if (existsSync35(cfgFile)) {
90383
90433
  try {
@@ -90413,10 +90463,10 @@ function errataMcpInvocation() {
90413
90463
  }
90414
90464
  async function installClaudeHooks(port) {
90415
90465
  const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync26 } = await import("node:fs");
90416
- const { join: join41 } = await import("node:path");
90417
- const dir = join41(ROOT, ".claude");
90466
+ const { join: join42 } = await import("node:path");
90467
+ const dir = join42(ROOT, ".claude");
90418
90468
  if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
90419
- const file2 = join41(dir, "settings.json");
90469
+ const file2 = join42(dir, "settings.json");
90420
90470
  let settings = {};
90421
90471
  if (existsSync35(file2)) {
90422
90472
  try {
@@ -90469,7 +90519,7 @@ async function installClaudeHooks(port) {
90469
90519
  writeFileSync26(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
90470
90520
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
90471
90521
  await installClaudeMcpConfig();
90472
- const claudeMd = join41(ROOT, "CLAUDE.md");
90522
+ const claudeMd = join42(ROOT, "CLAUDE.md");
90473
90523
  const recall = writeManagedBlock(claudeMd, { body: RECALL_FIRST_BLOCK });
90474
90524
  if (recall.kind === "collision") {
90475
90525
  console.warn(
@@ -90482,8 +90532,8 @@ async function installClaudeHooks(port) {
90482
90532
  }
90483
90533
  async function installClaudeMcpConfig() {
90484
90534
  const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync26 } = await import("node:fs");
90485
- const { join: join41, dirname: dirname13 } = await import("node:path");
90486
- const file2 = join41(ROOT, ".mcp.json");
90535
+ const { join: join42, dirname: dirname13 } = await import("node:path");
90536
+ const file2 = join42(ROOT, ".mcp.json");
90487
90537
  const dir = dirname13(file2);
90488
90538
  if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
90489
90539
  let cfg = {};
@@ -90504,10 +90554,10 @@ async function installClaudeMcpConfig() {
90504
90554
  }
90505
90555
  async function installCursorMcpConfig() {
90506
90556
  const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync26 } = await import("node:fs");
90507
- const { join: join41 } = await import("node:path");
90508
- const dir = join41(ROOT, ".cursor");
90557
+ const { join: join42 } = await import("node:path");
90558
+ const dir = join42(ROOT, ".cursor");
90509
90559
  if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
90510
- const file2 = join41(dir, "mcp.json");
90560
+ const file2 = join42(dir, "mcp.json");
90511
90561
  let cfg = {};
90512
90562
  if (existsSync35(file2)) {
90513
90563
  try {
@@ -90530,10 +90580,21 @@ async function installCodexHooks(port) {
90530
90580
  const gitRoot = nearestGitRoot(process.cwd());
90531
90581
  const hookRoot = gitRoot && mainCheckoutOfWorktree(gitRoot) ? gitRoot : ROOT;
90532
90582
  const file2 = writeCodexHooks(hookRoot, port);
90583
+ const instructions = writeManagedBlock(join41(hookRoot, "AGENTS.md"), {
90584
+ body: AGENTS_POINTER_BODY,
90585
+ stable: true
90586
+ });
90533
90587
  console.log(`installed Codex hooks \u2192 ${file2}`);
90588
+ if (instructions.kind === "collision") {
90589
+ console.warn(" AGENTS.md's Errata block was edited; preserved it. Add an explicit instruction to read .errata/context.md each turn.");
90590
+ } else {
90591
+ console.log(" AGENTS.md now tells Codex to read live context at the start of each turn.");
90592
+ }
90534
90593
  console.log(" Context loads on SessionStart/UserPromptSubmit and refreshes after tools when changed.");
90535
90594
  console.log(" Tool and final-reply capture are configured; hooks must be trusted before they run.");
90536
- console.log(" Restart/resume Codex to reload config, then review and trust the hooks with /hooks.");
90595
+ console.log(` ${CODEX_HOOK_REVIEW_HELP}`);
90596
+ console.log(" Use Reload hooks in that screen after installation. Restarting alone does not grant trust.");
90597
+ console.log(" No standalone Codex CLI is required for desktop setup.");
90537
90598
  console.log(" Run `errata doctor` here to check hook discovery and trust; installation alone is not activation.");
90538
90599
  console.log(" Respect an explicit [features] hooks = false setting (older builds use codex_hooks).");
90539
90600
  console.log(" Older Codex versions may not support every event; update if hooks do not fire.");
@@ -90923,7 +90984,7 @@ async function cmdDash(args2) {
90923
90984
  await yieldToLoop2();
90924
90985
  try {
90925
90986
  const items = selectDurableMemory(handle2.sharedStore, r.engine.profile);
90926
- const res = bleedRules(join40(r.root, ".claude", "rules"), items);
90987
+ const res = bleedRules(join41(r.root, ".claude", "rules"), items);
90927
90988
  if (res.written || res.pruned) {
90928
90989
  console.log(
90929
90990
  `[rules:${r.entry.name}] ${res.written} collective principle(s) \u2192 .claude/rules` + (res.pruned ? `, ${res.pruned} pruned` : "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.2759",
3
+ "version": "2.0.6",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -27255,6 +27255,11 @@ var AGENTS_POINTER_BODY = [
27255
27255
  "Live agent context for this checkout \u2014 priors, open problems, the capture",
27256
27256
  "protocol \u2014 is maintained by the errata daemon at `.errata/context.md`.",
27257
27257
  "",
27258
+ "At the beginning of each task and each new user turn, read that file before",
27259
+ "substantive work. Use the current file in preference to an earlier snapshot.",
27260
+ "If it is absent, continue normally. In Codex, an `@` mention alone does not",
27261
+ "load the file; read it explicitly.",
27262
+ "",
27258
27263
  "It is gitignored ON PURPOSE: it is this machine's runtime state, not repo",
27259
27264
  "content. Committing it would prime your teammates with your graph.",
27260
27265
  "",