@inerrata-corporation/errata 2.0.6-dev.2506 → 2.0.6-dev.2533

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.txt CHANGED
@@ -25,6 +25,28 @@ USE
25
25
 
26
26
  Dashboard: http://127.0.0.1:7891
27
27
 
28
+ CODEX
29
+
30
+ errata install-hooks codex # explicit install or upgrade for this workspace
31
+
32
+ Init detects .codex in the project or CODEX_HOME (default ~/.codex). Successful
33
+ errata link flows also refresh detected Codex hooks; --skip-hooks opts out of
34
+ either automatic install. The installer preserves other config and writes its
35
+ Node relay into .codex/hooks/errata-relay.mjs. No curl or Bash dependency.
36
+
37
+ Restart/resume Codex after installing, and review/trust the hooks with /hooks.
38
+ If your Codex build gates hooks, enable [features] codex_hooks = true. Update
39
+ older Codex builds if UserPromptSubmit or tool hooks are unsupported.
40
+
41
+ SessionStart injects .errata/context.md, including startup/resume/compaction.
42
+ UserPromptSubmit and PostToolUse inject it again only when it changes. Tool
43
+ recall and capture remain enabled. Refresh happens at a tool boundary, not
44
+ during model generation. The daemon must be running; unavailable hooks fail
45
+ open. AGENTS.md's file-read instruction remains a useful fallback.
46
+
47
+ The cloud MCP/plugin installer alone does not install this local daemon.
48
+ Install errata and run init in each workspace to enable daemon context.
49
+
28
50
  UPDATE
29
51
 
30
52
  errata update # pull the newest build on your channel (npm i -g)
@@ -15322,6 +15322,9 @@ var init_symbol_intent = __esm({
15322
15322
  });
15323
15323
 
15324
15324
  // ../../packages/shared/src/wire.ts
15325
+ function isAnchorLabelKind(kind) {
15326
+ return typeof kind === "string" && ANCHOR_LABEL_KINDS.includes(kind);
15327
+ }
15325
15328
  function anchorVersionAttrs(attrs) {
15326
15329
  if (!attrs) return {};
15327
15330
  const commit = attrs["observedAtCommit"];
@@ -15331,7 +15334,24 @@ function anchorVersionAttrs(attrs) {
15331
15334
  ...typeof hash2 === "string" && ANCHOR_BODY_HASH_RE.test(hash2) ? { symbolContentHash: hash2 } : {}
15332
15335
  };
15333
15336
  }
15334
- var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15337
+ function anchorLabelAttrs(attrs) {
15338
+ if (!attrs) return {};
15339
+ const relPath = attrs["labelRelPath"];
15340
+ const qname = attrs["labelQname"];
15341
+ const kind = attrs["labelKind"];
15342
+ const line = attrs["labelLine"];
15343
+ if (typeof relPath !== "string" || !ANCHOR_LABEL_PATH_RE.test(relPath)) return {};
15344
+ if (!isAnchorLabelKind(kind)) return {};
15345
+ const qnameOk = typeof qname === "string" && ANCHOR_LABEL_QNAME_RE.test(qname);
15346
+ if (kind !== "File" && !qnameOk) return {};
15347
+ return {
15348
+ labelRelPath: relPath,
15349
+ labelKind: kind,
15350
+ ...kind !== "File" && qnameOk ? { labelQname: qname } : {},
15351
+ ...typeof line === "number" && Number.isInteger(line) && line >= 1 && line <= 1e7 ? { labelLine: line } : {}
15352
+ };
15353
+ }
15354
+ var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, ANCHOR_SYMBOL_KINDS, ANCHOR_LABEL_KINDS, FILE_ANCHOR_QNAME, ANCHOR_LABEL_PATH_RE, ANCHOR_LABEL_QNAME_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15335
15355
  var init_wire = __esm({
15336
15356
  "../../packages/shared/src/wire.ts"() {
15337
15357
  "use strict";
@@ -15434,6 +15454,22 @@ var init_wire = __esm({
15434
15454
  });
15435
15455
  ANCHOR_COMMIT_SHA_RE = /^[0-9a-f]{7,40}$/;
15436
15456
  ANCHOR_BODY_HASH_RE = /^[0-9a-f]{8,64}$/;
15457
+ ANCHOR_SYMBOL_KINDS = [
15458
+ "Function",
15459
+ "Class",
15460
+ "Method",
15461
+ "Module",
15462
+ "Interface",
15463
+ "Const",
15464
+ "Enum",
15465
+ "TypeAlias",
15466
+ "Namespace",
15467
+ "Property"
15468
+ ];
15469
+ ANCHOR_LABEL_KINDS = [...ANCHOR_SYMBOL_KINDS, "File"];
15470
+ FILE_ANCHOR_QNAME = "";
15471
+ ANCHOR_LABEL_PATH_RE = /^(?![/])(?![A-Za-z]:)(?!(?:.*\/)?\.\.(?:\/|$))[^\x00-\x1f\x7f\\`]{1,512}$/;
15472
+ ANCHOR_LABEL_QNAME_RE = /^[^\x00-\x1f\x7f`]{1,256}$/;
15437
15473
  CastaliaIngestEdgeSchema = external_exports.object({
15438
15474
  fromCanonicalId: external_exports.string().min(6).max(160),
15439
15475
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15453,7 +15489,17 @@ var init_wire = __esm({
15453
15489
  * classify the anchor current / pre-change; absent ⇒ the pre-versioning
15454
15490
  * contract, unverified. Only meaningful on `ANCHORED_AT`. */
15455
15491
  observedAtCommit: external_exports.string().regex(ANCHOR_COMMIT_SHA_RE).optional(),
15456
- symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional()
15492
+ symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional(),
15493
+ /** PJ-anchor-labels: the READABLE identity of the anchor's target —
15494
+ * repo-relative path, qualified name (absent for a File), kind and line.
15495
+ * Sent only for a project whose owner opted in; the door strips them again
15496
+ * for any project that has not (the daemon's flag is not trusted alone).
15497
+ * Prefixed `label*` so no local edge attr (`anchorLine` lives there
15498
+ * natively) can ride out under a shared key. */
15499
+ labelRelPath: external_exports.string().regex(ANCHOR_LABEL_PATH_RE).optional(),
15500
+ labelQname: external_exports.string().regex(ANCHOR_LABEL_QNAME_RE).optional(),
15501
+ labelKind: external_exports.enum(ANCHOR_LABEL_KINDS).optional(),
15502
+ labelLine: external_exports.number().int().min(1).max(1e7).optional()
15457
15503
  }).optional()
15458
15504
  });
15459
15505
  SymbolSummarySidecarSchema = external_exports.record(
@@ -19153,19 +19199,39 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19153
19199
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
19154
19200
  const target = store.getNode(e.to);
19155
19201
  if (!target) continue;
19202
+ if (!isAnchorLabelKind(target.label)) continue;
19203
+ const isFile = target.label === "File";
19204
+ if (isFile && !project.anchorLabels) continue;
19156
19205
  const relPath = target.attrs["relPath"];
19157
- const qname = target.attrs["qname"];
19206
+ const qname = isFile ? FILE_ANCHOR_QNAME : target.attrs["qname"];
19158
19207
  if (typeof relPath !== "string" || typeof qname !== "string") continue;
19159
- if (ignored(relPath) || ignored(qname)) continue;
19160
- planned.push({ e, target, symId: projectSymbolId(project.salt, project.projectId, relPath, qname, target.label) });
19208
+ if (ignored(relPath) || qname !== "" && ignored(qname)) continue;
19209
+ const line = e.attrs?.["anchorLine"];
19210
+ const wireAttrs = {
19211
+ ...anchorVersionAttrs(e.attrs),
19212
+ // PJ-anchor-labels: only for an opted-in project, and only through the
19213
+ // shared projection (invalid path/kind ⇒ no label, never half of one).
19214
+ ...project.anchorLabels ? anchorLabelAttrs({
19215
+ labelRelPath: normalizeSymbolPath(relPath),
19216
+ labelKind: target.label,
19217
+ ...isFile ? {} : { labelQname: qname },
19218
+ ...typeof line === "number" ? { labelLine: line } : {}
19219
+ }) : {}
19220
+ };
19221
+ planned.push({
19222
+ e,
19223
+ target,
19224
+ symId: projectSymbolId(project.salt, project.projectId, relPath, qname, target.label),
19225
+ wireAttrs
19226
+ });
19161
19227
  }
19162
19228
  if (planned.length === 0) continue;
19163
19229
  const dg = digest(
19164
- planned.map(({ e, symId }) => `ANCHORED_AT>${symId}>${JSON.stringify(anchorVersionAttrs(e.attrs))}`).sort()
19230
+ planned.map(({ symId, wireAttrs }) => `ANCHORED_AT>${symId}>${JSON.stringify(wireAttrs)}`).sort()
19165
19231
  );
19166
19232
  if (source.attrs["projectAnchorsContributedDigest"] === dg) continue;
19167
19233
  projectAnchorDigests[sourceId] = dg;
19168
- for (const { e, target, symId } of planned) {
19234
+ for (const { e, target, symId, wireAttrs } of planned) {
19169
19235
  if (!contextSeen.has(symId)) {
19170
19236
  contextSeen.add(symId);
19171
19237
  contextNodes.push({
@@ -19192,7 +19258,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
19192
19258
  stability: "unstable"
19193
19259
  });
19194
19260
  }
19195
- anchorEdges.push({ ...e, from: wireFrom, to: symId, attrs: anchorVersionAttrs(e.attrs) });
19261
+ anchorEdges.push({ ...e, from: wireFrom, to: symId, attrs: wireAttrs });
19196
19262
  }
19197
19263
  }
19198
19264
  }
package/errata.mjs CHANGED
@@ -15729,6 +15729,9 @@ function provenanceBand(source) {
15729
15729
  function isSeedProvenance(source) {
15730
15730
  return provenanceBand(source) === "seed";
15731
15731
  }
15732
+ function isAnchorLabelKind(kind) {
15733
+ return typeof kind === "string" && ANCHOR_LABEL_KINDS.includes(kind);
15734
+ }
15732
15735
  function anchorVersionAttrs(attrs) {
15733
15736
  if (!attrs) return {};
15734
15737
  const commit = attrs["observedAtCommit"];
@@ -15738,6 +15741,28 @@ function anchorVersionAttrs(attrs) {
15738
15741
  ...typeof hash2 === "string" && ANCHOR_BODY_HASH_RE.test(hash2) ? { symbolContentHash: hash2 } : {}
15739
15742
  };
15740
15743
  }
15744
+ function anchorLabelAttrs(attrs) {
15745
+ if (!attrs) return {};
15746
+ const relPath = attrs["labelRelPath"];
15747
+ const qname = attrs["labelQname"];
15748
+ const kind = attrs["labelKind"];
15749
+ const line = attrs["labelLine"];
15750
+ if (typeof relPath !== "string" || !ANCHOR_LABEL_PATH_RE.test(relPath)) return {};
15751
+ if (!isAnchorLabelKind(kind)) return {};
15752
+ const qnameOk = typeof qname === "string" && ANCHOR_LABEL_QNAME_RE.test(qname);
15753
+ if (kind !== "File" && !qnameOk) return {};
15754
+ return {
15755
+ labelRelPath: relPath,
15756
+ labelKind: kind,
15757
+ ...kind !== "File" && qnameOk ? { labelQname: qname } : {},
15758
+ ...typeof line === "number" && Number.isInteger(line) && line >= 1 && line <= 1e7 ? { labelLine: line } : {}
15759
+ };
15760
+ }
15761
+ function stripAnchorLabels(attrs) {
15762
+ const out2 = { ...attrs };
15763
+ for (const k of ANCHOR_LABEL_KEYS) delete out2[k];
15764
+ return out2;
15765
+ }
15741
15766
  function validateCastaliaPayload(p) {
15742
15767
  const errors = [];
15743
15768
  const labelByCid = /* @__PURE__ */ new Map();
@@ -15767,7 +15792,7 @@ function summarizeIngestResult(r) {
15767
15792
  ...r.patternReconciliation ? { patternReconciliation: r.patternReconciliation } : {}
15768
15793
  };
15769
15794
  }
15770
- var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, QUARANTINE_EXTRACTION_SOURCE_PREFIX, MAX_NODES_PER_PAYLOAD, MAX_EDGES_PER_PAYLOAD, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15795
+ var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, QUARANTINE_EXTRACTION_SOURCE_PREFIX, MAX_NODES_PER_PAYLOAD, MAX_EDGES_PER_PAYLOAD, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, ANCHOR_SYMBOL_KINDS, ANCHOR_LABEL_KINDS, FILE_ANCHOR_QNAME, ANCHOR_LABEL_PATH_RE, ANCHOR_LABEL_QNAME_RE, ANCHOR_LABEL_KEYS, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15771
15796
  var init_wire = __esm({
15772
15797
  "../../packages/shared/src/wire.ts"() {
15773
15798
  "use strict";
@@ -15873,6 +15898,23 @@ var init_wire = __esm({
15873
15898
  });
15874
15899
  ANCHOR_COMMIT_SHA_RE = /^[0-9a-f]{7,40}$/;
15875
15900
  ANCHOR_BODY_HASH_RE = /^[0-9a-f]{8,64}$/;
15901
+ ANCHOR_SYMBOL_KINDS = [
15902
+ "Function",
15903
+ "Class",
15904
+ "Method",
15905
+ "Module",
15906
+ "Interface",
15907
+ "Const",
15908
+ "Enum",
15909
+ "TypeAlias",
15910
+ "Namespace",
15911
+ "Property"
15912
+ ];
15913
+ ANCHOR_LABEL_KINDS = [...ANCHOR_SYMBOL_KINDS, "File"];
15914
+ FILE_ANCHOR_QNAME = "";
15915
+ ANCHOR_LABEL_PATH_RE = /^(?![/])(?![A-Za-z]:)(?!(?:.*\/)?\.\.(?:\/|$))[^\x00-\x1f\x7f\\`]{1,512}$/;
15916
+ ANCHOR_LABEL_QNAME_RE = /^[^\x00-\x1f\x7f`]{1,256}$/;
15917
+ ANCHOR_LABEL_KEYS = ["labelRelPath", "labelQname", "labelKind", "labelLine"];
15876
15918
  CastaliaIngestEdgeSchema = external_exports.object({
15877
15919
  fromCanonicalId: external_exports.string().min(6).max(160),
15878
15920
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15892,7 +15934,17 @@ var init_wire = __esm({
15892
15934
  * classify the anchor current / pre-change; absent ⇒ the pre-versioning
15893
15935
  * contract, unverified. Only meaningful on `ANCHORED_AT`. */
15894
15936
  observedAtCommit: external_exports.string().regex(ANCHOR_COMMIT_SHA_RE).optional(),
15895
- symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional()
15937
+ symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional(),
15938
+ /** PJ-anchor-labels: the READABLE identity of the anchor's target —
15939
+ * repo-relative path, qualified name (absent for a File), kind and line.
15940
+ * Sent only for a project whose owner opted in; the door strips them again
15941
+ * for any project that has not (the daemon's flag is not trusted alone).
15942
+ * Prefixed `label*` so no local edge attr (`anchorLine` lives there
15943
+ * natively) can ride out under a shared key. */
15944
+ labelRelPath: external_exports.string().regex(ANCHOR_LABEL_PATH_RE).optional(),
15945
+ labelQname: external_exports.string().regex(ANCHOR_LABEL_QNAME_RE).optional(),
15946
+ labelKind: external_exports.enum(ANCHOR_LABEL_KINDS).optional(),
15947
+ labelLine: external_exports.number().int().min(1).max(1e7).optional()
15896
15948
  }).optional()
15897
15949
  });
15898
15950
  SymbolSummarySidecarSchema = external_exports.record(
@@ -16716,7 +16768,12 @@ __export(src_exports, {
16716
16768
  AMBIGUOUS_ALIASES: () => AMBIGUOUS_ALIASES,
16717
16769
  ANCHOR_BODY_HASH_RE: () => ANCHOR_BODY_HASH_RE,
16718
16770
  ANCHOR_COMMIT_SHA_RE: () => ANCHOR_COMMIT_SHA_RE,
16771
+ ANCHOR_LABEL_KEYS: () => ANCHOR_LABEL_KEYS,
16772
+ ANCHOR_LABEL_KINDS: () => ANCHOR_LABEL_KINDS,
16773
+ ANCHOR_LABEL_PATH_RE: () => ANCHOR_LABEL_PATH_RE,
16774
+ ANCHOR_LABEL_QNAME_RE: () => ANCHOR_LABEL_QNAME_RE,
16719
16775
  ANCHOR_REFUSAL_REASONS: () => ANCHOR_REFUSAL_REASONS,
16776
+ ANCHOR_SYMBOL_KINDS: () => ANCHOR_SYMBOL_KINDS,
16720
16777
  BELIEF_EDGES: () => BELIEF_EDGES,
16721
16778
  BRIDGE_EDGES: () => BRIDGE_EDGES,
16722
16779
  CAUSAL_EDGES: () => CAUSAL_EDGES,
@@ -16739,6 +16796,7 @@ __export(src_exports, {
16739
16796
  EDGE_RULES: () => EDGE_RULES,
16740
16797
  EDGE_WEIGHT: () => EDGE_WEIGHT,
16741
16798
  EVIDENCE_EDGES: () => EVIDENCE_EDGES,
16799
+ FILE_ANCHOR_QNAME: () => FILE_ANCHOR_QNAME,
16742
16800
  GIT_EDGES: () => GIT_EDGES,
16743
16801
  GRAPH_EVENT_VOCABULARIES: () => GRAPH_EVENT_VOCABULARIES,
16744
16802
  IDENTITY_EDGES: () => IDENTITY_EDGES,
@@ -16775,6 +16833,7 @@ __export(src_exports, {
16775
16833
  VALIDATION_SOURCES: () => VALIDATION_SOURCES,
16776
16834
  aliasesLongestFirst: () => aliasesLongestFirst,
16777
16835
  allEntities: () => allEntities,
16836
+ anchorLabelAttrs: () => anchorLabelAttrs,
16778
16837
  anchorVersionAttrs: () => anchorVersionAttrs,
16779
16838
  assertGraphEventVocabulary: () => assertGraphEventVocabulary,
16780
16839
  buildSpine: () => buildSpine,
@@ -16796,6 +16855,7 @@ __export(src_exports, {
16796
16855
  extractIdentifierTokens: () => extractIdentifierTokens,
16797
16856
  genericCanonicalId: () => genericCanonicalId,
16798
16857
  identityId: () => identityId,
16858
+ isAnchorLabelKind: () => isAnchorLabelKind,
16799
16859
  isCausalProtected: () => isCausalProtected,
16800
16860
  isCloudWritableNodeLabel: () => isCloudWritableNodeLabel,
16801
16861
  isCodeLabel: () => isCodeLabel,
@@ -16844,6 +16904,7 @@ __export(src_exports, {
16844
16904
  sidecarDropBucket: () => sidecarDropBucket,
16845
16905
  spineDeps: () => spineDeps,
16846
16906
  splitPnpmKey: () => splitPnpmKey,
16907
+ stripAnchorLabels: () => stripAnchorLabels,
16847
16908
  substituteSymbolSummaries: () => substituteSymbolSummaries,
16848
16909
  summarizeIngestResult: () => summarizeIngestResult,
16849
16910
  summaryRejectionReason: () => summaryRejectionReason,
@@ -24956,7 +25017,10 @@ function toWirePayload(batch, runId) {
24956
25017
  ...typeof e.confidence === "number" ? { confidence: clamp012(e.confidence) } : {},
24957
25018
  ...perContext ? { perContext } : {},
24958
25019
  // PJ-anchor-version: the two opaque version stamps ride on anchors only.
24959
- ...e.type === "ANCHORED_AT" ? anchorVersionAttrs(e.attrs) : {}
25020
+ ...e.type === "ANCHORED_AT" ? anchorVersionAttrs(e.attrs) : {},
25021
+ // PJ-anchor-labels: readable labels ride only when the producer set the
25022
+ // `label*` keys, which the daemon does only for an opted-in project.
25023
+ ...e.type === "ANCHORED_AT" ? anchorLabelAttrs(e.attrs) : {}
24960
25024
  }
24961
25025
  };
24962
25026
  });
@@ -51034,15 +51098,14 @@ function recallForTool(store, command, ctx) {
51034
51098
  ${lines.join("\n")}`;
51035
51099
  }
51036
51100
  function decideContextInject(args2) {
51037
- const { event, source, sessionId, block, lastInjectedHash } = args2;
51101
+ const { event, sessionId, block, lastInjectedHash } = args2;
51038
51102
  if (!block) return null;
51039
51103
  if (event === "SessionStart") {
51040
- lastInjectedHash.set(sessionId, block.hash);
51041
- if (source === "startup") return null;
51042
- } else if (lastInjectedHash.get(sessionId) === block.hash) {
51104
+ if (sessionId) lastInjectedHash.set(sessionId, block.hash);
51105
+ } else if (sessionId && lastInjectedHash.get(sessionId) === block.hash) {
51043
51106
  return null;
51044
51107
  } else {
51045
- lastInjectedHash.set(sessionId, block.hash);
51108
+ if (sessionId) lastInjectedHash.set(sessionId, block.hash);
51046
51109
  }
51047
51110
  const additionalContext = "The errata daemon has updated your live workspace context. The block below REPLACES any earlier errata context block in this conversation \u2014 treat it as the current source of truth:\n\n" + block.body;
51048
51111
  return { hookEventName: event, additionalContext };
@@ -70832,8 +70895,8 @@ var init_report_render = __esm({
70832
70895
 
70833
70896
  // src/cli.ts
70834
70897
  init_src6();
70835
- import { closeSync as closeSync2, existsSync as existsSync33, openSync as openSync2, readFileSync as readFileSync32, renameSync as renameSync4, statSync as statSync8 } from "node:fs";
70836
- import { join as join37, resolve as pathResolve2 } from "node:path";
70898
+ import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync33, renameSync as renameSync4, statSync as statSync8 } from "node:fs";
70899
+ import { join as join38, resolve as pathResolve2 } from "node:path";
70837
70900
  import { spawn as spawn3 } from "node:child_process";
70838
70901
 
70839
70902
  // src/daemon.ts
@@ -78143,7 +78206,7 @@ function createWatchBreaker(deps) {
78143
78206
  }
78144
78207
 
78145
78208
  // src/engine.ts
78146
- var DAEMON_VERSION = true ? "2.0.6-dev.2506" : "2.0.0-alpha.0";
78209
+ var DAEMON_VERSION = true ? "2.0.6-dev.2533" : "2.0.0-alpha.0";
78147
78210
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
78148
78211
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
78149
78212
  var GIT_OP_MUTE_MS = 4e3;
@@ -81172,19 +81235,39 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
81172
81235
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
81173
81236
  const target = store.getNode(e.to);
81174
81237
  if (!target) continue;
81238
+ if (!isAnchorLabelKind(target.label)) continue;
81239
+ const isFile = target.label === "File";
81240
+ if (isFile && !project.anchorLabels) continue;
81175
81241
  const relPath = target.attrs["relPath"];
81176
- const qname = target.attrs["qname"];
81242
+ const qname = isFile ? FILE_ANCHOR_QNAME : target.attrs["qname"];
81177
81243
  if (typeof relPath !== "string" || typeof qname !== "string") continue;
81178
- if (ignored(relPath) || ignored(qname)) continue;
81179
- planned.push({ e, target, symId: projectSymbolId(project.salt, project.projectId, relPath, qname, target.label) });
81244
+ if (ignored(relPath) || qname !== "" && ignored(qname)) continue;
81245
+ const line = e.attrs?.["anchorLine"];
81246
+ const wireAttrs = {
81247
+ ...anchorVersionAttrs(e.attrs),
81248
+ // PJ-anchor-labels: only for an opted-in project, and only through the
81249
+ // shared projection (invalid path/kind ⇒ no label, never half of one).
81250
+ ...project.anchorLabels ? anchorLabelAttrs({
81251
+ labelRelPath: normalizeSymbolPath(relPath),
81252
+ labelKind: target.label,
81253
+ ...isFile ? {} : { labelQname: qname },
81254
+ ...typeof line === "number" ? { labelLine: line } : {}
81255
+ }) : {}
81256
+ };
81257
+ planned.push({
81258
+ e,
81259
+ target,
81260
+ symId: projectSymbolId(project.salt, project.projectId, relPath, qname, target.label),
81261
+ wireAttrs
81262
+ });
81180
81263
  }
81181
81264
  if (planned.length === 0) continue;
81182
81265
  const dg = digest(
81183
- planned.map(({ e, symId }) => `ANCHORED_AT>${symId}>${JSON.stringify(anchorVersionAttrs(e.attrs))}`).sort()
81266
+ planned.map(({ symId, wireAttrs }) => `ANCHORED_AT>${symId}>${JSON.stringify(wireAttrs)}`).sort()
81184
81267
  );
81185
81268
  if (source.attrs["projectAnchorsContributedDigest"] === dg) continue;
81186
81269
  projectAnchorDigests[sourceId] = dg;
81187
- for (const { e, target, symId } of planned) {
81270
+ for (const { e, target, symId, wireAttrs } of planned) {
81188
81271
  if (!contextSeen.has(symId)) {
81189
81272
  contextSeen.add(symId);
81190
81273
  contextNodes.push({
@@ -81211,7 +81294,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
81211
81294
  stability: "unstable"
81212
81295
  });
81213
81296
  }
81214
- anchorEdges.push({ ...e, from: wireFrom, to: symId, attrs: anchorVersionAttrs(e.attrs) });
81297
+ anchorEdges.push({ ...e, from: wireFrom, to: symId, attrs: wireAttrs });
81215
81298
  }
81216
81299
  }
81217
81300
  }
@@ -81522,13 +81605,13 @@ function isInstalledEntry(entry) {
81522
81605
  }
81523
81606
  function foreignDaemonNote(lock, selfEntry) {
81524
81607
  if (!lock.entry || normEntry(lock.entry) === normEntry(selfEntry)) return null;
81525
- const what = isInstalledEntry(lock.entry) ? "another install" : "a LOCAL BUILD";
81526
- return [
81527
- `launched from ${what}, not this one${lock.version ? ` (it is ${lock.version})` : ""}:`,
81608
+ const runningInstalled = isInstalledEntry(lock.entry);
81609
+ const head2 = [
81610
+ `launched from ${runningInstalled ? "the installed package" : "a LOCAL BUILD"}, not this one${lock.version ? ` (it is ${lock.version})` : ""}:`,
81528
81611
  ` running: ${lock.entry}`,
81529
- ` this: ${selfEntry}`,
81530
- "`errata update` does not replace it. To move onto this install: errata stop, then errata dash."
81612
+ ` this: ${selfEntry}`
81531
81613
  ];
81614
+ return runningInstalled ? [...head2, "this CLI is a different bundle from the daemon it is talking to \u2014 its view may not match what the daemon runs."] : [...head2, "`errata update` does not replace it. To move onto the installed package: errata stop, then errata dash."];
81532
81615
  }
81533
81616
  function isProcessAlive(pid) {
81534
81617
  if (!pid || pid <= 0) return false;
@@ -81715,7 +81798,7 @@ init_src11();
81715
81798
  init_src();
81716
81799
  init_src11();
81717
81800
  var DEFAULT_LIMIT = 50;
81718
- var ANCHOR_SYMBOL_KINDS = [
81801
+ var ANCHOR_SYMBOL_KINDS2 = [
81719
81802
  "Function",
81720
81803
  "Class",
81721
81804
  "Method",
@@ -81736,7 +81819,7 @@ function classifyAnchorVersion(cloudAttrs, localSymbol) {
81736
81819
  function buildReverseSymbolMap(opts) {
81737
81820
  const { store, salt, projectId } = opts;
81738
81821
  const reverse = /* @__PURE__ */ new Map();
81739
- for (const kind of ANCHOR_SYMBOL_KINDS) {
81822
+ for (const kind of ANCHOR_SYMBOL_KINDS2) {
81740
81823
  for (const n of store.findNodeIdentitiesByLabel(kind)) {
81741
81824
  const { relPath, qname } = n;
81742
81825
  if (relPath === null || qname === null) continue;
@@ -81746,6 +81829,13 @@ function buildReverseSymbolMap(opts) {
81746
81829
  if (!reverse.has(twin)) reverse.set(twin, n.id);
81747
81830
  }
81748
81831
  }
81832
+ for (const n of store.findNodeIdentitiesByLabel("File")) {
81833
+ if (n.relPath === null) continue;
81834
+ const sym = projectSymbolId(salt, projectId, n.relPath, FILE_ANCHOR_QNAME, "File");
81835
+ if (!reverse.has(sym)) reverse.set(sym, n.id);
81836
+ const twin = projectSaltedNodeId(sym, salt);
81837
+ if (!reverse.has(twin)) reverse.set(twin, n.id);
81838
+ }
81749
81839
  return reverse;
81750
81840
  }
81751
81841
  function anchorConfidence(attrs) {
@@ -84482,12 +84572,21 @@ function withLaunchOrigin(pending) {
84482
84572
  return LOCAL_BUILD_ENTRY ? { ...pending, localBuildEntry: LOCAL_BUILD_ENTRY } : pending;
84483
84573
  }
84484
84574
  var projectSymbolSalts = /* @__PURE__ */ new Map();
84575
+ var PROJECT_SALT_REFRESH_MS = 10 * 6e4;
84485
84576
  async function resolveProjectSymbolSalt(client, projectId) {
84486
84577
  const cached2 = projectSymbolSalts.get(projectId);
84487
- if (cached2) return { projectId, salt: cached2 };
84488
- const { salt } = await client.projectSymbolSalt(projectId);
84489
- projectSymbolSalts.set(projectId, salt);
84490
- return { projectId, salt };
84578
+ if (cached2 && Date.now() - cached2.fetchedAt < PROJECT_SALT_REFRESH_MS) {
84579
+ return { projectId, salt: cached2.salt, anchorLabels: cached2.anchorLabels };
84580
+ }
84581
+ try {
84582
+ const res = await client.projectSymbolSalt(projectId);
84583
+ const entry = { salt: res.salt, anchorLabels: res.anchorLabels === true, fetchedAt: Date.now() };
84584
+ projectSymbolSalts.set(projectId, entry);
84585
+ return { projectId, salt: entry.salt, anchorLabels: entry.anchorLabels };
84586
+ } catch (err2) {
84587
+ if (cached2) return { projectId, salt: cached2.salt, anchorLabels: cached2.anchorLabels };
84588
+ throw err2;
84589
+ }
84491
84590
  }
84492
84591
  var WATERMARK_BATCH = 250;
84493
84592
  async function stampBatched(items, stamp) {
@@ -86612,32 +86711,92 @@ function hookRelayCommand(port, path2) {
86612
86711
  }
86613
86712
  var CODEX_HOOKS_BEGIN = `# >>> errata hooks (errata-managed)`;
86614
86713
  var CODEX_HOOKS_END = `# <<< errata hooks`;
86615
- function buildCodexHooksToml(port) {
86616
- const cmd2 = hookCurlCommand(port).replace(/"/g, '\\"');
86617
- const turnCmd = hookCurlCommand(port, "/api/turn").replace(/"/g, '\\"');
86618
- return `${CODEX_HOOKS_BEGIN}
86619
- [[hooks.PreToolUse]]
86620
- matcher = ".*"
86621
-
86622
- [[hooks.PreToolUse.hooks]]
86623
- type = "command"
86624
- command = "${cmd2}"
86625
- timeout = 10
86626
-
86627
- [[hooks.PostToolUse]]
86628
- matcher = ".*"
86629
-
86630
- [[hooks.PostToolUse.hooks]]
86631
- type = "command"
86632
- command = "${cmd2}"
86633
- timeout = 10
86634
-
86635
- [[hooks.PostToolUse.hooks]]
86636
- type = "command"
86637
- command = "${turnCmd}"
86638
- timeout = 10
86639
- ${CODEX_HOOKS_END}
86714
+ function buildCodexHooksToml(port, options) {
86715
+ const platform = options.platform ?? process.platform;
86716
+ const quote = (value) => {
86717
+ if (platform === "win32") {
86718
+ if (/["%\r\n]/.test(value)) throw new Error("Unsupported character in Codex hook path");
86719
+ return '"' + value + '"';
86720
+ }
86721
+ return "'" + value.replace(/'/g, "'\\''") + "'";
86722
+ };
86723
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid hook port");
86724
+ const command = quote(options.nodePath) + " " + quote(options.relayPath) + " " + port;
86725
+ const handlers = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse"].map(
86726
+ (event) => "[[hooks." + event + "]]\n" + (event === "UserPromptSubmit" ? "" : 'matcher = ".*"\n') + "\n[[hooks." + event + '.hooks]]\ntype = "command"\ncommand = ' + JSON.stringify(command) + "\ntimeout = 10\nadditionalContextLimit = 5000\n"
86727
+ );
86728
+ return CODEX_HOOKS_BEGIN + "\n" + handlers.join("\n") + CODEX_HOOKS_END + "\n";
86729
+ }
86730
+
86731
+ // src/codex-hooks.ts
86732
+ import { existsSync as existsSync33, mkdirSync as mkdirSync9, readFileSync as readFileSync32, writeFileSync as writeFileSync24 } from "node:fs";
86733
+ import { homedir as homedir7 } from "node:os";
86734
+ import { join as join37 } from "node:path";
86735
+
86736
+ // src/codex-hook-relay.ts
86737
+ var CODEX_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks codex
86738
+ import { readFileSync } from 'node:fs';
86739
+
86740
+ try {
86741
+ const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
86742
+ const port = Number(process.argv[2]);
86743
+ const event = payload.hook_event_name;
86744
+ if (!Number.isInteger(port) || port < 1 || port > 65535) process.exit(0);
86745
+ const contexts = [];
86746
+ const post = async (endpoint, relay = false) => {
86747
+ try {
86748
+ const response = await fetch('http://127.0.0.1:' + port + '/api/' + endpoint, {
86749
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
86750
+ body: JSON.stringify(payload), signal: AbortSignal.timeout(2000),
86751
+ });
86752
+ if (!response.ok || !relay) return;
86753
+ const result = await response.json();
86754
+ const context = result.hookSpecificOutput?.additionalContext;
86755
+ if (typeof context === 'string' && context.trim()) contexts.push(context);
86756
+ } catch { /* An unavailable daemon must never block the agent. */ }
86757
+ };
86758
+ if (event === 'PreToolUse' || event === 'PostToolUse') await post('hook', true);
86759
+ if (event === 'PostToolUse') await post('turn');
86760
+ if (['SessionStart', 'UserPromptSubmit', 'PostToolUse'].includes(event)) {
86761
+ // Run after capture, so changes already rendered by the daemon arrive now.
86762
+ // Background extraction finishing later arrives at the next tool boundary.
86763
+ await post('context-inject', true);
86764
+ }
86765
+ if (contexts.length) {
86766
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: {
86767
+ hookEventName: event, additionalContext: contexts.join('\n\n'),
86768
+ } }));
86769
+ }
86770
+ } catch { /* Invalid input is also fail-open. */ }
86640
86771
  `;
86772
+
86773
+ // src/codex-hooks.ts
86774
+ function hasCodexConfig(root, codexHome = process.env["CODEX_HOME"] || join37(homedir7(), ".codex")) {
86775
+ return existsSync33(join37(root, ".codex")) || existsSync33(codexHome);
86776
+ }
86777
+ function replaceCodexHooks(existing, block) {
86778
+ const begin = existing.indexOf(CODEX_HOOKS_BEGIN);
86779
+ const end = existing.indexOf(CODEX_HOOKS_END);
86780
+ if (begin >= 0 || end >= 0) {
86781
+ if (begin < 0 || end < begin || existing.indexOf(CODEX_HOOKS_BEGIN, begin + CODEX_HOOKS_BEGIN.length) >= 0) {
86782
+ throw new Error(
86783
+ "Incomplete or duplicate errata hooks markers in .codex/config.toml; repair them before reinstalling."
86784
+ );
86785
+ }
86786
+ return existing.slice(0, begin) + block.trimEnd() + existing.slice(end + CODEX_HOOKS_END.length);
86787
+ }
86788
+ return existing.trimEnd() + (existing.trim() ? "\n\n" : "") + block;
86789
+ }
86790
+ function writeCodexHooks(root, port, nodePath = process.execPath) {
86791
+ const dir = join37(root, ".codex");
86792
+ const file2 = join37(dir, "config.toml");
86793
+ const relay = join37(dir, "hooks", "errata-relay.mjs");
86794
+ const block = buildCodexHooksToml(port, { nodePath, relayPath: relay });
86795
+ const final = replaceCodexHooks(existsSync33(file2) ? readFileSync32(file2, "utf8") : "", block);
86796
+ mkdirSync9(join37(dir, "hooks"), { recursive: true });
86797
+ writeFileSync24(relay, CODEX_RELAY_SOURCE, "utf8");
86798
+ writeFileSync24(file2, final, "utf8");
86799
+ return file2;
86641
86800
  }
86642
86801
 
86643
86802
  // src/opencode-plugin.ts
@@ -86979,6 +87138,7 @@ Commands:
86979
87138
  Flags: --project <id> adopt an existing project (fork\u2192upstream);
86980
87139
  --remote <name> derive the locator from a non-origin remote;
86981
87140
  --name <name> mint a locator-less project; no flags = re-resolve now
87141
+ --skip-hooks leave existing harness configuration unchanged
86982
87142
  unlink Detach from the cloud project + disable ambient re-linking
86983
87143
  use [<handle>] Set the sticky session active agent the daemon acts as.
86984
87144
  No arg lists the agents you can act as + the current one;
@@ -87044,21 +87204,21 @@ async function cmdInit() {
87044
87204
  if (!skipHooks) {
87045
87205
  console.log("");
87046
87206
  console.log("installing harness hooks...");
87047
- const { existsSync: existsSync34 } = await import("node:fs");
87048
- const { join: join38 } = await import("node:path");
87207
+ const { existsSync: existsSync35 } = await import("node:fs");
87208
+ const { join: join39 } = await import("node:path");
87049
87209
  try {
87050
87210
  await installClaudeHooks(port);
87051
87211
  } catch (err2) {
87052
87212
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
87053
87213
  }
87054
- if (existsSync34(join38(ROOT, ".cursor"))) {
87214
+ if (existsSync35(join39(ROOT, ".cursor"))) {
87055
87215
  try {
87056
87216
  await installCursorMcpConfig();
87057
87217
  } catch (err2) {
87058
87218
  console.warn(` \u26A0\uFE0F Cursor MCP config failed: ${err2 instanceof Error ? err2.message : err2}`);
87059
87219
  }
87060
87220
  }
87061
- if (existsSync34(join38(ROOT, ".codex"))) {
87221
+ if (hasCodexConfig(ROOT)) {
87062
87222
  try {
87063
87223
  await installCodexHooks(port);
87064
87224
  } catch (err2) {
@@ -87235,9 +87395,9 @@ async function cmdStatus() {
87235
87395
  console.log(` stack: ${profile.stack.join(", ") || "(none)"}`);
87236
87396
  console.log(` languages: ${profile.languages.join(", ") || "(none)"}`);
87237
87397
  }
87238
- console.log(` graph db: ${existsSync33(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
87239
- console.log(` event log: ${existsSync33(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
87240
- if (existsSync33(paths.castalia)) {
87398
+ console.log(` graph db: ${existsSync34(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
87399
+ console.log(` event log: ${existsSync34(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
87400
+ if (existsSync34(paths.castalia)) {
87241
87401
  try {
87242
87402
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
87243
87403
  const store = openGraphStore2({ path: paths.castalia });
@@ -87872,6 +88032,7 @@ async function cmdLink(args2) {
87872
88032
  console.log(
87873
88033
  `consents: sync=${d.consents.sync} telemetry=${d.consents.telemetry ? "on" : "off"} packages=${applied.config.consent.contributePackages ? "on" : "off"} structure=${d.consents.structure ? "on" : "off"} history=${d.consents.mineHistory ? applied.profile.linkConsents?.contributeHistory ? "mine+contribute" : "mine" : "off"}`
87874
88034
  );
88035
+ await refreshLinkedCodexHooks(args2);
87875
88036
  if (applied.wroteIgnore) console.log("wrote .errataignore (starter) \u2014 edit it; it is your hard control");
87876
88037
  if (!hasCloudCredential(cfg)) {
87877
88038
  console.log(" note: run `errata login` so this daemon can sync under the link and report the history stage.");
@@ -87912,6 +88073,7 @@ async function cmdLink(args2) {
87912
88073
  profile.projectId = res.project.id;
87913
88074
  profile.projectLocator = profile.repoLocator;
87914
88075
  saveProfile(ROOT, profile);
88076
+ await refreshLinkedCodexHooks(args2);
87915
88077
  console.log(`linked (adopted): ${profile.repoLocator} \u2192 project ${res.project.id} (${res.project.name})`);
87916
88078
  return;
87917
88079
  }
@@ -87921,6 +88083,7 @@ async function cmdLink(args2) {
87921
88083
  profile.projectId = res.project.id;
87922
88084
  delete profile.projectLocator;
87923
88085
  saveProfile(ROOT, profile);
88086
+ await refreshLinkedCodexHooks(args2);
87924
88087
  console.log(`linked (locator-less): project ${res.project.id} (${res.project.name})`);
87925
88088
  return;
87926
88089
  }
@@ -87929,6 +88092,7 @@ async function cmdLink(args2) {
87929
88092
  delete profile.projectLocator;
87930
88093
  const out2 = await ensureProjectLink(ROOT, profile, client);
87931
88094
  if (out2.linked) {
88095
+ await refreshLinkedCodexHooks(args2);
87932
88096
  console.log(`linked: ${profile.repoLocator} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""}`);
87933
88097
  } else {
87934
88098
  console.error(`link failed (${out2.reason})${out2.detail ? `: ${out2.detail}` : ""}`);
@@ -88135,11 +88299,11 @@ function cmdInstallationProfile(args2) {
88135
88299
  }
88136
88300
  async function cmdReview() {
88137
88301
  const paths = workspacePaths(ROOT);
88138
- if (!existsSync33(paths.reviewQueue)) {
88302
+ if (!existsSync34(paths.reviewQueue)) {
88139
88303
  console.log("(review queue empty)");
88140
88304
  return;
88141
88305
  }
88142
- const queue = JSON.parse(readFileSync32(paths.reviewQueue, "utf8"));
88306
+ const queue = JSON.parse(readFileSync33(paths.reviewQueue, "utf8"));
88143
88307
  if (queue.length === 0) {
88144
88308
  console.log("(review queue empty)");
88145
88309
  return;
@@ -88810,7 +88974,7 @@ async function gatherRepo(store, ws) {
88810
88974
  };
88811
88975
  }
88812
88976
  async function gatherReportData(generatedAt) {
88813
- const { existsSync: existsSync34 } = await import("node:fs");
88977
+ const { existsSync: existsSync35 } = await import("node:fs");
88814
88978
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
88815
88979
  const cfg = loadConfig();
88816
88980
  const outbound = cfg.consent.sync ? "auto" : "off";
@@ -88818,7 +88982,7 @@ async function gatherReportData(generatedAt) {
88818
88982
  for (const ws of listWorkspaces()) {
88819
88983
  if (ws.missing) continue;
88820
88984
  const dbPath = workspacePaths(ws.path).castalia;
88821
- if (!existsSync34(dbPath)) continue;
88985
+ if (!existsSync35(dbPath)) continue;
88822
88986
  let store = null;
88823
88987
  try {
88824
88988
  store = openGraphStore2({ path: dbPath });
@@ -88849,7 +89013,7 @@ async function gatherReportData(generatedAt) {
88849
89013
  };
88850
89014
  }
88851
89015
  async function cmdReport(args2) {
88852
- const { mkdirSync: mkdirSync9, writeFileSync: writeFileSync24 } = await import("node:fs");
89016
+ const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync25 } = await import("node:fs");
88853
89017
  const { renderReport: renderReport2 } = await Promise.resolve().then(() => (init_report_render(), report_render_exports));
88854
89018
  const includeFutureVerbs = args2.includes("--future-verbs");
88855
89019
  const now = /* @__PURE__ */ new Date();
@@ -88860,10 +89024,10 @@ async function cmdReport(args2) {
88860
89024
  process.exit(2);
88861
89025
  }
88862
89026
  const outDir = workspacePaths(ROOT).configDir;
88863
- mkdirSync9(outDir, { recursive: true });
89027
+ mkdirSync10(outDir, { recursive: true });
88864
89028
  const files = renderReport2(data, { includeFutureVerbs });
88865
- for (const f of files) writeFileSync24(join37(outDir, f.name), f.html, "utf8");
88866
- const indexPath = join37(outDir, "report.html");
89029
+ for (const f of files) writeFileSync25(join38(outDir, f.name), f.html, "utf8");
89030
+ const indexPath = join38(outDir, "report.html");
88867
89031
  console.log(`report \u2192 ${indexPath}`);
88868
89032
  console.log(
88869
89033
  ` ${data.repos.length} repo(s) \xB7 ${files.length} file(s) \xB7 ${data.rollup.nodes.toLocaleString("en-US")} nodes`
@@ -88983,24 +89147,24 @@ async function cmdInstallHooks(args2) {
88983
89147
  }
88984
89148
  }
88985
89149
  async function installOpencodePlugin(port) {
88986
- const { mkdirSync: mkdirSync9, existsSync: existsSync34, readFileSync: readFileSync33, writeFileSync: writeFileSync24 } = await import("node:fs");
88987
- const { join: join38 } = await import("node:path");
88988
- const dir = join38(ROOT, ".opencode", "plugin");
88989
- if (!existsSync34(dir)) mkdirSync9(dir, { recursive: true });
88990
- const file2 = join38(dir, "inerrata.js");
88991
- if (existsSync34(file2)) {
88992
- const head2 = readFileSync33(file2, "utf8").split("\n")[0] ?? "";
89150
+ const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync34, writeFileSync: writeFileSync25 } = await import("node:fs");
89151
+ const { join: join39 } = await import("node:path");
89152
+ const dir = join39(ROOT, ".opencode", "plugin");
89153
+ if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
89154
+ const file2 = join39(dir, "inerrata.js");
89155
+ if (existsSync35(file2)) {
89156
+ const head2 = readFileSync34(file2, "utf8").split("\n")[0] ?? "";
88993
89157
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
88994
89158
  console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
88995
89159
  process.exit(2);
88996
89160
  }
88997
89161
  }
88998
- writeFileSync24(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
88999
- const cfgFile = join38(ROOT, "opencode.json");
89162
+ writeFileSync25(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
89163
+ const cfgFile = join39(ROOT, "opencode.json");
89000
89164
  let cfg = { $schema: "https://opencode.ai/config.json" };
89001
- if (existsSync34(cfgFile)) {
89165
+ if (existsSync35(cfgFile)) {
89002
89166
  try {
89003
- cfg = JSON.parse(readFileSync33(cfgFile, "utf8"));
89167
+ cfg = JSON.parse(readFileSync34(cfgFile, "utf8"));
89004
89168
  } catch {
89005
89169
  console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
89006
89170
  process.exit(2);
@@ -89011,7 +89175,7 @@ async function installOpencodePlugin(port) {
89011
89175
  if (!hadErrata) {
89012
89176
  const inv = errataMcpInvocation();
89013
89177
  cfg.mcp["errata"] = { type: "local", command: [inv.command, ...inv.args] };
89014
- writeFileSync24(cfgFile, JSON.stringify(cfg, null, 2) + "\n", "utf8");
89178
+ writeFileSync25(cfgFile, JSON.stringify(cfg, null, 2) + "\n", "utf8");
89015
89179
  }
89016
89180
  console.log(`installed OpenCode plugin \u2192 ${file2}`);
89017
89181
  if (hadErrata) console.log(` mcp.errata already present in ${cfgFile} \u2014 left untouched`);
@@ -89031,15 +89195,15 @@ function errataMcpInvocation() {
89031
89195
  return { command: cmd2, args: args2 };
89032
89196
  }
89033
89197
  async function installClaudeHooks(port) {
89034
- const { mkdirSync: mkdirSync9, existsSync: existsSync34, readFileSync: readFileSync33, writeFileSync: writeFileSync24 } = await import("node:fs");
89035
- const { join: join38 } = await import("node:path");
89036
- const dir = join38(ROOT, ".claude");
89037
- if (!existsSync34(dir)) mkdirSync9(dir, { recursive: true });
89038
- const file2 = join38(dir, "settings.json");
89198
+ const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync34, writeFileSync: writeFileSync25 } = await import("node:fs");
89199
+ const { join: join39 } = await import("node:path");
89200
+ const dir = join39(ROOT, ".claude");
89201
+ if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
89202
+ const file2 = join39(dir, "settings.json");
89039
89203
  let settings = {};
89040
- if (existsSync34(file2)) {
89204
+ if (existsSync35(file2)) {
89041
89205
  try {
89042
- settings = JSON.parse(readFileSync33(file2, "utf8"));
89206
+ settings = JSON.parse(readFileSync34(file2, "utf8"));
89043
89207
  } catch {
89044
89208
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
89045
89209
  process.exit(2);
@@ -89085,10 +89249,10 @@ async function installClaudeHooks(port) {
89085
89249
  dropErrata(list);
89086
89250
  list.push({ hooks: [{ type: "command", command: injectCmd }] });
89087
89251
  }
89088
- writeFileSync24(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
89252
+ writeFileSync25(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
89089
89253
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
89090
89254
  await installClaudeMcpConfig();
89091
- const claudeMd = join38(ROOT, "CLAUDE.md");
89255
+ const claudeMd = join39(ROOT, "CLAUDE.md");
89092
89256
  const recall = writeManagedBlock(claudeMd, { body: RECALL_FIRST_BLOCK });
89093
89257
  if (recall.kind === "collision") {
89094
89258
  console.warn(
@@ -89100,15 +89264,15 @@ async function installClaudeHooks(port) {
89100
89264
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
89101
89265
  }
89102
89266
  async function installClaudeMcpConfig() {
89103
- const { mkdirSync: mkdirSync9, existsSync: existsSync34, readFileSync: readFileSync33, writeFileSync: writeFileSync24 } = await import("node:fs");
89104
- const { join: join38, dirname: dirname13 } = await import("node:path");
89105
- const file2 = join38(ROOT, ".mcp.json");
89267
+ const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync34, writeFileSync: writeFileSync25 } = await import("node:fs");
89268
+ const { join: join39, dirname: dirname13 } = await import("node:path");
89269
+ const file2 = join39(ROOT, ".mcp.json");
89106
89270
  const dir = dirname13(file2);
89107
- if (!existsSync34(dir)) mkdirSync9(dir, { recursive: true });
89271
+ if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
89108
89272
  let cfg = {};
89109
- if (existsSync34(file2)) {
89273
+ if (existsSync35(file2)) {
89110
89274
  try {
89111
- cfg = JSON.parse(readFileSync33(file2, "utf8"));
89275
+ cfg = JSON.parse(readFileSync34(file2, "utf8"));
89112
89276
  } catch {
89113
89277
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
89114
89278
  process.exit(2);
@@ -89116,21 +89280,21 @@ async function installClaudeMcpConfig() {
89116
89280
  }
89117
89281
  cfg.mcpServers ??= {};
89118
89282
  cfg.mcpServers["errata"] = errataMcpInvocation();
89119
- writeFileSync24(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
89283
+ writeFileSync25(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
89120
89284
  console.log(`installed Claude Code MCP server config \u2192 ${file2}`);
89121
89285
  console.log(` Claude Code will spawn \`errata mcp\` on workspace open.`);
89122
89286
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
89123
89287
  }
89124
89288
  async function installCursorMcpConfig() {
89125
- const { mkdirSync: mkdirSync9, existsSync: existsSync34, readFileSync: readFileSync33, writeFileSync: writeFileSync24 } = await import("node:fs");
89126
- const { join: join38 } = await import("node:path");
89127
- const dir = join38(ROOT, ".cursor");
89128
- if (!existsSync34(dir)) mkdirSync9(dir, { recursive: true });
89129
- const file2 = join38(dir, "mcp.json");
89289
+ const { mkdirSync: mkdirSync10, existsSync: existsSync35, readFileSync: readFileSync34, writeFileSync: writeFileSync25 } = await import("node:fs");
89290
+ const { join: join39 } = await import("node:path");
89291
+ const dir = join39(ROOT, ".cursor");
89292
+ if (!existsSync35(dir)) mkdirSync10(dir, { recursive: true });
89293
+ const file2 = join39(dir, "mcp.json");
89130
89294
  let cfg = {};
89131
- if (existsSync34(file2)) {
89295
+ if (existsSync35(file2)) {
89132
89296
  try {
89133
- cfg = JSON.parse(readFileSync33(file2, "utf8"));
89297
+ cfg = JSON.parse(readFileSync34(file2, "utf8"));
89134
89298
  } catch {
89135
89299
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
89136
89300
  process.exit(2);
@@ -89138,7 +89302,7 @@ async function installCursorMcpConfig() {
89138
89302
  }
89139
89303
  cfg.mcpServers ??= {};
89140
89304
  cfg.mcpServers["errata"] = errataMcpInvocation();
89141
- writeFileSync24(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
89305
+ writeFileSync25(file2, JSON.stringify(cfg, null, 2) + "\n", "utf8");
89142
89306
  console.log(`installed Cursor MCP server config \u2192 ${file2}`);
89143
89307
  console.log(` Cursor will spawn \`errata mcp\` on workspace open.`);
89144
89308
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses`);
@@ -89146,39 +89310,21 @@ async function installCursorMcpConfig() {
89146
89310
  console.log(` \u26A0\uFE0F reload Cursor (Cmd/Ctrl-Shift-P \u2192 "Reload Window") to pick it up.`);
89147
89311
  }
89148
89312
  async function installCodexHooks(port) {
89149
- const { mkdirSync: mkdirSync9, existsSync: existsSync34, readFileSync: readFileSync33, writeFileSync: writeFileSync24 } = await import("node:fs");
89150
- const { join: join38 } = await import("node:path");
89151
- const dir = join38(ROOT, ".codex");
89152
- if (!existsSync34(dir)) mkdirSync9(dir, { recursive: true });
89153
- const file2 = join38(dir, "config.toml");
89154
- let existing = "";
89155
- if (existsSync34(file2)) {
89156
- existing = readFileSync33(file2, "utf8");
89157
- const beginIdx = existing.indexOf(CODEX_HOOKS_BEGIN);
89158
- const endIdx = existing.indexOf(CODEX_HOOKS_END);
89159
- if (beginIdx >= 0 && endIdx > beginIdx) {
89160
- existing = existing.slice(0, beginIdx).trimEnd() + existing.slice(endIdx + CODEX_HOOKS_END.length).trimStart();
89161
- }
89162
- }
89163
- const block = buildCodexHooksToml(port);
89164
- const final = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
89165
-
89166
- ${block}` : existing + (existing.endsWith("\n\n") ? "" : "\n") + block;
89167
- writeFileSync24(file2, final, "utf8");
89313
+ const file2 = writeCodexHooks(ROOT, port);
89168
89314
  console.log(`installed Codex hooks \u2192 ${file2}`);
89169
- console.log(` endpoint: http://127.0.0.1:${port}/api/hook (+ /api/turn on PostToolUse)`);
89170
- console.log("");
89171
- console.log(` Codex reads files via the shell, so the virtual graph surface`);
89172
- console.log(` works by \`cat .errata/g/<verb>/<seed>\` (e.g. cat .errata/g/burst/foo).`);
89173
- console.log("");
89174
- console.log(` Codex has no Stop/SessionEnd hook at this installed version, so`);
89175
- console.log(` [!\u2026]/(fix:[\u2026]) tags are harvested via the PostToolUse-triggered`);
89176
- console.log(` /api/turn call above instead \u2014 every turn with a tool call is`);
89177
- console.log(` covered; a tool-less final turn is picked up on this workspace's`);
89178
- console.log(` next daemon boot replay. Re-run this after a Codex upgrade in`);
89179
- console.log(` case a newer version adds a real turn-boundary hook.`);
89180
- console.log(` If [features] hooks=false in your config.toml, set it true. Hook`);
89181
- console.log(` schemas evolve \u2014 if hooks stop firing after an update, re-run this.`);
89315
+ console.log(" Context loads on SessionStart/UserPromptSubmit and refreshes after tools when changed.");
89316
+ console.log(" Tool capture and /api/turn harvesting remain enabled.");
89317
+ console.log(" Restart/resume Codex to reload config, then review and trust the hooks with /hooks.");
89318
+ console.log(" If your Codex version gates hooks, enable [features] codex_hooks = true.");
89319
+ console.log(" Older Codex versions may not support every event; update if hooks do not fire.");
89320
+ }
89321
+ async function refreshLinkedCodexHooks(args2) {
89322
+ if (args2.includes("--skip-hooks") || !hasCodexConfig(ROOT)) return;
89323
+ try {
89324
+ await installCodexHooks(resolveHookPort());
89325
+ } catch (err2) {
89326
+ console.warn(` Codex hook refresh failed: ${err2 instanceof Error ? err2.message : err2}`);
89327
+ }
89182
89328
  }
89183
89329
  function printAiderInstructions() {
89184
89330
  console.log(`Aider doesn't expose programmatic per-tool hooks, so the`);
@@ -89508,7 +89654,7 @@ async function cmdDash(args2) {
89508
89654
  await yieldToLoop2();
89509
89655
  try {
89510
89656
  const items = selectDurableMemory(handle2.sharedStore, r.engine.profile);
89511
- const res = bleedRules(join37(r.root, ".claude", "rules"), items);
89657
+ const res = bleedRules(join38(r.root, ".claude", "rules"), items);
89512
89658
  if (res.written || res.pruned) {
89513
89659
  console.log(
89514
89660
  `[rules:${r.entry.name}] ${res.written} collective principle(s) \u2192 .claude/rules` + (res.pruned ? `, ${res.pruned} pruned` : "")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.2506",
3
+ "version": "2.0.6-dev.2533",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -15140,7 +15140,7 @@ var init_symbol_intent = __esm({
15140
15140
  });
15141
15141
 
15142
15142
  // ../../packages/shared/src/wire.ts
15143
- var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15143
+ var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, ANCHOR_SYMBOL_KINDS, ANCHOR_LABEL_KINDS, ANCHOR_LABEL_PATH_RE, ANCHOR_LABEL_QNAME_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15144
15144
  var init_wire = __esm({
15145
15145
  "../../packages/shared/src/wire.ts"() {
15146
15146
  "use strict";
@@ -15243,6 +15243,21 @@ var init_wire = __esm({
15243
15243
  });
15244
15244
  ANCHOR_COMMIT_SHA_RE = /^[0-9a-f]{7,40}$/;
15245
15245
  ANCHOR_BODY_HASH_RE = /^[0-9a-f]{8,64}$/;
15246
+ ANCHOR_SYMBOL_KINDS = [
15247
+ "Function",
15248
+ "Class",
15249
+ "Method",
15250
+ "Module",
15251
+ "Interface",
15252
+ "Const",
15253
+ "Enum",
15254
+ "TypeAlias",
15255
+ "Namespace",
15256
+ "Property"
15257
+ ];
15258
+ ANCHOR_LABEL_KINDS = [...ANCHOR_SYMBOL_KINDS, "File"];
15259
+ ANCHOR_LABEL_PATH_RE = /^(?![/])(?![A-Za-z]:)(?!(?:.*\/)?\.\.(?:\/|$))[^\x00-\x1f\x7f\\`]{1,512}$/;
15260
+ ANCHOR_LABEL_QNAME_RE = /^[^\x00-\x1f\x7f`]{1,256}$/;
15246
15261
  CastaliaIngestEdgeSchema = external_exports.object({
15247
15262
  fromCanonicalId: external_exports.string().min(6).max(160),
15248
15263
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15262,7 +15277,17 @@ var init_wire = __esm({
15262
15277
  * classify the anchor current / pre-change; absent ⇒ the pre-versioning
15263
15278
  * contract, unverified. Only meaningful on `ANCHORED_AT`. */
15264
15279
  observedAtCommit: external_exports.string().regex(ANCHOR_COMMIT_SHA_RE).optional(),
15265
- symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional()
15280
+ symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional(),
15281
+ /** PJ-anchor-labels: the READABLE identity of the anchor's target —
15282
+ * repo-relative path, qualified name (absent for a File), kind and line.
15283
+ * Sent only for a project whose owner opted in; the door strips them again
15284
+ * for any project that has not (the daemon's flag is not trusted alone).
15285
+ * Prefixed `label*` so no local edge attr (`anchorLine` lives there
15286
+ * natively) can ride out under a shared key. */
15287
+ labelRelPath: external_exports.string().regex(ANCHOR_LABEL_PATH_RE).optional(),
15288
+ labelQname: external_exports.string().regex(ANCHOR_LABEL_QNAME_RE).optional(),
15289
+ labelKind: external_exports.enum(ANCHOR_LABEL_KINDS).optional(),
15290
+ labelLine: external_exports.number().int().min(1).max(1e7).optional()
15266
15291
  }).optional()
15267
15292
  });
15268
15293
  SymbolSummarySidecarSchema = external_exports.record(