@inerrata-corporation/errata 2.0.0-dev.21 → 2.0.0-dev.23

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.
@@ -15847,11 +15847,26 @@ function recordTriageObservation(l2, obs, ts) {
15847
15847
  const perContext = {
15848
15848
  ...existing?.attrs["perContext"] ?? {}
15849
15849
  };
15850
+ const inferred = obs.evidence === "inferred";
15850
15851
  for (const b of buckets) {
15851
15852
  const cur = perContext[b] ?? { confirmed: 0, contributors: [] };
15852
15853
  const contribs = new Set(cur.contributors ?? []);
15853
- if (obs.contributor) contribs.add(obs.contributor);
15854
- perContext[b] = { confirmed: cur.confirmed + 1, contributors: [...contribs] };
15854
+ const inferredContribs = new Set(cur.inferredContributors ?? []);
15855
+ if (obs.contributor) {
15856
+ if (inferred) {
15857
+ if (!contribs.has(obs.contributor)) inferredContribs.add(obs.contributor);
15858
+ } else {
15859
+ inferredContribs.delete(obs.contributor);
15860
+ }
15861
+ contribs.add(obs.contributor);
15862
+ }
15863
+ perContext[b] = {
15864
+ ...cur,
15865
+ confirmed: cur.confirmed + 1,
15866
+ contributors: [...contribs],
15867
+ ...inferredContribs.size > 0 ? { inferredContributors: [...inferredContribs] } : {}
15868
+ };
15869
+ if (inferredContribs.size === 0) delete perContext[b].inferredContributors;
15855
15870
  }
15856
15871
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
15857
15872
  const attrs = {
package/errata.mjs CHANGED
@@ -790,6 +790,12 @@ function identityId(input) {
790
790
  );
791
791
  }
792
792
  }
793
+ function genericCanonicalId(prefix, content) {
794
+ return `${prefix}_${digest(content)}`.slice(0, 56);
795
+ }
796
+ function languageCanonicalId(name2) {
797
+ return genericCanonicalId("Language", { name: name2.trim().toLowerCase() });
798
+ }
793
799
  var DESIGN_PROBLEM_PREFIX, DESIGN_PROBLEM_ID_LENGTH, DIAGNOSTIC_PROBLEM_PREFIX, DIAGNOSTIC_HASH_LENGTH, TRIAGE_PREFIX, TRIAGE_ID_LENGTH;
794
800
  var init_identity = __esm({
795
801
  "../../packages/shared/src/identity.ts"() {
@@ -18795,11 +18801,26 @@ function recordTriageObservation(l2, obs, ts) {
18795
18801
  const perContext = {
18796
18802
  ...existing?.attrs["perContext"] ?? {}
18797
18803
  };
18804
+ const inferred = obs.evidence === "inferred";
18798
18805
  for (const b of buckets) {
18799
18806
  const cur = perContext[b] ?? { confirmed: 0, contributors: [] };
18800
18807
  const contribs = new Set(cur.contributors ?? []);
18801
- if (obs.contributor) contribs.add(obs.contributor);
18802
- perContext[b] = { confirmed: cur.confirmed + 1, contributors: [...contribs] };
18808
+ const inferredContribs = new Set(cur.inferredContributors ?? []);
18809
+ if (obs.contributor) {
18810
+ if (inferred) {
18811
+ if (!contribs.has(obs.contributor)) inferredContribs.add(obs.contributor);
18812
+ } else {
18813
+ inferredContribs.delete(obs.contributor);
18814
+ }
18815
+ contribs.add(obs.contributor);
18816
+ }
18817
+ perContext[b] = {
18818
+ ...cur,
18819
+ confirmed: cur.confirmed + 1,
18820
+ contributors: [...contribs],
18821
+ ...inferredContribs.size > 0 ? { inferredContributors: [...inferredContribs] } : {}
18822
+ };
18823
+ if (inferredContribs.size === 0) delete perContext[b].inferredContributors;
18803
18824
  }
18804
18825
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
18805
18826
  const attrs = {
@@ -18864,8 +18885,10 @@ function triageOf(l2, q, mathOpts) {
18864
18885
  // Cloud-merged routes carry a distinct-uploader count; local routes count
18865
18886
  // their session contributors.
18866
18887
  independent: num2.independent ?? num2.contributors?.length ?? 0,
18867
- // Corpus-induced subset — discounted in the trust ramp (T3-weighting).
18868
- inferredIndependent: num2.inferredIndependent ?? 0
18888
+ // Discounted-in-trust-ramp subset (T3-weighting): the cloud-authoritative
18889
+ // corpus count when present, else the local inferred-only contributor count
18890
+ // (ambient-paired diagnoses whose binding the daemon guessed).
18891
+ inferredIndependent: num2.inferredIndependent ?? num2.inferredContributors?.length ?? 0
18869
18892
  };
18870
18893
  }
18871
18894
  const post = triagePosterior(counts, buckets, mathOpts);
@@ -20100,14 +20123,22 @@ var init_client = __esm({
20100
20123
  return { ...summarizeIngestResult(result), result };
20101
20124
  }
20102
20125
  const merged = { runId, nodes: [], edges: [] };
20126
+ let pendingEdges = [...batch.edges];
20103
20127
  for (const nodeChunk of chunkArray(batch.nodes, INGEST_NODE_CHUNK)) {
20104
- const r = await this.ingestWire(toWirePayload({ ...batch, nodes: nodeChunk, edges: [] }, runId));
20128
+ const ids = new Set(nodeChunk.map((n) => n.id));
20129
+ const inChunk = pendingEdges.filter((e) => ids.has(e.from) && ids.has(e.to)).slice(0, MAX_EDGES_PER_PAYLOAD);
20130
+ if (inChunk.length > 0) {
20131
+ const shipped = new Set(inChunk);
20132
+ pendingEdges = pendingEdges.filter((e) => !shipped.has(e));
20133
+ }
20134
+ const r = await this.ingestWire(toWirePayload({ ...batch, nodes: nodeChunk, edges: inChunk }, runId));
20105
20135
  merged.nodes.push(...r.nodes);
20136
+ merged.edges.push(...r.edges);
20106
20137
  if (r.patternReconciliation) {
20107
20138
  merged.patternReconciliation = { ...merged.patternReconciliation, ...r.patternReconciliation };
20108
20139
  }
20109
20140
  }
20110
- for (const edgeChunk of chunkArray(batch.edges, MAX_EDGES_PER_PAYLOAD)) {
20141
+ for (const edgeChunk of chunkArray(pendingEdges, MAX_EDGES_PER_PAYLOAD)) {
20111
20142
  const r = await this.ingestWire(toWirePayload({ ...batch, nodes: [], edges: edgeChunk }, runId));
20112
20143
  merged.edges.push(...r.edges);
20113
20144
  }
@@ -22937,18 +22968,34 @@ function triageBullet(ref) {
22937
22968
  " \xB7 the CAUSE is the mechanism you'd CHANGE in the code to make it stop \u2192 (cause: the mechanism)",
22938
22969
  " Test: if the line says WHY it breaks, it's a (cause:), not a [!problem]. e.g. [!the runner",
22939
22970
  " deadlocks after a task rejects] (cause: await fn() has no try/finally, so a rejection never",
22940
- ` decrements running). New cause as prose, or (cause:[${ref}]) to cite one shown.`
22971
+ ` decrements running). New cause as prose, or (cause:[${ref}]) to cite one shown.`,
22972
+ " State the cause in the same breath as its symptom when you can. When a diagnosis",
22973
+ " UNFOLDS \u2014 symptom now, cause turns later, several problems in play \u2014 name the thread:",
22974
+ " [!#slug the symptom] once, then (cause:#slug \u2026) / (fix:#slug \u2026) bind to it exactly,",
22975
+ " however far apart. Same slug, your choice of word. Explaining a problem we SHOWED",
22976
+ " you? bind by its handle/id directly: (cause:#the-shown-handle \u2026) / (fix:#dprob_\u2026 \u2026)."
22977
+ ].join("\n");
22978
+ }
22979
+ function attemptBullet() {
22980
+ return [
22981
+ " \u2022 you're attempting an approach to a flagged problem, or an attempt didn't pan out:",
22982
+ ` \xB7 ${TAG_EXAMPLE.tried()}`,
22983
+ ` \xB7 ${TAG_EXAMPLE.failed()}`,
22984
+ " Failed attempts are knowledge \u2014 they rule out a path. Add #slug to bind to a named",
22985
+ " thread: (tried:#slug \u2026) / (failed:#slug \u2026)."
22941
22986
  ].join("\n");
22942
22987
  }
22943
22988
  function buildAgentInstruction(opts = {}) {
22944
- const signals = opts.signals ?? ["prior", "problem", "fix", "constraint", "triage"];
22989
+ const signals = opts.signals ?? ["prior", "problem", "fix", "constraint", "triage", "attempt"];
22945
22990
  const ref = opts.referent ?? "its-handle";
22946
- const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : TAG_EXAMPLE[s](ref);
22991
+ const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : s === "attempt" ? TAG_EXAMPLE.tried() : TAG_EXAMPLE[s](ref);
22947
22992
  const head2 = `We tag the priors we show you with a short handle like [${opts.handleExample ?? "chokidar-glob"}]. This is a capture protocol, not optional notes: what you DON'T tag is silently lost \u2014 the tag is the ONLY thing recorded. Whenever one of these is true, wrap it inline in your prose (no fences, no extra calls). Tag every one you state; we filter downstream, so don't self-censor or batch:`;
22948
22993
  const tail = "Mid-turn text (between tool calls) can be dropped by the harness \u2014 if a tag above appeared only mid-turn, RESTATE it in your final message of the turn; the final message always survives.";
22949
22994
  return [
22950
22995
  head2,
22951
- ...signals.map((s) => s === "triage" ? triageBullet(ref) : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`),
22996
+ ...signals.map(
22997
+ (s) => s === "triage" ? triageBullet(ref) : s === "attempt" ? attemptBullet() : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`
22998
+ ),
22952
22999
  tail
22953
23000
  ].join("\n");
22954
23001
  }
@@ -22972,14 +23019,26 @@ var init_agent_signals = __esm({
22972
23019
  // (recallForFile) pastes this next to an open Problem that has no cause yet, so the
22973
23020
  // diagnostic moment is cued when the agent is looking at the problem. Kept here so
22974
23021
  // the grammar can't drift from parseInlineTags' `(cause: …)` acceptance.
22975
- triageMint: () => `(cause: why it happens)`
23022
+ triageMint: () => `(cause: why it happens)`,
23023
+ // THREAD forms — the witness-declared symptom↔cause/fix binding. A `#id` on the
23024
+ // flag names the diagnosis thread; the same `#id` on a later verb binds to it
23025
+ // explicitly (across sentences AND turns), replacing proximity guessing. Adjacent
23026
+ // tags never need one — the id is for diagnoses that unfold over distance.
23027
+ problemThread: () => `[!#slug one line on what's wrong]`,
23028
+ causeThread: () => `(cause:#slug the mechanism)`,
23029
+ fixThread: () => `(fix:#slug what you changed)`,
23030
+ // ATTEMPT arc — what's being tried and what didn't work. A failed attempt is a
23031
+ // witnessed NEGATIVE result: it saves the next agent the same dead end.
23032
+ tried: () => `(tried: the approach you're attempting)`,
23033
+ failed: () => `(failed: what didn't work and the dead end it rules out)`
22976
23034
  };
22977
23035
  GLOSS = {
22978
23036
  prior: "a prior we showed you helped \u2014 or this work sits in a primed language/package",
22979
23037
  problem: "you hit a real problem \u2014 incidental, or the one you're fixing",
22980
23038
  fix: "your edit fixes a problem \u2014 cite the one we showed you, or just say how you fixed one you found yourself: (fix: what you changed)",
22981
23039
  constraint: "you made a design decision because requirements conflict or something's constrained \u2014 tag the tension, ESPECIALLY when your fix makes it 'only look' contradictory (a clean solution still hides a real trap the next agent needs)",
22982
- triage: "you diagnosed a bug \u2014 SPLIT the symptom (what breaks \u2192 [!\u2026]) from the cause (the mechanism you'd change \u2192 (cause:\u2026)); if the line says WHY it breaks, it's a cause, not a problem"
23040
+ triage: "you diagnosed a bug \u2014 SPLIT the symptom (what breaks \u2192 [!\u2026]) from the cause (the mechanism you'd change \u2192 (cause:\u2026)); if the line says WHY it breaks, it's a cause, not a problem",
23041
+ attempt: "you're attempting an approach, or an attempt didn't pan out \u2014 (tried: \u2026) / (failed: \u2026); a failed attempt rules out a path for the next agent"
22983
23042
  };
22984
23043
  }
22985
23044
  });
@@ -39448,12 +39507,13 @@ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "
39448
39507
  var NEG = /n['']t|\b(?:not|never|no|none|neither|nor|unrelated|irrelevant|would|might|could|if)\b/i;
39449
39508
  var CITE_GROUP_RE = /\(((?:[^()\n]|\([^()\n]*\)){1,200})\)/g;
39450
39509
  var HANDLE_RE = /\[([a-z0-9][\w-]{0,40})\]|((?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,})/gi;
39451
- var FIX_PREFIX = /^\s*fix:\s*/i;
39510
+ var FIX_PREFIX = /^\s*fix:(?:#([a-z][\w-]{0,63}))?\s*/i;
39452
39511
  var CONSTRAINT_RE = /\(\s*constraint:\s*([^()\n]{8,}?)\s*\)/gi;
39453
- var CAUSE_PREFIX = /^\s*cause:\s*/i;
39512
+ var CAUSE_PREFIX = /^\s*cause:(?:#([a-z][\w-]{0,63}))?\s*/i;
39513
+ var ATTEMPT_RE = /\(\s*(tried|failed):(?:#([a-z][\w-]{0,63}))?\s*((?:[^()\n]|\([^()\n]*\)){8,}?)\s*\)/gi;
39454
39514
  var CAUSE_TEXT_MIN = 8;
39455
39515
  var CAUSE_TEXT_MAX = 200;
39456
- var FLAG_RE = /\[([!?])\s*([^\]\n]{8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
39516
+ var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*([^\]\n]{8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
39457
39517
  var FLAG_MAX = 200;
39458
39518
  var CONSTRAINT_MIN = 8;
39459
39519
  function clauseBefore(sentence, idx) {
@@ -39479,15 +39539,18 @@ function parseInlineTags(text) {
39479
39539
  const seqStart = out2.length;
39480
39540
  const sentence = raw2.replace(
39481
39541
  /`[^`]*`/g,
39482
- (m) => /\[[!?]|\((?:fix|cause|constraint):|\(\[/.test(m) ? " " : m
39542
+ (m) => /\[[!?]|\((?:fix|cause|constraint|tried|failed):|\(\[/.test(m) ? " " : m
39483
39543
  );
39484
39544
  const sfield = sentence.replace(/\s+/g, " ").trim().slice(0, 160);
39485
39545
  CITE_GROUP_RE.lastIndex = 0;
39486
39546
  let g;
39487
39547
  while ((g = CITE_GROUP_RE.exec(sentence)) !== null) {
39488
39548
  let content = g[1];
39489
- const isFix = FIX_PREFIX.test(content);
39490
- const isCause = !isFix && CAUSE_PREFIX.test(content);
39549
+ const fixM = FIX_PREFIX.exec(content);
39550
+ const causeM = fixM ? null : CAUSE_PREFIX.exec(content);
39551
+ const isFix = fixM !== null;
39552
+ const isCause = causeM !== null;
39553
+ const verbThread = fixM?.[1] ?? causeM?.[1] ?? void 0;
39491
39554
  if (isFix) content = content.replace(FIX_PREFIX, "");
39492
39555
  else if (isCause) content = content.replace(CAUSE_PREFIX, "");
39493
39556
  if (isCause) {
@@ -39495,12 +39558,12 @@ function parseInlineTags(text) {
39495
39558
  const h2 = HANDLE_RE.exec(content);
39496
39559
  const handle2 = h2 ? h2[1] ?? h2[2] : void 0;
39497
39560
  if (handle2) {
39498
- out2.push({ kind: "triage", handle: handle2, sentence: sfield });
39561
+ out2.push({ kind: "triage", handle: handle2, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39499
39562
  } else {
39500
39563
  const raw3 = content.replace(/\s+/g, " ").trim();
39501
39564
  if (raw3.length >= CAUSE_TEXT_MIN) {
39502
39565
  const causeText = raw3.length > CAUSE_TEXT_MAX ? `${raw3.slice(0, CAUSE_TEXT_MAX - 1).trimEnd()}\u2026` : raw3;
39503
- out2.push({ kind: "triage", causeText, sentence: sfield });
39566
+ out2.push({ kind: "triage", causeText, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39504
39567
  }
39505
39568
  }
39506
39569
  continue;
@@ -39522,33 +39585,52 @@ function parseInlineTags(text) {
39522
39585
  kind: isFix ? "fix" : "prior",
39523
39586
  handle: handle2,
39524
39587
  sentence: sfield,
39525
- ...fixRationale ? { fixRationale } : {}
39588
+ ...fixRationale ? { fixRationale } : {},
39589
+ ...isFix && verbThread ? { threadId: verbThread } : {}
39526
39590
  });
39527
39591
  }
39528
39592
  }
39529
39593
  if (isFix && !emittedHandle) {
39530
39594
  const note = content.replace(/\s+/g, " ").trim();
39531
- if (note.length >= FIX_RATIONALE_MIN) out2.push({ kind: "fix", fixRationale: note, sentence: sfield });
39595
+ if (note.length >= FIX_RATIONALE_MIN)
39596
+ out2.push({ kind: "fix", fixRationale: note, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39532
39597
  }
39533
39598
  }
39534
39599
  FLAG_RE.lastIndex = 0;
39535
39600
  let f;
39536
39601
  while ((f = FLAG_RE.exec(sentence)) !== null) {
39537
- const raw3 = f[2].trim();
39602
+ const flagThread = f[2];
39603
+ const raw3 = f[3].trim();
39538
39604
  if (raw3.length >= 8) {
39539
39605
  const statement = raw3.length > FLAG_MAX ? `${raw3.slice(0, FLAG_MAX - 1).trimEnd()}\u2026` : raw3;
39540
39606
  const asFix = f[1] === "!" && /^(?:fixed|resolved)\s*[:,-]\s*/i.exec(statement);
39541
39607
  if (asFix) {
39542
39608
  const note = statement.slice(asFix[0].length).trim();
39543
- if (note.length >= FIX_RATIONALE_MIN) out2.push({ kind: "fix", fixRationale: note, sentence: sfield });
39609
+ if (note.length >= FIX_RATIONALE_MIN)
39610
+ out2.push({ kind: "fix", fixRationale: note, sentence: sfield, ...flagThread ? { threadId: flagThread } : {} });
39544
39611
  continue;
39545
39612
  }
39546
- const path2 = f[3]?.trim();
39547
- const location = path2 ? { path: path2, ...f[4] ? { line: Number(f[4]) } : {} } : void 0;
39613
+ const path2 = f[4]?.trim();
39614
+ const location = path2 ? { path: path2, ...f[5] ? { line: Number(f[5]) } : {} } : void 0;
39548
39615
  out2.push({
39549
39616
  kind: f[1] === "!" ? "problem" : "todo",
39550
39617
  statement,
39551
39618
  ...location ? { location } : {},
39619
+ ...flagThread ? { threadId: flagThread } : {},
39620
+ sentence: sfield
39621
+ });
39622
+ }
39623
+ }
39624
+ ATTEMPT_RE.lastIndex = 0;
39625
+ let a;
39626
+ while ((a = ATTEMPT_RE.exec(sentence)) !== null) {
39627
+ const raw3 = a[3].replace(/\s+/g, " ").trim();
39628
+ if (raw3.length >= CONSTRAINT_MIN) {
39629
+ const statement = raw3.length > FLAG_MAX ? `${raw3.slice(0, FLAG_MAX - 1).trimEnd()}\u2026` : raw3;
39630
+ out2.push({
39631
+ kind: a[1].toLowerCase() === "tried" ? "attempt" : "failure",
39632
+ statement,
39633
+ ...a[2] ? { threadId: a[2] } : {},
39552
39634
  sentence: sfield
39553
39635
  });
39554
39636
  }
@@ -39673,24 +39755,36 @@ function harvestInlineTags(store, text, opts) {
39673
39755
  const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
39674
39756
  const mintPriors = opts.mintPriors ?? true;
39675
39757
  const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
39676
- const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], unresolvedFixes: [] };
39758
+ const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], attempts: [], unresolvedFixes: [] };
39677
39759
  const tags = parseInlineTags(text);
39678
- const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo" || t.kind === "constraint") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement })).sort((a, b) => a.seq - b.seq);
39679
- const nearestSymptom = (seq) => {
39680
- if (seq == null) return void 0;
39760
+ const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo" || t.kind === "constraint") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement, threadId: t.threadId })).sort((a, b) => a.seq - b.seq);
39761
+ const bindSymptom = (seq, threadId) => {
39762
+ if (threadId) {
39763
+ const hit = symptomSeqs.find((s) => s.threadId === threadId);
39764
+ if (hit) return { statement: hit.statement, evidence: "witnessed" };
39765
+ const cited = resolveHandle(store, threadId, opts.handleMap);
39766
+ const citedNode = cited ? store.getNode(cited) : null;
39767
+ if (citedNode) return { statement: citedNode.description, problemId: citedNode.id, evidence: "witnessed" };
39768
+ return { evidence: "witnessed" };
39769
+ }
39770
+ if (seq == null) return { evidence: "inferred" };
39681
39771
  let best;
39682
39772
  for (const s of symptomSeqs) {
39683
- if (s.seq <= seq) best = s.statement;
39773
+ if (s.seq <= seq) best = s;
39684
39774
  else break;
39685
39775
  }
39686
- return best;
39776
+ if (!best) return { evidence: "inferred" };
39777
+ if (seq - best.seq <= 1) return { statement: best.statement, evidence: "witnessed" };
39778
+ if (symptomSeqs.length === 1) return { statement: best.statement, evidence: "witnessed" };
39779
+ return { statement: best.statement, evidence: "inferred" };
39687
39780
  };
39688
39781
  for (const tag of tags) {
39689
39782
  if (tag.kind === "problem" || tag.kind === "todo") {
39690
39783
  plan.problems.push({
39691
39784
  statement: tag.statement,
39692
39785
  kind: tag.kind,
39693
- ...tag.location ? { location: tag.location } : {}
39786
+ ...tag.location ? { location: tag.location } : {},
39787
+ ...tag.threadId ? { threadId: tag.threadId } : {}
39694
39788
  });
39695
39789
  } else if (tag.kind === "constraint") {
39696
39790
  plan.problems.push({ statement: tag.statement, kind: "problem" });
@@ -39700,17 +39794,47 @@ function harvestInlineTags(store, text, opts) {
39700
39794
  if (problemId) plan.fixes.push({ problemId, ...tag.fixRationale ? { fixNote: tag.fixRationale } : {} });
39701
39795
  else plan.unresolvedFixes.push({ handle: tag.handle, ...tag.fixRationale ? { fixNote: tag.fixRationale } : {} });
39702
39796
  } else if (tag.fixRationale) {
39703
- plan.fixes.push({ inScope: true, fixNote: tag.fixRationale });
39797
+ const b = bindSymptom(tag.seq, tag.threadId);
39798
+ if (b.problemId) {
39799
+ plan.fixes.push({ problemId: b.problemId, fixNote: tag.fixRationale });
39800
+ } else if (tag.threadId && !b.statement) {
39801
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, threadId: tag.threadId });
39802
+ } else if (b.statement && b.evidence === "witnessed") {
39803
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, boundStatement: b.statement });
39804
+ } else {
39805
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, ambiguous: true });
39806
+ }
39704
39807
  }
39705
39808
  } else if (tag.kind === "triage") {
39706
- const symptomStatement = nearestSymptom(tag.seq);
39809
+ const b = bindSymptom(tag.seq, tag.threadId);
39810
+ const bound = {
39811
+ ...b.statement ? { symptomStatement: b.statement } : {},
39812
+ ...b.problemId ? { symptomProblemId: b.problemId } : {},
39813
+ ...tag.threadId ? { threadId: tag.threadId } : {},
39814
+ evidence: b.evidence
39815
+ };
39707
39816
  if (tag.handle) {
39708
39817
  const causeId = resolveHandle(store, tag.handle, opts.handleMap);
39709
39818
  const node2 = causeId ? store.getNode(causeId) : null;
39710
- if (node2) plan.triages.push({ causeId: node2.id, causeDescription: node2.description, ...symptomStatement ? { symptomStatement } : {} });
39819
+ if (node2) plan.triages.push({ causeId: node2.id, causeDescription: node2.description, ...bound });
39711
39820
  } else if (tag.causeText) {
39712
39821
  const causeId = `dcause_${digest({ statement: tag.causeText })}`.slice(0, 56);
39713
- plan.triages.push({ causeId, causeDescription: tag.causeText, ...symptomStatement ? { symptomStatement } : {} });
39822
+ plan.triages.push({ causeId, causeDescription: tag.causeText, ...bound });
39823
+ }
39824
+ } else if (tag.kind === "attempt" || tag.kind === "failure") {
39825
+ const b = bindSymptom(tag.seq, tag.threadId);
39826
+ const outcome = tag.kind === "attempt" ? "tried" : "failed";
39827
+ if (b.problemId) {
39828
+ plan.attempts.push({ note: tag.statement, outcome, problemId: b.problemId });
39829
+ } else if (tag.threadId && !b.statement) {
39830
+ plan.attempts.push({ note: tag.statement, outcome, threadId: tag.threadId });
39831
+ } else if (b.statement && b.evidence === "witnessed") {
39832
+ plan.attempts.push({
39833
+ note: tag.statement,
39834
+ outcome,
39835
+ ...tag.threadId ? { threadId: tag.threadId } : {},
39836
+ boundStatement: b.statement
39837
+ });
39714
39838
  }
39715
39839
  } else if (mintPriors && source) {
39716
39840
  const targetId = resolveHandle(store, tag.handle, opts.handleMap);
@@ -40967,7 +41091,7 @@ function maybeFlushDigests() {
40967
41091
  }
40968
41092
 
40969
41093
  // src/engine.ts
40970
- var DAEMON_VERSION = true ? "2.0.0-dev.21" : "2.0.0-alpha.0";
41094
+ var DAEMON_VERSION = true ? "2.0.0-dev.23" : "2.0.0-alpha.0";
40971
41095
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
40972
41096
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
40973
41097
  var GIT_OP_MUTE_MS = 4e3;
@@ -41391,6 +41515,7 @@ function createWorkspaceEngine(opts) {
41391
41515
  const turnCursorPath = join19(paths.configDir, "turn-cursors.json");
41392
41516
  const lastTurnUuid = loadTurnCursors(turnCursorPath);
41393
41517
  const sessionLastProblem = /* @__PURE__ */ new Map();
41518
+ const sessionThreads = /* @__PURE__ */ new Map();
41394
41519
  const harvestTexts = async (sessionId, items) => {
41395
41520
  if (items.length === 0) return;
41396
41521
  let minted = 0;
@@ -41504,6 +41629,8 @@ function createWorkspaceEngine(opts) {
41504
41629
  priorEdges += plan.priorEdges;
41505
41630
  const wf = workingFiles.get(sessionId);
41506
41631
  let inScopeProblemId;
41632
+ const threads = sessionThreads.get(sessionId) ?? /* @__PURE__ */ new Map();
41633
+ sessionThreads.set(sessionId, threads);
41507
41634
  for (const p of plan.problems) {
41508
41635
  const anchorPath = p.location?.path ?? turnFile ?? wf;
41509
41636
  if (anchorPath) {
@@ -41513,6 +41640,7 @@ function createWorkspaceEngine(opts) {
41513
41640
  minted++;
41514
41641
  sessionLastProblem.set(sessionId, dupId);
41515
41642
  inScopeProblemId = dupId;
41643
+ if (p.threadId) threads.set(p.threadId, dupId);
41516
41644
  continue;
41517
41645
  }
41518
41646
  } catch {
@@ -41527,6 +41655,7 @@ function createWorkspaceEngine(opts) {
41527
41655
  minted++;
41528
41656
  sessionLastProblem.set(sessionId, designProblemId(p.statement));
41529
41657
  inScopeProblemId = designProblemId(p.statement);
41658
+ if (p.threadId) threads.set(p.threadId, designProblemId(p.statement));
41530
41659
  if (anchorPath) {
41531
41660
  try {
41532
41661
  if (anchorProblemToWorkingFile(store, r.problemId, anchorPath, profile.id, t)) {
@@ -41543,8 +41672,16 @@ function createWorkspaceEngine(opts) {
41543
41672
  );
41544
41673
  }
41545
41674
  for (const fx of plan.fixes) {
41546
- const pid = fx.inScope ? inScopeProblemId : fx.problemId;
41547
- if (pid && resolveDesignProblemById(store, pid, fx.fixNote, t)) {
41675
+ const pid = fx.problemId ?? (fx.threadId ? threads.get(fx.threadId) : void 0) ?? (fx.boundStatement ? designProblemId(fx.boundStatement) : void 0);
41676
+ if (!pid) {
41677
+ if (fx.inScope) {
41678
+ console.warn(
41679
+ `[errata] fix tag not bound: ${fx.threadId ? `thread "#${fx.threadId}" unknown in this session` : "several candidate problems, none adjacent"} \u2014 NOT resolved (bind with (fix:#thread-id \u2026) or state it beside its [!problem])` + (fx.fixNote ? `; note: "${fx.fixNote.slice(0, 80)}"` : "")
41680
+ );
41681
+ }
41682
+ continue;
41683
+ }
41684
+ if (resolveDesignProblemById(store, pid, fx.fixNote, t)) {
41548
41685
  resolved++;
41549
41686
  const fxAnchor = turnFile ?? wf;
41550
41687
  if (fxAnchor) {
@@ -41561,9 +41698,24 @@ function createWorkspaceEngine(opts) {
41561
41698
  const fallbackId = sessionLastProblem.get(sessionId);
41562
41699
  const fallbackStatement = fallbackId ? store.getNode(fallbackId)?.description : void 0;
41563
41700
  for (const tr of plan.triages) {
41564
- const presentingStatement = tr.symptomStatement ?? fallbackStatement;
41701
+ let presentingStatement = tr.symptomStatement;
41702
+ let evidence = tr.evidence;
41703
+ if (!presentingStatement && tr.threadId) {
41704
+ const threadPid = threads.get(tr.threadId);
41705
+ const threadNode = threadPid ? store.getNode(threadPid) : void 0;
41706
+ if (threadNode) {
41707
+ presentingStatement = threadNode.description;
41708
+ } else {
41709
+ console.warn(`[errata] cause tag: thread "#${tr.threadId}" unknown in this session \u2014 falling back at inferred weight`);
41710
+ evidence = "inferred";
41711
+ }
41712
+ }
41713
+ if (!presentingStatement) {
41714
+ presentingStatement = fallbackStatement;
41715
+ evidence = "inferred";
41716
+ }
41565
41717
  if (!presentingStatement) continue;
41566
- const presentingId = designProblemId(presentingStatement);
41718
+ const presentingId = tr.symptomProblemId ?? designProblemId(presentingStatement);
41567
41719
  if (tr.causeId === presentingId) continue;
41568
41720
  try {
41569
41721
  recordTriageObservation(
@@ -41574,7 +41726,8 @@ function createWorkspaceEngine(opts) {
41574
41726
  causeId: tr.causeId,
41575
41727
  ...tr.causeDescription ? { causeDescription: tr.causeDescription } : {},
41576
41728
  context: { stack: profile.stack, domain: profile.domains[0] },
41577
- contributor: sessionId
41729
+ contributor: sessionId,
41730
+ evidence
41578
41731
  },
41579
41732
  t
41580
41733
  );
@@ -41584,6 +41737,22 @@ function createWorkspaceEngine(opts) {
41584
41737
  }
41585
41738
  }
41586
41739
  }
41740
+ for (const at of plan.attempts) {
41741
+ const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? designProblemId(at.boundStatement) : void 0);
41742
+ const node2 = pid ? store.getNode(pid) : void 0;
41743
+ if (!pid || !node2) continue;
41744
+ try {
41745
+ const journal = Array.isArray(node2.attrs["attempts"]) ? [...node2.attrs["attempts"]] : [];
41746
+ journal.push({ note: at.note, outcome: at.outcome, session: sessionId, ts: t });
41747
+ store.updateNode(pid, {
41748
+ attrs: { ...node2.attrs, attempts: journal.slice(-20) },
41749
+ // cap — newest wins
41750
+ lastUpdatedAt: t
41751
+ });
41752
+ } catch (err2) {
41753
+ console.warn("[errata] attempt journal failed:", err2);
41754
+ }
41755
+ }
41587
41756
  } catch (err2) {
41588
41757
  console.warn("[errata] inline-tag harvest failed:", err2);
41589
41758
  }
@@ -42212,6 +42381,10 @@ function buildContextIngest(store, profile, daemonVersion, ignorePatterns = [],
42212
42381
  if (ignored(n.description) || ignored(String(n.attrs["name"] ?? ""))) continue;
42213
42382
  nodes.push({
42214
42383
  ...n,
42384
+ // Language wire id = the shared `languageCanonicalId` (warming-spine
42385
+ // parity, EE-cloud-anchors) — the local `lang:<name>` id is internal.
42386
+ // Package ids are already the purl (= the cross-stratum canonicalId).
42387
+ id: label === "Language" ? languageCanonicalId(String(n.attrs["name"] ?? "").trim() || n.description) : n.id,
42215
42388
  embedding: [],
42216
42389
  attrs: label === "Package" ? {
42217
42390
  purl: n.attrs["purl"],
@@ -42441,11 +42614,34 @@ function noveltyAgainst(node2, neighbors, opts = {}) {
42441
42614
  // src/instance-ingest.ts
42442
42615
  var INSTANCE_LABELS = ["Problem", "Solution", "RootCause"];
42443
42616
  var INSTANCE_EDGES = ["CAUSED_BY", "SOLVED_BY"];
42617
+ var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON"];
42618
+ var ANCHOR_TARGET_LABELS = ["Language", "Package"];
42444
42619
  function stripCodebaseScope(scope) {
42445
42620
  const s = { ...scope ?? {} };
42446
42621
  delete s["codebase"];
42447
42622
  return s;
42448
42623
  }
42624
+ function wireContextId(n) {
42625
+ if (n.label === "Language" && n.attrs["source"] !== "cloud") {
42626
+ const name2 = String(n.attrs["name"] ?? "").trim() || n.description.trim() || n.id.replace(/^lang:/, "");
42627
+ return languageCanonicalId(name2);
42628
+ }
42629
+ return n.id;
42630
+ }
42631
+ function shareableContext(n, wireId) {
42632
+ return {
42633
+ ...n,
42634
+ id: wireId,
42635
+ embedding: [],
42636
+ attrs: n.label === "Package" ? {
42637
+ purl: n.attrs["purl"],
42638
+ name: n.attrs["name"],
42639
+ version: n.attrs["version"],
42640
+ ecosystem: n.attrs["ecosystem"],
42641
+ resolved: n.attrs["resolved"]
42642
+ } : { name: n.attrs["name"] }
42643
+ };
42644
+ }
42449
42645
  function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [], opts = {}) {
42450
42646
  const level = opts.level ?? 1;
42451
42647
  const noveltyOpts = opts.dedupCosine != null ? { dedupCosine: opts.dedupCosine } : {};
@@ -42462,6 +42658,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
42462
42658
  });
42463
42659
  const nodes = [];
42464
42660
  const seen = /* @__PURE__ */ new Set();
42661
+ const anchorSources = /* @__PURE__ */ new Set();
42465
42662
  const includedByLabel = /* @__PURE__ */ new Map();
42466
42663
  for (const label of INSTANCE_LABELS) {
42467
42664
  const ref = [];
@@ -42469,14 +42666,18 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
42469
42666
  for (const n of store.findNodesByLabel(label)) {
42470
42667
  if (seen.has(n.id)) continue;
42471
42668
  if (n.attrs["source"] === "cloud") continue;
42472
- const contributedAtSeq = n.attrs["contributedAtSeq"];
42473
- if (typeof contributedAtSeq === "number" && (n.lastReinforcedAtSeq ?? 0) <= contributedAtSeq) continue;
42474
42669
  if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
42475
42670
  if (ignored(n.description)) continue;
42671
+ const contributedAtSeq = n.attrs["contributedAtSeq"];
42672
+ if (typeof contributedAtSeq === "number" && (n.lastReinforcedAtSeq ?? 0) <= contributedAtSeq) {
42673
+ anchorSources.add(n.id);
42674
+ continue;
42675
+ }
42476
42676
  if (!noveltyAgainst(n, ref, noveltyOpts).ready) continue;
42477
42677
  ref.push(n);
42478
42678
  nodes.push(shareable(n));
42479
42679
  seen.add(n.id);
42680
+ anchorSources.add(n.id);
42480
42681
  }
42481
42682
  }
42482
42683
  for (const n of nodes) {
@@ -42484,20 +42685,50 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
42484
42685
  throw new Error(`buildInstanceIngest: refusing to ship non-instance ${n.id} (${n.label}) \u2014 C7 invariant`);
42485
42686
  }
42486
42687
  }
42487
- if (nodes.length === 0) return null;
42688
+ const anchorEdges = [];
42689
+ const contextNodes = [];
42690
+ const contextSeen = /* @__PURE__ */ new Set();
42691
+ const anchorDigests = {};
42692
+ for (const sourceId of anchorSources) {
42693
+ const targets = [];
42694
+ for (const e of store.outEdges(sourceId, [...ANCHOR_EDGES])) {
42695
+ const t = store.getNode(e.to);
42696
+ if (!t || !ANCHOR_TARGET_LABELS.includes(t.label)) continue;
42697
+ if (e.type === "OCCURS_IN" && t.label !== "Language") continue;
42698
+ if (e.type === "DEPENDS_ON" && t.label !== "Package") continue;
42699
+ if (t.label === "Package" && opts.includePackages !== true) continue;
42700
+ if (ignored(t.description) || ignored(String(t.attrs["name"] ?? ""))) continue;
42701
+ targets.push({ e, t, wireId: wireContextId(t) });
42702
+ }
42703
+ if (targets.length === 0) continue;
42704
+ const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
42705
+ if (store.getNode(sourceId)?.attrs["anchorsContributedDigest"] === dg) continue;
42706
+ anchorDigests[sourceId] = dg;
42707
+ for (const { e, t, wireId } of targets) {
42708
+ anchorEdges.push({ ...e, to: wireId, attrs: {} });
42709
+ if (t.attrs["source"] === "cloud" || contextSeen.has(wireId)) continue;
42710
+ contextSeen.add(wireId);
42711
+ contextNodes.push(shareableContext(t, wireId));
42712
+ }
42713
+ }
42714
+ if (nodes.length === 0 && anchorEdges.length === 0) return null;
42488
42715
  const edges = [];
42489
42716
  for (const id of seen) {
42490
42717
  for (const e of store.outEdges(id, [...INSTANCE_EDGES])) {
42491
42718
  if (seen.has(e.to)) edges.push({ ...e, attrs: {} });
42492
42719
  }
42493
42720
  }
42721
+ edges.push(...anchorEdges);
42494
42722
  const base = {
42495
42723
  daemonVersion,
42496
42724
  profile: { stack: profile.stack, languages: profile.languages, domains: profile.domains },
42497
- nodes,
42725
+ // Context nodes FIRST: a chunked drain (cloud-client, 25-node chunks) then
42726
+ // co-locates the few Language/Package stubs with the first instance chunk,
42727
+ // maximizing in-payload endpoint resolution.
42728
+ nodes: [...contextNodes, ...nodes],
42498
42729
  edges
42499
42730
  };
42500
- return { ...base, payloadDigest: digest(base) };
42731
+ return { ...base, payloadDigest: digest(base), anchorDigests };
42501
42732
  }
42502
42733
 
42503
42734
  // src/multi.ts
@@ -43079,7 +43310,16 @@ async function startMultiDaemon(opts = {}) {
43079
43310
  let uploaded = 0;
43080
43311
  for (const r of records) {
43081
43312
  const store = r.engine.store;
43082
- const instances = buildInstanceIngest(store, r.engine.profile, DAEMON_VERSION, ignore);
43313
+ const context = buildContextIngest(store, r.engine.profile, DAEMON_VERSION, ignore, {
43314
+ includePackages: cfg2.consent.contributePackages
43315
+ });
43316
+ if (context) {
43317
+ const res = await client.ingest(context);
43318
+ uploaded += res.accepted;
43319
+ }
43320
+ const instances = buildInstanceIngest(store, r.engine.profile, DAEMON_VERSION, ignore, {
43321
+ includePackages: cfg2.consent.contributePackages
43322
+ });
43083
43323
  if (instances) {
43084
43324
  const res = await client.ingest(instances);
43085
43325
  uploaded += res.accepted;
@@ -43089,7 +43329,7 @@ async function startMultiDaemon(opts = {}) {
43089
43329
  );
43090
43330
  for (const n of instances.nodes) {
43091
43331
  const local = store.getNode(n.id);
43092
- if (!local) continue;
43332
+ if (!local || !["Problem", "Solution", "RootCause"].includes(local.label)) continue;
43093
43333
  const cloudNodeId = cloudIdByLocal.get(n.id);
43094
43334
  store.updateNode(n.id, {
43095
43335
  attrs: {
@@ -43099,13 +43339,13 @@ async function startMultiDaemon(opts = {}) {
43099
43339
  }
43100
43340
  });
43101
43341
  }
43102
- }
43103
- const context = buildContextIngest(store, r.engine.profile, DAEMON_VERSION, ignore, {
43104
- includePackages: cfg2.consent.contributePackages
43105
- });
43106
- if (context) {
43107
- const res = await client.ingest(context);
43108
- uploaded += res.accepted;
43342
+ for (const [localId, dg] of Object.entries(instances.anchorDigests)) {
43343
+ const local = store.getNode(localId);
43344
+ if (!local) continue;
43345
+ store.updateNode(localId, {
43346
+ attrs: { ...local.attrs, anchorsContributedDigest: dg }
43347
+ });
43348
+ }
43109
43349
  }
43110
43350
  }
43111
43351
  return { uploaded };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.0-dev.21",
3
+ "version": "2.0.0-dev.23",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {