@inerrata-corporation/errata 2.0.23-dev.4864 → 2.0.23-dev.4900
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/errata.mjs
CHANGED
|
@@ -58885,7 +58885,14 @@ function buildWebUi(deps) {
|
|
|
58885
58885
|
}
|
|
58886
58886
|
}
|
|
58887
58887
|
}
|
|
58888
|
-
|
|
58888
|
+
let taskPriors = null;
|
|
58889
|
+
if ((event === "PostToolUse" || event === "PreToolUse") && audience.subagent === null && audience.sessionId && deps.takeTaskPriors) {
|
|
58890
|
+
try {
|
|
58891
|
+
taskPriors = deps.takeTaskPriors(audience.sessionId);
|
|
58892
|
+
} catch {
|
|
58893
|
+
}
|
|
58894
|
+
}
|
|
58895
|
+
if ((event === "PostToolUse" || event === "PreToolUse") && (recall || refreshed || taskPriors)) {
|
|
58889
58896
|
const showCard = recallCardEnabled();
|
|
58890
58897
|
const { sessionId, subagent } = audience;
|
|
58891
58898
|
const raw2 = recall?.card ?? refreshed?.card ?? null;
|
|
@@ -58895,7 +58902,7 @@ function buildWebUi(deps) {
|
|
|
58895
58902
|
else rememberCard(sessionId, card);
|
|
58896
58903
|
}
|
|
58897
58904
|
const line = showCard && recall && card ? renderRecallLine(card) : null;
|
|
58898
|
-
const additionalContext = [recall?.context, refreshed?.additionalContext].filter(Boolean).join("\n\n");
|
|
58905
|
+
const additionalContext = [recall?.context, refreshed?.additionalContext, taskPriors].filter(Boolean).join("\n\n");
|
|
58899
58906
|
return c.json({
|
|
58900
58907
|
...line ? { systemMessage: line } : {},
|
|
58901
58908
|
hookSpecificOutput: { hookEventName: hookEvent, additionalContext }
|
|
@@ -59004,6 +59011,15 @@ ${delta}${cue}`;
|
|
|
59004
59011
|
const { sessionId: sid, contextKey: contextKey2, subagent, agentId } = hookAudience(body2);
|
|
59005
59012
|
noteSessionFrom(c.req.header(ORIGIN_HEADER), body2);
|
|
59006
59013
|
const hookEvent = String(body2["hook_event_name"] ?? "UserPromptSubmit");
|
|
59014
|
+
if (hookEvent === "UserPromptSubmit" && subagent === null && sid && typeof body2["prompt"] === "string" && deps.onTaskPrompt) {
|
|
59015
|
+
const prompt = body2["prompt"];
|
|
59016
|
+
setImmediate(() => {
|
|
59017
|
+
try {
|
|
59018
|
+
deps.onTaskPrompt(sid, prompt);
|
|
59019
|
+
} catch {
|
|
59020
|
+
}
|
|
59021
|
+
});
|
|
59022
|
+
}
|
|
59007
59023
|
const atPrompt = hookEvent === "UserPromptSubmit" || hookEvent === "SessionStart" || hookEvent === "SubagentStart";
|
|
59008
59024
|
if (sid && recallCardEnabled() && atPrompt) {
|
|
59009
59025
|
setImmediate(() => {
|
|
@@ -78601,7 +78617,7 @@ var init_report_render = __esm({
|
|
|
78601
78617
|
|
|
78602
78618
|
// src/cli.ts
|
|
78603
78619
|
init_src6();
|
|
78604
|
-
import { closeSync as closeSync3, existsSync as existsSync41, openSync as openSync3, readFileSync as readFileSync49, renameSync as renameSync10, statSync as statSync17 } from "node:fs";
|
|
78620
|
+
import { closeSync as closeSync3, existsSync as existsSync41, openSync as openSync3, readFileSync as readFileSync49, renameSync as renameSync10, statSync as statSync17, writeFileSync as writeFileSync34 } from "node:fs";
|
|
78605
78621
|
import { join as join57, resolve as pathResolve2 } from "node:path";
|
|
78606
78622
|
import { spawn as spawn5 } from "node:child_process";
|
|
78607
78623
|
|
|
@@ -87529,6 +87545,100 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
|
|
|
87529
87545
|
};
|
|
87530
87546
|
}
|
|
87531
87547
|
|
|
87548
|
+
// src/engine.ts
|
|
87549
|
+
init_mcp();
|
|
87550
|
+
|
|
87551
|
+
// src/task-priors.ts
|
|
87552
|
+
function taskPriorsEnabled(env2 = process.env) {
|
|
87553
|
+
return /^(1|on|true|yes)$/i.test((env2["ERRATA_TASK_PRIORS"] ?? "").trim());
|
|
87554
|
+
}
|
|
87555
|
+
var TASK_PRIORS_FLOOR = 0.45;
|
|
87556
|
+
var TASK_PRIORS_BAND = 0.15;
|
|
87557
|
+
var TASK_PRIORS_MAX = 5;
|
|
87558
|
+
var TASK_QUERY_MAX_CHARS = 600;
|
|
87559
|
+
var KNOWLEDGE = /* @__PURE__ */ new Set(["Problem", "RootCause", "Solution", "Pattern", "Comment", "Claim", "Vulnerability"]);
|
|
87560
|
+
var clip2 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
87561
|
+
var oneLine2 = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
|
|
87562
|
+
function pickTaskHits(result) {
|
|
87563
|
+
const rows = Array.isArray(result) ? result : result?.results;
|
|
87564
|
+
if (!Array.isArray(rows)) return [];
|
|
87565
|
+
const relevanceOf = (r) => Number(r["why"]?.relevance ?? 0);
|
|
87566
|
+
const knowledge = rows.filter((r) => KNOWLEDGE.has(String(r["label"] ?? "")));
|
|
87567
|
+
const best = Math.max(0, ...knowledge.map(relevanceOf));
|
|
87568
|
+
const cut = Math.max(TASK_PRIORS_FLOOR, best - TASK_PRIORS_BAND);
|
|
87569
|
+
const hits = [];
|
|
87570
|
+
for (const r of knowledge) {
|
|
87571
|
+
const label = String(r["label"]);
|
|
87572
|
+
const relevance = relevanceOf(r);
|
|
87573
|
+
if (!(relevance >= cut)) continue;
|
|
87574
|
+
const chain2 = r["chain"];
|
|
87575
|
+
hits.push({
|
|
87576
|
+
id: String(r["cite"] ?? r["id"]),
|
|
87577
|
+
label,
|
|
87578
|
+
text: oneLine2(r["name"] ?? r["description"]),
|
|
87579
|
+
relevance,
|
|
87580
|
+
...typeof r["lifecycle"] === "string" ? { lifecycle: r["lifecycle"] } : {},
|
|
87581
|
+
...chain2?.cause?.text ? { cause: oneLine2(chain2.cause.text) } : {},
|
|
87582
|
+
...chain2?.fix?.text ? { fix: oneLine2(chain2.fix.text) } : {}
|
|
87583
|
+
});
|
|
87584
|
+
if (hits.length >= TASK_PRIORS_MAX) break;
|
|
87585
|
+
}
|
|
87586
|
+
return hits;
|
|
87587
|
+
}
|
|
87588
|
+
function renderTaskPriors(query, hits, addendum) {
|
|
87589
|
+
const lines = [
|
|
87590
|
+
addendum ? "### \u{1F3AF} errata \u2014 more for this task, from the Errata Network" : "### \u{1F3AF} errata \u2014 what the graph already knows about this task",
|
|
87591
|
+
addendum ? "_The Network side of the search on your task finished; these weren't in the earlier list._" : `_errata searched its graph for your task ("${clip2(oneLine2(query), 120)}") \u2014 no need to run that search yourself. Check a prior against the code before relying on it, and cite what you use as ([id])._`
|
|
87592
|
+
];
|
|
87593
|
+
for (const h of hits) {
|
|
87594
|
+
lines.push(`- **${h.label}:** ${clip2(h.text, 220)} \`[${h.id}]\`${h.lifecycle ? ` \u2014 ${h.lifecycle}` : ""}`);
|
|
87595
|
+
if (h.cause) lines.push(` - cause: ${clip2(h.cause, 220)}`);
|
|
87596
|
+
if (h.fix) lines.push(` - fix: ${clip2(h.fix, 260)}`);
|
|
87597
|
+
}
|
|
87598
|
+
return lines.join("\n");
|
|
87599
|
+
}
|
|
87600
|
+
function createTaskPriors(search, opts) {
|
|
87601
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
87602
|
+
const maxSessions = opts.maxSessions ?? 200;
|
|
87603
|
+
const phase = async (sessionId, state, generation, network) => {
|
|
87604
|
+
let result;
|
|
87605
|
+
try {
|
|
87606
|
+
result = await search(state.query, { network });
|
|
87607
|
+
} catch {
|
|
87608
|
+
return;
|
|
87609
|
+
}
|
|
87610
|
+
if (sessions.get(sessionId) !== state || state.generation !== generation) return;
|
|
87611
|
+
const fresh = pickTaskHits(result).filter((h) => !state.sent.has(h.id));
|
|
87612
|
+
if (fresh.length === 0) return;
|
|
87613
|
+
for (const h of fresh) state.sent.add(h.id);
|
|
87614
|
+
state.ready.push(renderTaskPriors(state.query, fresh, network && state.sent.size > fresh.length));
|
|
87615
|
+
};
|
|
87616
|
+
return {
|
|
87617
|
+
/** The session was handed a task (its prompt) or restated it (an intent fence). */
|
|
87618
|
+
start(sessionId, text) {
|
|
87619
|
+
const query = oneLine2(text).slice(0, TASK_QUERY_MAX_CHARS);
|
|
87620
|
+
if (!sessionId || query.length < 12) return;
|
|
87621
|
+
const prev = sessions.get(sessionId);
|
|
87622
|
+
if (prev?.query === query) return;
|
|
87623
|
+
const state = { query, generation: (prev?.generation ?? 0) + 1, sent: prev?.sent ?? /* @__PURE__ */ new Set(), ready: prev?.ready ?? [] };
|
|
87624
|
+
sessions.delete(sessionId);
|
|
87625
|
+
sessions.set(sessionId, state);
|
|
87626
|
+
if (sessions.size > maxSessions) sessions.delete(sessions.keys().next().value);
|
|
87627
|
+
const generation = state.generation;
|
|
87628
|
+
void phase(sessionId, state, generation, false).then(() => opts.network() ? phase(sessionId, state, generation, true) : void 0);
|
|
87629
|
+
},
|
|
87630
|
+
/** What is ready for this session's next tool hook, once; null when nothing is. */
|
|
87631
|
+
take(sessionId) {
|
|
87632
|
+
const state = sessions.get(sessionId);
|
|
87633
|
+
if (!state || state.ready.length === 0) return null;
|
|
87634
|
+
return state.ready.splice(0).join("\n\n");
|
|
87635
|
+
},
|
|
87636
|
+
end(sessionId) {
|
|
87637
|
+
sessions.delete(sessionId);
|
|
87638
|
+
}
|
|
87639
|
+
};
|
|
87640
|
+
}
|
|
87641
|
+
|
|
87532
87642
|
// src/working-file-state.ts
|
|
87533
87643
|
function createWorkingFileState() {
|
|
87534
87644
|
const bySession = /* @__PURE__ */ new Map();
|
|
@@ -88321,7 +88431,7 @@ function createWatchBreaker(deps) {
|
|
|
88321
88431
|
}
|
|
88322
88432
|
|
|
88323
88433
|
// src/engine.ts
|
|
88324
|
-
var DAEMON_VERSION = true ? "2.0.23-dev.
|
|
88434
|
+
var DAEMON_VERSION = true ? "2.0.23-dev.4900" : "2.0.0-alpha.0";
|
|
88325
88435
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
88326
88436
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
88327
88437
|
var GIT_OP_MUTE_MS = 4e3;
|
|
@@ -88756,6 +88866,16 @@ function createWorkspaceEngine(opts) {
|
|
|
88756
88866
|
witnessParked: () => witnessQueue.length
|
|
88757
88867
|
});
|
|
88758
88868
|
const intents = createIntentState();
|
|
88869
|
+
const taskPriorsOn = taskPriorsEnabled();
|
|
88870
|
+
const taskPriors = createTaskPriors(
|
|
88871
|
+
(query, { network }) => runTool(
|
|
88872
|
+
"errata.search",
|
|
88873
|
+
{ query, limit: 10 },
|
|
88874
|
+
store,
|
|
88875
|
+
network ? { cloud, ...opts.generalizeSoft ? { generalizeSoft: opts.generalizeSoft } : {} } : {}
|
|
88876
|
+
),
|
|
88877
|
+
{ network: () => !opts.skipContextWriter && (opts.syncConsent ?? consentNow().sync) && typeof cloud.search === "function" }
|
|
88878
|
+
);
|
|
88759
88879
|
const subagents = createSubagentState();
|
|
88760
88880
|
let subagentBase = null;
|
|
88761
88881
|
let subagentBaseVersion = 0;
|
|
@@ -88800,7 +88920,7 @@ function createWorkspaceEngine(opts) {
|
|
|
88800
88920
|
}
|
|
88801
88921
|
}
|
|
88802
88922
|
};
|
|
88803
|
-
const
|
|
88923
|
+
const clip3 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
88804
88924
|
const pickReviewItems = () => {
|
|
88805
88925
|
const shared = opts.sharedStore;
|
|
88806
88926
|
if (!shared) return [];
|
|
@@ -88811,12 +88931,12 @@ function createWorkspaceEngine(opts) {
|
|
|
88811
88931
|
return [{
|
|
88812
88932
|
kind: "abstraction",
|
|
88813
88933
|
candidate: small.candidate,
|
|
88814
|
-
members: small.members.map((m) => ({ id: m.id, description:
|
|
88934
|
+
members: small.members.map((m) => ({ id: m.id, description: clip3(m.description, 110) }))
|
|
88815
88935
|
}];
|
|
88816
88936
|
}
|
|
88817
88937
|
}
|
|
88818
88938
|
const [d] = pendingDiscriminators(shared, { limit: 1 });
|
|
88819
|
-
return d ? [{ kind: "discriminator", route: d.route, presenting:
|
|
88939
|
+
return d ? [{ kind: "discriminator", route: d.route, presenting: clip3(d.presenting, 140), cause: clip3(d.cause, 140) }] : [];
|
|
88820
88940
|
} catch {
|
|
88821
88941
|
return [];
|
|
88822
88942
|
}
|
|
@@ -89330,9 +89450,17 @@ function createWorkspaceEngine(opts) {
|
|
|
89330
89450
|
delete scoped.supersessionAsks;
|
|
89331
89451
|
const asks = supersessionAsksFor(sessionId);
|
|
89332
89452
|
if (asks.length > 0) scoped.supersessionAsks = asks;
|
|
89453
|
+
if (taskPriorsOn) {
|
|
89454
|
+
scoped.recentProblems = [];
|
|
89455
|
+
scoped.recentResolved = [];
|
|
89456
|
+
scoped.regressions = [];
|
|
89457
|
+
scoped.recentConstraints = [];
|
|
89458
|
+
scoped.motifs = [];
|
|
89459
|
+
delete scoped.motifPool;
|
|
89460
|
+
}
|
|
89333
89461
|
const pendingUpdate = opts.pendingUpdate?.() ?? null;
|
|
89334
89462
|
const daemonDistress = opts.daemonDistress?.() ?? null;
|
|
89335
|
-
const reviewItems = pickReviewItems();
|
|
89463
|
+
const reviewItems = taskPriorsOn ? [] : pickReviewItems();
|
|
89336
89464
|
const sessionIntent = intent ? statedIntentFor(intent) : null;
|
|
89337
89465
|
const sessionMotifSignal = motifSignalFor(scoped, sessionIntent);
|
|
89338
89466
|
const { body: body2, snapshot } = assembleAgentContext({
|
|
@@ -89341,7 +89469,7 @@ function createWorkspaceEngine(opts) {
|
|
|
89341
89469
|
...atCommit ? { atCommit } : {},
|
|
89342
89470
|
reviewCount: pendingCount(paths),
|
|
89343
89471
|
reviewUiUrl: opts.reviewUrl(),
|
|
89344
|
-
remote: remotePriors,
|
|
89472
|
+
remote: taskPriorsOn ? [] : remotePriors,
|
|
89345
89473
|
snapshot: scoped,
|
|
89346
89474
|
...elicit ? { edgeElicitation: { instruction: PRIOR_TAG_INSTRUCTION } } : {},
|
|
89347
89475
|
...pendingUpdate ? { pendingUpdate } : {},
|
|
@@ -89635,7 +89763,10 @@ function createWorkspaceEngine(opts) {
|
|
|
89635
89763
|
const fenceAgent = turn.agentId ?? (sessionId.startsWith("agent-") ? sessionId.slice(6) : void 0);
|
|
89636
89764
|
for (const fence of parseIntentFences(turn.text)) {
|
|
89637
89765
|
if (fenceAgent) subagents.setIntent(fenceAgent, fence.intent, t);
|
|
89638
|
-
else if (intents.set(sessionId, fence.intent, t))
|
|
89766
|
+
else if (intents.set(sessionId, fence.intent, t)) {
|
|
89767
|
+
refreshContextNow();
|
|
89768
|
+
if (taskPriorsOn) taskPriors.start(sessionId, fence.intent);
|
|
89769
|
+
}
|
|
89639
89770
|
}
|
|
89640
89771
|
const plan = harvestInlineTags(store, turn.text, {
|
|
89641
89772
|
sourceId: sessionLastProblem.get(sessionId),
|
|
@@ -90646,6 +90777,7 @@ B: ${b}`,
|
|
|
90646
90777
|
return null;
|
|
90647
90778
|
} : void 0;
|
|
90648
90779
|
const onSessionEnd = (e) => {
|
|
90780
|
+
taskPriors.end(e.sessionId);
|
|
90649
90781
|
if (!designRollup) return;
|
|
90650
90782
|
setImmediate(() => {
|
|
90651
90783
|
const convo = readConversation(e.transcriptPath);
|
|
@@ -90775,6 +90907,10 @@ B: ${b}`,
|
|
|
90775
90907
|
onSubagentStop,
|
|
90776
90908
|
noteSession,
|
|
90777
90909
|
sessionContext,
|
|
90910
|
+
onTaskPrompt: (sessionId, prompt) => {
|
|
90911
|
+
if (taskPriorsOn) taskPriors.start(sessionId, prompt);
|
|
90912
|
+
},
|
|
90913
|
+
takeTaskPriors: (sessionId) => taskPriorsOn ? taskPriors.take(sessionId) : null,
|
|
90778
90914
|
onHook: () => telemetry.count("hooks_fired"),
|
|
90779
90915
|
onToolsObserved: (sessionId, toolNodeIds) => toolRuns.record(sessionId, toolNodeIds),
|
|
90780
90916
|
onKnowledgeShown: (sessionId, shown) => supersessionAsks.noteShown(sessionId, shown),
|
|
@@ -91282,6 +91418,9 @@ async function startDaemon(opts) {
|
|
|
91282
91418
|
onSubagentStop: engine.webHooks.onSubagentStop,
|
|
91283
91419
|
noteSession: engine.webHooks.noteSession,
|
|
91284
91420
|
sessionContext: engine.webHooks.sessionContext,
|
|
91421
|
+
// Task priors: the task, searched in the background; delivered on the next tool hook.
|
|
91422
|
+
onTaskPrompt: engine.webHooks.onTaskPrompt,
|
|
91423
|
+
takeTaskPriors: engine.webHooks.takeTaskPriors,
|
|
91285
91424
|
onHook: engine.webHooks.onHook,
|
|
91286
91425
|
onHookEvent: engine.webHooks.onHookEvent,
|
|
91287
91426
|
onShellEvent: engine.webHooks.onShellEvent,
|
|
@@ -93399,15 +93538,20 @@ async function runInstalledHookRefresh() {
|
|
|
93399
93538
|
child.on("close", (code) => resolve11(code === 0 ? output.trim() : ""));
|
|
93400
93539
|
});
|
|
93401
93540
|
if (!isAbsolute6(root)) return 1;
|
|
93402
|
-
|
|
93403
|
-
|
|
93404
|
-
|
|
93405
|
-
|
|
93406
|
-
|
|
93541
|
+
let worst = 0;
|
|
93542
|
+
for (const harness of ["codex", "claude"]) {
|
|
93543
|
+
const code = await new Promise((resolve11) => {
|
|
93544
|
+
const child = spawn2(process.execPath, [join42(root, PACKAGE_NAME, "errata.mjs"), "install-hooks", harness, "--refresh-existing"], {
|
|
93545
|
+
windowsHide: true,
|
|
93546
|
+
stdio: "inherit",
|
|
93547
|
+
timeout: 3e4
|
|
93548
|
+
});
|
|
93549
|
+
child.on("error", () => resolve11(1));
|
|
93550
|
+
child.on("close", (exit) => resolve11(exit ?? 1));
|
|
93407
93551
|
});
|
|
93408
|
-
|
|
93409
|
-
|
|
93410
|
-
|
|
93552
|
+
worst = Math.max(worst, code);
|
|
93553
|
+
}
|
|
93554
|
+
return worst;
|
|
93411
93555
|
}
|
|
93412
93556
|
|
|
93413
93557
|
// src/multi.ts
|
|
@@ -97001,6 +97145,9 @@ async function startMultiDaemon(opts = {}) {
|
|
|
97001
97145
|
const engine = createWorkspaceEngine({
|
|
97002
97146
|
workspaceRoot: entry.path,
|
|
97003
97147
|
cloud: wsCloud,
|
|
97148
|
+
// The worker's warm lexicon for searches the engine runs itself (task
|
|
97149
|
+
// priors); without it each would build the symbol lexicon inline.
|
|
97150
|
+
...generalizeWorker ? { generalizeSoft: (text) => generalizeWorker.generalize(workspacePaths(entry.path).castalia, text).catch(() => void 0) } : {},
|
|
97004
97151
|
apiKey: opts.apiKey,
|
|
97005
97152
|
reviewUrl: () => `${baseUrl}/ws/${id}/review`,
|
|
97006
97153
|
sharedStore,
|
|
@@ -97026,6 +97173,9 @@ async function startMultiDaemon(opts = {}) {
|
|
|
97026
97173
|
// CAP-session-scope: per-session blocks + harness/fixture quarantine.
|
|
97027
97174
|
noteSession: engine.webHooks.noteSession,
|
|
97028
97175
|
sessionContext: engine.webHooks.sessionContext,
|
|
97176
|
+
// Task priors: the task, searched in the background; delivered on the next tool hook.
|
|
97177
|
+
onTaskPrompt: engine.webHooks.onTaskPrompt,
|
|
97178
|
+
takeTaskPriors: engine.webHooks.takeTaskPriors,
|
|
97029
97179
|
onHook: engine.webHooks.onHook,
|
|
97030
97180
|
onHookEvent: engine.webHooks.onHookEvent,
|
|
97031
97181
|
onShellEvent: engine.webHooks.onShellEvent,
|
|
@@ -99663,6 +99813,50 @@ function installedHookPort(settingsText) {
|
|
|
99663
99813
|
}
|
|
99664
99814
|
return null;
|
|
99665
99815
|
}
|
|
99816
|
+
var ERRATA_HOOK_TAG = "# errata-managed";
|
|
99817
|
+
function claudeErrataHookGroups(port) {
|
|
99818
|
+
const matcher = "Read|Edit|Write|Grep|Glob|NotebookEdit|Bash";
|
|
99819
|
+
const postMatcher = `${matcher}|ToolSearch|mcp__errata__.*`;
|
|
99820
|
+
const preMatcher = `${matcher}|Agent|Task`;
|
|
99821
|
+
const command = (cmd2) => [{ type: "command", command: `${cmd2} ${ERRATA_HOOK_TAG}` }];
|
|
99822
|
+
const toolHook = command(hookRelayCommand(port, "/api/hook"));
|
|
99823
|
+
const turn = command(hookCurlCommand(port, "/api/turn"));
|
|
99824
|
+
const inject = command(hookRelayCommand(port, "/api/context-inject"));
|
|
99825
|
+
return {
|
|
99826
|
+
PreToolUse: { matcher: preMatcher, hooks: toolHook },
|
|
99827
|
+
PostToolUse: { matcher: postMatcher, hooks: toolHook },
|
|
99828
|
+
PostToolUseFailure: { matcher: postMatcher, hooks: toolHook },
|
|
99829
|
+
Stop: { hooks: turn },
|
|
99830
|
+
SubagentStop: { hooks: turn },
|
|
99831
|
+
// The assistant's prose delivered LIVE by the harness: the transcript jsonl
|
|
99832
|
+
// drops mid-turn assistant text (anthropics/claude-code#21751), so this is the
|
|
99833
|
+
// reliable channel for inline tags and /api/turn is the backfill.
|
|
99834
|
+
MessageDisplay: { hooks: command(hookCurlCommand(port, "/api/message")) },
|
|
99835
|
+
// The gated end-of-session roll-up (P3).
|
|
99836
|
+
SessionEnd: { hooks: command(hookCurlCommand(port, "/api/session-end")) },
|
|
99837
|
+
SessionStart: { hooks: inject },
|
|
99838
|
+
UserPromptSubmit: { hooks: inject },
|
|
99839
|
+
SubagentStart: { hooks: inject }
|
|
99840
|
+
};
|
|
99841
|
+
}
|
|
99842
|
+
function applyClaudeErrataHooks(settings, port) {
|
|
99843
|
+
settings.hooks ??= {};
|
|
99844
|
+
for (const [event, group] of Object.entries(claudeErrataHookGroups(port))) {
|
|
99845
|
+
const list = settings.hooks[event] ??= [];
|
|
99846
|
+
for (let i2 = list.length - 1; i2 >= 0; i2--) {
|
|
99847
|
+
if (list[i2].hooks?.some((h) => typeof h.command === "string" && h.command.includes(ERRATA_HOOK_TAG))) list.splice(i2, 1);
|
|
99848
|
+
}
|
|
99849
|
+
list.push(group);
|
|
99850
|
+
}
|
|
99851
|
+
}
|
|
99852
|
+
function refreshClaudeHookSettings(text) {
|
|
99853
|
+
const port = installedHookPort(text);
|
|
99854
|
+
if (port === null) return null;
|
|
99855
|
+
const settings = JSON.parse(text);
|
|
99856
|
+
applyClaudeErrataHooks(settings, port);
|
|
99857
|
+
const next = JSON.stringify(settings, null, 2) + "\n";
|
|
99858
|
+
return next === text.replace(/\r\n/g, "\n") ? null : next;
|
|
99859
|
+
}
|
|
99666
99860
|
|
|
99667
99861
|
// src/codex-hook-relay.ts
|
|
99668
99862
|
var CODEX_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks codex
|
|
@@ -101599,6 +101793,7 @@ async function cmdNotifyTest() {
|
|
|
101599
101793
|
}
|
|
101600
101794
|
async function cmdStart() {
|
|
101601
101795
|
refreshExistingCodexHooks();
|
|
101796
|
+
refreshExistingClaudeHooks();
|
|
101602
101797
|
if (!loadProfile(ROOT)) {
|
|
101603
101798
|
const cfg = loadConfig();
|
|
101604
101799
|
const locator = detectRepoLocator(ROOT);
|
|
@@ -102076,7 +102271,7 @@ async function cmdUpdate(args2) {
|
|
|
102076
102271
|
const code = await runNpmInstall(channel);
|
|
102077
102272
|
if (code === 0) {
|
|
102078
102273
|
if (await runInstalledHookRefresh() !== 0) {
|
|
102079
|
-
console.warn("[!] Package updated, but some
|
|
102274
|
+
console.warn("[!] Package updated, but some existing hooks could not be refreshed. Run `errata install-hooks codex --refresh-existing` and `errata install-hooks claude --refresh-existing`; daemon startup also retries.");
|
|
102080
102275
|
}
|
|
102081
102276
|
console.log(`
|
|
102082
102277
|
[OK] updated to ${status.latest}. Restart the daemon to run it:`);
|
|
@@ -103459,7 +103654,7 @@ async function gatherReportData(generatedAt) {
|
|
|
103459
103654
|
};
|
|
103460
103655
|
}
|
|
103461
103656
|
async function cmdReport(args2) {
|
|
103462
|
-
const { mkdirSync: mkdirSync13, writeFileSync:
|
|
103657
|
+
const { mkdirSync: mkdirSync13, writeFileSync: writeFileSync35 } = await import("node:fs");
|
|
103463
103658
|
const { renderReport: renderReport2 } = await Promise.resolve().then(() => (init_report_render(), report_render_exports));
|
|
103464
103659
|
const includeFutureVerbs = args2.includes("--future-verbs");
|
|
103465
103660
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -103472,7 +103667,7 @@ async function cmdReport(args2) {
|
|
|
103472
103667
|
const outDir = workspacePaths(ROOT).configDir;
|
|
103473
103668
|
mkdirSync13(outDir, { recursive: true });
|
|
103474
103669
|
const files = renderReport2(data, { includeFutureVerbs });
|
|
103475
|
-
for (const f of files)
|
|
103670
|
+
for (const f of files) writeFileSync35(join57(outDir, f.name), f.html, "utf8");
|
|
103476
103671
|
const indexPath = join57(outDir, "report.html");
|
|
103477
103672
|
console.log(`report \u2192 ${indexPath}`);
|
|
103478
103673
|
console.log(
|
|
@@ -103573,6 +103768,10 @@ async function cmdInstallHooks(args2) {
|
|
|
103573
103768
|
const port = resolveHookPort(portIdx >= 0 && args2[portIdx + 1] ? Number(args2[portIdx + 1]) : void 0);
|
|
103574
103769
|
switch (harness) {
|
|
103575
103770
|
case "claude":
|
|
103771
|
+
if (args2.includes("--refresh-existing")) {
|
|
103772
|
+
if (!refreshExistingClaudeHooks()) process.exitCode = 1;
|
|
103773
|
+
return;
|
|
103774
|
+
}
|
|
103576
103775
|
await installClaudeHooks(port);
|
|
103577
103776
|
return;
|
|
103578
103777
|
case "cursor":
|
|
@@ -103598,7 +103797,7 @@ async function cmdInstallHooks(args2) {
|
|
|
103598
103797
|
}
|
|
103599
103798
|
}
|
|
103600
103799
|
async function installOpencodePlugin(port) {
|
|
103601
|
-
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync:
|
|
103800
|
+
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync: writeFileSync35 } = await import("node:fs");
|
|
103602
103801
|
const { join: join58 } = await import("node:path");
|
|
103603
103802
|
const dir = join58(ROOT, ".opencode", "plugin");
|
|
103604
103803
|
if (!existsSync42(dir)) mkdirSync13(dir, { recursive: true });
|
|
@@ -103610,7 +103809,7 @@ async function installOpencodePlugin(port) {
|
|
|
103610
103809
|
process.exit(2);
|
|
103611
103810
|
}
|
|
103612
103811
|
}
|
|
103613
|
-
|
|
103812
|
+
writeFileSync35(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
|
|
103614
103813
|
const cfgFile = join58(ROOT, "opencode.json");
|
|
103615
103814
|
let cfg = { $schema: "https://opencode.ai/config.json" };
|
|
103616
103815
|
if (existsSync42(cfgFile)) {
|
|
@@ -103626,7 +103825,7 @@ async function installOpencodePlugin(port) {
|
|
|
103626
103825
|
if (!hadErrata) {
|
|
103627
103826
|
const inv = errataMcpInvocation();
|
|
103628
103827
|
cfg.mcp["errata"] = { type: "local", command: [inv.command, ...inv.args] };
|
|
103629
|
-
|
|
103828
|
+
writeFileSync35(cfgFile, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
103630
103829
|
}
|
|
103631
103830
|
console.log(`installed OpenCode plugin \u2192 ${file2}`);
|
|
103632
103831
|
if (hadErrata) console.log(` mcp.errata already present in ${cfgFile} \u2014 left untouched`);
|
|
@@ -103640,7 +103839,6 @@ async function installOpencodePlugin(port) {
|
|
|
103640
103839
|
console.log(` Cloud tools (search/contribute) are the MCP entry in opencode.json:`);
|
|
103641
103840
|
console.log(` { "mcp": { "inerrata": { "type": "remote", "url": "${mcpUrlFor(loadConfig().cloudUrl)}", "headers": { "Authorization": "Bearer <errk_\u2026>" } } } }`);
|
|
103642
103841
|
}
|
|
103643
|
-
var ERRATA_TAG = "# errata-managed";
|
|
103644
103842
|
function errataMcpInvocation() {
|
|
103645
103843
|
const { cmd: cmd2, args: args2 } = selfArgv("mcp");
|
|
103646
103844
|
return { command: cmd2, args: args2 };
|
|
@@ -103661,7 +103859,7 @@ function installClaudeMods() {
|
|
|
103661
103859
|
}
|
|
103662
103860
|
}
|
|
103663
103861
|
async function installClaudeHooks(port) {
|
|
103664
|
-
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync:
|
|
103862
|
+
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync: writeFileSync35 } = await import("node:fs");
|
|
103665
103863
|
const { join: join58 } = await import("node:path");
|
|
103666
103864
|
const dir = join58(ROOT, ".claude");
|
|
103667
103865
|
if (!existsSync42(dir)) mkdirSync13(dir, { recursive: true });
|
|
@@ -103675,50 +103873,9 @@ async function installClaudeHooks(port) {
|
|
|
103675
103873
|
process.exit(2);
|
|
103676
103874
|
}
|
|
103677
103875
|
}
|
|
103678
|
-
|
|
103679
|
-
const postMatcher = `${matcher}|ToolSearch|mcp__errata__.*`;
|
|
103680
|
-
const preMatcher = `${matcher}|Agent|Task`;
|
|
103681
|
-
const preCmd = `${hookRelayCommand(port, "/api/hook")} ${ERRATA_TAG}`;
|
|
103682
|
-
const postCmd = `${hookRelayCommand(port, "/api/hook")} ${ERRATA_TAG}`;
|
|
103683
|
-
settings.hooks ??= {};
|
|
103684
|
-
const dropErrata = (list) => {
|
|
103685
|
-
for (let i2 = list.length - 1; i2 >= 0; i2--) {
|
|
103686
|
-
if (list[i2].hooks.some((h) => h.command.includes(ERRATA_TAG))) list.splice(i2, 1);
|
|
103687
|
-
}
|
|
103688
|
-
};
|
|
103689
|
-
for (const [event, command, eventMatcher] of [
|
|
103690
|
-
["PreToolUse", preCmd, preMatcher],
|
|
103691
|
-
["PostToolUse", postCmd, postMatcher],
|
|
103692
|
-
["PostToolUseFailure", postCmd, postMatcher]
|
|
103693
|
-
]) {
|
|
103694
|
-
const list = settings.hooks[event] ??= [];
|
|
103695
|
-
dropErrata(list);
|
|
103696
|
-
list.push({ matcher: eventMatcher, hooks: [{ type: "command", command }] });
|
|
103697
|
-
}
|
|
103698
|
-
const turnCmd = `${hookCurlCommand(port, "/api/turn")} ${ERRATA_TAG}`;
|
|
103699
|
-
for (const event of ["Stop", "SubagentStop"]) {
|
|
103700
|
-
const list = settings.hooks[event] ??= [];
|
|
103701
|
-
dropErrata(list);
|
|
103702
|
-
list.push({ hooks: [{ type: "command", command: turnCmd }] });
|
|
103703
|
-
}
|
|
103704
|
-
const msgCmd = `${hookCurlCommand(port, "/api/message")} ${ERRATA_TAG}`;
|
|
103705
|
-
{
|
|
103706
|
-
const list = settings.hooks["MessageDisplay"] ??= [];
|
|
103707
|
-
dropErrata(list);
|
|
103708
|
-
list.push({ hooks: [{ type: "command", command: msgCmd }] });
|
|
103709
|
-
}
|
|
103710
|
-
const endCmd = `${hookCurlCommand(port, "/api/session-end")} ${ERRATA_TAG}`;
|
|
103711
|
-
const endList = settings.hooks["SessionEnd"] ??= [];
|
|
103712
|
-
dropErrata(endList);
|
|
103713
|
-
endList.push({ hooks: [{ type: "command", command: endCmd }] });
|
|
103714
|
-
const injectCmd = `${hookRelayCommand(port, "/api/context-inject")} ${ERRATA_TAG}`;
|
|
103715
|
-
for (const event of ["SessionStart", "UserPromptSubmit", "SubagentStart"]) {
|
|
103716
|
-
const list = settings.hooks[event] ??= [];
|
|
103717
|
-
dropErrata(list);
|
|
103718
|
-
list.push({ hooks: [{ type: "command", command: injectCmd }] });
|
|
103719
|
-
}
|
|
103876
|
+
applyClaudeErrataHooks(settings, port);
|
|
103720
103877
|
removeProjectStatusLine(settings);
|
|
103721
|
-
|
|
103878
|
+
writeFileSync35(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
103722
103879
|
console.log(`installed Claude Code hooks \u2192 ${file2}`);
|
|
103723
103880
|
console.log(` ${ensureStatusLine(port)}`);
|
|
103724
103881
|
console.log(` ${installClaudeMods().line}`);
|
|
@@ -103735,7 +103892,7 @@ async function installClaudeHooks(port) {
|
|
|
103735
103892
|
console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
|
|
103736
103893
|
}
|
|
103737
103894
|
async function installClaudeMcpConfig() {
|
|
103738
|
-
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync:
|
|
103895
|
+
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync: writeFileSync35 } = await import("node:fs");
|
|
103739
103896
|
const { join: join58, dirname: dirname22 } = await import("node:path");
|
|
103740
103897
|
const file2 = join58(ROOT, ".mcp.json");
|
|
103741
103898
|
const dir = dirname22(file2);
|
|
@@ -103751,13 +103908,13 @@ async function installClaudeMcpConfig() {
|
|
|
103751
103908
|
}
|
|
103752
103909
|
cfg.mcpServers ??= {};
|
|
103753
103910
|
cfg.mcpServers["errata"] = errataMcpInvocation();
|
|
103754
|
-
|
|
103911
|
+
writeFileSync35(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
103755
103912
|
console.log(`installed Claude Code MCP server config \u2192 ${file2}`);
|
|
103756
103913
|
console.log(` Claude Code will spawn \`errata mcp\` on workspace open.`);
|
|
103757
103914
|
console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
|
|
103758
103915
|
}
|
|
103759
103916
|
async function installCursorMcpConfig() {
|
|
103760
|
-
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync:
|
|
103917
|
+
const { mkdirSync: mkdirSync13, existsSync: existsSync42, readFileSync: readFileSync50, writeFileSync: writeFileSync35 } = await import("node:fs");
|
|
103761
103918
|
const { join: join58 } = await import("node:path");
|
|
103762
103919
|
const dir = join58(ROOT, ".cursor");
|
|
103763
103920
|
if (!existsSync42(dir)) mkdirSync13(dir, { recursive: true });
|
|
@@ -103773,7 +103930,7 @@ async function installCursorMcpConfig() {
|
|
|
103773
103930
|
}
|
|
103774
103931
|
cfg.mcpServers ??= {};
|
|
103775
103932
|
cfg.mcpServers["errata"] = errataMcpInvocation();
|
|
103776
|
-
|
|
103933
|
+
writeFileSync35(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
103777
103934
|
console.log(`installed Cursor MCP server config \u2192 ${file2}`);
|
|
103778
103935
|
console.log(` Cursor will spawn \`errata mcp\` on workspace open.`);
|
|
103779
103936
|
console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses`);
|
|
@@ -103828,6 +103985,24 @@ function refreshExistingCodexHooks() {
|
|
|
103828
103985
|
}
|
|
103829
103986
|
return ok;
|
|
103830
103987
|
}
|
|
103988
|
+
function refreshExistingClaudeHooks() {
|
|
103989
|
+
let ok = true;
|
|
103990
|
+
const roots = [...listWorkspaceEntries().map((entry) => entry.path), process.cwd()];
|
|
103991
|
+
for (const root of codexHookRoots(roots)) {
|
|
103992
|
+
const file2 = join57(root, ".claude", "settings.json");
|
|
103993
|
+
try {
|
|
103994
|
+
if (!existsSync41(file2) || isReplayCheckout(root)) continue;
|
|
103995
|
+
const next = refreshClaudeHookSettings(readFileSync49(file2, "utf8"));
|
|
103996
|
+
if (next === null) continue;
|
|
103997
|
+
writeFileSync34(file2, next, "utf8");
|
|
103998
|
+
console.log(`updated existing Claude Code hooks \u2192 ${file2}`);
|
|
103999
|
+
} catch (error48) {
|
|
104000
|
+
ok = false;
|
|
104001
|
+
console.warn(`Claude Code hook refresh needs attention in ${root}: ${error48 instanceof Error ? error48.message : error48}`);
|
|
104002
|
+
}
|
|
104003
|
+
}
|
|
104004
|
+
return ok;
|
|
104005
|
+
}
|
|
103831
104006
|
async function refreshLinkedCodexHooks(args2) {
|
|
103832
104007
|
if (args2.includes("--skip-hooks") || !hasCodexConfig(ROOT)) return;
|
|
103833
104008
|
try {
|
|
@@ -103996,6 +104171,7 @@ async function cmdDash(args2) {
|
|
|
103996
104171
|
process.title = "errata-daemon";
|
|
103997
104172
|
installLogTimestamps();
|
|
103998
104173
|
refreshExistingCodexHooks();
|
|
104174
|
+
refreshExistingClaudeHooks();
|
|
103999
104175
|
const portIdx = args2.indexOf("--port");
|
|
104000
104176
|
const port = portIdx >= 0 && args2[portIdx + 1] ? Number(args2[portIdx + 1]) : 7891;
|
|
104001
104177
|
const reindexOnStart = !args2.includes("--no-reindex");
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
71e234cc465af344aa5e0ba160f5e1a634188138cf1f6285d276185889fa4ad7 .codex-plugin/plugin.json
|
|
2
2
|
a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
|
|
3
3
|
fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
|
|
4
4
|
73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js
|