@inerrata-corporation/errata 2.0.2-dev.982 → 2.0.2

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
@@ -64,7 +64,7 @@ function toCloudAttrs(attrs) {
64
64
  }
65
65
  return rest2;
66
66
  }
67
- var SEMANTIC_NODE_LABELS, CONTEXT_NODE_LABELS, CODE_NODE_LABELS, ALL_NODE_LABELS, STRUCTURAL_EDGES, CAUSAL_EDGES, RESOLUTION_EDGES, CONCEPTUAL_EDGES, EVIDENCE_EDGES, CODE_EDGES, BRIDGE_EDGES, IDENTITY_EDGES, JUSTIFICATION_EDGES, BELIEF_EDGES, ABSTRACTION_EDGES, DECOMPOSITION_EDGES, TRANSFER_EDGES, SOLUTION_STRUCTURE_EDGES, SUSPICION_EDGES, TAXONOMY_EDGES, TRIAGE_EDGES, GIT_EDGES, ALL_EDGE_TYPES, PAGERANK_EXCLUSIONS, EDGE_WEIGHT, EDGE_CONDUCTANCE_OVERRIDES, CAUSAL_PROTECT_EDGES, VALIDATION_SOURCES, TRUTH_KIND, ABSTRACTION_LEVEL, PROBLEM_RESOLUTION, DRIFT_KIND, TEST_PATH_RE, TEST_SCOPE_RE;
67
+ var SEMANTIC_NODE_LABELS, ANCHOR_REFUSAL_REASONS, ROUTING_REASONS, CONTEXT_NODE_LABELS, CODE_NODE_LABELS, ALL_NODE_LABELS, STRUCTURAL_EDGES, CAUSAL_EDGES, RESOLUTION_EDGES, CONCEPTUAL_EDGES, EVIDENCE_EDGES, CODE_EDGES, BRIDGE_EDGES, IDENTITY_EDGES, JUSTIFICATION_EDGES, BELIEF_EDGES, ABSTRACTION_EDGES, DECOMPOSITION_EDGES, TRANSFER_EDGES, SOLUTION_STRUCTURE_EDGES, SUSPICION_EDGES, TAXONOMY_EDGES, TRIAGE_EDGES, GIT_EDGES, ALL_EDGE_TYPES, PAGERANK_EXCLUSIONS, EDGE_WEIGHT, EDGE_CONDUCTANCE_OVERRIDES, CAUSAL_PROTECT_EDGES, VALIDATION_SOURCES, TRUTH_KIND, ABSTRACTION_LEVEL, PROBLEM_RESOLUTION, DRIFT_KIND, TEST_PATH_RE, TEST_SCOPE_RE;
68
68
  var init_castalia = __esm({
69
69
  "../../packages/shared/src/castalia.ts"() {
70
70
  "use strict";
@@ -96,6 +96,43 @@ var init_castalia = __esm({
96
96
  // truth (PLAN_PLASTICITY §2.3 / §4). truthKind/scope/groundedSupport in attrs.
97
97
  "Claim"
98
98
  ];
99
+ ANCHOR_REFUSAL_REASONS = [
100
+ /** The spine probe ran CLEAN and did not confirm the public claim. */
101
+ "spine-refused",
102
+ /** No anchor tag at all — cannot positively confirm, fail closed. */
103
+ "untagged-failclosed",
104
+ /** The daemon itself tagged the anchor private — a deliberate claim, not a refusal. */
105
+ "daemon-tagged-private",
106
+ /** The probe threw or timed out — the system was blind, not decided (see routingProbeFailed). */
107
+ "probe-failed"
108
+ ];
109
+ ROUTING_REASONS = [
110
+ /** Harness org (CTF lane, e2e battery, canary) — forced org-private. */
111
+ "harness-clamp",
112
+ /** A signed gateway write target named the destination. */
113
+ "signed-target-public",
114
+ "signed-target-org",
115
+ "signed-target-team",
116
+ /** No membrane in play (gate off, or a caller with no org) — collective. */
117
+ "membrane-off",
118
+ /** A public Domain/Algorithm of this name already exists — recognized topic. */
119
+ "label-public-exists",
120
+ /** A Domain/Algorithm naming no existing public topic — fails closed. */
121
+ "label-novel-failclosed",
122
+ /** The spine confirmed the anchor and no promotion gate stood in the way. */
123
+ "spine-public",
124
+ /** Spine-confirmed, but the rule-of-3 gate held it back for later graduation. */
125
+ "promotion-gate-denied",
126
+ /** The spine did not confirm a public anchor. `routingProbeFailed` says
127
+ * whether that was a verdict or a blind spot. */
128
+ "spine-private"
129
+ // NOTE: no `report-*` reason. Report-extracted nodes (D2) go through the same
130
+ // `routeOrgVisibility` decision as every other write, with the row's stored
131
+ // scope as the enforced target, so they carry `signed-target-*` /
132
+ // `harness-clamp` like anything else. The first D2 router (!1526) minted a
133
+ // `report-scope-inherited` reason from a scope field-copy; it was retired
134
+ // 2026-08-15 with zero nodes carrying it (dev count 0; prod extraction off).
135
+ ];
99
136
  CONTEXT_NODE_LABELS = [
100
137
  "Language",
101
138
  "Package",
@@ -251,7 +288,7 @@ var init_castalia = __esm({
251
288
  DECOMPOSITION_EDGES = ["SPLIT_INTO"];
252
289
  TRANSFER_EDGES = ["MAY_RESOLVE"];
253
290
  SOLUTION_STRUCTURE_EDGES = ["BUILDS_ON", "SUPERSEDES", "ALTERNATIVE_TO"];
254
- SUSPICION_EDGES = ["SUSPECTED_LINK"];
291
+ SUSPICION_EDGES = ["SUSPECTED_LINK", "REGRESSION_OF"];
255
292
  TAXONOMY_EDGES = ["IS_A"];
256
293
  TRIAGE_EDGES = ["TRIAGED_BY", "CONFIRMS", "INDICATES", "ROUTES_TO"];
257
294
  GIT_EDGES = ["POINTS_AT", "PARENT", "AUTHORED_BY"];
@@ -316,8 +353,10 @@ var init_castalia = __esm({
316
353
  "ALTERNATIVE_TO",
317
354
  // symmetric peer marker — weight would let alternatives inflate each other
318
355
  // NB: BUILDS_ON is NOT excluded — it flows extension→base so foundational solutions rank up.
319
- "SUSPECTED_LINK"
356
+ "SUSPECTED_LINK",
320
357
  // Somnus hypothesis (PLAN_SOMNUS_V2 §4) — never flows rank; navigation-invisible.
358
+ "REGRESSION_OF"
359
+ // recurrence marker (RG-recognize) — a question about a close, not signal-flow.
321
360
  ]);
322
361
  EDGE_WEIGHT = {
323
362
  // Causal — high signal
@@ -430,6 +469,7 @@ var init_castalia = __esm({
430
469
  ALTERNATIVE_TO: 0,
431
470
  // Suspicion — a hypothesis, weightless + PageRank-excluded (never flows rank / EIG).
432
471
  SUSPECTED_LINK: 0,
472
+ REGRESSION_OF: 0,
433
473
  // Git — topology/authorship, weightless (not domain signal-flow)
434
474
  POINTS_AT: 0,
435
475
  PARENT: 0,
@@ -472,6 +512,76 @@ var init_castalia = __esm({
472
512
  }
473
513
  });
474
514
 
515
+ // ../../packages/shared/src/graph-events.ts
516
+ function unregisteredGraphEventKeys(event, attrKeys) {
517
+ const vocab = GRAPH_EVENT_VOCABULARIES[event];
518
+ if (!vocab) return [`<unregistered event: ${event}>`];
519
+ const exact = new Set(vocab.keys ?? []);
520
+ const offenders = [];
521
+ for (const key of attrKeys) {
522
+ if (exact.has(key)) continue;
523
+ if (vocab.prefixes.some((prefix) => key.startsWith(prefix))) continue;
524
+ offenders.push(key);
525
+ }
526
+ return offenders;
527
+ }
528
+ function assertGraphEventVocabulary(event, attrs) {
529
+ const env2 = typeof process !== "undefined" ? process.env["NODE_ENV"] : void 0;
530
+ if (env2 === "production") return;
531
+ const offenders = unregisteredGraphEventKeys(event, Object.keys(attrs));
532
+ if (offenders.length === 0) return;
533
+ const message = `graph event '${event}' emits unregistered attribute key(s): ${offenders.join(", ")} \u2014 register the vocabulary and its obligated consumer in @inerrata-corporation/shared graph-events.ts (contribution contract D17)`;
534
+ console.error(message);
535
+ throw new UnregisteredGraphEventError(message);
536
+ }
537
+ var GRAPH_EVENT_VOCABULARIES, UnregisteredGraphEventError;
538
+ var init_graph_events = __esm({
539
+ "../../packages/shared/src/graph-events.ts"() {
540
+ "use strict";
541
+ GRAPH_EVENT_VOCABULARIES = {
542
+ "graph.ingest": {
543
+ // 'queue.' and 'skew.' (queue-health.ts, process-cumulative levels) were
544
+ // found by this registry's FIRST enforcement run to have no consumer
545
+ // anywhere in the tree — registered with that fact stated rather than
546
+ // silently admitted, so the next graph-health pass either gives them a
547
+ // trigger or retires them (D12: a record nothing reads is the defect).
548
+ prefixes: ["ingest.", "sanitize.", "queue.", "skew."],
549
+ consumer: "graph-health.mjs ingest triggers (edge-reject RATE, reinforcedFraction band, the volume floor) + the 'Graph \u2014 Ingest funnel & integrity' board. queue.*/skew.* currently have NO standing consumer (found unread 2026-08-14) \u2014 trigger or retirement owed"
550
+ },
551
+ "graph.twin": {
552
+ prefixes: [],
553
+ keys: ["rewritten", "already-generic", "rejected", "failed", "low-content", "threw", "failureFraction"],
554
+ consumer: "failureFraction is the alertable number (twin-outcomes.ts); its graph-health.mjs trigger is pending a live baseline"
555
+ },
556
+ "graph.burst": {
557
+ prefixes: ["burst."],
558
+ consumer: "the 'Graph \u2014 Demand loop (voids & hypotheses)' board \u2014 misses always emitted, hits sampled 1-in-10"
559
+ },
560
+ "graph.promotion": {
561
+ prefixes: ["promotion."],
562
+ consumer: "graph-health.mjs promotion triggers (receipt-absence floor, kCountErrors > 0) + probe:mechanism-liveness reading the durable pgboss.job.output twin"
563
+ },
564
+ "graph.census": {
565
+ prefixes: [
566
+ "nodes.",
567
+ "tier.",
568
+ "edges.",
569
+ "suspectedLinks.",
570
+ "voids.",
571
+ "membrane.",
572
+ "prose.",
573
+ "overlay."
574
+ ],
575
+ keys: ["communities", "landmarks", "ingestSeq", "censusPgErrored"],
576
+ consumer: "graph-health.mjs census triggers (reversedResolution, communities floor, byVisibility.other drift) + the 'Graph \u2014 Metabolism (nightly pipeline)' board"
577
+ }
578
+ };
579
+ UnregisteredGraphEventError = class extends Error {
580
+ name = "UnregisteredGraphEventError";
581
+ };
582
+ }
583
+ });
584
+
475
585
  // ../../packages/shared/src/canonical/hash.ts
476
586
  import { createHash } from "node:crypto";
477
587
  function sha256(input) {
@@ -15979,6 +16089,7 @@ __export(src_exports, {
15979
16089
  ALL_EDGE_TYPES: () => ALL_EDGE_TYPES,
15980
16090
  ALL_NODE_LABELS: () => ALL_NODE_LABELS,
15981
16091
  AMBIGUOUS_ALIASES: () => AMBIGUOUS_ALIASES,
16092
+ ANCHOR_REFUSAL_REASONS: () => ANCHOR_REFUSAL_REASONS,
15982
16093
  BELIEF_EDGES: () => BELIEF_EDGES,
15983
16094
  BRIDGE_EDGES: () => BRIDGE_EDGES,
15984
16095
  CAUSAL_EDGES: () => CAUSAL_EDGES,
@@ -16000,6 +16111,7 @@ __export(src_exports, {
16000
16111
  EDGE_WEIGHT: () => EDGE_WEIGHT,
16001
16112
  EVIDENCE_EDGES: () => EVIDENCE_EDGES,
16002
16113
  GIT_EDGES: () => GIT_EDGES,
16114
+ GRAPH_EVENT_VOCABULARIES: () => GRAPH_EVENT_VOCABULARIES,
16003
16115
  IDENTITY_EDGES: () => IDENTITY_EDGES,
16004
16116
  INGEST_EXTRACTION_SOURCES: () => INGEST_EXTRACTION_SOURCES,
16005
16117
  INGEST_SOURCES: () => INGEST_SOURCES,
@@ -16016,6 +16128,7 @@ __export(src_exports, {
16016
16128
  QUARANTINE_EXTRACTION_SOURCE_PREFIX: () => QUARANTINE_EXTRACTION_SOURCE_PREFIX,
16017
16129
  RESOLUTION_EDGES: () => RESOLUTION_EDGES,
16018
16130
  RE_LEADING_ARTICLE: () => RE_LEADING_ARTICLE,
16131
+ ROUTING_REASONS: () => ROUTING_REASONS,
16019
16132
  RouteContextCountWireSchema: () => RouteContextCountWireSchema,
16020
16133
  SEMANTIC_NODE_LABELS: () => SEMANTIC_NODE_LABELS,
16021
16134
  SOLUTION_STRUCTURE_EDGES: () => SOLUTION_STRUCTURE_EDGES,
@@ -16027,9 +16140,11 @@ __export(src_exports, {
16027
16140
  TRANSFER_EDGES: () => TRANSFER_EDGES,
16028
16141
  TRIAGE_EDGES: () => TRIAGE_EDGES,
16029
16142
  TRUTH_KIND: () => TRUTH_KIND,
16143
+ UnregisteredGraphEventError: () => UnregisteredGraphEventError,
16030
16144
  VALIDATION_SOURCES: () => VALIDATION_SOURCES,
16031
16145
  aliasesLongestFirst: () => aliasesLongestFirst,
16032
16146
  allEntities: () => allEntities,
16147
+ assertGraphEventVocabulary: () => assertGraphEventVocabulary,
16033
16148
  canonicalClaimId: () => canonicalClaimId,
16034
16149
  canonicalJSON: () => canonicalJSON,
16035
16150
  canonicalize: () => canonicalize,
@@ -16091,6 +16206,7 @@ __export(src_exports, {
16091
16206
  summaryRejectionReason: () => summaryRejectionReason,
16092
16207
  toCloudAttrs: () => toCloudAttrs,
16093
16208
  toolCanonicalId: () => toolCanonicalId,
16209
+ unregisteredGraphEventKeys: () => unregisteredGraphEventKeys,
16094
16210
  validateCastaliaPayload: () => validateCastaliaPayload,
16095
16211
  versionlessPurl: () => versionlessPurl,
16096
16212
  vetSidecarSummaries: () => vetSidecarSummaries
@@ -16099,6 +16215,7 @@ var init_src = __esm({
16099
16215
  "../../packages/shared/src/index.ts"() {
16100
16216
  "use strict";
16101
16217
  init_castalia();
16218
+ init_graph_events();
16102
16219
  init_identity();
16103
16220
  init_wire();
16104
16221
  init_edge_rules();
@@ -16617,7 +16734,13 @@ var init_store = __esm({
16617
16734
  // the cloud door's `to: [Solution]` rule is untouched. The `from` constraint
16618
16735
  // stays: the reversed (Solution)-FIXED_BY->(...) splash bug is the reason
16619
16736
  // this rule exists at all.
16620
- FIXED_BY: { from: EDGE_RULES["FIXED_BY"]?.from, to: ["Solution", "Episode"] }
16737
+ FIXED_BY: { from: EDGE_RULES["FIXED_BY"]?.from, to: ["Solution", "Episode"] },
16738
+ // RG-recognize: a NEW open Problem re-observing a RESOLVED one ("this is
16739
+ // back — the fix didn't hold"). Local-only for now: the instance drain's
16740
+ // INSTANCE_EDGES allowlist doesn't ship it, and the cloud door's matrix
16741
+ // doesn't know it — crossing the membrane is a deliberate later step, never
16742
+ // a side effect (the half-shipped-edge-type lesson).
16743
+ REGRESSION_OF: { from: ["Problem"], to: ["Problem"] }
16621
16744
  };
16622
16745
  SCHEMA_VERSION = 6;
16623
16746
  SCHEMA_SQL = `
@@ -18850,11 +18973,16 @@ function foldDuplicateProblem(store, dup, survivor, ts) {
18850
18973
  if (!surv) return;
18851
18974
  const sources = new Set(surv.attrs["sources"] ?? []);
18852
18975
  for (const s of dup.attrs["sources"] ?? []) sources.add(s);
18976
+ const dupSeq = dup.attrs["createdAtSeq"] ?? store.currentIngestSeq();
18977
+ const exposure = priorExposure(store, survivor.id, dupSeq);
18978
+ const expField = `reinforce${exposure[0].toUpperCase()}${exposure.slice(1)}Count`;
18853
18979
  store.updateNode(survivor.id, {
18854
18980
  attrs: {
18855
18981
  ...surv.attrs,
18856
18982
  sources: [...sources],
18857
- corroborations: corroborations(surv) + corroborations(dup) + 1
18983
+ corroborations: corroborations(surv) + corroborations(dup) + 1,
18984
+ [expField]: (surv.attrs[expField] ?? 0) + 1,
18985
+ lastReinforceExposure: exposure
18858
18986
  },
18859
18987
  cumulativeHits: (surv.cumulativeHits ?? 0) + (dup.cumulativeHits ?? 0),
18860
18988
  lastUpdatedAt: ts
@@ -19018,7 +19146,22 @@ function ingestDesignProblem(store, flag, opts) {
19018
19146
  const existing = store.getNode(problemId);
19019
19147
  let created = false;
19020
19148
  let corroborated = false;
19149
+ let recurredResolved = false;
19150
+ let reopenedFromRegression = false;
19021
19151
  if (existing) {
19152
+ const resolvedUnretracted = existing.attrs["resolvedAt"] != null && existing.attrs["resolvedAs"] == null;
19153
+ const recurAttrs = () => {
19154
+ recurredResolved = true;
19155
+ const sources2 = existing.attrs["recurrenceSources"] ?? [];
19156
+ return {
19157
+ resolutionSuspect: true,
19158
+ recurredAt: opts.ts,
19159
+ recurrences: (Number(existing.attrs["recurrences"]) || 0) + 1,
19160
+ // RG-reopen: the door touch carries its session — the reopen check
19161
+ // after the update counts DISTINCT sources against REOPEN_MIN_SOURCES.
19162
+ ...sources2.includes(opts.source) ? {} : { recurrenceSources: [...sources2, opts.source].slice(-8) }
19163
+ };
19164
+ };
19022
19165
  const sources = existing.attrs["sources"] ?? [];
19023
19166
  if (!sources.includes(opts.source)) {
19024
19167
  corroborated = true;
@@ -19027,6 +19170,9 @@ function ingestDesignProblem(store, flag, opts) {
19027
19170
  store.updateNode(problemId, {
19028
19171
  attrs: {
19029
19172
  ...existing.attrs,
19173
+ // An INDEPENDENT source re-deriving a resolved problem marks
19174
+ // immediately — that is the strongest recurrence evidence there is.
19175
+ ...resolvedUnretracted ? recurAttrs() : {},
19030
19176
  sources: [...sources, opts.source],
19031
19177
  corroborations: corroborations2,
19032
19178
  ...promoted ? { provisional: false } : {},
@@ -19052,6 +19198,13 @@ function ingestDesignProblem(store, flag, opts) {
19052
19198
  ...reinforcedSeqAttrs(store, existing.label),
19053
19199
  lastUpdatedAt: opts.ts
19054
19200
  });
19201
+ if (recurredResolved) reopenedFromRegression = maybeReopenFromRegression(store, problemId, opts.ts);
19202
+ } else if (resolvedUnretracted && opts.ts - (Number(existing.attrs["recurredAt"]) || 0) > RECURRENCE_DEBOUNCE_MS) {
19203
+ store.updateNode(problemId, {
19204
+ attrs: { ...existing.attrs, ...recurAttrs() },
19205
+ lastUpdatedAt: opts.ts
19206
+ });
19207
+ reopenedFromRegression = maybeReopenFromRegression(store, problemId, opts.ts);
19055
19208
  }
19056
19209
  } else {
19057
19210
  created = true;
@@ -19063,7 +19216,8 @@ function ingestDesignProblem(store, flag, opts) {
19063
19216
  designProblemId: problemId,
19064
19217
  sources: [opts.source],
19065
19218
  corroborations: 0,
19066
- scope: {}
19219
+ scope: {},
19220
+ ...opts.hostHarness ? { hostHarness: opts.hostHarness } : {}
19067
19221
  })
19068
19222
  );
19069
19223
  }
@@ -19106,7 +19260,17 @@ function ingestDesignProblem(store, flag, opts) {
19106
19260
  const linkText = flag.cause?.trim() ? `${statement}
19107
19261
  ${flag.cause.trim()}` : statement;
19108
19262
  const linkedPackages = linkProblemToPackages(store, problemId, linkText, opts.ts).linked;
19109
- return { problemId, created, corroborated, rootCauseId, solutionId, anchored, linkedPackages };
19263
+ return {
19264
+ problemId,
19265
+ created,
19266
+ corroborated,
19267
+ rootCauseId,
19268
+ solutionId,
19269
+ anchored,
19270
+ linkedPackages,
19271
+ ...recurredResolved ? { recurredResolved } : {},
19272
+ ...reopenedFromRegression ? { reopenedFromRegression } : {}
19273
+ };
19110
19274
  }
19111
19275
  function canonicalizePatternText(name2) {
19112
19276
  const stripped = name2.replace(/^\s*pattern(?:\s*[×x]\s*\d+)?\s*(?:\(exemplar\))?\s*[:—–-]\s*/i, "").replace(/\s+/g, " ").trim();
@@ -19252,22 +19416,61 @@ function tokenJaccard(a, b) {
19252
19416
  return inter / (sa.size + sb.size - inter);
19253
19417
  }
19254
19418
  function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
19419
+ return foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind).foldedId;
19420
+ }
19421
+ function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
19422
+ const report = { ...EMPTY_FOLD_REPORT };
19255
19423
  const stmt = statement.trim();
19256
- if (!isAnchorableCodePath(relPath)) return null;
19424
+ if (!isAnchorableCodePath(relPath)) {
19425
+ report.noFile = true;
19426
+ return report;
19427
+ }
19257
19428
  const file2 = resolveFileNode(store, relPath, workspaceId2);
19258
- if (!file2) return null;
19429
+ if (!file2) {
19430
+ report.noFile = true;
19431
+ return report;
19432
+ }
19259
19433
  const selfId = identityId({ kind: "DesignProblem", statement: stmt });
19434
+ let stmtVec = null;
19260
19435
  let best = null;
19436
+ let bestScout = null;
19261
19437
  for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
19262
19438
  const cand = store.getNode(e.from);
19263
19439
  if (!cand || cand.label !== "Problem" || cand.attrs["resolvedAt"]) continue;
19264
19440
  if (cand.id === selfId) continue;
19265
19441
  const candKind = cand.attrs["kind"] === "constraint" ? "constraint" : "problem";
19266
- if (candKind !== (kind === "constraint" ? "constraint" : "problem")) continue;
19442
+ if (candKind !== (kind === "constraint" ? "constraint" : "problem")) {
19443
+ report.kindVetoed++;
19444
+ continue;
19445
+ }
19446
+ report.candidates++;
19267
19447
  const score2 = tokenJaccard(stmt, cand.description);
19448
+ if (score2 > report.bestJaccard) report.bestJaccard = score2;
19268
19449
  if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) best = { node: cand, score: score2 };
19450
+ if (!best) {
19451
+ stmtVec ??= embed(stmt);
19452
+ const hc = cosine(stmtVec, embed(cand.description));
19453
+ if (hc > report.bestHashCos) report.bestHashCos = hc;
19454
+ if ((hc >= SAME_ANCHOR_SCOUT_MIN_COSINE || score2 >= SAME_ANCHOR_NOMINATE_JACCARD) && (!bestScout || hc > bestScout.hashCos)) {
19455
+ bestScout = { node: cand, hashCos: hc, jaccard: score2 };
19456
+ }
19457
+ }
19269
19458
  }
19270
- if (!best) return null;
19459
+ if (!best && bestScout && bestScout.hashCos >= SAME_ANCHOR_SCOUT_AUTO_COSINE) {
19460
+ best = { node: bestScout.node, score: bestScout.jaccard };
19461
+ report.foldedBy = "scout";
19462
+ }
19463
+ if (!best) {
19464
+ if (bestScout) {
19465
+ report.nominate = {
19466
+ targetId: bestScout.node.id,
19467
+ jaccard: bestScout.jaccard,
19468
+ hashCos: bestScout.hashCos
19469
+ };
19470
+ }
19471
+ return report;
19472
+ }
19473
+ report.foldedBy ??= "jaccard";
19271
19474
  const existing = best.node;
19272
19475
  const sources = existing.attrs["sources"] ?? [];
19273
19476
  if (!sources.includes(source)) {
@@ -19289,7 +19492,79 @@ function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, sou
19289
19492
  lastUpdatedAt: ts
19290
19493
  });
19291
19494
  }
19292
- return existing.id;
19495
+ report.foldedId = existing.id;
19496
+ return report;
19497
+ }
19498
+ function findResolvedAnchorMatch(store, relPath, workspaceId2, statement, kind = "problem") {
19499
+ const stmt = statement.trim();
19500
+ if (!isAnchorableCodePath(relPath)) return null;
19501
+ const file2 = resolveFileNode(store, relPath, workspaceId2);
19502
+ if (!file2) return null;
19503
+ const selfId = identityId({ kind: "DesignProblem", statement: stmt });
19504
+ let best = null;
19505
+ for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
19506
+ const cand = store.getNode(e.from);
19507
+ if (!cand || cand.label !== "Problem") continue;
19508
+ if (cand.attrs["resolvedAt"] == null || cand.attrs["resolvedAs"] != null) continue;
19509
+ if (cand.attrs["mergedInto"] !== void 0) continue;
19510
+ if (cand.id === selfId) continue;
19511
+ const candKind = cand.attrs["kind"] === "constraint" ? "constraint" : "problem";
19512
+ if (candKind !== (kind === "constraint" ? "constraint" : "problem")) continue;
19513
+ const score2 = tokenJaccard(stmt, cand.description);
19514
+ if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) {
19515
+ best = { id: cand.id, score: score2 };
19516
+ }
19517
+ }
19518
+ return best?.id ?? null;
19519
+ }
19520
+ function maybeReopenFromRegression(store, nodeId, ts) {
19521
+ const n = store.getNode(nodeId);
19522
+ if (!n) return false;
19523
+ if (n.attrs["resolvedAt"] == null || n.attrs["resolvedAs"] != null) return false;
19524
+ const sources = n.attrs["recurrenceSources"] ?? [];
19525
+ if (new Set(sources).size < REOPEN_MIN_SOURCES) return false;
19526
+ const attrs = { ...n.attrs };
19527
+ delete attrs["resolvedAt"];
19528
+ delete attrs["resolutionSuspect"];
19529
+ attrs["reopenedFrom"] = "regression";
19530
+ attrs["reopenedAt"] = ts;
19531
+ store.updateNode(nodeId, { attrs, lastUpdatedAt: ts });
19532
+ return true;
19533
+ }
19534
+ function markResolvedRecurrence(store, nodeId, ts, source) {
19535
+ const n = store.getNode(nodeId);
19536
+ if (!n) return { marked: false, reopened: false };
19537
+ if (n.attrs["resolvedAt"] == null || n.attrs["resolvedAs"] != null) {
19538
+ return { marked: false, reopened: false };
19539
+ }
19540
+ const sources = n.attrs["recurrenceSources"] ?? [];
19541
+ store.updateNode(nodeId, {
19542
+ attrs: {
19543
+ ...n.attrs,
19544
+ resolutionSuspect: true,
19545
+ recurredAt: ts,
19546
+ recurrences: (Number(n.attrs["recurrences"]) || 0) + 1,
19547
+ ...source && !sources.includes(source) ? { recurrenceSources: [...sources, source].slice(-RECURRENCE_SOURCES_CAP) } : {}
19548
+ },
19549
+ lastUpdatedAt: ts
19550
+ });
19551
+ const reopened = source ? maybeReopenFromRegression(store, nodeId, ts) : false;
19552
+ return { marked: true, reopened };
19553
+ }
19554
+ function confirmFixHeld(store, solutionId, ts) {
19555
+ let acknowledged = 0;
19556
+ for (const e of store.inEdges(solutionId, ["SOLVED_BY", "FIXED_BY"])) {
19557
+ const p = store.getNode(e.from);
19558
+ if (!p || p.label !== "Problem") continue;
19559
+ if (p.attrs["resolutionSuspect"] !== true || p.attrs["resolvedAt"] == null) continue;
19560
+ const attrs = { ...p.attrs };
19561
+ delete attrs["resolutionSuspect"];
19562
+ attrs["fixHeldConfirmedAt"] = ts;
19563
+ delete attrs["recurrenceSources"];
19564
+ store.updateNode(p.id, { attrs, lastUpdatedAt: ts });
19565
+ acknowledged++;
19566
+ }
19567
+ return acknowledged;
19293
19568
  }
19294
19569
  function isFragmentFixNote(note) {
19295
19570
  const t = note.trim();
@@ -19692,7 +19967,7 @@ function priorExposure(store, nodeId, atSeq) {
19692
19967
  if ((n.attrs["evictedCount"] ?? 0) > 0) return "evicted";
19693
19968
  return "unshown";
19694
19969
  }
19695
- var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, AUTO_MINT_PREFIX, FIX_CANDIDATE_ASK_LIMIT;
19970
+ var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, RECURRENCE_DEBOUNCE_MS, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, EMPTY_FOLD_REPORT, SAME_ANCHOR_SCOUT_AUTO_COSINE, SAME_ANCHOR_SCOUT_MIN_COSINE, SAME_ANCHOR_NOMINATE_JACCARD, REOPEN_MIN_SOURCES, RECURRENCE_SOURCES_CAP, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, AUTO_MINT_PREFIX, FIX_CANDIDATE_ASK_LIMIT;
19696
19971
  var init_design_problem = __esm({
19697
19972
  "../../packages/local-graph/src/design-problem.ts"() {
19698
19973
  "use strict";
@@ -19705,6 +19980,7 @@ var init_design_problem = __esm({
19705
19980
  init_problem_dedup();
19706
19981
  DESIGN_PROMOTE_AT = 1;
19707
19982
  STATEMENT_ALIAS_CAP = 5;
19983
+ RECURRENCE_DEBOUNCE_MS = 60 * 60 * 1e3;
19708
19984
  PATTERN_DEDUP_COSINE = 0.85;
19709
19985
  PATTERN_ALIAS_CAP = 5;
19710
19986
  DEDUP_STOPWORDS = /* @__PURE__ */ new Set([
@@ -19731,6 +20007,21 @@ var init_design_problem = __esm({
19731
20007
  "where"
19732
20008
  ]);
19733
20009
  SAME_ANCHOR_DEDUP_JACCARD = 0.6;
20010
+ EMPTY_FOLD_REPORT = {
20011
+ foldedId: null,
20012
+ foldedBy: null,
20013
+ noFile: false,
20014
+ candidates: 0,
20015
+ kindVetoed: 0,
20016
+ bestJaccard: 0,
20017
+ bestHashCos: 0,
20018
+ nominate: null
20019
+ };
20020
+ SAME_ANCHOR_SCOUT_AUTO_COSINE = 0.85;
20021
+ SAME_ANCHOR_SCOUT_MIN_COSINE = 0.5;
20022
+ SAME_ANCHOR_NOMINATE_JACCARD = 0.3;
20023
+ REOPEN_MIN_SOURCES = 2;
20024
+ RECURRENCE_SOURCES_CAP = 8;
19734
20025
  CITABLE_PRIOR_LABELS = /* @__PURE__ */ new Set([
19735
20026
  "Solution",
19736
20027
  "RootCause",
@@ -21973,6 +22264,160 @@ var init_community2 = __esm({
21973
22264
  }
21974
22265
  });
21975
22266
 
22267
+ // ../../packages/local-graph/src/fold-candidates.ts
22268
+ function markFoldCandidate(store, problemId, targetId, info2) {
22269
+ if (problemId === targetId) return false;
22270
+ const n = store.getNode(problemId);
22271
+ if (!openProblem(n)) return false;
22272
+ if (n.attrs["foldCandidate"] !== void 0) return false;
22273
+ const rejected = n.attrs["foldRejected"] ?? [];
22274
+ if (rejected.includes(targetId)) return false;
22275
+ if (!openProblem(store.getNode(targetId))) return false;
22276
+ const mark = {
22277
+ targetId,
22278
+ ...info2.hashCos !== void 0 ? { hashCos: info2.hashCos } : {},
22279
+ ...info2.jaccard !== void 0 ? { jaccard: info2.jaccard } : {},
22280
+ source: info2.source,
22281
+ ts: info2.ts,
22282
+ markedAtSeq: store.currentIngestSeq()
22283
+ };
22284
+ store.updateNode(problemId, { attrs: { ...n.attrs, foldCandidate: mark }, lastUpdatedAt: info2.ts });
22285
+ return true;
22286
+ }
22287
+ function findWorkspaceFoldCandidate(store, workspaceId2, statement, kind, opts) {
22288
+ const stmtVec = embed(statement);
22289
+ const wantConstraint = kind === "constraint";
22290
+ const open2 = store.findNodesByLabel("Problem").filter(
22291
+ (p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.id !== opts?.excludeId && (p.attrs["workspaceId"] === void 0 || p.attrs["workspaceId"] === workspaceId2) && isConstraintProblem(p) === wantConstraint
22292
+ ).sort((a, b) => (b.lastUpdatedAt ?? b.createdAt) - (a.lastUpdatedAt ?? a.createdAt)).slice(0, opts?.scanCap ?? WORKSPACE_SCAN_CAP);
22293
+ let best = null;
22294
+ for (const p of open2) {
22295
+ const c = cosine(stmtVec, embed(p.description));
22296
+ if (c >= FOLD_SCOUT_MIN_COSINE && (!best || c > best.hashCos)) best = { targetId: p.id, hashCos: c };
22297
+ }
22298
+ return best;
22299
+ }
22300
+ function followMerges(store, id) {
22301
+ let cur = store.getNode(id);
22302
+ for (let hop = 0; hop < 3 && cur && cur.attrs["mergedInto"] !== void 0; hop++) {
22303
+ cur = store.getNode(String(cur.attrs["mergedInto"]));
22304
+ }
22305
+ return cur ?? null;
22306
+ }
22307
+ function clearCandidate(store, node2, ts, rejectTargetId) {
22308
+ const attrs = { ...node2.attrs };
22309
+ delete attrs["foldCandidate"];
22310
+ if (rejectTargetId) {
22311
+ const rejected = attrs["foldRejected"] ?? [];
22312
+ if (!rejected.includes(rejectTargetId)) {
22313
+ attrs["foldRejected"] = [...rejected, rejectTargetId].slice(-FOLD_REJECTED_CAP);
22314
+ }
22315
+ }
22316
+ store.updateNode(node2.id, { attrs, lastUpdatedAt: ts });
22317
+ }
22318
+ async function resolveFoldCandidates(store, opts) {
22319
+ const report = {
22320
+ examined: 0,
22321
+ folded: 0,
22322
+ foldedEmbedding: 0,
22323
+ foldedJudge: 0,
22324
+ rejected: 0,
22325
+ standing: 0,
22326
+ judgeErrors: 0,
22327
+ retargeted: 0
22328
+ };
22329
+ const limit = opts.limit ?? 8;
22330
+ const marked = store.findNodesByLabel("Problem").filter((p) => openProblem(p) && p.attrs["foldCandidate"] !== void 0).sort((a, b) => {
22331
+ const ma = a.attrs["foldCandidate"].ts ?? 0;
22332
+ const mb = b.attrs["foldCandidate"].ts ?? 0;
22333
+ return ma - mb;
22334
+ }).slice(0, limit);
22335
+ for (const node2 of marked) {
22336
+ const fresh = store.getNode(node2.id);
22337
+ if (!openProblem(fresh)) continue;
22338
+ report.examined++;
22339
+ const mark = fresh.attrs["foldCandidate"];
22340
+ let target = followMerges(store, mark.targetId);
22341
+ if (target && target.id !== mark.targetId) report.retargeted++;
22342
+ if (!openProblem(target)) {
22343
+ clearCandidate(store, fresh, opts.ts);
22344
+ report.rejected++;
22345
+ continue;
22346
+ }
22347
+ if (isConstraintProblem(fresh) !== isConstraintProblem(target)) {
22348
+ clearCandidate(store, fresh, opts.ts, target.id);
22349
+ report.rejected++;
22350
+ continue;
22351
+ }
22352
+ let cos = null;
22353
+ if (fresh.embedding.length > 0 && fresh.embedding.length === target.embedding.length && (fresh.attrs["embeddingVersion"] ?? "") === (target.attrs["embeddingVersion"] ?? "")) {
22354
+ cos = cosine(fresh.embedding, target.embedding);
22355
+ }
22356
+ if (cos !== null && cos >= FOLD_EMBED_AUTO_COSINE) {
22357
+ foldPair(store, fresh, target, opts.ts);
22358
+ report.folded++;
22359
+ report.foldedEmbedding++;
22360
+ continue;
22361
+ }
22362
+ if (cos !== null && cos < FOLD_JUDGE_MIN_COSINE) {
22363
+ clearCandidate(store, fresh, opts.ts, target.id);
22364
+ report.rejected++;
22365
+ continue;
22366
+ }
22367
+ if (!opts.judge) {
22368
+ report.standing++;
22369
+ continue;
22370
+ }
22371
+ let verdict = null;
22372
+ try {
22373
+ verdict = await opts.judge(fresh.description, target.description);
22374
+ } catch {
22375
+ verdict = null;
22376
+ }
22377
+ if (verdict === "same") {
22378
+ foldPair(store, fresh, target, opts.ts);
22379
+ report.folded++;
22380
+ report.foldedJudge++;
22381
+ } else if (verdict === "distinct") {
22382
+ clearCandidate(store, fresh, opts.ts, target.id);
22383
+ report.rejected++;
22384
+ } else {
22385
+ report.judgeErrors++;
22386
+ report.standing++;
22387
+ }
22388
+ }
22389
+ return report;
22390
+ }
22391
+ function foldPair(store, a, b, ts) {
22392
+ const corro = (n) => Number(n.attrs["corroborations"] ?? 0);
22393
+ const survivor = corro(a) !== corro(b) ? corro(a) > corro(b) ? a : b : a.createdAt <= b.createdAt ? a : b;
22394
+ const dup = survivor === a ? b : a;
22395
+ for (const n of [a, b]) {
22396
+ if (n.attrs["foldCandidate"] !== void 0) clearCandidate(store, n, ts);
22397
+ }
22398
+ const freshSurvivor = store.getNode(survivor.id);
22399
+ const freshDup = store.getNode(dup.id);
22400
+ if (!freshSurvivor || !freshDup) return;
22401
+ foldDuplicateProblem(store, freshDup, freshSurvivor, ts);
22402
+ }
22403
+ var FOLD_SCOUT_AUTO_COSINE, FOLD_SCOUT_MIN_COSINE, FOLD_SCOUT_MIN_JACCARD, FOLD_EMBED_AUTO_COSINE, FOLD_JUDGE_MIN_COSINE, FOLD_REJECTED_CAP, WORKSPACE_SCAN_CAP, openProblem;
22404
+ var init_fold_candidates = __esm({
22405
+ "../../packages/local-graph/src/fold-candidates.ts"() {
22406
+ "use strict";
22407
+ init_src3();
22408
+ init_design_problem();
22409
+ init_problem_dedup();
22410
+ FOLD_SCOUT_AUTO_COSINE = 0.85;
22411
+ FOLD_SCOUT_MIN_COSINE = 0.5;
22412
+ FOLD_SCOUT_MIN_JACCARD = 0.3;
22413
+ FOLD_EMBED_AUTO_COSINE = 0.88;
22414
+ FOLD_JUDGE_MIN_COSINE = 0.5;
22415
+ FOLD_REJECTED_CAP = 8;
22416
+ WORKSPACE_SCAN_CAP = 400;
22417
+ openProblem = (n) => !!n && n.label === "Problem" && n.attrs["resolvedAt"] == null && n.attrs["mergedInto"] === void 0;
22418
+ }
22419
+ });
22420
+
21976
22421
  // ../../packages/local-graph/src/tools.ts
21977
22422
  function toolNodeId(name2) {
21978
22423
  return `tool:${name2}`;
@@ -22253,6 +22698,50 @@ var init_mechanism_liveness = __esm({
22253
22698
  STALL_MIN_RUNS = 3;
22254
22699
  STALL_MIN_SPAN_MS = 60 * 60 * 1e3;
22255
22700
  MECHANISMS = [
22701
+ {
22702
+ id: "refute-local-stamp",
22703
+ what: "stamps a harvested refute witness onto the LOCAL node (refutedAt/refutations/reason)",
22704
+ // Stamped at harvest, so the capture pass carries the effect counter.
22705
+ pass: "capture",
22706
+ // Refutes are rare by design (a verdict, not a lean-on) — the bar is "the
22707
+ // field is written at all", the revisitAnsweredWhen precedent. RG-mark
22708
+ // ships the producer; RG-recall is the ranking consumer — until it lands,
22709
+ // THIS descriptor is the consumer, which is exactly the condition the
22710
+ // probe exists to keep visible rather than let rot silently.
22711
+ fed: {
22712
+ labels: ["Problem", "Solution", "RootCause", "Pattern"],
22713
+ attr: "refutedAt",
22714
+ minFraction: 0
22715
+ },
22716
+ effectCounter: "refutesStamped",
22717
+ note: "RG-mark; reason text is local-only (wire ships nodeId+witnessKey)"
22718
+ },
22719
+ {
22720
+ id: "regression-recognize",
22721
+ what: "marks a resolved problem as resolutionSuspect when it recurs at the door",
22722
+ pass: "capture",
22723
+ // Regressions are rare and precious — written-at-all bar, like refutedAt.
22724
+ fed: {
22725
+ labels: ["Problem"],
22726
+ attr: "recurredAt",
22727
+ minFraction: 0
22728
+ },
22729
+ effectCounter: "recurrencesMarked",
22730
+ note: "RG-recognize tier A (exact-id door); tiers B/C add REGRESSION_OF edges; reopen is RG-reopen's"
22731
+ },
22732
+ {
22733
+ id: "regression-reopen",
22734
+ what: "flips a marked close back open once two DISTINCT sessions witnessed the recurrence",
22735
+ pass: "capture",
22736
+ // Reopens should be rarer than recurrences by construction — written-at-all.
22737
+ fed: {
22738
+ labels: ["Problem"],
22739
+ attr: "reopenedFrom",
22740
+ minFraction: 0
22741
+ },
22742
+ effectCounter: "reopenedFromRegression",
22743
+ note: "RG-reopen; the machine sweep never counts as a witness \u2014 only sessions do"
22744
+ },
22256
22745
  {
22257
22746
  id: "fix-candidate-ask",
22258
22747
  what: "asks an agent whether an edit to a problem's anchor fixed it",
@@ -22383,6 +22872,32 @@ var init_mechanism_liveness = __esm({
22383
22872
  backlogCounter: "anchorHintsStanding",
22384
22873
  note: "drains as hinted files change; contentChangedAt is forward-only so the backlog moves with real edits"
22385
22874
  },
22875
+ {
22876
+ id: "fold-candidate-resolve",
22877
+ what: "resolves parked paraphrase-fold nominations (embedding-certain bands auto, ambiguous band via the equivalence judge)",
22878
+ // The settle boundary's fold-judge pass is the sole consumer of
22879
+ // attrs.foldCandidate; producers are the file/workspace scouts (capture)
22880
+ // and the settle near-miss band (mergeProblemsByEmbedding).
22881
+ pass: "fold-judge",
22882
+ // Conditional mark, rare by design (a nomination is a near-miss event) —
22883
+ // the bar is "the field is written at all", the revisit-sweep precedent.
22884
+ fed: { labels: ["Problem"], attr: "foldCandidate", minFraction: 0 },
22885
+ effectCounter: "folded",
22886
+ // Standing nominations ARE the judge-less backlog — without the gauge a
22887
+ // deployment with no judge configured reads as "nothing to fold".
22888
+ backlogCounter: "standing",
22889
+ note: "standing > 0 with folded 0 = the judge lane is unconfigured (ERRATA_FOLD_JUDGE / azure env), not an empty queue"
22890
+ },
22891
+ {
22892
+ id: "cite-anchor-accrual",
22893
+ what: "accrues cite-context anchor evidence on anchorless cited priors; two distinct sessions promote a cite-confirmed ANCHORED_AT",
22894
+ pass: "capture",
22895
+ // Conditional attr: only anchorless semantic priors cited from a known
22896
+ // working file carry hints — "written at all" is the honest floor.
22897
+ fed: { labels: ["Problem"], attr: "citeAnchorHints", minFraction: 0 },
22898
+ effectCounter: "citeAnchorsPromoted",
22899
+ note: "the supply-side fix for corroboration 0-for-8: promoted anchors are what the overlap gate intersects on the NEXT citation"
22900
+ },
22386
22901
  {
22387
22902
  id: "wm-reinforce-exposure",
22388
22903
  what: "labels each paraphrase re-derivation with the prior's exposure (shown/evicted/unshown)",
@@ -22503,6 +23018,12 @@ __export(src_exports2, {
22503
23018
  CREDIT_FAMILY: () => CREDIT_FAMILY,
22504
23019
  DEFAULT_MIN_COVERAGE: () => DEFAULT_MIN_COVERAGE,
22505
23020
  FIX_CANDIDATE_ASK_LIMIT: () => FIX_CANDIDATE_ASK_LIMIT,
23021
+ FOLD_EMBED_AUTO_COSINE: () => FOLD_EMBED_AUTO_COSINE,
23022
+ FOLD_JUDGE_MIN_COSINE: () => FOLD_JUDGE_MIN_COSINE,
23023
+ FOLD_REJECTED_CAP: () => FOLD_REJECTED_CAP,
23024
+ FOLD_SCOUT_AUTO_COSINE: () => FOLD_SCOUT_AUTO_COSINE,
23025
+ FOLD_SCOUT_MIN_COSINE: () => FOLD_SCOUT_MIN_COSINE,
23026
+ FOLD_SCOUT_MIN_JACCARD: () => FOLD_SCOUT_MIN_JACCARD,
22506
23027
  JOINT_COMMUNITY_EDGES: () => JOINT_COMMUNITY_EDGES,
22507
23028
  JOINT_COMMUNITY_LABELS: () => JOINT_COMMUNITY_LABELS,
22508
23029
  MAX_ANCHOR_FILES: () => MAX_ANCHOR_FILES,
@@ -22512,11 +23033,17 @@ __export(src_exports2, {
22512
23033
  PERCOLATING_DRIFT_EDGES: () => PERCOLATING_DRIFT_EDGES,
22513
23034
  PERCOLATING_EDGES: () => PERCOLATING_EDGES,
22514
23035
  PERCOLATING_LABELS: () => PERCOLATING_LABELS,
23036
+ RECURRENCE_DEBOUNCE_MS: () => RECURRENCE_DEBOUNCE_MS,
23037
+ REOPEN_MIN_SOURCES: () => REOPEN_MIN_SOURCES,
22515
23038
  SAME_ANCHOR_DEDUP_JACCARD: () => SAME_ANCHOR_DEDUP_JACCARD,
23039
+ SAME_ANCHOR_NOMINATE_JACCARD: () => SAME_ANCHOR_NOMINATE_JACCARD,
23040
+ SAME_ANCHOR_SCOUT_AUTO_COSINE: () => SAME_ANCHOR_SCOUT_AUTO_COSINE,
23041
+ SAME_ANCHOR_SCOUT_MIN_COSINE: () => SAME_ANCHOR_SCOUT_MIN_COSINE,
22516
23042
  STALL_MIN_RUNS: () => STALL_MIN_RUNS,
22517
23043
  STALL_MIN_SPAN_MS: () => STALL_MIN_SPAN_MS,
22518
23044
  SqliteGraphStore: () => SqliteGraphStore,
22519
23045
  WM_CALIBRATION_LABELS: () => WM_CALIBRATION_LABELS,
23046
+ WORKSPACE_SCAN_CAP: () => WORKSPACE_SCAN_CAP,
22520
23047
  addDependency: () => addDependency,
22521
23048
  aggregateClaimConfidence: () => aggregateClaimConfidence,
22522
23049
  anchorProblemToDiff: () => anchorProblemToDiff,
@@ -22540,6 +23067,7 @@ __export(src_exports2, {
22540
23067
  communityInductionRequests: () => communityInductionRequests,
22541
23068
  communitySeeds: () => communitySeeds,
22542
23069
  compareSemver: () => compareSemver,
23070
+ confirmFixHeld: () => confirmFixHeld,
22543
23071
  corroborateClaim: () => corroborateClaim,
22544
23072
  creditAssignment: () => creditAssignment,
22545
23073
  crystallize: () => crystallize,
@@ -22554,9 +23082,12 @@ __export(src_exports2, {
22554
23082
  findMintTimeDuplicate: () => findMintTimeDuplicate,
22555
23083
  findOrCreatePackage: () => findOrCreatePackage,
22556
23084
  findPath: () => findPath,
23085
+ findResolvedAnchorMatch: () => findResolvedAnchorMatch,
22557
23086
  findReusableSolution: () => findReusableSolution,
23087
+ findWorkspaceFoldCandidate: () => findWorkspaceFoldCandidate,
22558
23088
  flattenCloudCounts: () => flattenCloudCounts,
22559
23089
  foldDuplicateProblem: () => foldDuplicateProblem,
23090
+ foldSameAnchorProblem: () => foldSameAnchorProblem,
22560
23091
  formatMechanismStatus: () => formatMechanismStatus,
22561
23092
  getClaim: () => getClaim,
22562
23093
  harvestAbstractionFences: () => harvestAbstractionFences,
@@ -22576,10 +23107,13 @@ __export(src_exports2, {
22576
23107
  localEdgeViolation: () => localEdgeViolation,
22577
23108
  markDiscriminatorAsked: () => markDiscriminatorAsked,
22578
23109
  markFixCandidates: () => markFixCandidates,
23110
+ markFoldCandidate: () => markFoldCandidate,
23111
+ markResolvedRecurrence: () => markResolvedRecurrence,
22579
23112
  markRevisit: () => markRevisit,
22580
23113
  matchLanguagesInText: () => matchLanguagesInText,
22581
23114
  matchPackagesInText: () => matchPackagesInText,
22582
23115
  matchSymbolsInText: () => matchSymbolsInText,
23116
+ maybeReopenFromRegression: () => maybeReopenFromRegression,
22583
23117
  mergeCloudCounts: () => mergeCloudCounts,
22584
23118
  mergeDuplicateProblems: () => mergeDuplicateProblems,
22585
23119
  migrateProjectAlias: () => migrateProjectAlias,
@@ -22615,6 +23149,7 @@ __export(src_exports2, {
22615
23149
  repairLegacyAnchorsFromStatement: () => repairLegacyAnchorsFromStatement,
22616
23150
  resolveDesignProblemById: () => resolveDesignProblemById,
22617
23151
  resolveDesignProblemByStatement: () => resolveDesignProblemByStatement,
23152
+ resolveFoldCandidates: () => resolveFoldCandidates,
22618
23153
  resolveOsNode: () => resolveOsNode,
22619
23154
  retractDesignProblemById: () => retractDesignProblemById,
22620
23155
  retractDesignProblemByStatement: () => retractDesignProblemByStatement,
@@ -22653,6 +23188,7 @@ var init_src5 = __esm({
22653
23188
  init_community2();
22654
23189
  init_triage2();
22655
23190
  init_problem_dedup();
23191
+ init_fold_candidates();
22656
23192
  init_problem_package_link();
22657
23193
  init_tools();
22658
23194
  init_principle_sync();
@@ -22681,7 +23217,19 @@ function buildSnapshot(opts) {
22681
23217
  anchors: opts.store.outEdges(p.id, ["MANIFESTED_IN", "EVIDENCED_BY"])
22682
23218
  }));
22683
23219
  const recentConstraints = stillOpen.filter((p) => isConstraintProblem(p)).sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 3);
22684
- const recentResolved = problems.filter((p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null).sort((a, b) => Number(b.attrs["resolvedAt"] ?? 0) - Number(a.attrs["resolvedAt"] ?? 0)).slice(0, 4).map((p) => {
23220
+ const regressions = problems.filter((p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0).flatMap(
23221
+ (open2) => opts.store.outEdges(open2.id, ["REGRESSION_OF"]).flatMap((e) => {
23222
+ const closed = opts.store.getNode(e.to);
23223
+ if (!closed || closed.attrs["resolutionSuspect"] !== true || closed.attrs["resolvedAt"] == null) {
23224
+ return [];
23225
+ }
23226
+ const solEdge = opts.store.outEdges(closed.id, ["SOLVED_BY"])[0];
23227
+ const solution = solEdge ? opts.store.getNode(solEdge.to) ?? void 0 : void 0;
23228
+ return [{ open: open2, closed, ...solution ? { solution } : {} }];
23229
+ })
23230
+ ).sort((a, b) => Number(b.closed.attrs["recurredAt"] ?? 0) - Number(a.closed.attrs["recurredAt"] ?? 0)).slice(0, 2);
23231
+ const regressionClosedIds = new Set(regressions.map((r) => r.closed.id));
23232
+ const recentResolved = problems.filter((p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null).filter((p) => !regressionClosedIds.has(p.id)).sort((a, b) => Number(b.attrs["resolvedAt"] ?? 0) - Number(a.attrs["resolvedAt"] ?? 0)).slice(0, 4).map((p) => {
22685
23233
  const solEdge = opts.store.outEdges(p.id, ["SOLVED_BY"])[0];
22686
23234
  const solution = solEdge ? opts.store.getNode(solEdge.to) ?? void 0 : void 0;
22687
23235
  return { node: p, ...solution ? { solution } : {} };
@@ -22751,6 +23299,7 @@ function buildSnapshot(opts) {
22751
23299
  recentProblems: recent2,
22752
23300
  recentResolved,
22753
23301
  recentConstraints,
23302
+ regressions,
22754
23303
  ...causalNudge ? { causalNudge } : {},
22755
23304
  ...opts.selfCiteSkips && opts.selfCiteSkips > 0 ? { selfCiteSkips: opts.selfCiteSkips } : {},
22756
23305
  ...domainNudge ? { domainNudge } : {},
@@ -22872,6 +23421,14 @@ function renderSnapshot(s) {
22872
23421
  );
22873
23422
  lines.push("");
22874
23423
  }
23424
+ if (s.daemonDistress) {
23425
+ const mins = Math.max(1, Math.round((Date.now() - s.daemonDistress.sinceTs) / 6e4));
23426
+ lines.push("### \u{1F6A8} errata daemon distress");
23427
+ lines.push(
23428
+ `${s.daemonDistress.body} (ongoing ~${mins} min). TELL YOUR USER about this now \u2014 the daemon cannot fix everything itself, and this line may be its only witness. \`errata status\` has detail; a daemon restart (\`errata stop\` then \`errata start\`) is safe mid-session.`
23429
+ );
23430
+ lines.push("");
23431
+ }
22875
23432
  if (s.needsRevisit.length > 0) {
22876
23433
  lines.push("### \u26A0\uFE0F Needs revisit \u2014 a fact these rested on changed");
22877
23434
  lines.push(
@@ -22907,7 +23464,7 @@ function renderSnapshot(s) {
22907
23464
  lines.push("");
22908
23465
  }
22909
23466
  lines.push("### Recently observed problems in this workspace");
22910
- if (s.recentProblems.length === 0 && s.recentResolved.length === 0 && s.recentConstraints.length === 0) {
23467
+ if (s.recentProblems.length === 0 && s.recentResolved.length === 0 && s.recentConstraints.length === 0 && s.regressions.length === 0) {
22911
23468
  lines.push("- _none yet \u2014 errata is still building its model_");
22912
23469
  } else {
22913
23470
  if (s.recentProblems.length > 0) {
@@ -22929,6 +23486,22 @@ function renderSnapshot(s) {
22929
23486
  );
22930
23487
  }
22931
23488
  }
23489
+ if (s.regressions.length > 0) {
23490
+ lines.push("_\u{1F501} RECURRED \u2014 a previously-solved problem appears to be back; did the fix hold?_");
23491
+ for (const g of s.regressions) {
23492
+ lines.push(`- **${g.open.description}** \u2014 \`${g.open.id}\`${tagOf(g.open)}`);
23493
+ lines.push(` - previously resolved as: ${g.closed.description.slice(0, 120)} \u2014 \`${g.closed.id}\``);
23494
+ if (g.solution) {
23495
+ lines.push(
23496
+ ` - the fix on record: ${g.solution.description.slice(0, 120)} \u2014 \`${g.solution.id}\` \xB7 fix still holds \u2192 (confirm:[${g.solution.id}]) \xB7 hitting it again \u2192 flag it (a second independent witness reopens the close)`
23497
+ );
23498
+ } else {
23499
+ lines.push(
23500
+ ` - no fix prose on record \xB7 hitting it again \u2192 flag it (a second independent witness reopens the close)`
23501
+ );
23502
+ }
23503
+ }
23504
+ }
22932
23505
  if (s.recentConstraints.length > 0) {
22933
23506
  lines.push("_Design tensions \u2014 constraints this work is shaped around, not defects to fix:_");
22934
23507
  for (const c of s.recentConstraints) {
@@ -22963,12 +23536,31 @@ function renderSnapshot(s) {
22963
23536
  }
22964
23537
  lines.push("");
22965
23538
  if (s.remote && s.remote.length > 0) {
23539
+ const stratumOf = (m) => {
23540
+ const v = m.attrs?.["stratum"];
23541
+ return v === "project" || v === "team" || v === "org" ? v : void 0;
23542
+ };
23543
+ const contributorOf = (m) => {
23544
+ if (stratumOf(m) === void 0) return void 0;
23545
+ const c = m.attrs?.["contributor"];
23546
+ if (!c || typeof c.label !== "string" || c.label.length === 0) return void 0;
23547
+ return { label: c.label, ...typeof c.team === "string" && c.team ? { team: c.team } : {} };
23548
+ };
23549
+ const hasOwnOrg = s.remote.some((m) => stratumOf(m) !== void 0);
22966
23550
  lines.push("### From the Errata Network \u2014 collective priors");
22967
23551
  lines.push(
22968
- "_A small sample of knowledge from OTHER codebases facing your stack/domain (not yet local \u2014 the Network holds far more; these ids seed deeper bursts). Weigh accordingly \u2014 it's corroborated across teams, but not from this repo._"
23552
+ "_A small sample of knowledge facing your stack/domain (not yet local \u2014 the Network holds far more; these ids seed deeper bursts). Unmarked entries are network-generalized: corroborated across teams, but not from this repo \u2014 treat as pattern and re-derive locally._"
22969
23553
  );
23554
+ if (hasOwnOrg) {
23555
+ lines.push(
23556
+ "_Entries marked `[org]`/`[team]`/`[project]` are from YOUR org's membrane \u2014 specifics are real and checkable; someone here already hit this, so consider coordinating before re-deriving. A `\xB7 name` after the stratum is the authoring installation \u2014 who to coordinate with._"
23557
+ );
23558
+ }
22970
23559
  for (const m of s.remote) {
22971
- lines.push(`- **${m.label}:** ${m.description}${tagOf(m)}`);
23560
+ const stratum = stratumOf(m);
23561
+ const contributor = contributorOf(m);
23562
+ const chip = stratum ? `\`[${stratum}${contributor ? ` \xB7 ${contributor.label}${contributor.team ? `/${contributor.team}` : ""}` : ""}]\` ` : "";
23563
+ lines.push(`- ${chip}**${m.label}:** ${m.description}${tagOf(m)}`);
22972
23564
  }
22973
23565
  lines.push("");
22974
23566
  }
@@ -23165,6 +23757,7 @@ function assembleAgentContext(opts) {
23165
23757
  });
23166
23758
  if (opts.edgeElicitation) snapshot.edgeElicitation = opts.edgeElicitation;
23167
23759
  if (opts.pendingUpdate) snapshot.pendingUpdate = opts.pendingUpdate;
23760
+ if (opts.daemonDistress) snapshot.daemonDistress = opts.daemonDistress;
23168
23761
  if (opts.reviewItems?.length) snapshot.reviewItems = [...opts.reviewItems];
23169
23762
  if (opts.statedIntent) snapshot.statedIntent = opts.statedIntent;
23170
23763
  if (opts.remote && opts.remote.length > 0) {
@@ -23750,7 +24343,7 @@ function provenanceHeaders(provenance) {
23750
24343
  ...provenance.agentModel ? { "x-inerrata-agent-model": provenance.agentModel } : {}
23751
24344
  };
23752
24345
  }
23753
- var asWireCount, INGEST_NODE_CHUNK, CloudClient, CloudError;
24346
+ var asWireCount, INGEST_NODE_CHUNK, E6B_PUBLIC_CONTEXT_SENTINEL, CloudClient, CloudError;
23754
24347
  var init_client = __esm({
23755
24348
  "../../packages/cloud-client/src/client.ts"() {
23756
24349
  "use strict";
@@ -23758,6 +24351,7 @@ var init_client = __esm({
23758
24351
  init_src();
23759
24352
  asWireCount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
23760
24353
  INGEST_NODE_CHUNK = 8;
24354
+ E6B_PUBLIC_CONTEXT_SENTINEL = "00000000-0000-4e6b-8000-000000000e6b";
23761
24355
  CloudClient = class {
23762
24356
  baseUrl;
23763
24357
  apiKey;
@@ -24149,7 +24743,19 @@ var init_client = __esm({
24149
24743
  if (q.seed?.length) qs.set("seed", q.seed.join(","));
24150
24744
  if (q.q) qs.set("q", q.q);
24151
24745
  if (q.channel) qs.set("channel", q.channel);
24152
- const res = await this.json("GET", `/v2/search?${qs.toString()}`);
24746
+ const publicCtx = process.env["ERRATA_PRIMING_PUBLIC_CONTEXT"];
24747
+ const usePublicCtx = Boolean(publicCtx && publicCtx !== "0" && q.channel === "priming");
24748
+ const UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24749
+ const res = await this.json(
24750
+ "GET",
24751
+ `/v2/search?${qs.toString()}`,
24752
+ void 0,
24753
+ usePublicCtx ? {
24754
+ extraHeaders: {
24755
+ "X-Inerrata-Project-Id": UUID_RE2.test(publicCtx) ? publicCtx : E6B_PUBLIC_CONTEXT_SENTINEL
24756
+ }
24757
+ } : void 0
24758
+ );
24153
24759
  const { nodes, edges } = this.toCastalia(res);
24154
24760
  return {
24155
24761
  nodes,
@@ -24313,7 +24919,8 @@ var init_client = __esm({
24313
24919
  const headers = {
24314
24920
  "content-type": "application/json",
24315
24921
  accept: "application/json",
24316
- ...provenanceHeaders(this.provenance)
24922
+ ...provenanceHeaders(this.provenance),
24923
+ ...opts?.extraHeaders ?? {}
24317
24924
  };
24318
24925
  if (!opts?.skipAuth) {
24319
24926
  const token = opts?.bearerToken ?? await this.authToken();
@@ -24527,6 +25134,7 @@ function defaultConfig() {
24527
25134
  // user opts into sync at all.
24528
25135
  consent: { sync: false, telemetry: false, contributePackages: true },
24529
25136
  notifications: true,
25137
+ opsNotices: false,
24530
25138
  onboardedAt: null,
24531
25139
  machineId: null,
24532
25140
  updateChannel: "dev",
@@ -24669,7 +25277,7 @@ var init_config = __esm({
24669
25277
  "use strict";
24670
25278
  init_paths();
24671
25279
  LEGACY_CLOUD_URL = "https://inerrata-gateway.onrender.com";
24672
- DEFAULT_CLOUD_URL = true ? "https://dev.inerrata.dev" : DEV_CLOUD_URL;
25280
+ DEFAULT_CLOUD_URL = true ? "https://inerrata.dev" : DEV_CLOUD_URL;
24673
25281
  }
24674
25282
  });
24675
25283
 
@@ -28577,7 +29185,13 @@ function generalizeSymbols(text, lexicon, level = 1, fileLexicon) {
28577
29185
  }
28578
29186
  return generalize(out2, { level }).text;
28579
29187
  }
28580
- var SYMBOL_LABELS2, KIND_PHRASE, BUILTIN_TYPE_NAMES, DOC_COMMENT_KINDS, MAX_DOC_CHARS, RE_RESERVED2, escapeRe4, RE_LEADING_ARTICLE2, CONVENTION_FILENAMES, CONVENTION_BASENAME_MIN_FILES, CONVENTION_STEMS, FILE_TOKEN_RE, STEM_TOKEN_RE, SOURCE_EXTENSIONS, TOKEN_RE3;
29188
+ function registerRenderedContributorLabels(labels) {
29189
+ for (const label of labels) {
29190
+ const trimmed = label.trim();
29191
+ if (trimmed.length >= 3) renderedContributorLabels.add(trimmed);
29192
+ }
29193
+ }
29194
+ var SYMBOL_LABELS2, KIND_PHRASE, BUILTIN_TYPE_NAMES, DOC_COMMENT_KINDS, MAX_DOC_CHARS, RE_RESERVED2, escapeRe4, RE_LEADING_ARTICLE2, CONVENTION_FILENAMES, CONVENTION_BASENAME_MIN_FILES, CONVENTION_STEMS, FILE_TOKEN_RE, STEM_TOKEN_RE, SOURCE_EXTENSIONS, TOKEN_RE3, renderedContributorLabels;
28581
29195
  var init_generalize_graph = __esm({
28582
29196
  "src/generalize-graph.ts"() {
28583
29197
  "use strict";
@@ -28718,6 +29332,7 @@ var init_generalize_graph = __esm({
28718
29332
  "cypher"
28719
29333
  ]);
28720
29334
  TOKEN_RE3 = /[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
29335
+ renderedContributorLabels = /* @__PURE__ */ new Set();
28721
29336
  }
28722
29337
  });
28723
29338
 
@@ -29228,14 +29843,16 @@ function rankOpenProblemNeighbors(store, embedding, limit = 5) {
29228
29843
  }
29229
29844
  function mergeProblemsByEmbedding(store, opts) {
29230
29845
  const minCosine = opts.minCosine ?? NEMORI_DEDUP_COSINE;
29231
- const report = { clusters: 0, merged: 0 };
29846
+ const NEAR_MISS_FLOOR = 0.72;
29847
+ const report = { clusters: 0, merged: 0, nearMissMarked: 0 };
29232
29848
  const open2 = store.findNodesByLabel("Problem").filter(
29233
29849
  (p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.embedding.length > 0
29234
29850
  );
29235
29851
  const corro = (n) => Number(n.attrs["corroborations"] ?? 0);
29236
29852
  const consumed = /* @__PURE__ */ new Set();
29853
+ const seeds = opts.freshSince === void 0 ? open2 : open2.filter((p) => p.createdAt > opts.freshSince);
29237
29854
  store.transaction(() => {
29238
- for (const a of open2) {
29855
+ for (const a of seeds) {
29239
29856
  if (consumed.has(a.id)) continue;
29240
29857
  const cluster = [a];
29241
29858
  for (const b of open2) {
@@ -29243,9 +29860,15 @@ function mergeProblemsByEmbedding(store, opts) {
29243
29860
  if (b.embedding.length !== a.embedding.length) continue;
29244
29861
  if ((a.attrs["embeddingVersion"] ?? "") !== (b.attrs["embeddingVersion"] ?? "")) continue;
29245
29862
  if (isConstraintProblem(a) !== isConstraintProblem(b)) continue;
29246
- if (cosine(a.embedding, b.embedding) >= minCosine) {
29863
+ const cos = cosine(a.embedding, b.embedding);
29864
+ if (cos >= minCosine) {
29247
29865
  cluster.push(b);
29248
29866
  consumed.add(b.id);
29867
+ } else if (cos >= NEAR_MISS_FLOOR) {
29868
+ const [younger, elder] = a.createdAt >= b.createdAt ? [a, b] : [b, a];
29869
+ if (markFoldCandidate(store, younger.id, elder.id, { hashCos: cos, source: "settle-nearmiss", ts: opts.ts })) {
29870
+ report.nearMissMarked = (report.nearMissMarked ?? 0) + 1;
29871
+ }
29249
29872
  }
29250
29873
  }
29251
29874
  if (cluster.length < 2) continue;
@@ -29260,6 +29883,50 @@ function mergeProblemsByEmbedding(store, opts) {
29260
29883
  });
29261
29884
  return report;
29262
29885
  }
29886
+ function bindResolvedRegressions(store, opts) {
29887
+ const minCosine = opts.minCosine ?? NEMORI_DEDUP_COSINE;
29888
+ const watermarkRaw = store.getMeta?.("regression_sweep_at") ?? null;
29889
+ const full = watermarkRaw === null;
29890
+ const since = full ? 0 : Number(watermarkRaw) || 0;
29891
+ const live2 = (n) => n.attrs["mergedInto"] === void 0;
29892
+ const problems = store.findNodesByLabel("Problem").filter((p) => p.embedding.length > 0 && live2(p));
29893
+ const fresh = problems.filter((p) => p.attrs["resolvedAt"] == null && p.createdAt > since);
29894
+ const resolved = problems.filter(
29895
+ (p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null
29896
+ );
29897
+ const report = { scanned: fresh.length, bound: 0, full };
29898
+ if (fresh.length > 0 && resolved.length > 0) {
29899
+ for (const f of fresh) {
29900
+ let best = null;
29901
+ for (const r of resolved) {
29902
+ if (r.embedding.length !== f.embedding.length) continue;
29903
+ if ((r.attrs["embeddingVersion"] ?? "") !== (f.attrs["embeddingVersion"] ?? "")) continue;
29904
+ if (isConstraintProblem(f) !== isConstraintProblem(r)) continue;
29905
+ const s = cosine(f.embedding, r.embedding);
29906
+ if (s >= minCosine && (!best || s > best.s)) best = { node: r, s };
29907
+ }
29908
+ if (!best) continue;
29909
+ if (store.outEdges(f.id, ["REGRESSION_OF"]).some((e) => e.to === best.node.id)) continue;
29910
+ if (!markResolvedRecurrence(store, best.node.id, opts.ts).marked) continue;
29911
+ store.mergeEdge({
29912
+ id: `edge_${digest({ from: f.id, type: "REGRESSION_OF", to: best.node.id })}`.slice(0, 24),
29913
+ from: f.id,
29914
+ to: best.node.id,
29915
+ type: "REGRESSION_OF",
29916
+ confidence: 0.5,
29917
+ extractionSource: "daemon-extracted",
29918
+ createdAt: opts.ts,
29919
+ lastSeenAt: opts.ts,
29920
+ navSuccesses: 0,
29921
+ navFailures: 0,
29922
+ attrs: { provisional: true, machineProposed: true, cosine: Math.round(best.s * 100) / 100 }
29923
+ });
29924
+ report.bound++;
29925
+ }
29926
+ }
29927
+ store.setMeta?.("regression_sweep_at", String(opts.ts));
29928
+ return report;
29929
+ }
29263
29930
  function bindCanonicalNeighbors(store, opts) {
29264
29931
  const k = opts.k ?? 3;
29265
29932
  const live2 = (n) => n.attrs["mergedInto"] === void 0;
@@ -30741,6 +31408,7 @@ __export(src_exports4, {
30741
31408
  annotateResolution: () => annotateResolution,
30742
31409
  attributeRootCause: () => attributeRootCause,
30743
31410
  bindCanonicalNeighbors: () => bindCanonicalNeighbors,
31411
+ bindResolvedRegressions: () => bindResolvedRegressions,
30744
31412
  closeProblem: () => closeProblem,
30745
31413
  codeAnchorProjection: () => codeAnchorProjection,
30746
31414
  commandSignature: () => commandSignature,
@@ -39686,6 +40354,8 @@ var init_reconcile = __esm({
39686
40354
  var mcp_exports = {};
39687
40355
  __export(mcp_exports, {
39688
40356
  INSTRUCTIONS: () => INSTRUCTIONS,
40357
+ RG_REFUTED_DAMP: () => RG_REFUTED_DAMP,
40358
+ RG_SUSPECT_BOOST: () => RG_SUSPECT_BOOST,
39689
40359
  createMcpHandler: () => createMcpHandler,
39690
40360
  runMcpServer: () => runMcpServer,
39691
40361
  runTool: () => runTool,
@@ -39757,6 +40427,22 @@ function takeDiscriminatorNudge(path2) {
39757
40427
  shared.close();
39758
40428
  }
39759
40429
  }
40430
+ function hitState(n) {
40431
+ const reasons = n.attrs["refuteReasons"];
40432
+ if (n.attrs["resolutionSuspect"] === true && n.attrs["resolvedAt"] != null) {
40433
+ return { state: "regression-suspect", factor: RG_SUSPECT_BOOST };
40434
+ }
40435
+ if (n.attrs["refutedAt"] != null) {
40436
+ return { state: "refuted", ...reasons?.[0] ? { refutedWhy: reasons[0] } : {}, factor: RG_REFUTED_DAMP };
40437
+ }
40438
+ if (n.attrs["reopenedFrom"] === "regression") {
40439
+ return { state: "reopened-regression", factor: RG_SUSPECT_BOOST };
40440
+ }
40441
+ if (n.attrs["resolvedAt"] != null && n.attrs["resolvedAs"] == null) {
40442
+ return { state: "resolved", factor: 1 };
40443
+ }
40444
+ return { factor: 1 };
40445
+ }
39760
40446
  function commentAnchorId(store, commentId) {
39761
40447
  const out2 = store.outEdges(commentId, ["EXPLAINS", "ANNOTATES"]);
39762
40448
  if (out2.length > 0) return out2[0].to;
@@ -39821,14 +40507,16 @@ function searchGraph(store, query, limit) {
39821
40507
  }
39822
40508
  add(node2, matched);
39823
40509
  }
39824
- return [...merged.values()].sort(
39825
- (a, b) => rankTier(a.node) - rankTier(b.node) || b.matched - a.matched || b.node.pageRank - a.node.pageRank
39826
- ).slice(0, limit).map(({ node: node2, via }) => ({
40510
+ return [...merged.values()].map((e) => ({ ...e, rg: hitState(e.node) })).sort(
40511
+ (a, b) => rankTier(a.node) - rankTier(b.node) || b.matched - a.matched || b.node.pageRank * b.rg.factor - a.node.pageRank * a.rg.factor
40512
+ ).slice(0, limit).map(({ node: node2, via, rg }) => ({
39827
40513
  id: node2.id,
39828
40514
  label: node2.label,
39829
40515
  description: node2.description,
39830
40516
  pageRank: node2.pageRank,
39831
- ...via ? { via } : {}
40517
+ ...via ? { via } : {},
40518
+ ...rg.state ? { state: rg.state } : {},
40519
+ ...rg.refutedWhy ? { refutedWhy: rg.refutedWhy } : {}
39832
40520
  }));
39833
40521
  }
39834
40522
  function runTool(name2, args2, store, ctx = {}) {
@@ -39970,7 +40658,10 @@ function unresolved(args2) {
39970
40658
  const q = args2["qname"] ?? args2["nodeId"];
39971
40659
  return { found: false, reason: "unresolved", query: typeof q === "string" ? q : "" };
39972
40660
  }
39973
- function createMcpHandler(store, ctx = {}) {
40661
+ function createMcpHandler(store, ctx = {}, options = {}) {
40662
+ const bareToolNames = options.toolNameStyle === "bare";
40663
+ const publicToolName = (canonical) => bareToolNames ? canonical.replace(/^errata\./, "") : canonical;
40664
+ const canonicalToolName = (presented) => bareToolNames && presented && !presented.includes(".") ? `errata.${presented}` : presented;
39974
40665
  let auditReadRun = 0;
39975
40666
  let toolCallIndex = 0;
39976
40667
  let lastAuditNudgeAt = Number.NEGATIVE_INFINITY;
@@ -40000,7 +40691,7 @@ function createMcpHandler(store, ctx = {}) {
40000
40691
  id,
40001
40692
  result: {
40002
40693
  tools: TOOLS.map((t) => ({
40003
- name: t.name,
40694
+ name: publicToolName(t.name),
40004
40695
  description: t.description,
40005
40696
  inputSchema: t.inputSchema
40006
40697
  }))
@@ -40008,7 +40699,8 @@ function createMcpHandler(store, ctx = {}) {
40008
40699
  };
40009
40700
  case "tools/call": {
40010
40701
  const params = req.params ?? {};
40011
- const tool = TOOLS.find((t) => t.name === params.name);
40702
+ const requestedToolName = canonicalToolName(params.name);
40703
+ const tool = TOOLS.find((t) => t.name === requestedToolName);
40012
40704
  if (!tool) {
40013
40705
  return {
40014
40706
  jsonrpc: "2.0",
@@ -40020,8 +40712,8 @@ function createMcpHandler(store, ctx = {}) {
40020
40712
  const content = [
40021
40713
  { type: "text", text: JSON.stringify(out2, null, 2) }
40022
40714
  ];
40023
- const isEnrichmentTool = params.name === "errata.pending_enrichment" || params.name === "errata.annotate_resolution";
40024
- const isTriageTool = params.name === "errata.pending_discriminators" || params.name === "errata.triage";
40715
+ const isEnrichmentTool = requestedToolName === "errata.pending_enrichment" || requestedToolName === "errata.annotate_resolution";
40716
+ const isTriageTool = requestedToolName === "errata.pending_discriminators" || requestedToolName === "errata.triage";
40025
40717
  toolCallIndex++;
40026
40718
  auditReadRun = isEnrichmentTool ? 0 : auditReadRun + 1;
40027
40719
  if (!isEnrichmentTool) {
@@ -40087,13 +40779,13 @@ function buildToolContext(workspaceRoot) {
40087
40779
  }
40088
40780
  return base;
40089
40781
  }
40090
- async function runMcpServer(workspaceRoot) {
40782
+ async function runMcpServer(workspaceRoot, options = {}) {
40091
40783
  const paths = workspacePaths(workspaceRoot);
40092
40784
  const store = openGraphStore({ path: paths.castalia });
40093
40785
  const handle2 = createMcpHandler(store, {
40094
40786
  ...buildToolContext(workspaceRoot),
40095
40787
  sharedDbPath: sharedStorePath()
40096
- });
40788
+ }, options);
40097
40789
  process.stdin.setEncoding("utf8");
40098
40790
  let buffer = "";
40099
40791
  process.stdin.on("data", (chunk) => {
@@ -40121,7 +40813,7 @@ async function runMcpServer(workspaceRoot) {
40121
40813
  if (resp) process.stdout.write(JSON.stringify(resp) + "\n");
40122
40814
  }
40123
40815
  }
40124
- var INSTRUCTIONS, AUDIT_RUN_THRESHOLD, AUDIT_NUDGE_COOLDOWN, DISCRIMINATOR_NUDGE_COOLDOWN, AUDIT_FLAG_NUDGE, TOOLS;
40816
+ var INSTRUCTIONS, AUDIT_RUN_THRESHOLD, AUDIT_NUDGE_COOLDOWN, DISCRIMINATOR_NUDGE_COOLDOWN, AUDIT_FLAG_NUDGE, RG_REFUTED_DAMP, RG_SUSPECT_BOOST, TOOLS;
40125
40817
  var init_mcp = __esm({
40126
40818
  "src/mcp.ts"() {
40127
40819
  "use strict";
@@ -40185,6 +40877,8 @@ var init_mcp = __esm({
40185
40877
  AUDIT_NUDGE_COOLDOWN = 12;
40186
40878
  DISCRIMINATOR_NUDGE_COOLDOWN = 40;
40187
40879
  AUDIT_FLAG_NUDGE = "\u26A1 errata \u2014 reading/auditing? anything you notice that's off, flag it inline as `[!one line]` (`[?\u2026]` = TODO) \u2014 no tool call, we harvest it.";
40880
+ RG_REFUTED_DAMP = 0.75;
40881
+ RG_SUSPECT_BOOST = 1.25;
40188
40882
  TOOLS = [
40189
40883
  {
40190
40884
  name: "errata.switch_context",
@@ -52676,6 +53370,7 @@ function watch(paths, options = {}) {
52676
53370
  var esm_default = { watch, FSWatcher };
52677
53371
 
52678
53372
  // src/engine.ts
53373
+ init_generalize_graph();
52679
53374
  init_src();
52680
53375
  init_src2();
52681
53376
  init_src5();
@@ -52837,6 +53532,91 @@ function openEventLog(opts) {
52837
53532
  return new EventLog(opts);
52838
53533
  }
52839
53534
 
53535
+ // src/llm-provider.ts
53536
+ function chatProvider() {
53537
+ return (process.env["EXTRACTION_PROVIDER"] ?? "azure").toLowerCase() === "anthropic" ? "anthropic" : "azure";
53538
+ }
53539
+ function deploymentFor(model) {
53540
+ const key = `MODEL_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_OPENAI`;
53541
+ return process.env[key] ?? process.env["AZURE_OPENAI_DEPLOYMENT"];
53542
+ }
53543
+ function isChatConfigured(model) {
53544
+ if (chatProvider() === "azure") {
53545
+ return Boolean(
53546
+ process.env["AZURE_OPENAI_API_KEY"] && process.env["AZURE_OPENAI_ENDPOINT"] && deploymentFor(model)
53547
+ );
53548
+ }
53549
+ return Boolean(process.env["ANTHROPIC_API_KEY"]);
53550
+ }
53551
+ async function chat(req) {
53552
+ const provider = chatProvider();
53553
+ try {
53554
+ if (provider === "azure") {
53555
+ const endpoint = (process.env["AZURE_OPENAI_ENDPOINT"] ?? "").replace(/\/+$/, "");
53556
+ const version2 = process.env["AZURE_OPENAI_API_VERSION"] ?? "2024-10-21";
53557
+ const deployment = deploymentFor(req.model);
53558
+ if (!endpoint || !deployment) {
53559
+ console.warn("[errata] llm: azure selected but endpoint/deployment missing \u2014 skipping");
53560
+ return null;
53561
+ }
53562
+ const resp2 = await fetch(
53563
+ `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${version2}`,
53564
+ {
53565
+ method: "POST",
53566
+ headers: {
53567
+ "api-key": process.env["AZURE_OPENAI_API_KEY"] ?? "",
53568
+ "content-type": "application/json"
53569
+ },
53570
+ body: JSON.stringify({
53571
+ messages: [
53572
+ ...req.system ? [{ role: "system", content: req.system }] : [],
53573
+ { role: "user", content: req.user }
53574
+ ],
53575
+ // `max_completion_tokens`: the newer deployments reject `max_tokens`.
53576
+ max_completion_tokens: req.maxTokens
53577
+ })
53578
+ }
53579
+ );
53580
+ if (!resp2.ok) {
53581
+ console.warn(
53582
+ `[errata] llm: azure ${resp2.status} \u2014 ${(await resp2.text().catch(() => "")).slice(0, 200)}`
53583
+ );
53584
+ return null;
53585
+ }
53586
+ const json3 = await resp2.json();
53587
+ return json3.choices?.[0]?.message?.content ?? null;
53588
+ }
53589
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
53590
+ method: "POST",
53591
+ headers: {
53592
+ "x-api-key": process.env["ANTHROPIC_API_KEY"] ?? "",
53593
+ "anthropic-version": "2023-06-01",
53594
+ "content-type": "application/json"
53595
+ },
53596
+ body: JSON.stringify({
53597
+ model: req.model,
53598
+ max_tokens: req.maxTokens,
53599
+ ...req.system ? { system: req.system } : {},
53600
+ messages: [{ role: "user", content: req.user }]
53601
+ })
53602
+ });
53603
+ if (!resp.ok) {
53604
+ console.warn(
53605
+ `[errata] llm: anthropic ${resp.status} \u2014 ${(await resp.text().catch(() => "")).slice(0, 200)}`
53606
+ );
53607
+ return null;
53608
+ }
53609
+ const json2 = await resp.json();
53610
+ return json2.content?.find((c) => c.type === "text")?.text ?? null;
53611
+ } catch (err2) {
53612
+ console.warn(
53613
+ `[errata] llm: ${provider} transport failed \u2014`,
53614
+ err2 instanceof Error ? err2.message : err2
53615
+ );
53616
+ return null;
53617
+ }
53618
+ }
53619
+
52840
53620
  // src/engine.ts
52841
53621
  init_src11();
52842
53622
  init_src6();
@@ -52844,9 +53624,10 @@ init_src10();
52844
53624
  init_review2();
52845
53625
 
52846
53626
  // src/turn.ts
52847
- import { closeSync, existsSync as existsSync13, fstatSync, openSync, readdirSync as readdirSync6, readSync, statSync as statSync4 } from "node:fs";
53627
+ import { closeSync, existsSync as existsSync13, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync4 } from "node:fs";
52848
53628
  import { basename as basename3, dirname as dirname8, join as join16 } from "node:path";
52849
53629
  import { homedir as homedir4 } from "node:os";
53630
+ import { createHash as createHash13 } from "node:crypto";
52850
53631
  function readFrom(path2, fromByte, maxBytes) {
52851
53632
  let fd;
52852
53633
  try {
@@ -52964,6 +53745,8 @@ function parseDesignResolutions(text) {
52964
53745
  }
52965
53746
  return out2;
52966
53747
  }
53748
+ var OPENCODE_PARSER_PINNED_VERSION = "1.18.18";
53749
+ var OPENCODE_TRANSCRIPT_HEADER_TYPE = "errata-opencode-session";
52967
53750
  var FILE_TOOLS = /* @__PURE__ */ new Set(["Read", "Edit", "Write", "MultiEdit", "NotebookEdit"]);
52968
53751
  var WRITE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
52969
53752
  function isUserTurnBoundary(obj) {
@@ -52997,7 +53780,161 @@ function readAssistantTurnsFrom(transcriptPath, fromByte, includeThinking = true
52997
53780
  losses: { parseFailures: parsed.parseFailures, ioFailed, bytesUnreachable: 0 }
52998
53781
  };
52999
53782
  }
53783
+ var CODEX_PARSER_PINNED_VERSION = "0.144.5";
53784
+ function detectHostHarness(raw2) {
53785
+ const lines = raw2.split(/\r?\n/);
53786
+ let checked = 0;
53787
+ for (const line of lines) {
53788
+ if (!line.trim()) continue;
53789
+ if (checked++ >= 5) break;
53790
+ let obj;
53791
+ try {
53792
+ obj = JSON.parse(line);
53793
+ } catch {
53794
+ continue;
53795
+ }
53796
+ if (obj.type === "response_item" || obj.type === "event_msg" || obj.type === "session_meta" || obj.type === "turn_context" || obj.type === "world_state") {
53797
+ return "codex";
53798
+ }
53799
+ if (obj.type === "gemini" || "$set" in obj || "projectHash" in obj && "sessionId" in obj || obj.type === "user" && !("message" in obj) && "content" in obj) {
53800
+ return "gemini";
53801
+ }
53802
+ if (obj.type === OPENCODE_TRANSCRIPT_HEADER_TYPE || obj.type === void 0 && "info" in obj && Array.isArray(obj["parts"]) && typeof obj["info"]?.["role"] === "string") {
53803
+ return "opencode";
53804
+ }
53805
+ if (obj.type === "user" || obj.type === "assistant" || obj.type === "summary") {
53806
+ return "claude_code";
53807
+ }
53808
+ }
53809
+ return "claude_code";
53810
+ }
53000
53811
  function parseAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure = false) {
53812
+ if (!raw2) return { turns: [], parseFailures: 0 };
53813
+ const harness = detectHostHarness(raw2);
53814
+ if (harness === "codex") return parseCodexAssistantTurns(raw2, skipFirstLineParseFailure);
53815
+ if (harness === "gemini") return parseGeminiAssistantTurns(raw2, skipFirstLineParseFailure);
53816
+ if (harness === "opencode") return parseOpencodeAssistantTurns(raw2, skipFirstLineParseFailure);
53817
+ return parseClaudeAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure);
53818
+ }
53819
+ function parseOpencodeAssistantTurns(raw2, skipFirstLineParseFailure = false) {
53820
+ const lines = raw2.split(/\r?\n/);
53821
+ const byId = /* @__PURE__ */ new Map();
53822
+ let order = 0;
53823
+ let parseFailures = 0;
53824
+ for (let i2 = 0; i2 < lines.length; i2++) {
53825
+ const line = lines[i2];
53826
+ if (!line) continue;
53827
+ let obj;
53828
+ try {
53829
+ obj = JSON.parse(line);
53830
+ } catch {
53831
+ if (!(i2 === 0 && skipFirstLineParseFailure)) parseFailures++;
53832
+ continue;
53833
+ }
53834
+ if (!obj || typeof obj !== "object") continue;
53835
+ if (obj["type"] === OPENCODE_TRANSCRIPT_HEADER_TYPE) continue;
53836
+ const info2 = obj["info"];
53837
+ const parts2 = obj["parts"];
53838
+ if (!info2 || typeof info2 !== "object" || !Array.isArray(parts2)) continue;
53839
+ if (info2["role"] !== "assistant") continue;
53840
+ const texts = [];
53841
+ for (const p of parts2) {
53842
+ if (!p || p["type"] !== "text") continue;
53843
+ if (p["synthetic"] === true || p["ignored"] === true) continue;
53844
+ if (typeof p["text"] === "string" && p["text"].length > 0) texts.push(p["text"]);
53845
+ }
53846
+ if (texts.length === 0) continue;
53847
+ const id = typeof info2["id"] === "string" ? info2["id"] : `opencode-${i2}`;
53848
+ const prev = byId.get(id);
53849
+ byId.set(id, { text: texts.join("\n\n"), order: prev ? prev.order : order++ });
53850
+ }
53851
+ const turns = [...byId.entries()].sort((a, b) => a[1].order - b[1].order).map(([uuid3, v]) => ({ uuid: uuid3, text: v.text, hostHarness: "opencode" }));
53852
+ return { turns, parseFailures };
53853
+ }
53854
+ function parseGeminiAssistantTurns(raw2, skipFirstLineParseFailure = false) {
53855
+ const lines = raw2.split(/\r?\n/);
53856
+ const byId = /* @__PURE__ */ new Map();
53857
+ let order = 0;
53858
+ let parseFailures = 0;
53859
+ const textOf = (content) => {
53860
+ if (typeof content === "string") return content;
53861
+ if (Array.isArray(content)) {
53862
+ const parts2 = [];
53863
+ for (const b of content) {
53864
+ if (b && typeof b["text"] === "string") parts2.push(b["text"]);
53865
+ }
53866
+ return parts2.length > 0 ? parts2.join("\n\n") : null;
53867
+ }
53868
+ return null;
53869
+ };
53870
+ const fold = (m, fallbackId) => {
53871
+ if (m["type"] !== "gemini") return;
53872
+ const text = textOf(m["content"]);
53873
+ if (text === null || text.length === 0) return;
53874
+ const id = typeof m["id"] === "string" ? m["id"] : fallbackId;
53875
+ const prev = byId.get(id);
53876
+ byId.set(id, { text, order: prev ? prev.order : order++ });
53877
+ };
53878
+ for (let i2 = 0; i2 < lines.length; i2++) {
53879
+ const line = lines[i2];
53880
+ if (!line) continue;
53881
+ let obj;
53882
+ try {
53883
+ obj = JSON.parse(line);
53884
+ } catch {
53885
+ if (!(i2 === 0 && skipFirstLineParseFailure)) parseFailures++;
53886
+ continue;
53887
+ }
53888
+ if (!obj || typeof obj !== "object") continue;
53889
+ const set2 = obj["$set"];
53890
+ if (set2 && typeof set2 === "object") {
53891
+ const msgs = set2["messages"];
53892
+ if (Array.isArray(msgs)) {
53893
+ for (let j = 0; j < msgs.length; j++) {
53894
+ const m = msgs[j];
53895
+ if (m && typeof m === "object") fold(m, `gemini-${i2}-${j}`);
53896
+ }
53897
+ }
53898
+ continue;
53899
+ }
53900
+ if (typeof obj["type"] === "string") fold(obj, `gemini-${i2}`);
53901
+ }
53902
+ const turns = [...byId.entries()].sort((a, b) => a[1].order - b[1].order).map(([uuid3, v]) => ({ uuid: uuid3, text: v.text, hostHarness: "gemini" }));
53903
+ return { turns, parseFailures };
53904
+ }
53905
+ function parseCodexAssistantTurns(raw2, skipFirstLineParseFailure = false) {
53906
+ const turns = [];
53907
+ const lines = raw2.split(/\r?\n/);
53908
+ let parseFailures = 0;
53909
+ for (let i2 = 0; i2 < lines.length; i2++) {
53910
+ const line = lines[i2];
53911
+ if (!line) continue;
53912
+ let obj;
53913
+ try {
53914
+ obj = JSON.parse(line);
53915
+ } catch {
53916
+ if (!(i2 === 0 && skipFirstLineParseFailure)) parseFailures++;
53917
+ continue;
53918
+ }
53919
+ if (obj.type !== "response_item") continue;
53920
+ const payload = obj.payload;
53921
+ if (!payload || payload["type"] !== "message" || payload["role"] !== "assistant") continue;
53922
+ const content = payload["content"];
53923
+ if (!Array.isArray(content)) continue;
53924
+ const parts2 = [];
53925
+ for (const b of content) {
53926
+ if (b["type"] === "output_text" && typeof b["text"] === "string") parts2.push(b["text"]);
53927
+ }
53928
+ if (parts2.length === 0) continue;
53929
+ turns.push({
53930
+ uuid: typeof payload["id"] === "string" ? payload["id"] : `codex-${i2}`,
53931
+ text: parts2.join("\n\n"),
53932
+ hostHarness: "codex"
53933
+ });
53934
+ }
53935
+ return { turns, parseFailures };
53936
+ }
53937
+ function parseClaudeAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure = false) {
53001
53938
  if (!raw2) return { turns: [], parseFailures: 0 };
53002
53939
  const turns = [];
53003
53940
  const lines = raw2.split(/\r?\n/);
@@ -53051,6 +53988,7 @@ function parseAssistantTurns(raw2, includeThinking, skipFirstLineParseFailure =
53051
53988
  turns.push({
53052
53989
  uuid: String(obj.uuid ?? i2),
53053
53990
  text: parts2.join("\n\n"),
53991
+ hostHarness: "claude_code",
53054
53992
  ...lastFile ? { workingFile: lastFile } : {},
53055
53993
  ...provenance ? { workingFileProvenance: provenance } : {},
53056
53994
  ...editedFile && editedFileTurnSeq === turnSeq ? { editedFile } : {},
@@ -53118,6 +54056,237 @@ function subagentTranscripts(mainTranscriptPath, sessionId) {
53118
54056
  refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
53119
54057
  return refs;
53120
54058
  }
54059
+ function codexSessionRoots(env2 = process.env, home = homedir4()) {
54060
+ const roots = /* @__PURE__ */ new Set();
54061
+ const explicit = env2["ERRATA_CODEX_SESSIONS_DIRS"];
54062
+ if (explicit) {
54063
+ for (const d of explicit.split(":")) if (d.trim()) roots.add(d.trim());
54064
+ }
54065
+ if (env2["CODEX_HOME"]) roots.add(join16(env2["CODEX_HOME"], "sessions"));
54066
+ roots.add(join16(home, ".codex", "sessions"));
54067
+ return [...roots];
54068
+ }
54069
+ function readCodexRolloutCwd(path2) {
54070
+ let fd;
54071
+ try {
54072
+ fd = openSync(path2, "r");
54073
+ const CAP = 1e6;
54074
+ const chunk = 65536;
54075
+ let acc = Buffer.alloc(0);
54076
+ let pos = 0;
54077
+ let nl = -1;
54078
+ while (acc.length < CAP) {
54079
+ const buf = Buffer.allocUnsafe(chunk);
54080
+ const n = readSync(fd, buf, 0, chunk, pos);
54081
+ if (n <= 0) break;
54082
+ acc = acc.length === 0 ? buf.subarray(0, n) : Buffer.concat([acc, buf.subarray(0, n)]);
54083
+ pos += n;
54084
+ nl = acc.indexOf(10);
54085
+ if (nl >= 0) break;
54086
+ }
54087
+ const firstLine = acc.toString("utf8", 0, nl >= 0 ? nl : acc.length);
54088
+ try {
54089
+ const o = JSON.parse(firstLine);
54090
+ const meta3 = o?.type === "session_meta" ? o.payload ?? o : o.payload ?? o;
54091
+ const cwd = meta3?.cwd ?? o?.cwd;
54092
+ if (typeof cwd === "string") return cwd;
54093
+ } catch {
54094
+ }
54095
+ const m = firstLine.match(/"cwd"\s*:\s*"((?:[^"\\]|\\.)*)"/);
54096
+ return m ? JSON.parse(`"${m[1]}"`) : null;
54097
+ } catch {
54098
+ return null;
54099
+ } finally {
54100
+ if (fd !== void 0) try {
54101
+ closeSync(fd);
54102
+ } catch {
54103
+ }
54104
+ }
54105
+ }
54106
+ function codexRolloutsForCwd(cwd, opts = {}) {
54107
+ const sinceMs = opts.sinceMs ?? 0;
54108
+ const limit = opts.limit ?? 25;
54109
+ const out2 = [];
54110
+ for (const root of codexSessionRoots(opts.env, opts.home)) {
54111
+ if (!existsSync13(root)) continue;
54112
+ const stack = [{ dir: root, depth: 0 }];
54113
+ while (stack.length > 0) {
54114
+ const { dir, depth } = stack.pop();
54115
+ let names;
54116
+ try {
54117
+ names = readdirSync6(dir);
54118
+ } catch {
54119
+ continue;
54120
+ }
54121
+ for (const name2 of names) {
54122
+ const full = join16(dir, name2);
54123
+ let st;
54124
+ try {
54125
+ st = statSync4(full);
54126
+ } catch {
54127
+ continue;
54128
+ }
54129
+ if (st.isDirectory()) {
54130
+ if (depth < 3) stack.push({ dir: full, depth: depth + 1 });
54131
+ continue;
54132
+ }
54133
+ if (depth < 3) continue;
54134
+ if (!name2.startsWith("rollout-") || !name2.endsWith(".jsonl")) continue;
54135
+ if (st.mtimeMs < sinceMs) continue;
54136
+ if (readCodexRolloutCwd(full) !== cwd) continue;
54137
+ out2.push({ path: full, sessionId: `codex:${basename3(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
54138
+ }
54139
+ }
54140
+ }
54141
+ out2.sort((a, b) => b.mtimeMs - a.mtimeMs);
54142
+ return out2.slice(0, limit);
54143
+ }
54144
+ function geminiSessionRoots(env2 = process.env, home = homedir4()) {
54145
+ const roots = /* @__PURE__ */ new Set();
54146
+ const explicit = env2["ERRATA_GEMINI_SESSIONS_DIRS"];
54147
+ if (explicit) {
54148
+ for (const d of explicit.split(":")) if (d.trim()) roots.add(d.trim());
54149
+ }
54150
+ if (env2["GEMINI_CLI_HOME"]) roots.add(join16(env2["GEMINI_CLI_HOME"], ".gemini", "tmp"));
54151
+ roots.add(join16(home, ".gemini", "tmp"));
54152
+ return [...roots];
54153
+ }
54154
+ function geminiProjectHash(cwd) {
54155
+ return createHash13("sha256").update(cwd).digest("hex");
54156
+ }
54157
+ function geminiHashesFor(cwd) {
54158
+ const variants = /* @__PURE__ */ new Set([cwd, cwd.replace(/\/+$/, "")]);
54159
+ try {
54160
+ variants.add(realpathSync(cwd));
54161
+ } catch {
54162
+ }
54163
+ return new Set([...variants].map(geminiProjectHash));
54164
+ }
54165
+ function readGeminiTranscriptProjectHash(path2) {
54166
+ let fd;
54167
+ try {
54168
+ fd = openSync(path2, "r");
54169
+ const CAP = 65536;
54170
+ const buf = Buffer.allocUnsafe(CAP);
54171
+ const n = readSync(fd, buf, 0, CAP, 0);
54172
+ const nl = buf.indexOf(10);
54173
+ const firstLine = buf.toString("utf8", 0, nl >= 0 && nl < n ? nl : n);
54174
+ const o = JSON.parse(firstLine);
54175
+ return typeof o?.projectHash === "string" ? o.projectHash : null;
54176
+ } catch {
54177
+ return null;
54178
+ } finally {
54179
+ if (fd !== void 0) try {
54180
+ closeSync(fd);
54181
+ } catch {
54182
+ }
54183
+ }
54184
+ }
54185
+ function geminiTranscriptsForCwd(cwd, opts = {}) {
54186
+ const sinceMs = opts.sinceMs ?? 0;
54187
+ const limit = opts.limit ?? 25;
54188
+ const hashes = geminiHashesFor(cwd);
54189
+ const out2 = [];
54190
+ for (const root of geminiSessionRoots(opts.env, opts.home)) {
54191
+ if (!existsSync13(root)) continue;
54192
+ let slugs;
54193
+ try {
54194
+ slugs = readdirSync6(root);
54195
+ } catch {
54196
+ continue;
54197
+ }
54198
+ for (const slug2 of slugs) {
54199
+ const chats = join16(root, slug2, "chats");
54200
+ let names;
54201
+ try {
54202
+ names = readdirSync6(chats);
54203
+ } catch {
54204
+ continue;
54205
+ }
54206
+ for (const name2 of names) {
54207
+ if (!name2.startsWith("session-") || !name2.endsWith(".jsonl")) continue;
54208
+ const full = join16(chats, name2);
54209
+ let st;
54210
+ try {
54211
+ st = statSync4(full);
54212
+ } catch {
54213
+ continue;
54214
+ }
54215
+ if (!st.isFile() || st.mtimeMs < sinceMs) continue;
54216
+ const h = readGeminiTranscriptProjectHash(full);
54217
+ if (!h || !hashes.has(h)) continue;
54218
+ out2.push({ path: full, sessionId: `gemini:${basename3(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
54219
+ }
54220
+ }
54221
+ }
54222
+ out2.sort((a, b) => b.mtimeMs - a.mtimeMs);
54223
+ return out2.slice(0, limit);
54224
+ }
54225
+ function opencodeSessionRoots(env2 = process.env, home = homedir4()) {
54226
+ const roots = /* @__PURE__ */ new Set();
54227
+ const explicit = env2["ERRATA_OPENCODE_SESSIONS_DIRS"];
54228
+ if (explicit) {
54229
+ for (const d of explicit.split(":")) if (d.trim()) roots.add(d.trim());
54230
+ }
54231
+ if (env2["XDG_DATA_HOME"]) roots.add(join16(env2["XDG_DATA_HOME"], "opencode", "errata"));
54232
+ roots.add(join16(home, ".local", "share", "opencode", "errata"));
54233
+ return [...roots];
54234
+ }
54235
+ function readOpencodeTranscriptDirectory(path2) {
54236
+ let fd;
54237
+ try {
54238
+ fd = openSync(path2, "r");
54239
+ const buf = Buffer.allocUnsafe(8192);
54240
+ const n = readSync(fd, buf, 0, buf.length, 0);
54241
+ const nl = buf.indexOf(10);
54242
+ const first = buf.toString("utf8", 0, nl >= 0 && nl < n ? nl : n);
54243
+ const o = JSON.parse(first);
54244
+ if (o?.type !== OPENCODE_TRANSCRIPT_HEADER_TYPE) return null;
54245
+ return typeof o.directory === "string" ? o.directory : null;
54246
+ } catch {
54247
+ return null;
54248
+ } finally {
54249
+ if (fd !== void 0) try {
54250
+ closeSync(fd);
54251
+ } catch {
54252
+ }
54253
+ }
54254
+ }
54255
+ function opencodeTranscriptsForCwd(cwd, opts = {}) {
54256
+ const sinceMs = opts.sinceMs ?? 0;
54257
+ const limit = opts.limit ?? 25;
54258
+ const want = /* @__PURE__ */ new Set([cwd, cwd.replace(/\/+$/, "")]);
54259
+ try {
54260
+ want.add(realpathSync(cwd));
54261
+ } catch {
54262
+ }
54263
+ const out2 = [];
54264
+ for (const root of opencodeSessionRoots(opts.env, opts.home)) {
54265
+ if (!existsSync13(root)) continue;
54266
+ let names;
54267
+ try {
54268
+ names = readdirSync6(root);
54269
+ } catch {
54270
+ continue;
54271
+ }
54272
+ for (const name2 of names) {
54273
+ if (!name2.endsWith(".jsonl")) continue;
54274
+ const full = join16(root, name2);
54275
+ let st;
54276
+ try {
54277
+ st = statSync4(full);
54278
+ } catch {
54279
+ continue;
54280
+ }
54281
+ if (!st.isFile() || st.mtimeMs < sinceMs) continue;
54282
+ const dir = readOpencodeTranscriptDirectory(full);
54283
+ if (!dir || !(want.has(dir) || want.has(dir.replace(/\/+$/, "")))) continue;
54284
+ out2.push({ path: full, sessionId: `opencode:${basename3(name2, ".jsonl")}`, mtimeMs: st.mtimeMs });
54285
+ }
54286
+ }
54287
+ out2.sort((a, b) => b.mtimeMs - a.mtimeMs);
54288
+ return out2.slice(0, limit);
54289
+ }
53121
54290
 
53122
54291
  // src/prior-tags.ts
53123
54292
  init_src();
@@ -53596,6 +54765,44 @@ function typePriorEdge(sourceLabel, targetLabel2, sentence = "") {
53596
54765
  }
53597
54766
  return "RELATES_TO";
53598
54767
  }
54768
+ function refuteReason(statement) {
54769
+ const s = (statement ?? "").trim();
54770
+ return s.length > 0 ? s.slice(0, 240) : void 0;
54771
+ }
54772
+ function wireWitnessItems(items) {
54773
+ return items.map(({ nodeId, witnessKey }) => ({ nodeId, witnessKey }));
54774
+ }
54775
+ var REFUTE_REASONS_CAP = 5;
54776
+ var REFUTE_WITNESS_KEYS_CAP = 16;
54777
+ function applyLocalRefutations(store, refutes, ts) {
54778
+ const out2 = { stamped: 0, duplicate: 0, unknownNode: 0, stampedIds: [] };
54779
+ for (const r of refutes) {
54780
+ const node2 = store.getNode(r.nodeId);
54781
+ if (!node2) {
54782
+ out2.unknownNode++;
54783
+ continue;
54784
+ }
54785
+ const keys = node2.attrs["refuteWitnessKeys"] ?? [];
54786
+ if (keys.includes(r.witnessKey)) {
54787
+ out2.duplicate++;
54788
+ continue;
54789
+ }
54790
+ const reasons = node2.attrs["refuteReasons"] ?? [];
54791
+ store.updateNode(r.nodeId, {
54792
+ attrs: {
54793
+ ...node2.attrs,
54794
+ refutedAt: ts,
54795
+ refutations: (Number(node2.attrs["refutations"]) || 0) + 1,
54796
+ ...r.reason ? { refuteReasons: [r.reason, ...reasons].slice(0, REFUTE_REASONS_CAP) } : {},
54797
+ refuteWitnessKeys: [...keys, r.witnessKey].slice(-REFUTE_WITNESS_KEYS_CAP)
54798
+ },
54799
+ lastUpdatedAt: ts
54800
+ });
54801
+ out2.stamped++;
54802
+ out2.stampedIds.push(r.nodeId);
54803
+ }
54804
+ return out2;
54805
+ }
53599
54806
  var PRIOR_TAG_INSTRUCTION = buildAgentInstruction();
53600
54807
  function isEdgeElicitationEnabled() {
53601
54808
  return (process.env["EDGE_ELICITATION_ENABLED"] ?? "true").toLowerCase() !== "false";
@@ -53634,7 +54841,52 @@ function readPrimingHandles(path2) {
53634
54841
  function resolveHandle(store, handle2, handleMap) {
53635
54842
  return handleMap[handle2]?.id ?? (store.getNode(handle2) ? handle2 : void 0);
53636
54843
  }
53637
- function mintPriorEdge(store, source, target, sentence, touched, ts) {
54844
+ var CITE_ANCHOR_LABELS = /* @__PURE__ */ new Set(["Problem", "Solution", "RootCause", "Pattern", "Claim", "Technique", "AntiPattern"]);
54845
+ var CITE_ANCHOR_PROMOTE_SESSIONS = 2;
54846
+ var CITE_ANCHOR_MAX_FILES = 4;
54847
+ var CITE_ANCHOR_MAX_SESSIONS = 6;
54848
+ function accrueCiteAnchor(store, targetId, touched, sessionId, ts) {
54849
+ let fileId;
54850
+ for (const tid of touched) {
54851
+ if (store.getNode(tid)?.label === "File") {
54852
+ fileId = tid;
54853
+ break;
54854
+ }
54855
+ }
54856
+ if (!fileId) return { accrued: false, promoted: false };
54857
+ const node2 = store.getNode(targetId);
54858
+ if (!node2) return { accrued: false, promoted: false };
54859
+ const hints = { ...node2.attrs["citeAnchorHints"] ?? {} };
54860
+ const entry = hints[fileId] ?? [];
54861
+ if (entry.includes(sessionId)) return { accrued: false, promoted: false };
54862
+ if (hints[fileId] === void 0 && Object.keys(hints).length >= CITE_ANCHOR_MAX_FILES) {
54863
+ return { accrued: false, promoted: false };
54864
+ }
54865
+ const grown = [...entry, sessionId].slice(-CITE_ANCHOR_MAX_SESSIONS);
54866
+ let promoted = false;
54867
+ if (grown.length >= CITE_ANCHOR_PROMOTE_SESSIONS) {
54868
+ store.mergeEdge({
54869
+ id: `edge_${digest({ from: targetId, type: "ANCHORED_AT", to: fileId })}`.slice(0, 24),
54870
+ from: targetId,
54871
+ to: fileId,
54872
+ type: "ANCHORED_AT",
54873
+ confidence: 0.3,
54874
+ extractionSource: "agent-observed",
54875
+ createdAt: ts,
54876
+ lastSeenAt: ts,
54877
+ navSuccesses: 0,
54878
+ navFailures: 0,
54879
+ attrs: { provisional: true, captureTime: true, anchorProvenance: "cite-confirmed", citeSessions: grown }
54880
+ });
54881
+ delete hints[fileId];
54882
+ promoted = true;
54883
+ } else {
54884
+ hints[fileId] = grown;
54885
+ }
54886
+ store.updateNode(targetId, { attrs: { ...node2.attrs, citeAnchorHints: hints }, lastUpdatedAt: ts });
54887
+ return { accrued: true, promoted };
54888
+ }
54889
+ function mintPriorEdge(store, source, target, sentence, touched, ts, sessionId) {
53638
54890
  if (target.id === source.id) return false;
53639
54891
  const type = typePriorEdge(source.label, target.label, sentence);
53640
54892
  const anchorIds = new Set(
@@ -53678,13 +54930,24 @@ function mintPriorEdge(store, source, target, sentence, touched, ts) {
53678
54930
  } catch {
53679
54931
  }
53680
54932
  }
53681
- return { corroborated };
54933
+ let citeAnchorAccrued = false;
54934
+ let citeAnchorPromoted = false;
54935
+ if (!corroborated && sessionId && CITE_ANCHOR_LABELS.has(target.label)) {
54936
+ try {
54937
+ const acc = accrueCiteAnchor(store, target.id, touched, sessionId, ts);
54938
+ citeAnchorAccrued = acc.accrued;
54939
+ citeAnchorPromoted = acc.promoted;
54940
+ } catch (err2) {
54941
+ console.warn("[errata] cite-anchor accrual failed (citation kept):", err2 instanceof Error ? err2.message : err2);
54942
+ }
54943
+ }
54944
+ return { corroborated, citeAnchorAccrued, citeAnchorPromoted };
53682
54945
  }
53683
54946
  function harvestInlineTags(store, text, opts) {
53684
54947
  const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
53685
54948
  const mintPriors = opts.mintPriors ?? true;
53686
54949
  const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
53687
- const plan = { priorEdges: 0, corroboratedEdges: 0, exposureShown: 0, exposureEvicted: 0, exposureUnshown: 0, problems: [], fixes: [], triages: [], causalLinks: [], attempts: [], unresolvedFixes: [], unresolvedHandles: [], priorEdgesSuppressed: { flagOff: 0, noSource: 0 }, dispositions: emptyTagDispositions(), transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
54950
+ const plan = { priorEdges: 0, corroboratedEdges: 0, citeAnchorsAccrued: 0, citeAnchorsPromoted: 0, exposureShown: 0, exposureEvicted: 0, exposureUnshown: 0, problems: [], fixes: [], triages: [], causalLinks: [], attempts: [], unresolvedFixes: [], unresolvedHandles: [], priorEdgesSuppressed: { flagOff: 0, noSource: 0 }, dispositions: emptyTagDispositions(), transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
53688
54951
  const parsed = parseInlineTagsWithDispositions(text);
53689
54952
  const tags = parsed.tags;
53690
54953
  plan.dispositions = parsed.dispositions;
@@ -53856,7 +55119,8 @@ function harvestInlineTags(store, text, opts) {
53856
55119
  if (nodeId) {
53857
55120
  const witnessKey = `refute:${nodeId}:${digest({ s: tag.statement ?? "" })}`.slice(0, 72);
53858
55121
  if (!plan.refutes.some((r) => r.witnessKey === witnessKey)) {
53859
- plan.refutes.push({ nodeId, witnessKey });
55122
+ const reason = refuteReason(tag.statement);
55123
+ plan.refutes.push({ nodeId, witnessKey, ...reason ? { reason } : {} });
53860
55124
  }
53861
55125
  }
53862
55126
  }
@@ -53891,7 +55155,8 @@ function harvestInlineTags(store, text, opts) {
53891
55155
  } else {
53892
55156
  const witnessKey = `refute:${targetId}:${digest({ s: tag.statement ?? "" })}`.slice(0, 72);
53893
55157
  if (!plan.refutes.some((r) => r.witnessKey === witnessKey)) {
53894
- plan.refutes.push({ nodeId: targetId, witnessKey });
55158
+ const reason = refuteReason(tag.statement);
55159
+ plan.refutes.push({ nodeId: targetId, witnessKey, ...reason ? { reason } : {} });
53895
55160
  }
53896
55161
  }
53897
55162
  }
@@ -53910,10 +55175,12 @@ function harvestInlineTags(store, text, opts) {
53910
55175
  }
53911
55176
  }
53912
55177
  if (mintPriors && source && target) {
53913
- const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts);
55178
+ const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts, opts.sessionId);
53914
55179
  if (m) {
53915
55180
  plan.priorEdges++;
53916
55181
  if (m.corroborated) plan.corroboratedEdges++;
55182
+ if (m.citeAnchorAccrued) plan.citeAnchorsAccrued++;
55183
+ if (m.citeAnchorPromoted) plan.citeAnchorsPromoted++;
53917
55184
  }
53918
55185
  } else if (target) {
53919
55186
  if (!mintPriors) plan.priorEdgesSuppressed.flagOff++;
@@ -53923,10 +55190,12 @@ function harvestInlineTags(store, text, opts) {
53923
55190
  const targetId = resolveCited(tag.kind, tag.handle);
53924
55191
  const target = targetId ? store.getNode(targetId) : null;
53925
55192
  if (target) {
53926
- const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts);
55193
+ const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts, opts.sessionId);
53927
55194
  if (m) {
53928
55195
  plan.priorEdges++;
53929
55196
  if (m.corroborated) plan.corroboratedEdges++;
55197
+ if (m.citeAnchorAccrued) plan.citeAnchorsAccrued++;
55198
+ if (m.citeAnchorPromoted) plan.citeAnchorsPromoted++;
53930
55199
  }
53931
55200
  }
53932
55201
  } else if (tag.handle) {
@@ -55751,7 +57020,7 @@ init_paths();
55751
57020
  init_src2();
55752
57021
  init_paths();
55753
57022
  import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "node:fs";
55754
- import { createHash as createHash13 } from "node:crypto";
57023
+ import { createHash as createHash14 } from "node:crypto";
55755
57024
  import { join as join25 } from "node:path";
55756
57025
 
55757
57026
  // src/git-remote.ts
@@ -55808,11 +57077,11 @@ function detectRepoLocator(root, remote) {
55808
57077
 
55809
57078
  // src/profile.ts
55810
57079
  function workspaceId(root) {
55811
- return "wp_" + createHash13("sha256").update(root).digest("hex").slice(0, 12);
57080
+ return "wp_" + createHash14("sha256").update(root).digest("hex").slice(0, 12);
55812
57081
  }
55813
57082
  function sessionOriginKey(sessionId) {
55814
57083
  if (!sessionId) return void 0;
55815
- return "ws_" + createHash13("sha256").update(sessionId).digest("hex").slice(0, 12);
57084
+ return "ws_" + createHash14("sha256").update(sessionId).digest("hex").slice(0, 12);
55816
57085
  }
55817
57086
  function refreshRepoLocator(root, profile) {
55818
57087
  const detected = detectRepoLocator(root, profile.repoRemote);
@@ -56153,7 +57422,8 @@ var TITLES = {
56153
57422
  "hotspot-problem": "Errata \xB7 high-impact problem",
56154
57423
  "index-started": "Errata \xB7 indexing\u2026",
56155
57424
  "index-completed": "Errata \xB7 index ready \u2713",
56156
- "mechanism-stalled": "Errata \xB7 a mechanism is not working"
57425
+ "mechanism-stalled": "Errata \xB7 a mechanism is not working",
57426
+ "daemon-distress": "Errata \xB7 daemon health \u26A0"
56157
57427
  };
56158
57428
  var lastFired = /* @__PURE__ */ new Map();
56159
57429
  var THROTTLE_MS = 3e4;
@@ -56169,6 +57439,14 @@ function notifyEvent(kind, body2, opts = {}) {
56169
57439
  } catch {
56170
57440
  }
56171
57441
  }
57442
+ function notifyOpsEvent(kind, body2, opts = {}) {
57443
+ try {
57444
+ if (!loadConfig().opsNotices) return;
57445
+ } catch {
57446
+ return;
57447
+ }
57448
+ notifyEvent(kind, body2, opts);
57449
+ }
56172
57450
  function notifyTick(delta) {
56173
57451
  const { problemsResolved, reviewsTriggered } = delta;
56174
57452
  if (problemsResolved > 0)
@@ -56258,11 +57536,30 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
56258
57536
  }
56259
57537
 
56260
57538
  // src/watch-census.ts
56261
- var census = { dispatched: 0, emitted: 0, logged: 0, rejected: 0, since: Date.now() };
57539
+ var census = {
57540
+ dispatched: 0,
57541
+ emitted: 0,
57542
+ logged: 0,
57543
+ rejected: 0,
57544
+ since: Date.now(),
57545
+ breakerRecycles: 0,
57546
+ breakerQuarantined: [],
57547
+ topOffender: null
57548
+ };
57549
+ var QUARANTINE_LIST_CAP = 8;
56262
57550
  var noteWatchDispatched = () => void census.dispatched++;
56263
57551
  var noteWatchEmitted = () => void census.emitted++;
56264
57552
  var noteWatchLogged = () => void census.logged++;
56265
57553
  var noteWatchRejected = () => void census.rejected++;
57554
+ var noteBreakerRecycle = (_path) => void census.breakerRecycles++;
57555
+ var noteBreakerQuarantine = (path2) => {
57556
+ if (!census.breakerQuarantined.includes(path2) && census.breakerQuarantined.length < QUARANTINE_LIST_CAP) {
57557
+ census.breakerQuarantined.push(path2);
57558
+ }
57559
+ };
57560
+ var noteWindowOffender = (path2, count, windowMs) => {
57561
+ census.topOffender = { path: path2, count, windowMs, at: Date.now() };
57562
+ };
56266
57563
  function watchCensus() {
56267
57564
  return { ...census };
56268
57565
  }
@@ -56270,9 +57567,14 @@ function watchCensusLine(c = watchCensus()) {
56270
57567
  if (c.dispatched === 0 && c.emitted === 0) return null;
56271
57568
  const mins = Math.max(1, Math.round((Date.now() - c.since) / 6e4));
56272
57569
  const perMin = Math.round(c.dispatched / mins);
56273
- const base = `${c.dispatched} OS event(s) \u2192 ${c.logged} logged (${perMin}/min, ${c.rejected} rejected here)`;
56274
- if (c.logged === 0 && c.dispatched > 500) {
56275
- return `${base} \u26A0 watcher is doing work with NO output \u2014 a watch target likely contains an ignored tree`;
57570
+ let base = `${c.dispatched} OS event(s) \u2192 ${c.logged} logged (${perMin}/min, ${c.rejected} rejected here)`;
57571
+ if (c.breakerRecycles > 0 || c.breakerQuarantined.length > 0) {
57572
+ const q = c.breakerQuarantined.length > 0 ? `, ${c.breakerQuarantined.length} quarantined` : "";
57573
+ base += ` \xB7 breaker: ${c.breakerRecycles} recycled${q}`;
57574
+ }
57575
+ if (c.dispatched > 500 && c.dispatched / Math.max(c.logged, 1) > 1e4) {
57576
+ const top = c.topOffender ? ` \u2014 top: ${c.topOffender.path} (${c.topOffender.count}/${Math.round(c.topOffender.windowMs / 1e3)}s)` : "";
57577
+ return `${base} \u26A0 watcher is doing work with NO output \u2014 a watch target likely contains an ignored tree${top}`;
56276
57578
  }
56277
57579
  return base;
56278
57580
  }
@@ -56314,6 +57616,11 @@ function createLivenessWatch(deps) {
56314
57616
  watchEmitted: watch2.emitted,
56315
57617
  watchLogged: watch2.logged,
56316
57618
  watchRejected: watch2.rejected,
57619
+ // HZ-watch-breaker: a recycle count trending upward across hours is a
57620
+ // handle that keeps wedging — the restart-survivable trace the 8-16
57621
+ // phantom-rename storm never left.
57622
+ watchBreakerRecycles: watch2.breakerRecycles,
57623
+ watchBreakerQuarantined: watch2.breakerQuarantined.length,
56317
57624
  // Concurrency and the longest single holder — the two readings that
56318
57625
  // named the 6017s zombie pass. A pass legitimately running for hours
56319
57626
  // and one wedged forever look identical without a trend.
@@ -56338,14 +57645,115 @@ function createLivenessWatch(deps) {
56338
57645
  };
56339
57646
  }
56340
57647
 
57648
+ // src/watch-breaker.ts
57649
+ function numEnv3(key, fallback) {
57650
+ const n = Number(process.env[key]);
57651
+ return Number.isFinite(n) && n > 0 ? n : fallback;
57652
+ }
57653
+ var BREAKER_WINDOW_MS = numEnv3("ERRATA_WATCH_BREAKER_WINDOW_MS", 1e4);
57654
+ var BREAKER_TRIP_SELF_EVENTS = numEnv3("ERRATA_WATCH_BREAKER_TRIP", 1e3);
57655
+ var BREAKER_MAX_RECYCLES = numEnv3("ERRATA_WATCH_BREAKER_RECYCLES", 2);
57656
+ var BREAKER_REWEDGE_TTL_MS = numEnv3("ERRATA_WATCH_BREAKER_TTL_MS", 6e5);
57657
+ var BREAKER_READD_DELAY_MS = numEnv3("ERRATA_WATCH_BREAKER_READD_MS", 250);
57658
+ var OFFENDER_FLOOR = 100;
57659
+ function normalizeWatchPath(p) {
57660
+ let s = p.startsWith("\\\\?\\") ? p.slice(4) : p;
57661
+ s = s.replace(/\\/g, "/");
57662
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
57663
+ return process.platform === "win32" ? s.toLowerCase() : s;
57664
+ }
57665
+ function createWatchBreaker(deps) {
57666
+ const now = deps.now ?? Date.now;
57667
+ const say = deps.onAction ?? (() => {
57668
+ });
57669
+ const windows2 = /* @__PURE__ */ new Map();
57670
+ let windowStart = now();
57671
+ const recycleHistory = /* @__PURE__ */ new Map();
57672
+ const quarantined = /* @__PURE__ */ new Set();
57673
+ const pendingReadds = /* @__PURE__ */ new Set();
57674
+ const rotate = (t) => {
57675
+ let top = null;
57676
+ for (const [path2, w] of windows2) {
57677
+ if (w.dispatched >= OFFENDER_FLOOR && (!top || w.dispatched > top.count)) {
57678
+ top = { path: path2, count: w.dispatched };
57679
+ }
57680
+ }
57681
+ if (top) noteWindowOffender(top.path, top.count, BREAKER_WINDOW_MS);
57682
+ windows2.clear();
57683
+ windowStart = t;
57684
+ };
57685
+ const trip = (path2, t) => {
57686
+ const history = (recycleHistory.get(path2) ?? []).filter(
57687
+ (ts) => t - ts < BREAKER_REWEDGE_TTL_MS
57688
+ );
57689
+ if (history.length >= BREAKER_MAX_RECYCLES) {
57690
+ quarantined.add(path2);
57691
+ recycleHistory.delete(path2);
57692
+ try {
57693
+ deps.unwatch(path2);
57694
+ } catch {
57695
+ }
57696
+ noteBreakerQuarantine(path2);
57697
+ say(
57698
+ `QUARANTINED ${path2} \u2014 re-wedged after ${history.length} recycle(s); unwatched for this session (reconcile covers its edits)`
57699
+ );
57700
+ return;
57701
+ }
57702
+ history.push(t);
57703
+ recycleHistory.set(path2, history);
57704
+ try {
57705
+ deps.unwatch(path2);
57706
+ } catch {
57707
+ }
57708
+ const timer = setTimeout(() => {
57709
+ pendingReadds.delete(timer);
57710
+ try {
57711
+ deps.readd(path2);
57712
+ } catch {
57713
+ }
57714
+ }, BREAKER_READD_DELAY_MS);
57715
+ timer.unref?.();
57716
+ pendingReadds.add(timer);
57717
+ noteBreakerRecycle(path2);
57718
+ say(
57719
+ `recycled ${path2} \u2014 ${BREAKER_TRIP_SELF_EVENTS}+ phantom self-events in ${Math.round(BREAKER_WINDOW_MS / 1e3)}s; handle closed + re-created`
57720
+ );
57721
+ };
57722
+ return {
57723
+ onRaw(event, evPath, watchedPath) {
57724
+ if (typeof watchedPath !== "string" || watchedPath.length === 0) return;
57725
+ const t = now();
57726
+ if (t - windowStart >= BREAKER_WINDOW_MS) rotate(t);
57727
+ let w = windows2.get(watchedPath);
57728
+ if (!w) {
57729
+ w = { dispatched: 0, self: 0, tripped: false };
57730
+ windows2.set(watchedPath, w);
57731
+ }
57732
+ w.dispatched++;
57733
+ const self = evPath == null || evPath === "" || typeof evPath === "string" && normalizeWatchPath(evPath) === normalizeWatchPath(watchedPath);
57734
+ if (!self) return;
57735
+ w.self++;
57736
+ if (!w.tripped && !quarantined.has(watchedPath) && w.self >= BREAKER_TRIP_SELF_EVENTS) {
57737
+ w.tripped = true;
57738
+ trip(watchedPath, t);
57739
+ }
57740
+ },
57741
+ stop() {
57742
+ for (const timer of pendingReadds) clearTimeout(timer);
57743
+ pendingReadds.clear();
57744
+ }
57745
+ };
57746
+ }
57747
+
56341
57748
  // src/engine.ts
56342
- var DAEMON_VERSION = true ? "2.0.2-dev.982" : "2.0.0-alpha.0";
57749
+ var DAEMON_VERSION = true ? "2.0.2" : "2.0.0-alpha.0";
56343
57750
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
56344
57751
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
56345
57752
  var GIT_OP_MUTE_MS = 4e3;
56346
57753
  var REINDEX_DEBOUNCE_MS = 200;
56347
57754
  var IDENTITY_AUDIT_MAX_BYTES = 16 * 1024 * 1024;
56348
57755
  var TURN_REPLAY_LOOKBACK_MS = 7 * 24 * 60 * 6e4;
57756
+ var CODEX_SWEEP_LOOKBACK_MS = 10 * 6e4;
56349
57757
  function appendIdentityAudit(path2, record2, line) {
56350
57758
  if (!record2.accepted && record2.score <= 0) return;
56351
57759
  try {
@@ -56470,6 +57878,7 @@ function createWorkspaceEngine(opts) {
56470
57878
  const cloud = opts.cloud;
56471
57879
  const telemetry = new TelemetryRecorder(profile.id, DAEMON_VERSION);
56472
57880
  let watcher = null;
57881
+ let watchBreaker = null;
56473
57882
  let lastActivityTs = Date.now();
56474
57883
  let stopGit = null;
56475
57884
  let flushTimer = null;
@@ -56482,7 +57891,23 @@ function createWorkspaceEngine(opts) {
56482
57891
  ignoreInitial: true,
56483
57892
  persistent: true
56484
57893
  });
56485
- watcher.on("raw", noteWatchDispatched);
57894
+ watchBreaker = createWatchBreaker({
57895
+ unwatch: (p) => void watcher?.unwatch(p),
57896
+ readd: (p) => void watcher?.add(p),
57897
+ // Toast AND log: the autostarted daemon's stdout is a discarded stream
57898
+ // (the 8-01 unobservability finding), so console alone would make every
57899
+ // production breaker action invisible — the exact gauge-with-no-alarm
57900
+ // shape this mechanism exists to end.
57901
+ onAction: (line) => {
57902
+ console.warn(`[errata] watch-breaker: ${line}`);
57903
+ notifyOpsEvent("daemon-distress", `watch-breaker: ${line}`, { key: `breaker:${profile.id}` });
57904
+ }
57905
+ });
57906
+ watcher.on("raw", (event, evPath, opts2) => {
57907
+ noteWatchDispatched();
57908
+ const watchedPath = opts2 && typeof opts2 === "object" && "watchedPath" in opts2 ? opts2.watchedPath : void 0;
57909
+ watchBreaker?.onRaw(event, evPath, watchedPath);
57910
+ });
56486
57911
  const onFs = (kind) => (path2) => {
56487
57912
  noteWatchEmitted();
56488
57913
  if (IGNORED_PATH.test(path2) || IGNORED_NOISE.test(path2)) {
@@ -56777,6 +58202,7 @@ function createWorkspaceEngine(opts) {
56777
58202
  }) : null;
56778
58203
  const doneRender = prebuilt ? null : markPass(`context-render-inline:${profile.name}`);
56779
58204
  const pendingUpdate = opts.pendingUpdate?.() ?? null;
58205
+ const daemonDistress = opts.daemonDistress?.() ?? null;
56780
58206
  const reviewItems = pickReviewItems();
56781
58207
  const liveIntent = intents.mostRecent(Date.now());
56782
58208
  maybeRefreshIntentPriors(liveIntent ?? null);
@@ -56794,6 +58220,7 @@ function createWorkspaceEngine(opts) {
56794
58220
  ...elicit ? { edgeElicitation: { instruction: PRIOR_TAG_INSTRUCTION } } : {},
56795
58221
  ...prebuilt ? { snapshot: prebuilt } : {},
56796
58222
  ...pendingUpdate ? { pendingUpdate } : {},
58223
+ ...daemonDistress ? { daemonDistress } : {},
56797
58224
  ...reviewItems.length ? { reviewItems } : {},
56798
58225
  ...liveIntent ? {
56799
58226
  statedIntent: {
@@ -56845,6 +58272,7 @@ function createWorkspaceEngine(opts) {
56845
58272
  contextDirty = false;
56846
58273
  notifyDigest("recall");
56847
58274
  };
58275
+ const normStackTokens = (xs) => (xs ?? []).map((t) => t.trim().toLowerCase().replace(/(?!^)[@\s].*$/, "")).filter(Boolean);
56848
58276
  const maybeRefreshRemotePriors = () => {
56849
58277
  if (opts.skipContextWriter || remoteInFlight) return;
56850
58278
  const now = Date.now();
@@ -56855,10 +58283,13 @@ function createWorkspaceEngine(opts) {
56855
58283
  remoteInFlight = true;
56856
58284
  lastRemoteAt = now;
56857
58285
  try {
56858
- const norm = (xs) => (xs ?? []).map((t) => t.trim().toLowerCase().replace(/(?!^)[@\s].*$/, "")).filter(Boolean);
58286
+ const norm = normStackTokens;
56859
58287
  const seed = store.findNodesByLabel("Problem").sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 6).map((p) => p.id);
56860
58288
  const q = {
56861
- stack: norm(profile.stack),
58289
+ // Languages ride the stack list: the cloud's context-affinity matcher
58290
+ // resolves each token against both the Package and Language
58291
+ // namespaces, and the profile keeps them in separate fields.
58292
+ stack: norm([...profile.stack ?? [], ...profile.languages ?? []]),
56862
58293
  domains: norm(profile.domains),
56863
58294
  kinds: ["Pattern", "Technique", "AntiPattern"],
56864
58295
  ...seed.length ? { seed } : {},
@@ -56870,6 +58301,13 @@ function createWorkspaceEngine(opts) {
56870
58301
  };
56871
58302
  cloud.search(q).then((res) => {
56872
58303
  remotePriors = res.nodes ?? [];
58304
+ const contributorLabels = [];
58305
+ for (const n of remotePriors) {
58306
+ const c = n.attrs?.["contributor"];
58307
+ if (typeof c?.label === "string") contributorLabels.push(c.label);
58308
+ if (typeof c?.team === "string") contributorLabels.push(c.team);
58309
+ }
58310
+ if (contributorLabels.length > 0) registerRenderedContributorLabels(contributorLabels);
56873
58311
  contextDirty = true;
56874
58312
  }).catch(() => {
56875
58313
  }).finally(() => {
@@ -56887,8 +58325,16 @@ function createWorkspaceEngine(opts) {
56887
58325
  if (typeof cloud.search !== "function") return;
56888
58326
  intentRemoteInFlight = true;
56889
58327
  cloud.search({
56890
- stack: [],
56891
- domains: [],
58328
+ // VS-context-affinity: the intent lane used to send an empty context
58329
+ // while the priming lane sent the profile — opposite postures on the
58330
+ // same method. Under "union derived + stated, canonicalize once"
58331
+ // (VS-intent-fence) the stated intent IS the query and the derived
58332
+ // stack rides along as structured relevanceContext: the cloud no
58333
+ // longer concatenates these tokens into the embedding string, it
58334
+ // re-weights anchored hits with them (stated beats derived — the
58335
+ // intent text drives entry search, the stack only re-ranks).
58336
+ stack: normStackTokens([...profile?.stack ?? [], ...profile?.languages ?? []]),
58337
+ domains: normStackTokens(profile?.domains ?? []),
56892
58338
  kinds: ["Pattern", "Technique", "AntiPattern", "Problem", "Solution"],
56893
58339
  q: liveIntent,
56894
58340
  channel: "intent",
@@ -57135,6 +58581,10 @@ function createWorkspaceEngine(opts) {
57135
58581
  let exposureEvicted = 0;
57136
58582
  let exposureUnshown = 0;
57137
58583
  let corroboratedEdges = 0;
58584
+ let refutesStamped = 0;
58585
+ let recurrencesMarked = 0;
58586
+ let reopenedFromRegression = 0;
58587
+ let fixHeldConfirmed = 0;
57138
58588
  const dispositions = emptyTagDispositions();
57139
58589
  let unresolvedHandles = 0;
57140
58590
  let priorEdgesFlagOff = 0;
@@ -57142,6 +58592,17 @@ function createWorkspaceEngine(opts) {
57142
58592
  let touchedFileTurns = 0;
57143
58593
  let touchedToolTurns = 0;
57144
58594
  let anchorHintsUpgraded = 0;
58595
+ let foldAttempts = 0;
58596
+ let foldNoFile = 0;
58597
+ let foldNoCandidates = 0;
58598
+ let foldKindVeto = 0;
58599
+ let foldJaccardHit = 0;
58600
+ let foldScoutHit = 0;
58601
+ let foldNearMiss30 = 0;
58602
+ let foldNearMiss45 = 0;
58603
+ let foldCandidatesMarked = 0;
58604
+ let citeAnchorsAccrued = 0;
58605
+ let citeAnchorsPromoted = 0;
57145
58606
  let linked = 0;
57146
58607
  const t = Date.now();
57147
58608
  let processedTurns = 0;
@@ -57193,11 +58654,25 @@ function createWorkspaceEngine(opts) {
57193
58654
  const r = ingestDesignProblem(store, flag, {
57194
58655
  workspaceId: profile.id,
57195
58656
  source: sessionId,
57196
- ts: t
58657
+ ts: t,
58658
+ hostHarness: turn.hostHarness
57197
58659
  });
57198
58660
  if (r.created || r.corroborated) minted++;
57199
58661
  else if (r.rejected) tagsRejected++;
57200
58662
  else tagsRestated++;
58663
+ if (r.recurredResolved) {
58664
+ recurrencesMarked++;
58665
+ if (r.reopenedFromRegression) {
58666
+ reopenedFromRegression++;
58667
+ console.warn(
58668
+ `[errata] REOPENED from regression: ${r.problemId} \u2014 second independent witness; the fix did not hold`
58669
+ );
58670
+ } else {
58671
+ console.warn(
58672
+ `[errata] REGRESSION candidate: a resolved problem recurred (${r.problemId}) \u2014 marked resolutionSuspect, not reopened`
58673
+ );
58674
+ }
58675
+ }
57201
58676
  if (flag.kind !== "constraint") sessionLastProblem.set(sessionId, designProblemId(flag.problem));
57202
58677
  if (r.created || r.corroborated) {
57203
58678
  try {
@@ -57271,10 +58746,42 @@ function createWorkspaceEngine(opts) {
57271
58746
  handleMap,
57272
58747
  ts: t,
57273
58748
  mintPriors: elicit,
58749
+ // WM-fold phase 3: cite-time anchor accrual keys on the session — two
58750
+ // DISTINCT sessions co-citing a prior from the same file promote a
58751
+ // real anchor; one session, or none, accrues silently.
58752
+ sessionId,
57274
58753
  ...touched.size > 0 ? { sessionTouchedIds: touched } : {}
57275
58754
  });
57276
58755
  priorEdges += plan.priorEdges;
57277
58756
  corroboratedEdges += plan.corroboratedEdges;
58757
+ citeAnchorsAccrued += plan.citeAnchorsAccrued;
58758
+ citeAnchorsPromoted += plan.citeAnchorsPromoted;
58759
+ const refuteResult = applyLocalRefutations(store, plan.refutes, t);
58760
+ refutesStamped += refuteResult.stamped;
58761
+ for (const c of plan.corroborations) {
58762
+ const node2 = store.getNode(c.nodeId);
58763
+ if (node2?.label !== "Solution") continue;
58764
+ const held = confirmFixHeld(store, c.nodeId, t);
58765
+ if (held > 0) {
58766
+ fixHeldConfirmed += held;
58767
+ console.log(`[errata] fix-held confirmed: ${node2.id} \u2014 ${held} regression question(s) acknowledged`);
58768
+ }
58769
+ }
58770
+ for (const id of refuteResult.stampedIds) {
58771
+ const node2 = store.getNode(id);
58772
+ if (node2?.label !== "Solution") continue;
58773
+ for (const e of store.inEdges(id, ["SOLVED_BY", "FIXED_BY"])) {
58774
+ const touch = markResolvedRecurrence(store, e.from, t, sessionId);
58775
+ if (!touch.marked) continue;
58776
+ recurrencesMarked++;
58777
+ if (touch.reopened) {
58778
+ reopenedFromRegression++;
58779
+ console.warn(
58780
+ `[errata] REOPENED from regression: ${e.from} \u2014 its solution ${id} was refuted by a second independent witness`
58781
+ );
58782
+ }
58783
+ }
58784
+ }
57278
58785
  addTagDispositions(dispositions, plan.dispositions);
57279
58786
  unresolvedHandles += plan.unresolvedHandles.length;
57280
58787
  priorEdgesFlagOff += plan.priorEdgesSuppressed.flagOff;
@@ -57294,26 +58801,93 @@ function createWorkspaceEngine(opts) {
57294
58801
  const anchorProvenance = p.location?.path ? "witnessed" : "edited";
57295
58802
  const hintPath = anchorPath ? void 0 : turnFile ?? wf;
57296
58803
  const dedupPath = anchorPath ?? hintPath;
58804
+ let pendingNomination = null;
57297
58805
  if (dedupPath) {
57298
58806
  try {
57299
- const dupId = reinforceSameAnchorProblem(store, dedupPath, profile.id, p.statement, sessionId, t, p.kind);
57300
- if (dupId) {
58807
+ foldAttempts++;
58808
+ const fold = foldSameAnchorProblem(store, dedupPath, profile.id, p.statement, sessionId, t, p.kind);
58809
+ if (fold.noFile) foldNoFile++;
58810
+ else if (fold.candidates === 0) foldNoCandidates++;
58811
+ foldKindVeto += fold.kindVetoed;
58812
+ if (!fold.foldedId) {
58813
+ if (fold.bestJaccard >= 0.45) foldNearMiss45++;
58814
+ else if (fold.bestJaccard >= 0.3) foldNearMiss30++;
58815
+ if (fold.nominate) {
58816
+ pendingNomination = { ...fold.nominate, source: "file-scout" };
58817
+ }
58818
+ }
58819
+ if (fold.foldedId) {
58820
+ if (fold.foldedBy === "scout") foldScoutHit++;
58821
+ else foldJaccardHit++;
57301
58822
  minted++;
57302
58823
  if (p.kind !== "constraint") {
57303
- sessionLastProblem.set(sessionId, dupId);
57304
- inScopeProblemId = dupId;
58824
+ sessionLastProblem.set(sessionId, fold.foldedId);
58825
+ inScopeProblemId = fold.foldedId;
57305
58826
  }
57306
- if (p.threadId) threads.set(p.threadId, dupId);
58827
+ if (p.threadId) threads.set(p.threadId, fold.foldedId);
57307
58828
  continue;
57308
58829
  }
57309
58830
  } catch {
57310
58831
  }
57311
58832
  }
58833
+ if (!pendingNomination) {
58834
+ try {
58835
+ const ws = findWorkspaceFoldCandidate(store, profile.id, p.statement, p.kind);
58836
+ if (ws) pendingNomination = { ...ws, source: "workspace-scout" };
58837
+ } catch (err2) {
58838
+ console.warn("[errata] workspace fold scout failed (mint unaffected):", err2 instanceof Error ? err2.message : err2);
58839
+ }
58840
+ }
57312
58841
  const r = ingestDesignProblem(store, { problem: p.statement, kind: p.kind }, {
57313
58842
  workspaceId: profile.id,
57314
58843
  source: sessionId,
57315
- ts: t
58844
+ ts: t,
58845
+ hostHarness: turn.hostHarness
57316
58846
  });
58847
+ if (r.created && dedupPath) {
58848
+ try {
58849
+ const closedId = findResolvedAnchorMatch(store, dedupPath, profile.id, p.statement, p.kind);
58850
+ const touch = closedId ? markResolvedRecurrence(store, closedId, t, sessionId) : { marked: false, reopened: false };
58851
+ if (closedId && touch.marked) {
58852
+ store.mergeEdge({
58853
+ id: `edge_${digest({ from: r.problemId, type: "REGRESSION_OF", to: closedId })}`.slice(0, 24),
58854
+ from: r.problemId,
58855
+ to: closedId,
58856
+ type: "REGRESSION_OF",
58857
+ confidence: 0.5,
58858
+ extractionSource: "daemon-extracted",
58859
+ createdAt: t,
58860
+ lastSeenAt: t,
58861
+ navSuccesses: 0,
58862
+ navFailures: 0,
58863
+ attrs: { provisional: true, machineProposed: true, session: sessionId }
58864
+ });
58865
+ recurrencesMarked++;
58866
+ if (touch.reopened) reopenedFromRegression++;
58867
+ console.warn(
58868
+ `[errata] REGRESSION candidate: new problem ${r.problemId} paraphrases resolved ${closedId} on ${dedupPath} \u2014 bound REGRESSION_OF, marked resolutionSuspect` + (touch.reopened ? "; second independent witness \u2014 REOPENED" : "")
58869
+ );
58870
+ }
58871
+ } catch (err2) {
58872
+ console.warn(
58873
+ "[errata] regression recognition failed (mint unaffected):",
58874
+ err2 instanceof Error ? err2.message : err2
58875
+ );
58876
+ }
58877
+ }
58878
+ if (r.created && pendingNomination) {
58879
+ try {
58880
+ const marked = markFoldCandidate(store, r.problemId, pendingNomination.targetId, {
58881
+ ...pendingNomination.hashCos !== void 0 ? { hashCos: pendingNomination.hashCos } : {},
58882
+ ...pendingNomination.jaccard !== void 0 ? { jaccard: pendingNomination.jaccard } : {},
58883
+ source: pendingNomination.source,
58884
+ ts: t
58885
+ });
58886
+ if (marked) foldCandidatesMarked++;
58887
+ } catch (err2) {
58888
+ console.warn("[errata] fold nomination failed (mint unaffected):", err2 instanceof Error ? err2.message : err2);
58889
+ }
58890
+ }
57317
58891
  if (r.created || r.corroborated) {
57318
58892
  minted++;
57319
58893
  if (p.kind !== "constraint") {
@@ -57649,7 +59223,7 @@ function createWorkspaceEngine(opts) {
57649
59223
  if (typeof cloud.reportContradictions === "function") {
57650
59224
  await sendWitnesses(
57651
59225
  "contradict",
57652
- plan.refutes,
59226
+ wireWitnessItems(plan.refutes),
57653
59227
  (items2, session) => cloud.reportContradictions({
57654
59228
  daemonVersion: DAEMON_VERSION,
57655
59229
  projectId: profile.id,
@@ -57717,6 +59291,15 @@ function createWorkspaceEngine(opts) {
57717
59291
  triaged,
57718
59292
  priorEdges,
57719
59293
  corroboratedEdges,
59294
+ // RG-mark: refute witnesses stamped onto local nodes (the local half of
59295
+ // the contradict channel; the wire half rides the witness ledger).
59296
+ refutesStamped,
59297
+ // RG-recognize tier A: resolved problems the door saw recur this pass.
59298
+ recurrencesMarked,
59299
+ // RG-reopen: closes flipped open on the two-touch evidence bar.
59300
+ reopenedFromRegression,
59301
+ // RG-prime: regression questions acknowledged by a fix-held confirm.
59302
+ fixHeldConfirmed,
57720
59303
  // WM-labels calibration cells (see prior-tags exposure split).
57721
59304
  exposureShown,
57722
59305
  exposureEvicted,
@@ -57726,6 +59309,20 @@ function createWorkspaceEngine(opts) {
57726
59309
  exposureOutcomes: exposureShown + exposureEvicted + exposureUnshown,
57727
59310
  // AC-hint-upgrade (harvest seam): edited-this-turn hint promotions.
57728
59311
  anchorHintsUpgraded,
59312
+ // WM-fold funnel (phase 0) — which gate of the same-anchor fold
59313
+ // conjunction eats the mass, and the near-miss bands under the bars.
59314
+ foldAttempts,
59315
+ foldNoFile,
59316
+ foldNoCandidates,
59317
+ foldKindVeto,
59318
+ foldJaccardHit,
59319
+ foldScoutHit,
59320
+ foldNearMiss30,
59321
+ foldNearMiss45,
59322
+ foldCandidatesMarked,
59323
+ // WM-fold phase 3 — cite-time anchor accrual on cited priors.
59324
+ citeAnchorsAccrued,
59325
+ citeAnchorsPromoted,
57729
59326
  touchedFileTurns,
57730
59327
  touchedToolTurns,
57731
59328
  // Seam diagnostic: sessions holding tool-run records at harvest time.
@@ -57850,11 +59447,70 @@ function createWorkspaceEngine(opts) {
57850
59447
  await yieldToLoop();
57851
59448
  await harvestSession(ref.sessionId, ref.path);
57852
59449
  }
59450
+ const codexRefs = codexRolloutsForCwd(opts.workspaceRoot, {
59451
+ sinceMs: Date.now() - TURN_REPLAY_LOOKBACK_MS,
59452
+ limit: 25
59453
+ });
59454
+ for (const ref of codexRefs) {
59455
+ await yieldToLoop();
59456
+ await harvestSession(ref.sessionId, ref.path);
59457
+ }
59458
+ const geminiRefs = geminiTranscriptsForCwd(opts.workspaceRoot, {
59459
+ sinceMs: Date.now() - TURN_REPLAY_LOOKBACK_MS,
59460
+ limit: 25
59461
+ });
59462
+ for (const ref of geminiRefs) {
59463
+ await yieldToLoop();
59464
+ await harvestSession(ref.sessionId, ref.path);
59465
+ }
59466
+ const opencodeRefs = opencodeTranscriptsForCwd(opts.workspaceRoot, {
59467
+ sinceMs: Date.now() - TURN_REPLAY_LOOKBACK_MS,
59468
+ limit: 25
59469
+ });
59470
+ for (const ref of opencodeRefs) {
59471
+ await yieldToLoop();
59472
+ await harvestSession(ref.sessionId, ref.path);
59473
+ }
57853
59474
  } catch {
57854
59475
  }
57855
59476
  })();
57856
59477
  });
59478
+ const sweepCodexRollouts = () => {
59479
+ setImmediate(() => {
59480
+ void (async () => {
59481
+ try {
59482
+ const since = Date.now() - CODEX_SWEEP_LOOKBACK_MS;
59483
+ const refs = [
59484
+ ...codexRolloutsForCwd(opts.workspaceRoot, { sinceMs: since, limit: 10 }),
59485
+ ...geminiTranscriptsForCwd(opts.workspaceRoot, { sinceMs: since, limit: 10 }),
59486
+ ...opencodeTranscriptsForCwd(opts.workspaceRoot, { sinceMs: since, limit: 10 })
59487
+ ];
59488
+ for (const ref of refs) {
59489
+ await yieldToLoop();
59490
+ await harvestSession(ref.sessionId, ref.path);
59491
+ }
59492
+ } catch {
59493
+ }
59494
+ })();
59495
+ });
59496
+ };
57857
59497
  const designRollup = opts.designRollup ?? (process.env["ERRATA_ROLLUP"] === "1" ? haikuDesignRollup(process.env["ANTHROPIC_API_KEY"] ?? null) : void 0);
59498
+ const foldJudgeModel = process.env["ERRATA_FOLD_JUDGE_MODEL"] ?? "gpt-5-mini";
59499
+ const foldJudge = process.env["ERRATA_FOLD_JUDGE"] === "1" && isChatConfigured(foldJudgeModel) ? async (a, b) => {
59500
+ const out2 = await chat({
59501
+ model: foldJudgeModel,
59502
+ system: "You judge whether two independently-written one-line problem reports from the same codebase describe the SAME underlying problem. Reworded restatements of one problem are SAME; related-but-different defects, or a defect vs a design constraint, are DISTINCT. Answer with exactly one word: SAME or DISTINCT.",
59503
+ user: `A: ${a}
59504
+ B: ${b}`,
59505
+ maxTokens: 8
59506
+ });
59507
+ if (!out2) return null;
59508
+ const same = /\bSAME\b/i.test(out2);
59509
+ const distinct = /\bDISTINCT\b/i.test(out2);
59510
+ if (same && !distinct) return "same";
59511
+ if (distinct && !same) return "distinct";
59512
+ return null;
59513
+ } : void 0;
57858
59514
  const onSessionEnd = (e) => {
57859
59515
  if (!designRollup) return;
57860
59516
  setImmediate(() => {
@@ -57992,6 +59648,7 @@ function createWorkspaceEngine(opts) {
57992
59648
  refreshContextNow();
57993
59649
  },
57994
59650
  async tick() {
59651
+ sweepCodexRollouts();
57995
59652
  maybeRefreshRemotePriors();
57996
59653
  const report = {
57997
59654
  generalizerEventsProcessed: 0,
@@ -58130,16 +59787,49 @@ function createWorkspaceEngine(opts) {
58130
59787
  const sem = await embedSemanticNodes(store);
58131
59788
  if (sem.embedded > 0) {
58132
59789
  console.log(`[errata] embedded ${sem.embedded} semantic node(s)`);
58133
- const e = mergeProblemsByEmbedding(store, { ts: Date.now() });
58134
- if (e.merged > 0) {
58135
- console.log(`[errata] dedup: merged ${e.merged} paraphrased problem(s) by embedding across ${e.clusters} cluster(s)`);
58136
- }
59790
+ }
59791
+ const parkWmRaw = store.getMeta?.("fold_park_sweep_at") ?? null;
59792
+ const e = mergeProblemsByEmbedding(store, {
59793
+ ts: Date.now(),
59794
+ ...parkWmRaw !== null ? { freshSince: Number(parkWmRaw) || 0 } : {}
59795
+ });
59796
+ store.setMeta?.("fold_park_sweep_at", String(Date.now()));
59797
+ if (e.merged > 0 || (e.nearMissMarked ?? 0) > 0) {
59798
+ console.log(`[errata] dedup: merged ${e.merged} paraphrased problem(s) across ${e.clusters} cluster(s), parked ${e.nearMissMarked ?? 0} near-miss nomination(s)${parkWmRaw === null ? " (first full sweep)" : ""}`);
59799
+ }
59800
+ if (sem.embedded > 0) {
58137
59801
  const cb = bindCanonicalNeighbors(store, { ts: Date.now() });
58138
59802
  if (cb.bound > 0) {
58139
59803
  console.log(`[errata] canonical-bind: +${cb.bound} machine-proposed link(s) across ${cb.scanned} problem(s)`);
58140
59804
  }
58141
59805
  refreshContextNow();
58142
59806
  }
59807
+ const rg = bindResolvedRegressions(store, { ts: Date.now() });
59808
+ if (rg.bound > 0) {
59809
+ console.warn(`[errata] REGRESSION: bound ${rg.bound} open problem(s) to resolved predecessors${rg.full ? " (first full sweep)" : ""}`);
59810
+ refreshContextNow();
59811
+ }
59812
+ const fjStart = Date.now();
59813
+ const fj = await resolveFoldCandidates(store, {
59814
+ ts: fjStart,
59815
+ ...foldJudge ? { judge: foldJudge } : {}
59816
+ });
59817
+ if (fj.examined > 0) {
59818
+ appendPassLedger(paths.configDir, "fold-judge", Date.now() - fjStart, {
59819
+ examined: fj.examined,
59820
+ folded: fj.folded,
59821
+ foldedEmbedding: fj.foldedEmbedding,
59822
+ foldedJudge: fj.foldedJudge,
59823
+ rejected: fj.rejected,
59824
+ standing: fj.standing,
59825
+ judgeErrors: fj.judgeErrors,
59826
+ retargeted: fj.retargeted
59827
+ });
59828
+ if (fj.folded > 0) {
59829
+ console.log(`[errata] fold-judge: folded ${fj.folded} paraphrase pair(s) (${fj.foldedEmbedding} by embedding, ${fj.foldedJudge} by judge), ${fj.standing} standing`);
59830
+ refreshContextNow();
59831
+ }
59832
+ }
58143
59833
  return { embedded: sem.embedded };
58144
59834
  } catch (err2) {
58145
59835
  console.warn("[errata] settled embed failed:", err2);
@@ -58174,6 +59864,10 @@ function createWorkspaceEngine(opts) {
58174
59864
  console.log(`[errata] dedup: merged ${e.merged} paraphrased problem(s) by embedding across ${e.clusters} cluster(s)`);
58175
59865
  refreshContextNow();
58176
59866
  }
59867
+ const rg = bindResolvedRegressions(store, { ts: Date.now() });
59868
+ if (rg.bound > 0) {
59869
+ console.warn(`[errata] REGRESSION: bound ${rg.bound} open problem(s) to resolved predecessors${rg.full ? " (first full sweep)" : ""}`);
59870
+ }
58177
59871
  const cb = bindCanonicalNeighbors(store, { ts: Date.now() });
58178
59872
  if (cb.bound > 0) {
58179
59873
  console.log(`[errata] canonical-bind: +${cb.bound} machine-proposed link(s) across ${cb.scanned} problem(s)`);
@@ -58264,6 +59958,7 @@ function createWorkspaceEngine(opts) {
58264
59958
  async stop() {
58265
59959
  if (stopGit) stopGit();
58266
59960
  if (passWorker) await passWorker.stop();
59961
+ if (watchBreaker) watchBreaker.stop();
58267
59962
  if (watcher) await watcher.close();
58268
59963
  if (flushTimer) clearTimeout(flushTimer);
58269
59964
  for (const tmr of diagTimers.values()) clearTimeout(tmr);
@@ -59633,6 +61328,157 @@ function createRootAdopter(deps) {
59633
61328
  };
59634
61329
  }
59635
61330
 
61331
+ // src/health-sentinel.ts
61332
+ function numEnv4(key, fallback) {
61333
+ const n = Number(process.env[key]);
61334
+ return Number.isFinite(n) && n > 0 ? n : fallback;
61335
+ }
61336
+ var SENTINEL_TICK_MS = numEnv4("ERRATA_SENTINEL_TICK_MS", 3e4);
61337
+ var SENTINEL_WINDOW_TICKS = numEnv4("ERRATA_SENTINEL_WINDOW_TICKS", 6);
61338
+ var SENTINEL_BURN_CORES = numEnv4("ERRATA_SENTINEL_BURN_CORES", 1.2);
61339
+ var SENTINEL_STORM_RATE = numEnv4("ERRATA_SENTINEL_STORM_RATE", 1e3);
61340
+ var SENTINEL_STORM_TICKS = numEnv4("ERRATA_SENTINEL_STORM_TICKS", 4);
61341
+ var SENTINEL_RENOTIFY_MS = numEnv4("ERRATA_SENTINEL_RENOTIFY_MS", 18e5);
61342
+ var SENTINEL_CALM_TICKS = numEnv4("ERRATA_SENTINEL_CALM_TICKS", 2);
61343
+ var SENTINEL_TOAST_COOLDOWN_MS = numEnv4("ERRATA_SENTINEL_TOAST_COOLDOWN_MS", 6e5);
61344
+ function createHealthSentinel(deps = {}) {
61345
+ const now = deps.now ?? Date.now;
61346
+ const cpuUsage = deps.cpuUsage ?? (() => process.cpuUsage());
61347
+ const readCensus = deps.readCensus ?? (() => {
61348
+ const c = watchCensus();
61349
+ return {
61350
+ dispatched: c.dispatched,
61351
+ logged: c.logged,
61352
+ topOffender: c.topOffender ? { path: c.topOffender.path, count: c.topOffender.count } : null
61353
+ };
61354
+ });
61355
+ const activePasses = deps.activePasses ?? (() => passState().active.map((p) => p.name));
61356
+ const rssMb = deps.rssMb ?? (() => Math.round(process.memoryUsage.rss() / 1048576));
61357
+ const toast = deps.notify ?? ((body2, key) => notifyOpsEvent("daemon-distress", body2, { key }));
61358
+ let prevCpu = cpuUsage();
61359
+ let prevTs = now();
61360
+ let prevDispatched = null;
61361
+ let prevLogged = 0;
61362
+ const coreSamples = [];
61363
+ let stormTicks = 0;
61364
+ let calmTicks = 0;
61365
+ let distress = null;
61366
+ let lastNotifiedAt = 0;
61367
+ const lastToastByKind = /* @__PURE__ */ new Map();
61368
+ const attribution = () => {
61369
+ const parts2 = [];
61370
+ const passes = activePasses();
61371
+ if (passes.length > 0) parts2.push(`active pass: ${passes.join(", ")}`);
61372
+ const c = readCensus();
61373
+ if (c.topOffender) parts2.push(`hottest watch path: ${c.topOffender.path}`);
61374
+ parts2.push(`rss ${rssMb()}MB`);
61375
+ return parts2.join(" \xB7 ");
61376
+ };
61377
+ const maybeToast = (d, t) => {
61378
+ if (t - (lastToastByKind.get(d.kind) ?? 0) < SENTINEL_TOAST_COOLDOWN_MS) return;
61379
+ lastToastByKind.set(d.kind, t);
61380
+ toast(d.headline, d.kind);
61381
+ lastNotifiedAt = t;
61382
+ };
61383
+ const transition = (next) => {
61384
+ const t = now();
61385
+ if (next && !distress) {
61386
+ distress = next;
61387
+ deps.ledger?.("enter", next);
61388
+ maybeToast(next, t);
61389
+ deps.onChange?.(next);
61390
+ } else if (next && distress) {
61391
+ distress.body = next.body;
61392
+ distress.headline = next.headline;
61393
+ if (t - lastNotifiedAt >= SENTINEL_RENOTIFY_MS) maybeToast(distress, t);
61394
+ } else if (!next && distress) {
61395
+ deps.ledger?.("clear", distress);
61396
+ distress = null;
61397
+ deps.onChange?.(null);
61398
+ }
61399
+ };
61400
+ const evaluate = () => {
61401
+ const t = now();
61402
+ const wallMs = Math.max(1, t - prevTs);
61403
+ const cpu = cpuUsage();
61404
+ const cores = (cpu.user + cpu.system - prevCpu.user - prevCpu.system) / 1e3 / wallMs;
61405
+ prevCpu = cpu;
61406
+ prevTs = t;
61407
+ coreSamples.push(cores);
61408
+ if (coreSamples.length > SENTINEL_WINDOW_TICKS) coreSamples.shift();
61409
+ const c = readCensus();
61410
+ const dispatchRate = prevDispatched === null ? 0 : (c.dispatched - prevDispatched) / wallMs * 1e3;
61411
+ const survived = c.logged - prevLogged;
61412
+ prevDispatched = c.dispatched;
61413
+ prevLogged = c.logged;
61414
+ stormTicks = dispatchRate > SENTINEL_STORM_RATE && survived === 0 ? stormTicks + 1 : 0;
61415
+ let wanted = null;
61416
+ if (stormTicks >= SENTINEL_STORM_TICKS) {
61417
+ wanted = {
61418
+ kind: "watch-storm",
61419
+ headline: `errata's file watcher is stuck busy (~${Math.round(dispatchRate)} events/sec with nothing changing) \u2014 run \`errata status\` for detail`,
61420
+ body: `filesystem watcher is processing ~${Math.round(dispatchRate)} events/sec with NOTHING surviving \u2014 a wedged or ignored-tree watch is burning CPU for no signal \xB7 ${attribution()}`,
61421
+ sinceTs: distress?.kind === "watch-storm" ? distress.sinceTs : t
61422
+ };
61423
+ } else {
61424
+ const windowFull = coreSamples.length >= SENTINEL_WINDOW_TICKS;
61425
+ const avgCores = coreSamples.reduce((a, b) => a + b, 0) / Math.max(1, coreSamples.length);
61426
+ if (windowFull && avgCores >= SENTINEL_BURN_CORES && cores >= SENTINEL_BURN_CORES && activePasses().length === 0) {
61427
+ wanted = {
61428
+ kind: "cpu-burn",
61429
+ headline: `errata is using more CPU than expected (${avgCores.toFixed(1)} cores with no work running) \u2014 run \`errata status\` for detail`,
61430
+ body: `sustained CPU with no pass running \u2014 avg ${avgCores.toFixed(1)} cores \xB7 ${attribution()}`,
61431
+ sinceTs: distress?.kind === "cpu-burn" ? distress.sinceTs : t
61432
+ };
61433
+ }
61434
+ }
61435
+ if (wanted) {
61436
+ calmTicks = 0;
61437
+ transition(wanted);
61438
+ } else if (distress) {
61439
+ calmTicks++;
61440
+ if (calmTicks >= SENTINEL_CALM_TICKS) {
61441
+ calmTicks = 0;
61442
+ transition(null);
61443
+ }
61444
+ }
61445
+ };
61446
+ const interval = deps.scheduleTicks === false ? null : setInterval(() => {
61447
+ try {
61448
+ evaluate();
61449
+ } catch {
61450
+ }
61451
+ }, SENTINEL_TICK_MS);
61452
+ interval?.unref?.();
61453
+ return {
61454
+ current: () => distress ? { ...distress } : null,
61455
+ tick: evaluate,
61456
+ stop: () => {
61457
+ if (interval) clearInterval(interval);
61458
+ }
61459
+ };
61460
+ }
61461
+
61462
+ // src/codex-wake.ts
61463
+ var CODEX_WAKE_LOOKBACK_MS = 10 * 6e4;
61464
+ var CODEX_WAKE_BOOT_LOOKBACK_MS = 24 * 60 * 6e4;
61465
+ function codexWakeCandidates(input) {
61466
+ if (input.disabled) return [];
61467
+ const find = input.find ?? ((cwd, since) => codexRolloutsForCwd(cwd, { sinceMs: since, limit: 1 }).length + geminiTranscriptsForCwd(cwd, { sinceMs: since, limit: 1 }).length + opencodeTranscriptsForCwd(cwd, { sinceMs: since, limit: 1 }).length);
61468
+ const out2 = [];
61469
+ for (const entry of input.entries) {
61470
+ if (input.liveRoots.has(input.normPath(entry.path))) continue;
61471
+ let n = 0;
61472
+ try {
61473
+ n = find(entry.path, input.sinceMs);
61474
+ } catch {
61475
+ continue;
61476
+ }
61477
+ if (n > 0) out2.push({ name: entry.name, path: entry.path });
61478
+ }
61479
+ return out2;
61480
+ }
61481
+
59636
61482
  // src/multi.ts
59637
61483
  var projectSymbolSalts = /* @__PURE__ */ new Map();
59638
61484
  async function resolveProjectSymbolSalt(client, projectId) {
@@ -59777,9 +61623,24 @@ async function startMultiDaemon(opts = {}) {
59777
61623
  for (const r of records) r.engine.markContextDirty();
59778
61624
  }
59779
61625
  }) : null;
61626
+ const healthSentinel = createHealthSentinel({
61627
+ onChange: () => {
61628
+ for (const r of records) r.engine.markContextDirty();
61629
+ },
61630
+ ledger: (event, d) => {
61631
+ const dir = records[0]?.engine.paths.configDir;
61632
+ if (dir) {
61633
+ appendPassLedger(dir, "health-sentinel", 0, {
61634
+ [`distress:${event}:${d.kind}`]: 1,
61635
+ distressSinceTs: d.sinceTs
61636
+ });
61637
+ }
61638
+ }
61639
+ });
59780
61640
  let serverPending = null;
59781
61641
  const boundaryListeners = [];
59782
61642
  let boundaryFlushing = false;
61643
+ let settleRunning = false;
59783
61644
  let boundaryFlushStartedAt = 0;
59784
61645
  let boundaryFlushStage = "";
59785
61646
  let boundaryFlushUnits = 0;
@@ -59871,6 +61732,10 @@ async function startMultiDaemon(opts = {}) {
59871
61732
  reviewUrl: () => `${baseUrl}/ws/${id}/review`,
59872
61733
  sharedStore,
59873
61734
  pendingUpdate: () => serverPending ?? updatePoller?.current() ?? null,
61735
+ // Ops-gated (default OFF): the 🚨 notice reads as "errata broke my
61736
+ // machine" to a non-operator. Detection + ledger run regardless — only
61737
+ // this push surface (and the toast, gated in notifyOpsEvent) is scoped.
61738
+ daemonDistress: () => loadConfig().opsNotices ? healthSentinel.current() : null,
59874
61739
  ...opts.skipWatchers ? { skipWatchers: true } : {}
59875
61740
  });
59876
61741
  const webApp = buildWebUi({
@@ -59959,7 +61824,7 @@ async function startMultiDaemon(opts = {}) {
59959
61824
  registerWorkspace(rec.engine.profile, rec.root);
59960
61825
  } catch {
59961
61826
  }
59962
- void rec.engine.reindex({ skipEmbed: true }).catch(() => {
61827
+ void rec.engine.reindex({ skipEmbed: true, quiet: rec.engine.store.nodeCount() > 0 }).catch(() => {
59963
61828
  });
59964
61829
  console.log(`[errata] hot-registered ${rec.entry.name} (${rec.id}) \u2014 now watching live`);
59965
61830
  return { attached: true, id: rec.id };
@@ -60155,6 +62020,33 @@ async function startMultiDaemon(opts = {}) {
60155
62020
  };
60156
62021
  const idleSweep = setInterval(sweepIdleWorkspaces, WORKSPACE_SWEEP_INTERVAL_MS);
60157
62022
  idleSweep.unref?.();
62023
+ let codexWakeFirstPass = true;
62024
+ const sweepCodexWake = () => {
62025
+ const lookback = codexWakeFirstPass ? CODEX_WAKE_BOOT_LOOKBACK_MS : CODEX_WAKE_LOOKBACK_MS;
62026
+ codexWakeFirstPass = false;
62027
+ let entries2;
62028
+ try {
62029
+ entries2 = listWorkspaceEntries();
62030
+ } catch {
62031
+ return;
62032
+ }
62033
+ const candidates = codexWakeCandidates({
62034
+ entries: entries2,
62035
+ liveRoots: new Set(records.map((r) => normPath(r.root))),
62036
+ normPath,
62037
+ sinceMs: Date.now() - lookback,
62038
+ disabled: process.env["ERRATA_CODEX_WAKE_DISABLE"] === "1"
62039
+ });
62040
+ for (const c of candidates) {
62041
+ if (attachWorkspace(c.path).attached) {
62042
+ console.log(`[errata] woke workspace ${c.name} \u2014 recent Codex/Gemini/OpenCode transcript (headless capture)`);
62043
+ }
62044
+ }
62045
+ };
62046
+ const codexWake = setInterval(sweepCodexWake, WORKSPACE_SWEEP_INTERVAL_MS);
62047
+ codexWake.unref?.();
62048
+ const codexWakeBoot = setTimeout(sweepCodexWake, 5e3);
62049
+ codexWakeBoot.unref?.();
60158
62050
  const reindexAll = async (rOpts) => {
60159
62051
  const out2 = /* @__PURE__ */ new Map();
60160
62052
  const toIndex = records.filter((r) => rOpts?.force || r.engine.store.nodeCount() === 0);
@@ -60843,12 +62735,19 @@ async function startMultiDaemon(opts = {}) {
60843
62735
  return { written, pruned: pruned2 };
60844
62736
  },
60845
62737
  async sessionBoundaryFlush() {
62738
+ if (!settleRunning) {
62739
+ settleRunning = true;
62740
+ try {
62741
+ for (const r of records) {
62742
+ await r.engine.embedSettled();
62743
+ }
62744
+ } finally {
62745
+ settleRunning = false;
62746
+ }
62747
+ }
60846
62748
  if (boundaryFlushing) return { uploaded: 0, written: 0, pruned: 0, skipped: "in-flight" };
60847
- beginFlush("embed");
62749
+ beginFlush("instances");
60848
62750
  try {
60849
- for (const r of records) {
60850
- await r.engine.embedSettled();
60851
- }
60852
62751
  flushStage("instances");
60853
62752
  const inst = await daemon.syncInstancesPublic();
60854
62753
  flushStage("skills");
@@ -60868,6 +62767,8 @@ async function startMultiDaemon(opts = {}) {
60868
62767
  },
60869
62768
  async stop() {
60870
62769
  clearInterval(idleSweep);
62770
+ clearInterval(codexWake);
62771
+ clearTimeout(codexWakeBoot);
60871
62772
  try {
60872
62773
  const cur = readFileSync27(lockPath, "utf8");
60873
62774
  if (JSON.parse(cur).pid === process.pid) unlinkSync3(lockPath);
@@ -60877,6 +62778,7 @@ async function startMultiDaemon(opts = {}) {
60877
62778
  (res) => server.close(res)
60878
62779
  );
60879
62780
  updatePoller?.stop();
62781
+ healthSentinel.stop();
60880
62782
  for (const r of records) await r.engine.stop();
60881
62783
  if (consolidateWorker) await consolidateWorker.stop();
60882
62784
  try {
@@ -61501,6 +63403,17 @@ function shouldUseOAuthLogin(flags2, env2 = process.env) {
61501
63403
  }
61502
63404
 
61503
63405
  // src/doctor.ts
63406
+ import { spawnSync } from "node:child_process";
63407
+ function installedHarnessVersion(bin) {
63408
+ try {
63409
+ const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 3e3 });
63410
+ if (r.status !== 0 && !r.stdout) return null;
63411
+ const m = /(\d+\.\d+\.\d+)/.exec(`${r.stdout ?? ""}${r.stderr ?? ""}`);
63412
+ return m ? m[1] : null;
63413
+ } catch {
63414
+ return null;
63415
+ }
63416
+ }
61504
63417
  async function diagnoseInstallation(cfg, dependencies) {
61505
63418
  const checks = [];
61506
63419
  const profileName = cfg.activeInstallationProfile;
@@ -61573,6 +63486,20 @@ async function diagnoseInstallation(cfg, dependencies) {
61573
63486
  });
61574
63487
  }
61575
63488
  }
63489
+ const versionOf = dependencies.harnessVersion ?? installedHarnessVersion;
63490
+ for (const { bin, pinned } of [
63491
+ { bin: "codex", pinned: CODEX_PARSER_PINNED_VERSION },
63492
+ { bin: "opencode", pinned: OPENCODE_PARSER_PINNED_VERSION }
63493
+ ]) {
63494
+ const installed = versionOf(bin);
63495
+ checks.push(
63496
+ installed === null ? { id: `${bin}_parser_pin`, status: "pass", detail: `${bin} not installed \u2014 parser-pin check skipped` } : installed === pinned ? { id: `${bin}_parser_pin`, status: "pass", detail: `${bin} ${installed} matches the transcript-parser pin` } : {
63497
+ id: `${bin}_parser_pin`,
63498
+ status: "warn",
63499
+ detail: `${bin} ${installed} \u2260 parser pinned ${pinned} \u2014 transcripts verified against ${pinned}; re-pin fixtures if capture drifts`
63500
+ }
63501
+ );
63502
+ }
61576
63503
  return {
61577
63504
  ok: checks.every((check2) => check2.status !== "fail"),
61578
63505
  profile: profileName,
@@ -61632,7 +63559,7 @@ var DEFAULT_CONSOLIDATION_POLICY = {
61632
63559
  quiescenceMs: 6e4
61633
63560
  // sim preferred 60s over 90s (fresher, esp. heavy-code)
61634
63561
  };
61635
- function numEnv3(env2, key, fallback) {
63562
+ function numEnv5(env2, key, fallback) {
61636
63563
  const raw2 = env2[key];
61637
63564
  if (raw2 == null || raw2.trim() === "") return fallback;
61638
63565
  const n = Number(raw2);
@@ -61640,18 +63567,196 @@ function numEnv3(env2, key, fallback) {
61640
63567
  }
61641
63568
  function consolidationPolicyFromEnv(env2 = process.env) {
61642
63569
  return {
61643
- baseFloorMs: numEnv3(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
61644
- dutyFactor: numEnv3(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
61645
- momentumThreshold: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
61646
- momentumRatio: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
61647
- momentumRatioCap: numEnv3(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
61648
- quiescenceMs: numEnv3(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
63570
+ baseFloorMs: numEnv5(env2, "ERRATA_CONSOLIDATE_BASE_FLOOR_MS", DEFAULT_CONSOLIDATION_POLICY.baseFloorMs),
63571
+ dutyFactor: numEnv5(env2, "ERRATA_CONSOLIDATE_DUTY_FACTOR", DEFAULT_CONSOLIDATION_POLICY.dutyFactor),
63572
+ momentumThreshold: numEnv5(env2, "ERRATA_CONSOLIDATE_MOMENTUM", DEFAULT_CONSOLIDATION_POLICY.momentumThreshold),
63573
+ momentumRatio: numEnv5(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO", DEFAULT_CONSOLIDATION_POLICY.momentumRatio),
63574
+ momentumRatioCap: numEnv5(env2, "ERRATA_CONSOLIDATE_MOMENTUM_RATIO_CAP", DEFAULT_CONSOLIDATION_POLICY.momentumRatioCap),
63575
+ quiescenceMs: numEnv5(env2, "ERRATA_CONSOLIDATE_QUIESCENCE_MS", DEFAULT_CONSOLIDATION_POLICY.quiescenceMs)
61649
63576
  };
61650
63577
  }
61651
63578
  function consolidationGapMs(lastPassMs, policy) {
61652
63579
  return Math.max(policy.baseFloorMs, lastPassMs * policy.dutyFactor);
61653
63580
  }
61654
63581
 
63582
+ // src/hook-commands.ts
63583
+ function hookCurlCommand(port, path2 = "/api/hook") {
63584
+ const url2 = `http://127.0.0.1:${port}${path2}`;
63585
+ return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 3 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} >NUL 2>NUL || exit /b 0"` : `curl -s --connect-timeout 1 --max-time 3 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} >/dev/null 2>&1 || true`;
63586
+ }
63587
+ function hookRelayCommand(port, path2) {
63588
+ const url2 = `http://127.0.0.1:${port}${path2}`;
63589
+ return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
63590
+ }
63591
+ var CODEX_HOOKS_BEGIN = `# >>> errata hooks (errata-managed)`;
63592
+ var CODEX_HOOKS_END = `# <<< errata hooks`;
63593
+ function buildCodexHooksToml(port) {
63594
+ const cmd2 = hookCurlCommand(port).replace(/"/g, '\\"');
63595
+ const turnCmd = hookCurlCommand(port, "/api/turn").replace(/"/g, '\\"');
63596
+ return `${CODEX_HOOKS_BEGIN}
63597
+ [[hooks.PreToolUse]]
63598
+ matcher = ".*"
63599
+
63600
+ [[hooks.PreToolUse.hooks]]
63601
+ type = "command"
63602
+ command = "${cmd2}"
63603
+ timeout = 10
63604
+
63605
+ [[hooks.PostToolUse]]
63606
+ matcher = ".*"
63607
+
63608
+ [[hooks.PostToolUse.hooks]]
63609
+ type = "command"
63610
+ command = "${cmd2}"
63611
+ timeout = 10
63612
+
63613
+ [[hooks.PostToolUse.hooks]]
63614
+ type = "command"
63615
+ command = "${turnCmd}"
63616
+ timeout = 10
63617
+ ${CODEX_HOOKS_END}
63618
+ `;
63619
+ }
63620
+
63621
+ // src/opencode-plugin.ts
63622
+ var OPENCODE_PLUGIN_MARKER = "// @inerrata opencode plugin \u2014 errata-managed (errata install-hooks opencode)";
63623
+ var OPENCODE_D1_CONTRACT = "Behavioral contract: (1) On ANY error, call search() BEFORE debugging \u2014 costs ~400 tokens vs 5,000\u201350,000 cold. (2) After solving non-trivial problems, contribute back: if your harness emits typed nodes (canonicalId + label + attrs), use ingest \u2014 the typed door; otherwise call contribute(), the prose fallback. (3) Before web search, try search() first \u2014 graph is 2\u201315\xD7 cheaper. (4) At session end, review what you solved and contribute anything novel.";
63624
+ var OPENCODE_TOOL_FAILURE_NUDGE = "[inerrata] This tool call failed \u2014 call search() with the error message BEFORE debugging from scratch (~400 tokens vs 5,000\u201350,000 cold).";
63625
+ function buildOpencodePluginSource(port, opts = {}) {
63626
+ const version2 = opts.version ?? "dev";
63627
+ return `${OPENCODE_PLUGIN_MARKER}
63628
+ // Generated by errata ${version2}. Re-run \`errata install-hooks opencode\` to update; edits here are overwritten.
63629
+ // What it does: mirrors each OpenCode session's OWN message record to a JSONL
63630
+ // file the errata daemon can parse, and tells the daemon on every turn boundary.
63631
+ // It has no dependencies and never throws into OpenCode.
63632
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
63633
+ import { join } from "node:path";
63634
+ import { homedir } from "node:os";
63635
+
63636
+ const DAEMON = "http://127.0.0.1:${port}";
63637
+ const HEADER_TYPE = "errata-opencode-session";
63638
+ const D1_CONTRACT = ${JSON.stringify(OPENCODE_D1_CONTRACT)};
63639
+ const TOOL_FAILURE_NUDGE = ${JSON.stringify(OPENCODE_TOOL_FAILURE_NUDGE)};
63640
+
63641
+ function mirrorRoot() {
63642
+ const explicit = process.env.ERRATA_OPENCODE_SESSIONS_DIRS;
63643
+ if (explicit) { const first = explicit.split(":").map((s) => s.trim()).find(Boolean); if (first) return first; }
63644
+ if (process.env.XDG_DATA_HOME) return join(process.env.XDG_DATA_HOME, "opencode", "errata");
63645
+ return join(homedir(), ".local", "share", "opencode", "errata");
63646
+ }
63647
+
63648
+ async function post(path, body) {
63649
+ try {
63650
+ const res = await fetch(DAEMON + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(15000) });
63651
+ return res.status;
63652
+ } catch { return 0; }
63653
+ }
63654
+
63655
+ export const InerrataPlugin = async (input) => {
63656
+ const directory = input.directory;
63657
+ const projectID = input.project && input.project.id ? input.project.id : null;
63658
+ const root = mirrorRoot();
63659
+ // per-session state: which message ids are already in the mirror file
63660
+ const written = new Map(); // sessionID -> Set<messageId>
63661
+ const files = new Map(); // sessionID -> path
63662
+
63663
+ function fileFor(sessionID) {
63664
+ let p = files.get(sessionID);
63665
+ if (p) return p;
63666
+ try { mkdirSync(root, { recursive: true }); } catch {}
63667
+ p = join(root, sessionID + ".jsonl");
63668
+ files.set(sessionID, p);
63669
+ const seen = new Set();
63670
+ if (existsSync(p)) {
63671
+ // resumed session / plugin re-instantiated: learn what is already mirrored
63672
+ try {
63673
+ for (const line of readFileSync(p, "utf8").split("\\n")) {
63674
+ if (!line) continue;
63675
+ try { const o = JSON.parse(line); if (o && o.info && typeof o.info.id === "string") seen.add(o.info.id); } catch {}
63676
+ }
63677
+ } catch {}
63678
+ } else {
63679
+ appendFileSync(p, JSON.stringify({ type: HEADER_TYPE, version: 1, sessionID, directory, projectID, worktree: input.worktree || null, writtenAt: Date.now() }) + "\\n");
63680
+ }
63681
+ written.set(sessionID, seen);
63682
+ return p;
63683
+ }
63684
+
63685
+ async function mirror(sessionID) {
63686
+ // OpenCode's own record: session.messages() items are {info: Message, parts: Part[]}
63687
+ // (the same objects \`opencode export\` prints). Append only completed assistant
63688
+ // messages + every user message not yet mirrored; ids never repeat.
63689
+ const p = fileFor(sessionID);
63690
+ const seen = written.get(sessionID);
63691
+ let items = [];
63692
+ try {
63693
+ const r = await input.client.session.messages({ path: { id: sessionID } });
63694
+ items = (r && r.data) ? r.data : (Array.isArray(r) ? r : []);
63695
+ } catch { return { path: p, appended: 0 }; }
63696
+ let appended = 0;
63697
+ for (const item of items) {
63698
+ const info = item && item.info;
63699
+ if (!info || typeof info.id !== "string" || seen.has(info.id)) continue;
63700
+ if (info.role === "assistant" && !(info.time && info.time.completed)) continue; // still streaming
63701
+ try {
63702
+ appendFileSync(p, JSON.stringify({ info, parts: Array.isArray(item.parts) ? item.parts : [] }) + "\\n");
63703
+ seen.add(info.id); appended++;
63704
+ } catch {}
63705
+ }
63706
+ return { path: p, appended };
63707
+ }
63708
+
63709
+ const touched = new Set();
63710
+ return {
63711
+ // D1's text reaches OpenCode (\xA711.5 item 1): appended to the SYSTEM prompt
63712
+ // of every request \u2014 the same channel the Claude session-start hook uses
63713
+ // (system-level fires reliably; per-turn nudges measurably do not).
63714
+ // \`experimental.chat.system.transform\` is the hook the 1.18.18 plugin
63715
+ // API exposes for this ({sessionID?, model} \u2192 {system: string[]}).
63716
+ "experimental.chat.system.transform": async (_input, output) => {
63717
+ try {
63718
+ if (output && Array.isArray(output.system) && !output.system.includes(D1_CONTRACT)) output.system.push(D1_CONTRACT);
63719
+ } catch {}
63720
+ },
63721
+ // Tool-failure nudge (\xA711.5 item 2): when a tool result looks failed, one
63722
+ // advisory line is appended to the OUTPUT the model reads. Failure is a
63723
+ // heuristic \u2014 \`metadata\` is untyped in the plugin API \u2014 pinned to the
63724
+ // shapes observed: metadata.error truthy, or a non-zero exit/exitCode.
63725
+ "tool.execute.after": async (_input, output) => {
63726
+ try {
63727
+ if (!output || typeof output.output !== "string") return;
63728
+ const meta = output.metadata;
63729
+ const failed = !!(meta && (meta.error || (typeof meta.exit === "number" && meta.exit !== 0) || (typeof meta.exitCode === "number" && meta.exitCode !== 0)));
63730
+ if (failed && !output.output.includes(TOOL_FAILURE_NUDGE)) output.output = output.output + "\\n\\n" + TOOL_FAILURE_NUDGE;
63731
+ } catch {}
63732
+ },
63733
+ event: async ({ event }) => {
63734
+ try {
63735
+ if (!event || typeof event.type !== "string") return;
63736
+ const sid = event.properties && event.properties.sessionID;
63737
+ if (event.type === "session.created" && sid) { fileFor(sid); touched.add(sid); return; }
63738
+ if (event.type === "session.idle" && sid) {
63739
+ const { path, appended } = await mirror(sid);
63740
+ touched.add(sid);
63741
+ // turn boundary \u2192 the daemon harvests the mirror (idempotent per turn)
63742
+ await post("/api/turn", { session_id: "opencode:" + sid, transcript_path: path, cwd: directory, hostHarness: "opencode", appended });
63743
+ }
63744
+ } catch {}
63745
+ },
63746
+ dispose: async () => {
63747
+ // OpenCode has no session-end event; process disposal is the closest.
63748
+ for (const sid of touched) {
63749
+ try {
63750
+ const { path } = await mirror(sid);
63751
+ await post("/api/session-end", { session_id: "opencode:" + sid, transcript_path: path, cwd: directory, reason: "opencode-dispose" });
63752
+ } catch {}
63753
+ }
63754
+ },
63755
+ };
63756
+ };
63757
+ `;
63758
+ }
63759
+
61655
63760
  // src/cli.ts
61656
63761
  var exitCleanOnEpipe = (err2) => {
61657
63762
  if (err2.code === "EPIPE") process.exit(0);
@@ -61733,7 +63838,7 @@ async function main() {
61733
63838
  case "install-hooks":
61734
63839
  return cmdInstallHooks(rest);
61735
63840
  case "mcp":
61736
- return cmdMcp();
63841
+ return cmdMcp(rest);
61737
63842
  case "sync":
61738
63843
  return cmdSync(rest[0] ?? "now");
61739
63844
  case "feedback":
@@ -61744,6 +63849,8 @@ async function main() {
61744
63849
  return cmdConsent(rest);
61745
63850
  case "notifications":
61746
63851
  return cmdNotifications(rest[0] ?? "");
63852
+ case "ops-notices":
63853
+ return cmdOpsNotices(rest[0] ?? "");
61747
63854
  case "update":
61748
63855
  return cmdUpdate(rest);
61749
63856
  case "projects":
@@ -61832,10 +63939,11 @@ Commands:
61832
63939
  Written to .errata/report.html. Flag: --future-verbs
61833
63940
  install-hooks <harness>
61834
63941
  Wire harness hooks \u2192 daemon /api/hook (or MCP).
61835
- <harness>: claude (default) | cursor | codex | aider
63942
+ <harness>: claude (default) | cursor | codex | opencode | aider
61836
63943
  Flags: --port N (default 7891)
61837
- mcp Run the MCP stdio server \u2014 the agent's full errata tool
61838
- surface (navigation, problems, claims, burst, health)
63944
+ mcp [--grok] Run the MCP stdio server \u2014 the agent's full errata tool
63945
+ surface (navigation, problems, claims, burst, health).
63946
+ --grok emits bare tool names accepted by Grok Code.
61839
63947
  login Sign in with the cloud. Default: short verification URL +
61840
63948
  typeable code (device-bridged OAuth). Flags: --browser
61841
63949
  (loopback code flow) \xB7 --token <key> \xB7
@@ -61867,6 +63975,9 @@ Commands:
61867
63975
  Send scrubbed feedback to the maintainers (needs login)
61868
63976
  notifications <on|off>
61869
63977
  Desktop toasts on problem open/resolve + review ready
63978
+ ops-notices <on|off>
63979
+ Ops-grade health surfaces (daemon-distress toast + agent
63980
+ notice). Default off; for people operating errata itself
61870
63981
 
61871
63982
  Environment:
61872
63983
  ERRATA_CLOUD_URL Cloud base URL (default ${DEFAULT_CLOUD_URL})
@@ -63660,19 +65771,22 @@ async function cmdStop() {
63660
65771
  }
63661
65772
  }
63662
65773
  }
63663
- async function cmdMcp() {
65774
+ async function cmdMcp(args2) {
63664
65775
  await ensureProfile();
63665
- ensureSingletonRunning();
65776
+ const grokCompatibility = args2.includes("--grok");
65777
+ ensureSingletonRunning({ withoutPinnedConfig: grokCompatibility });
63666
65778
  const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
63667
- await runMcpServer2(ROOT);
65779
+ await runMcpServer2(ROOT, { toolNameStyle: grokCompatibility ? "bare" : "prefixed" });
63668
65780
  }
63669
65781
  function selfArgv(sub) {
63670
65782
  const self = process.execPath;
63671
65783
  if (/errata(\.exe)?$/i.test(self)) return { cmd: self, args: [sub] };
63672
65784
  return { cmd: self, args: [...process.execArgv, process.argv[1] ?? "", sub] };
63673
65785
  }
63674
- function spawnDaemonDetached() {
65786
+ function spawnDaemonDetached(options = {}) {
63675
65787
  const { cmd: cmd2, args: args2 } = selfArgv("dash");
65788
+ const daemonEnv = { ...process.env };
65789
+ if (options.withoutPinnedConfig) delete daemonEnv["ERRATA_CONFIG_PATH"];
63676
65790
  let out2 = "ignore";
63677
65791
  try {
63678
65792
  const logPath = daemonLogPath();
@@ -63694,20 +65808,20 @@ function spawnDaemonDetached() {
63694
65808
  // a backstop, not a working limit. NODE_OPTIONS so it applies however the
63695
65809
  // bundle re-executes.
63696
65810
  env: {
63697
- ...process.env,
63698
- NODE_OPTIONS: `${process.env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${DAEMON_MAX_HEAP_MB}`.trim()
65811
+ ...daemonEnv,
65812
+ NODE_OPTIONS: `${daemonEnv["NODE_OPTIONS"] ?? ""} --max-old-space-size=${DAEMON_MAX_HEAP_MB}`.trim()
63699
65813
  }
63700
65814
  }).unref();
63701
65815
  if (out2 !== "ignore") closeSync2(out2);
63702
65816
  }
63703
- function ensureSingletonRunning() {
65817
+ function ensureSingletonRunning(options = {}) {
63704
65818
  try {
63705
65819
  if (isDaemonAlive(globalDaemonLock())) return true;
63706
- spawnDaemonDetached();
65820
+ spawnDaemonDetached(options);
63707
65821
  void (async () => {
63708
65822
  if (await waitForDaemon(globalDaemonLock(), 8e3)) return;
63709
65823
  if (isDaemonAlive(globalDaemonLock())) return;
63710
- spawnDaemonDetached();
65824
+ spawnDaemonDetached(options);
63711
65825
  await waitForDaemon(globalDaemonLock(), 8e3);
63712
65826
  })();
63713
65827
  return true;
@@ -63729,28 +65843,66 @@ async function cmdInstallHooks(args2) {
63729
65843
  case "codex":
63730
65844
  await installCodexHooks(port);
63731
65845
  return;
65846
+ case "opencode":
65847
+ await installOpencodePlugin(port);
65848
+ return;
63732
65849
  case "aider":
63733
65850
  printAiderInstructions();
63734
65851
  return;
63735
65852
  default:
63736
65853
  console.error(`unknown harness: ${harness}`);
63737
- console.error(` supported: claude, cursor, codex, aider`);
65854
+ console.error(` supported: claude, cursor, codex, opencode, aider`);
63738
65855
  process.exit(2);
63739
65856
  }
63740
65857
  }
65858
+ async function installOpencodePlugin(port) {
65859
+ const { mkdirSync: mkdirSync9, existsSync: existsSync31, readFileSync: readFileSync29, writeFileSync: writeFileSync23 } = await import("node:fs");
65860
+ const { join: join33 } = await import("node:path");
65861
+ const dir = join33(ROOT, ".opencode", "plugin");
65862
+ if (!existsSync31(dir)) mkdirSync9(dir, { recursive: true });
65863
+ const file2 = join33(dir, "inerrata.js");
65864
+ if (existsSync31(file2)) {
65865
+ const head2 = readFileSync29(file2, "utf8").split("\n")[0] ?? "";
65866
+ if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
65867
+ console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
65868
+ process.exit(2);
65869
+ }
65870
+ }
65871
+ writeFileSync23(file2, buildOpencodePluginSource(port, { version: DAEMON_VERSION }), "utf8");
65872
+ const cfgFile = join33(ROOT, "opencode.json");
65873
+ let cfg = { $schema: "https://opencode.ai/config.json" };
65874
+ if (existsSync31(cfgFile)) {
65875
+ try {
65876
+ cfg = JSON.parse(readFileSync29(cfgFile, "utf8"));
65877
+ } catch {
65878
+ console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
65879
+ process.exit(2);
65880
+ }
65881
+ }
65882
+ cfg.mcp ??= {};
65883
+ const hadErrata = "errata" in cfg.mcp;
65884
+ if (!hadErrata) {
65885
+ const inv = errataMcpInvocation();
65886
+ cfg.mcp["errata"] = { type: "local", command: [inv.command, ...inv.args] };
65887
+ writeFileSync23(cfgFile, JSON.stringify(cfg, null, 2) + "\n", "utf8");
65888
+ }
65889
+ console.log(`installed OpenCode plugin \u2192 ${file2}`);
65890
+ if (hadErrata) console.log(` mcp.errata already present in ${cfgFile} \u2014 left untouched`);
65891
+ else console.log(`installed OpenCode MCP server config \u2192 ${cfgFile} (mcp.errata: local \`errata mcp\`)`);
65892
+ console.log(` endpoint: http://127.0.0.1:${port}/api/turn on every session.idle (+ /api/session-end on dispose)`);
65893
+ console.log("");
65894
+ console.log(` OpenCode keeps sessions in SQLite, so the plugin mirrors each session's`);
65895
+ console.log(` own message record to $XDG_DATA_HOME/opencode/errata/<sessionID>.jsonl`);
65896
+ console.log(` (override: ERRATA_OPENCODE_SESSIONS_DIRS) and the daemon parses THAT \u2014`);
65897
+ console.log(` role-typed, attested. Works for headless \`opencode run\` too.`);
65898
+ console.log(` Cloud tools (search/contribute) are the MCP entry in opencode.json:`);
65899
+ console.log(` { "mcp": { "inerrata": { "type": "remote", "url": "https://mcp.inerrata.dev/mcp", "headers": { "Authorization": "Bearer <errk_\u2026>" } } } }`);
65900
+ }
63741
65901
  var ERRATA_TAG = "# errata-managed";
63742
65902
  function errataMcpInvocation() {
63743
65903
  const { cmd: cmd2, args: args2 } = selfArgv("mcp");
63744
65904
  return { command: cmd2, args: args2 };
63745
65905
  }
63746
- function hookCurlCommand(port, path2 = "/api/hook") {
63747
- const url2 = `http://127.0.0.1:${port}${path2}`;
63748
- return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 3 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} >NUL 2>NUL || exit /b 0"` : `curl -s --connect-timeout 1 --max-time 3 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} >/dev/null 2>&1 || true`;
63749
- }
63750
- function hookRelayCommand(port, path2) {
63751
- const url2 = `http://127.0.0.1:${port}${path2}`;
63752
- return process.platform === "win32" ? `cmd //c "curl -s --connect-timeout 1 --max-time 2 -X POST -H \\"Content-Type: application/json\\" --data-binary @- ${url2} 2>NUL || echo {}"` : `curl -s --connect-timeout 1 --max-time 2 -X POST -H 'Content-Type: application/json' --data-binary @- ${url2} 2>/dev/null || echo '{}'`;
63753
- }
63754
65906
  async function installClaudeHooks(port) {
63755
65907
  const { mkdirSync: mkdirSync9, existsSync: existsSync31, readFileSync: readFileSync29, writeFileSync: writeFileSync23 } = await import("node:fs");
63756
65908
  const { join: join33 } = await import("node:path");
@@ -63872,45 +66024,32 @@ async function installCodexHooks(port) {
63872
66024
  const dir = join33(ROOT, ".codex");
63873
66025
  if (!existsSync31(dir)) mkdirSync9(dir, { recursive: true });
63874
66026
  const file2 = join33(dir, "config.toml");
63875
- const BEGIN = `# >>> errata hooks (errata-managed)`;
63876
- const END = `# <<< errata hooks`;
63877
66027
  let existing = "";
63878
66028
  if (existsSync31(file2)) {
63879
66029
  existing = readFileSync29(file2, "utf8");
63880
- const beginIdx = existing.indexOf(BEGIN);
63881
- const endIdx = existing.indexOf(END);
66030
+ const beginIdx = existing.indexOf(CODEX_HOOKS_BEGIN);
66031
+ const endIdx = existing.indexOf(CODEX_HOOKS_END);
63882
66032
  if (beginIdx >= 0 && endIdx > beginIdx) {
63883
- existing = existing.slice(0, beginIdx).trimEnd() + existing.slice(endIdx + END.length).trimStart();
66033
+ existing = existing.slice(0, beginIdx).trimEnd() + existing.slice(endIdx + CODEX_HOOKS_END.length).trimStart();
63884
66034
  }
63885
66035
  }
63886
- const cmd2 = hookCurlCommand(port).replace(/"/g, '\\"');
63887
- const block = `${BEGIN}
63888
- [[hooks.PreToolUse]]
63889
- matcher = ".*"
63890
-
63891
- [[hooks.PreToolUse.hooks]]
63892
- type = "command"
63893
- command = "${cmd2}"
63894
- timeout = 10
63895
-
63896
- [[hooks.PostToolUse]]
63897
- matcher = ".*"
63898
-
63899
- [[hooks.PostToolUse.hooks]]
63900
- type = "command"
63901
- command = "${cmd2}"
63902
- timeout = 10
63903
- ${END}
63904
- `;
66036
+ const block = buildCodexHooksToml(port);
63905
66037
  const final = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
63906
66038
 
63907
66039
  ${block}` : existing + (existing.endsWith("\n\n") ? "" : "\n") + block;
63908
66040
  writeFileSync23(file2, final, "utf8");
63909
66041
  console.log(`installed Codex hooks \u2192 ${file2}`);
63910
- console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
66042
+ console.log(` endpoint: http://127.0.0.1:${port}/api/hook (+ /api/turn on PostToolUse)`);
63911
66043
  console.log("");
63912
66044
  console.log(` Codex reads files via the shell, so the virtual graph surface`);
63913
66045
  console.log(` works by \`cat .errata/g/<verb>/<seed>\` (e.g. cat .errata/g/burst/foo).`);
66046
+ console.log("");
66047
+ console.log(` Codex has no Stop/SessionEnd hook at this installed version, so`);
66048
+ console.log(` [!\u2026]/(fix:[\u2026]) tags are harvested via the PostToolUse-triggered`);
66049
+ console.log(` /api/turn call above instead \u2014 every turn with a tool call is`);
66050
+ console.log(` covered; a tool-less final turn is picked up on this workspace's`);
66051
+ console.log(` next daemon boot replay. Re-run this after a Codex upgrade in`);
66052
+ console.log(` case a newer version adds a real turn-boundary hook.`);
63914
66053
  console.log(` If [features] hooks=false in your config.toml, set it true. Hook`);
63915
66054
  console.log(` schemas evolve \u2014 if hooks stop firing after an update, re-run this.`);
63916
66055
  }
@@ -64413,6 +66552,19 @@ async function cmdNotifications(state) {
64413
66552
  saveConfig(cfg);
64414
66553
  console.log(`desktop notifications ${cfg.notifications ? "enabled" : "disabled"}`);
64415
66554
  }
66555
+ async function cmdOpsNotices(state) {
66556
+ const s = state.toLowerCase();
66557
+ if (s !== "on" && s !== "off") {
66558
+ console.error("usage: errata ops-notices <on|off>");
66559
+ process.exit(2);
66560
+ }
66561
+ const cfg = loadConfig();
66562
+ cfg.opsNotices = s === "on";
66563
+ saveConfig(cfg);
66564
+ console.log(
66565
+ `ops health notices ${cfg.opsNotices ? "enabled \u2014 distress conditions will toast and reach the agent context" : "disabled \u2014 detection and `errata status` still run"}`
66566
+ );
66567
+ }
64416
66568
  async function cmdFeedback(args2) {
64417
66569
  const cfg = loadConfig();
64418
66570
  if (!hasCloudCredential(cfg)) {