@inerrata-corporation/errata 2.0.0-dev.21 → 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) {
@@ -39479,15 +39525,18 @@ function parseInlineTags(text) {
39479
39525
  const seqStart = out2.length;
39480
39526
  const sentence = raw2.replace(
39481
39527
  /`[^`]*`/g,
39482
- (m) => /\[[!?]|\((?:fix|cause|constraint):|\(\[/.test(m) ? " " : m
39528
+ (m) => /\[[!?]|\((?:fix|cause|constraint|tried|failed):|\(\[/.test(m) ? " " : m
39483
39529
  );
39484
39530
  const sfield = sentence.replace(/\s+/g, " ").trim().slice(0, 160);
39485
39531
  CITE_GROUP_RE.lastIndex = 0;
39486
39532
  let g;
39487
39533
  while ((g = CITE_GROUP_RE.exec(sentence)) !== null) {
39488
39534
  let content = g[1];
39489
- const isFix = FIX_PREFIX.test(content);
39490
- 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;
39491
39540
  if (isFix) content = content.replace(FIX_PREFIX, "");
39492
39541
  else if (isCause) content = content.replace(CAUSE_PREFIX, "");
39493
39542
  if (isCause) {
@@ -39495,12 +39544,12 @@ function parseInlineTags(text) {
39495
39544
  const h2 = HANDLE_RE.exec(content);
39496
39545
  const handle2 = h2 ? h2[1] ?? h2[2] : void 0;
39497
39546
  if (handle2) {
39498
- out2.push({ kind: "triage", handle: handle2, sentence: sfield });
39547
+ out2.push({ kind: "triage", handle: handle2, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39499
39548
  } else {
39500
39549
  const raw3 = content.replace(/\s+/g, " ").trim();
39501
39550
  if (raw3.length >= CAUSE_TEXT_MIN) {
39502
39551
  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 });
39552
+ out2.push({ kind: "triage", causeText, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
39504
39553
  }
39505
39554
  }
39506
39555
  continue;
@@ -39522,33 +39571,52 @@ function parseInlineTags(text) {
39522
39571
  kind: isFix ? "fix" : "prior",
39523
39572
  handle: handle2,
39524
39573
  sentence: sfield,
39525
- ...fixRationale ? { fixRationale } : {}
39574
+ ...fixRationale ? { fixRationale } : {},
39575
+ ...isFix && verbThread ? { threadId: verbThread } : {}
39526
39576
  });
39527
39577
  }
39528
39578
  }
39529
39579
  if (isFix && !emittedHandle) {
39530
39580
  const note = content.replace(/\s+/g, " ").trim();
39531
- 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 } : {} });
39532
39583
  }
39533
39584
  }
39534
39585
  FLAG_RE.lastIndex = 0;
39535
39586
  let f;
39536
39587
  while ((f = FLAG_RE.exec(sentence)) !== null) {
39537
- const raw3 = f[2].trim();
39588
+ const flagThread = f[2];
39589
+ const raw3 = f[3].trim();
39538
39590
  if (raw3.length >= 8) {
39539
39591
  const statement = raw3.length > FLAG_MAX ? `${raw3.slice(0, FLAG_MAX - 1).trimEnd()}\u2026` : raw3;
39540
39592
  const asFix = f[1] === "!" && /^(?:fixed|resolved)\s*[:,-]\s*/i.exec(statement);
39541
39593
  if (asFix) {
39542
39594
  const note = statement.slice(asFix[0].length).trim();
39543
- 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 } : {} });
39544
39597
  continue;
39545
39598
  }
39546
- const path2 = f[3]?.trim();
39547
- 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;
39548
39601
  out2.push({
39549
39602
  kind: f[1] === "!" ? "problem" : "todo",
39550
39603
  statement,
39551
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] } : {},
39552
39620
  sentence: sfield
39553
39621
  });
39554
39622
  }
@@ -39673,24 +39741,36 @@ function harvestInlineTags(store, text, opts) {
39673
39741
  const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
39674
39742
  const mintPriors = opts.mintPriors ?? true;
39675
39743
  const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
39676
- const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], unresolvedFixes: [] };
39744
+ const plan = { priorEdges: 0, problems: [], fixes: [], triages: [], attempts: [], unresolvedFixes: [] };
39677
39745
  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;
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" };
39681
39757
  let best;
39682
39758
  for (const s of symptomSeqs) {
39683
- if (s.seq <= seq) best = s.statement;
39759
+ if (s.seq <= seq) best = s;
39684
39760
  else break;
39685
39761
  }
39686
- return best;
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" };
39687
39766
  };
39688
39767
  for (const tag of tags) {
39689
39768
  if (tag.kind === "problem" || tag.kind === "todo") {
39690
39769
  plan.problems.push({
39691
39770
  statement: tag.statement,
39692
39771
  kind: tag.kind,
39693
- ...tag.location ? { location: tag.location } : {}
39772
+ ...tag.location ? { location: tag.location } : {},
39773
+ ...tag.threadId ? { threadId: tag.threadId } : {}
39694
39774
  });
39695
39775
  } else if (tag.kind === "constraint") {
39696
39776
  plan.problems.push({ statement: tag.statement, kind: "problem" });
@@ -39700,17 +39780,47 @@ function harvestInlineTags(store, text, opts) {
39700
39780
  if (problemId) plan.fixes.push({ problemId, ...tag.fixRationale ? { fixNote: tag.fixRationale } : {} });
39701
39781
  else plan.unresolvedFixes.push({ handle: tag.handle, ...tag.fixRationale ? { fixNote: tag.fixRationale } : {} });
39702
39782
  } else if (tag.fixRationale) {
39703
- 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
+ }
39704
39793
  }
39705
39794
  } else if (tag.kind === "triage") {
39706
- const symptomStatement = nearestSymptom(tag.seq);
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
+ };
39707
39802
  if (tag.handle) {
39708
39803
  const causeId = resolveHandle(store, tag.handle, opts.handleMap);
39709
39804
  const node2 = causeId ? store.getNode(causeId) : null;
39710
- if (node2) plan.triages.push({ causeId: node2.id, causeDescription: node2.description, ...symptomStatement ? { symptomStatement } : {} });
39805
+ if (node2) plan.triages.push({ causeId: node2.id, causeDescription: node2.description, ...bound });
39711
39806
  } else if (tag.causeText) {
39712
39807
  const causeId = `dcause_${digest({ statement: tag.causeText })}`.slice(0, 56);
39713
- plan.triages.push({ causeId, causeDescription: tag.causeText, ...symptomStatement ? { symptomStatement } : {} });
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
+ });
39714
39824
  }
39715
39825
  } else if (mintPriors && source) {
39716
39826
  const targetId = resolveHandle(store, tag.handle, opts.handleMap);
@@ -40967,7 +41077,7 @@ function maybeFlushDigests() {
40967
41077
  }
40968
41078
 
40969
41079
  // src/engine.ts
40970
- var DAEMON_VERSION = true ? "2.0.0-dev.21" : "2.0.0-alpha.0";
41080
+ var DAEMON_VERSION = true ? "2.0.0-dev.22" : "2.0.0-alpha.0";
40971
41081
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
40972
41082
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
40973
41083
  var GIT_OP_MUTE_MS = 4e3;
@@ -41391,6 +41501,7 @@ function createWorkspaceEngine(opts) {
41391
41501
  const turnCursorPath = join19(paths.configDir, "turn-cursors.json");
41392
41502
  const lastTurnUuid = loadTurnCursors(turnCursorPath);
41393
41503
  const sessionLastProblem = /* @__PURE__ */ new Map();
41504
+ const sessionThreads = /* @__PURE__ */ new Map();
41394
41505
  const harvestTexts = async (sessionId, items) => {
41395
41506
  if (items.length === 0) return;
41396
41507
  let minted = 0;
@@ -41504,6 +41615,8 @@ function createWorkspaceEngine(opts) {
41504
41615
  priorEdges += plan.priorEdges;
41505
41616
  const wf = workingFiles.get(sessionId);
41506
41617
  let inScopeProblemId;
41618
+ const threads = sessionThreads.get(sessionId) ?? /* @__PURE__ */ new Map();
41619
+ sessionThreads.set(sessionId, threads);
41507
41620
  for (const p of plan.problems) {
41508
41621
  const anchorPath = p.location?.path ?? turnFile ?? wf;
41509
41622
  if (anchorPath) {
@@ -41513,6 +41626,7 @@ function createWorkspaceEngine(opts) {
41513
41626
  minted++;
41514
41627
  sessionLastProblem.set(sessionId, dupId);
41515
41628
  inScopeProblemId = dupId;
41629
+ if (p.threadId) threads.set(p.threadId, dupId);
41516
41630
  continue;
41517
41631
  }
41518
41632
  } catch {
@@ -41527,6 +41641,7 @@ function createWorkspaceEngine(opts) {
41527
41641
  minted++;
41528
41642
  sessionLastProblem.set(sessionId, designProblemId(p.statement));
41529
41643
  inScopeProblemId = designProblemId(p.statement);
41644
+ if (p.threadId) threads.set(p.threadId, designProblemId(p.statement));
41530
41645
  if (anchorPath) {
41531
41646
  try {
41532
41647
  if (anchorProblemToWorkingFile(store, r.problemId, anchorPath, profile.id, t)) {
@@ -41543,8 +41658,16 @@ function createWorkspaceEngine(opts) {
41543
41658
  );
41544
41659
  }
41545
41660
  for (const fx of plan.fixes) {
41546
- const pid = fx.inScope ? inScopeProblemId : fx.problemId;
41547
- 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)) {
41548
41671
  resolved++;
41549
41672
  const fxAnchor = turnFile ?? wf;
41550
41673
  if (fxAnchor) {
@@ -41561,9 +41684,24 @@ function createWorkspaceEngine(opts) {
41561
41684
  const fallbackId = sessionLastProblem.get(sessionId);
41562
41685
  const fallbackStatement = fallbackId ? store.getNode(fallbackId)?.description : void 0;
41563
41686
  for (const tr of plan.triages) {
41564
- const presentingStatement = tr.symptomStatement ?? fallbackStatement;
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";
41697
+ }
41698
+ }
41699
+ if (!presentingStatement) {
41700
+ presentingStatement = fallbackStatement;
41701
+ evidence = "inferred";
41702
+ }
41565
41703
  if (!presentingStatement) continue;
41566
- const presentingId = designProblemId(presentingStatement);
41704
+ const presentingId = tr.symptomProblemId ?? designProblemId(presentingStatement);
41567
41705
  if (tr.causeId === presentingId) continue;
41568
41706
  try {
41569
41707
  recordTriageObservation(
@@ -41574,7 +41712,8 @@ function createWorkspaceEngine(opts) {
41574
41712
  causeId: tr.causeId,
41575
41713
  ...tr.causeDescription ? { causeDescription: tr.causeDescription } : {},
41576
41714
  context: { stack: profile.stack, domain: profile.domains[0] },
41577
- contributor: sessionId
41715
+ contributor: sessionId,
41716
+ evidence
41578
41717
  },
41579
41718
  t
41580
41719
  );
@@ -41584,6 +41723,22 @@ function createWorkspaceEngine(opts) {
41584
41723
  }
41585
41724
  }
41586
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);
41740
+ }
41741
+ }
41587
41742
  } catch (err2) {
41588
41743
  console.warn("[errata] inline-tag harvest failed:", err2);
41589
41744
  }
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.22",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {