@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 +6 -3
- package/errata.mjs +194 -133
- package/package.json +1 -1
- package/pass-worker.mjs +5 -0
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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://
|
|
26305
|
-
DEFAULT_UPDATE_CHANNEL =
|
|
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
|
|
71191
|
-
import { join as
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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(
|
|
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(
|
|
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",
|
|
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",
|
|
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",
|
|
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:
|
|
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(
|
|
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(
|
|
90247
|
-
const indexPath =
|
|
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 (
|
|
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:
|
|
90369
|
-
const dir =
|
|
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 =
|
|
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 =
|
|
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:
|
|
90417
|
-
const dir =
|
|
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 =
|
|
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 =
|
|
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:
|
|
90486
|
-
const file2 =
|
|
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:
|
|
90508
|
-
const dir =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
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
|
"",
|