@inerrata-corporation/errata 2.0.2-dev.261 → 2.0.2-dev.282

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.
Files changed (2) hide show
  1. package/errata.mjs +160 -9
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -20500,6 +20500,56 @@ function induceTriage(l2, opts) {
20500
20500
  }
20501
20501
  return report;
20502
20502
  }
20503
+ function pendingDiscriminators(l2, opts = {}) {
20504
+ const minSources = opts.minSources ?? 1;
20505
+ const minObservations = opts.minObservations ?? 2;
20506
+ const out2 = [];
20507
+ for (const triage of l2.findNodesByLabel("Triage")) {
20508
+ for (const e of l2.outEdges(triage.id, [...ROUTE_EDGE_TYPES])) {
20509
+ if (isRouterRoute(e)) continue;
20510
+ if (typeof e.attrs["discriminator"] === "string") continue;
20511
+ if (opts.onlyUnsurfaced && e.attrs["discriminatorAsked"] === true) continue;
20512
+ const perContext = e.attrs["perContext"] ?? {};
20513
+ const global2 = perContext[TRIAGE_GLOBAL_BUCKET] ?? {};
20514
+ const observations = Number(global2.confirmed ?? 0);
20515
+ const sources = new Set(global2.contributors ?? []).size;
20516
+ if (sources < minSources || observations < minObservations) continue;
20517
+ out2.push({ route: e.id, presenting: triage.description, cause: "", sources, observations, causeId: e.to });
20518
+ }
20519
+ }
20520
+ out2.sort((a, b) => b.sources - a.sources || b.observations - a.observations);
20521
+ const top = typeof opts.limit === "number" ? out2.slice(0, opts.limit) : out2;
20522
+ for (const p of top) p.cause = l2.getNode(p.causeId)?.description ?? "(missing cause)";
20523
+ return top;
20524
+ }
20525
+ function markDiscriminatorAsked(l2, routeIds, ts) {
20526
+ for (const id of routeIds) {
20527
+ const e = l2.getEdge(id);
20528
+ if (!e) continue;
20529
+ l2.updateEdge(id, { attrs: { ...e.attrs, discriminatorAsked: true }, lastSeenAt: ts });
20530
+ }
20531
+ }
20532
+ function promoteRouteWithDiscriminator(l2, routeId, discriminator, ts, source) {
20533
+ const test = discriminator.trim();
20534
+ if (!test) return false;
20535
+ const existing = l2.getEdge(routeId);
20536
+ if (!existing || !ROUTE_EDGE_TYPES.includes(existing.type)) return false;
20537
+ const attrs = {
20538
+ ...existing.attrs,
20539
+ discriminator: test,
20540
+ provisional: false,
20541
+ ...source ? { discriminatorSource: source } : {}
20542
+ };
20543
+ delete attrs["discriminatorAsked"];
20544
+ const wantType = routeTypeFor(false);
20545
+ if (existing.type === wantType) {
20546
+ l2.updateEdge(routeId, { attrs, lastSeenAt: ts });
20547
+ return true;
20548
+ }
20549
+ l2.deleteEdge(routeId);
20550
+ l2.mergeEdge({ ...existing, type: wantType, attrs, lastSeenAt: ts });
20551
+ return true;
20552
+ }
20503
20553
  function revisitStaleRoutes(l2, ts, opts = {}) {
20504
20554
  const minNavFailures = opts.minNavFailures ?? 3;
20505
20555
  const report = { flagged: 0, demoted: 0 };
@@ -20560,15 +20610,20 @@ function parseTriageFences(text) {
20560
20610
  while ((m = block.exec(text)) !== null) {
20561
20611
  const fields = {};
20562
20612
  for (const line of m[1].split(/\r?\n/)) {
20563
- const kv = /^\s*(presenting|cause|test|fix|drift)\s*:\s*(.+?)\s*$/i.exec(line);
20613
+ const kv = /^\s*(presenting|cause|test|fix|drift|route)\s*:\s*(.+?)\s*$/i.exec(line);
20564
20614
  if (kv) fields[kv[1].toLowerCase()] = kv[2].trim();
20565
20615
  }
20616
+ if (fields["route"] && fields["test"]) {
20617
+ out2.push({ presenting: "", cause: "", route: fields["route"], test: fields["test"] });
20618
+ continue;
20619
+ }
20566
20620
  if (!fields["presenting"] || !fields["cause"]) continue;
20567
20621
  const drift = fields["drift"]?.toLowerCase();
20568
20622
  out2.push({
20569
20623
  presenting: fields["presenting"],
20570
20624
  cause: fields["cause"],
20571
20625
  ...fields["test"] ? { test: fields["test"] } : {},
20626
+ ...fields["route"] ? { route: fields["route"] } : {},
20572
20627
  ...fields["fix"] ? { fix: fields["fix"] } : {},
20573
20628
  ...drift && DRIFT_VALUES.has(drift) ? { drift } : {}
20574
20629
  });
@@ -20577,7 +20632,12 @@ function parseTriageFences(text) {
20577
20632
  }
20578
20633
  function harvestTriageFences(l2, text, opts) {
20579
20634
  let recorded = 0;
20635
+ let promoted = 0;
20580
20636
  for (const f of parseTriageFences(text)) {
20637
+ if (f.route && f.test) {
20638
+ if (promoteRouteWithDiscriminator(l2, f.route.trim(), f.test, opts.ts, opts.contributor)) promoted++;
20639
+ continue;
20640
+ }
20581
20641
  const cause = f.cause.trim();
20582
20642
  const causeId = `dcause_${digest({ statement: cause })}`.slice(0, 56);
20583
20643
  recordTriageObservation(
@@ -20608,7 +20668,7 @@ function harvestTriageFences(l2, text, opts) {
20608
20668
  }
20609
20669
  recorded++;
20610
20670
  }
20611
- return { recorded };
20671
+ return { recorded, promoted };
20612
20672
  }
20613
20673
  var COOCCUR_BONUS, routeEdgeId, ROUTE_EDGE_TYPES, routeTypeFor, isRouterRoute, INDUCTED_MARK, DRIFT_VALUES;
20614
20674
  var init_triage2 = __esm({
@@ -20981,6 +21041,7 @@ __export(src_exports2, {
20981
21041
  linkProblemToSymbols: () => linkProblemToSymbols,
20982
21042
  listNeedsRevisit: () => listNeedsRevisit,
20983
21043
  localEdgeViolation: () => localEdgeViolation,
21044
+ markDiscriminatorAsked: () => markDiscriminatorAsked,
20984
21045
  markRevisit: () => markRevisit,
20985
21046
  matchLanguagesInText: () => matchLanguagesInText,
20986
21047
  matchPackagesInText: () => matchPackagesInText,
@@ -20997,8 +21058,10 @@ __export(src_exports2, {
20997
21058
  parseSemver: () => parseSemver,
20998
21059
  parseTriageFences: () => parseTriageFences,
20999
21060
  pendingAbstractions: () => pendingAbstractions,
21061
+ pendingDiscriminators: () => pendingDiscriminators,
21000
21062
  percolate: () => percolate,
21001
21063
  priorsForFile: () => priorsForFile,
21064
+ promoteRouteWithDiscriminator: () => promoteRouteWithDiscriminator,
21002
21065
  propagateFactChange: () => propagateFactChange,
21003
21066
  propagateVersionChange: () => propagateVersionChange,
21004
21067
  rankToolsForHandles: () => rankToolsForHandles,
@@ -36969,6 +37032,36 @@ function takeEnrichmentNudge(store) {
36969
37032
  return `\u26A1 errata \u2014 ${pulls.length} recent fix(es) await a one-line root cause (optional, answer in-flow):
36970
37033
  ` + items;
36971
37034
  }
37035
+ function takeDiscriminatorNudge(path2) {
37036
+ if (!path2 || !existsSync10(path2)) return null;
37037
+ let shared;
37038
+ try {
37039
+ shared = openGraphStore({ path: path2 });
37040
+ } catch {
37041
+ return null;
37042
+ }
37043
+ try {
37044
+ const pending = pendingDiscriminators(shared, { limit: 1, onlyUnsurfaced: true });
37045
+ const top = pending[0];
37046
+ if (!top) return null;
37047
+ markDiscriminatorAsked(shared, [top.route], Date.now());
37048
+ return [
37049
+ `\u26A1 errata \u2014 a known wrong-door is one field from ranking: "${top.presenting.slice(0, 90)}"`,
37050
+ ` usually turns out to be: ${top.cause.slice(0, 90)} (seen ${top.observations}x)`,
37051
+ " Know a CHEAP check that confirms it? Answer in-flow \u2014 it promotes the route so the",
37052
+ " next agent gets it first. Only if you actually know one: a guessed test ranks first",
37053
+ " and misroutes everyone after you.",
37054
+ " ```errata-triage",
37055
+ ` route: ${top.route}`,
37056
+ " test: <the cheap check>",
37057
+ " ```"
37058
+ ].join("\n");
37059
+ } catch {
37060
+ return null;
37061
+ } finally {
37062
+ shared.close();
37063
+ }
37064
+ }
36972
37065
  function commentAnchorId(store, commentId) {
36973
37066
  const out2 = store.outEdges(commentId, ["EXPLAINS", "ANNOTATES"]);
36974
37067
  if (out2.length > 0) return out2[0].to;
@@ -37176,6 +37269,7 @@ function createMcpHandler(store, ctx = {}) {
37176
37269
  let auditReadRun = 0;
37177
37270
  let toolCallIndex = 0;
37178
37271
  let lastAuditNudgeAt = Number.NEGATIVE_INFINITY;
37272
+ let lastDiscriminatorNudgeAt = Number.NEGATIVE_INFINITY;
37179
37273
  return async function handle2(req) {
37180
37274
  const id = req.id ?? null;
37181
37275
  const isNotification = req.id === void 0;
@@ -37222,6 +37316,7 @@ function createMcpHandler(store, ctx = {}) {
37222
37316
  { type: "text", text: JSON.stringify(out2, null, 2) }
37223
37317
  ];
37224
37318
  const isEnrichmentTool = params.name === "errata.pending_enrichment" || params.name === "errata.annotate_resolution";
37319
+ const isTriageTool = params.name === "errata.pending_discriminators" || params.name === "errata.triage";
37225
37320
  toolCallIndex++;
37226
37321
  auditReadRun = isEnrichmentTool ? 0 : auditReadRun + 1;
37227
37322
  if (!isEnrichmentTool) {
@@ -37233,6 +37328,13 @@ function createMcpHandler(store, ctx = {}) {
37233
37328
  lastAuditNudgeAt = toolCallIndex;
37234
37329
  auditReadRun = 0;
37235
37330
  }
37331
+ if (!isTriageTool && content.length === 1 && toolCallIndex - lastDiscriminatorNudgeAt >= DISCRIMINATOR_NUDGE_COOLDOWN) {
37332
+ const disc = takeDiscriminatorNudge(ctx.sharedDbPath);
37333
+ if (disc) {
37334
+ content.push({ type: "text", text: disc });
37335
+ lastDiscriminatorNudgeAt = toolCallIndex;
37336
+ }
37337
+ }
37236
37338
  return { jsonrpc: "2.0", id, result: { content } };
37237
37339
  }
37238
37340
  default:
@@ -37282,7 +37384,11 @@ async function runMcpServer(workspaceRoot) {
37282
37384
  }
37283
37385
  return summariesMemo.map;
37284
37386
  };
37285
- const handle2 = createMcpHandler(store, { ...buildToolContext(), symbolSummaries });
37387
+ const handle2 = createMcpHandler(store, {
37388
+ ...buildToolContext(),
37389
+ symbolSummaries,
37390
+ sharedDbPath: sharedStorePath()
37391
+ });
37286
37392
  process.stdin.setEncoding("utf8");
37287
37393
  let buffer = "";
37288
37394
  process.stdin.on("data", (chunk) => {
@@ -37310,7 +37416,7 @@ async function runMcpServer(workspaceRoot) {
37310
37416
  if (resp) process.stdout.write(JSON.stringify(resp) + "\n");
37311
37417
  }
37312
37418
  }
37313
- var INSTRUCTIONS, AUDIT_RUN_THRESHOLD, AUDIT_NUDGE_COOLDOWN, AUDIT_FLAG_NUDGE, TOOLS;
37419
+ var INSTRUCTIONS, AUDIT_RUN_THRESHOLD, AUDIT_NUDGE_COOLDOWN, DISCRIMINATOR_NUDGE_COOLDOWN, AUDIT_FLAG_NUDGE, TOOLS;
37314
37420
  var init_mcp = __esm({
37315
37421
  "src/mcp.ts"() {
37316
37422
  "use strict";
@@ -37371,6 +37477,7 @@ var init_mcp = __esm({
37371
37477
  ].join("\n");
37372
37478
  AUDIT_RUN_THRESHOLD = 5;
37373
37479
  AUDIT_NUDGE_COOLDOWN = 12;
37480
+ DISCRIMINATOR_NUDGE_COOLDOWN = 40;
37374
37481
  AUDIT_FLAG_NUDGE = "\u26A1 errata \u2014 reading/auditing? anything you notice that's off, flag it inline as `[!one line]` (`[?\u2026]` = TODO) \u2014 no tool call, we harvest it.";
37375
37482
  TOOLS = [
37376
37483
  {
@@ -38396,6 +38503,47 @@ var init_mcp = __esm({
38396
38503
  }
38397
38504
  }
38398
38505
  },
38506
+ {
38507
+ name: "errata.pending_discriminators",
38508
+ description: "Routes awaiting the ONE field that promotes them to routers (PLAN_TRIAGE). A route is a known wrong-door \u2014 symptom S usually turns out to be cause C \u2014 but it only ranks as a ROUTER once someone supplies its `discriminator`: the cheap ante-hoc test that confirms C before you commit. Returns provisional routes corroborated by 2+ DISTINCT CONTRIBUTORS, most-corroborated first; these are one field away from ranking every future `errata.triage` on that symptom. Answer only where you actually know a cheap check \u2014 a guessed test is worse than none, because it ranks first and sends the next agent down it. Empty = nothing awaiting. Reads the machine-wide shared store.",
38509
+ inputSchema: {
38510
+ type: "object",
38511
+ properties: {
38512
+ limit: { type: "number", description: "Max routes to return (default 10)." },
38513
+ minSources: { type: "number", description: "Min distinct contributors behind the route (default 2)." }
38514
+ }
38515
+ },
38516
+ handler: (args2) => {
38517
+ const path2 = sharedStorePath();
38518
+ if (!existsSync10(path2)) return { count: 0, pending: [] };
38519
+ const shared = openGraphStore({ path: path2 });
38520
+ try {
38521
+ const pending = pendingDiscriminators(shared, {
38522
+ limit: typeof args2?.limit === "number" ? args2.limit : 10,
38523
+ ...typeof args2?.minSources === "number" ? { minSources: args2.minSources } : {}
38524
+ });
38525
+ return {
38526
+ count: pending.length,
38527
+ pending,
38528
+ ...pending.length > 0 ? {
38529
+ howToAnswer: [
38530
+ "For each route you know a cheap test for, emit an errata-triage fence",
38531
+ "carrying its `route` id and the `test` \u2014 no presenting/cause needed, the",
38532
+ "route id identifies both endpoints. Attaching a test does NOT count as",
38533
+ "another sighting, so it cannot inflate the route's evidence.",
38534
+ "",
38535
+ "```errata-triage",
38536
+ "route: <route id from above>",
38537
+ "test: <the cheap check that confirms this cause>",
38538
+ "```"
38539
+ ].join("\n")
38540
+ } : {}
38541
+ };
38542
+ } finally {
38543
+ shared.close();
38544
+ }
38545
+ }
38546
+ },
38399
38547
  {
38400
38548
  name: "errata.triage",
38401
38549
  description: "Before you chase a problem, TRIAGE it \u2014 is it a known wrong-door? (PLAN_TRIAGE). Pass the problem you're about to work on (`problem`: a one-line statement, or `id`: a dprob_\u2026 id) and YOUR context (`stack`, `domain`) so the probabilities are conditioned on it \u2014 a symptom routes to different causes on different stacks. Returns the differential: candidate causes ranked router-first then by context-conditioned probability \u2014 {route, cause:{description}, probability, sampleSize, trust, router, discriminator, solutions:[\u2026]}. `probability` is P(cause | symptom, your context); `trust`/`sampleSize` tell you how much evidence stands behind it. Run the top router's `discriminator` (a cheap check) before committing. Empty = not a known wrong-door. Reads the machine-wide shared store.",
@@ -52664,7 +52812,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
52664
52812
  }
52665
52813
 
52666
52814
  // src/engine.ts
52667
- var DAEMON_VERSION = true ? "2.0.2-dev.261" : "2.0.0-alpha.0";
52815
+ var DAEMON_VERSION = true ? "2.0.2-dev.282" : "2.0.0-alpha.0";
52668
52816
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
52669
52817
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
52670
52818
  var GIT_OP_MUTE_MS = 4e3;
@@ -54537,8 +54685,8 @@ function contributedWireId(n) {
54537
54685
  return isMembraneSaltedId(cloudId) ? cloudId : n.id;
54538
54686
  }
54539
54687
  var WIRE_CANONICAL_ID_MIN = 6;
54540
- function unshippableReason(n) {
54541
- const canonical = typeof n.attrs?.["canonicalId"] === "string" ? n.attrs["canonicalId"] : n.id;
54688
+ function unshippableReason(canonical, label) {
54689
+ void label;
54542
54690
  if (canonical.length < WIRE_CANONICAL_ID_MIN) {
54543
54691
  return `canonicalId "${canonical}" is ${canonical.length} chars; the door requires >= ${WIRE_CANONICAL_ID_MIN}`;
54544
54692
  }
@@ -54552,6 +54700,9 @@ function wireContextId(n) {
54552
54700
  if (n.label === "Domain" && n.attrs["source"] !== "cloud") {
54553
54701
  return String(n.attrs["canonicalId"] ?? n.id);
54554
54702
  }
54703
+ if (n.label === "Component" && n.attrs["source"] !== "cloud") {
54704
+ return String(n.attrs["canonicalId"] ?? n.id);
54705
+ }
54555
54706
  return n.id;
54556
54707
  }
54557
54708
  function langAnchor(t) {
@@ -54621,7 +54772,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
54621
54772
  }
54622
54773
  if (label === "Solution" && n.description.startsWith(AUTO_MINT_PREFIX)) continue;
54623
54774
  if (!noveltyAgainst(n, ref, noveltyOpts).ready) continue;
54624
- const reject = unshippableReason(n);
54775
+ const reject = unshippableReason(n.id, n.label);
54625
54776
  if (reject) {
54626
54777
  console.warn(`[errata] skipping unshippable node ${n.id} (${n.label}): ${reject}`);
54627
54778
  continue;
@@ -54675,7 +54826,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
54675
54826
  anchorDigests[sourceId] = dg;
54676
54827
  const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode);
54677
54828
  for (const { e, t, wireId } of targets) {
54678
- const reject = unshippableReason({ id: wireId, label: t.label, attrs: t.attrs });
54829
+ const reject = unshippableReason(wireId, t.label);
54679
54830
  if (reject) {
54680
54831
  console.warn(`[errata] skipping unshippable anchor ${wireId} (${t.label}): ${reject}`);
54681
54832
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.261",
3
+ "version": "2.0.2-dev.282",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {