@inerrata-corporation/errata 2.0.8 → 2.0.10

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
@@ -15732,6 +15732,13 @@ function isSeedProvenance(source) {
15732
15732
  function isAnchorLabelKind(kind) {
15733
15733
  return typeof kind === "string" && ANCHOR_LABEL_KINDS.includes(kind);
15734
15734
  }
15735
+ function triageDiscriminatorAttrs(type, attrs) {
15736
+ if (!["CONFIRMS", "INDICATES", "ROUTES_TO"].includes(type)) return {};
15737
+ const value = attrs?.["discriminator"];
15738
+ if (typeof value !== "string") return {};
15739
+ const test = value.trim();
15740
+ return test && test.length <= TRIAGE_DISCRIMINATOR_MAX_LENGTH && !(test.startsWith("<") && test.endsWith(">")) ? { discriminator: test } : {};
15741
+ }
15735
15742
  function anchorVersionAttrs(attrs) {
15736
15743
  if (!attrs) return {};
15737
15744
  const commit = attrs["observedAtCommit"];
@@ -15792,7 +15799,7 @@ function summarizeIngestResult(r) {
15792
15799
  ...r.patternReconciliation ? { patternReconciliation: r.patternReconciliation } : {}
15793
15800
  };
15794
15801
  }
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;
15802
+ 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, TRIAGE_DISCRIMINATOR_MAX_LENGTH, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15796
15803
  var init_wire = __esm({
15797
15804
  "../../packages/shared/src/wire.ts"() {
15798
15805
  "use strict";
@@ -15915,6 +15922,7 @@ var init_wire = __esm({
15915
15922
  ANCHOR_LABEL_PATH_RE = /^(?![/])(?![A-Za-z]:)(?!(?:.*\/)?\.\.(?:\/|$))[^\x00-\x1f\x7f\\`]{1,512}$/;
15916
15923
  ANCHOR_LABEL_QNAME_RE = /^[^\x00-\x1f\x7f`]{1,256}$/;
15917
15924
  ANCHOR_LABEL_KEYS = ["labelRelPath", "labelQname", "labelKind", "labelLine"];
15925
+ TRIAGE_DISCRIMINATOR_MAX_LENGTH = 2e3;
15918
15926
  CastaliaIngestEdgeSchema = external_exports.object({
15919
15927
  fromCanonicalId: external_exports.string().min(6).max(160),
15920
15928
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15925,6 +15933,8 @@ var init_wire = __esm({
15925
15933
  /** Triage route per-context counts (T3 induction). Carried as structured data
15926
15934
  * here; stored JSON-encoded on the Neo4j rel (`perContextJson`). */
15927
15935
  perContext: external_exports.record(external_exports.string(), RouteContextCountWireSchema).optional(),
15936
+ /** Scrubbed diagnostic check for a triage route, re-scanned at the cloud door. */
15937
+ discriminator: external_exports.string().trim().min(1).max(TRIAGE_DISCRIMINATOR_MAX_LENGTH).optional(),
15928
15938
  /** PJ-anchor-version: WHICH version of a symbol an `ANCHORED_AT` was
15929
15939
  * witnessed against. Both opaque — the observing checkout's HEAD sha and
15930
15940
  * the indexer's 16-hex sha256 of the symbol body. Neither is reversible
@@ -16827,6 +16837,7 @@ __export(src_exports, {
16827
16837
  TAXONOMY_EDGES: () => TAXONOMY_EDGES,
16828
16838
  TOOL_KINDS: () => TOOL_KINDS,
16829
16839
  TRANSFER_EDGES: () => TRANSFER_EDGES,
16840
+ TRIAGE_DISCRIMINATOR_MAX_LENGTH: () => TRIAGE_DISCRIMINATOR_MAX_LENGTH,
16830
16841
  TRIAGE_EDGES: () => TRIAGE_EDGES,
16831
16842
  TRUTH_KIND: () => TRUTH_KIND,
16832
16843
  UnregisteredGraphEventError: () => UnregisteredGraphEventError,
@@ -16910,6 +16921,7 @@ __export(src_exports, {
16910
16921
  summaryRejectionReason: () => summaryRejectionReason,
16911
16922
  toCloudAttrs: () => toCloudAttrs,
16912
16923
  toolCanonicalId: () => toolCanonicalId,
16924
+ triageDiscriminatorAttrs: () => triageDiscriminatorAttrs,
16913
16925
  unregisteredGraphEventKeys: () => unregisteredGraphEventKeys,
16914
16926
  validateCastaliaPayload: () => validateCastaliaPayload,
16915
16927
  versionlessPurl: () => versionlessPurl,
@@ -17438,7 +17450,7 @@ function rowToEdge(r) {
17438
17450
  function openGraphStore(opts) {
17439
17451
  return new SqliteGraphStore(opts);
17440
17452
  }
17441
- var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, SqliteGraphStore;
17453
+ var LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, SqliteGraphStore;
17442
17454
  var init_store = __esm({
17443
17455
  "../../packages/local-graph/src/store.ts"() {
17444
17456
  "use strict";
@@ -17559,6 +17571,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
17559
17571
  `;
17560
17572
  TEMPORAL_INDEX_SQL = `
17561
17573
  CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
17574
+ -- Covering + partial index for labelFingerprint over the instance labels: the
17575
+ -- live-push probe runs it per workspace on a timer, and each hit used to read
17576
+ -- its table row. Partial, so it maintains a few thousand rows instead of adding
17577
+ -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
17578
+ DROP INDEX IF EXISTS nodes_live_fingerprint;
17579
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
17562
17580
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
17563
17581
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
17564
17582
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -17572,6 +17590,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
17572
17590
  -- every edited file of every turn, and a scan parses every Problem's attrs.
17573
17591
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
17574
17592
  `;
17593
+ FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
17575
17594
  SqliteGraphStore = class {
17576
17595
  db;
17577
17596
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -18048,6 +18067,32 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18048
18067
  attrs: JSON.parse(r.attrs_json)
18049
18068
  }));
18050
18069
  }
18070
+ *iterateNodeIdentitiesByLabel(label, pageSize = 2e3) {
18071
+ const page = this.db.prepare(
18072
+ "SELECT rowid AS rid, id, json_extract(attrs_json, '$.relPath') AS relPath, json_extract(attrs_json, '$.qname') AS qname FROM nodes WHERE rowid > ? AND label = ? AND valid_to IS NULL ORDER BY rowid LIMIT ?"
18073
+ );
18074
+ let after = 0;
18075
+ for (; ; ) {
18076
+ const rows = page.all(after, label, pageSize);
18077
+ for (const r of rows) {
18078
+ yield {
18079
+ id: r.id,
18080
+ relPath: typeof r.relPath === "string" ? r.relPath : null,
18081
+ qname: typeof r.qname === "string" ? r.qname : null
18082
+ };
18083
+ }
18084
+ if (rows.length < pageSize) return;
18085
+ after = rows[rows.length - 1].rid;
18086
+ }
18087
+ }
18088
+ recentNodeIds(label, limit) {
18089
+ const rows = this.db.prepare("SELECT id FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(label, Math.max(0, limit));
18090
+ return rows.map((r) => r.id);
18091
+ }
18092
+ hasLiveNodes() {
18093
+ const r = this.db.prepare("SELECT EXISTS(SELECT 1 FROM nodes WHERE valid_to IS NULL) AS e").get();
18094
+ return r.e === 1;
18095
+ }
18051
18096
  findNodeIdentitiesByLabel(label) {
18052
18097
  const rows = this.stmts.identitiesByLabel.all(label);
18053
18098
  return rows.map((r) => ({
@@ -18195,6 +18240,15 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18195
18240
  }
18196
18241
  labelFingerprint(labels) {
18197
18242
  if (labels.length === 0) return "0";
18243
+ if (labels.length === FINGERPRINT_INDEXED_LABELS.length && FINGERPRINT_INDEXED_LABELS.every((l) => labels.includes(l))) {
18244
+ const r2 = this.db.prepare(
18245
+ `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18246
+ MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
18247
+ FROM nodes INDEXED BY nodes_instance_fingerprint
18248
+ WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
18249
+ ).get();
18250
+ return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
18251
+ }
18198
18252
  const r = this.db.prepare(
18199
18253
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18200
18254
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
@@ -22578,7 +22632,8 @@ function pendingDiscriminators(l2, opts = {}) {
22578
22632
  const perContext = e.attrs["perContext"] ?? {};
22579
22633
  const global2 = perContext[TRIAGE_GLOBAL_BUCKET] ?? {};
22580
22634
  const observations = Number(global2.confirmed ?? 0);
22581
- const sources = new Set(global2.contributors ?? []).size;
22635
+ const inferred = new Set(global2.inferredContributors ?? []);
22636
+ const sources = new Set((global2.contributors ?? []).filter((id) => !inferred.has(id))).size;
22582
22637
  if (sources < minSources || observations < minObservations) continue;
22583
22638
  out2.push({ route: e.id, presenting: triage.description, cause: "", sources, observations, causeId: e.to });
22584
22639
  }
@@ -22596,7 +22651,7 @@ function markDiscriminatorAsked(l2, routeIds, ts) {
22596
22651
  }
22597
22652
  }
22598
22653
  function promoteRouteWithDiscriminator(l2, routeId, discriminator, ts, source) {
22599
- const test = discriminator.trim();
22654
+ const test = triageDiscriminatorAttrs("CONFIRMS", { discriminator }).discriminator;
22600
22655
  if (!test) return false;
22601
22656
  if (isUnfilledPlaceholder(test)) return false;
22602
22657
  const existing = l2.getEdge(routeId);
@@ -22698,9 +22753,12 @@ function parseTriageFences(text) {
22698
22753
  return out2;
22699
22754
  }
22700
22755
  function harvestTriageFences(l2, text, opts) {
22756
+ const seen = [...text.matchAll(/```errata-triage[^\n]*\n([\s\S]*?)```/g)].length;
22701
22757
  let recorded = 0;
22702
22758
  let promoted = 0;
22703
22759
  for (const f of parseTriageFences(text)) {
22760
+ if ([f.presenting, f.cause, f.test, f.fix, f.route].some((value) => value && isUnfilledPlaceholder(value))) continue;
22761
+ if (f.test && !triageDiscriminatorAttrs("CONFIRMS", { discriminator: f.test }).discriminator) continue;
22704
22762
  if (f.route && f.test) {
22705
22763
  if (promoteRouteWithDiscriminator(l2, f.route.trim(), f.test, opts.ts, opts.contributor)) promoted++;
22706
22764
  continue;
@@ -22736,7 +22794,7 @@ function harvestTriageFences(l2, text, opts) {
22736
22794
  }
22737
22795
  recorded++;
22738
22796
  }
22739
- return { recorded, promoted };
22797
+ return { seen, recorded, promoted, rejected: seen - recorded - promoted };
22740
22798
  }
22741
22799
  var COOCCUR_BONUS, routeEdgeId, ROUTE_EDGE_TYPES, routeTypeFor, isRouterRoute, INDUCTED_MARK, DRIFT_VALUES;
22742
22800
  var init_triage2 = __esm({
@@ -23978,6 +24036,7 @@ __export(src_exports2, {
23978
24036
  CODE_REACH_EDGES: () => CODE_REACH_EDGES,
23979
24037
  CREDIT_FAMILY: () => CREDIT_FAMILY,
23980
24038
  DEFAULT_MIN_COVERAGE: () => DEFAULT_MIN_COVERAGE,
24039
+ FINGERPRINT_INDEXED_LABELS: () => FINGERPRINT_INDEXED_LABELS,
23981
24040
  FIX_CANDIDATE_ASK_LIMIT: () => FIX_CANDIDATE_ASK_LIMIT,
23982
24041
  FOLD_EMBED_AUTO_COSINE: () => FOLD_EMBED_AUTO_COSINE,
23983
24042
  FOLD_JUDGE_MIN_COSINE: () => FOLD_JUDGE_MIN_COSINE,
@@ -24353,7 +24412,7 @@ function renderSnapshot(s) {
24353
24412
  lines.push("");
24354
24413
  lines.push("### \u270D\uFE0F CAPTURE PROTOCOL \u2014 tags are the record");
24355
24414
  lines.push(
24356
- "errata records ONLY inline tags: `[!one line]` for every problem you state, `(fix:[id])` when your edit fixed a shown problem, and to JUDGE a prior shown below \u2014 `(confirm:[id])` if it held, `(refute:[id] why)` if it's wrong or didn't hold. Untagged prose is silently lost \u2014 tag as you write (no fences), we filter downstream."
24415
+ "For problems and fixes, errata records inline tags: `[!one line]` for every problem you state, `(fix:[id])` when your edit fixed a shown problem, and to JUDGE a prior shown below \u2014 `(confirm:[id])` if it held, `(refute:[id] why)` if it's wrong or didn't hold. Untagged prose is silently lost \u2014 tag as you write. Requested errata-* answer blocks keep their fences; put them in your turn-final reply."
24357
24416
  );
24358
24417
  lines.push("");
24359
24418
  lines.push(SEARCH_IMPERATIVE_HEADER);
@@ -24639,7 +24698,7 @@ function renderSnapshot(s) {
24639
24698
  for (const item of reviewItems) {
24640
24699
  if (item.kind === "discriminator") {
24641
24700
  lines.push(
24642
- `- **A recurring diagnosis is one cheap check from promoting** \u2014 "${item.presenting}" usually turns out to be: ${item.cause}. If (and only if) you KNOW a cheap ante-hoc check that confirms it, answer in your reply:`
24701
+ `- **A recurring diagnosis is one cheap check from promoting** \u2014 "${item.presenting}" usually turns out to be: ${item.cause}. If (and only if) you KNOW a cheap ante-hoc check that confirms it, fill this fenced block in your turn-final reply:`
24643
24702
  );
24644
24703
  lines.push(" ```errata-triage");
24645
24704
  lines.push(` route: ${item.route}`);
@@ -24647,7 +24706,7 @@ function renderSnapshot(s) {
24647
24706
  lines.push(" ```");
24648
24707
  } else {
24649
24708
  lines.push(
24650
- `- **Name the principle this cluster of your beliefs generalizes** \u2014 it ships to the collective only once named, and the prose must cover ALL members in one sentence:`
24709
+ `- **Name the principle this cluster of your beliefs generalizes** \u2014 it ships to the collective only once named. Cover ALL members in one sentence; fill this fenced block in your turn-final reply:`
24651
24710
  );
24652
24711
  for (const m of item.members) lines.push(` - ${m.description}`);
24653
24712
  lines.push(" ```errata-abstraction");
@@ -25153,6 +25212,7 @@ function toWirePayload(batch, runId) {
25153
25212
  attrs: {
25154
25213
  ...typeof e.confidence === "number" ? { confidence: clamp012(e.confidence) } : {},
25155
25214
  ...perContext ? { perContext } : {},
25215
+ ...triageDiscriminatorAttrs(e.type, e.attrs),
25156
25216
  // PJ-anchor-version: the two opaque version stamps ride on anchors only.
25157
25217
  ...e.type === "ANCHORED_AT" ? anchorVersionAttrs(e.attrs) : {},
25158
25218
  // PJ-anchor-labels: readable labels ride only when the producer set the
@@ -25759,6 +25819,7 @@ var init_client = __esm({
25759
25819
  if (q.seed?.length) qs.set("seed", q.seed.join(","));
25760
25820
  if (q.q) qs.set("q", q.q);
25761
25821
  if (q.channel) qs.set("channel", q.channel);
25822
+ if (q.includeTriage) qs.set("includeTriage", "true");
25762
25823
  const publicCtx = process.env["ERRATA_PRIMING_PUBLIC_CONTEXT"];
25763
25824
  const usePublicCtx = Boolean(publicCtx && publicCtx !== "0" && q.channel === "priming");
25764
25825
  const UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -28973,7 +29034,7 @@ function buildAgentInstruction(opts = {}) {
28973
29034
  const signals = opts.signals ?? ["prior", "problem", "domain", "fix", "constraint", "triage", "attempt", "link"];
28974
29035
  const ref = opts.referent ?? "its-handle";
28975
29036
  const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : s === "attempt" ? TAG_EXAMPLE.tried() : s === "domain" ? TAG_EXAMPLE.domain() : s === "link" ? TAG_EXAMPLE.pattern() : s === "verdict" ? TAG_EXAMPLE.confirm(ref) : TAG_EXAMPLE[s](ref);
28976
- const head2 = `We tag the priors we show you with a short handle like [${opts.handleExample ?? "chokidar-glob"}]. Tags are the ONLY signal recorded \u2014 anything you state but don't tag is silently lost. Weave each tag inline in your prose as you state it (no fences or backticks, no extra calls); tag everything, we filter downstream:`;
29037
+ const head2 = `We tag the priors we show you with a short handle like [${opts.handleExample ?? "chokidar-glob"}]. Untagged prose is silently lost. Put each tag inline in prose (no fences or backticks). Requested errata-* answer blocks are the exception: keep their fences in your turn-final reply. Tag everything, we filter downstream:`;
28977
29038
  const tail = "Mid-turn text (between tool calls) can be dropped by the harness \u2014 RESTATE every tag in your turn-final message; the final message is what reliably survives \u2014 EXCEPT one already shown above with an id: that is captured, so cite `[its-handle]` rather than retyping it (drift mints a duplicate).";
28978
29039
  return [
28979
29040
  head2,
@@ -29131,6 +29192,12 @@ function statuslineText(sessionId, now = Date.now()) {
29131
29192
  if (!hit || now - hit.at > STATUS_CARD_TTL_MS) return IDLE_STATUS;
29132
29193
  return renderStatusCard(hit.card);
29133
29194
  }
29195
+ function statuslineDir(body2) {
29196
+ const ws = body2["workspace"];
29197
+ const current = ws && typeof ws === "object" ? ws["current_dir"] : void 0;
29198
+ if (typeof current === "string" && current) return current;
29199
+ return typeof body2["cwd"] === "string" && body2["cwd"] ? body2["cwd"] : null;
29200
+ }
29134
29201
  function recallCardEnabled(env2 = process.env) {
29135
29202
  const v = (env2["ERRATA_RECALL_CARD"] ?? "").trim().toLowerCase();
29136
29203
  return !(v === "0" || v === "off" || v === "false" || v === "no");
@@ -29457,6 +29524,18 @@ function resolveWorkspaceIdentity(cfg, entry, env2 = process.env) {
29457
29524
  function identityKey(id) {
29458
29525
  return id.profileName ?? "@default";
29459
29526
  }
29527
+ function publicWorkspaceIdentity(identity) {
29528
+ return {
29529
+ profileName: identity.profileName,
29530
+ rule: identity.rule,
29531
+ invalid: identity.invalid,
29532
+ reason: identity.reason,
29533
+ profile: identity.profile ? {
29534
+ installationId: identity.profile.installationId,
29535
+ cloudUrl: identity.profile.cloudUrl
29536
+ } : null
29537
+ };
29538
+ }
29460
29539
  function blockedCloudClient(reason) {
29461
29540
  return new Proxy(
29462
29541
  {},
@@ -41839,7 +41918,7 @@ function takeDiscriminatorNudge(path2) {
41839
41918
  return [
41840
41919
  `\u26A1 errata \u2014 a known wrong-door is one field from ranking: "${top.presenting.slice(0, 90)}"`,
41841
41920
  ` usually turns out to be: ${top.cause.slice(0, 90)} (seen ${top.observations}x)`,
41842
- " Know a CHEAP check that confirms it? Answer in-flow \u2014 it promotes the route so the",
41921
+ " Know a CHEAP check that confirms it? Fill this fenced block in your turn-final reply so the",
41843
41922
  " next agent gets it first. Only if you actually know one: a guessed test ranks first",
41844
41923
  " and misroutes everyone after you.",
41845
41924
  " ```errata-triage",
@@ -43356,7 +43435,7 @@ var init_mcp = __esm({
43356
43435
  pending,
43357
43436
  ...pending.length > 0 ? {
43358
43437
  howToAnswer: [
43359
- "For each route you know a cheap test for, emit an errata-triage fence",
43438
+ "For each route you know a cheap test for, emit an errata-triage fence in your turn-final reply",
43360
43439
  "carrying its `route` id and the `test` \u2014 no presenting/cause needed, the",
43361
43440
  "route id identifies both endpoints. Attaching a test does NOT count as",
43362
43441
  "another sighting, so it cannot inflate the route's evidence.",
@@ -71393,7 +71472,7 @@ var init_report_render = __esm({
71393
71472
 
71394
71473
  // src/cli.ts
71395
71474
  init_src6();
71396
- import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync35, renameSync as renameSync4, statSync as statSync10 } from "node:fs";
71475
+ import { closeSync as closeSync2, existsSync as existsSync35, openSync as openSync2, readFileSync as readFileSync36, renameSync as renameSync4, statSync as statSync10 } from "node:fs";
71397
71476
  import { join as join43, resolve as pathResolve2 } from "node:path";
71398
71477
  import { spawn as spawn5 } from "node:child_process";
71399
71478
 
@@ -72715,9 +72794,9 @@ var NodeFsHandler = class {
72715
72794
  if (this.fsw.closed) {
72716
72795
  return;
72717
72796
  }
72718
- const dirname13 = sysPath.dirname(file2);
72797
+ const dirname14 = sysPath.dirname(file2);
72719
72798
  const basename8 = sysPath.basename(file2);
72720
- const parent = this.fsw._getWatchedDir(dirname13);
72799
+ const parent = this.fsw._getWatchedDir(dirname14);
72721
72800
  let prevStats = stats;
72722
72801
  if (parent.has(basename8))
72723
72802
  return;
@@ -72744,7 +72823,7 @@ var NodeFsHandler = class {
72744
72823
  prevStats = newStats2;
72745
72824
  }
72746
72825
  } catch (error48) {
72747
- this.fsw._remove(dirname13, basename8);
72826
+ this.fsw._remove(dirname14, basename8);
72748
72827
  }
72749
72828
  } else if (parent.has(basename8)) {
72750
72829
  const at = newStats.atimeMs;
@@ -74083,18 +74162,21 @@ function readTailWithLosses(path2, maxBytes) {
74083
74162
  try {
74084
74163
  fd = openSync(path2, "r");
74085
74164
  } catch {
74086
- return { text: "", bytesUnreachable: 0, ioFailed: true };
74165
+ return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true };
74087
74166
  }
74088
74167
  try {
74089
74168
  const size = fstatSync(fd).size;
74090
74169
  const start2 = Math.max(0, size - maxBytes);
74091
74170
  const len = size - start2;
74092
- if (len <= 0) return { text: "", bytesUnreachable: 0, ioFailed: false };
74171
+ if (len <= 0) return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: false };
74093
74172
  const buf = Buffer.allocUnsafe(len);
74094
74173
  readSync(fd, buf, 0, len, start2);
74095
- return { text: buf.toString("utf8"), bytesUnreachable: start2, ioFailed: false };
74174
+ const text = buf.toString("utf8");
74175
+ const firstNewline = buf.indexOf(10);
74176
+ const parseStartByte = firstNewline < 0 ? start2 : start2 + firstNewline + 1 - Buffer.byteLength(buf.subarray(0, firstNewline + 1).toString("utf8"));
74177
+ return { text, bytesUnreachable: start2, parseStartByte, ioFailed: false };
74096
74178
  } catch {
74097
- return { text: "", bytesUnreachable: 0, ioFailed: true };
74179
+ return { text: "", bytesUnreachable: 0, parseStartByte: 0, ioFailed: true };
74098
74180
  } finally {
74099
74181
  closeSync(fd);
74100
74182
  }
@@ -74158,7 +74240,7 @@ function isUserTurnBoundary(obj) {
74158
74240
  }
74159
74241
  function readAssistantTurnsWithLosses(transcriptPath, includeThinking = true, maxBytes = 2e6) {
74160
74242
  const tail = readTailWithLosses(transcriptPath, maxBytes);
74161
- const parsed = parseAssistantTurns(tail.text, includeThinking, false);
74243
+ const parsed = parseAssistantTurns(tail.text, includeThinking, false, tail.parseStartByte);
74162
74244
  return {
74163
74245
  turns: parsed.turns,
74164
74246
  losses: {
@@ -74170,7 +74252,8 @@ function readAssistantTurnsWithLosses(transcriptPath, includeThinking = true, ma
74170
74252
  }
74171
74253
  function readAssistantTurnsFrom(transcriptPath, fromByte, includeThinking = true, maxBytes) {
74172
74254
  const { text, nextOffset, more, ioFailed } = readFrom(transcriptPath, fromByte, maxBytes);
74173
- const parsed = parseAssistantTurns(text, includeThinking, fromByte > 0);
74255
+ const startByte = fromByte > 0 && fromByte <= nextOffset ? fromByte : 0;
74256
+ const parsed = parseAssistantTurns(text, includeThinking, fromByte > 0, startByte);
74174
74257
  return {
74175
74258
  turns: parsed.turns,
74176
74259
  nextOffset,
@@ -74206,10 +74289,10 @@ function detectHostHarness(raw2) {
74206
74289
  }
74207
74290
  return "claude_code";
74208
74291
  }
74209
- function parseAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure = false) {
74292
+ function parseAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure = false, startByte = 0) {
74210
74293
  if (!raw2) return { turns: [], parseFailures: 0 };
74211
74294
  const harness = detectHostHarness(raw2);
74212
- if (harness === "codex") return parseCodexAssistantTurns(raw2, skipFirstLineParseFailure);
74295
+ if (harness === "codex") return parseCodexAssistantTurns(raw2, skipFirstLineParseFailure, startByte);
74213
74296
  if (harness === "gemini") return parseGeminiAssistantTurns(raw2, skipFirstLineParseFailure);
74214
74297
  if (harness === "opencode") return parseOpencodeAssistantTurns(raw2, skipFirstLineParseFailure);
74215
74298
  return parseClaudeAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure);
@@ -74300,13 +74383,16 @@ function parseGeminiAssistantTurns(raw2, skipFirstLineParseFailure = false) {
74300
74383
  const turns = [...byId.entries()].sort((a, b) => a[1].order - b[1].order).map(([uuid3, v]) => ({ uuid: uuid3, text: v.text, hostHarness: "gemini" }));
74301
74384
  return { turns, parseFailures };
74302
74385
  }
74303
- function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false) {
74386
+ function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false, startByte = 0) {
74304
74387
  const turns = [];
74305
- const lines = raw2.split(/\r?\n/);
74388
+ const lines = raw2.split("\n");
74306
74389
  let parseFailures = 0;
74390
+ let nextByte = startByte;
74307
74391
  for (let i2 = 0; i2 < lines.length; i2++) {
74308
74392
  const line = lines[i2];
74309
- if (!line) continue;
74393
+ const lineByte = nextByte;
74394
+ nextByte += Buffer.byteLength(line ?? "", "utf8") + 1;
74395
+ if (!line?.trim()) continue;
74310
74396
  let obj;
74311
74397
  try {
74312
74398
  obj = JSON.parse(line);
@@ -74325,7 +74411,9 @@ function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false) {
74325
74411
  }
74326
74412
  if (parts2.length === 0) continue;
74327
74413
  turns.push({
74328
- uuid: typeof payload["id"] === "string" ? payload["id"] : `codex-${i2}`,
74414
+ // Slice-local line numbers repeat on every Stop. Position distinguishes
74415
+ // identical messages; the digest distinguishes a replaced row at that position.
74416
+ uuid: typeof payload["id"] === "string" && payload["id"].length > 0 ? payload["id"] : `codex-${lineByte}-${createHash14("sha256").update(line.trimEnd()).digest("hex")}`,
74329
74417
  text: parts2.join("\n\n"),
74330
74418
  hostHarness: "codex"
74331
74419
  });
@@ -76897,21 +76985,31 @@ async function pullCloudTriage(shared, cloud, profile, limit = 50) {
76897
76985
  stack: profile.stack,
76898
76986
  domains: profile.domains,
76899
76987
  kinds: ["Problem"],
76988
+ includeTriage: true,
76900
76989
  limit
76901
76990
  });
76902
76991
  const byId = new Map(res.nodes.map((n) => [n.id, n]));
76903
76992
  const now = Date.now();
76904
76993
  let merged = 0;
76994
+ const landedTriages = /* @__PURE__ */ new Set();
76905
76995
  const landNode = (id) => {
76906
- if (shared.getNode(id)) return true;
76996
+ const existing = shared.getNode(id);
76907
76997
  const n = byId.get(id);
76998
+ if (existing) {
76999
+ if (n && existing.attrs["source"] === "cloud") {
77000
+ shared.updateNode(id, { attrs: { ...existing.attrs, ...n.attrs, source: "cloud" }, lastUpdatedAt: now });
77001
+ }
77002
+ return true;
77003
+ }
76908
77004
  if (!n) return false;
76909
77005
  shared.mergeNode({ ...n, extractionSource: "bko-inferred", embedding: [], attrs: { ...n.attrs, source: "cloud" } });
76910
77006
  return true;
76911
77007
  };
76912
77008
  for (const edge2 of res.edges) {
76913
77009
  if (edge2.type !== "CONFIRMS" && edge2.type !== "ROUTES_TO") continue;
77010
+ if (!triageDiscriminatorAttrs(edge2.type, edge2.attrs).discriminator) continue;
76914
77011
  if (!landNode(edge2.from) || !landNode(edge2.to)) continue;
77012
+ landedTriages.add(edge2.from);
76915
77013
  const cloudAttrs = { ...edge2.attrs, source: "cloud", provisional: false };
76916
77014
  const existing = shared.getEdge(edge2.id);
76917
77015
  if (existing && existing.type === "CONFIRMS") {
@@ -76922,6 +77020,11 @@ async function pullCloudTriage(shared, cloud, profile, limit = 50) {
76922
77020
  }
76923
77021
  merged++;
76924
77022
  }
77023
+ for (const edge2 of res.edges) {
77024
+ if (edge2.type !== "TRIAGED_BY" || !landedTriages.has(edge2.to)) continue;
77025
+ if (!landNode(edge2.from)) continue;
77026
+ shared.mergeEdge({ ...edge2, attrs: { ...edge2.attrs, source: "cloud" } });
77027
+ }
76925
77028
  return merged;
76926
77029
  }
76927
77030
 
@@ -80218,7 +80321,7 @@ function createWatchBreaker(deps) {
80218
80321
  }
80219
80322
 
80220
80323
  // src/engine.ts
80221
- var DAEMON_VERSION = true ? "2.0.8" : "2.0.0-alpha.0";
80324
+ var DAEMON_VERSION = true ? "2.0.10" : "2.0.0-alpha.0";
80222
80325
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80223
80326
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80224
80327
  var GIT_OP_MUTE_MS = 4e3;
@@ -80785,7 +80888,7 @@ function createWorkspaceEngine(opts) {
80785
80888
  lastRemoteAt = now;
80786
80889
  try {
80787
80890
  const norm = normStackTokens;
80788
- const seed = store.findNodesByLabel("Problem").sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 6).map((p) => p.id);
80891
+ const seed = store.recentNodeIds("Problem", 6);
80789
80892
  const q = {
80790
80893
  stack: outboundStack(),
80791
80894
  domains: norm(profile.domains),
@@ -81104,6 +81207,9 @@ function createWorkspaceEngine(opts) {
81104
81207
  let resolved = 0;
81105
81208
  let abstracted = 0;
81106
81209
  let triaged = 0;
81210
+ let triageFencesSeen = 0;
81211
+ let triagePromoted = 0;
81212
+ let triageFencesRejected = 0;
81107
81213
  let priorEdges = 0;
81108
81214
  let exposureShown = 0;
81109
81215
  let exposureEvicted = 0;
@@ -81167,11 +81273,16 @@ function createWorkspaceEngine(opts) {
81167
81273
  console.warn("[errata] abstraction harvest failed:", err2);
81168
81274
  }
81169
81275
  try {
81170
- triaged += harvestTriageFences(opts.sharedStore, turn.text, {
81276
+ const triage = harvestTriageFences(opts.sharedStore, turn.text, {
81171
81277
  context: { stack: profile.stack, domain: profile.domains[0] },
81172
81278
  contributor: sessionId,
81173
81279
  ts: t
81174
- }).recorded;
81280
+ });
81281
+ triaged += triage.recorded;
81282
+ triageFencesSeen += triage.seen;
81283
+ triagePromoted += triage.promoted;
81284
+ triageFencesRejected += triage.rejected;
81285
+ if (triage.recorded > 0 || triage.promoted > 0) contextDirty = true;
81175
81286
  } catch (err2) {
81176
81287
  console.warn("[errata] triage harvest failed:", err2);
81177
81288
  }
@@ -81847,8 +81958,8 @@ function createWorkspaceEngine(opts) {
81847
81958
  if (abstracted > 0) {
81848
81959
  console.log(`[errata] abstraction: ${abstracted} principle(s) distilled from the conversation`);
81849
81960
  }
81850
- if (triaged > 0) {
81851
- console.log(`[errata] triage: ${triaged} wrong-door(s) captured from the conversation`);
81961
+ if (triaged > 0 || triagePromoted > 0 || triageFencesRejected > 0) {
81962
+ console.log(`[errata] triage: ${triaged} diagnosis(es), ${triagePromoted} route promotion(s), ${triageFencesRejected} rejected fence(s)`);
81852
81963
  }
81853
81964
  if (processedTurns > 0) {
81854
81965
  appendPassLedger(paths.configDir, "capture", Date.now() - t, {
@@ -81863,6 +81974,9 @@ function createWorkspaceEngine(opts) {
81863
81974
  tagsRestated,
81864
81975
  resolved,
81865
81976
  triaged,
81977
+ triageFencesSeen,
81978
+ triagePromoted,
81979
+ triageFencesRejected,
81866
81980
  priorEdges,
81867
81981
  corroboratedEdges,
81868
81982
  // RG-mark: refute witnesses stamped onto local nodes (the local half of
@@ -82664,6 +82778,40 @@ async function listenServer(fetchFn, port) {
82664
82778
  init_registry();
82665
82779
  init_identity2();
82666
82780
 
82781
+ // src/instance-receipts.ts
82782
+ import { createHash as createHash16 } from "node:crypto";
82783
+ var FIELDS = ["contributedAtSeq", "cloudNodeId", "anchorsContributedDigest", "projectAnchorsContributedDigest", "semanticEdgesContributedDigest", "shipRefusedSinceSeq", "shipRefusedCount", "shipRefusedReason", "shipRefusedFingerprint"];
82784
+ function instanceDestinationKey(input) {
82785
+ return createHash16("sha256").update(JSON.stringify([
82786
+ input.workspace,
82787
+ input.cloudUrl.replace(/\/+$/, ""),
82788
+ input.installationId ?? input.profileName ?? "default",
82789
+ input.projectId ?? null,
82790
+ input.writeTarget?.visibility ?? null,
82791
+ input.writeTarget?.org_id ?? null,
82792
+ input.writeTarget?.team_id ?? null
82793
+ ])).digest("hex");
82794
+ }
82795
+ function receiptMap(attrs) {
82796
+ const value = attrs["instanceReceipts"];
82797
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
82798
+ }
82799
+ function instanceReceipt(n, destination) {
82800
+ return destination ? receiptMap(n.attrs)[destination] ?? {} : n.attrs;
82801
+ }
82802
+ function updateInstanceReceipt(n, destination, patch, mirrorLegacy) {
82803
+ const receipt = { ...instanceReceipt(n, destination), ...patch };
82804
+ const attrs = { ...n.attrs, instanceReceipts: { ...receiptMap(n.attrs), [destination]: receipt } };
82805
+ if (mirrorLegacy) for (const field of FIELDS) {
82806
+ if (field in receipt) attrs[field] = receipt[field];
82807
+ else delete attrs[field];
82808
+ }
82809
+ return attrs;
82810
+ }
82811
+ function instanceReceiptSequence(n) {
82812
+ return Math.max(n.lastReinforcedAtSeq ?? 0, n.createdAtSeq ?? 0);
82813
+ }
82814
+
82667
82815
  // src/multi.ts
82668
82816
  init_dist();
82669
82817
  init_src5();
@@ -83068,8 +83216,8 @@ function compositionCounters(c) {
83068
83216
  ...Object.fromEntries(buckets.map((b) => [`sidecarDrop_${b}`, sidecarDropReasons[b] ?? 0]))
83069
83217
  };
83070
83218
  }
83071
- function contributedWireId(n) {
83072
- const cloudId = n.attrs["cloudNodeId"];
83219
+ function contributedWireId(n, destinationKey) {
83220
+ const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
83073
83221
  return isMembraneSaltedId(cloudId) ? cloudId : n.id;
83074
83222
  }
83075
83223
  var WIRE_CANONICAL_ID_MIN = 6;
@@ -83213,10 +83361,26 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83213
83361
  fileRefsSubstituted: 0,
83214
83362
  fileRefLexiconMisses: 0
83215
83363
  };
83364
+ const sharedCauses = /* @__PURE__ */ new Map();
83365
+ const causalQueue = store.findNodesByLabel("Problem").filter(
83366
+ (n) => n.attrs["source"] !== "cloud" && n.attrs["resolvedAs"] !== PROBLEM_RESOLUTION.FALSE_POSITIVE && !ignored(n.description)
83367
+ ).map((n) => n.id);
83368
+ const causalSeen = new Set(causalQueue);
83369
+ for (let i2 = 0; i2 < causalQueue.length; i2++) {
83370
+ const id = causalQueue[i2];
83371
+ for (const e of [...store.outEdges(id, ["CAUSED_BY"]), ...opts.sharedStore?.outEdges(id, ["CAUSED_BY"]) ?? []]) {
83372
+ if (causalSeen.has(e.to)) continue;
83373
+ causalSeen.add(e.to);
83374
+ const cause = nodeOf(e.to);
83375
+ if (!cause || cause.label !== "RootCause" || cause.attrs["source"] === "cloud" || ignored(cause.description)) continue;
83376
+ sharedCauses.set(cause.id, cause);
83377
+ causalQueue.push(cause.id);
83378
+ }
83379
+ }
83216
83380
  for (const label of INSTANCE_LABELS) {
83217
83381
  const ref = [];
83218
83382
  includedByLabel.set(label, ref);
83219
- const population = label === "RootCause" && opts.sharedStore ? [...store.findNodesByLabel(label), ...opts.sharedStore.findNodesByLabel(label)] : store.findNodesByLabel(label);
83383
+ const population = label === "RootCause" && opts.sharedStore ? [...store.findNodesByLabel(label), ...sharedCauses.values()] : store.findNodesByLabel(label);
83220
83384
  for (const n of population) {
83221
83385
  if (seen.has(n.id)) continue;
83222
83386
  if (walked.has(n.id)) continue;
@@ -83234,18 +83398,18 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83234
83398
  composition.ignored++;
83235
83399
  continue;
83236
83400
  }
83237
- const contributedAtSeq = n.attrs["contributedAtSeq"];
83401
+ const contributedAtSeq = instanceReceipt(n, opts.destinationKey)["contributedAtSeq"];
83238
83402
  if (typeof contributedAtSeq === "number" && (n.lastReinforcedAtSeq ?? 0) <= contributedAtSeq) {
83239
83403
  composition.watermarked++;
83240
83404
  anchorSources.add(n.id);
83241
- semanticEndpoints.set(n.id, contributedWireId(n));
83405
+ semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
83242
83406
  continue;
83243
83407
  }
83244
83408
  if (label === "Solution" && n.description.startsWith(AUTO_MINT_PREFIX)) {
83245
83409
  composition.autoMint++;
83246
83410
  continue;
83247
83411
  }
83248
- if (isTerminallyRefused(n)) {
83412
+ if (isTerminallyRefused({ ...n, attrs: instanceReceipt(n, opts.destinationKey) })) {
83249
83413
  composition.terminalRefused++;
83250
83414
  continue;
83251
83415
  }
@@ -83304,9 +83468,9 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83304
83468
  }
83305
83469
  const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
83306
83470
  const sourceNode = nodeOf(sourceId);
83307
- if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
83471
+ if ((sourceNode && instanceReceipt(sourceNode, opts.destinationKey)["anchorsContributedDigest"]) === dg) continue;
83308
83472
  anchorDigests[sourceId] = dg;
83309
- const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode);
83473
+ const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode, opts.destinationKey);
83310
83474
  for (const { e, t, wireId } of targets) {
83311
83475
  const reject = unshippableReason(wireId, t.label);
83312
83476
  if (reject) {
@@ -83335,7 +83499,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83335
83499
  for (const sourceId of anchorSources) {
83336
83500
  const source = nodeOf(sourceId);
83337
83501
  if (!source) continue;
83338
- const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source);
83502
+ const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source, opts.destinationKey);
83339
83503
  const planned = [];
83340
83504
  for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
83341
83505
  const target = nodeOf(e.to);
@@ -83370,7 +83534,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83370
83534
  const dg = digest(
83371
83535
  planned.map(({ symId, wireAttrs }) => `ANCHORED_AT>${symId}>${JSON.stringify(wireAttrs)}`).sort()
83372
83536
  );
83373
- if (source.attrs["projectAnchorsContributedDigest"] === dg) continue;
83537
+ if (instanceReceipt(source, opts.destinationKey)["projectAnchorsContributedDigest"] === dg) continue;
83374
83538
  projectAnchorDigests[sourceId] = dg;
83375
83539
  for (const { e, target, symId, wireAttrs } of planned) {
83376
83540
  if (!contextSeen.has(symId)) {
@@ -83416,7 +83580,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83416
83580
  if (outs.length === 0) continue;
83417
83581
  const dg = digest(outs.map(({ e, wireTo }) => `${e.type}>${wireTo}`).sort());
83418
83582
  const owner = nodeOf(sourceId);
83419
- const digestSkip = owner?.attrs["semanticEdgesContributedDigest"] === dg;
83583
+ const digestSkip = (owner && instanceReceipt(owner, opts.destinationKey)["semanticEdgesContributedDigest"]) === dg;
83420
83584
  if (!digestSkip) semanticEdgeDigests[sourceId] = dg;
83421
83585
  for (const { e, wireTo } of outs) {
83422
83586
  if (digestSkip && !shippedById.has(e.to)) continue;
@@ -83473,7 +83637,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
83473
83637
  for (const wn of nodes) {
83474
83638
  if (wn.label !== "RootCause" && wn.label !== "Solution") continue;
83475
83639
  const src = nodeOf(wn.id);
83476
- if (typeof src?.attrs["contributedAtSeq"] === "number") continue;
83640
+ if (src && typeof instanceReceipt(src, opts.destinationKey)["contributedAtSeq"] === "number") continue;
83477
83641
  if (bindingEndpoints.has(wn.id)) continue;
83478
83642
  heldOrphans.add(wn.id);
83479
83643
  }
@@ -83627,6 +83791,7 @@ function buildDrainInstances(store, sharedStore, input) {
83627
83791
  ...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {},
83628
83792
  workspaceRoot: input.workspaceRoot,
83629
83793
  sharedStore,
83794
+ destinationKey: input.destinationKey,
83630
83795
  onComposition: (c) => {
83631
83796
  composition = c;
83632
83797
  },
@@ -84124,7 +84289,7 @@ function buildReverseSymbolMap(opts) {
84124
84289
  function* reverseSymbolEntries(opts) {
84125
84290
  const { store, salt, projectId } = opts;
84126
84291
  for (const kind of ANCHOR_SYMBOL_KINDS2) {
84127
- for (const n of store.findNodeIdentitiesByLabel(kind)) {
84292
+ for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
84128
84293
  const { relPath, qname } = n;
84129
84294
  if (relPath === null || qname === null) continue;
84130
84295
  const sym = projectSymbolId(salt, projectId, relPath, qname, kind);
@@ -84132,7 +84297,7 @@ function* reverseSymbolEntries(opts) {
84132
84297
  yield [projectSaltedNodeId(sym, salt), n.id];
84133
84298
  }
84134
84299
  }
84135
- for (const n of store.findNodeIdentitiesByLabel("File")) {
84300
+ for (const n of store.iterateNodeIdentitiesByLabel("File")) {
84136
84301
  if (n.relPath === null) continue;
84137
84302
  const sym = projectSymbolId(salt, projectId, n.relPath, FILE_ANCHOR_QNAME, "File");
84138
84303
  yield [sym, n.id];
@@ -84172,7 +84337,7 @@ function buildOwnKnowledgeMap(opts) {
84172
84337
  function* ownKnowledgeEntries(opts) {
84173
84338
  const { store, salt } = opts;
84174
84339
  for (const kind of ANCHOR_SOURCE_KINDS) {
84175
- for (const n of store.findNodeIdentitiesByLabel(kind)) {
84340
+ for (const n of store.iterateNodeIdentitiesByLabel(kind)) {
84176
84341
  if (n.id.startsWith("projn_")) continue;
84177
84342
  yield [projectSaltedNodeId(n.id, salt), n.id];
84178
84343
  }
@@ -85294,7 +85459,7 @@ async function startHistoryStage(root, input, client, walk11 = {}) {
85294
85459
 
85295
85460
  // src/link-flow.ts
85296
85461
  init_cloud_auth();
85297
- import { createHash as createHash16 } from "node:crypto";
85462
+ import { createHash as createHash17 } from "node:crypto";
85298
85463
  import { execFileSync as execFileSync5 } from "node:child_process";
85299
85464
  import { existsSync as existsSync30, writeFileSync as writeFileSync23 } from "node:fs";
85300
85465
  import { join as join38 } from "node:path";
@@ -85588,7 +85753,7 @@ async function loginOAuthLoopback(opts) {
85588
85753
 
85589
85754
  // src/link-flow.ts
85590
85755
  function remoteFingerprint(locator) {
85591
- return createHash16("sha256").update(locator).digest("hex");
85756
+ return createHash17("sha256").update(locator).digest("hex");
85592
85757
  }
85593
85758
  function isDecision(value) {
85594
85759
  if (!value || typeof value !== "object") return false;
@@ -86483,7 +86648,7 @@ async function startMultiDaemon(opts = {}) {
86483
86648
  registerWorkspace(rec.engine.profile, rec.root);
86484
86649
  } catch {
86485
86650
  }
86486
- if (wakeIndexPlan(rec.engine.store.nodeCount(), opts.reindexOnStart !== false) === "full-index") {
86651
+ if (wakeIndexPlan(rec.engine.store.hasLiveNodes() ? 1 : 0, opts.reindexOnStart !== false) === "full-index") {
86487
86652
  void enqueueWakeIndex(async () => {
86488
86653
  const done = markPass(`wake-index:${rec.entry.name}`);
86489
86654
  try {
@@ -86706,6 +86871,15 @@ async function startMultiDaemon(opts = {}) {
86706
86871
  app.post("/api/context-inject", (c) => forward(c, cwdOf, { affinity: "follow" }));
86707
86872
  app.post("/api/statusline", async (c) => {
86708
86873
  const body2 = await c.req.json().catch(() => ({}));
86874
+ const dir = statuslineDir(body2);
86875
+ if (dir !== null) {
86876
+ const d = normPath(dir);
86877
+ const known = listWorkspaceEntries().some((e) => {
86878
+ const root = normPath(e.path);
86879
+ return d === root || d.startsWith(`${root}/`);
86880
+ });
86881
+ if (!known) return c.text("");
86882
+ }
86709
86883
  return c.text(statuslineText(String(body2["session_id"] ?? "")));
86710
86884
  });
86711
86885
  for (const r of records) {
@@ -86886,11 +87060,17 @@ async function startMultiDaemon(opts = {}) {
86886
87060
  const ANCHOR_PULL_INTERVAL_MS = 10 * 60 * 1e3;
86887
87061
  const anchorPullAll = async () => {
86888
87062
  const c = loadConfig();
86889
- if (!hasCloudCredential(c)) return;
86890
- const client = cloudNow();
87063
+ if (registryCorrupt()) return;
86891
87064
  for (const r of records) {
86892
87065
  const projectId = r.engine.profile.projectId;
86893
87066
  if (!projectId || !effectiveConsent(c, r.engine.profile).sync) continue;
87067
+ const identity = identityFor(liveWorkspaceEntry(r.entry));
87068
+ if (identity.invalid) {
87069
+ noteIdentityBlock(r.id, r.entry.name, identity);
87070
+ continue;
87071
+ }
87072
+ if (identity.rule !== "workspace-binding" && !hasCloudCredential(c)) continue;
87073
+ const client = cloudForIdentity(identity);
86894
87074
  try {
86895
87075
  const { salt } = await resolveProjectSymbolSalt(client, projectId);
86896
87076
  await pullProjectAnchors(r, client, salt);
@@ -86904,11 +87084,17 @@ async function startMultiDaemon(opts = {}) {
86904
87084
  }, ANCHOR_PULL_INTERVAL_MS);
86905
87085
  const ambientLinkAll = async () => {
86906
87086
  const c = loadConfig();
86907
- if (!hasCloudCredential(c)) return;
86908
- const client = cloudNow();
87087
+ if (registryCorrupt()) return;
86909
87088
  for (const r of records) {
86910
87089
  const working = loadProfile(r.root) ?? r.engine.profile;
86911
87090
  if (!effectiveConsent(c, working).sync) continue;
87091
+ const identity = identityFor(liveWorkspaceEntry(r.entry));
87092
+ if (identity.invalid) {
87093
+ noteIdentityBlock(r.id, r.entry.name, identity);
87094
+ continue;
87095
+ }
87096
+ if (identity.rule !== "workspace-binding" && !hasCloudCredential(c)) continue;
87097
+ const client = cloudForIdentity(identity);
86912
87098
  const out2 = await ensureProjectLink(r.root, working, client);
86913
87099
  if (working !== r.engine.profile) adoptLinkFields(r.engine.profile, working);
86914
87100
  if (out2.linked) {
@@ -87296,6 +87482,7 @@ async function startMultiDaemon(opts = {}) {
87296
87482
  }
87297
87483
  const ignore2 = loadClaimIgnorePatterns(globalDir());
87298
87484
  const clientPool = /* @__PURE__ */ new Map();
87485
+ const receiptContexts = /* @__PURE__ */ new Map();
87299
87486
  const poolFor = (identity) => {
87300
87487
  const k = identityKey(identity);
87301
87488
  let c = clientPool.get(k);
@@ -87438,7 +87625,26 @@ async function startMultiDaemon(opts = {}) {
87438
87625
  return res.accepted;
87439
87626
  });
87440
87627
  }
87628
+ const receiptProfile = recordIdentity.profile ?? (cfg2.activeInstallationProfile ? cfg2.installationProfiles[cfg2.activeInstallationProfile] : void 0);
87629
+ let receiptContext = receiptContexts.get(identityKey(recordIdentity));
87630
+ if (!receiptContext) {
87631
+ try {
87632
+ receiptContext = await client.context();
87633
+ receiptContexts.set(identityKey(recordIdentity), receiptContext);
87634
+ } catch (err2) {
87635
+ errors.push(`[${projectName}] instance destination unresolved: ${err2 instanceof Error ? err2.message : String(err2)}`);
87636
+ continue;
87637
+ }
87638
+ }
87639
+ const destinationKey = instanceDestinationKey({
87640
+ workspace: r.id,
87641
+ cloudUrl: opts.cloudUrl ?? receiptProfile?.cloudUrl ?? cfg2.cloudUrl,
87642
+ installationId: receiptContext.context.installation_id ?? receiptContext.identity.credential_id,
87643
+ projectId: project?.projectId,
87644
+ writeTarget: project ? null : receiptContext.context.default_write_target
87645
+ });
87441
87646
  const built = await buildInstanceBatch(r, {
87647
+ destinationKey,
87442
87648
  profile: r.engine.profile,
87443
87649
  daemonVersion: DAEMON_VERSION,
87444
87650
  ignorePatterns: [...ignore2],
@@ -87483,7 +87689,6 @@ async function startMultiDaemon(opts = {}) {
87483
87689
  );
87484
87690
  }
87485
87691
  uploaded += res.accepted;
87486
- const seq = store.currentIngestSeq();
87487
87692
  const cloudIdByLocal = new Map(
87488
87693
  res.result.nodes.filter((rn) => rn.nodeId).map((rn) => [rn.canonicalId, rn.nodeId])
87489
87694
  );
@@ -87492,6 +87697,7 @@ async function startMultiDaemon(opts = {}) {
87492
87697
  if (rn.decision === "rejected") rejected.set(rn.canonicalId, rn.reason ?? "no reason given");
87493
87698
  }
87494
87699
  const rejectedEdges = res.result.edges.filter((re) => re.decision === "rejected");
87700
+ const edgeReceiptsComplete = rejectedEdges.length === 0 && res.result.edges.length === instances.edges.length;
87495
87701
  if (rejectedEdges.length > 0) {
87496
87702
  const byReason = /* @__PURE__ */ new Map();
87497
87703
  for (const re of rejectedEdges) {
@@ -87522,17 +87728,17 @@ async function startMultiDaemon(opts = {}) {
87522
87728
  const owner = owningStore(id);
87523
87729
  if (!owner) return;
87524
87730
  const fp = refusalFingerprint(reason, owner.n.description);
87525
- const same = owner.n.attrs["shipRefusedFingerprint"] === fp;
87526
- const count = same ? Number(owner.n.attrs["shipRefusedCount"] ?? 0) + 1 : 1;
87527
- if (owner.n.attrs["shipRefusedSinceSeq"] !== void 0) repeat();
87731
+ const same = instanceReceipt(owner.n, destinationKey)["shipRefusedFingerprint"] === fp;
87732
+ const count = same ? Number(instanceReceipt(owner.n, destinationKey)["shipRefusedCount"] ?? 0) + 1 : 1;
87733
+ if (instanceReceipt(owner.n, destinationKey)["shipRefusedSinceSeq"] !== void 0) repeat();
87528
87734
  owner.s.updateNode(id, {
87529
- attrs: {
87530
- ...owner.n.attrs,
87531
- ...same && owner.n.attrs["shipRefusedSinceSeq"] !== void 0 ? {} : { shipRefusedSinceSeq: seq },
87735
+ attrs: updateInstanceReceipt(owner.n, destinationKey, {
87736
+ ...instanceReceipt(owner.n, destinationKey),
87737
+ ...same && instanceReceipt(owner.n, destinationKey)["shipRefusedSinceSeq"] !== void 0 ? {} : { shipRefusedSinceSeq: owner.s.currentIngestSeq() },
87532
87738
  shipRefusedCount: count,
87533
87739
  shipRefusedReason: reason,
87534
87740
  shipRefusedFingerprint: fp
87535
- }
87741
+ }, owner.s === store)
87536
87742
  });
87537
87743
  if (same && count === SHIP_TERMINAL_REFUSALS && !isRetryableRefusal(reason)) {
87538
87744
  nowHeld.push({ id, reason });
@@ -87549,6 +87755,7 @@ async function startMultiDaemon(opts = {}) {
87549
87755
  const owner = owningStore(n.id);
87550
87756
  if (!owner || !["Problem", "Solution", "RootCause"].includes(owner.n.label)) return;
87551
87757
  if (blocked.has(n.id)) return;
87758
+ if (!cloudIdByLocal.has(n.id)) return;
87552
87759
  if (rejected.has(n.id)) return;
87553
87760
  const cloudNodeId = cloudIdByLocal.get(n.id);
87554
87761
  const {
@@ -87557,37 +87764,41 @@ async function startMultiDaemon(opts = {}) {
87557
87764
  shipRefusedReason: _clearedReason,
87558
87765
  shipRefusedFingerprint: _clearedFp,
87559
87766
  ...carried
87560
- } = owner.n.attrs;
87767
+ } = instanceReceipt(owner.n, destinationKey);
87561
87768
  owner.s.updateNode(n.id, {
87562
- attrs: {
87769
+ attrs: updateInstanceReceipt(owner.n, destinationKey, {
87563
87770
  ...carried,
87564
- contributedAtSeq: seq,
87565
- ...cloudNodeId && cloudNodeId !== n.id ? { cloudNodeId } : {}
87566
- }
87771
+ shipRefusedSinceSeq: void 0,
87772
+ shipRefusedCount: void 0,
87773
+ shipRefusedReason: void 0,
87774
+ shipRefusedFingerprint: void 0,
87775
+ contributedAtSeq: instanceReceiptSequence(n),
87776
+ cloudNodeId
87777
+ }, owner.s === store)
87567
87778
  });
87568
87779
  });
87569
87780
  await stampBatched(Object.entries(instances.anchorDigests), ([localId, dg]) => {
87570
87781
  const owner = owningStore(localId);
87571
87782
  if (!owner) return;
87572
- if (rejected.has(localId) || blocked.has(localId)) return;
87783
+ if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
87573
87784
  owner.s.updateNode(localId, {
87574
- attrs: { ...owner.n.attrs, anchorsContributedDigest: dg }
87785
+ attrs: updateInstanceReceipt(owner.n, destinationKey, { anchorsContributedDigest: dg }, owner.s === store)
87575
87786
  });
87576
87787
  });
87577
87788
  await stampBatched(Object.entries(instances.projectAnchorDigests), ([localId, dg]) => {
87578
87789
  const owner = owningStore(localId);
87579
87790
  if (!owner) return;
87580
- if (rejected.has(localId) || blocked.has(localId)) return;
87791
+ if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
87581
87792
  owner.s.updateNode(localId, {
87582
- attrs: { ...owner.n.attrs, projectAnchorsContributedDigest: dg }
87793
+ attrs: updateInstanceReceipt(owner.n, destinationKey, { projectAnchorsContributedDigest: dg }, owner.s === store)
87583
87794
  });
87584
87795
  });
87585
87796
  await stampBatched(Object.entries(instances.semanticEdgeDigests), ([localId, dg]) => {
87586
87797
  const owner = owningStore(localId);
87587
87798
  if (!owner) return;
87588
- if (rejected.has(localId) || blocked.has(localId)) return;
87799
+ if (!edgeReceiptsComplete || !instanceReceipt(owner.n, destinationKey)["cloudNodeId"] || rejected.has(localId) || blocked.has(localId)) return;
87589
87800
  owner.s.updateNode(localId, {
87590
- attrs: { ...owner.n.attrs, semanticEdgesContributedDigest: dg }
87801
+ attrs: updateInstanceReceipt(owner.n, destinationKey, { semanticEdgesContributedDigest: dg }, owner.s === store)
87591
87802
  });
87592
87803
  });
87593
87804
  appendPassLedger(r.engine.paths.configDir, "ship", Date.now() - shipStartedAt, {
@@ -88566,9 +88777,10 @@ function consolidationGapMs(lastPassMs, policy) {
88566
88777
  }
88567
88778
 
88568
88779
  // src/claude-statusline.ts
88569
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync26 } from "node:fs";
88570
- import { join as join42 } from "node:path";
88571
- var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks claude
88780
+ import { existsSync as existsSync34, mkdirSync as mkdirSync10, readFileSync as readFileSync35, writeFileSync as writeFileSync26 } from "node:fs";
88781
+ import { homedir as homedir8 } from "node:os";
88782
+ import { dirname as dirname13, join as join42 } from "node:path";
88783
+ var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata init / install-hooks claude
88572
88784
  import { readFileSync } from 'node:fs';
88573
88785
 
88574
88786
  try {
@@ -88594,15 +88806,57 @@ function buildStatusLine(port, nodePath, relayPath) {
88594
88806
  function isErrataStatusLine(value) {
88595
88807
  return !!value && typeof value === "object" && typeof value["command"] === "string" && value["command"].includes(STATUSLINE_RELAY_FILE);
88596
88808
  }
88597
- function installStatusLine(settings, root, port, nodePath = process.execPath) {
88598
- if (settings["statusLine"] !== void 0 && !isErrataStatusLine(settings["statusLine"])) return false;
88599
- const dir = join42(root, ".claude", "hooks");
88600
- const relay = join42(dir, STATUSLINE_RELAY_FILE);
88601
- mkdirSync10(dir, { recursive: true });
88602
- writeFileSync26(relay, STATUSLINE_RELAY_SOURCE, "utf8");
88603
- settings["statusLine"] = buildStatusLine(port, nodePath, relay);
88809
+ function removeProjectStatusLine(settings) {
88810
+ if (!isErrataStatusLine(settings["statusLine"])) return false;
88811
+ delete settings["statusLine"];
88604
88812
  return true;
88605
88813
  }
88814
+ function userClaudeSettingsPath() {
88815
+ const base = process.env["CLAUDE_CONFIG_DIR"] ?? join42(homedir8(), ".claude");
88816
+ return join42(base, "settings.json");
88817
+ }
88818
+ function ensureUserStatusLine(opts) {
88819
+ const env2 = opts.env ?? process.env;
88820
+ const off = (env2["ERRATA_STATUSLINE"] ?? "").trim().toLowerCase();
88821
+ if (off === "0" || off === "off" || off === "false" || off === "no") return "disabled";
88822
+ const settingsPath = opts.userSettingsPath ?? userClaudeSettingsPath();
88823
+ let settings = {};
88824
+ if (existsSync34(settingsPath)) {
88825
+ try {
88826
+ const parsed = JSON.parse(readFileSync35(settingsPath, "utf8"));
88827
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "unreadable";
88828
+ settings = parsed;
88829
+ } catch {
88830
+ return "unreadable";
88831
+ }
88832
+ }
88833
+ if (settings["statusLine"] !== void 0 && !isErrataStatusLine(settings["statusLine"])) return "kept-own";
88834
+ const relay = join42(opts.relayDir, STATUSLINE_RELAY_FILE);
88835
+ mkdirSync10(opts.relayDir, { recursive: true });
88836
+ if (!existsSync34(relay) || readFileSync35(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
88837
+ writeFileSync26(relay, STATUSLINE_RELAY_SOURCE, "utf8");
88838
+ }
88839
+ const next = buildStatusLine(opts.port, opts.nodePath ?? process.execPath, relay);
88840
+ if (JSON.stringify(settings["statusLine"]) === JSON.stringify(next)) return "unchanged";
88841
+ settings["statusLine"] = next;
88842
+ mkdirSync10(dirname13(settingsPath), { recursive: true });
88843
+ writeFileSync26(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
88844
+ return "installed";
88845
+ }
88846
+ function describeStatusLineOutcome(outcome) {
88847
+ switch (outcome) {
88848
+ case "installed":
88849
+ return "status line: errata recall card in colour (user settings; ERRATA_STATUSLINE=off opts out)";
88850
+ case "unchanged":
88851
+ return "status line: errata recall card already set up";
88852
+ case "kept-own":
88853
+ return "status line: kept your own \u2014 errata's recall card shows inline only";
88854
+ case "disabled":
88855
+ return "status line: skipped (ERRATA_STATUSLINE=off)";
88856
+ case "unreadable":
88857
+ return "status line: skipped \u2014 ~/.claude/settings.json is not valid JSON";
88858
+ }
88859
+ }
88606
88860
 
88607
88861
  // src/opencode-plugin.ts
88608
88862
  var OPENCODE_PLUGIN_MARKER = "// @inerrata opencode plugin \u2014 errata-managed (errata install-hooks opencode)";
@@ -89009,14 +89263,14 @@ async function cmdInit() {
89009
89263
  if (!skipHooks) {
89010
89264
  console.log("");
89011
89265
  console.log("installing harness hooks...");
89012
- const { existsSync: existsSync35 } = await import("node:fs");
89266
+ const { existsSync: existsSync36 } = await import("node:fs");
89013
89267
  const { join: join44 } = await import("node:path");
89014
89268
  try {
89015
89269
  await installClaudeHooks(port);
89016
89270
  } catch (err2) {
89017
89271
  console.warn(` \u26A0\uFE0F Claude hook install failed: ${err2 instanceof Error ? err2.message : err2}`);
89018
89272
  }
89019
- if (existsSync35(join44(ROOT, ".cursor"))) {
89273
+ if (existsSync36(join44(ROOT, ".cursor"))) {
89020
89274
  try {
89021
89275
  await installCursorMcpConfig();
89022
89276
  } catch (err2) {
@@ -89118,6 +89372,7 @@ async function cmdStart() {
89118
89372
  await ensureProfile();
89119
89373
  const profile = loadProfile(ROOT);
89120
89374
  if (profile) registerWorkspace(profile, ROOT);
89375
+ if (profile) console.log(`errata: ${ensureStatusLine(resolveHookPort())}`);
89121
89376
  const lockPath = globalDaemonLock();
89122
89377
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
89123
89378
  const where = profile ? `${profile.name} (${profile.id})` : ROOT;
@@ -89205,9 +89460,9 @@ async function cmdStatus() {
89205
89460
  console.log(` stack: ${profile.stack.join(", ") || "(none)"}`);
89206
89461
  console.log(` languages: ${profile.languages.join(", ") || "(none)"}`);
89207
89462
  }
89208
- console.log(` graph db: ${existsSync34(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
89209
- console.log(` event log: ${existsSync34(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
89210
- if (existsSync34(paths.castalia)) {
89463
+ console.log(` graph db: ${existsSync35(paths.castalia) ? "yes" : "no"} (${paths.castalia})`);
89464
+ console.log(` event log: ${existsSync35(paths.eventLog) ? "yes" : "no"} (${paths.eventLog})`);
89465
+ if (existsSync35(paths.castalia)) {
89211
89466
  try {
89212
89467
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
89213
89468
  const store = openGraphStore2({ path: paths.castalia });
@@ -89437,7 +89692,7 @@ async function cmdWhoami(args2) {
89437
89692
  `note: daemon capture and the MCP server resolve identically; a process with ERRATA_CONFIG_PATH set resolves by rule 1 regardless of binding.`
89438
89693
  ];
89439
89694
  if (args2.includes("--json")) {
89440
- console.log(JSON.stringify({ path: path2, registered: Boolean(entry), ...identity }, null, 2));
89695
+ console.log(JSON.stringify({ path: path2, registered: Boolean(entry), ...publicWorkspaceIdentity(identity) }, null, 2));
89441
89696
  } else {
89442
89697
  for (const l of lines) console.log(l);
89443
89698
  }
@@ -89840,6 +90095,7 @@ async function cmdLink(args2) {
89840
90095
  process.exitCode = 1;
89841
90096
  return;
89842
90097
  }
90098
+ console.log(ensureStatusLine(resolveHookPort()));
89843
90099
  delete profile.projectLinkDisabled;
89844
90100
  const remote = flag("--remote");
89845
90101
  if (remote) {
@@ -90176,11 +90432,11 @@ function cmdInstallationProfile(args2) {
90176
90432
  }
90177
90433
  async function cmdReview() {
90178
90434
  const paths = workspacePaths(ROOT);
90179
- if (!existsSync34(paths.reviewQueue)) {
90435
+ if (!existsSync35(paths.reviewQueue)) {
90180
90436
  console.log("(review queue empty)");
90181
90437
  return;
90182
90438
  }
90183
- const queue = JSON.parse(readFileSync35(paths.reviewQueue, "utf8"));
90439
+ const queue = JSON.parse(readFileSync36(paths.reviewQueue, "utf8"));
90184
90440
  if (queue.length === 0) {
90185
90441
  console.log("(review queue empty)");
90186
90442
  return;
@@ -90851,7 +91107,7 @@ async function gatherRepo(store, ws) {
90851
91107
  };
90852
91108
  }
90853
91109
  async function gatherReportData(generatedAt) {
90854
- const { existsSync: existsSync35 } = await import("node:fs");
91110
+ const { existsSync: existsSync36 } = await import("node:fs");
90855
91111
  const { openGraphStore: openGraphStore2 } = await Promise.resolve().then(() => (init_src5(), src_exports2));
90856
91112
  const cfg = loadConfig();
90857
91113
  const outbound = cfg.consent.sync ? "auto" : "off";
@@ -90859,7 +91115,7 @@ async function gatherReportData(generatedAt) {
90859
91115
  for (const ws of listWorkspaces()) {
90860
91116
  if (ws.missing) continue;
90861
91117
  const dbPath = workspacePaths(ws.path).castalia;
90862
- if (!existsSync35(dbPath)) continue;
91118
+ if (!existsSync36(dbPath)) continue;
90863
91119
  let store = null;
90864
91120
  try {
90865
91121
  store = openGraphStore2({ path: dbPath });
@@ -91024,13 +91280,13 @@ async function cmdInstallHooks(args2) {
91024
91280
  }
91025
91281
  }
91026
91282
  async function installOpencodePlugin(port) {
91027
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91283
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91028
91284
  const { join: join44 } = await import("node:path");
91029
91285
  const dir = join44(ROOT, ".opencode", "plugin");
91030
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91286
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91031
91287
  const file2 = join44(dir, "inerrata.js");
91032
- if (existsSync35(file2)) {
91033
- const head2 = readFileSync36(file2, "utf8").split("\n")[0] ?? "";
91288
+ if (existsSync36(file2)) {
91289
+ const head2 = readFileSync37(file2, "utf8").split("\n")[0] ?? "";
91034
91290
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
91035
91291
  console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
91036
91292
  process.exit(2);
@@ -91039,9 +91295,9 @@ async function installOpencodePlugin(port) {
91039
91295
  writeFileSync27(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
91040
91296
  const cfgFile = join44(ROOT, "opencode.json");
91041
91297
  let cfg = { $schema: "https://opencode.ai/config.json" };
91042
- if (existsSync35(cfgFile)) {
91298
+ if (existsSync36(cfgFile)) {
91043
91299
  try {
91044
- cfg = JSON.parse(readFileSync36(cfgFile, "utf8"));
91300
+ cfg = JSON.parse(readFileSync37(cfgFile, "utf8"));
91045
91301
  } catch {
91046
91302
  console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
91047
91303
  process.exit(2);
@@ -91071,16 +91327,23 @@ function errataMcpInvocation() {
91071
91327
  const { cmd: cmd2, args: args2 } = selfArgv("mcp");
91072
91328
  return { command: cmd2, args: args2 };
91073
91329
  }
91330
+ function ensureStatusLine(port) {
91331
+ try {
91332
+ return describeStatusLineOutcome(ensureUserStatusLine({ port, relayDir: globalDir() }));
91333
+ } catch (err2) {
91334
+ return `status line: not installed (${err2 instanceof Error ? err2.message : err2})`;
91335
+ }
91336
+ }
91074
91337
  async function installClaudeHooks(port) {
91075
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91338
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91076
91339
  const { join: join44 } = await import("node:path");
91077
91340
  const dir = join44(ROOT, ".claude");
91078
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91341
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91079
91342
  const file2 = join44(dir, "settings.json");
91080
91343
  let settings = {};
91081
- if (existsSync35(file2)) {
91344
+ if (existsSync36(file2)) {
91082
91345
  try {
91083
- settings = JSON.parse(readFileSync36(file2, "utf8"));
91346
+ settings = JSON.parse(readFileSync37(file2, "utf8"));
91084
91347
  } catch {
91085
91348
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
91086
91349
  process.exit(2);
@@ -91127,18 +91390,10 @@ async function installClaudeHooks(port) {
91127
91390
  dropErrata(list);
91128
91391
  list.push({ hooks: [{ type: "command", command: injectCmd }] });
91129
91392
  }
91130
- const { homedir: homedir8 } = await import("node:os");
91131
- let userStatusLine;
91132
- try {
91133
- userStatusLine = JSON.parse(readFileSync36(join44(homedir8(), ".claude", "settings.json"), "utf8"))["statusLine"];
91134
- } catch {
91135
- }
91136
- const statusLineInstalled = (userStatusLine === void 0 || isErrataStatusLine(userStatusLine)) && installStatusLine(settings, ROOT, port);
91393
+ removeProjectStatusLine(settings);
91137
91394
  writeFileSync27(file2, JSON.stringify(settings, null, 2) + "\n", "utf8");
91138
91395
  console.log(`installed Claude Code hooks \u2192 ${file2}`);
91139
- console.log(
91140
- statusLineInstalled ? " status line: errata recall card in colour (daemon env ERRATA_RECALL_CARD=off hides the cards)" : " status line: kept your own \u2014 errata's recall card shows inline only"
91141
- );
91396
+ console.log(` ${ensureStatusLine(port)}`);
91142
91397
  await installClaudeMcpConfig();
91143
91398
  const claudeMd = join44(ROOT, "CLAUDE.md");
91144
91399
  const recall = writeManagedBlock(claudeMd, { body: RECALL_FIRST_BLOCK });
@@ -91152,15 +91407,15 @@ async function installClaudeHooks(port) {
91152
91407
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
91153
91408
  }
91154
91409
  async function installClaudeMcpConfig() {
91155
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91156
- const { join: join44, dirname: dirname13 } = await import("node:path");
91410
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91411
+ const { join: join44, dirname: dirname14 } = await import("node:path");
91157
91412
  const file2 = join44(ROOT, ".mcp.json");
91158
- const dir = dirname13(file2);
91159
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91413
+ const dir = dirname14(file2);
91414
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91160
91415
  let cfg = {};
91161
- if (existsSync35(file2)) {
91416
+ if (existsSync36(file2)) {
91162
91417
  try {
91163
- cfg = JSON.parse(readFileSync36(file2, "utf8"));
91418
+ cfg = JSON.parse(readFileSync37(file2, "utf8"));
91164
91419
  } catch {
91165
91420
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
91166
91421
  process.exit(2);
@@ -91174,15 +91429,15 @@ async function installClaudeMcpConfig() {
91174
91429
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
91175
91430
  }
91176
91431
  async function installCursorMcpConfig() {
91177
- const { mkdirSync: mkdirSync11, existsSync: existsSync35, readFileSync: readFileSync36, writeFileSync: writeFileSync27 } = await import("node:fs");
91432
+ const { mkdirSync: mkdirSync11, existsSync: existsSync36, readFileSync: readFileSync37, writeFileSync: writeFileSync27 } = await import("node:fs");
91178
91433
  const { join: join44 } = await import("node:path");
91179
91434
  const dir = join44(ROOT, ".cursor");
91180
- if (!existsSync35(dir)) mkdirSync11(dir, { recursive: true });
91435
+ if (!existsSync36(dir)) mkdirSync11(dir, { recursive: true });
91181
91436
  const file2 = join44(dir, "mcp.json");
91182
91437
  let cfg = {};
91183
- if (existsSync35(file2)) {
91438
+ if (existsSync36(file2)) {
91184
91439
  try {
91185
- cfg = JSON.parse(readFileSync36(file2, "utf8"));
91440
+ cfg = JSON.parse(readFileSync37(file2, "utf8"));
91186
91441
  } catch {
91187
91442
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
91188
91443
  process.exit(2);