@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
- if ((event === "PostToolUse" || event === "PreToolUse") && (recall || refreshed)) {
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.4864" : "2.0.0-alpha.0";
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 clip2 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
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: clip2(m.description, 110) }))
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: clip2(d.presenting, 140), cause: clip2(d.cause, 140) }] : [];
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)) refreshContextNow();
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
- return new Promise((resolve11) => {
93403
- const child = spawn2(process.execPath, [join42(root, PACKAGE_NAME, "errata.mjs"), "install-hooks", "codex", "--refresh-existing"], {
93404
- windowsHide: true,
93405
- stdio: "inherit",
93406
- timeout: 3e4
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
- child.on("error", () => resolve11(1));
93409
- child.on("close", (code) => resolve11(code ?? 1));
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 Codex relays could not be refreshed. Run `errata install-hooks codex --refresh-existing`; daemon startup also retries.");
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: writeFileSync34 } = await import("node:fs");
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) writeFileSync34(join57(outDir, f.name), f.html, "utf8");
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: writeFileSync34 } = await import("node:fs");
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
- writeFileSync34(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
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
- writeFileSync34(cfgFile, JSON.stringify(cfg, null, 2) + "\n", "utf8");
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: writeFileSync34 } = await import("node:fs");
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
- const matcher = "Read|Edit|Write|Grep|Glob|NotebookEdit|Bash";
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
- writeFileSync34(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
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: writeFileSync34 } = await import("node:fs");
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
- writeFileSync34(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
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: writeFileSync34 } = await import("node:fs");
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
- writeFileSync34(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.23-dev.4864",
3
+ "version": "2.0.23-dev.4900",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.23-dev.4864",
3
+ "version": "2.0.23-dev.4900",
4
4
  "description": "A minimal 3D view of loaded Errata priors and recent graph bursts.",
5
5
  "author": {
6
6
  "name": "inErrata"
@@ -1,4 +1,4 @@
1
- d2a34f333032bcc14b4e3da00eaa5038b377c3d0f1e279b9b245e4c1634d5992 .codex-plugin/plugin.json
1
+ 71e234cc465af344aa5e0ba160f5e1a634188138cf1f6285d276185889fa4ad7 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js