@inerrata-corporation/errata 2.0.16-dev.3332 → 2.0.16-dev.3355

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.
package/errata.mjs CHANGED
@@ -139,6 +139,7 @@ var init_castalia = __esm({
139
139
  ROUTING_REASONS = [
140
140
  /** Harness org (CTF lane, e2e battery, canary) — forced org-private. */
141
141
  "harness-clamp",
142
+ "witnessed-vulnerability-private",
142
143
  /** A signed gateway write target named the destination. */
143
144
  "signed-target-public",
144
145
  "signed-target-org",
@@ -1080,6 +1081,12 @@ function identityId(input) {
1080
1081
  }
1081
1082
  case "Claim":
1082
1083
  return canonicalClaimId(input.text, input.scope);
1084
+ case "WitnessedVulnerability":
1085
+ return genericCanonicalId("dvuln", {
1086
+ workspaceId: input.workspaceId,
1087
+ target: input.target.replace(/\\/g, "/").replace(/^\.\//, ""),
1088
+ statement: canonicalClaimId(input.statement.trim())
1089
+ });
1083
1090
  case "DesignProblem":
1084
1091
  return `${DESIGN_PROBLEM_PREFIX}${canonicalClaimId(input.statement.trim())}`.slice(
1085
1092
  0,
@@ -15849,7 +15856,7 @@ var init_edge_rules = __esm({
15849
15856
  // anchor anything at anything, e.g. at a public `Package`, quietly re-creating
15850
15857
  // the code↔collective bridge the membrane exists to prevent.
15851
15858
  ANCHORED_AT: {
15852
- from: ["Problem", "Solution", "RootCause", "Claim"],
15859
+ from: ["Problem", "Vulnerability", "Solution", "RootCause", "Claim"],
15853
15860
  to: ["Symbol"]
15854
15861
  },
15855
15862
  // ── Security spine (v1 taxonomy.ts SECURITY_FORWARD_ONLY commentary) ──
@@ -15894,7 +15901,7 @@ var init_edge_rules = __esm({
15894
15901
  // `Component` joined the target set with OM-agent-anchors: an agent-named
15895
15902
  // component ("React Router") is the same knowledge→named-unit anchor shape as
15896
15903
  // a Tool — the knowledge is ABOUT it, not dependent on it.
15897
- CONCERNS: { from: ["Problem", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15904
+ CONCERNS: { from: ["Problem", "Vulnerability", "Solution", "RootCause", "Claim", "Pattern"], to: ["Tool", "Component"] },
15898
15905
  OPERATES_ON: { from: ["Algorithm"], to: ["DataStructure"] },
15899
15906
  INVOLVES: { from: ["Problem", "Solution"], to: ["DataStructure"] },
15900
15907
  // ── Taxonomy (Track 4 Stream C: MITRE CWE `ChildOf` → `Weakness —IS_A→ Weakness`) ──
@@ -16448,8 +16455,8 @@ function parsePackageLockJson(jsonText) {
16448
16455
  }
16449
16456
  const dependencies = [];
16450
16457
  for (const [path2, meta3] of Object.entries(packages)) {
16451
- const self = byPath.get(path2);
16452
- if (!self) continue;
16458
+ const self2 = byPath.get(path2);
16459
+ if (!self2) continue;
16453
16460
  const deps = meta3.dependencies;
16454
16461
  if (!deps || typeof deps !== "object") continue;
16455
16462
  const dependsOn = [];
@@ -16458,7 +16465,7 @@ function parsePackageLockJson(jsonText) {
16458
16465
  if (target) dependsOn.push(npmPurl(depName, target.version));
16459
16466
  }
16460
16467
  if (dependsOn.length > 0)
16461
- dependencies.push({ ref: npmPurl(self.name, self.version), dependsOn });
16468
+ dependencies.push({ ref: npmPurl(self2.name, self2.version), dependsOn });
16462
16469
  }
16463
16470
  const directRaw = [];
16464
16471
  for (const [path2, meta3] of Object.entries(packages)) {
@@ -16929,9 +16936,9 @@ function pomSelf(text) {
16929
16936
  const group = (/<groupId>([^<]+)<\/groupId>/.exec(own)?.[1] ?? parentGroup)?.trim();
16930
16937
  const artifact = /<artifactId>([^<]+)<\/artifactId>/.exec(own)?.[1]?.trim();
16931
16938
  if (!group) return [];
16932
- const self = [{ ecosystem: "maven", kind: "namespace", value: group }];
16933
- if (artifact) self.push({ ecosystem: "maven", kind: "name", value: mavenName(group, artifact) });
16934
- return self;
16939
+ const self2 = [{ ecosystem: "maven", kind: "namespace", value: group }];
16940
+ if (artifact) self2.push({ ecosystem: "maven", kind: "name", value: mavenName(group, artifact) });
16941
+ return self2;
16935
16942
  }
16936
16943
  function parseManifestSelf(path2, text) {
16937
16944
  const file2 = basename(path2);
@@ -16944,10 +16951,10 @@ function parseManifestSelf(path2, text) {
16944
16951
  }
16945
16952
  const name2 = obj && typeof obj === "object" ? obj["name"] : null;
16946
16953
  if (typeof name2 !== "string" || !name2.trim()) return [];
16947
- const self = [{ ecosystem: "npm", kind: "name", value: name2.trim() }];
16954
+ const self2 = [{ ecosystem: "npm", kind: "name", value: name2.trim() }];
16948
16955
  const scope = /^(@[^/]+)\//.exec(name2.trim())?.[1];
16949
- if (scope) self.push({ ecosystem: "npm", kind: "namespace", value: scope });
16950
- return self;
16956
+ if (scope) self2.push({ ecosystem: "npm", kind: "namespace", value: scope });
16957
+ return self2;
16951
16958
  }
16952
16959
  if (file2 === "pom.xml") return pomSelf(text);
16953
16960
  if (file2 === "build.gradle" || file2 === "build.gradle.kts") {
@@ -18268,7 +18275,8 @@ CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
18268
18275
  -- its table row. Partial, so it maintains a few thousand rows instead of adding
18269
18276
  -- a write to every node write. Its label list is FINGERPRINT_INDEXED_LABELS.
18270
18277
  DROP INDEX IF EXISTS nodes_live_fingerprint;
18271
- CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause');
18278
+ DROP INDEX IF EXISTS nodes_instance_fingerprint;
18279
+ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_updated_at, last_reinforced_at_seq, created_at_seq) WHERE valid_to IS NULL AND label IN ('Problem','Vulnerability','Solution','RootCause');
18272
18280
  CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
18273
18281
  CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
18274
18282
  -- Expression index on the per-node relPath (stored in attrs_json) so the
@@ -18282,7 +18290,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
18282
18290
  -- every edited file of every turn, and a scan parses every Problem's attrs.
18283
18291
  CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
18284
18292
  `;
18285
- FINGERPRINT_INDEXED_LABELS = ["Problem", "Solution", "RootCause"];
18293
+ FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
18286
18294
  SqliteGraphStore = class {
18287
18295
  db;
18288
18296
  // Typed edge lookups build their SQL from the number of types asked for, and
@@ -18960,8 +18968,8 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
18960
18968
  const r2 = this.db.prepare(
18961
18969
  `SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
18962
18970
  MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
18963
- FROM nodes INDEXED BY nodes_instance_fingerprint
18964
- WHERE valid_to IS NULL AND label IN ('Problem','Solution','RootCause')`
18971
+ FROM nodes INDEXED BY nodes_instance_fingerprint_v2
18972
+ WHERE valid_to IS NULL AND label IN ('Problem','Vulnerability','Solution','RootCause')`
18965
18973
  ).get();
18966
18974
  return `${r2.n}:${r2.t ?? 0}:${r2.s ?? 0}`;
18967
18975
  }
@@ -20257,7 +20265,7 @@ function mergeContextEdge(store, from, to, type, ts) {
20257
20265
  function deriveContextFromAnchors(store, problemId, ts, pkgIndex) {
20258
20266
  const report = { languages: [], packages: [] };
20259
20267
  const p = store.getNode(problemId);
20260
- if (!p || p.label !== "Problem") return report;
20268
+ if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return report;
20261
20269
  const files = store.outEdges(problemId, ["ANCHORED_AT"]).map((e) => store.getNode(e.to)).filter((n) => !!n && n.label === "File");
20262
20270
  if (files.length === 0) return report;
20263
20271
  const idx = pkgIndex ?? buildPackageIndex(store);
@@ -20895,6 +20903,81 @@ function ingestDesignProblem(store, flag, opts) {
20895
20903
  ...reopenedFromRegression ? { reopenedFromRegression } : {}
20896
20904
  };
20897
20905
  }
20906
+ function ingestWitnessedVulnerability(store, flag, opts) {
20907
+ const statement = flag.statement.trim();
20908
+ if (isPlaceholderStatement(statement)) return { id: "", created: false, corroborated: false, upgraded: false, rejected: "statement" };
20909
+ const target = flag.target?.trim().replace(/\\/g, "/").replace(/^\.\//, "") ?? "";
20910
+ const file2 = target ? resolveFileNode(store, target, opts.workspaceId) : null;
20911
+ const symbol2 = file2 && flag.line !== void 0 && flag.fileText !== void 0 ? resolveEnclosingSymbol(store, file2.id, flag.line, flag.fileText) : null;
20912
+ const identityTarget = symbol2 ? String(symbol2.attrs["logicalId"] ?? `${target}#${symbol2.attrs["qname"] ?? symbol2.description}`) : target;
20913
+ const compatibleTarget = (n) => !symbol2 || !n.attrs["identityTarget"] || n.attrs["identityTarget"] === target || n.attrs["identityTarget"] === identityTarget;
20914
+ const bound = flag.existingId ? store.getNode(flag.existingId) : null;
20915
+ if (flag.existingId && (!bound || bound.label !== "Vulnerability" || bound.attrs["workspaceId"] !== opts.workspaceId || target && bound.attrs["affectedTarget"] && bound.attrs["affectedTarget"] !== target || !compatibleTarget(bound))) {
20916
+ return { id: "", created: false, corroborated: false, upgraded: false, rejected: "binding-conflict" };
20917
+ }
20918
+ const statementKey = identityId({ kind: "DesignProblem", statement });
20919
+ const restated = bound ? null : store.findNodesByLabel("Vulnerability").find((n) => n.attrs["workspaceId"] === opts.workspaceId && n.attrs["affectedTarget"] === target && compatibleTarget(n) && [n.description, ...n.attrs["statementAliases"] ?? []].some((s) => identityId({ kind: "DesignProblem", statement: s }) === statementKey));
20920
+ const id = bound?.id ?? restated?.id ?? identityId({ kind: "WitnessedVulnerability", workspaceId: opts.workspaceId, target: identityTarget, statement });
20921
+ const existing = bound ?? store.getNode(id);
20922
+ const sources = existing?.attrs["sources"] ?? [];
20923
+ const corroborated = !!existing && !sources.includes(opts.source);
20924
+ const upgraded = !!existing && existing.attrs["assessment"] !== "asserted" && flag.assessment === "asserted";
20925
+ const assessment = existing?.attrs["assessment"] === "asserted" ? "asserted" : flag.assessment;
20926
+ if (!existing) {
20927
+ store.mergeNode(buildNode(store, id, "Vulnerability", statement, opts.ts, {
20928
+ source: "convo",
20929
+ sourceClass: "agent-observation",
20930
+ privateFinding: id,
20931
+ workspaceId: opts.workspaceId,
20932
+ affectedTarget: target,
20933
+ identityTarget,
20934
+ assessment,
20935
+ anchorDisposition: target ? "unresolved" : "absent",
20936
+ provisional: true,
20937
+ sources: [opts.source],
20938
+ corroborations: 0,
20939
+ ...opts.hostHarness ? { hostHarness: opts.hostHarness } : {}
20940
+ }));
20941
+ } else if (corroborated || upgraded || !existing.attrs["affectedTarget"] && target || symbol2 && existing.attrs["identityTarget"] !== identityTarget) {
20942
+ store.updateNode(id, {
20943
+ attrs: {
20944
+ ...existing.attrs,
20945
+ assessment,
20946
+ ...upgraded && existing.description !== statement ? { statementAliases: [...existing.attrs["statementAliases"] ?? [], existing.description].slice(-5) } : {},
20947
+ affectedTarget: existing.attrs["affectedTarget"] || target,
20948
+ ...symbol2 ? { identityTarget } : {},
20949
+ sources: corroborated ? [...sources, opts.source] : sources,
20950
+ corroborations: (Number(existing.attrs["corroborations"]) || 0) + (corroborated ? 1 : 0)
20951
+ },
20952
+ // Assessment strengthens on evidence, but does not certify exploitability.
20953
+ ...upgraded ? { description: statement } : {},
20954
+ ...corroborated ? { cumulativeHits: existing.cumulativeHits + 1 } : {},
20955
+ ...reinforcedSeqAttrs(store, "Vulnerability"),
20956
+ lastUpdatedAt: opts.ts
20957
+ });
20958
+ }
20959
+ return { id, created: !existing, corroborated, upgraded };
20960
+ }
20961
+ function recordVulnerabilityCause(store, findingId, causes, ts) {
20962
+ const finding = store.getNode(findingId);
20963
+ if (finding?.label !== "Vulnerability") return 0;
20964
+ let from = findingId;
20965
+ let count = 0;
20966
+ for (const description of causes) {
20967
+ if (isPlaceholderStatement(description)) continue;
20968
+ const id = genericCanonicalId("dcause", { findingId, description: description.trim() });
20969
+ if (!store.getNode(id)) store.mergeNode(buildNode(store, id, "RootCause", description.trim(), ts, {
20970
+ source: "convo",
20971
+ privateFinding: findingId,
20972
+ sources: finding.attrs["sources"],
20973
+ assessment: finding.attrs["assessment"]
20974
+ }));
20975
+ mergeEdge(store, from, id, "CAUSED_BY", ts);
20976
+ from = id;
20977
+ count++;
20978
+ }
20979
+ return count;
20980
+ }
20898
20981
  function canonicalizePatternText(name2) {
20899
20982
  const stripped = name2.replace(/^\s*pattern(?:\s*[×x]\s*\d+)?\s*(?:\(exemplar\))?\s*[:—–-]\s*/i, "").replace(/\s+/g, " ").trim();
20900
20983
  return stripped.length > 0 ? stripped : name2.replace(/\s+/g, " ").trim();
@@ -21205,6 +21288,7 @@ function findReusableSolution(store, fixText) {
21205
21288
  const key = solutionTextKey(fixText);
21206
21289
  if (!key) return null;
21207
21290
  for (const s of store.findNodesByLabel("Solution")) {
21291
+ if (s.attrs["privateFinding"]) continue;
21208
21292
  if (s.validTo != null) continue;
21209
21293
  if (isAutoMinted(s)) continue;
21210
21294
  if (solutionTextKey(s.description ?? "") === key) return s;
@@ -21229,7 +21313,7 @@ function priorsForFile(store, relPath) {
21229
21313
  for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
21230
21314
  const n = store.getNode(e.from);
21231
21315
  if (!n) continue;
21232
- if (n.label === "Problem") {
21316
+ if (n.label === "Problem" || n.label === "Vulnerability") {
21233
21317
  if (seenProblem.has(n.id)) continue;
21234
21318
  seenProblem.add(n.id);
21235
21319
  if (!n.attrs["resolvedAt"]) {
@@ -21299,7 +21383,8 @@ function solutionForProblem(store, problemId) {
21299
21383
  }
21300
21384
  function anchorProblemToWorkingFile(store, problemId, relPath, workspaceId2, ts, provenance = "edited", opts = {}) {
21301
21385
  const p = store.getNode(problemId);
21302
- if (!p || p.label !== "Problem") return false;
21386
+ if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return false;
21387
+ if (p.label === "Vulnerability" && provenance !== "witnessed") return false;
21303
21388
  if (!isAnchorableCodePath(relPath)) return false;
21304
21389
  const file2 = resolveFileNode(store, relPath, workspaceId2);
21305
21390
  if (!file2) return false;
@@ -21478,18 +21563,22 @@ function resolveDesignProblemById(store, id, fixNote, t) {
21478
21563
  }
21479
21564
  function closeDesignProblem(store, problemId, fixNote, t) {
21480
21565
  const p = store.getNode(problemId);
21481
- if (!p || p.label !== "Problem") return false;
21566
+ if (!p || p.label !== "Problem" && p.label !== "Vulnerability") return false;
21482
21567
  if (p.attrs["resolvedAs"]) return false;
21483
21568
  if (fixNote?.trim() && !isFragmentFixNote(fixNote)) {
21484
21569
  const sols = store.outEdges(problemId, ["SOLVED_BY"]).map((e) => store.getNode(e.to)).filter((n) => n !== null);
21485
21570
  if (sols.every((s) => isAutoMinted(s))) {
21486
- const reuse = findReusableSolution(store, fixNote);
21571
+ const reuse = p.label === "Vulnerability" ? null : findReusableSolution(store, fixNote);
21487
21572
  if (reuse) {
21488
21573
  mergeEdge(store, problemId, reuse.id, "SOLVED_BY", t);
21489
21574
  } else {
21490
21575
  const solId = `dfix_${digest({ problemId, fix: fixNote })}`.slice(0, 56);
21491
21576
  store.mergeNode(
21492
- buildNode(store, solId, "Solution", fixNote.trim(), t, { source: "convo", provisional: false })
21577
+ buildNode(store, solId, "Solution", fixNote.trim(), t, {
21578
+ source: "convo",
21579
+ provisional: false,
21580
+ ...p.label === "Vulnerability" ? { privateFinding: problemId, sources: p.attrs["sources"] } : {}
21581
+ })
21493
21582
  );
21494
21583
  mergeEdge(store, problemId, solId, "SOLVED_BY", t);
21495
21584
  }
@@ -21503,6 +21592,7 @@ function closeDesignProblem(store, problemId, fixNote, t) {
21503
21592
  // mints no Solution, and a legacy placeholder beside it reads as
21504
21593
  // all-heuristic either way.
21505
21594
  attrs: { ...p.attrs, provisional: false, resolvedAt: t, resolutionWitnessed: true },
21595
+ ...reinforcedSeqAttrs(store, p.label),
21506
21596
  lastUpdatedAt: t
21507
21597
  });
21508
21598
  return true;
@@ -23854,7 +23944,7 @@ function detectLocalCommunities(store, opts = {}) {
23854
23944
  const ids = /* @__PURE__ */ new Set();
23855
23945
  for (const label of JOINT_COMMUNITY_LABELS) {
23856
23946
  for (const n of store.findNodesByLabel(label)) {
23857
- if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
23947
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) continue;
23858
23948
  ids.add(n.id);
23859
23949
  }
23860
23950
  }
@@ -23991,6 +24081,7 @@ var init_community2 = __esm({
23991
24081
  init_src4();
23992
24082
  JOINT_COMMUNITY_LABELS = [
23993
24083
  "Problem",
24084
+ "Vulnerability",
23994
24085
  "Solution",
23995
24086
  "RootCause",
23996
24087
  "Pattern"
@@ -24852,6 +24943,7 @@ __export(src_exports2, {
24852
24943
  induceAbstractions: () => induceAbstractions,
24853
24944
  induceTriage: () => induceTriage,
24854
24945
  ingestDesignProblem: () => ingestDesignProblem,
24946
+ ingestWitnessedVulnerability: () => ingestWitnessedVulnerability,
24855
24947
  isConstraintProblem: () => isConstraintProblem,
24856
24948
  isFragmentFixNote: () => isFragmentFixNote,
24857
24949
  isPlaceholderStatement: () => isPlaceholderStatement,
@@ -24899,6 +24991,7 @@ __export(src_exports2, {
24899
24991
  recordMisreadPrior: () => recordMisreadPrior,
24900
24992
  recordRenderLedger: () => recordRenderLedger,
24901
24993
  recordTriageObservation: () => recordTriageObservation,
24994
+ recordVulnerabilityCause: () => recordVulnerabilityCause,
24902
24995
  reinforceSameAnchorProblem: () => reinforceSameAnchorProblem,
24903
24996
  relateClaims: () => relateClaims,
24904
24997
  reopenAutoClosedProblems: () => reopenAutoClosedProblems,
@@ -24968,11 +25061,11 @@ function isPassiveInjectable(node2, atCommit) {
24968
25061
  return false;
24969
25062
  }
24970
25063
  function buildSnapshot(opts) {
24971
- const problems = opts.store.findNodesByLabel("Problem").filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive", atCommit: opts.atCommit }).allow);
25064
+ const problems = [...opts.store.findNodesByLabel("Problem"), ...opts.store.findNodesByLabel("Vulnerability")].filter((p) => p.attrs["mergedInto"] === void 0 && historyReadPolicy(p, { mode: "passive", atCommit: opts.atCommit }).allow);
24972
25065
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
24973
25066
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
24974
25067
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
24975
- const recent2 = allProblems.slice(0, 8).map((p) => ({
25068
+ const recent3 = allProblems.slice(0, 8).map((p) => ({
24976
25069
  node: p,
24977
25070
  anchors: opts.store.outEdges(p.id, ["MANIFESTED_IN", "EVIDENCED_BY"])
24978
25071
  }));
@@ -25045,10 +25138,10 @@ function buildSnapshot(opts) {
25045
25138
  packages: opts.profile.stack.map((name2) => ({ name: name2, node: matchNode(pkgNodes, pkgBase(name2)) })),
25046
25139
  tools: toolNodes.map((node2) => ({ name: String(node2.attrs["name"] ?? node2.description), node: node2 }))
25047
25140
  };
25048
- const causalNudge = recent2.length >= 2 && recent2.every(
25141
+ const causalNudge = recent3.length >= 2 && recent3.every(
25049
25142
  (r) => opts.store.outEdges(r.node.id, ["CAUSED_BY"]).length === 0 && opts.store.inEdges(r.node.id, ["CAUSED_BY"]).length === 0
25050
25143
  );
25051
- const abstractUndomained = recent2.filter((r) => {
25144
+ const abstractUndomained = recent3.filter((r) => {
25052
25145
  const grounded = opts.store.outEdges(r.node.id, ["ANCHORED_AT", "OCCURS_IN", "DEPENDS_ON"]).length > 0;
25053
25146
  const hasDomain = opts.store.outEdges(r.node.id, ["PERTAIN_TO"]).length > 0;
25054
25147
  return !grounded && !hasDomain;
@@ -25057,7 +25150,7 @@ function buildSnapshot(opts) {
25057
25150
  return {
25058
25151
  profile: opts.profile,
25059
25152
  profileContext,
25060
- recentProblems: recent2,
25153
+ recentProblems: recent3,
25061
25154
  recentResolved,
25062
25155
  recentConstraints,
25063
25156
  regressions,
@@ -25123,11 +25216,11 @@ function sliceForFile(store, relPath, atCommit) {
25123
25216
  const priors = fp ? {
25124
25217
  openProblems: fp.openProblems.slice(0, 3).map((p) => ({
25125
25218
  id: p.id,
25126
- description: p.description,
25219
+ description: p.label === "Vulnerability" ? `Vulnerability (${p.attrs["assessment"] ?? "unassessed"}): ${p.description}` : p.description,
25127
25220
  ...typeof p.attrs["fixCandidateFile"] === "string" ? { fixCandidateFile: p.attrs["fixCandidateFile"] } : {}
25128
25221
  })),
25129
25222
  constraints: fp.constraints.slice(0, 2).map((c) => ({ id: c.id, description: c.description })),
25130
- resolvedProblems: fp.resolvedProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.description })),
25223
+ resolvedProblems: fp.resolvedProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.label === "Vulnerability" ? `Vulnerability (${p.attrs["assessment"] ?? "unassessed"}): ${p.description}` : p.description })),
25131
25224
  related: fp.related.slice(0, 4).map((n) => ({ id: n.id, label: n.label, description: n.description })),
25132
25225
  solutionsByProblem: Object.fromEntries(
25133
25226
  [...fp.openProblems.slice(0, 3), ...fp.resolvedProblems.slice(0, 3)].map((p) => [
@@ -25245,7 +25338,8 @@ function renderSnapshot(s) {
25245
25338
  if (s.recentProblems.length > 0) {
25246
25339
  lines.push("_Open \u2014 recently observed here, derived from your work (a recency slice, not an inventory \u2014 burst an id for its full neighborhood):_");
25247
25340
  for (const r of s.recentProblems) {
25248
- lines.push(`- **${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25341
+ const badge = r.node.label === "Vulnerability" ? `Vulnerability (${r.node.attrs["assessment"] ?? "unassessed"}): ` : "";
25342
+ lines.push(`- **${badge}${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25249
25343
  const hits = r.node.cumulativeHits ?? 0;
25250
25344
  const surprise = (r.node.cumulativeSurprise ?? 0).toFixed(2);
25251
25345
  lines.push(` - observed ${hits}\xD7 \xB7 cumulativeSurprise ${surprise}`);
@@ -25286,7 +25380,8 @@ function renderSnapshot(s) {
25286
25380
  if (s.recentResolved.length > 0) {
25287
25381
  lines.push("_Recently resolved \u2014 solved here; jumping-off points, not live defects:_");
25288
25382
  for (const r of s.recentResolved) {
25289
- lines.push(`- \u2713 **${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25383
+ const badge = r.node.label === "Vulnerability" ? `Vulnerability (${r.node.attrs["assessment"] ?? "unassessed"}): ` : "";
25384
+ lines.push(`- \u2713 **${badge}${r.node.description}** \u2014 \`${r.node.id}\`${tagOf(r.node)}`);
25290
25385
  if (r.solution) {
25291
25386
  lines.push(` - solved: ${r.solution.description.slice(0, 160)} \u2014 \`${r.solution.id}\`${tagOf(r.solution)}`);
25292
25387
  }
@@ -31707,7 +31802,7 @@ var init_src8 = __esm({
31707
31802
 
31708
31803
  // src/agent-signals.ts
31709
31804
  function buildFileRecallInstruction(referent = "\u2026") {
31710
- return `Touching one? paste its token: ${TAG_EXAMPLE.fix(referent)} resolved \xB7 ${TAG_EXAMPLE.prior(referent)} leaned-on \xB7 ${TAG_EXAMPLE.problem()} new. We harvest it.`;
31805
+ return `Touching one? paste its token: ${TAG_EXAMPLE.fix(referent)} resolved \xB7 ${TAG_EXAMPLE.prior(referent)} leaned-on \xB7 ${TAG_EXAMPLE.problem()} new \xB7 ${TAG_EXAMPLE.vulnerability()} security. We harvest it.`;
31711
31806
  }
31712
31807
  function triageBullet(ref) {
31713
31808
  return [
@@ -31743,15 +31838,15 @@ function attemptBullet() {
31743
31838
  ].join("\n");
31744
31839
  }
31745
31840
  function buildAgentInstruction(opts = {}) {
31746
- const signals = opts.signals ?? ["prior", "problem", "domain", "fix", "constraint", "triage", "attempt", "link"];
31841
+ const signals = opts.signals ?? ["prior", "problem", "vulnerability", "domain", "fix", "constraint", "triage", "attempt", "link"];
31747
31842
  const ref = opts.referent ?? "its-handle";
31748
31843
  const token = (s) => s === "problem" ? TAG_EXAMPLE.problem() : s === "constraint" ? TAG_EXAMPLE.constraint() : s === "attempt" ? TAG_EXAMPLE.tried() : s === "domain" ? TAG_EXAMPLE.domain() : s === "link" ? TAG_EXAMPLE.pattern() : s === "verdict" ? TAG_EXAMPLE.confirm(ref) : TAG_EXAMPLE[s](ref);
31749
- const head2 = `We tag the priors we show you with a short handle like [${opts.handleExample ?? "chokidar-glob"}]. Untagged prose is silently lost. Put each tag inline in prose (no fences or backticks). Requested errata-* answer blocks are the exception: keep their fences in your turn-final reply. Tag everything, we filter downstream:`;
31750
- const tail = "Mid-turn text (between tool calls) can be dropped by the harness \u2014 RESTATE every tag in your turn-final message; the final message is what reliably survives \u2014 EXCEPT one already shown above with an id: that is captured, so cite `[its-handle]` rather than retyping it (drift mints a duplicate).";
31844
+ const head2 = "Tag inline (no fences or backticks). Untagged findings are silently lost. Cite short handles; tag everything, we filter downstream. Requested errata-* answer blocks are the exception: keep their fences in your turn-final reply:";
31845
+ const tail = "Restate mid-turn tags in your final reply; the harness can drop mid-turn text. EXCEPT one already shown with an id: cite [its-handle] instead of retyping (drift mints a duplicate).";
31751
31846
  return [
31752
31847
  head2,
31753
31848
  ...signals.map(
31754
- (s) => s === "triage" ? triageBullet(ref) : s === "attempt" ? attemptBullet() : s === "link" ? linkBullet(ref) : s === "verdict" ? verdictBullet(ref) : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`
31849
+ (s) => s === "triage" ? triageBullet(ref) : s === "vulnerability" ? ` \u2022 ${GLOSS.vulnerability} \u2192 ${TAG_EXAMPLE.vulnerability()}. Uncertain? ${TAG_EXAMPLE.suspectedVulnerability()}. Code review counts, even if immediately fixed. No CVE/exploit needed. For fixes: ${TAG_EXAMPLE.vulnerabilityThread()} ${TAG_EXAMPLE.fixThread("v")}.` : s === "attempt" ? attemptBullet() : s === "link" ? linkBullet(ref) : s === "verdict" ? verdictBullet(ref) : ` \u2022 ${GLOSS[s]} \u2192 ${token(s)}`
31755
31850
  ),
31756
31851
  tail
31757
31852
  ].join("\n");
@@ -31763,6 +31858,9 @@ var init_agent_signals = __esm({
31763
31858
  TAG_EXAMPLE = {
31764
31859
  prior: (ref) => `([${ref}])`,
31765
31860
  problem: () => `[!one line on what's wrong]`,
31861
+ vulnerability: () => `[!vuln: who can do what; observed basis @ path:line]`,
31862
+ suspectedVulnerability: () => `[?vuln: consequence; missing check]`,
31863
+ vulnerabilityThread: () => `[!#v vuln: finding here]`,
31766
31864
  fix: (ref) => `(fix:[${ref}])`,
31767
31865
  // CONSTRAINT — the native capture path for a DIFFUSE problem (a constraint/tension/
31768
31866
  // conflict that shaped a decision). Free text, attaches to the decision sentence the
@@ -31783,7 +31881,7 @@ var init_agent_signals = __esm({
31783
31881
  // tags never need one — the id is for diagnoses that unfold over distance.
31784
31882
  problemThread: () => `[!#slug one line on what's wrong]`,
31785
31883
  causeThread: () => `(cause:#slug the mechanism)`,
31786
- fixThread: () => `(fix:#slug what you changed)`,
31884
+ fixThread: (thread = "slug") => `(fix:#${thread} what you changed)`,
31787
31885
  // ATTEMPT arc — what's being tried and what didn't work. A failed attempt is a
31788
31886
  // witnessed NEGATIVE result: it saves the next agent the same dead end.
31789
31887
  tried: () => `(tried: the approach you're attempting)`,
@@ -31825,8 +31923,9 @@ var init_agent_signals = __esm({
31825
31923
  // problems, and narrated them as progress (a pick-best-before-scoring bug lost).
31826
31924
  // Every example here was diagnosis-framed, so nothing contradicted that sorting.
31827
31925
  // Kept to 29 chars: the instruction sits at the 2600 passive-budget ceiling.
31828
- problem: "a problem you notice or report \u2014 any bug, finding, or thing wrong, even in code you just wrote; about specific code? name the file: [!what's wrong @ src/file.ts:42]",
31829
- fix: "your edit fixed a PROBLEM we showed you; for one you flagged yourself: (fix: what you changed) \u2014 a fix resolves a stated problem, completing routine work is not a fix",
31926
+ problem: "any bug or finding, even in code you just wrote; name affected code: [!what's wrong @ path:42]",
31927
+ vulnerability: "unauthorized actions, data exposure, or execution/disruption an attacker can trigger",
31928
+ fix: "you fixed a shown finding; for your own non-security finding: (fix: what changed) \u2014 a fix resolves a stated problem, completing routine work is not a fix",
31830
31929
  constraint: "a design decision forced by conflicting requirements or constraints \u2014 tag the tension, especially when your fix makes it 'only look' resolved",
31831
31930
  triage: "you diagnosed a cause \u2014 fold the chain into the flag with <- (caused-by), deepest sure mechanism rightmost",
31832
31931
  attempt: "attempting an approach / an attempt failed \u2014 a failed attempt is knowledge, it rules out a path",
@@ -31848,7 +31947,7 @@ var init_agent_signals = __esm({
31848
31947
  // Sim note (2026-08-02 shrink): domain emission held 4/4 with just the trigger
31849
31948
  // clause; the pattern convergence hook now rides here too (it fell 0/4 when it
31850
31949
  // only appeared in the link bullet's tail).
31851
- domain: "a problem with no single file \u2014 name the area it's about (Title Case). Also name the general shape it instantiates: (pattern: the shape) \u2014 needs no code anchor, and two agents naming the same shape converge on one node",
31950
+ domain: "no single file? name the area (Title Case), and (pattern: the shape). No code anchor needed; two agents naming the same shape converge on one node",
31852
31951
  link: "the problem you flagged is an instance of priors/abstractions you can name \u2014 (instance:[h1],[h2],\u2026) / (pattern: \u2026) / (aids:[h]); rendered whole via linkBullet",
31853
31952
  verdict: "you can judge a prior we SHOWED you \u2014 confirm it if it held, refute it (with why) if it didn't; rendered whole via verdictBullet"
31854
31953
  };
@@ -32085,7 +32184,7 @@ function readContextCard(root, hash2) {
32085
32184
  function openProblems(store, now = Date.now()) {
32086
32185
  const hit = openProblemsCache.get(store);
32087
32186
  if (hit && now - hit.at < OPEN_PROBLEMS_TTL_MS) return hit.nodes;
32088
- const nodes = store.findNodesByLabel("Problem").filter((p) => live(p) && !p.attrs["resolvedAt"] && p.attrs["mergedInto"] === void 0 && !isConstraintProblem(p));
32187
+ const nodes = [...store.findNodesByLabel("Problem"), ...store.findNodesByLabel("Vulnerability")].filter((p) => live(p) && !p.attrs["resolvedAt"] && p.attrs["mergedInto"] === void 0 && !isConstraintProblem(p));
32089
32188
  openProblemsCache.set(store, { at: now, nodes });
32090
32189
  return nodes;
32091
32190
  }
@@ -32479,17 +32578,172 @@ var init_checkout_root = __esm({
32479
32578
  }
32480
32579
  });
32481
32580
 
32581
+ // src/process-identity.ts
32582
+ import { execFileSync } from "node:child_process";
32583
+ import { readFileSync as readFileSync10 } from "node:fs";
32584
+ function readProcessIdentity(pid, fresh = false) {
32585
+ if (!Number.isSafeInteger(pid) || pid <= 0) return null;
32586
+ if (pid === process.pid && self) return self;
32587
+ const cached2 = recent.get(pid);
32588
+ if (!fresh && cached2 && Date.now() - cached2.at < 1e3) return cached2.value;
32589
+ try {
32590
+ const run3 = (bin, args2) => execFileSync(bin, args2, {
32591
+ encoding: "utf8",
32592
+ windowsHide: true,
32593
+ timeout: 3e3,
32594
+ stdio: ["ignore", "pipe", "ignore"],
32595
+ env: { ...process.env, LC_ALL: "C", TZ: "UTC" }
32596
+ }).trim();
32597
+ let value;
32598
+ if (process.platform === "win32") {
32599
+ const ticks = run3("powershell.exe", [
32600
+ "-NoProfile",
32601
+ "-NonInteractive",
32602
+ "-Command",
32603
+ `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`
32604
+ ]);
32605
+ if (!/^\d+$/.test(ticks)) return null;
32606
+ value = { key: `win32:${ticks}`, bornAt: Number((BigInt(ticks) - 621355968000000000n) / 10000n) };
32607
+ } else if (process.platform === "linux") {
32608
+ const stat4 = readFileSync10(`/proc/${pid}/stat`, "utf8");
32609
+ const ticks = stat4.slice(stat4.lastIndexOf(")") + 2).split(" ")[19];
32610
+ const bootId = readFileSync10("/proc/sys/kernel/random/boot_id", "utf8").trim();
32611
+ const boot = /^btime (\d+)$/m.exec(readFileSync10("/proc/stat", "utf8"))?.[1];
32612
+ const hz = Number(run3("getconf", ["CLK_TCK"]));
32613
+ if (!boot || !/^\d+$/.test(ticks) || !Number.isFinite(hz) || hz <= 0) return null;
32614
+ value = { key: `linux:${bootId}:${ticks}`, bornAt: Number(boot) * 1e3 + Number(ticks) * 1e3 / hz };
32615
+ } else if (process.platform === "darwin") {
32616
+ const start2 = run3("ps", ["-p", String(pid), "-o", "lstart="]);
32617
+ value = { key: `darwin:${start2}`, bornAt: Date.parse(`${start2} UTC`) };
32618
+ } else return null;
32619
+ if (!Number.isFinite(value.bornAt) || value.bornAt <= 0) return null;
32620
+ if (pid === process.pid) self = value;
32621
+ if (recent.size >= 64) recent.clear();
32622
+ recent.set(pid, { at: Date.now(), value });
32623
+ return value;
32624
+ } catch {
32625
+ return null;
32626
+ }
32627
+ }
32628
+ var recent, self;
32629
+ var init_process_identity = __esm({
32630
+ "src/process-identity.ts"() {
32631
+ "use strict";
32632
+ recent = /* @__PURE__ */ new Map();
32633
+ }
32634
+ });
32635
+
32636
+ // src/lock.ts
32637
+ import { existsSync as existsSync12, readFileSync as readFileSync11, unlinkSync } from "node:fs";
32638
+ function daemonLockPayload(lock) {
32639
+ return JSON.stringify({
32640
+ pid: lock.pid,
32641
+ webUiUrl: lock.webUiUrl,
32642
+ startedAt: lock.processStartedAt,
32643
+ ...lock.pid === process.pid ? { processIdentity: readProcessIdentity(lock.pid)?.key } : {},
32644
+ ...lock.wokeAt !== void 0 ? { wokeAt: lock.wokeAt } : {},
32645
+ ...lock.entry ? { entry: lock.entry } : {},
32646
+ ...lock.version ? { version: lock.version } : {}
32647
+ });
32648
+ }
32649
+ function isInstalledEntry(entry) {
32650
+ return /\/node_modules\/@inerrata-corporation\/errata\//.test(normEntry(entry) + "/");
32651
+ }
32652
+ function foreignDaemonNote(lock, selfEntry) {
32653
+ if (!lock.entry || normEntry(lock.entry) === normEntry(selfEntry)) return null;
32654
+ const runningInstalled = isInstalledEntry(lock.entry);
32655
+ const head2 = [
32656
+ `launched from ${runningInstalled ? "the installed package" : "a LOCAL BUILD"}, not this one${lock.version ? ` (it is ${lock.version})` : ""}:`,
32657
+ ` running: ${lock.entry}`,
32658
+ ` this: ${selfEntry}`
32659
+ ];
32660
+ return runningInstalled ? [...head2, "this CLI is a different bundle from the daemon it is talking to \u2014 its view may not match what the daemon runs."] : [...head2, "`errata update` does not replace it. To move onto the installed package: errata stop, then errata dash."];
32661
+ }
32662
+ function isProcessAlive(pid) {
32663
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
32664
+ try {
32665
+ process.kill(pid, 0);
32666
+ return true;
32667
+ } catch (err2) {
32668
+ return err2.code === "EPERM";
32669
+ }
32670
+ }
32671
+ function readDaemonLock(lockPath) {
32672
+ if (!existsSync12(lockPath)) return null;
32673
+ try {
32674
+ return parseDaemonLock(readFileSync11(lockPath, "utf8"));
32675
+ } catch {
32676
+ return null;
32677
+ }
32678
+ }
32679
+ function parseDaemonLock(raw2) {
32680
+ try {
32681
+ const lock = JSON.parse(raw2);
32682
+ return lock && Number.isSafeInteger(lock.pid) && lock.pid > 0 ? lock : null;
32683
+ } catch {
32684
+ return null;
32685
+ }
32686
+ }
32687
+ function daemonLockState(lock, fresh = false) {
32688
+ if (!lock || !isProcessAlive(lock.pid)) return "stale";
32689
+ const identity = readProcessIdentity(lock.pid, fresh);
32690
+ if (!identity) return "unknown";
32691
+ if (lock.processIdentity) return lock.processIdentity === identity.key ? "running" : "stale";
32692
+ if (!Number.isFinite(lock.startedAt) || lock.startedAt <= 0) return "unknown";
32693
+ return identity.bornAt <= lock.startedAt ? "running" : "stale";
32694
+ }
32695
+ function isDaemonAlive(lockPath) {
32696
+ return daemonLockState(readDaemonLock(lockPath)) === "running";
32697
+ }
32698
+ function daemonLockBlocksStartup(lockPath) {
32699
+ return daemonLockState(readDaemonLock(lockPath), true) !== "stale";
32700
+ }
32701
+ function stopLockedDaemon(lockPath) {
32702
+ let original;
32703
+ try {
32704
+ original = readFileSync11(lockPath, "utf8");
32705
+ } catch {
32706
+ return "missing";
32707
+ }
32708
+ const lock = parseDaemonLock(original);
32709
+ if (!lock) return "unknown";
32710
+ const state = daemonLockState(lock, true);
32711
+ try {
32712
+ if (readFileSync11(lockPath, "utf8") !== original) return "changed";
32713
+ } catch {
32714
+ return "changed";
32715
+ }
32716
+ if (state === "unknown") return "unknown";
32717
+ if (state === "stale") {
32718
+ unlinkSync(lockPath);
32719
+ return "stale";
32720
+ }
32721
+ process.kill(lock.pid, "SIGTERM");
32722
+ return "signaled";
32723
+ }
32724
+ var normEntry;
32725
+ var init_lock = __esm({
32726
+ "src/lock.ts"() {
32727
+ "use strict";
32728
+ init_process_identity();
32729
+ normEntry = (p) => {
32730
+ const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
32731
+ return /^[A-Za-z]:\//.test(s) ? s.toLowerCase() : s;
32732
+ };
32733
+ }
32734
+ });
32735
+
32482
32736
  // src/registry.ts
32483
- import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
32737
+ import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
32484
32738
  import { join as join11 } from "node:path";
32485
32739
  function registryPath() {
32486
32740
  return process.env["ERRATA_REGISTRY_PATH"] ?? join11(globalDir(), "workspaces.json");
32487
32741
  }
32488
32742
  function read() {
32489
32743
  const p = registryPath();
32490
- if (!existsSync12(p)) return { version: 1, workspaces: {} };
32744
+ if (!existsSync13(p)) return { version: 1, workspaces: {} };
32491
32745
  try {
32492
- const parsed = JSON.parse(readFileSync10(p, "utf8"));
32746
+ const parsed = JSON.parse(readFileSync12(p, "utf8"));
32493
32747
  return { version: 1, workspaces: parsed.workspaces ?? {} };
32494
32748
  } catch {
32495
32749
  return { version: 1, workspaces: {} };
@@ -32497,9 +32751,9 @@ function read() {
32497
32751
  }
32498
32752
  function registryCorrupt() {
32499
32753
  const p = registryPath();
32500
- if (!existsSync12(p)) return false;
32754
+ if (!existsSync13(p)) return false;
32501
32755
  try {
32502
- JSON.parse(readFileSync10(p, "utf8"));
32756
+ JSON.parse(readFileSync12(p, "utf8"));
32503
32757
  return false;
32504
32758
  } catch {
32505
32759
  return true;
@@ -32552,7 +32806,7 @@ function pruneMissingWorkspaces() {
32552
32806
  const reg = read();
32553
32807
  const removed = [];
32554
32808
  for (const [id, entry] of Object.entries(reg.workspaces)) {
32555
- if (!existsSync12(entry.path)) {
32809
+ if (!existsSync13(entry.path)) {
32556
32810
  removed.push(entry);
32557
32811
  delete reg.workspaces[id];
32558
32812
  }
@@ -32561,19 +32815,14 @@ function pruneMissingWorkspaces() {
32561
32815
  return removed;
32562
32816
  }
32563
32817
  function workspaceStatus(entry) {
32564
- const missing = !existsSync12(entry.path);
32818
+ const missing = !existsSync13(entry.path);
32565
32819
  const lockPath = workspacePaths(entry.path).daemonLock;
32566
32820
  let running = false;
32567
32821
  let webUiUrl = null;
32568
- if (existsSync12(lockPath)) {
32569
- try {
32570
- const lock = JSON.parse(readFileSync10(lockPath, "utf8"));
32571
- if (lock.pid && lock.webUiUrl && pidAlive(lock.pid)) {
32572
- running = true;
32573
- webUiUrl = lock.webUiUrl;
32574
- }
32575
- } catch {
32576
- }
32822
+ const lock = readDaemonLock(lockPath);
32823
+ if (lock?.webUiUrl && daemonLockState(lock) === "running") {
32824
+ running = true;
32825
+ webUiUrl = lock.webUiUrl;
32577
32826
  }
32578
32827
  return { ...entry, running, webUiUrl, missing };
32579
32828
  }
@@ -32583,19 +32832,12 @@ function listWorkspaces() {
32583
32832
  function listWorkspaceEntries() {
32584
32833
  return Object.values(read().workspaces).sort((a, b) => b.lastSeenAt - a.lastSeenAt);
32585
32834
  }
32586
- function pidAlive(pid) {
32587
- try {
32588
- process.kill(pid, 0);
32589
- return true;
32590
- } catch (err2) {
32591
- return err2.code === "EPERM";
32592
- }
32593
- }
32594
32835
  var init_registry = __esm({
32595
32836
  "src/registry.ts"() {
32596
32837
  "use strict";
32597
32838
  init_paths();
32598
32839
  init_checkout_root();
32840
+ init_lock();
32599
32841
  }
32600
32842
  });
32601
32843
 
@@ -33892,7 +34134,7 @@ var init_dual_augment = __esm({
33892
34134
 
33893
34135
  // ../../packages/indexer/src/parse-cache.ts
33894
34136
  import { createHash as createHash6 } from "node:crypto";
33895
- import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync10 } from "node:fs";
34137
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync13, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync10 } from "node:fs";
33896
34138
  import { homedir as homedir3 } from "node:os";
33897
34139
  import { join as join12 } from "node:path";
33898
34140
  function parseCacheDir() {
@@ -33907,7 +34149,7 @@ function entryPath(dir, key) {
33907
34149
  function readParseCache(dir, key) {
33908
34150
  const file2 = entryPath(dir, key);
33909
34151
  try {
33910
- const raw2 = readFileSync11(file2, "utf8");
34152
+ const raw2 = readFileSync13(file2, "utf8");
33911
34153
  const parsed = JSON.parse(raw2);
33912
34154
  if (parsed.v !== PARSE_CACHE_VERSION || !Array.isArray(parsed.symbols)) return null;
33913
34155
  return { symbols: parsed.symbols, reExports: parsed.reExports ?? [] };
@@ -33936,7 +34178,7 @@ function sweepParseCacheOnce(dir, now = Date.now()) {
33936
34178
  sweptThisProcess = true;
33937
34179
  let removed = 0;
33938
34180
  try {
33939
- if (!existsSync13(dir)) return 0;
34181
+ if (!existsSync14(dir)) return 0;
33940
34182
  for (const bucket of readdirSync2(dir)) {
33941
34183
  const bucketDir = join12(dir, bucket);
33942
34184
  let names;
@@ -34194,11 +34436,11 @@ var init_identity3 = __esm({
34194
34436
  });
34195
34437
 
34196
34438
  // ../../packages/indexer/src/pipeline.ts
34197
- import { appendFileSync, readFileSync as readFileSync12, statSync as statSync3 } from "node:fs";
34439
+ import { appendFileSync, readFileSync as readFileSync14, statSync as statSync3 } from "node:fs";
34198
34440
  import { readdir } from "node:fs/promises";
34199
34441
  import { extname, join as join13, relative as relative2, sep } from "node:path";
34200
34442
  import { createHash as createHash7 } from "node:crypto";
34201
- import { execFileSync } from "node:child_process";
34443
+ import { execFileSync as execFileSync2 } from "node:child_process";
34202
34444
  function nowTs() {
34203
34445
  return indexNow ?? Date.now();
34204
34446
  }
@@ -34334,7 +34576,7 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
34334
34576
  for (const rel of [...changedRelPaths]) {
34335
34577
  let h;
34336
34578
  try {
34337
- h = createHash7("sha256").update(readFileSync12(join13(rootPath, rel))).digest("hex");
34579
+ h = createHash7("sha256").update(readFileSync14(join13(rootPath, rel))).digest("hex");
34338
34580
  } catch {
34339
34581
  continue;
34340
34582
  }
@@ -34389,7 +34631,7 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
34389
34631
  if (!provider) continue;
34390
34632
  let symbols;
34391
34633
  try {
34392
- symbols = provider.extractSymbols(readFileSync12(abs, "utf8"), abs);
34634
+ symbols = provider.extractSymbols(readFileSync14(abs, "utf8"), abs);
34393
34635
  } catch {
34394
34636
  continue;
34395
34637
  }
@@ -34709,7 +34951,7 @@ async function runIndexer(store, opts) {
34709
34951
  report.filesSkipped++;
34710
34952
  continue;
34711
34953
  }
34712
- source = readFileSync12(f.absPath, "utf8");
34954
+ source = readFileSync14(f.absPath, "utf8");
34713
34955
  } catch {
34714
34956
  report.filesSkipped++;
34715
34957
  continue;
@@ -34781,7 +35023,7 @@ async function runIndexer(store, opts) {
34781
35023
  const depth = fileNode.relPath.split("/").length;
34782
35024
  if (depth !== 3) continue;
34783
35025
  try {
34784
- const pkg = JSON.parse(readFileSync12(fileNode.absPath, "utf8"));
35026
+ const pkg = JSON.parse(readFileSync14(fileNode.absPath, "utf8"));
34785
35027
  if (!pkg.name) continue;
34786
35028
  const pkgDir = fileNode.relPath.replace(/\/package\.json$/, "");
34787
35029
  const candidates = [
@@ -35199,7 +35441,7 @@ async function scan(root, current, ignores, providers, out2) {
35199
35441
  function gitListRelPaths(root, ignores) {
35200
35442
  let stdout;
35201
35443
  try {
35202
- stdout = execFileSync(
35444
+ stdout = execFileSync2(
35203
35445
  "git",
35204
35446
  ["-C", root, "ls-files", "--cached", "--others", "--exclude-standard", "-z"],
35205
35447
  {
@@ -35257,7 +35499,7 @@ function upsertFile(store, id, f, workspaceId2, skip) {
35257
35499
  if (existing && fileUnchanged(existing.attrs, f, skip)) return;
35258
35500
  let contentHash;
35259
35501
  try {
35260
- contentHash = createHash7("sha256").update(readFileSync12(f.absPath)).digest("hex");
35502
+ contentHash = createHash7("sha256").update(readFileSync14(f.absPath)).digest("hex");
35261
35503
  } catch {
35262
35504
  }
35263
35505
  const carried = { ...existing?.attrs ?? {} };
@@ -39641,7 +39883,7 @@ ${JSON.stringify(symbolNames, null, 2)}`);
39641
39883
  // ../../packages/indexer/src/languages/tree-sitter-loader.ts
39642
39884
  import { fileURLToPath } from "node:url";
39643
39885
  import { dirname as dirname7, join as join14 } from "node:path";
39644
- import { existsSync as existsSync14, readdirSync as readdirSync3 } from "node:fs";
39886
+ import { existsSync as existsSync15, readdirSync as readdirSync3 } from "node:fs";
39645
39887
  import { createRequire as createRequire2 } from "node:module";
39646
39888
  function entryDir() {
39647
39889
  try {
@@ -39656,7 +39898,7 @@ function entryDir() {
39656
39898
  function findWasmDir() {
39657
39899
  const here = entryDir();
39658
39900
  const seaWasm = join14(here, "resources", "wasm");
39659
- if (existsSync14(join14(seaWasm, "tree-sitter-typescript.wasm"))) return seaWasm;
39901
+ if (existsSync15(join14(seaWasm, "tree-sitter-typescript.wasm"))) return seaWasm;
39660
39902
  let dir = here;
39661
39903
  for (let i2 = 0; i2 < 6; i2++) {
39662
39904
  const flat = join14(
@@ -39666,13 +39908,13 @@ function findWasmDir() {
39666
39908
  "tree-sitter-wasm",
39667
39909
  "wasm"
39668
39910
  );
39669
- if (existsSync14(join14(flat, "tree-sitter-typescript.wasm"))) return flat;
39911
+ if (existsSync15(join14(flat, "tree-sitter-typescript.wasm"))) return flat;
39670
39912
  dir = dirname7(dir);
39671
39913
  }
39672
39914
  let root = here;
39673
39915
  for (let i2 = 0; i2 < 6; i2++) {
39674
39916
  const pnpmDir = join14(root, "node_modules", ".pnpm");
39675
- if (existsSync14(pnpmDir)) {
39917
+ if (existsSync15(pnpmDir)) {
39676
39918
  for (const entry of readdirSync3(pnpmDir)) {
39677
39919
  if (entry.startsWith("@vscode+tree-sitter-wasm@")) {
39678
39920
  const candidate = join14(
@@ -39683,7 +39925,7 @@ function findWasmDir() {
39683
39925
  "tree-sitter-wasm",
39684
39926
  "wasm"
39685
39927
  );
39686
- if (existsSync14(join14(candidate, "tree-sitter-typescript.wasm"))) {
39928
+ if (existsSync15(join14(candidate, "tree-sitter-typescript.wasm"))) {
39687
39929
  return candidate;
39688
39930
  }
39689
39931
  }
@@ -39698,17 +39940,17 @@ function findWasmDir() {
39698
39940
  function findRuntimeDir() {
39699
39941
  const here = entryDir();
39700
39942
  const seaRuntime = join14(here, "resources", "wasm");
39701
- if (existsSync14(join14(seaRuntime, "web-tree-sitter.wasm"))) return seaRuntime;
39943
+ if (existsSync15(join14(seaRuntime, "web-tree-sitter.wasm"))) return seaRuntime;
39702
39944
  let dir = here;
39703
39945
  for (let i2 = 0; i2 < 6; i2++) {
39704
39946
  const flat = join14(dir, "node_modules", "web-tree-sitter");
39705
- if (existsSync14(join14(flat, "web-tree-sitter.wasm"))) return flat;
39947
+ if (existsSync15(join14(flat, "web-tree-sitter.wasm"))) return flat;
39706
39948
  dir = dirname7(dir);
39707
39949
  }
39708
39950
  let root = here;
39709
39951
  for (let i2 = 0; i2 < 6; i2++) {
39710
39952
  const pnpmDir = join14(root, "node_modules", ".pnpm");
39711
- if (existsSync14(pnpmDir)) {
39953
+ if (existsSync15(pnpmDir)) {
39712
39954
  for (const entry of readdirSync3(pnpmDir)) {
39713
39955
  if (entry.startsWith("web-tree-sitter@")) {
39714
39956
  const candidate = join14(
@@ -39717,7 +39959,7 @@ function findRuntimeDir() {
39717
39959
  "node_modules",
39718
39960
  "web-tree-sitter"
39719
39961
  );
39720
- if (existsSync14(join14(candidate, "web-tree-sitter.wasm"))) {
39962
+ if (existsSync15(join14(candidate, "web-tree-sitter.wasm"))) {
39721
39963
  return candidate;
39722
39964
  }
39723
39965
  }
@@ -42666,7 +42908,7 @@ var init_src11 = __esm({
42666
42908
  });
42667
42909
 
42668
42910
  // src/reconcile.ts
42669
- import { execFileSync as execFileSync2 } from "node:child_process";
42911
+ import { execFileSync as execFileSync3 } from "node:child_process";
42670
42912
  import { readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
42671
42913
  import { join as join15, relative as relative3, sep as sep2 } from "node:path";
42672
42914
  function indexerWouldScan(rel) {
@@ -42680,7 +42922,7 @@ function indexerWouldScan(rel) {
42680
42922
  function gitSourceFiles(root) {
42681
42923
  let stdout;
42682
42924
  try {
42683
- stdout = execFileSync2(
42925
+ stdout = execFileSync3(
42684
42926
  "git",
42685
42927
  ["-C", root, "ls-files", "--cached", "--others", "--exclude-standard", "-z"],
42686
42928
  { encoding: "utf8", maxBuffer: 128 * 1024 * 1024, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
@@ -42856,7 +43098,7 @@ __export(mcp_exports, {
42856
43098
  runTool: () => runTool,
42857
43099
  searchGraph: () => searchGraph
42858
43100
  });
42859
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
43101
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "node:fs";
42860
43102
  import { resolve as resolve3 } from "node:path";
42861
43103
  function collectEnrichmentPulls(store, opts = {}) {
42862
43104
  const pending = store.findNodesByLabel("Solution").filter((s) => s.attrs["enrichmentPending"] === true).filter((s) => !opts.onlyUnsurfaced || s.attrs["enrichmentSurfaced"] !== true);
@@ -42893,7 +43135,7 @@ function takeEnrichmentNudge(store) {
42893
43135
  ` + items;
42894
43136
  }
42895
43137
  function takeDiscriminatorNudge(path2) {
42896
- if (!path2 || !existsSync15(path2)) return null;
43138
+ if (!path2 || !existsSync16(path2)) return null;
42897
43139
  let shared;
42898
43140
  try {
42899
43141
  shared = openGraphStore({ path: path2 });
@@ -43024,10 +43266,10 @@ function showNode(store, node2, maxLines) {
43024
43266
  if (!relPath) return { found: false, reason: "no file owner for node" };
43025
43267
  const workspaceRoot = process.cwd();
43026
43268
  const absPath = resolve3(workspaceRoot, relPath);
43027
- if (!existsSync15(absPath)) {
43269
+ if (!existsSync16(absPath)) {
43028
43270
  return { found: false, reason: `file not found on disk: ${absPath}` };
43029
43271
  }
43030
- const source = readFileSync13(absPath, "utf8");
43272
+ const source = readFileSync15(absPath, "utf8");
43031
43273
  const attrs = node2.attrs;
43032
43274
  let startByte = attrs["bodyStartByte"] ?? attrs["startByte"];
43033
43275
  let endByte = attrs["bodyEndByte"] ?? attrs["endByte"];
@@ -43329,47 +43571,9 @@ var init_mcp = __esm({
43329
43571
  init_reconcile();
43330
43572
  init_agent_signals();
43331
43573
  INSTRUCTIONS = [
43332
- // KEEP THIS UNDER THE CLIENT'S ~2 KB instruction-injection budget (the client
43333
- // truncates `instructions` on connect, ~2008 chars observed). Capture no longer
43334
- // depends on a standing emit-instruction block: the daemon derives problems/fixes
43335
- // from your WORK deterministically, and the one thing it asks of the agent — a
43336
- // one-line root cause at a fix — rides INLINE on the next tool response (the ⚡
43337
- // errata line; see the dispatch's enrichment piggyback) instead of a fenced
43338
- // protocol the agent has to remember. So this stays short: tool-routing steering
43339
- // plus a single line about the inline nudge. Per-tool routing lives in each tool's
43340
- // own `description` (delivered via tools/list). Don't re-add a tool table here.
43341
- "errata is a LIVE, bi-temporal code graph of this workspace, kept current by a",
43342
- "file watcher \u2014 a SEPARATE DIAGNOSTIC AXIS to run ALONGSIDE grep/glob/read, not",
43343
- "instead of them. For STRUCTURAL questions (callers of callers, blast-radius,",
43344
- "rename lineage) prefer it: faster, more complete, and it reaches relationships",
43345
- "grep can't. Each tool's description says when to reach for it. Fall back to",
43346
- 'grep/read when a tool returns found:false (ESPECIALLY reason:"unresolved" \u2014 the',
43347
- "name was not recognized, NOT an empty answer; an empty list like callers:[] is a",
43348
- 'confident "none" \u2014 trust it), when you need EXACT bytes before a high-stakes',
43349
- "edit, or for an unindexed file/language. Always confirm exact bytes with a read",
43350
- "before editing.",
43351
- "",
43352
- "errata is a cloud knowledge substrate \u2014 AND ALSO this workspace's code graph.",
43353
- "The problems you hit and the fixes you make become shared knowledge other agents",
43354
- "recall. That capture is automatic: derived from your edits/commands/commits \u2014 no",
43355
- "emit needed. Just after you resolve something a tool result may carry a one-line",
43356
- "\u26A1 errata note asking the ROOT CAUSE; answer via errata.annotate_resolution when",
43357
- "you know it, skip it otherwise.",
43358
- "",
43359
- "Two things it CAN'T derive \u2014 only you witness them, and this is a CAPTURE",
43360
- "PROTOCOL, not optional formatting: a problem or fix you state only in prose, a",
43361
- "list, or your own numbering is SILENTLY LOST \u2014 the tag is the ONLY thing recorded.",
43362
- "So the moment you state one, wrap it inline (no fence, no extra call):",
43363
- // Signal tokens come from the single source (agent-signals.ts) so this always-on
43364
- // block can't teach a grammar the parser rejects. Referent is "its-id" here — the
43365
- // raw MCP channel has no primed handles, so the agent cites a tool-result id.
43366
- ` \u2022 ${TAG_EXAMPLE.problem()} \u2014 EVERY problem you notice or report ([?\u2026] = TODO)`,
43367
- ` \u2022 ${TAG_EXAMPLE.fix("its-id")} \u2014 your edit fixed a problem errata showed you`,
43368
- "Tag every one you state \u2014 we filter downstream, so don't self-censor or batch.",
43369
- "",
43370
- "Graph-as-files: the graph is ALSO a virtual FS \u2014 Read (or `cat`)",
43371
- ".errata/g/<verb>/<seed> to run a query as a file read (e.g. .errata/g/burst/",
43372
- "<symbol>, /why/<sym>, /trace/<a>..<b>). Read .errata/g/index for the vocabulary."
43574
+ "errata is a live code and knowledge graph, a SEPARATE DIAGNOSTIC AXIS alongside grep/read for callers, blast radius, rename lineage and prior fixes. Fall back to read/search on found:false (unresolved is not an empty answer), or for unindexed code. Confirm exact bytes before editing.",
43575
+ buildAgentInstruction({ signals: ["prior", "problem", "vulnerability", "fix"], referent: "its-id" }),
43576
+ "Read .errata/g/index for graph-as-files query paths. A tool result may ask for a root cause; answer via errata.annotate_resolution when known."
43373
43577
  ].join("\n");
43374
43578
  AUDIT_RUN_THRESHOLD = 5;
43375
43579
  AUDIT_NUDGE_COOLDOWN = 12;
@@ -43784,7 +43988,7 @@ var init_mcp = __esm({
43784
43988
  const local = causalChain(store, { seedId: id, direction: "both", maxHops, limit });
43785
43989
  try {
43786
43990
  const path2 = sharedStorePath();
43787
- if (!existsSync15(path2)) return { found: true, ...local };
43991
+ if (!existsSync16(path2)) return { found: true, ...local };
43788
43992
  const shared = openGraphStore({ path: path2 });
43789
43993
  try {
43790
43994
  if (!shared.getNode(id)) return { found: true, ...local };
@@ -43961,7 +44165,7 @@ var init_mcp = __esm({
43961
44165
  },
43962
44166
  {
43963
44167
  name: "errata.problems",
43964
- description: "List the workspace's Problem backlog \u2014 the triage view that search/why/impact can't give because they need a seed. status: 'open' (default) | 'resolved' (a real fix) | 'retracted' (false alarms \u2014 false_positive/dissolution) | 'constraint' (design TENSIONS captured via `(constraint: \u2026)` \u2014 standing context, not open work, so they're excluded from 'open') | 'all'. Also returns a `retracted` breakdown so the false-positive rate stays visible.",
44168
+ description: "List the workspace's Problem and Vulnerability backlog \u2014 the triage view that search/why/impact can't give because they need a seed. status: 'open' (default) | 'resolved' (a real fix) | 'retracted' (false alarms \u2014 false_positive/dissolution) | 'constraint' (design TENSIONS captured via `(constraint: \u2026)` \u2014 standing context, not open work, so they're excluded from 'open') | 'all'. Also returns a `retracted` breakdown so the false-positive rate stays visible.",
43965
44169
  inputSchema: {
43966
44170
  type: "object",
43967
44171
  properties: {
@@ -43973,13 +44177,15 @@ var init_mcp = __esm({
43973
44177
  const want = String(args2["status"] ?? "open");
43974
44178
  const limit = Math.max(1, Math.min(200, Number(args2["limit"] ?? 50)));
43975
44179
  const RETRACTIONS = /* @__PURE__ */ new Set(["false_positive", "invalid", "duplicate"]);
43976
- const rows = store.findAllVersionsByLabel("Problem").map((p) => {
44180
+ const rows = [...store.findAllVersionsByLabel("Problem"), ...store.findAllVersionsByLabel("Vulnerability")].map((p) => {
43977
44181
  const ra = p.attrs["resolvedAs"];
43978
44182
  const status = ra && RETRACTIONS.has(ra) ? ra : p.attrs["resolvedAt"] != null ? "resolved" : isConstraintProblem(p) ? "constraint" : "open";
43979
44183
  const anchorEdge = store.outEdges(p.id, ["ANCHORED_AT"])[0];
43980
44184
  const anchor = anchorEdge ? store.getNode(anchorEdge.to)?.description : void 0;
43981
44185
  return {
43982
44186
  id: p.id,
44187
+ label: p.label,
44188
+ ...p.label === "Vulnerability" ? { assessment: p.attrs["assessment"] } : {},
43983
44189
  problem: p.description,
43984
44190
  status,
43985
44191
  createdAt: p.createdAt,
@@ -44385,7 +44591,7 @@ var init_mcp = __esm({
44385
44591
  inputSchema: { type: "object", properties: {} },
44386
44592
  handler: () => {
44387
44593
  const path2 = sharedStorePath();
44388
- if (!existsSync15(path2)) return { count: 0, pending: [] };
44594
+ if (!existsSync16(path2)) return { count: 0, pending: [] };
44389
44595
  const shared = openGraphStore({ path: path2 });
44390
44596
  try {
44391
44597
  const pending = pendingAbstractions(shared);
@@ -44413,7 +44619,7 @@ var init_mcp = __esm({
44413
44619
  },
44414
44620
  handler: (args2) => {
44415
44621
  const path2 = sharedStorePath();
44416
- if (!existsSync15(path2)) return { count: 0, pending: [] };
44622
+ if (!existsSync16(path2)) return { count: 0, pending: [] };
44417
44623
  const shared = openGraphStore({ path: path2 });
44418
44624
  try {
44419
44625
  const pending = pendingDiscriminators(shared, {
@@ -44457,7 +44663,7 @@ var init_mcp = __esm({
44457
44663
  },
44458
44664
  handler: (args2) => {
44459
44665
  const path2 = sharedStorePath();
44460
- if (!existsSync15(path2)) return { count: 0, routes: [] };
44666
+ if (!existsSync16(path2)) return { count: 0, routes: [] };
44461
44667
  const shared = openGraphStore({ path: path2 });
44462
44668
  try {
44463
44669
  const result = triageOf(shared, {
@@ -52344,11 +52550,11 @@ var init_tool_index = __esm({
52344
52550
 
52345
52551
  // src/outbox.ts
52346
52552
  import {
52347
- existsSync as existsSync16,
52553
+ existsSync as existsSync17,
52348
52554
  readdirSync as readdirSync5,
52349
- readFileSync as readFileSync14,
52555
+ readFileSync as readFileSync16,
52350
52556
  renameSync,
52351
- unlinkSync,
52557
+ unlinkSync as unlinkSync2,
52352
52558
  writeFileSync as writeFileSync12
52353
52559
  } from "node:fs";
52354
52560
  import { join as join17 } from "node:path";
@@ -52362,7 +52568,7 @@ function enqueueOutbox(paths, payload) {
52362
52568
  }
52363
52569
  async function flushOutbox(paths, client, store) {
52364
52570
  ensureDir(paths.outbox);
52365
- const entries = existsSync16(paths.outbox) ? readdirSync5(paths.outbox) : [];
52571
+ const entries = existsSync17(paths.outbox) ? readdirSync5(paths.outbox) : [];
52366
52572
  let uploaded = 0;
52367
52573
  let failed = 0;
52368
52574
  const quarantine = (abs, reason) => {
@@ -52376,7 +52582,7 @@ async function flushOutbox(paths, client, store) {
52376
52582
  const abs = join17(paths.outbox, f);
52377
52583
  let payload;
52378
52584
  try {
52379
- payload = JSON.parse(readFileSync14(abs, "utf8"));
52585
+ payload = JSON.parse(readFileSync16(abs, "utf8"));
52380
52586
  } catch {
52381
52587
  failed++;
52382
52588
  quarantine(abs, "unparseable JSON");
@@ -52411,7 +52617,7 @@ async function flushOutbox(paths, client, store) {
52411
52617
  }
52412
52618
  }
52413
52619
  }
52414
- unlinkSync(abs);
52620
+ unlinkSync2(abs);
52415
52621
  if (res.accepted === 0) uploaded++;
52416
52622
  } catch (err2) {
52417
52623
  failed++;
@@ -52421,7 +52627,7 @@ async function flushOutbox(paths, client, store) {
52421
52627
  }
52422
52628
  }
52423
52629
  }
52424
- const remainingFiles = existsSync16(paths.outbox) ? readdirSync5(paths.outbox).filter((e) => e.endsWith(".json")) : [];
52630
+ const remainingFiles = existsSync17(paths.outbox) ? readdirSync5(paths.outbox).filter((e) => e.endsWith(".json")) : [];
52425
52631
  return { uploaded, failed, remaining: remainingFiles.length };
52426
52632
  }
52427
52633
  var init_outbox = __esm({
@@ -52544,7 +52750,8 @@ function errorRecallTerms(errorText) {
52544
52750
  }
52545
52751
  function recallLine(n, token) {
52546
52752
  const desc = (n.description ?? "").replace(/\s+/g, " ").slice(0, 120);
52547
- return `- [${n.label}] ${desc} \u2192 ${token}`;
52753
+ const label = n.label === "Vulnerability" ? `Vulnerability; ${n.attrs["assessment"] ?? "unassessed"}` : n.label;
52754
+ return `- [${label}] ${desc} \u2192 ${token}`;
52548
52755
  }
52549
52756
  function recallForFile(store, relPath) {
52550
52757
  return fileRecall(store, relPath)?.context ?? null;
@@ -52565,7 +52772,7 @@ function fileRecall(store, relPath) {
52565
52772
  const shownRelated = priors.related.slice(0, 4);
52566
52773
  for (const p of shownProblems) {
52567
52774
  const hasCause = store.outEdges(p.id, ["CAUSED_BY"]).some((e) => store.getNode(e.to)?.label === "RootCause");
52568
- const causeCue = hasCause ? "" : ` \xB7 ${TAG_EXAMPLE.triageMint()} to diagnose its cause`;
52775
+ const causeCue = hasCause ? "" : ` \xB7 ${p.label === "Vulnerability" ? `(cause:#${p.id} the mechanism)` : TAG_EXAMPLE.triageMint()} to diagnose its cause`;
52569
52776
  lines.push(
52570
52777
  recallLine(p, `${TAG_EXAMPLE.fix(p.id)} if you resolved it \xB7 ${TAG_EXAMPLE.prior(p.id)} to cite${causeCue}`)
52571
52778
  );
@@ -52986,12 +53193,17 @@ function buildWebUi(deps) {
52986
53193
  block,
52987
53194
  lastInjectedHash
52988
53195
  });
53196
+ let systemMessage = null;
52989
53197
  if (sid && block && recallCardEnabled() && (decision || atPrompt)) {
52990
53198
  const card = readContextCard(deps.paths.root, block.hash);
52991
- if (card) rememberCard(sid, card);
53199
+ if (card) {
53200
+ rememberCard(sid, card);
53201
+ if (decision && sid.startsWith("codex:")) systemMessage = renderRecallLine(card);
53202
+ }
52992
53203
  }
52993
53204
  if (!decision) return c.json({});
52994
53205
  return c.json({
53206
+ ...systemMessage ? { systemMessage } : {},
52995
53207
  hookSpecificOutput: {
52996
53208
  hookEventName: decision.hookEventName,
52997
53209
  additionalContext: decision.additionalContext
@@ -62746,11 +62958,11 @@ var require_readable = __commonJS({
62746
62958
  });
62747
62959
  }
62748
62960
  };
62749
- function isLocked(self) {
62750
- return self[kBody] && self[kBody].locked === true || self[kConsume];
62961
+ function isLocked(self2) {
62962
+ return self2[kBody] && self2[kBody].locked === true || self2[kConsume];
62751
62963
  }
62752
- function isUnusable(self) {
62753
- return util.isDisturbed(self) || isLocked(self);
62964
+ function isUnusable(self2) {
62965
+ return util.isDisturbed(self2) || isLocked(self2);
62754
62966
  }
62755
62967
  async function consume(stream, type) {
62756
62968
  assert2(!stream[kConsume]);
@@ -63137,42 +63349,42 @@ var require_abort_signal = __commonJS({
63137
63349
  var { RequestAbortedError } = require_errors();
63138
63350
  var kListener = Symbol("kListener");
63139
63351
  var kSignal = Symbol("kSignal");
63140
- function abort2(self) {
63141
- if (self.abort) {
63142
- self.abort(self[kSignal]?.reason);
63352
+ function abort2(self2) {
63353
+ if (self2.abort) {
63354
+ self2.abort(self2[kSignal]?.reason);
63143
63355
  } else {
63144
- self.reason = self[kSignal]?.reason ?? new RequestAbortedError();
63356
+ self2.reason = self2[kSignal]?.reason ?? new RequestAbortedError();
63145
63357
  }
63146
- removeSignal(self);
63358
+ removeSignal(self2);
63147
63359
  }
63148
- function addSignal(self, signal) {
63149
- self.reason = null;
63150
- self[kSignal] = null;
63151
- self[kListener] = null;
63360
+ function addSignal(self2, signal) {
63361
+ self2.reason = null;
63362
+ self2[kSignal] = null;
63363
+ self2[kListener] = null;
63152
63364
  if (!signal) {
63153
63365
  return;
63154
63366
  }
63155
63367
  if (signal.aborted) {
63156
- abort2(self);
63368
+ abort2(self2);
63157
63369
  return;
63158
63370
  }
63159
- self[kSignal] = signal;
63160
- self[kListener] = () => {
63161
- abort2(self);
63371
+ self2[kSignal] = signal;
63372
+ self2[kListener] = () => {
63373
+ abort2(self2);
63162
63374
  };
63163
- addAbortListener(self[kSignal], self[kListener]);
63375
+ addAbortListener(self2[kSignal], self2[kListener]);
63164
63376
  }
63165
- function removeSignal(self) {
63166
- if (!self[kSignal]) {
63377
+ function removeSignal(self2) {
63378
+ if (!self2[kSignal]) {
63167
63379
  return;
63168
63380
  }
63169
- if ("removeEventListener" in self[kSignal]) {
63170
- self[kSignal].removeEventListener("abort", self[kListener]);
63381
+ if ("removeEventListener" in self2[kSignal]) {
63382
+ self2[kSignal].removeEventListener("abort", self2[kListener]);
63171
63383
  } else {
63172
- self[kSignal].removeListener("abort", self[kListener]);
63384
+ self2[kSignal].removeListener("abort", self2[kListener]);
63173
63385
  }
63174
- self[kSignal] = null;
63175
- self[kListener] = null;
63386
+ self2[kSignal] = null;
63387
+ self2[kListener] = null;
63176
63388
  }
63177
63389
  module2.exports = {
63178
63390
  addSignal,
@@ -72029,7 +72241,7 @@ __export(witness_ledger_exports, {
72029
72241
  summarizeWitnessLedger: () => summarizeWitnessLedger,
72030
72242
  witnessLedgerPath: () => witnessLedgerPath
72031
72243
  });
72032
- import { appendFileSync as appendFileSync3, existsSync as existsSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync20 } from "node:fs";
72244
+ import { appendFileSync as appendFileSync3, existsSync as existsSync26, readFileSync as readFileSync29, writeFileSync as writeFileSync20 } from "node:fs";
72033
72245
  import { join as join33 } from "node:path";
72034
72246
  function witnessLedgerPath(configDir) {
72035
72247
  return join33(configDir, "witness-ledger.jsonl");
@@ -72038,7 +72250,7 @@ function appendWitnessLedger(configDir, entry) {
72038
72250
  const path2 = witnessLedgerPath(configDir);
72039
72251
  try {
72040
72252
  appendFileSync3(path2, JSON.stringify(entry) + "\n");
72041
- const lines = readFileSync27(path2, "utf8").split("\n").filter(Boolean);
72253
+ const lines = readFileSync29(path2, "utf8").split("\n").filter(Boolean);
72042
72254
  if (lines.length > LEDGER_MAX_LINES) {
72043
72255
  writeFileSync20(path2, lines.slice(-Math.floor(LEDGER_MAX_LINES / 2)).join("\n") + "\n");
72044
72256
  }
@@ -72047,9 +72259,9 @@ function appendWitnessLedger(configDir, entry) {
72047
72259
  }
72048
72260
  function readWitnessLedger(configDir) {
72049
72261
  const path2 = witnessLedgerPath(configDir);
72050
- if (!existsSync25(path2)) return [];
72262
+ if (!existsSync26(path2)) return [];
72051
72263
  try {
72052
- return readFileSync27(path2, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter((e) => typeof e.ts === "number" && typeof e.channel === "string");
72264
+ return readFileSync29(path2, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter((e) => typeof e.ts === "number" && typeof e.channel === "string");
72053
72265
  } catch {
72054
72266
  return [];
72055
72267
  }
@@ -72488,12 +72700,12 @@ var init_report_render = __esm({
72488
72700
 
72489
72701
  // src/cli.ts
72490
72702
  init_src6();
72491
- import { closeSync as closeSync2, existsSync as existsSync36, openSync as openSync2, readFileSync as readFileSync37, renameSync as renameSync4, statSync as statSync10 } from "node:fs";
72703
+ import { closeSync as closeSync2, existsSync as existsSync36, openSync as openSync2, readFileSync as readFileSync38, renameSync as renameSync4, statSync as statSync10 } from "node:fs";
72492
72704
  import { join as join44, resolve as pathResolve2 } from "node:path";
72493
72705
  import { spawn as spawn5 } from "node:child_process";
72494
72706
 
72495
72707
  // src/daemon.ts
72496
- import { existsSync as existsSync27, writeFileSync as writeFileSync23 } from "node:fs";
72708
+ import { existsSync as existsSync28, writeFileSync as writeFileSync23 } from "node:fs";
72497
72709
 
72498
72710
  // ../../node_modules/.pnpm/@hono+node-server@1.19.11_hono@4.12.8/node_modules/@hono/node-server/dist/index.mjs
72499
72711
  import { createServer as createServerHTTP } from "http";
@@ -73072,8 +73284,8 @@ init_webui();
73072
73284
  init_config();
73073
73285
 
73074
73286
  // src/engine.ts
73075
- import { execFileSync as execFileSync4 } from "node:child_process";
73076
- import { existsSync as existsSync26, statSync as statSync8, appendFileSync as appendFileSync4, readdirSync as readdirSync10, renameSync as renameSync3, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "node:fs";
73287
+ import { execFileSync as execFileSync5 } from "node:child_process";
73288
+ import { existsSync as existsSync27, statSync as statSync8, appendFileSync as appendFileSync4, readdirSync as readdirSync10, renameSync as renameSync3, readFileSync as readFileSync31, writeFileSync as writeFileSync22 } from "node:fs";
73077
73289
  import { basename as basename7, join as join35, relative as relative6, sep as sep4 } from "node:path";
73078
73290
 
73079
73291
  // src/source-watcher.ts
@@ -74781,16 +74993,16 @@ function createSourceWatcher(targets, opts) {
74781
74993
  }
74782
74994
  function chokidarSourceWatcher(targets, ignored) {
74783
74995
  const w = esm_default.watch([...targets], { ignored, ignoreInitial: true, persistent: true });
74784
- const self = {
74996
+ const self2 = {
74785
74997
  on(event, listener) {
74786
74998
  w.on(event, listener);
74787
- return self;
74999
+ return self2;
74788
75000
  },
74789
75001
  add: (p) => void w.add(p),
74790
75002
  unwatch: (p) => void w.unwatch(p),
74791
75003
  close: () => w.close()
74792
75004
  };
74793
- return self;
75005
+ return self2;
74794
75006
  }
74795
75007
  var NativeSourceWatcher = class extends EventEmitter2 {
74796
75008
  constructor(targets, ignored) {
@@ -75118,7 +75330,7 @@ init_review2();
75118
75330
 
75119
75331
  // src/turn.ts
75120
75332
  init_checkout_root();
75121
- import { closeSync, existsSync as existsSync17, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync6 } from "node:fs";
75333
+ import { closeSync, existsSync as existsSync18, fstatSync, openSync, readdirSync as readdirSync6, readSync, realpathSync, statSync as statSync6 } from "node:fs";
75122
75334
  import { basename as basename5, delimiter, dirname as dirname10, join as join21, normalize as normalize3 } from "node:path";
75123
75335
  import { homedir as homedir4 } from "node:os";
75124
75336
  import { createHash as createHash16 } from "node:crypto";
@@ -75511,7 +75723,7 @@ function claudeProjectDir(cwd, home = homedir4()) {
75511
75723
  }
75512
75724
  function recentTranscripts(cwd, opts = {}) {
75513
75725
  const dir = claudeProjectDir(cwd, opts.home ?? homedir4());
75514
- if (!existsSync17(dir)) return [];
75726
+ if (!existsSync18(dir)) return [];
75515
75727
  let names;
75516
75728
  try {
75517
75729
  names = readdirSync6(dir);
@@ -75538,7 +75750,7 @@ function recentTranscripts(cwd, opts = {}) {
75538
75750
  function subagentTranscripts(mainTranscriptPath, sessionId) {
75539
75751
  if (!mainTranscriptPath || !sessionId) return [];
75540
75752
  const dir = join21(dirname10(mainTranscriptPath), sessionId, "subagents");
75541
- if (!existsSync17(dir)) return [];
75753
+ if (!existsSync18(dir)) return [];
75542
75754
  let names;
75543
75755
  try {
75544
75756
  names = readdirSync6(dir);
@@ -75622,7 +75834,7 @@ function codexRolloutsForCwd(cwd, opts = {}) {
75622
75834
  const owner = checkoutRootOf(cwd);
75623
75835
  const owners = /* @__PURE__ */ new Map();
75624
75836
  for (const root of codexSessionRoots(opts.env, opts.home)) {
75625
- if (!existsSync17(root)) {
75837
+ if (!existsSync18(root)) {
75626
75838
  codexRolloutActivity.delete(root);
75627
75839
  continue;
75628
75840
  }
@@ -75723,7 +75935,7 @@ function geminiTranscriptsForCwd(cwd, opts = {}) {
75723
75935
  const hashes = geminiHashesFor(cwd);
75724
75936
  const out2 = [];
75725
75937
  for (const root of geminiSessionRoots(opts.env, opts.home)) {
75726
- if (!existsSync17(root)) continue;
75938
+ if (!existsSync18(root)) continue;
75727
75939
  let slugs;
75728
75940
  try {
75729
75941
  slugs = readdirSync6(root);
@@ -75794,7 +76006,7 @@ function opencodeTranscriptsForCwd(cwd, opts = {}) {
75794
76006
  }
75795
76007
  const out2 = [];
75796
76008
  for (const root of opencodeSessionRoots(opts.env, opts.home)) {
75797
- if (!existsSync17(root)) continue;
76009
+ if (!existsSync18(root)) continue;
75798
76010
  let names;
75799
76011
  try {
75800
76012
  names = readdirSync6(root);
@@ -75826,10 +76038,10 @@ init_src2();
75826
76038
  init_agent_signals();
75827
76039
  init_tool_index();
75828
76040
  init_src5();
75829
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "node:fs";
76041
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "node:fs";
75830
76042
  var NEG = /n['']t|\b(?:not|never|no|none|neither|nor|unrelated|irrelevant|would|might|could|if)\b/i;
75831
76043
  var CITE_GROUP_RE = /\(((?:[^()\n]|\([^()\n]*\)){1,200})\)/g;
75832
- var HANDLE_RE = /\[([a-z0-9][\w-]{0,80})\]|((?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,})/gi;
76044
+ var HANDLE_RE = /\[([a-z0-9][\w-]{0,80})\]|((?:pat|sol|drc|dcause|dfix|dprob|dvuln|prob|claim|err)_[0-9a-f]{6,})/gi;
75833
76045
  var PLACEHOLDER_HANDLES = /* @__PURE__ */ new Set([
75834
76046
  "handle",
75835
76047
  "its-handle",
@@ -75844,7 +76056,7 @@ var PLACEHOLDER_HANDLES = /* @__PURE__ */ new Set([
75844
76056
  "b"
75845
76057
  ]);
75846
76058
  var isPlaceholderHandle = (h) => PLACEHOLDER_HANDLES.has(h.toLowerCase());
75847
- var FLAG_BODY_HANDLE_RE = /^(?:[a-z0-9]+(?:-[a-z0-9]+){2,}-(?=[0-9a-f]{0,3}\d)[0-9a-f]{4}|(?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,})$/i;
76059
+ var FLAG_BODY_HANDLE_RE = /^(?:[a-z0-9]+(?:-[a-z0-9]+){2,}-(?=[0-9a-f]{0,3}\d)[0-9a-f]{4}|(?:pat|sol|drc|dcause|dfix|dprob|dvuln|prob|claim|err)_[0-9a-f]{6,})$/i;
75848
76060
  var FIX_PREFIX = /^\s*fix:(?:#([a-z][\w-]{0,63}))?\s*/i;
75849
76061
  var CONSTRAINT_RE = /\(\s*constraint:\s*([^()\n]{8,}?)\s*\)/gi;
75850
76062
  var CAUSE_PREFIX = /^\s*cause:(?:#([a-z][\w-]{0,63}))?\s*/i;
@@ -75869,7 +76081,7 @@ var DOMAIN_RE = /\(\s*domain:\s*([^()\n]{3,}?)\s*\)/gi;
75869
76081
  var PACKAGE_RE = /\(\s*package:\s*([^()\n]{2,}?)\s*\)/gi;
75870
76082
  var COMPONENT_RE = /\(\s*component:\s*([^()\n]{2,}?)\s*\)/gi;
75871
76083
  var CAUSE_TEXT_MIN = 8;
75872
- var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*((?:\[[^\]\n]*\]|[^\]\n]){8,}?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
76084
+ var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*((?:\[[^\]\n]*\]|[^\]\n])*?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
75873
76085
  var CONSTRAINT_MIN = 8;
75874
76086
  function clauseBefore(sentence, idx) {
75875
76087
  const win = sentence.slice(Math.max(0, idx - 80), idx);
@@ -76013,6 +76225,7 @@ function cleanEmittedTags(tags) {
76013
76225
  }
76014
76226
  function emptyTagDispositions() {
76015
76227
  return {
76228
+ invalidVulnerability: 0,
76016
76229
  oversizeSegments: 0,
76017
76230
  codeSpanNeutralized: 0,
76018
76231
  refuteTooShort: 0,
@@ -76025,6 +76238,7 @@ function emptyTagDispositions() {
76025
76238
  };
76026
76239
  }
76027
76240
  function addTagDispositions(a, b) {
76241
+ a.invalidVulnerability += b.invalidVulnerability;
76028
76242
  a.oversizeSegments += b.oversizeSegments;
76029
76243
  a.codeSpanNeutralized += b.codeSpanNeutralized;
76030
76244
  a.refuteTooShort += b.refuteTooShort;
@@ -76038,12 +76252,31 @@ function addTagDispositions(a, b) {
76038
76252
  function parseInlineTags(text) {
76039
76253
  return parseInlineTagsWithDispositions(text).tags;
76040
76254
  }
76255
+ function* captureSentences(text) {
76256
+ for (const line of text.split(/\n+/)) {
76257
+ if (line.length > 2e4) {
76258
+ yield line;
76259
+ continue;
76260
+ }
76261
+ const flags2 = [...line.matchAll(new RegExp(FLAG_RE.source, FLAG_RE.flags))].map((m) => ({ start: m.index, end: m.index + m[0].length }));
76262
+ let flag = 0;
76263
+ let start2 = 0;
76264
+ for (const boundary of line.matchAll(/(?<=[.!?])\s+(?!\(fix:)/g)) {
76265
+ const index = boundary.index;
76266
+ while (flags2[flag] && flags2[flag].end <= index) flag++;
76267
+ if (flags2[flag] && flags2[flag].start < index) continue;
76268
+ yield line.slice(start2, index);
76269
+ start2 = index + boundary[0].length;
76270
+ }
76271
+ yield line.slice(start2);
76272
+ }
76273
+ }
76041
76274
  function parseInlineTagsWithDispositions(text) {
76042
76275
  const out2 = [];
76043
76276
  const dispositions = emptyTagDispositions();
76044
76277
  const deFenced = text.replace(/```[\s\S]*?```/g, " ");
76045
76278
  let seqNo = -1;
76046
- for (const raw2 of deFenced.split(/(?<=[.!?])\s+(?!\(fix:)|\n+/)) {
76279
+ for (const raw2 of captureSentences(deFenced)) {
76047
76280
  seqNo++;
76048
76281
  if (raw2.length > 2e4) {
76049
76282
  dispositions.oversizeSegments++;
@@ -76269,6 +76502,29 @@ function parseInlineTagsWithDispositions(text) {
76269
76502
  while ((f = FLAG_RE.exec(sentence)) !== null) {
76270
76503
  const flagThread = f[2];
76271
76504
  const raw3 = f[3].trim();
76505
+ if (/^vuln\b/i.test(raw3)) {
76506
+ const typed = /^vuln:\s*([\s\S]*)$/i.exec(raw3);
76507
+ const body2 = typed?.[1]?.trim() ?? "";
76508
+ const parts2 = body2.split(CAUSE_ARROW).map((s) => s.trim());
76509
+ const symptom = f[1] === "!" && parts2.length > 1 && !f[4] ? new RegExp(FLAG_RE.source, "i").exec(`[!${parts2[0]}]`) : null;
76510
+ const statement = f[1] === "?" ? body2 : symptom?.[3]?.trim() ?? parts2[0];
76511
+ if (statement.length < 8 || /^<[^>]*>$/.test(statement)) {
76512
+ dispositions.invalidVulnerability++;
76513
+ continue;
76514
+ }
76515
+ const path2 = (f[4] ?? symptom?.[4])?.trim();
76516
+ const line = f[5] ?? symptom?.[5];
76517
+ out2.push({
76518
+ kind: "vulnerability",
76519
+ statement,
76520
+ assessment: f[1] === "!" ? "asserted" : "suspected",
76521
+ ...f[1] === "!" && parts2.length > 1 ? { vulnerabilityCauses: parts2.slice(1).filter((s) => s.length >= CAUSE_TEXT_MIN) } : {},
76522
+ ...path2 ? { location: { path: path2, ...line ? { line: Number(line) } : {} } } : {},
76523
+ ...flagThread ? { threadId: flagThread } : {},
76524
+ sentence: sfield
76525
+ });
76526
+ continue;
76527
+ }
76272
76528
  if (FLAG_BODY_HANDLE_RE.test(raw3) && !f[4]) {
76273
76529
  dispositions.flagAsHandle++;
76274
76530
  out2.push({ kind: "prior", handle: raw3, sentence: sfield });
@@ -76521,7 +76777,7 @@ function writePrimingHandles(path2, nodes, now = Date.now()) {
76521
76777
  }
76522
76778
  function readPrimingHandles(path2) {
76523
76779
  try {
76524
- return JSON.parse(readFileSync15(path2, "utf8"));
76780
+ return JSON.parse(readFileSync17(path2, "utf8"));
76525
76781
  } catch {
76526
76782
  return {};
76527
76783
  }
@@ -76639,7 +76895,7 @@ function harvestInlineTags(store, text, opts) {
76639
76895
  const parsed = parseInlineTagsWithDispositions(text);
76640
76896
  const tags = parsed.tags;
76641
76897
  plan.dispositions = parsed.dispositions;
76642
- const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement, threadId: t.threadId })).sort((a, b) => a.seq - b.seq);
76898
+ const symptomSeqs = tags.filter((t) => (t.kind === "problem" || t.kind === "todo" || t.kind === "vulnerability") && t.statement).map((t) => ({ seq: t.seq ?? -1, statement: t.statement, threadId: t.threadId, kind: t.kind })).sort((a, b) => a.seq - b.seq);
76643
76899
  const bindSymptom = (seq, threadId) => {
76644
76900
  if (threadId) {
76645
76901
  const hit = symptomSeqs.find((s) => s.threadId === threadId);
@@ -76652,6 +76908,7 @@ function harvestInlineTags(store, text, opts) {
76652
76908
  if (seq == null) return { evidence: "inferred" };
76653
76909
  let best;
76654
76910
  for (const s of symptomSeqs) {
76911
+ if (s.kind === "vulnerability") continue;
76655
76912
  if (s.seq <= seq) best = s;
76656
76913
  else break;
76657
76914
  }
@@ -76666,10 +76923,13 @@ function harvestInlineTags(store, text, opts) {
76666
76923
  return id;
76667
76924
  };
76668
76925
  for (const tag of tags) {
76669
- if (tag.kind === "problem" || tag.kind === "todo") {
76926
+ if (tag.kind === "problem" || tag.kind === "todo" || tag.kind === "vulnerability") {
76670
76927
  plan.problems.push({
76671
76928
  statement: tag.statement,
76672
76929
  kind: tag.kind,
76930
+ ...tag.assessment ? { assessment: tag.assessment } : {},
76931
+ ...tag.vulnerabilityCauses ? { vulnerabilityCauses: tag.vulnerabilityCauses } : {},
76932
+ ...tag.kind === "vulnerability" && tag.seq !== void 0 ? { seq: tag.seq } : {},
76673
76933
  ...tag.location ? { location: tag.location } : {},
76674
76934
  ...tag.threadId ? { threadId: tag.threadId } : {}
76675
76935
  });
@@ -76692,7 +76952,7 @@ function harvestInlineTags(store, text, opts) {
76692
76952
  } else if (tag.threadId && !b.statement) {
76693
76953
  plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, threadId: tag.threadId });
76694
76954
  } else if (b.statement && b.evidence === "witnessed") {
76695
- plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, boundStatement: b.statement });
76955
+ plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, boundStatement: b.statement, ...tag.threadId ? { threadId: tag.threadId } : {} });
76696
76956
  } else {
76697
76957
  plan.fixes.push({ inScope: true, fixNote: tag.fixRationale, ambiguous: true });
76698
76958
  }
@@ -76895,6 +77155,7 @@ function harvestInlineTags(store, text, opts) {
76895
77155
  }
76896
77156
  var CORROBORATABLE_LABELS = /* @__PURE__ */ new Set([
76897
77157
  "Problem",
77158
+ "Vulnerability",
76898
77159
  "Solution",
76899
77160
  "RootCause",
76900
77161
  "Pattern",
@@ -76996,7 +77257,7 @@ ${conversation}` }]
76996
77257
  // src/constraint-backfill.ts
76997
77258
  init_src5();
76998
77259
  init_src2();
76999
- import { existsSync as existsSync18, readFileSync as readFileSync16, readdirSync as readdirSync7, writeFileSync as writeFileSync14 } from "node:fs";
77260
+ import { existsSync as existsSync19, readFileSync as readFileSync18, readdirSync as readdirSync7, writeFileSync as writeFileSync14 } from "node:fs";
77000
77261
  import { join as join22 } from "node:path";
77001
77262
  import { homedir as homedir5 } from "node:os";
77002
77263
  var BACKFILL_VERSION = 1;
@@ -77015,9 +77276,9 @@ function markerPath(configDir) {
77015
77276
  }
77016
77277
  function alreadyDone(configDir) {
77017
77278
  const p = markerPath(configDir);
77018
- if (!existsSync18(p)) return false;
77279
+ if (!existsSync19(p)) return false;
77019
77280
  try {
77020
- const raw2 = JSON.parse(readFileSync16(p, "utf8"));
77281
+ const raw2 = JSON.parse(readFileSync18(p, "utf8"));
77021
77282
  return raw2?.version === BACKFILL_VERSION;
77022
77283
  } catch {
77023
77284
  return false;
@@ -77027,7 +77288,7 @@ function replay(root) {
77027
77288
  const statements = /* @__PURE__ */ new Set();
77028
77289
  const citedByFix = /* @__PURE__ */ new Set();
77029
77290
  const dir = claudeProjectDir(root, homedir5());
77030
- if (!existsSync18(dir)) return { statements, citedByFix };
77291
+ if (!existsSync19(dir)) return { statements, citedByFix };
77031
77292
  let names;
77032
77293
  try {
77033
77294
  names = readdirSync7(dir).filter((n) => n.endsWith(".jsonl"));
@@ -77037,7 +77298,7 @@ function replay(root) {
77037
77298
  for (const f of names) {
77038
77299
  let lines;
77039
77300
  try {
77040
- lines = readFileSync16(join22(dir, f), "utf8").split("\n");
77301
+ lines = readFileSync18(join22(dir, f), "utf8").split("\n");
77041
77302
  } catch {
77042
77303
  continue;
77043
77304
  }
@@ -77164,7 +77425,7 @@ function backfillConstraintKind(store, opts) {
77164
77425
  // src/edge-repair.ts
77165
77426
  init_src();
77166
77427
  init_src5();
77167
- import { existsSync as existsSync19, readFileSync as readFileSync17, writeFileSync as writeFileSync15 } from "node:fs";
77428
+ import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync15 } from "node:fs";
77168
77429
  import { join as join23 } from "node:path";
77169
77430
  function citeEdgeId(from, type, to) {
77170
77431
  return `edge_${digest({ from, type, to })}`.slice(0, 24);
@@ -77184,9 +77445,9 @@ function markerPath2(configDir) {
77184
77445
  }
77185
77446
  function alreadyDone2(configDir) {
77186
77447
  const p = markerPath2(configDir);
77187
- if (!existsSync19(p)) return false;
77448
+ if (!existsSync20(p)) return false;
77188
77449
  try {
77189
- return JSON.parse(readFileSync17(p, "utf8"))?.version === EDGE_REPAIR_VERSION;
77450
+ return JSON.parse(readFileSync19(p, "utf8"))?.version === EDGE_REPAIR_VERSION;
77190
77451
  } catch {
77191
77452
  return false;
77192
77453
  }
@@ -77249,7 +77510,7 @@ function repairInvalidEdges(store, opts) {
77249
77510
  init_reconcile();
77250
77511
 
77251
77512
  // src/pass-ledger.ts
77252
- import { appendFileSync as appendFileSync2, existsSync as existsSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "node:fs";
77513
+ import { appendFileSync as appendFileSync2, existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync16 } from "node:fs";
77253
77514
  import { join as join24 } from "node:path";
77254
77515
 
77255
77516
  // src/isolate-census.ts
@@ -77302,7 +77563,7 @@ function appendPassLedger(configDir, kind, durationMs, counts) {
77302
77563
  return;
77303
77564
  }
77304
77565
  appendsSinceTrim.set(path2, 0);
77305
- const lines = readFileSync18(path2, "utf8").split("\n").filter(Boolean);
77566
+ const lines = readFileSync20(path2, "utf8").split("\n").filter(Boolean);
77306
77567
  const keptByKind = /* @__PURE__ */ new Map();
77307
77568
  const kept = [];
77308
77569
  for (let i2 = lines.length - 1; i2 >= 0; i2--) {
@@ -77325,9 +77586,9 @@ function appendPassLedger(configDir, kind, durationMs, counts) {
77325
77586
  }
77326
77587
  function readPassLedger(configDir) {
77327
77588
  const path2 = passLedgerPath(configDir);
77328
- if (!existsSync20(path2)) return [];
77589
+ if (!existsSync21(path2)) return [];
77329
77590
  try {
77330
- return readFileSync18(path2, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter(
77591
+ return readFileSync20(path2, "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l)).filter(
77331
77592
  (e) => typeof e?.ts === "number" && typeof e?.kind === "string" && typeof e?.durationMs === "number"
77332
77593
  ).map((e) => ({ ...e, counts: e.counts ?? {} }));
77333
77594
  } catch {
@@ -77699,11 +77960,11 @@ init_outbox();
77699
77960
  init_src10();
77700
77961
  init_src();
77701
77962
  init_src2();
77702
- import { readFileSync as readFileSync19 } from "node:fs";
77963
+ import { readFileSync as readFileSync21 } from "node:fs";
77703
77964
  import { join as join25 } from "node:path";
77704
77965
  function loadClaimIgnorePatterns(workspaceRoot) {
77705
77966
  try {
77706
- return readFileSync19(join25(workspaceRoot, ".errataignore"), "utf8").split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#")).map((l) => l.toLowerCase());
77967
+ return readFileSync21(join25(workspaceRoot, ".errataignore"), "utf8").split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#")).map((l) => l.toLowerCase());
77707
77968
  } catch {
77708
77969
  return [];
77709
77970
  }
@@ -78080,8 +78341,8 @@ async function pullCloudTriage(shared, cloud, profile, limit = 50) {
78080
78341
 
78081
78342
  // src/retrieval-stack.ts
78082
78343
  init_src();
78083
- import { execFileSync as execFileSync3 } from "node:child_process";
78084
- import { existsSync as existsSync21, readFileSync as readFileSync20, statSync as statSync7 } from "node:fs";
78344
+ import { execFileSync as execFileSync4 } from "node:child_process";
78345
+ import { existsSync as existsSync22, readFileSync as readFileSync22, statSync as statSync7 } from "node:fs";
78085
78346
  import { homedir as homedir6 } from "node:os";
78086
78347
  import { join as join26 } from "node:path";
78087
78348
  var RETRIEVAL_STACK_CAP = 24;
@@ -78373,7 +78634,7 @@ function mavenGroupIsSelfDisclosing(name2, selfTokens) {
78373
78634
  function loadRegistryPolicy(root, env2 = process.env, repoLocator) {
78374
78635
  const read2 = (p) => {
78375
78636
  try {
78376
- return readFileSync20(p, "utf8");
78637
+ return readFileSync22(p, "utf8");
78377
78638
  } catch {
78378
78639
  return null;
78379
78640
  }
@@ -78599,7 +78860,7 @@ function readDirectDependencies(root) {
78599
78860
  for (const c of LOCKFILES) {
78600
78861
  if (familiesSeen.has(c.family)) continue;
78601
78862
  const p = join26(root, c.file);
78602
- if (!existsSync21(p)) continue;
78863
+ if (!existsSync22(p)) continue;
78603
78864
  try {
78604
78865
  const s = statSync7(p);
78605
78866
  stamps.push(`${c.file}:${s.mtimeMs}:${s.size}`);
@@ -78619,7 +78880,7 @@ function readDirectDependencies(root) {
78619
78880
  const direct = [];
78620
78881
  for (const { c, path: path2 } of found) {
78621
78882
  try {
78622
- direct.push(...c.parse(readFileSync20(path2, "utf8")).direct ?? []);
78883
+ direct.push(...c.parse(readFileSync22(path2, "utf8")).direct ?? []);
78623
78884
  } catch {
78624
78885
  }
78625
78886
  }
@@ -78628,7 +78889,7 @@ function readDirectDependencies(root) {
78628
78889
  }
78629
78890
  function manifestPaths(root) {
78630
78891
  try {
78631
- const out2 = execFileSync3("git", ["-C", root, "ls-files", "-z"], {
78892
+ const out2 = execFileSync4("git", ["-C", root, "ls-files", "-z"], {
78632
78893
  encoding: "utf8",
78633
78894
  maxBuffer: 128 * 1024 * 1024,
78634
78895
  windowsHide: true,
@@ -78662,14 +78923,14 @@ function manifestPaths(root) {
78662
78923
  "project.godot"
78663
78924
  ];
78664
78925
  return {
78665
- paths: ROOT_MANIFESTS.filter((f) => existsSync21(join26(root, f))),
78926
+ paths: ROOT_MANIFESTS.filter((f) => existsSync22(join26(root, f))),
78666
78927
  tracked: false,
78667
78928
  truncated: false
78668
78929
  };
78669
78930
  }
78670
78931
  var WORKSPACE_MARKERS = ["pnpm-workspace.yaml", "lerna.json", "go.work", "settings.gradle", "settings.gradle.kts"];
78671
78932
  function rootDeclaresWorkspace(root, texts) {
78672
- if (WORKSPACE_MARKERS.some((f) => existsSync21(join26(root, f)))) return true;
78933
+ if (WORKSPACE_MARKERS.some((f) => existsSync22(join26(root, f)))) return true;
78673
78934
  return /"workspaces"\s*:/.test(texts.get("package.json") ?? "") || /<modules>/.test(texts.get("pom.xml") ?? "") || /^\s*\[workspace\]/m.test(texts.get("Cargo.toml") ?? "");
78674
78935
  }
78675
78936
  var EMPTY_SCAN = { deps: [], self: [], complete: true };
@@ -78687,7 +78948,7 @@ function scanManifests(root) {
78687
78948
  try {
78688
78949
  const s = statSync7(abs);
78689
78950
  stamps.push(`${rel}:${s.mtimeMs}:${s.size}`);
78690
- texts.set(rel, readFileSync20(abs, "utf8"));
78951
+ texts.set(rel, readFileSync22(abs, "utf8"));
78691
78952
  } catch {
78692
78953
  }
78693
78954
  }
@@ -78716,11 +78977,11 @@ function readManifestDependencies(root) {
78716
78977
  }
78717
78978
 
78718
78979
  // src/git-sensor.ts
78719
- import { existsSync as existsSync22, readFileSync as readFileSync21, watch as fsWatch } from "node:fs";
78980
+ import { existsSync as existsSync23, readFileSync as readFileSync23, watch as fsWatch } from "node:fs";
78720
78981
  import { join as join27 } from "node:path";
78721
78982
  function readFirstLine(path2) {
78722
78983
  try {
78723
- return readFileSync21(path2, "utf8").split(/\r?\n/, 1)[0].trim();
78984
+ return readFileSync23(path2, "utf8").split(/\r?\n/, 1)[0].trim();
78724
78985
  } catch {
78725
78986
  return null;
78726
78987
  }
@@ -78739,13 +79000,13 @@ function readGitRefState(gitDir) {
78739
79000
  return {
78740
79001
  branch,
78741
79002
  sha: sha2,
78742
- mergeHeadExists: existsSync22(join27(gitDir, "MERGE_HEAD")),
78743
- origHeadExists: existsSync22(join27(gitDir, "ORIG_HEAD"))
79003
+ mergeHeadExists: existsSync23(join27(gitDir, "MERGE_HEAD")),
79004
+ origHeadExists: existsSync23(join27(gitDir, "ORIG_HEAD"))
78744
79005
  };
78745
79006
  }
78746
79007
  function shaFromPackedRefs(gitDir, ref) {
78747
79008
  try {
78748
- for (const line of readFileSync21(join27(gitDir, "packed-refs"), "utf8").split(/\r?\n/)) {
79009
+ for (const line of readFileSync23(join27(gitDir, "packed-refs"), "utf8").split(/\r?\n/)) {
78749
79010
  const [sha2, name2] = line.split(/\s+/);
78750
79011
  if (name2 === ref && sha2) return sha2;
78751
79012
  }
@@ -78779,7 +79040,7 @@ function startGitSensor(gitDir, onEvent, opts = {}) {
78779
79040
  const settle = () => {
78780
79041
  if (timer) clearTimeout(timer);
78781
79042
  timer = setTimeout(() => {
78782
- if (existsSync22(join27(gitDir, "index.lock"))) {
79043
+ if (existsSync23(join27(gitDir, "index.lock"))) {
78783
79044
  settle();
78784
79045
  return;
78785
79046
  }
@@ -79014,7 +79275,7 @@ var TelemetryRecorder = class {
79014
79275
  };
79015
79276
 
79016
79277
  // src/file-activity.ts
79017
- import { readFileSync as readFileSync23 } from "node:fs";
79278
+ import { readFileSync as readFileSync25 } from "node:fs";
79018
79279
  import { join as join29 } from "node:path";
79019
79280
  import { createHash as createHash17 } from "node:crypto";
79020
79281
 
@@ -79767,7 +80028,7 @@ init_git_remote();
79767
80028
  // src/history-bundle.ts
79768
80029
  init_src();
79769
80030
  import { execFile as execFile2 } from "node:child_process";
79770
- import { readFileSync as readFileSync22 } from "node:fs";
80031
+ import { readFileSync as readFileSync24 } from "node:fs";
79771
80032
  import { join as join28 } from "node:path";
79772
80033
  var DEFAULT_MAX_TOKENS = 6e3;
79773
80034
  var FILE_LIST_CAP = 60;
@@ -79803,7 +80064,7 @@ function pathAllowedByFloor(path2) {
79803
80064
  function loadRepoIgnore(root, extraPatterns = []) {
79804
80065
  let source = "";
79805
80066
  try {
79806
- source = readFileSync22(join28(root, ".errataignore"), "utf8");
80067
+ source = readFileSync24(join28(root, ".errataignore"), "utf8");
79807
80068
  } catch {
79808
80069
  source = "";
79809
80070
  }
@@ -80096,7 +80357,7 @@ function readActivityBranch(root) {
80096
80357
  try {
80097
80358
  const gitDir = resolveGitDir(root);
80098
80359
  if (!gitDir) return null;
80099
- const head2 = readFileSync23(join29(gitDir, "HEAD"), "utf8").trim();
80360
+ const head2 = readFileSync25(join29(gitDir, "HEAD"), "utf8").trim();
80100
80361
  const branch = head2.startsWith("ref: refs/heads/") ? head2.slice(16) : /^[a-f0-9]{40,64}$/i.test(head2) ? "HEAD" : null;
80101
80362
  return branch && branch.length <= 256 && !/[\x00-\x1f\x7f]/.test(branch) ? branch : null;
80102
80363
  } catch {
@@ -80253,11 +80514,11 @@ var FileActivityReporter = class {
80253
80514
 
80254
80515
  // src/skills.ts
80255
80516
  import {
80256
- existsSync as existsSync23,
80517
+ existsSync as existsSync24,
80257
80518
  mkdirSync as mkdirSync7,
80258
- readFileSync as readFileSync24,
80519
+ readFileSync as readFileSync26,
80259
80520
  readdirSync as readdirSync8,
80260
- unlinkSync as unlinkSync2,
80521
+ unlinkSync as unlinkSync3,
80261
80522
  writeFileSync as writeFileSync17
80262
80523
  } from "node:fs";
80263
80524
  import { basename as basename6, join as join30 } from "node:path";
@@ -80265,9 +80526,9 @@ function skillFileName(id) {
80265
80526
  return `${id.replace(/[^A-Za-z0-9_.-]/g, "_")}.md`;
80266
80527
  }
80267
80528
  function readSkillManifest(manifestPath) {
80268
- if (!existsSync23(manifestPath)) return [];
80529
+ if (!existsSync24(manifestPath)) return [];
80269
80530
  try {
80270
- const parsed = JSON.parse(readFileSync24(manifestPath, "utf8"));
80531
+ const parsed = JSON.parse(readFileSync26(manifestPath, "utf8"));
80271
80532
  return (parsed.skills ?? []).map((s) => ({
80272
80533
  title: s.title ?? "",
80273
80534
  layer: s.layer ?? "technique",
@@ -80318,7 +80579,7 @@ async function syncSkills(paths, client, seed, pins = [], techSeed) {
80318
80579
  if (!f.endsWith(".md")) continue;
80319
80580
  if (keep.has(basename6(f))) continue;
80320
80581
  try {
80321
- unlinkSync2(join30(paths.skillsDir, f));
80582
+ unlinkSync3(join30(paths.skillsDir, f));
80322
80583
  pruned++;
80323
80584
  } catch {
80324
80585
  }
@@ -80336,10 +80597,10 @@ async function syncSkills(paths, client, seed, pins = [], techSeed) {
80336
80597
  init_src2();
80337
80598
  import {
80338
80599
  cpSync,
80339
- existsSync as existsSync24,
80600
+ existsSync as existsSync25,
80340
80601
  lstatSync,
80341
80602
  mkdirSync as mkdirSync8,
80342
- readFileSync as readFileSync25,
80603
+ readFileSync as readFileSync27,
80343
80604
  readdirSync as readdirSync9,
80344
80605
  rmSync as rmSync3,
80345
80606
  symlinkSync,
@@ -80396,7 +80657,7 @@ function skillCiteHandle(s) {
80396
80657
  return priorHandle({ id: s.id, description: s.title });
80397
80658
  }
80398
80659
  function reconcileNamespaced(dir, keep) {
80399
- if (!existsSync24(dir)) return 0;
80660
+ if (!existsSync25(dir)) return 0;
80400
80661
  let pruned = 0;
80401
80662
  for (const name2 of readdirSync9(dir)) {
80402
80663
  if (!name2.startsWith(SKILL_NS) || keep.has(name2)) continue;
@@ -80410,7 +80671,7 @@ function reconcileNamespaced(dir, keep) {
80410
80671
  }
80411
80672
  function linkOrCopy(linkPath, target) {
80412
80673
  try {
80413
- if (existsSync24(linkPath) || safeLstat(linkPath)) rmSync3(linkPath, { recursive: true, force: true });
80674
+ if (existsSync25(linkPath) || safeLstat(linkPath)) rmSync3(linkPath, { recursive: true, force: true });
80414
80675
  } catch {
80415
80676
  }
80416
80677
  try {
@@ -80439,7 +80700,7 @@ function emitAndProjectSkills(root, skills) {
80439
80700
  for (const s of skills) {
80440
80701
  let body2;
80441
80702
  try {
80442
- body2 = readFileSync25(s.bodyPath, "utf8");
80703
+ body2 = readFileSync27(s.bodyPath, "utf8");
80443
80704
  } catch {
80444
80705
  continue;
80445
80706
  }
@@ -80459,7 +80720,7 @@ function emitAndProjectSkills(root, skills) {
80459
80720
  reconcileNamespaced(agentsSkillsDir, keep);
80460
80721
  let projected = 0;
80461
80722
  for (const h of HARNESS_SKILL_DIRS) {
80462
- if (!existsSync24(join31(root, h.configDir))) continue;
80723
+ if (!existsSync25(join31(root, h.configDir))) continue;
80463
80724
  const dir = join31(root, h.skillsDir);
80464
80725
  mkdirSync8(dir, { recursive: true });
80465
80726
  for (const slug2 of slugs) {
@@ -80472,9 +80733,9 @@ function emitAndProjectSkills(root, skills) {
80472
80733
  return { slugs, emitted, projected };
80473
80734
  }
80474
80735
  function emitInputsFromManifest(erretaDir, manifestPath) {
80475
- if (!existsSync24(manifestPath)) return [];
80736
+ if (!existsSync25(manifestPath)) return [];
80476
80737
  try {
80477
- const parsed = JSON.parse(readFileSync25(manifestPath, "utf8"));
80738
+ const parsed = JSON.parse(readFileSync27(manifestPath, "utf8"));
80478
80739
  return (parsed.skills ?? []).filter((s) => Boolean(s.id && s.file)).map((s) => ({
80479
80740
  id: s.id,
80480
80741
  title: s.title ?? s.id,
@@ -80497,7 +80758,7 @@ function ensureSkillGitignore(root) {
80497
80758
  const path2 = join31(root, ".gitignore");
80498
80759
  let current = "";
80499
80760
  try {
80500
- current = existsSync24(path2) ? readFileSync25(path2, "utf8") : "";
80761
+ current = existsSync25(path2) ? readFileSync27(path2, "utf8") : "";
80501
80762
  } catch {
80502
80763
  return;
80503
80764
  }
@@ -80515,7 +80776,7 @@ ${GITIGNORE_LINES.join("\n")}
80515
80776
  var INTENT_TTL_MS = 15 * 60 * 1e3;
80516
80777
  function createIntentState(ttlMs = INTENT_TTL_MS) {
80517
80778
  const bySession = /* @__PURE__ */ new Map();
80518
- let recent2 = null;
80779
+ let recent3 = null;
80519
80780
  const live3 = (s, nowMs) => s !== void 0 && nowMs - s.atMs < ttlMs;
80520
80781
  return {
80521
80782
  set(sessionId, text, nowMs) {
@@ -80523,8 +80784,8 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
80523
80784
  if (!trimmed) return false;
80524
80785
  const prev = bySession.get(sessionId);
80525
80786
  bySession.set(sessionId, { text: trimmed, atMs: nowMs });
80526
- const changed = !(recent2 && recent2.text === trimmed);
80527
- recent2 = { sessionId, text: trimmed, atMs: nowMs };
80787
+ const changed = !(recent3 && recent3.text === trimmed);
80788
+ recent3 = { sessionId, text: trimmed, atMs: nowMs };
80528
80789
  if (!changed && prev && prev.text === trimmed) return false;
80529
80790
  return changed;
80530
80791
  },
@@ -80533,12 +80794,12 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
80533
80794
  return live3(s, nowMs) ? s.text : void 0;
80534
80795
  },
80535
80796
  mostRecent(nowMs) {
80536
- if (!recent2) return void 0;
80537
- return nowMs - recent2.atMs < ttlMs ? recent2.text : void 0;
80797
+ if (!recent3) return void 0;
80798
+ return nowMs - recent3.atMs < ttlMs ? recent3.text : void 0;
80538
80799
  },
80539
80800
  clear(sessionId) {
80540
80801
  bySession.delete(sessionId);
80541
- if (recent2?.sessionId === sessionId) recent2 = null;
80802
+ if (recent3?.sessionId === sessionId) recent3 = null;
80542
80803
  }
80543
80804
  };
80544
80805
  }
@@ -80546,19 +80807,19 @@ function createIntentState(ttlMs = INTENT_TTL_MS) {
80546
80807
  // src/working-file-state.ts
80547
80808
  function createWorkingFileState() {
80548
80809
  const bySession = /* @__PURE__ */ new Map();
80549
- let recent2 = null;
80810
+ let recent3 = null;
80550
80811
  return {
80551
80812
  set(sessionId, rel) {
80552
80813
  bySession.set(sessionId, rel);
80553
- if (rel === recent2) return false;
80554
- recent2 = rel;
80814
+ if (rel === recent3) return false;
80815
+ recent3 = rel;
80555
80816
  return true;
80556
80817
  },
80557
80818
  get(sessionId) {
80558
80819
  return bySession.get(sessionId);
80559
80820
  },
80560
80821
  mostRecent() {
80561
- return recent2;
80822
+ return recent3;
80562
80823
  }
80563
80824
  };
80564
80825
  }
@@ -80600,7 +80861,7 @@ init_paths();
80600
80861
  init_profile2();
80601
80862
 
80602
80863
  // src/witness-queue.ts
80603
- import { readFileSync as readFileSync26, renameSync as renameSync2, writeFileSync as writeFileSync19 } from "node:fs";
80864
+ import { readFileSync as readFileSync28, renameSync as renameSync2, writeFileSync as writeFileSync19 } from "node:fs";
80604
80865
  import { dirname as dirname11, join as join32 } from "node:path";
80605
80866
  var WITNESS_QUEUE_CAP = 500;
80606
80867
  var WITNESS_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
@@ -80611,7 +80872,7 @@ function witnessQueuePath(workspaceConfigDir) {
80611
80872
  function loadWitnessQueueWithLosses(path2) {
80612
80873
  let raw2;
80613
80874
  try {
80614
- raw2 = JSON.parse(readFileSync26(path2, "utf8"));
80875
+ raw2 = JSON.parse(readFileSync28(path2, "utf8"));
80615
80876
  } catch (err2) {
80616
80877
  const absent = err2?.code === "ENOENT";
80617
80878
  return { queue: [], fileUnreadable: !absent, malformedEntries: 0 };
@@ -80673,9 +80934,9 @@ function partitionReplayable(queue, channel, isShipped) {
80673
80934
  function retireWitnesses(queue, channel, settledKeys) {
80674
80935
  return queue.filter((w) => !(w.channel === channel && settledKeys.has(w.witnessKey)));
80675
80936
  }
80676
- var WIRE_SHIPPED_LABELS = /* @__PURE__ */ new Set(["Problem", "Solution", "RootCause"]);
80937
+ var WIRE_SHIPPED_LABELS = /* @__PURE__ */ new Set(["Problem", "Vulnerability", "Solution", "RootCause"]);
80677
80938
  var MOTIF_ID_PREFIX = "motif_community_";
80678
- var CANONICAL_ID_RE = /^(?:pat|sol|drc|dcause|dfix|dprob|prob|claim|err)_[0-9a-f]{6,}$/i;
80939
+ var CANONICAL_ID_RE = /^(?:pat|sol|drc|dcause|dfix|dprob|dvuln|prob|claim|err)_[0-9a-f]{6,}$/i;
80679
80940
  function classifyWitnessTarget(nodeId, getNode) {
80680
80941
  if (nodeId.startsWith(MOTIF_ID_PREFIX)) {
80681
80942
  const exemplar = nodeId.slice(MOTIF_ID_PREFIX.length);
@@ -80932,7 +81193,7 @@ function maybeFlushDigests() {
80932
81193
 
80933
81194
  // src/liveness-watch.ts
80934
81195
  init_src5();
80935
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync21 } from "node:fs";
81196
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync21 } from "node:fs";
80936
81197
  import { join as join34 } from "node:path";
80937
81198
 
80938
81199
  // src/loop-lag.ts
@@ -80941,7 +81202,7 @@ var RECENT_PASS_LIMIT = 12;
80941
81202
  var active = /* @__PURE__ */ new Map();
80942
81203
  var nextPassId = 1;
80943
81204
  var started = false;
80944
- var recent = [];
81205
+ var recent2 = [];
80945
81206
  function markPass(name2) {
80946
81207
  const id = nextPassId++;
80947
81208
  const startedAt = Date.now();
@@ -80951,8 +81212,8 @@ function markPass(name2) {
80951
81212
  if (ended) return;
80952
81213
  ended = true;
80953
81214
  active.delete(id);
80954
- recent.unshift({ name: name2, ms: Date.now() - startedAt, endedAt: Date.now() });
80955
- if (recent.length > RECENT_PASS_LIMIT) recent.pop();
81215
+ recent2.unshift({ name: name2, ms: Date.now() - startedAt, endedAt: Date.now() });
81216
+ if (recent2.length > RECENT_PASS_LIMIT) recent2.pop();
80956
81217
  };
80957
81218
  }
80958
81219
  function oldestActive() {
@@ -80966,7 +81227,7 @@ function passState() {
80966
81227
  current: oldest?.name ?? "idle",
80967
81228
  runningMs: oldest === null ? null : now - oldest.startedAt,
80968
81229
  active: [...active.values()].map((p) => ({ name: p.name, runningMs: now - p.startedAt })),
80969
- recent: [...recent]
81230
+ recent: [...recent2]
80970
81231
  };
80971
81232
  }
80972
81233
  function describeHolders(running, finished, windowStart, now) {
@@ -80985,7 +81246,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
80985
81246
  const now = Date.now();
80986
81247
  const lag = now - last - HEARTBEAT_MS;
80987
81248
  if (lag > thresholdMs) {
80988
- const who = describeHolders(active.values(), recent, last, now);
81249
+ const who = describeHolders(active.values(), recent2, last, now);
80989
81250
  console.warn(
80990
81251
  `[loop-lag] ${(/* @__PURE__ */ new Date()).toISOString()} event loop held ~${(lag / 1e3).toFixed(1)}s during "${who}"`
80991
81252
  );
@@ -81068,7 +81329,7 @@ function alarmStatePath(configDir) {
81068
81329
  }
81069
81330
  function loadAlarmState(configDir) {
81070
81331
  try {
81071
- const raw2 = JSON.parse(readFileSync28(alarmStatePath(configDir), "utf8"));
81332
+ const raw2 = JSON.parse(readFileSync30(alarmStatePath(configDir), "utf8"));
81072
81333
  return new Map(Object.entries(raw2).filter((e) => typeof e[1] === "number"));
81073
81334
  } catch {
81074
81335
  return /* @__PURE__ */ new Map();
@@ -81229,8 +81490,8 @@ function createWatchBreaker(deps) {
81229
81490
  windows2.set(watchedPath, w);
81230
81491
  }
81231
81492
  w.dispatched++;
81232
- const self = evPath == null || evPath === "" || typeof evPath === "string" && normalizeWatchPath(evPath) === normalizeWatchPath(watchedPath);
81233
- if (!self) return;
81493
+ const self2 = evPath == null || evPath === "" || typeof evPath === "string" && normalizeWatchPath(evPath) === normalizeWatchPath(watchedPath);
81494
+ if (!self2) return;
81234
81495
  w.self++;
81235
81496
  if (!w.tripped && !quarantined.has(watchedPath) && w.self >= BREAKER_TRIP_SELF_EVENTS) {
81236
81497
  w.tripped = true;
@@ -81245,7 +81506,7 @@ function createWatchBreaker(deps) {
81245
81506
  }
81246
81507
 
81247
81508
  // src/engine.ts
81248
- var DAEMON_VERSION = true ? "2.0.16-dev.3332" : "2.0.0-alpha.0";
81509
+ var DAEMON_VERSION = true ? "2.0.16-dev.3355" : "2.0.0-alpha.0";
81249
81510
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
81250
81511
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
81251
81512
  var GIT_OP_MUTE_MS = 4e3;
@@ -81257,7 +81518,7 @@ var CODEX_SWEEP_LOOKBACK_MS = 10 * 6e4;
81257
81518
  function appendIdentityAudit(path2, record2, line) {
81258
81519
  if (!record2.accepted && record2.score <= 0) return;
81259
81520
  try {
81260
- if (existsSync26(path2) && statSync8(path2).size >= IDENTITY_AUDIT_MAX_BYTES) {
81521
+ if (existsSync27(path2) && statSync8(path2).size >= IDENTITY_AUDIT_MAX_BYTES) {
81261
81522
  renameSync3(path2, `${path2}.1`);
81262
81523
  }
81263
81524
  appendFileSync4(path2, line);
@@ -81267,7 +81528,7 @@ function appendIdentityAudit(path2, record2, line) {
81267
81528
  var yieldToLoop = () => new Promise((r) => setImmediate(r));
81268
81529
  function loadTurnCursors(path2) {
81269
81530
  try {
81270
- const raw2 = JSON.parse(readFileSync29(path2, "utf8"));
81531
+ const raw2 = JSON.parse(readFileSync31(path2, "utf8"));
81271
81532
  return new Map(
81272
81533
  Object.entries(raw2).map(([k, v]) => [k, typeof v === "string" ? v : String(v?.uuid ?? "")])
81273
81534
  );
@@ -81277,7 +81538,7 @@ function loadTurnCursors(path2) {
81277
81538
  }
81278
81539
  function loadTurnOffsets(path2) {
81279
81540
  try {
81280
- const raw2 = JSON.parse(readFileSync29(path2, "utf8"));
81541
+ const raw2 = JSON.parse(readFileSync31(path2, "utf8"));
81281
81542
  const out2 = /* @__PURE__ */ new Map();
81282
81543
  for (const [k, v] of Object.entries(raw2)) {
81283
81544
  const off = typeof v === "object" && v !== null ? v.offset : void 0;
@@ -81300,7 +81561,7 @@ function saveTurnCursors(path2, cursors, offsets) {
81300
81561
  function gitSourceWatchTargets(root) {
81301
81562
  const SOURCE = /\.(ts|tsx|js|jsx|mjs|cjs|py)$/i;
81302
81563
  try {
81303
- const lsOut = execFileSync4("git", ["-C", root, "ls-files", "-z"], {
81564
+ const lsOut = execFileSync5("git", ["-C", root, "ls-files", "-z"], {
81304
81565
  encoding: "utf8",
81305
81566
  maxBuffer: 128 * 1024 * 1024,
81306
81567
  windowsHide: true,
@@ -81310,7 +81571,7 @@ function gitSourceWatchTargets(root) {
81310
81571
  if (sourceFiles.length === 0) throw new Error("no tracked source");
81311
81572
  let ignoredDirs = [];
81312
81573
  try {
81313
- const igOut = execFileSync4(
81574
+ const igOut = execFileSync5(
81314
81575
  "git",
81315
81576
  ["-C", root, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory", "-z"],
81316
81577
  { encoding: "utf8", maxBuffer: 256 * 1024 * 1024, windowsHide: true, stdio: ["ignore", "pipe", "ignore"] }
@@ -81582,7 +81843,7 @@ function createWorkspaceEngine(opts) {
81582
81843
  );
81583
81844
  };
81584
81845
  const gitDir = join35(opts.workspaceRoot, ".git");
81585
- if (existsSync26(gitDir)) {
81846
+ if (existsSync27(gitDir)) {
81586
81847
  stopGit = startGitSensor(gitDir, (ev) => {
81587
81848
  void handleGitEvent(ev).catch((err2) => {
81588
81849
  console.warn("[errata] git event handler failed:", err2);
@@ -82137,6 +82398,9 @@ function createWorkspaceEngine(opts) {
82137
82398
  const harvestTextsInner = async (sessionId, items) => {
82138
82399
  let minted = 0;
82139
82400
  let tagsSeen = 0;
82401
+ let vulnerabilitiesSeen = 0;
82402
+ let vulnerabilitiesMinted = 0;
82403
+ let vulnerabilitiesSuspected = 0;
82140
82404
  let tagsRejected = 0;
82141
82405
  let tagsRestated = 0;
82142
82406
  let resolved = 0;
@@ -82367,10 +82631,73 @@ function createWorkspaceEngine(opts) {
82367
82631
  if ((toolRuns.get(sessionId)?.size ?? 0) > 0) touchedToolTurns++;
82368
82632
  const wf = workingFiles.get(sessionId);
82369
82633
  let inScopeProblemId;
82634
+ const findingIds = /* @__PURE__ */ new Map();
82635
+ const conflictingThreads = /* @__PURE__ */ new Set();
82370
82636
  const threads = sessionThreads.get(sessionId) ?? /* @__PURE__ */ new Map();
82371
82637
  sessionThreads.set(sessionId, threads);
82372
82638
  for (const p of plan.problems) {
82373
82639
  tagsSeen++;
82640
+ if (p.kind === "vulnerability") {
82641
+ vulnerabilitiesSeen++;
82642
+ if (p.assessment === "suspected") vulnerabilitiesSuspected++;
82643
+ let fileText;
82644
+ let anchorReadFailed = false;
82645
+ if (p.location) {
82646
+ try {
82647
+ fileText = readFileSync31(join35(opts.workspaceRoot, p.location.path), "utf8");
82648
+ } catch {
82649
+ anchorReadFailed = true;
82650
+ }
82651
+ }
82652
+ const result = ingestWitnessedVulnerability(store, {
82653
+ statement: p.statement,
82654
+ assessment: p.assessment,
82655
+ target: p.location?.path,
82656
+ line: p.location?.line,
82657
+ fileText,
82658
+ existingId: p.threadId ? threads.get(p.threadId) ?? handleMap[p.threadId]?.id ?? store.getNode(p.threadId)?.id : void 0
82659
+ }, { workspaceId: profile.id, source: sessionId, ts: t, hostHarness: turn.hostHarness });
82660
+ let anchorDisposition = p.location ? "unresolved" : "absent";
82661
+ if (result.rejected) {
82662
+ tagsRejected++;
82663
+ if (p.threadId) conflictingThreads.add(p.threadId);
82664
+ } else {
82665
+ findingIds.set(p.statement, result.id);
82666
+ if (p.threadId) threads.set(p.threadId, result.id);
82667
+ if (result.created || result.corroborated || result.upgraded) minted++;
82668
+ else tagsRestated++;
82669
+ if (result.created) vulnerabilitiesMinted++;
82670
+ if (p.location) {
82671
+ try {
82672
+ const anchored = anchorProblemToWorkingFile(store, result.id, p.location.path, profile.id, t, "witnessed", { line: p.location.line, fileText });
82673
+ anchorDisposition = anchored ? "resolved" : anchorReadFailed ? "error" : "unresolved";
82674
+ if (anchored) deriveContextFromAnchors(store, result.id, t);
82675
+ } catch {
82676
+ anchorDisposition = "error";
82677
+ }
82678
+ }
82679
+ const node2 = store.getNode(result.id);
82680
+ store.updateNode(result.id, { attrs: { ...node2.attrs, anchorDisposition } });
82681
+ if (p.vulnerabilityCauses) triaged += recordVulnerabilityCause(store, result.id, p.vulnerabilityCauses, t);
82682
+ }
82683
+ log.append({
82684
+ kind: "vulnerability.witness",
82685
+ workspaceId: profile.id,
82686
+ ts: t,
82687
+ digest: digest({ kind: "vulnerability.witness", workspaceId: profile.id, sessionId, turnId: turn.uuid, statement: p.statement, assessment: p.assessment, location: p.location }),
82688
+ sessionId,
82689
+ turnId: turn.uuid,
82690
+ harness: turn.hostHarness,
82691
+ findingId: result.id,
82692
+ statement: p.statement,
82693
+ assessment: p.assessment,
82694
+ location: p.location,
82695
+ seq: p.seq,
82696
+ anchorDisposition,
82697
+ disposition: result.rejected ?? (result.created ? "created" : result.upgraded ? "upgraded" : result.corroborated ? "corroborated" : "restated")
82698
+ });
82699
+ continue;
82700
+ }
82374
82701
  const anchorPath = p.location?.path ?? editedTurnFile;
82375
82702
  const anchorProvenance = p.location?.path ? "witnessed" : "edited";
82376
82703
  const hintPath = anchorPath ? void 0 : turnFile ?? wf;
@@ -82465,10 +82792,10 @@ function createWorkspaceEngine(opts) {
82465
82792
  if (r.created || r.corroborated) {
82466
82793
  minted++;
82467
82794
  if (p.kind !== "constraint") {
82468
- sessionLastProblem.set(sessionId, designProblemId(p.statement));
82469
- inScopeProblemId = designProblemId(p.statement);
82795
+ sessionLastProblem.set(sessionId, r.problemId);
82796
+ inScopeProblemId = r.problemId;
82470
82797
  }
82471
- if (p.threadId) threads.set(p.threadId, designProblemId(p.statement));
82798
+ if (p.threadId) threads.set(p.threadId, r.problemId);
82472
82799
  try {
82473
82800
  if (anchorPath) {
82474
82801
  let enclosing = {};
@@ -82477,7 +82804,7 @@ function createWorkspaceEngine(opts) {
82477
82804
  const headSha = readGitRefState(join35(opts.workspaceRoot, ".git")).sha ?? void 0;
82478
82805
  enclosing = {
82479
82806
  line: p.location.line,
82480
- fileText: readFileSync29(join35(opts.workspaceRoot, anchorPath), "utf8"),
82807
+ fileText: readFileSync31(join35(opts.workspaceRoot, anchorPath), "utf8"),
82481
82808
  ...headSha ? { headSha } : {}
82482
82809
  };
82483
82810
  } catch {
@@ -82505,7 +82832,8 @@ function createWorkspaceEngine(opts) {
82505
82832
  );
82506
82833
  }
82507
82834
  for (const fx of plan.fixes) {
82508
- const pid = fx.problemId ?? (fx.threadId ? threads.get(fx.threadId) : void 0) ?? (fx.boundStatement ? designProblemId(fx.boundStatement) : void 0);
82835
+ if (fx.threadId && conflictingThreads.has(fx.threadId)) continue;
82836
+ const pid = fx.problemId ?? (fx.threadId ? threads.get(fx.threadId) : void 0) ?? (fx.boundStatement ? findingIds.get(fx.boundStatement) ?? designProblemId(fx.boundStatement) : void 0);
82509
82837
  if (!pid) {
82510
82838
  if (fx.inScope) {
82511
82839
  console.warn(
@@ -82517,7 +82845,7 @@ function createWorkspaceEngine(opts) {
82517
82845
  const closed = resolveDesignProblemById(store, pid, fx.fixNote, t);
82518
82846
  if (!closed) {
82519
82847
  const n = store.getNode(pid);
82520
- if (!n || n.label !== "Problem" || n.attrs["resolvedAs"] != null) {
82848
+ if (!n || n.label !== "Problem" && n.label !== "Vulnerability" || n.attrs["resolvedAs"] != null) {
82521
82849
  console.warn(
82522
82850
  `[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`
82523
82851
  );
@@ -82536,10 +82864,23 @@ function createWorkspaceEngine(opts) {
82536
82864
  }
82537
82865
  }
82538
82866
  }
82867
+ const vulnerabilityTriages = /* @__PURE__ */ new Set();
82868
+ for (const tr of plan.triages) {
82869
+ if (tr.threadId && conflictingThreads.has(tr.threadId)) {
82870
+ vulnerabilityTriages.add(tr);
82871
+ continue;
82872
+ }
82873
+ const id = tr.symptomProblemId ?? (tr.threadId ? threads.get(tr.threadId) : void 0) ?? (tr.symptomStatement ? findingIds.get(tr.symptomStatement) : void 0);
82874
+ if (id && store.getNode(id)?.label === "Vulnerability") {
82875
+ vulnerabilityTriages.add(tr);
82876
+ if (tr.evidence === "witnessed" && tr.causeDescription) triaged += recordVulnerabilityCause(store, id, [tr.causeDescription], t);
82877
+ }
82878
+ }
82539
82879
  if (opts.sharedStore && plan.triages.length > 0) {
82540
82880
  const fallbackId = sessionLastProblem.get(sessionId);
82541
82881
  const fallbackStatement = fallbackId ? store.getNode(fallbackId)?.description : void 0;
82542
82882
  for (const tr of plan.triages) {
82883
+ if (vulnerabilityTriages.has(tr)) continue;
82543
82884
  let presentingStatement = tr.symptomStatement;
82544
82885
  let evidence = tr.evidence;
82545
82886
  if (!presentingStatement && tr.threadId) {
@@ -82557,7 +82898,7 @@ function createWorkspaceEngine(opts) {
82557
82898
  evidence = "inferred";
82558
82899
  }
82559
82900
  if (!presentingStatement) continue;
82560
- const presentingId = tr.symptomProblemId ?? designProblemId(presentingStatement);
82901
+ const presentingId = tr.symptomProblemId ?? (tr.threadId ? threads.get(tr.threadId) : void 0) ?? designProblemId(presentingStatement);
82561
82902
  if (tr.causeId === presentingId) continue;
82562
82903
  try {
82563
82904
  recordTriageObservation(
@@ -82615,7 +82956,7 @@ function createWorkspaceEngine(opts) {
82615
82956
  }
82616
82957
  }
82617
82958
  for (const at of plan.attempts) {
82618
- const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? designProblemId(at.boundStatement) : void 0);
82959
+ const pid = at.problemId ?? (at.threadId ? threads.get(at.threadId) : void 0) ?? (at.boundStatement ? findingIds.get(at.boundStatement) ?? designProblemId(at.boundStatement) : void 0);
82619
82960
  const node2 = pid ? store.getNode(pid) : void 0;
82620
82961
  if (!pid || !node2) continue;
82621
82962
  try {
@@ -82631,7 +82972,7 @@ function createWorkspaceEngine(opts) {
82631
82972
  }
82632
82973
  }
82633
82974
  const bindPid = (tag) => {
82634
- const pid = tag.problemId ?? (tag.threadId ? threads.get(tag.threadId) : void 0) ?? (tag.boundStatement ? designProblemId(tag.boundStatement) : void 0) ?? inScopeProblemId ?? sessionLastProblem.get(sessionId);
82975
+ const pid = tag.problemId ?? (tag.threadId ? threads.get(tag.threadId) : void 0) ?? (tag.boundStatement ? findingIds.get(tag.boundStatement) ?? designProblemId(tag.boundStatement) : void 0) ?? inScopeProblemId ?? sessionLastProblem.get(sessionId);
82635
82976
  return pid && store.getNode(pid) ? pid : void 0;
82636
82977
  };
82637
82978
  const mintCiteEdge = (from, to, type, confidence, extraAttrs) => {
@@ -82785,7 +83126,12 @@ function createWorkspaceEngine(opts) {
82785
83126
  for (const [session, items2] of groups) {
82786
83127
  if (items2.length === 0) continue;
82787
83128
  try {
82788
- const raw2 = await send(items2, session);
83129
+ const wireItems = items2.map(({ nodeId, witnessKey }) => {
83130
+ const node2 = store.getNode(nodeId);
83131
+ const cloudId = node2?.attrs["privateFinding"] ? node2.attrs["cloudNodeId"] : void 0;
83132
+ return { nodeId: typeof cloudId === "string" ? cloudId : nodeId, witnessKey };
83133
+ });
83134
+ const raw2 = await send(wireItems, session);
82789
83135
  const d = {
82790
83136
  recorded: raw2?.recorded ?? 0,
82791
83137
  unmatched: raw2?.unmatched ?? 0,
@@ -82798,8 +83144,8 @@ function createWorkspaceEngine(opts) {
82798
83144
  duplicate += d.duplicate;
82799
83145
  selfGated += d.selfGated;
82800
83146
  const missing = new Set(d.unmatchedIds);
82801
- for (const it of items2) {
82802
- if (missing.has(it.nodeId)) stillUnmatched.push({ channel, ...it, sessionKey: session, counted: isShipped(it.nodeId) });
83147
+ for (const [index, it] of items2.entries()) {
83148
+ if (missing.has(wireItems[index].nodeId)) stillUnmatched.push({ channel, ...it, sessionKey: session, counted: isShipped(it.nodeId) });
82803
83149
  else settled.add(it.witnessKey);
82804
83150
  }
82805
83151
  } catch (err2) {
@@ -82969,6 +83315,10 @@ function createWorkspaceEngine(opts) {
82969
83315
  // but nothing judgeable was cited.
82970
83316
  tagsVerdictNoHandle: dispositions.verdictNoHandle,
82971
83317
  tagsFlagAsHandle: dispositions.flagAsHandle,
83318
+ tagsInvalidVulnerability: dispositions.invalidVulnerability,
83319
+ vulnerabilitiesSeen,
83320
+ vulnerabilitiesMinted,
83321
+ vulnerabilitiesSuspected,
82972
83322
  unresolvedHandles,
82973
83323
  priorEdgesFlagOff,
82974
83324
  priorEdgesNoSource,
@@ -83583,7 +83933,7 @@ B: ${b}`,
83583
83933
  console.log(
83584
83934
  "[errata] sync skipped \u2014 cloud sync consent is off (enable with `errata consent sync on`)"
83585
83935
  );
83586
- const pending = existsSync26(paths.outbox) ? readdirSync10(paths.outbox).filter((f) => f.endsWith(".json")).length : 0;
83936
+ const pending = existsSync27(paths.outbox) ? readdirSync10(paths.outbox).filter((f) => f.endsWith(".json")).length : 0;
83587
83937
  return { uploaded: 0, failed: 0, remaining: pending };
83588
83938
  }
83589
83939
  const onDisk = loadProfile(opts.workspaceRoot);
@@ -83591,7 +83941,7 @@ B: ${b}`,
83591
83941
  console.log(
83592
83942
  "[errata] sync held \u2014 this repo is already on errata in another workspace; run `errata link` to join it or create your own project"
83593
83943
  );
83594
- const pending = existsSync26(paths.outbox) ? readdirSync10(paths.outbox).filter((f) => f.endsWith(".json")).length : 0;
83944
+ const pending = existsSync27(paths.outbox) ? readdirSync10(paths.outbox).filter((f) => f.endsWith(".json")).length : 0;
83595
83945
  return { uploaded: 0, failed: 0, remaining: pending };
83596
83946
  }
83597
83947
  try {
@@ -83727,7 +84077,7 @@ async function startDaemon(opts) {
83727
84077
  );
83728
84078
  await engine.stop();
83729
84079
  try {
83730
- if (existsSync27(engine.paths.daemonLock)) {
84080
+ if (existsSync28(engine.paths.daemonLock)) {
83731
84081
  }
83732
84082
  } catch {
83733
84083
  }
@@ -83928,7 +84278,7 @@ function instanceReceiptSequence(n) {
83928
84278
  // src/multi.ts
83929
84279
  init_dist();
83930
84280
  init_src5();
83931
- import { existsSync as existsSync33, readFileSync as readFileSync34, unlinkSync as unlinkSync3, writeFileSync as writeFileSync25 } from "node:fs";
84281
+ import { existsSync as existsSync33, readFileSync as readFileSync35, unlinkSync as unlinkSync4, writeFileSync as writeFileSync25 } from "node:fs";
83932
84282
  import { resolve as pathResolve } from "node:path";
83933
84283
 
83934
84284
  // src/principle-sync.ts
@@ -83957,7 +84307,7 @@ init_reconcile();
83957
84307
 
83958
84308
  // src/lockfile-auto.ts
83959
84309
  init_src();
83960
- import { existsSync as existsSync28, readFileSync as readFileSync30 } from "node:fs";
84310
+ import { existsSync as existsSync29, readFileSync as readFileSync32 } from "node:fs";
83961
84311
  import { join as join36 } from "node:path";
83962
84312
 
83963
84313
  // src/package-index.ts
@@ -84108,10 +84458,10 @@ function runLockfilePass(opts) {
84108
84458
  ];
84109
84459
  for (const c of candidates) {
84110
84460
  const p = join36(opts.root, c.file);
84111
- if (!existsSync28(p)) continue;
84461
+ if (!existsSync29(p)) continue;
84112
84462
  let sbom;
84113
84463
  try {
84114
- sbom = c.parse(readFileSync30(p, "utf8"));
84464
+ sbom = c.parse(readFileSync32(p, "utf8"));
84115
84465
  } catch {
84116
84466
  continue;
84117
84467
  }
@@ -84171,7 +84521,7 @@ function noveltyAgainst(node2, neighbors, opts = {}) {
84171
84521
  init_profile2();
84172
84522
 
84173
84523
  // src/public-symbols.ts
84174
- import { readFileSync as readFileSync31, readdirSync as readdirSync11, existsSync as existsSync29 } from "node:fs";
84524
+ import { readFileSync as readFileSync33, readdirSync as readdirSync11, existsSync as existsSync30 } from "node:fs";
84175
84525
  import { join as join37 } from "node:path";
84176
84526
  var HEAD_BYTES = 8 * 1024;
84177
84527
  var INDEX_TTL_MS = 10 * 60 * 1e3;
@@ -84206,9 +84556,9 @@ function internalPackageNames(workspaceRoot) {
84206
84556
  const names = /* @__PURE__ */ new Set();
84207
84557
  const tryRead = (dir) => {
84208
84558
  const pj = join37(dir, "package.json");
84209
- if (!existsSync29(pj)) return;
84559
+ if (!existsSync30(pj)) return;
84210
84560
  try {
84211
- const name2 = JSON.parse(readFileSync31(pj, "utf8")).name;
84561
+ const name2 = JSON.parse(readFileSync33(pj, "utf8")).name;
84212
84562
  if (typeof name2 === "string" && name2.length > 0) names.add(name2);
84213
84563
  } catch {
84214
84564
  }
@@ -84216,7 +84566,7 @@ function internalPackageNames(workspaceRoot) {
84216
84566
  tryRead(workspaceRoot);
84217
84567
  for (const group of ["packages", "apps"]) {
84218
84568
  const groupDir = join37(workspaceRoot, group);
84219
- if (!existsSync29(groupDir)) continue;
84569
+ if (!existsSync30(groupDir)) continue;
84220
84570
  try {
84221
84571
  for (const entry of readdirSync11(groupDir)) tryRead(join37(groupDir, entry));
84222
84572
  } catch {
@@ -84237,7 +84587,7 @@ function publicSymbolIndex(store, workspaceRoot, deps = {}) {
84237
84587
  }
84238
84588
  const readHead = deps.readHead ?? ((absPath) => {
84239
84589
  try {
84240
- return readFileSync31(absPath, "utf8").slice(0, HEAD_BYTES);
84590
+ return readFileSync33(absPath, "utf8").slice(0, HEAD_BYTES);
84241
84591
  } catch {
84242
84592
  return "";
84243
84593
  }
@@ -84305,7 +84655,7 @@ function internalSymbolsFor(description, lexiconHas, publicIndex) {
84305
84655
 
84306
84656
  // src/instance-ingest.ts
84307
84657
  init_generalize_graph();
84308
- var INSTANCE_LABELS = ["Problem", "Solution", "RootCause"];
84658
+ var INSTANCE_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
84309
84659
  var INSTANCE_EDGES = ["CAUSED_BY", "SOLVED_BY"];
84310
84660
  var ANCHOR_EDGES = ["OCCURS_IN", "DEPENDS_ON", "PERTAIN_TO", "CONCERNS"];
84311
84661
  var ANCHOR_TARGET_LABELS = ["Language", "Package", "Domain", "Component"];
@@ -84440,6 +84790,10 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84440
84790
  attrs: {
84441
84791
  scope: stripCodebaseScope(n.attrs["scope"]),
84442
84792
  ...n.attrs["kind"] ? { kind: n.attrs["kind"] } : {},
84793
+ ...n.attrs["privateFinding"] ? { privateFinding: true } : {},
84794
+ ...n.attrs["assessment"] ? { assessment: n.attrs["assessment"] } : {},
84795
+ ...n.attrs["sourceClass"] ? { sourceClass: n.attrs["sourceClass"] } : {},
84796
+ ...n.attrs["resolutionWitnessed"] ? { resolutionWitnessed: true, resolvedAt: n.attrs["resolvedAt"] } : {},
84443
84797
  ...n.attrs["resolvedAs"] ? { resolvedAs: n.attrs["resolvedAs"] } : {},
84444
84798
  ...preserved.length > 0 ? { preservedSymbols: preserved } : {},
84445
84799
  ...internal.length > 0 ? { internalSymbols: internal } : {}
@@ -84454,6 +84808,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84454
84808
  const semanticEndpoints = /* @__PURE__ */ new Map();
84455
84809
  const includedByLabel = /* @__PURE__ */ new Map();
84456
84810
  const composition = {
84811
+ privateTargetMissing: 0,
84457
84812
  candidates: 0,
84458
84813
  shipped: 0,
84459
84814
  cloudSourced: 0,
@@ -84499,11 +84854,15 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84499
84854
  if (walked.has(n.id)) continue;
84500
84855
  walked.add(n.id);
84501
84856
  composition.candidates++;
84857
+ if ((n.attrs["privateFinding"] || n.label === "Vulnerability") && !opts.project) {
84858
+ composition.privateTargetMissing++;
84859
+ continue;
84860
+ }
84502
84861
  if (n.attrs["source"] === "cloud") {
84503
84862
  composition.cloudSourced++;
84504
84863
  continue;
84505
84864
  }
84506
- if (label === "Problem" && n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) {
84865
+ if (n.attrs["resolvedAs"] === PROBLEM_RESOLUTION.FALSE_POSITIVE) {
84507
84866
  composition.falsePositive++;
84508
84867
  continue;
84509
84868
  }
@@ -84526,7 +84885,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84526
84885
  composition.terminalRefused++;
84527
84886
  continue;
84528
84887
  }
84529
- if (!noveltyAgainst(n, ref, noveltyOpts).ready) {
84888
+ if (!n.attrs["privateFinding"] && !noveltyAgainst(n, ref, noveltyOpts).ready) {
84530
84889
  composition.notNovel++;
84531
84890
  continue;
84532
84891
  }
@@ -84569,7 +84928,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
84569
84928
  }
84570
84929
  if (targets.length === 0) continue;
84571
84930
  const shipped = shippedById.get(sourceId);
84572
- if (shipped) {
84931
+ if (shipped && !shipped.attrs["privateFinding"]) {
84573
84932
  const anchors = (label) => targets.filter((x) => x.t.label === label).map(
84574
84933
  (x) => label === "Language" ? langAnchor(x.t) : label === "Component" ? `component:${x.wireId}` : x.wireId
84575
84934
  ).filter((a) => a != null).sort();
@@ -85055,59 +85414,8 @@ function cloudTargetDrift(daemon, shell2) {
85055
85414
  return out2;
85056
85415
  }
85057
85416
 
85058
- // src/lock.ts
85059
- import { existsSync as existsSync30, readFileSync as readFileSync32 } from "node:fs";
85060
- function daemonLockPayload(lock) {
85061
- return JSON.stringify({
85062
- pid: lock.pid,
85063
- webUiUrl: lock.webUiUrl,
85064
- startedAt: lock.processStartedAt,
85065
- ...lock.wokeAt !== void 0 ? { wokeAt: lock.wokeAt } : {},
85066
- ...lock.entry ? { entry: lock.entry } : {},
85067
- ...lock.version ? { version: lock.version } : {}
85068
- });
85069
- }
85070
- var normEntry = (p) => {
85071
- const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
85072
- return /^[A-Za-z]:\//.test(s) ? s.toLowerCase() : s;
85073
- };
85074
- function isInstalledEntry(entry) {
85075
- return /\/node_modules\/@inerrata-corporation\/errata\//.test(normEntry(entry) + "/");
85076
- }
85077
- function foreignDaemonNote(lock, selfEntry) {
85078
- if (!lock.entry || normEntry(lock.entry) === normEntry(selfEntry)) return null;
85079
- const runningInstalled = isInstalledEntry(lock.entry);
85080
- const head2 = [
85081
- `launched from ${runningInstalled ? "the installed package" : "a LOCAL BUILD"}, not this one${lock.version ? ` (it is ${lock.version})` : ""}:`,
85082
- ` running: ${lock.entry}`,
85083
- ` this: ${selfEntry}`
85084
- ];
85085
- return runningInstalled ? [...head2, "this CLI is a different bundle from the daemon it is talking to \u2014 its view may not match what the daemon runs."] : [...head2, "`errata update` does not replace it. To move onto the installed package: errata stop, then errata dash."];
85086
- }
85087
- function isProcessAlive(pid) {
85088
- if (!pid || pid <= 0) return false;
85089
- try {
85090
- process.kill(pid, 0);
85091
- return true;
85092
- } catch (err2) {
85093
- return err2.code === "EPERM";
85094
- }
85095
- }
85096
- function readDaemonLock(lockPath) {
85097
- if (!existsSync30(lockPath)) return null;
85098
- try {
85099
- const lock = JSON.parse(readFileSync32(lockPath, "utf8"));
85100
- return typeof lock.pid === "number" ? lock : null;
85101
- } catch {
85102
- return null;
85103
- }
85104
- }
85105
- function isDaemonAlive(lockPath) {
85106
- const lock = readDaemonLock(lockPath);
85107
- return lock !== null && isProcessAlive(lock.pid);
85108
- }
85109
-
85110
85417
  // src/multi.ts
85418
+ init_lock();
85111
85419
  init_config();
85112
85420
 
85113
85421
  // src/update.ts
@@ -85810,7 +86118,7 @@ async function syncHistoryRevisionPage(opts) {
85810
86118
  init_src7();
85811
86119
  init_src();
85812
86120
  import { execFile as execFile5 } from "node:child_process";
85813
- import { readFileSync as readFileSync33 } from "node:fs";
86121
+ import { readFileSync as readFileSync34 } from "node:fs";
85814
86122
  import { join as join38 } from "node:path";
85815
86123
  import { promisify as promisify3 } from "node:util";
85816
86124
 
@@ -86358,7 +86666,7 @@ async function defaultGit(root, args2) {
86358
86666
  function readIgnoreLines(root, profile) {
86359
86667
  let lines = [];
86360
86668
  try {
86361
- lines = readFileSync33(join38(root, ".errataignore"), "utf8").split("\n");
86669
+ lines = readFileSync34(join38(root, ".errataignore"), "utf8").split("\n");
86362
86670
  } catch {
86363
86671
  }
86364
86672
  return [...lines, ...profile.ignoreExtra ?? []].map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
@@ -86787,7 +87095,7 @@ async function startHistoryStage(root, input, client, walk11 = {}) {
86787
87095
  // src/link-flow.ts
86788
87096
  init_cloud_auth();
86789
87097
  import { createHash as createHash19 } from "node:crypto";
86790
- import { execFileSync as execFileSync5 } from "node:child_process";
87098
+ import { execFileSync as execFileSync6 } from "node:child_process";
86791
87099
  import { existsSync as existsSync31, writeFileSync as writeFileSync24 } from "node:fs";
86792
87100
  import { join as join39 } from "node:path";
86793
87101
 
@@ -87175,7 +87483,7 @@ async function linkThroughConsole(opts) {
87175
87483
  function collectRepoFacts(root) {
87176
87484
  const git3 = (args2) => {
87177
87485
  try {
87178
- return execFileSync5("git", args2, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
87486
+ return execFileSync6("git", args2, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
87179
87487
  } catch {
87180
87488
  return null;
87181
87489
  }
@@ -87609,9 +87917,9 @@ function ownerOf(records, absPath) {
87609
87917
  }
87610
87918
  async function startMultiDaemon(opts = {}) {
87611
87919
  const lockPath = globalDaemonLock();
87612
- if (opts.enforceSingleton !== false && isDaemonAlive(lockPath)) {
87920
+ if (opts.enforceSingleton !== false && daemonLockBlocksStartup(lockPath)) {
87613
87921
  throw new Error(
87614
- `an errata daemon is already running (lock: ${lockPath}). Run \`errata stop\` first, or attach to the running one.`
87922
+ `a daemon lock is live or its process ownership cannot be verified (lock: ${lockPath}). Run \`errata doctor\` to inspect it, or \`errata stop\` to stop a verified owner.`
87615
87923
  );
87616
87924
  }
87617
87925
  const cfg = loadConfig();
@@ -89298,8 +89606,8 @@ async function startMultiDaemon(opts = {}) {
89298
89606
  clearInterval(anchorPull);
89299
89607
  clearTimeout(codexWakeBoot);
89300
89608
  try {
89301
- const cur = readFileSync34(lockPath, "utf8");
89302
- if (JSON.parse(cur).pid === process.pid) unlinkSync3(lockPath);
89609
+ const cur = readFileSync35(lockPath, "utf8");
89610
+ if (JSON.parse(cur).pid === process.pid) unlinkSync4(lockPath);
89303
89611
  } catch {
89304
89612
  }
89305
89613
  await new Promise(
@@ -89659,6 +89967,7 @@ function openInBrowser(url2) {
89659
89967
  // src/cli.ts
89660
89968
  init_paths();
89661
89969
  init_checkout_root();
89970
+ init_lock();
89662
89971
 
89663
89972
  // src/cli-args.ts
89664
89973
  var BURST_VALUE_FLAGS = /* @__PURE__ */ new Set(["--limit", "--hops", "--max-hops"]);
@@ -89861,7 +90170,7 @@ import { spawn as spawn4 } from "node:child_process";
89861
90170
  import { createInterface } from "node:readline";
89862
90171
 
89863
90172
  // src/codex-hooks.ts
89864
- import { existsSync as existsSync34, mkdirSync as mkdirSync9, readFileSync as readFileSync35, writeFileSync as writeFileSync26 } from "node:fs";
90173
+ import { existsSync as existsSync34, mkdirSync as mkdirSync9, readFileSync as readFileSync36, writeFileSync as writeFileSync26 } from "node:fs";
89865
90174
  import { homedir as homedir7 } from "node:os";
89866
90175
  import { join as join42 } from "node:path";
89867
90176
 
@@ -89887,15 +90196,24 @@ function buildCodexHooksToml(port, options) {
89887
90196
  };
89888
90197
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid hook port");
89889
90198
  const command = (platform === "win32" ? "& " : "") + quote(options.nodePath) + " " + quote(options.relayPath) + " " + port;
89890
- const handlers = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"].map(
89891
- (event) => "[[hooks." + event + "]]\n" + (["UserPromptSubmit", "Stop"].includes(event) ? "" : 'matcher = ".*"\n') + "\n[[hooks." + event + '.hooks]]\ntype = "command"\ncommand = ' + JSON.stringify(command) + "\ntimeout = 10\n" + (event === "Stop" ? "" : "additionalContextLimit = 5000\n")
90199
+ const status = {
90200
+ SessionStart: "errata: loading workspace priors",
90201
+ UserPromptSubmit: "errata: checking workspace priors",
90202
+ PreToolUse: "errata: checking relevant priors",
90203
+ PostToolUse: "errata: capturing feedback and checking priors",
90204
+ Stop: "errata: capturing session feedback"
90205
+ };
90206
+ const handlers = Object.keys(status).map(
90207
+ (event) => "[[hooks." + event + "]]\n" + (["UserPromptSubmit", "Stop"].includes(event) ? "" : 'matcher = ".*"\n') + "\n[[hooks." + event + '.hooks]]\ntype = "command"\ncommand = ' + JSON.stringify(command) + "\nstatusMessage = " + JSON.stringify(status[event]) + "\ntimeout = 10\n" + (event === "Stop" ? "" : "additionalContextLimit = 5000\n")
89892
90208
  );
89893
90209
  return CODEX_HOOKS_BEGIN + "\n" + handlers.join("\n") + CODEX_HOOKS_END + "\n";
89894
90210
  }
89895
90211
 
89896
90212
  // src/codex-hook-relay.ts
89897
90213
  var CODEX_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata install-hooks codex
89898
- import { readFileSync } from 'node:fs';
90214
+ import { readFileSync, statSync } from 'node:fs';
90215
+ import { join } from 'node:path';
90216
+ const workspaceRoot = null;
89899
90217
 
89900
90218
  try {
89901
90219
  const payload = JSON.parse(readFileSync(0, 'utf8') || '{}');
@@ -89913,13 +90231,15 @@ try {
89913
90231
  method: 'POST', headers: { 'Content-Type': 'application/json' },
89914
90232
  body: JSON.stringify(payload), signal: AbortSignal.timeout(2000),
89915
90233
  });
89916
- if (!response.ok || !relay) return;
90234
+ if (!response.ok) return false;
90235
+ if (!relay) return true;
89917
90236
  const result = await response.json();
89918
90237
  const context = result.hookSpecificOutput?.additionalContext;
89919
90238
  if (typeof context === 'string' && context.trim()) contexts.push(context);
89920
- // The user-visible recall card; Codex renders it inline as '↳ Hook · …'.
90239
+ // Codex exposes systemMessage to the UI/event stream; styling is host-owned.
89921
90240
  if (typeof result.systemMessage === 'string' && result.systemMessage.trim()) messages.push(result.systemMessage);
89922
- } catch { /* An unavailable daemon must never block the agent. */ }
90241
+ return true;
90242
+ } catch { return false; /* An unavailable daemon must never block the agent. */ }
89923
90243
  };
89924
90244
  if (event === 'PreToolUse' || event === 'PostToolUse') await post('hook', true);
89925
90245
  // Stop sees the final assistant message, including turns with no tools.
@@ -89927,7 +90247,23 @@ try {
89927
90247
  if (['SessionStart', 'UserPromptSubmit', 'PostToolUse'].includes(event)) {
89928
90248
  // Run after capture, so changes already rendered by the daemon arrive now.
89929
90249
  // Background extraction finishing later arrives at the next tool boundary.
89930
- await post('context-inject', true);
90250
+ const available = await post('context-inject', true);
90251
+ // Startup/resume/compaction must not depend on the daemon's busy event loop.
90252
+ // Read only the installer's resolved workspace, never a caller-supplied path
90253
+ // or tracked AGENTS.md. SessionStart already re-injects unconditionally;
90254
+ // prompt/tool refreshes retain daemon hash deduplication (no repeated copies).
90255
+ if (!available && event === 'SessionStart' && workspaceRoot) {
90256
+ try {
90257
+ const path = join(workspaceRoot, '.errata', 'context.md');
90258
+ if (statSync(path).size <= 128 * 1024) {
90259
+ const body = readFileSync(path, 'utf8').trim();
90260
+ if (body) {
90261
+ contexts.push('Errata loaded the maintained local workspace context while the daemon response was unavailable. This block REPLACES any earlier errata context block in this conversation. It is the last saved snapshot; live updates resume when the daemon responds.\n\n' + body);
90262
+ if (!/^(0|off|false|no)$/i.test((process.env.ERRATA_RECALL_CARD || '').trim())) messages.push('⚡ errata · saved workspace priors loaded');
90263
+ }
90264
+ }
90265
+ } catch { /* No saved context is honest; never substitute another project. */ }
90266
+ }
89931
90267
  }
89932
90268
  if (contexts.length || messages.length) {
89933
90269
  process.stdout.write(JSON.stringify({
@@ -89939,8 +90275,12 @@ try {
89939
90275
  }
89940
90276
  } catch { /* Invalid input is also fail-open. */ }
89941
90277
  `;
90278
+ function buildCodexRelaySource(workspaceRoot) {
90279
+ return CODEX_RELAY_SOURCE.replace("const workspaceRoot = null;", () => `const workspaceRoot = ${JSON.stringify(workspaceRoot)};`);
90280
+ }
89942
90281
 
89943
90282
  // src/codex-hooks.ts
90283
+ init_checkout_root();
89944
90284
  var CODEX_HOOK_REVIEW_HELP = "Codex desktop: Settings > Hooks, select this project, expand each event and Trust the Errata hook. CLI users can use /hooks.";
89945
90285
  function hasCodexConfig(root, codexHome = process.env["CODEX_HOME"] || join42(homedir7(), ".codex")) {
89946
90286
  return existsSync34(join42(root, ".codex")) || existsSync34(codexHome);
@@ -89963,9 +90303,9 @@ function writeCodexHooks(root, port, nodePath = process.execPath) {
89963
90303
  const file2 = join42(dir, "config.toml");
89964
90304
  const relay = join42(dir, "hooks", "errata-relay.mjs");
89965
90305
  const block = buildCodexHooksToml(port, { nodePath, relayPath: relay });
89966
- const final = replaceCodexHooks(existsSync34(file2) ? readFileSync35(file2, "utf8") : "", block);
90306
+ const final = replaceCodexHooks(existsSync34(file2) ? readFileSync36(file2, "utf8") : "", block);
89967
90307
  mkdirSync9(join42(dir, "hooks"), { recursive: true });
89968
- writeFileSync26(relay, CODEX_RELAY_SOURCE, "utf8");
90308
+ writeFileSync26(relay, buildCodexRelaySource(checkoutRootOf(root)), "utf8");
89969
90309
  writeFileSync26(file2, final, "utf8");
89970
90310
  return file2;
89971
90311
  }
@@ -90133,7 +90473,7 @@ function consolidationGapMs(lastPassMs, policy) {
90133
90473
  }
90134
90474
 
90135
90475
  // src/claude-statusline.ts
90136
- import { existsSync as existsSync35, mkdirSync as mkdirSync10, readFileSync as readFileSync36, writeFileSync as writeFileSync27 } from "node:fs";
90476
+ import { existsSync as existsSync35, mkdirSync as mkdirSync10, readFileSync as readFileSync37, writeFileSync as writeFileSync27 } from "node:fs";
90137
90477
  import { homedir as homedir8 } from "node:os";
90138
90478
  import { dirname as dirname13, join as join43 } from "node:path";
90139
90479
  var STATUSLINE_RELAY_SOURCE = String.raw`// errata-managed: regenerated by errata init / install-hooks claude
@@ -90179,7 +90519,7 @@ function ensureUserStatusLine(opts) {
90179
90519
  let settings = {};
90180
90520
  if (existsSync35(settingsPath)) {
90181
90521
  try {
90182
- const parsed = JSON.parse(readFileSync36(settingsPath, "utf8"));
90522
+ const parsed = JSON.parse(readFileSync37(settingsPath, "utf8"));
90183
90523
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return "unreadable";
90184
90524
  settings = parsed;
90185
90525
  } catch {
@@ -90189,7 +90529,7 @@ function ensureUserStatusLine(opts) {
90189
90529
  if (settings["statusLine"] !== void 0 && !isErrataStatusLine(settings["statusLine"])) return "kept-own";
90190
90530
  const relay = join43(opts.relayDir, STATUSLINE_RELAY_FILE);
90191
90531
  mkdirSync10(opts.relayDir, { recursive: true });
90192
- if (!existsSync35(relay) || readFileSync36(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
90532
+ if (!existsSync35(relay) || readFileSync37(relay, "utf8") !== STATUSLINE_RELAY_SOURCE) {
90193
90533
  writeFileSync27(relay, STATUSLINE_RELAY_SOURCE, "utf8");
90194
90534
  }
90195
90535
  const next = buildStatusLine(opts.port, opts.nodePath ?? process.execPath, relay);
@@ -90732,6 +91072,9 @@ async function cmdStart() {
90732
91072
  const lockPath = globalDaemonLock();
90733
91073
  const running = isDaemonAlive(lockPath) ? readDaemonLock(lockPath) : null;
90734
91074
  const where = profile ? `${profile.name} (${profile.id})` : ROOT;
91075
+ if (!running && daemonLockState(readDaemonLock(lockPath), true) === "unknown") {
91076
+ throw new Error(`Cannot verify the daemon process owning ${lockPath}; leaving it untouched. Run errata doctor to inspect it.`);
91077
+ }
90735
91078
  if (running) {
90736
91079
  console.log(`errata: workspace registered; attached to the running daemon`);
90737
91080
  console.log(` workspace: ${where}`);
@@ -91790,7 +92133,7 @@ async function cmdReview() {
91790
92133
  console.log("(review queue empty)");
91791
92134
  return;
91792
92135
  }
91793
- const queue = JSON.parse(readFileSync37(paths.reviewQueue, "utf8"));
92136
+ const queue = JSON.parse(readFileSync38(paths.reviewQueue, "utf8"));
91794
92137
  if (queue.length === 0) {
91795
92138
  console.log("(review queue empty)");
91796
92139
  return;
@@ -92522,31 +92865,20 @@ async function cmdReport(args2) {
92522
92865
  console.log(` open: file://${indexPath.replace(/\\/g, "/")}`);
92523
92866
  }
92524
92867
  async function cmdStop() {
92525
- const { unlinkSync: unlinkSync4 } = await import("node:fs");
92526
92868
  const lockPath = [globalDaemonLock(), workspacePaths(ROOT).daemonLock].find((p) => readDaemonLock(p) !== null);
92527
92869
  if (!lockPath) {
92528
92870
  console.log(`no daemon lock found \u2014 nothing to stop`);
92529
92871
  return;
92530
92872
  }
92531
- const lock = readDaemonLock(lockPath);
92532
- if (!isProcessAlive(lock.pid)) {
92533
- try {
92534
- unlinkSync4(lockPath);
92535
- console.log(`removed stale lock (pid ${lock.pid} not running)`);
92536
- } catch {
92537
- }
92538
- return;
92539
- }
92540
92873
  try {
92541
- process.kill(lock.pid, "SIGTERM");
92542
- console.log(`sent SIGTERM to daemon (pid ${lock.pid})`);
92874
+ const result = stopLockedDaemon(lockPath);
92875
+ if (result === "signaled") console.log(`sent SIGTERM to the verified daemon process`);
92876
+ else if (result === "stale") console.log(`removed stale daemon lock; no process was signaled`);
92877
+ else if (result === "missing") console.log(`no daemon lock found \u2014 nothing to stop`);
92878
+ else throw new Error(`daemon lock ownership is ${result}; no process was signaled and the lock was preserved`);
92543
92879
  } catch (err2) {
92544
- console.warn(`could not signal pid ${lock.pid}: ${err2 instanceof Error ? err2.message : err2}`);
92545
- try {
92546
- unlinkSync4(lockPath);
92547
- console.log(`removed stale lock file`);
92548
- } catch {
92549
- }
92880
+ console.warn(`could not stop daemon: ${err2 instanceof Error ? err2.message : err2}`);
92881
+ process.exitCode = 1;
92550
92882
  }
92551
92883
  }
92552
92884
  async function cmdMcp(args2) {
@@ -92557,9 +92889,9 @@ async function cmdMcp(args2) {
92557
92889
  await runMcpServer2(ROOT, { toolNameStyle: grokCompatibility ? "bare" : "prefixed" });
92558
92890
  }
92559
92891
  function selfArgv(sub) {
92560
- const self = process.execPath;
92561
- if (/errata(\.exe)?$/i.test(self)) return { cmd: self, args: [sub] };
92562
- return { cmd: self, args: [...process.execArgv, process.argv[1] ?? "", sub] };
92892
+ const self2 = process.execPath;
92893
+ if (/errata(\.exe)?$/i.test(self2)) return { cmd: self2, args: [sub] };
92894
+ return { cmd: self2, args: [...process.execArgv, process.argv[1] ?? "", sub] };
92563
92895
  }
92564
92896
  function spawnDaemonDetached(options = {}) {
92565
92897
  const { cmd: cmd2, args: args2 } = selfArgv("dash");
@@ -92594,11 +92926,11 @@ function spawnDaemonDetached(options = {}) {
92594
92926
  }
92595
92927
  function ensureSingletonRunning(options = {}) {
92596
92928
  try {
92597
- if (isDaemonAlive(globalDaemonLock())) return true;
92929
+ if (daemonLockBlocksStartup(globalDaemonLock())) return true;
92598
92930
  spawnDaemonDetached(options);
92599
92931
  void (async () => {
92600
92932
  if (await waitForDaemon(globalDaemonLock(), 8e3)) return;
92601
- if (isDaemonAlive(globalDaemonLock())) return;
92933
+ if (daemonLockBlocksStartup(globalDaemonLock())) return;
92602
92934
  spawnDaemonDetached(options);
92603
92935
  await waitForDaemon(globalDaemonLock(), 8e3);
92604
92936
  })();
@@ -92634,13 +92966,13 @@ async function cmdInstallHooks(args2) {
92634
92966
  }
92635
92967
  }
92636
92968
  async function installOpencodePlugin(port) {
92637
- const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync38, writeFileSync: writeFileSync28 } = await import("node:fs");
92969
+ const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync39, writeFileSync: writeFileSync28 } = await import("node:fs");
92638
92970
  const { join: join45 } = await import("node:path");
92639
92971
  const dir = join45(ROOT, ".opencode", "plugin");
92640
92972
  if (!existsSync37(dir)) mkdirSync11(dir, { recursive: true });
92641
92973
  const file2 = join45(dir, "inerrata.js");
92642
92974
  if (existsSync37(file2)) {
92643
- const head2 = readFileSync38(file2, "utf8").split("\n")[0] ?? "";
92975
+ const head2 = readFileSync39(file2, "utf8").split("\n")[0] ?? "";
92644
92976
  if (head2.trim() !== OPENCODE_PLUGIN_MARKER) {
92645
92977
  console.error(`refusing to overwrite ${file2}: not an errata-managed plugin (line 1 lacks the marker)`);
92646
92978
  process.exit(2);
@@ -92651,7 +92983,7 @@ async function installOpencodePlugin(port) {
92651
92983
  let cfg = { $schema: "https://opencode.ai/config.json" };
92652
92984
  if (existsSync37(cfgFile)) {
92653
92985
  try {
92654
- cfg = JSON.parse(readFileSync38(cfgFile, "utf8"));
92986
+ cfg = JSON.parse(readFileSync39(cfgFile, "utf8"));
92655
92987
  } catch {
92656
92988
  console.error(`refusing to touch invalid JSON at ${cfgFile} \u2014 fix it and re-run`);
92657
92989
  process.exit(2);
@@ -92689,7 +93021,7 @@ function ensureStatusLine(port) {
92689
93021
  }
92690
93022
  }
92691
93023
  async function installClaudeHooks(port) {
92692
- const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync38, writeFileSync: writeFileSync28 } = await import("node:fs");
93024
+ const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync39, writeFileSync: writeFileSync28 } = await import("node:fs");
92693
93025
  const { join: join45 } = await import("node:path");
92694
93026
  const dir = join45(ROOT, ".claude");
92695
93027
  if (!existsSync37(dir)) mkdirSync11(dir, { recursive: true });
@@ -92697,7 +93029,7 @@ async function installClaudeHooks(port) {
92697
93029
  let settings = {};
92698
93030
  if (existsSync37(file2)) {
92699
93031
  try {
92700
- settings = JSON.parse(readFileSync38(file2, "utf8"));
93032
+ settings = JSON.parse(readFileSync39(file2, "utf8"));
92701
93033
  } catch {
92702
93034
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
92703
93035
  process.exit(2);
@@ -92761,7 +93093,7 @@ async function installClaudeHooks(port) {
92761
93093
  console.log(` endpoint: http://127.0.0.1:${port}/api/hook`);
92762
93094
  }
92763
93095
  async function installClaudeMcpConfig() {
92764
- const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync38, writeFileSync: writeFileSync28 } = await import("node:fs");
93096
+ const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync39, writeFileSync: writeFileSync28 } = await import("node:fs");
92765
93097
  const { join: join45, dirname: dirname14 } = await import("node:path");
92766
93098
  const file2 = join45(ROOT, ".mcp.json");
92767
93099
  const dir = dirname14(file2);
@@ -92769,7 +93101,7 @@ async function installClaudeMcpConfig() {
92769
93101
  let cfg = {};
92770
93102
  if (existsSync37(file2)) {
92771
93103
  try {
92772
- cfg = JSON.parse(readFileSync38(file2, "utf8"));
93104
+ cfg = JSON.parse(readFileSync39(file2, "utf8"));
92773
93105
  } catch {
92774
93106
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
92775
93107
  process.exit(2);
@@ -92783,7 +93115,7 @@ async function installClaudeMcpConfig() {
92783
93115
  console.log(` Tools: errata.search / locate / neighbors / callers_of / what_uses / show / similar`);
92784
93116
  }
92785
93117
  async function installCursorMcpConfig() {
92786
- const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync38, writeFileSync: writeFileSync28 } = await import("node:fs");
93118
+ const { mkdirSync: mkdirSync11, existsSync: existsSync37, readFileSync: readFileSync39, writeFileSync: writeFileSync28 } = await import("node:fs");
92787
93119
  const { join: join45 } = await import("node:path");
92788
93120
  const dir = join45(ROOT, ".cursor");
92789
93121
  if (!existsSync37(dir)) mkdirSync11(dir, { recursive: true });
@@ -92791,7 +93123,7 @@ async function installCursorMcpConfig() {
92791
93123
  let cfg = {};
92792
93124
  if (existsSync37(file2)) {
92793
93125
  try {
92794
- cfg = JSON.parse(readFileSync38(file2, "utf8"));
93126
+ cfg = JSON.parse(readFileSync39(file2, "utf8"));
92795
93127
  } catch {
92796
93128
  console.error(`refusing to overwrite invalid JSON at ${file2}`);
92797
93129
  process.exit(2);