@inerrata-corporation/errata 2.0.20 → 2.0.21

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.
@@ -16552,6 +16552,11 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
16552
16552
  -- Problems by the file their anchor hint names: the harvest upgrades hints for
16553
16553
  -- every edited file of every turn, and a scan parses every Problem's attrs.
16554
16554
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
16555
+ -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
16556
+ -- tool response (the enrichment piggyback), so it must not parse every
16557
+ -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
16558
+ -- closed versions too.
16559
+ CREATE INDEX IF NOT EXISTS nodes_enrichment_pending ON nodes(json_extract(attrs_json, '$.enrichmentPending'));
16555
16560
  `;
16556
16561
  function encodeEmbedding(emb) {
16557
16562
  if (!emb || emb.length === 0) return null;
@@ -17209,7 +17214,10 @@ var SqliteGraphStore = class {
17209
17214
  }));
17210
17215
  }
17211
17216
  findAllVersionsByLabel(label, attribute) {
17212
- const rows = attribute ? this.db.prepare("SELECT * FROM nodes WHERE label = ? AND json_extract(attrs_json, ?) = ?").all(label, `$.${attribute.path}`, attribute.value) : this.stmts.findByLabelAll.all(label);
17217
+ if (attribute && !/^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(attribute.path)) {
17218
+ throw new Error(`invalid attr path: ${attribute.path}`);
17219
+ }
17220
+ const rows = attribute ? this.db.prepare(`SELECT * FROM nodes WHERE json_extract(attrs_json, '$.${attribute.path}') = ? AND label = ?`).all(attribute.value, label) : this.stmts.findByLabelAll.all(label);
17213
17221
  return rows.map(rowToNode);
17214
17222
  }
17215
17223
  searchNodes(opts) {
@@ -17234,6 +17242,21 @@ var SqliteGraphStore = class {
17234
17242
  const rows = this.db.prepare(sql).all(params);
17235
17243
  return rows.map(rowToNode);
17236
17244
  }
17245
+ searchDescriptionTerms(terms, limit) {
17246
+ if (terms.length === 0) return [];
17247
+ const score = terms.map(() => "(description LIKE ?)").join(" + ");
17248
+ const rows = this.db.prepare(
17249
+ `SELECT n.*, h.m AS matched
17250
+ FROM (SELECT rowid AS rid, ${score} AS m
17251
+ FROM nodes INDEXED BY nodes_live_description
17252
+ WHERE valid_to IS NULL) h
17253
+ JOIN nodes n ON n.rowid = h.rid
17254
+ WHERE h.m > 0
17255
+ ORDER BY h.m DESC, n.page_rank DESC, n.last_updated_at DESC
17256
+ LIMIT ?`
17257
+ ).all(...terms.map((t) => `%${t}%`), Math.max(1, limit));
17258
+ return rows.map((r) => ({ node: rowToNode(r), matched: Number(r.matched) }));
17259
+ }
17237
17260
  typedEdgeStmt(sql) {
17238
17261
  let stmt = this.typedEdgeStmts.get(sql);
17239
17262
  if (!stmt) {
@@ -19383,6 +19406,21 @@ function buildSymbolLexicon(store) {
19383
19406
  }
19384
19407
  return lex;
19385
19408
  }
19409
+ var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
19410
+ function codeGraphKey(store) {
19411
+ const files = store.labelFingerprint?.(["File"]);
19412
+ if (files === void 0) return null;
19413
+ return [files, ...LEXICON_SYMBOL_LABELS.map((l) => store.countNodesByLabel(l))].join("|");
19414
+ }
19415
+ var lexiconCache = /* @__PURE__ */ new WeakMap();
19416
+ function lexiconsFor(store) {
19417
+ const key = codeGraphKey(store);
19418
+ const hit = key !== null ? lexiconCache.get(store) : void 0;
19419
+ if (hit && hit.key === key) return hit;
19420
+ const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
19421
+ if (key !== null) lexiconCache.set(store, { key, ...built });
19422
+ return built;
19423
+ }
19386
19424
  var RE_RESERVED = /[.*+?^${}()|[\]\\]/g;
19387
19425
  var escapeRe2 = (s) => s.replace(RE_RESERVED, "\\$&");
19388
19426
  var RE_LEADING_ARTICLE = /^(?:an?|the)\s/i;
@@ -20126,21 +20164,6 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
20126
20164
  }
20127
20165
 
20128
20166
  // src/drain-build.ts
20129
- var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
20130
- function codeGraphKey(store) {
20131
- const files = store.labelFingerprint?.(["File"]);
20132
- if (files === void 0) return null;
20133
- return [files, ...LEXICON_SYMBOL_LABELS.map((l) => store.countNodesByLabel(l))].join("|");
20134
- }
20135
- var lexiconCache = /* @__PURE__ */ new WeakMap();
20136
- function lexiconsFor(store) {
20137
- const key = codeGraphKey(store);
20138
- const hit = key !== null ? lexiconCache.get(store) : void 0;
20139
- if (hit && hit.key === key) return hit;
20140
- const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
20141
- if (key !== null) lexiconCache.set(store, { key, ...built });
20142
- return built;
20143
- }
20144
20167
  function buildDrainContext(store, input) {
20145
20168
  return buildContextIngest(store, input.profile, input.daemonVersion, input.ignorePatterns, {
20146
20169
  ...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {}
package/errata.mjs CHANGED
@@ -137,6 +137,9 @@ var init_castalia = __esm({
137
137
  "probe-failed"
138
138
  ];
139
139
  ROUTING_REASONS = [
140
+ "trusted-system-public",
141
+ "public-target-staged",
142
+ "source-membrane",
140
143
  /** Harness org (CTF lane, e2e battery, canary) — forced org-private. */
141
144
  "harness-clamp",
142
145
  "witnessed-vulnerability-private",
@@ -18594,6 +18597,11 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
18594
18597
  -- Problems by the file their anchor hint names: the harvest upgrades hints for
18595
18598
  -- every edited file of every turn, and a scan parses every Problem's attrs.
18596
18599
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
18600
+ -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
18601
+ -- tool response (the enrichment piggyback), so it must not parse every
18602
+ -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
18603
+ -- closed versions too.
18604
+ CREATE INDEX IF NOT EXISTS nodes_enrichment_pending ON nodes(json_extract(attrs_json, '$.enrichmentPending'));
18597
18605
  `;
18598
18606
  FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
18599
18607
  NODE_LABELS_CHUNK = 256;
@@ -19194,7 +19202,10 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
19194
19202
  }));
19195
19203
  }
19196
19204
  findAllVersionsByLabel(label, attribute) {
19197
- const rows = attribute ? this.db.prepare("SELECT * FROM nodes WHERE label = ? AND json_extract(attrs_json, ?) = ?").all(label, `$.${attribute.path}`, attribute.value) : this.stmts.findByLabelAll.all(label);
19205
+ if (attribute && !/^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(attribute.path)) {
19206
+ throw new Error(`invalid attr path: ${attribute.path}`);
19207
+ }
19208
+ const rows = attribute ? this.db.prepare(`SELECT * FROM nodes WHERE json_extract(attrs_json, '$.${attribute.path}') = ? AND label = ?`).all(attribute.value, label) : this.stmts.findByLabelAll.all(label);
19198
19209
  return rows.map(rowToNode);
19199
19210
  }
19200
19211
  searchNodes(opts) {
@@ -19219,6 +19230,21 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
19219
19230
  const rows = this.db.prepare(sql).all(params);
19220
19231
  return rows.map(rowToNode);
19221
19232
  }
19233
+ searchDescriptionTerms(terms, limit) {
19234
+ if (terms.length === 0) return [];
19235
+ const score2 = terms.map(() => "(description LIKE ?)").join(" + ");
19236
+ const rows = this.db.prepare(
19237
+ `SELECT n.*, h.m AS matched
19238
+ FROM (SELECT rowid AS rid, ${score2} AS m
19239
+ FROM nodes INDEXED BY nodes_live_description
19240
+ WHERE valid_to IS NULL) h
19241
+ JOIN nodes n ON n.rowid = h.rid
19242
+ WHERE h.m > 0
19243
+ ORDER BY h.m DESC, n.page_rank DESC, n.last_updated_at DESC
19244
+ LIMIT ?`
19245
+ ).all(...terms.map((t) => `%${t}%`), Math.max(1, limit));
19246
+ return rows.map((r) => ({ node: rowToNode(r), matched: Number(r.matched) }));
19247
+ }
19222
19248
  typedEdgeStmt(sql) {
19223
19249
  let stmt = this.typedEdgeStmts.get(sql);
19224
19250
  if (!stmt) {
@@ -21681,7 +21707,11 @@ function priorsForFile(store, relPath) {
21681
21707
  const resolvedProblems = [];
21682
21708
  const seenProblem = /* @__PURE__ */ new Set();
21683
21709
  const related = /* @__PURE__ */ new Map();
21684
- for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
21710
+ const anchorEdges = [
21711
+ ...store.inEdges(file2.id, ["ANCHORED_AT"]),
21712
+ ...store.outEdges(file2.id, ["DEFINES"]).flatMap((d) => store.inEdges(d.to, ["ANCHORED_AT"]))
21713
+ ];
21714
+ for (const e of anchorEdges) {
21685
21715
  const n = store.getNode(e.from);
21686
21716
  if (!n) continue;
21687
21717
  if (n.label === "Problem" || n.label === "Vulnerability") {
@@ -21929,12 +21959,21 @@ function resolveDesignProblemByStatement(store, statement, fixNote, t) {
21929
21959
  const problemId = identityId({ kind: "DesignProblem", statement });
21930
21960
  return closeDesignProblem(store, problemId, fixNote, t);
21931
21961
  }
21962
+ function closableByCaller(node2) {
21963
+ if (node2.attrs["source"] !== "cloud") return true;
21964
+ const visibility = node2.attrs["visibility"];
21965
+ return typeof visibility === "string" && MEMBER_VISIBILITIES.has(visibility);
21966
+ }
21967
+ function isVerdictable(node2) {
21968
+ return node2.label === "Problem" && node2.attrs["resolvedAt"] == null && (node2.validTo ?? null) === null && !isConstraintProblem(node2) && node2.attrs["kind"] !== "todo";
21969
+ }
21932
21970
  function resolveDesignProblemById(store, id, fixNote, t) {
21933
21971
  return closeDesignProblem(store, id.trim(), fixNote, t);
21934
21972
  }
21935
21973
  function closeDesignProblem(store, problemId, fixNote, t) {
21936
21974
  const p = store.getNode(problemId);
21937
21975
  if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return false;
21976
+ if (!closableByCaller(p)) return false;
21938
21977
  if (p.attrs["resolvedAs"]) return false;
21939
21978
  if (fixNote?.trim() && !isFragmentFixNote(fixNote)) {
21940
21979
  const sols = store.outEdges(problemId, ["SOLVED_BY"]).map((e) => store.getNode(e.to)).filter((n) => n !== null);
@@ -22007,6 +22046,7 @@ function retractDesignProblemByStatement(store, statement, resolvedAs, reason, t
22007
22046
  function retractDesignProblem(store, problemId, resolvedAs, reason, t) {
22008
22047
  const p = store.getNode(problemId);
22009
22048
  if (!p || p.label !== "Problem" || p.attrs["resolvedAt"]) return false;
22049
+ if (!closableByCaller(p)) return false;
22010
22050
  store.updateNode(problemId, {
22011
22051
  attrs: {
22012
22052
  ...p.attrs,
@@ -22142,7 +22182,7 @@ function priorExposure(store, nodeId, atSeq) {
22142
22182
  if ((n.attrs["evictedCount"] ?? 0) > 0) return "evicted";
22143
22183
  return "unshown";
22144
22184
  }
22145
- var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, RECURRENCE_DEBOUNCE_MS, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, EMPTY_FOLD_REPORT, SAME_ANCHOR_SCOUT_AUTO_COSINE, SAME_ANCHOR_SCOUT_MIN_COSINE, SAME_ANCHOR_NOMINATE_JACCARD, REOPEN_MIN_SOURCES, RECURRENCE_SOURCES_CAP, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, ENCLOSING_SYMBOL_KINDS, ENCLOSING_WALK_DEPTH, FIX_CANDIDATE_ASK_LIMIT;
22185
+ var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, RECURRENCE_DEBOUNCE_MS, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, EMPTY_FOLD_REPORT, SAME_ANCHOR_SCOUT_AUTO_COSINE, SAME_ANCHOR_SCOUT_MIN_COSINE, SAME_ANCHOR_NOMINATE_JACCARD, REOPEN_MIN_SOURCES, RECURRENCE_SOURCES_CAP, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, ENCLOSING_SYMBOL_KINDS, ENCLOSING_WALK_DEPTH, MEMBER_VISIBILITIES, FIX_CANDIDATE_ASK_LIMIT;
22146
22186
  var init_design_problem = __esm({
22147
22187
  "../../packages/local-graph/src/design-problem.ts"() {
22148
22188
  "use strict";
@@ -22219,6 +22259,7 @@ var init_design_problem = __esm({
22219
22259
  "Property"
22220
22260
  ]);
22221
22261
  ENCLOSING_WALK_DEPTH = 8;
22262
+ MEMBER_VISIBILITIES = /* @__PURE__ */ new Set(["project", "team", "org"]);
22222
22263
  FIX_CANDIDATE_ASK_LIMIT = 5;
22223
22264
  }
22224
22265
  });
@@ -25358,6 +25399,7 @@ __export(src_exports2, {
25358
25399
  clearAnsweredRevisits: () => clearAnsweredRevisits,
25359
25400
  clearRevisit: () => clearRevisit,
25360
25401
  clearSettledFixCandidates: () => clearSettledFixCandidates,
25402
+ closableByCaller: () => closableByCaller,
25361
25403
  communityInductionRequests: () => communityInductionRequests,
25362
25404
  communitySeeds: () => communitySeeds,
25363
25405
  compareSemver: () => compareSemver,
@@ -25396,6 +25438,7 @@ __export(src_exports2, {
25396
25438
  isFragmentFixNote: () => isFragmentFixNote,
25397
25439
  isPlaceholderStatement: () => isPlaceholderStatement,
25398
25440
  isUnfilledPlaceholder: () => isUnfilledPlaceholder,
25441
+ isVerdictable: () => isVerdictable,
25399
25442
  learnedConductance: () => learnedConductance,
25400
25443
  linkProblemToLanguages: () => linkProblemToLanguages,
25401
25444
  linkProblemToPackages: () => linkProblemToPackages,
@@ -25923,6 +25966,13 @@ function renderSnapshot(s) {
25923
25966
  lines.push("_Priors that speak to it \u2014 cite one with its `[handle]` if you lean on it:_");
25924
25967
  for (const h of si.hits) {
25925
25968
  lines.push(`- **${h.description}** \u2014 \`${h.id}\``);
25969
+ if (h.verdict === "own") {
25970
+ lines.push(
25971
+ ` - still open \u2014 did this work settle it? fixed \u2192 (fix:[${h.id}]) \xB7 not a real problem \u2192 an \`errata-resolved\` block with \`id: ` + h.id + "`, `disposition: false_positive`, `reason: \u2026` \xB7 still open \u2192 say nothing"
25972
+ );
25973
+ } else if (h.verdict === "foreign") {
25974
+ lines.push(` - open in another membrane \u2014 not yours to close: (confirm:[${h.id}]) it held \xB7 (refute:[${h.id}] one line why)`);
25975
+ }
25926
25976
  }
25927
25977
  }
25928
25978
  if (si.remoteHits?.length) {
@@ -32524,7 +32574,10 @@ var init_agent_signals = __esm({
32524
32574
  // lean-on corroboration lived, because only corroboration had an inline path. Grammar
32525
32575
  // single-sourced with parseInlineTags' CONFIRM_PREFIX/REFUTE_PREFIX.
32526
32576
  confirm: (ref) => `(confirm:[${ref}])`,
32527
- refute: (ref) => `(refute:[${ref}] one line why)`
32577
+ refute: (ref) => `(refute:[${ref}] one line why)`,
32578
+ // RETRACT — "not a real problem": the errata-resolved block with a disposition
32579
+ // (parseDesignResolutions, turn.ts). No inline form exists; the block is the grammar.
32580
+ retract: (id) => ["```errata-resolved", `id: ${id}`, "disposition: false_positive", "reason: why it was never a real problem", "```"].join("\n")
32528
32581
  };
32529
32582
  TAG_OPENERS = [
32530
32583
  ...new Set(
@@ -34414,6 +34467,19 @@ function buildSymbolLexicon(store) {
34414
34467
  }
34415
34468
  return lex;
34416
34469
  }
34470
+ function codeGraphKey(store) {
34471
+ const files = store.labelFingerprint?.(["File"]);
34472
+ if (files === void 0) return null;
34473
+ return [files, ...LEXICON_SYMBOL_LABELS.map((l) => store.countNodesByLabel(l))].join("|");
34474
+ }
34475
+ function lexiconsFor(store) {
34476
+ const key = codeGraphKey(store);
34477
+ const hit = key !== null ? lexiconCache.get(store) : void 0;
34478
+ if (hit && hit.key === key) return hit;
34479
+ const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
34480
+ if (key !== null) lexiconCache.set(store, { key, ...built });
34481
+ return built;
34482
+ }
34417
34483
  function replaceWithPhrase(text, before, escapedKey, after, phrase2) {
34418
34484
  if (!RE_LEADING_ARTICLE2.test(phrase2)) {
34419
34485
  return text.replace(new RegExp(`${before}${escapedKey}${after}`, "g"), phrase2);
@@ -34579,7 +34645,7 @@ function registerRenderedContributorLabels(labels) {
34579
34645
  if (trimmed.length >= 3) renderedContributorLabels.add(trimmed);
34580
34646
  }
34581
34647
  }
34582
- var SYMBOL_LABELS2, KIND_PHRASE, BUILTIN_TYPE_NAMES, DOC_COMMENT_KINDS, MAX_DOC_CHARS, RE_RESERVED2, escapeRe4, RE_LEADING_ARTICLE2, CONVENTION_FILENAMES, CONVENTION_BASENAME_MIN_FILES, CONVENTION_STEMS, FILE_TOKEN_RE, STEM_TOKEN_RE, SOURCE_EXTENSIONS, TOKEN_RE3, renderedContributorLabels;
34648
+ var SYMBOL_LABELS2, KIND_PHRASE, BUILTIN_TYPE_NAMES, DOC_COMMENT_KINDS, MAX_DOC_CHARS, LEXICON_SYMBOL_LABELS, lexiconCache, RE_RESERVED2, escapeRe4, RE_LEADING_ARTICLE2, CONVENTION_FILENAMES, CONVENTION_BASENAME_MIN_FILES, CONVENTION_STEMS, FILE_TOKEN_RE, STEM_TOKEN_RE, SOURCE_EXTENSIONS, TOKEN_RE3, renderedContributorLabels;
34583
34649
  var init_generalize_graph = __esm({
34584
34650
  "src/generalize-graph.ts"() {
34585
34651
  "use strict";
@@ -34629,6 +34695,8 @@ var init_generalize_graph = __esm({
34629
34695
  ]);
34630
34696
  DOC_COMMENT_KINDS = /* @__PURE__ */ new Set(["docstring", "explanation"]);
34631
34697
  MAX_DOC_CHARS = 600;
34698
+ LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
34699
+ lexiconCache = /* @__PURE__ */ new WeakMap();
34632
34700
  RE_RESERVED2 = /[.*+?^${}()|[\]\\]/g;
34633
34701
  escapeRe4 = (s) => s.replace(RE_RESERVED2, "\\$&");
34634
34702
  RE_LEADING_ARTICLE2 = /^(?:an?|the)\s/i;
@@ -34837,13 +34905,12 @@ async function dualAugment(opts) {
34837
34905
  let seedProvenance = "local";
34838
34906
  const calls = [];
34839
34907
  try {
34840
- const lexicon = buildSymbolLexicon(store);
34841
34908
  const hardIds = [.../* @__PURE__ */ new Set([...seedNode ? [seedNode.id] : [], ...localHits.map((h) => h.id)])].filter((nid) => {
34842
34909
  const n = seedNode && nid === seedNode.id ? seedNode : store.getNode(nid);
34843
34910
  return !!n && isHardBridge(n);
34844
34911
  });
34845
34912
  const softText = queryText ?? (seedNode && BRIDGE_LABELS.has(seedNode.label) ? seedNode.description : void 0);
34846
- const softQ = softText ? generalizeSymbols(softText, lexicon, 1) : void 0;
34913
+ const softQ = softText ? generalizeSymbols(softText, lexiconsFor(store).symbols, 1) : void 0;
34847
34914
  if (seedNode?.label === "Package") {
34848
34915
  calls.push(
34849
34916
  cloud.packages(seedNode.id).then((pn) => {
@@ -43897,7 +43964,7 @@ __export(mcp_exports, {
43897
43964
  import { existsSync as existsSync17, readFileSync as readFileSync16 } from "node:fs";
43898
43965
  import { resolve as resolve4 } from "node:path";
43899
43966
  function collectEnrichmentPulls(store, opts = {}) {
43900
- const pending = store.findNodesByLabel("Solution").filter((s) => s.attrs["enrichmentPending"] === true).filter((s) => !opts.onlyUnsurfaced || s.attrs["enrichmentSurfaced"] !== true);
43967
+ const pending = store.findLiveNodesByAttr("Solution", "enrichmentPending", 1).filter((s) => s.attrs["enrichmentPending"] === true).filter((s) => !opts.onlyUnsurfaced || s.attrs["enrichmentSurfaced"] !== true);
43901
43968
  const byEpisode = /* @__PURE__ */ new Map();
43902
43969
  for (const s of pending) {
43903
43970
  const ep = s.attrs["episodeId"] ?? s.id;
@@ -44014,20 +44081,8 @@ function searchGraph(store, query, limit) {
44014
44081
  const terms = q.split(/\s+/).filter((t) => t.length >= 2);
44015
44082
  const pool = Math.max(limit * 3, 60);
44016
44083
  const raw2 = /* @__PURE__ */ new Map();
44017
- const collect2 = (nodes) => {
44018
- for (const n of nodes) {
44019
- const e = raw2.get(n.id);
44020
- if (e) e.matched++;
44021
- else raw2.set(n.id, { node: n, matched: 1 });
44022
- }
44023
- };
44024
- if (terms.length <= 1) {
44025
- collect2(store.searchNodes({ descriptionLike: q, limit: pool }));
44026
- } else {
44027
- for (const term of terms) {
44028
- collect2(store.searchNodes({ descriptionLike: term, limit: 200 }));
44029
- }
44030
- }
44084
+ const scored = !q ? store.searchNodes({ limit: pool }).map((node2) => ({ node: node2, matched: 1 })) : terms.length <= 1 ? store.searchDescriptionTerms([q], pool) : store.searchDescriptionTerms(terms, Math.max(pool, 600));
44085
+ for (const hit of scored) raw2.set(hit.node.id, hit);
44031
44086
  const merged = /* @__PURE__ */ new Map();
44032
44087
  const add = (node2, matched, via) => {
44033
44088
  const e = merged.get(node2.id);
@@ -53491,6 +53546,7 @@ __export(webui_exports, {
53491
53546
  asFailedPostToolUse: () => asFailedPostToolUse,
53492
53547
  buildWebUi: () => buildWebUi,
53493
53548
  decideContextInject: () => decideContextInject,
53549
+ editVerdict: () => editVerdict,
53494
53550
  errorRecallTerms: () => errorRecallTerms,
53495
53551
  fileRecall: () => fileRecall,
53496
53552
  fileUriToFsPath: () => fileUriToFsPath,
@@ -53601,6 +53657,26 @@ ${lines.join("\n")}
53601
53657
  card
53602
53658
  };
53603
53659
  }
53660
+ function editVerdict(store, relPath) {
53661
+ const priors = priorsForFile(store, relPath);
53662
+ if (!priors || priors.openProblems.length === 0) return null;
53663
+ const ranked = rankByRelevance(store, priors.file.id, priors.openProblems.map((n) => n.id));
53664
+ const rank = new Map(ranked.map((r, i2) => [r.id, i2]));
53665
+ const asked = [...priors.openProblems].sort((a, b) => (rank.get(a.id) ?? 0) - (rank.get(b.id) ?? 0)).slice(0, 3);
53666
+ const lines = asked.map(
53667
+ (p) => closableByCaller(p) ? recallLine(p, `fixed: ${TAG_EXAMPLE.fix(p.id)} \xB7 not a real problem: retract it (block below) \xB7 still open: say nothing`) : recallLine(p, `another membrane's \u2014 not yours to close: ${TAG_EXAMPLE.confirm(p.id)} it held \xB7 ${TAG_EXAMPLE.refute(p.id)}`)
53668
+ );
53669
+ const retractable = asked.find((p) => closableByCaller(p));
53670
+ const shown = asked.map(deliveredPrior);
53671
+ const headline = `verdict asked: ${plural(shown.length, "open problem")} on ${relPath.split("/").slice(-2).join("/")}`;
53672
+ return {
53673
+ context: `errata \u2014 you just edited ${relPath}; ${plural(priors.openProblems.length, "open problem")} recorded on it. Give each one below a verdict in your reply:
53674
+ ${lines.join("\n")}
53675
+ ` + (retractable ? `Retract (not a real problem):
53676
+ ${TAG_EXAMPLE.retract(retractable.id)}` : ""),
53677
+ card: deliveredCard(store, headline, shown) ?? { headline, items: [] }
53678
+ };
53679
+ }
53604
53680
  function workspacePackageIds(store) {
53605
53681
  const hit = wsPackagesCache.get(store);
53606
53682
  const now = Date.now();
@@ -53671,6 +53747,7 @@ function buildWebUi(deps) {
53671
53747
  const B = deps.basePath ?? "";
53672
53748
  const lastInjectedHash = /* @__PURE__ */ new Map();
53673
53749
  const fileRecallSeen = /* @__PURE__ */ new Map();
53750
+ const editVerdictAsked = /* @__PURE__ */ new Map();
53674
53751
  const lastSubagentInjectAt = /* @__PURE__ */ new Map();
53675
53752
  const noteSessionFrom = (headerOrigin, body2, ended = false) => {
53676
53753
  const { sessionId, agentId } = hookAudience(body2);
@@ -53935,6 +54012,27 @@ function buildWebUi(deps) {
53935
54012
  }
53936
54013
  }
53937
54014
  }
54015
+ if (event === "PostToolUse" && !recall && rel && (tool === "Edit" || tool === "MultiEdit" || tool === "Write" || tool === "NotebookEdit")) {
54016
+ const sid = hookAudience(body2).contextKey;
54017
+ let asked = editVerdictAsked.get(sid);
54018
+ if (!asked) {
54019
+ asked = /* @__PURE__ */ new Set();
54020
+ editVerdictAsked.set(sid, asked);
54021
+ }
54022
+ if (!asked.has(rel)) {
54023
+ const verdict = editVerdict(deps.store, rel);
54024
+ if (verdict) {
54025
+ asked.add(rel);
54026
+ let seen = fileRecallSeen.get(sid);
54027
+ if (!seen) {
54028
+ seen = /* @__PURE__ */ new Set();
54029
+ fileRecallSeen.set(sid, seen);
54030
+ }
54031
+ seen.add(rel);
54032
+ recall = verdict;
54033
+ }
54034
+ }
54035
+ }
53938
54036
  if (event === "PostToolUse" && !recall && rel && (tool === "Read" || tool === "Edit" || tool === "Write" || tool === "NotebookEdit")) {
53939
54037
  const sid = hookAudience(body2).contextKey;
53940
54038
  let seen = fileRecallSeen.get(sid);
@@ -82706,7 +82804,7 @@ function createWatchBreaker(deps) {
82706
82804
  }
82707
82805
 
82708
82806
  // src/engine.ts
82709
- var DAEMON_VERSION = true ? "2.0.20" : "2.0.0-alpha.0";
82807
+ var DAEMON_VERSION = true ? "2.0.21" : "2.0.0-alpha.0";
82710
82808
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
82711
82809
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
82712
82810
  var GIT_OP_MUTE_MS = 4e3;
@@ -83167,7 +83265,8 @@ function createWorkspaceEngine(opts) {
83167
83265
  id: h.node.id,
83168
83266
  label: h.node.label,
83169
83267
  description: h.node.description,
83170
- coverage: h.coverage
83268
+ coverage: h.coverage,
83269
+ ...isVerdictable(h.node) ? { verdict: closableByCaller(h.node) ? "own" : "foreign" } : {}
83171
83270
  })),
83172
83271
  ...collective && collective.hits.length > 0 ? {
83173
83272
  remoteHits: collective.hits.slice(0, 4).map((n) => ({ id: n.id, label: n.label, description: n.description }))
@@ -84176,7 +84275,11 @@ function createWorkspaceEngine(opts) {
84176
84275
  const closed = resolveDesignProblemById(store, pid, fx.fixNote, t);
84177
84276
  if (!closed) {
84178
84277
  const n = store.getNode(pid);
84179
- if (!n || n.label !== "Problem" && n.label !== "Vulnerability" || n.attrs["resolvedAs"] != null) {
84278
+ if (n && n.attrs["resolvedAt"] == null && !closableByCaller(n)) {
84279
+ console.warn(
84280
+ `[errata] fix tag not applied: ${pid.slice(0, 32)} belongs to a membrane you are not in \u2014 a verdict closes only your own problems; cite it with (confirm:[id]) or (refute:[id] why) instead`
84281
+ );
84282
+ } else if (!n || n.label !== "Problem" && n.label !== "Vulnerability" || n.attrs["resolvedAs"] != null) {
84180
84283
  console.warn(
84181
84284
  `[errata] fix tag went nowhere: ${!n ? `id ${pid.slice(0, 32)} unknown` : n.label !== "Problem" ? `${pid.slice(0, 32)} is a ${n.label}, not a Problem` : "the problem was retracted \u2014 a retraction is not fixable"} \u2014 the fix was NOT recorded`
84182
84285
  );
@@ -86732,21 +86835,7 @@ function liveBackoffMs(failures) {
86732
86835
 
86733
86836
  // src/drain-build.ts
86734
86837
  init_generalize_graph();
86735
- var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
86736
- function codeGraphKey(store) {
86737
- const files = store.labelFingerprint?.(["File"]);
86738
- if (files === void 0) return null;
86739
- return [files, ...LEXICON_SYMBOL_LABELS.map((l) => store.countNodesByLabel(l))].join("|");
86740
- }
86741
- var lexiconCache = /* @__PURE__ */ new WeakMap();
86742
- function lexiconsFor(store) {
86743
- const key = codeGraphKey(store);
86744
- const hit = key !== null ? lexiconCache.get(store) : void 0;
86745
- if (hit && hit.key === key) return hit;
86746
- const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
86747
- if (key !== null) lexiconCache.set(store, { key, ...built });
86748
- return built;
86749
- }
86838
+ init_generalize_graph();
86750
86839
  function buildDrainContext(store, input) {
86751
86840
  return buildContextIngest(store, input.profile, input.daemonVersion, input.ignorePatterns, {
86752
86841
  ...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {}
@@ -94099,7 +94188,7 @@ async function cmdInit() {
94099
94188
  }
94100
94189
  console.log("");
94101
94190
  console.log("connecting your cloud account...");
94102
- await cmdLoginOAuth(cfgNow);
94191
+ await cmdLoginOAuth(cfgNow, false, false);
94103
94192
  cfgNow = loadConfig();
94104
94193
  if (!hasCloudCredential(cfgNow)) {
94105
94194
  console.error("init requires login \u2014 re-run `errata init`, or `errata init --no-login` to stay unlinked.");
@@ -94742,7 +94831,7 @@ async function warnIfApprovalUnreachable(url2) {
94742
94831
  } catch {
94743
94832
  }
94744
94833
  }
94745
- async function cmdLoginOAuth(cfg, useBrowserLoopback = false) {
94834
+ async function cmdLoginOAuth(cfg, useBrowserLoopback = false, linkAfterLogin = true) {
94746
94835
  const inspection = await inspectCloudEndpoint(cfg.cloudUrl);
94747
94836
  try {
94748
94837
  assertCloudEndpointAllowed(cfg.cloudUrl, inspection);
@@ -94779,7 +94868,7 @@ async function cmdLoginOAuth(cfg, useBrowserLoopback = false) {
94779
94868
  nextCfg.email = me.handle;
94780
94869
  saveConfig(nextCfg);
94781
94870
  console.log(`logged in as ${me.handle} (${me.tier}) \u2014 tokens stored at ${globalConfigPath()}`);
94782
- await linkRegisteredWorkspaces(nextCfg);
94871
+ if (linkAfterLogin) await linkRegisteredWorkspaces(nextCfg);
94783
94872
  } catch (err2) {
94784
94873
  console.error(`signed in, but identity check failed: ${err2 instanceof Error ? err2.message : err2}`);
94785
94874
  console.error(` credentials were not stored; try again or paste a key: errata login --token <key>`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.20",
3
+ "version": "2.0.21",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -25581,6 +25581,11 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
25581
25581
  -- Problems by the file their anchor hint names: the harvest upgrades hints for
25582
25582
  -- every edited file of every turn, and a scan parses every Problem's attrs.
25583
25583
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
25584
+ -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
25585
+ -- tool response (the enrichment piggyback), so it must not parse every
25586
+ -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
25587
+ -- closed versions too.
25588
+ CREATE INDEX IF NOT EXISTS nodes_enrichment_pending ON nodes(json_extract(attrs_json, '$.enrichmentPending'));
25584
25589
  `;
25585
25590
  function encodeEmbedding(emb) {
25586
25591
  if (!emb || emb.length === 0) return null;
@@ -26238,7 +26243,10 @@ var SqliteGraphStore = class {
26238
26243
  }));
26239
26244
  }
26240
26245
  findAllVersionsByLabel(label, attribute) {
26241
- const rows = attribute ? this.db.prepare("SELECT * FROM nodes WHERE label = ? AND json_extract(attrs_json, ?) = ?").all(label, `$.${attribute.path}`, attribute.value) : this.stmts.findByLabelAll.all(label);
26246
+ if (attribute && !/^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(attribute.path)) {
26247
+ throw new Error(`invalid attr path: ${attribute.path}`);
26248
+ }
26249
+ const rows = attribute ? this.db.prepare(`SELECT * FROM nodes WHERE json_extract(attrs_json, '$.${attribute.path}') = ? AND label = ?`).all(attribute.value, label) : this.stmts.findByLabelAll.all(label);
26242
26250
  return rows.map(rowToNode);
26243
26251
  }
26244
26252
  searchNodes(opts) {
@@ -26263,6 +26271,21 @@ var SqliteGraphStore = class {
26263
26271
  const rows = this.db.prepare(sql).all(params);
26264
26272
  return rows.map(rowToNode);
26265
26273
  }
26274
+ searchDescriptionTerms(terms, limit) {
26275
+ if (terms.length === 0) return [];
26276
+ const score2 = terms.map(() => "(description LIKE ?)").join(" + ");
26277
+ const rows = this.db.prepare(
26278
+ `SELECT n.*, h.m AS matched
26279
+ FROM (SELECT rowid AS rid, ${score2} AS m
26280
+ FROM nodes INDEXED BY nodes_live_description
26281
+ WHERE valid_to IS NULL) h
26282
+ JOIN nodes n ON n.rowid = h.rid
26283
+ WHERE h.m > 0
26284
+ ORDER BY h.m DESC, n.page_rank DESC, n.last_updated_at DESC
26285
+ LIMIT ?`
26286
+ ).all(...terms.map((t) => `%${t}%`), Math.max(1, limit));
26287
+ return rows.map((r) => ({ node: rowToNode(r), matched: Number(r.matched) }));
26288
+ }
26266
26289
  typedEdgeStmt(sql) {
26267
26290
  let stmt = this.typedEdgeStmts.get(sql);
26268
26291
  if (!stmt) {
@@ -27197,7 +27220,11 @@ function priorsForFile(store2, relPath) {
27197
27220
  const resolvedProblems = [];
27198
27221
  const seenProblem = /* @__PURE__ */ new Set();
27199
27222
  const related = /* @__PURE__ */ new Map();
27200
- for (const e of store2.inEdges(file2.id, ["ANCHORED_AT"])) {
27223
+ const anchorEdges = [
27224
+ ...store2.inEdges(file2.id, ["ANCHORED_AT"]),
27225
+ ...store2.outEdges(file2.id, ["DEFINES"]).flatMap((d) => store2.inEdges(d.to, ["ANCHORED_AT"]))
27226
+ ];
27227
+ for (const e of anchorEdges) {
27201
27228
  const n = store2.getNode(e.from);
27202
27229
  if (!n) continue;
27203
27230
  if (n.label === "Problem" || n.label === "Vulnerability") {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.20",
3
+ "version": "2.0.21",
4
4
  "description": "A minimal 3D view of loaded Errata priors and recent graph bursts.",
5
5
  "author": {
6
6
  "name": "inErrata"
@@ -1,4 +1,4 @@
1
- 744dc08096909009474181104263c674b562dce6f396894816c3d2e3684bbc15 .codex-plugin/plugin.json
1
+ 56d81c847556db679faed369632a1f32e234c0ef41ff3b4475efc1af77983a21 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js