@inerrata-corporation/errata 2.0.0-dev.20 → 2.0.0-dev.22

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
@@ -18795,11 +18795,26 @@ function recordTriageObservation(l2, obs, ts) {
18795
18795
  const perContext = {
18796
18796
  ...existing?.attrs["perContext"] ?? {}
18797
18797
  };
18798
+ const inferred = obs.evidence === "inferred";
18798
18799
  for (const b of buckets) {
18799
18800
  const cur = perContext[b] ?? { confirmed: 0, contributors: [] };
18800
18801
  const contribs = new Set(cur.contributors ?? []);
18801
- if (obs.contributor) contribs.add(obs.contributor);
18802
- perContext[b] = { confirmed: cur.confirmed + 1, contributors: [...contribs] };
18802
+ const inferredContribs = new Set(cur.inferredContributors ?? []);
18803
+ if (obs.contributor) {
18804
+ if (inferred) {
18805
+ if (!contribs.has(obs.contributor)) inferredContribs.add(obs.contributor);
18806
+ } else {
18807
+ inferredContribs.delete(obs.contributor);
18808
+ }
18809
+ contribs.add(obs.contributor);
18810
+ }
18811
+ perContext[b] = {
18812
+ ...cur,
18813
+ confirmed: cur.confirmed + 1,
18814
+ contributors: [...contribs],
18815
+ ...inferredContribs.size > 0 ? { inferredContributors: [...inferredContribs] } : {}
18816
+ };
18817
+ if (inferredContribs.size === 0) delete perContext[b].inferredContributors;
18803
18818
  }
18804
18819
  const discriminator = obs.discriminator ?? existing?.attrs["discriminator"];
18805
18820
  const attrs = {
@@ -18864,8 +18879,10 @@ function triageOf(l2, q, mathOpts) {
18864
18879
  // Cloud-merged routes carry a distinct-uploader count; local routes count
18865
18880
  // their session contributors.
18866
18881
  independent: num2.independent ?? num2.contributors?.length ?? 0,
18867
- // Corpus-induced subset — discounted in the trust ramp (T3-weighting).
18868
- inferredIndependent: num2.inferredIndependent ?? 0
18882
+ // Discounted-in-trust-ramp subset (T3-weighting): the cloud-authoritative
18883
+ // corpus count when present, else the local inferred-only contributor count
18884
+ // (ambient-paired diagnoses whose binding the daemon guessed).
18885
+ inferredIndependent: num2.inferredIndependent ?? num2.inferredContributors?.length ?? 0
18869
18886
  };
18870
18887
  }
18871
18888
  const post = triagePosterior(counts, buckets, mathOpts);
@@ -22937,18 +22954,34 @@ function triageBullet(ref) {
22937
22954
  " \xB7 the CAUSE is the mechanism you'd CHANGE in the code to make it stop \u2192 (cause: the mechanism)",
22938
22955
  " Test: if the line says WHY it breaks, it's a (cause:), not a [!problem]. e.g. [!the runner",
22939
22956
  " 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.`
22957
+ ` decrements running). New cause as prose, or (cause:[${ref}]) to cite one shown.`,
22958
+ " State the cause in the same breath as its symptom when you can. When a diagnosis",
22959
+ " UNFOLDS \u2014 symptom now, cause turns later, several problems in play \u2014 name the thread:",
22960
+ " [!#slug the symptom] once, then (cause:#slug \u2026) / (fix:#slug \u2026) bind to it exactly,",
22961
+ " however far apart. Same slug, your choice of word. Explaining a problem we SHOWED",
22962
+ " you? bind by its handle/id directly: (cause:#the-shown-handle \u2026) / (fix:#dprob_\u2026 \u2026)."
22963
+ ].join("\n");
22964
+ }
22965
+ function attemptBullet() {
22966
+ return [
22967
+ " \u2022 you're attempting an approach to a flagged problem, or an attempt didn't pan out:",
22968
+ ` \xB7 ${TAG_EXAMPLE.tried()}`,
22969
+ ` \xB7 ${TAG_EXAMPLE.failed()}`,
22970
+ " Failed attempts are knowledge \u2014 they rule out a path. Add #slug to bind to a named",
22971
+ " thread: (tried:#slug \u2026) / (failed:#slug \u2026)."
22941
22972
  ].join("\n");
22942
22973
  }
22943
22974
  function buildAgentInstruction(opts = {}) {
22944
- const signals = opts.signals ?? ["prior", "problem", "fix", "constraint", "triage"];
22975
+ const signals = opts.signals ?? ["prior", "problem", "fix", "constraint", "triage", "attempt"];
22945
22976
  const ref = opts.referent ?? "its-handle";
22946
- const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : TAG_EXAMPLE[s](ref);
22977
+ const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : s === "attempt" ? TAG_EXAMPLE.tried() : TAG_EXAMPLE[s](ref);
22947
22978
  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
22979
  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
22980
  return [
22950
22981
  head2,
22951
- ...signals.map((s) => s === "triage" ? triageBullet(ref) : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`),
22982
+ ...signals.map(
22983
+ (s) => s === "triage" ? triageBullet(ref) : s === "attempt" ? attemptBullet() : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`
22984
+ ),
22952
22985
  tail
22953
22986
  ].join("\n");
22954
22987
  }
@@ -22972,14 +23005,26 @@ var init_agent_signals = __esm({
22972
23005
  // (recallForFile) pastes this next to an open Problem that has no cause yet, so the
22973
23006
  // diagnostic moment is cued when the agent is looking at the problem. Kept here so
22974
23007
  // the grammar can't drift from parseInlineTags' `(cause: …)` acceptance.
22975
- triageMint: () => `(cause: why it happens)`
23008
+ triageMint: () => `(cause: why it happens)`,
23009
+ // THREAD forms — the witness-declared symptom↔cause/fix binding. A `#id` on the
23010
+ // flag names the diagnosis thread; the same `#id` on a later verb binds to it
23011
+ // explicitly (across sentences AND turns), replacing proximity guessing. Adjacent
23012
+ // tags never need one — the id is for diagnoses that unfold over distance.
23013
+ problemThread: () => `[!#slug one line on what's wrong]`,
23014
+ causeThread: () => `(cause:#slug the mechanism)`,
23015
+ fixThread: () => `(fix:#slug what you changed)`,
23016
+ // ATTEMPT arc — what's being tried and what didn't work. A failed attempt is a
23017
+ // witnessed NEGATIVE result: it saves the next agent the same dead end.
23018
+ tried: () => `(tried: the approach you're attempting)`,
23019
+ failed: () => `(failed: what didn't work and the dead end it rules out)`
22976
23020
  };
22977
23021
  GLOSS = {
22978
23022
  prior: "a prior we showed you helped \u2014 or this work sits in a primed language/package",
22979
23023
  problem: "you hit a real problem \u2014 incidental, or the one you're fixing",
22980
23024
  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
23025
  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"
23026
+ 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",
23027
+ 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
23028
  };
22984
23029
  }
22985
23030
  });
@@ -39448,12 +39493,13 @@ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "
39448
39493
  var NEG = /n['']t|\b(?:not|never|no|none|neither|nor|unrelated|irrelevant|would|might|could|if)\b/i;
39449
39494
  var CITE_GROUP_RE = /\(((?:[^()\n]|\([^()\n]*\)){1,200})\)/g;
39450
39495
  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;
39496
+ var FIX_PREFIX = /^\s*fix:(?:#([a-z][\w-]{0,63}))?\s*/i;
39452
39497
  var CONSTRAINT_RE = /\(\s*constraint:\s*([^()\n]{8,}?)\s*\)/gi;
39453
- var CAUSE_PREFIX = /^\s*cause:\s*/i;
39498
+ var CAUSE_PREFIX = /^\s*cause:(?:#([a-z][\w-]{0,63}))?\s*/i;
39499
+ var ATTEMPT_RE = /\(\s*(tried|failed):(?:#([a-z][\w-]{0,63}))?\s*((?:[^()\n]|\([^()\n]*\)){8,}?)\s*\)/gi;
39454
39500
  var CAUSE_TEXT_MIN = 8;
39455
39501
  var CAUSE_TEXT_MAX = 200;
39456
- var FLAG_RE = /\[([!?])\s*([^\]\n]{8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
39502
+ var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*([^\]\n]{8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
39457
39503
  var FLAG_MAX = 200;
39458
39504
  var CONSTRAINT_MIN = 8;
39459
39505
  function clauseBefore(sentence, idx) {
@@ -39472,19 +39518,25 @@ function fixRationaleBefore(sentence, idx) {
39472
39518
  function parseInlineTags(text) {
39473
39519
  const out2 = [];
39474
39520
  const deFenced = text.replace(/```[\s\S]*?```/g, " ");
39521
+ let seqNo = -1;
39475
39522
  for (const raw2 of deFenced.split(/(?<=[.!?])\s+|\n+/)) {
39523
+ seqNo++;
39476
39524
  if (raw2.length > 600) continue;
39525
+ const seqStart = out2.length;
39477
39526
  const sentence = raw2.replace(
39478
39527
  /`[^`]*`/g,
39479
- (m) => /\[[!?]|\((?:fix|cause|constraint):|\(\[/.test(m) ? " " : m
39528
+ (m) => /\[[!?]|\((?:fix|cause|constraint|tried|failed):|\(\[/.test(m) ? " " : m
39480
39529
  );
39481
39530
  const sfield = sentence.replace(/\s+/g, " ").trim().slice(0, 160);
39482
39531
  CITE_GROUP_RE.lastIndex = 0;
39483
39532
  let g;
39484
39533
  while ((g = CITE_GROUP_RE.exec(sentence)) !== null) {
39485
39534
  let content = g[1];
39486
- const isFix = FIX_PREFIX.test(content);
39487
- const isCause = !isFix && CAUSE_PREFIX.test(content);
39535
+ const fixM = FIX_PREFIX.exec(content);
39536
+ const causeM = fixM ? null : CAUSE_PREFIX.exec(content);
39537
+ const isFix = fixM !== null;
39538
+ const isCause = causeM !== null;
39539
+ const verbThread = fixM?.[1] ?? causeM?.[1] ?? void 0;
39488
39540
  if (isFix) content = content.replace(FIX_PREFIX, "");
39489
39541
  else if (isCause) content = content.replace(CAUSE_PREFIX, "");
39490
39542
  if (isCause) {
@@ -39492,12 +39544,12 @@ function parseInlineTags(text) {
39492
39544
  const h2 = HANDLE_RE.exec(content);
39493
39545
  const handle2 = h2 ? h2[1] ?? h2[2] : void 0;
39494
39546
  if (handle2) {
39495
- out2.push({ kind: "triage", handle: handle2, sentence: sfield });
39547
+ out2.push({ kind: "triage", handle: handle2, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39496
39548
  } else {
39497
39549
  const raw3 = content.replace(/\s+/g, " ").trim();
39498
39550
  if (raw3.length >= CAUSE_TEXT_MIN) {
39499
39551
  const causeText = raw3.length > CAUSE_TEXT_MAX ? `${raw3.slice(0, CAUSE_TEXT_MAX - 1).trimEnd()}\u2026` : raw3;
39500
- out2.push({ kind: "triage", causeText, sentence: sfield });
39552
+ out2.push({ kind: "triage", causeText, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39501
39553
  }
39502
39554
  }
39503
39555
  continue;
@@ -39519,33 +39571,52 @@ function parseInlineTags(text) {
39519
39571
  kind: isFix ? "fix" : "prior",
39520
39572
  handle: handle2,
39521
39573
  sentence: sfield,
39522
- ...fixRationale ? { fixRationale } : {}
39574
+ ...fixRationale ? { fixRationale } : {},
39575
+ ...isFix && verbThread ? { threadId: verbThread } : {}
39523
39576
  });
39524
39577
  }
39525
39578
  }
39526
39579
  if (isFix && !emittedHandle) {
39527
39580
  const note = content.replace(/\s+/g, " ").trim();
39528
- if (note.length >= FIX_RATIONALE_MIN) out2.push({ kind: "fix", fixRationale: note, sentence: sfield });
39581
+ if (note.length >= FIX_RATIONALE_MIN)
39582
+ out2.push({ kind: "fix", fixRationale: note, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39529
39583
  }
39530
39584
  }
39531
39585
  FLAG_RE.lastIndex = 0;
39532
39586
  let f;
39533
39587
  while ((f = FLAG_RE.exec(sentence)) !== null) {
39534
- const raw3 = f[2].trim();
39588
+ const flagThread = f[2];
39589
+ const raw3 = f[3].trim();
39535
39590
  if (raw3.length >= 8) {
39536
39591
  const statement = raw3.length > FLAG_MAX ? `${raw3.slice(0, FLAG_MAX - 1).trimEnd()}\u2026` : raw3;
39537
39592
  const asFix = f[1] === "!" && /^(?:fixed|resolved)\s*[:,-]\s*/i.exec(statement);
39538
39593
  if (asFix) {
39539
39594
  const note = statement.slice(asFix[0].length).trim();
39540
- 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, ...flagThread ? { threadId: flagThread } : {} });
39541
39597
  continue;
39542
39598
  }
39543
- const path2 = f[3]?.trim();
39544
- const location = path2 ? { path: path2, ...f[4] ? { line: Number(f[4]) } : {} } : void 0;
39599
+ const path2 = f[4]?.trim();
39600
+ const location = path2 ? { path: path2, ...f[5] ? { line: Number(f[5]) } : {} } : void 0;
39545
39601
  out2.push({
39546
39602
  kind: f[1] === "!" ? "problem" : "todo",
39547
39603
  statement,
39548
39604
  ...location ? { location } : {},
39605
+ ...flagThread ? { threadId: flagThread } : {},
39606
+ sentence: sfield
39607
+ });
39608
+ }
39609
+ }
39610
+ ATTEMPT_RE.lastIndex = 0;
39611
+ let a;
39612
+ while ((a = ATTEMPT_RE.exec(sentence)) !== null) {
39613
+ const raw3 = a[3].replace(/\s+/g, " ").trim();
39614
+ if (raw3.length >= CONSTRAINT_MIN) {
39615
+ const statement = raw3.length > FLAG_MAX ? `${raw3.slice(0, FLAG_MAX - 1).trimEnd()}\u2026` : raw3;
39616
+ out2.push({
39617
+ kind: a[1].toLowerCase() === "tried" ? "attempt" : "failure",
39618
+ statement,
39619
+ ...a[2] ? { threadId: a[2] } : {},
39549
39620
  sentence: sfield
39550
39621
  });
39551
39622
  }
@@ -39559,6 +39630,7 @@ function parseInlineTags(text) {
39559
39630
  out2.push({ kind: "constraint", statement, sentence: sfield });
39560
39631
  }
39561
39632
  }
39633
+ for (let i2 = seqStart; i2 < out2.length; i2++) out2[i2].seq = seqNo;
39562
39634
  }
39563
39635
  return out2;
39564
39636
  }
@@ -39669,13 +39741,36 @@ function harvestInlineTags(store, text, opts) {
39669
39741
  const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
39670
39742
  const mintPriors = opts.mintPriors ?? true;
39671
39743
  const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
39672
- const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], unresolvedFixes: [] };
39673
- for (const tag of parseInlineTags(text)) {
39744
+ const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], attempts: [], unresolvedFixes: [] };
39745
+ const tags = parseInlineTags(text);
39746
+ 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);
39747
+ const bindSymptom = (seq, threadId) => {
39748
+ if (threadId) {
39749
+ const hit = symptomSeqs.find((s) => s.threadId === threadId);
39750
+ if (hit) return { statement: hit.statement, evidence: "witnessed" };
39751
+ const cited = resolveHandle(store, threadId, opts.handleMap);
39752
+ const citedNode = cited ? store.getNode(cited) : null;
39753
+ if (citedNode) return { statement: citedNode.description, problemId: citedNode.id, evidence: "witnessed" };
39754
+ return { evidence: "witnessed" };
39755
+ }
39756
+ if (seq == null) return { evidence: "inferred" };
39757
+ let best;
39758
+ for (const s of symptomSeqs) {
39759
+ if (s.seq <= seq) best = s;
39760
+ else break;
39761
+ }
39762
+ if (!best) return { evidence: "inferred" };
39763
+ if (seq - best.seq <= 1) return { statement: best.statement, evidence: "witnessed" };
39764
+ if (symptomSeqs.length === 1) return { statement: best.statement, evidence: "witnessed" };
39765
+ return { statement: best.statement, evidence: "inferred" };
39766
+ };
39767
+ for (const tag of tags) {
39674
39768
  if (tag.kind === "problem" || tag.kind === "todo") {
39675
39769
  plan.problems.push({
39676
39770
  statement: tag.statement,
39677
39771
  kind: tag.kind,
39678
- ...tag.location ? { location: tag.location } : {}
39772
+ ...tag.location ? { location: tag.location } : {},
39773
+ ...tag.threadId ? { threadId: tag.threadId } : {}
39679
39774
  });
39680
39775
  } else if (tag.kind === "constraint") {
39681
39776
  plan.problems.push({ statement: tag.statement, kind: "problem" });
@@ -39685,16 +39780,47 @@ function harvestInlineTags(store, text, opts) {
39685
39780
  if (problemId) plan.fixes.push({ problemId, ...tag.fixRationale ? { fixNote: tag.fixRationale } : {} });
39686
39781
  else plan.unresolvedFixes.push({ handle: tag.handle, ...tag.fixRationale ? { fixNote: tag.fixRationale } : {} });
39687
39782
  } else if (tag.fixRationale) {
39688
- plan.fixes.push({ inScope: true, fixNote: tag.fixRationale });
39783
+ const b = bindSymptom(tag.seq, tag.threadId);
39784
+ if (b.problemId) {
39785
+ plan.fixes.push({ problemId: b.problemId, fixNote: tag.fixRationale });
39786
+ } else if (tag.threadId && !b.statement) {
39787
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, threadId: tag.threadId });
39788
+ } else if (b.statement && b.evidence === "witnessed") {
39789
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, boundStatement: b.statement });
39790
+ } else {
39791
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, ambiguous: true });
39792
+ }
39689
39793
  }
39690
39794
  } else if (tag.kind === "triage") {
39795
+ const b = bindSymptom(tag.seq, tag.threadId);
39796
+ const bound = {
39797
+ ...b.statement ? { symptomStatement: b.statement } : {},
39798
+ ...b.problemId ? { symptomProblemId: b.problemId } : {},
39799
+ ...tag.threadId ? { threadId: tag.threadId } : {},
39800
+ evidence: b.evidence
39801
+ };
39691
39802
  if (tag.handle) {
39692
39803
  const causeId = resolveHandle(store, tag.handle, opts.handleMap);
39693
39804
  const node2 = causeId ? store.getNode(causeId) : null;
39694
- if (node2) plan.triages.push({ causeId: node2.id, causeDescription: node2.description });
39805
+ if (node2) plan.triages.push({ causeId: node2.id, causeDescription: node2.description, ...bound });
39695
39806
  } else if (tag.causeText) {
39696
39807
  const causeId = `dcause_${digest({ statement: tag.causeText })}`.slice(0, 56);
39697
- plan.triages.push({ causeId, causeDescription: tag.causeText });
39808
+ plan.triages.push({ causeId, causeDescription: tag.causeText, ...bound });
39809
+ }
39810
+ } else if (tag.kind === "attempt" || tag.kind === "failure") {
39811
+ const b = bindSymptom(tag.seq, tag.threadId);
39812
+ const outcome = tag.kind === "attempt" ? "tried" : "failed";
39813
+ if (b.problemId) {
39814
+ plan.attempts.push({ note: tag.statement, outcome, problemId: b.problemId });
39815
+ } else if (tag.threadId && !b.statement) {
39816
+ plan.attempts.push({ note: tag.statement, outcome, threadId: tag.threadId });
39817
+ } else if (b.statement && b.evidence === "witnessed") {
39818
+ plan.attempts.push({
39819
+ note: tag.statement,
39820
+ outcome,
39821
+ ...tag.threadId ? { threadId: tag.threadId } : {},
39822
+ boundStatement: b.statement
39823
+ });
39698
39824
  }
39699
39825
  } else if (mintPriors && source) {
39700
39826
  const targetId = resolveHandle(store, tag.handle, opts.handleMap);
@@ -40951,7 +41077,7 @@ function maybeFlushDigests() {
40951
41077
  }
40952
41078
 
40953
41079
  // src/engine.ts
40954
- var DAEMON_VERSION = true ? "2.0.0-dev.20" : "2.0.0-alpha.0";
41080
+ var DAEMON_VERSION = true ? "2.0.0-dev.22" : "2.0.0-alpha.0";
40955
41081
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
40956
41082
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
40957
41083
  var GIT_OP_MUTE_MS = 4e3;
@@ -41375,6 +41501,7 @@ function createWorkspaceEngine(opts) {
41375
41501
  const turnCursorPath = join19(paths.configDir, "turn-cursors.json");
41376
41502
  const lastTurnUuid = loadTurnCursors(turnCursorPath);
41377
41503
  const sessionLastProblem = /* @__PURE__ */ new Map();
41504
+ const sessionThreads = /* @__PURE__ */ new Map();
41378
41505
  const harvestTexts = async (sessionId, items) => {
41379
41506
  if (items.length === 0) return;
41380
41507
  let minted = 0;
@@ -41488,6 +41615,8 @@ function createWorkspaceEngine(opts) {
41488
41615
  priorEdges += plan.priorEdges;
41489
41616
  const wf = workingFiles.get(sessionId);
41490
41617
  let inScopeProblemId;
41618
+ const threads = sessionThreads.get(sessionId) ?? /* @__PURE__ */ new Map();
41619
+ sessionThreads.set(sessionId, threads);
41491
41620
  for (const p of plan.problems) {
41492
41621
  const anchorPath = p.location?.path ?? turnFile ?? wf;
41493
41622
  if (anchorPath) {
@@ -41497,6 +41626,7 @@ function createWorkspaceEngine(opts) {
41497
41626
  minted++;
41498
41627
  sessionLastProblem.set(sessionId, dupId);
41499
41628
  inScopeProblemId = dupId;
41629
+ if (p.threadId) threads.set(p.threadId, dupId);
41500
41630
  continue;
41501
41631
  }
41502
41632
  } catch {
@@ -41511,6 +41641,7 @@ function createWorkspaceEngine(opts) {
41511
41641
  minted++;
41512
41642
  sessionLastProblem.set(sessionId, designProblemId(p.statement));
41513
41643
  inScopeProblemId = designProblemId(p.statement);
41644
+ if (p.threadId) threads.set(p.threadId, designProblemId(p.statement));
41514
41645
  if (anchorPath) {
41515
41646
  try {
41516
41647
  if (anchorProblemToWorkingFile(store, r.problemId, anchorPath, profile.id, t)) {
@@ -41527,8 +41658,16 @@ function createWorkspaceEngine(opts) {
41527
41658
  );
41528
41659
  }
41529
41660
  for (const fx of plan.fixes) {
41530
- const pid = fx.inScope ? inScopeProblemId : fx.problemId;
41531
- if (pid && resolveDesignProblemById(store, pid, fx.fixNote, t)) {
41661
+ const pid = fx.problemId ?? (fx.threadId ? threads.get(fx.threadId) : void 0) ?? (fx.boundStatement ? designProblemId(fx.boundStatement) : void 0);
41662
+ if (!pid) {
41663
+ if (fx.inScope) {
41664
+ console.warn(
41665
+ `[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)}"` : "")
41666
+ );
41667
+ }
41668
+ continue;
41669
+ }
41670
+ if (resolveDesignProblemById(store, pid, fx.fixNote, t)) {
41532
41671
  resolved++;
41533
41672
  const fxAnchor = turnFile ?? wf;
41534
41673
  if (fxAnchor) {
@@ -41542,28 +41681,62 @@ function createWorkspaceEngine(opts) {
41542
41681
  }
41543
41682
  }
41544
41683
  if (opts.sharedStore && plan.triages.length > 0) {
41545
- const symptomId = sessionLastProblem.get(sessionId);
41546
- const symptom = symptomId ? store.getNode(symptomId) : void 0;
41547
- if (symptom) {
41548
- for (const tr of plan.triages) {
41549
- if (tr.causeId === symptomId) continue;
41550
- try {
41551
- recordTriageObservation(
41552
- opts.sharedStore,
41553
- {
41554
- presentingStatement: symptom.description,
41555
- causeId: tr.causeId,
41556
- ...tr.causeDescription ? { causeDescription: tr.causeDescription } : {},
41557
- context: { stack: profile.stack, domain: profile.domains[0] },
41558
- contributor: sessionId
41559
- },
41560
- t
41561
- );
41562
- triaged++;
41563
- } catch (err2) {
41564
- console.warn("[errata] inline triage route failed:", err2);
41684
+ const fallbackId = sessionLastProblem.get(sessionId);
41685
+ const fallbackStatement = fallbackId ? store.getNode(fallbackId)?.description : void 0;
41686
+ for (const tr of plan.triages) {
41687
+ let presentingStatement = tr.symptomStatement;
41688
+ let evidence = tr.evidence;
41689
+ if (!presentingStatement && tr.threadId) {
41690
+ const threadPid = threads.get(tr.threadId);
41691
+ const threadNode = threadPid ? store.getNode(threadPid) : void 0;
41692
+ if (threadNode) {
41693
+ presentingStatement = threadNode.description;
41694
+ } else {
41695
+ console.warn(`[errata] cause tag: thread "#${tr.threadId}" unknown in this session \u2014 falling back at inferred weight`);
41696
+ evidence = "inferred";
41565
41697
  }
41566
41698
  }
41699
+ if (!presentingStatement) {
41700
+ presentingStatement = fallbackStatement;
41701
+ evidence = "inferred";
41702
+ }
41703
+ if (!presentingStatement) continue;
41704
+ const presentingId = tr.symptomProblemId ?? designProblemId(presentingStatement);
41705
+ if (tr.causeId === presentingId) continue;
41706
+ try {
41707
+ recordTriageObservation(
41708
+ opts.sharedStore,
41709
+ {
41710
+ presentingStatement,
41711
+ presentingId,
41712
+ causeId: tr.causeId,
41713
+ ...tr.causeDescription ? { causeDescription: tr.causeDescription } : {},
41714
+ context: { stack: profile.stack, domain: profile.domains[0] },
41715
+ contributor: sessionId,
41716
+ evidence
41717
+ },
41718
+ t
41719
+ );
41720
+ triaged++;
41721
+ } catch (err2) {
41722
+ console.warn("[errata] inline triage route failed:", err2);
41723
+ }
41724
+ }
41725
+ }
41726
+ for (const at of plan.attempts) {
41727
+ const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? designProblemId(at.boundStatement) : void 0);
41728
+ const node2 = pid ? store.getNode(pid) : void 0;
41729
+ if (!pid || !node2) continue;
41730
+ try {
41731
+ const journal = Array.isArray(node2.attrs["attempts"]) ? [...node2.attrs["attempts"]] : [];
41732
+ journal.push({ note: at.note, outcome: at.outcome, session: sessionId, ts: t });
41733
+ store.updateNode(pid, {
41734
+ attrs: { ...node2.attrs, attempts: journal.slice(-20) },
41735
+ // cap — newest wins
41736
+ lastUpdatedAt: t
41737
+ });
41738
+ } catch (err2) {
41739
+ console.warn("[errata] attempt journal failed:", err2);
41567
41740
  }
41568
41741
  }
41569
41742
  } catch (err2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.0-dev.20",
3
+ "version": "2.0.0-dev.22",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {