@inerrata-corporation/errata 2.0.2-dev.334 → 2.0.2-dev.338

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.
@@ -16630,6 +16630,9 @@ init_src2();
16630
16630
  // ../../packages/local-graph/src/problem-package-link.ts
16631
16631
  init_src();
16632
16632
 
16633
+ // ../../packages/local-graph/src/anchor-backfill.ts
16634
+ init_src();
16635
+
16633
16636
  // ../../packages/local-graph/src/percolate.ts
16634
16637
  init_src2();
16635
16638
  var PERCOLATING_LABELS = ["Claim", "Problem", "Solution"];
package/errata.mjs CHANGED
@@ -18597,6 +18597,9 @@ function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, sou
18597
18597
  }
18598
18598
  return existing.id;
18599
18599
  }
18600
+ function isAutoMinted(n) {
18601
+ return n.label === "Solution" && String(n.description ?? "").startsWith(AUTO_MINT_PREFIX);
18602
+ }
18600
18603
  function priorsForFile(store, relPath) {
18601
18604
  const want = relPath.trim().replace(/^\.?\//, "");
18602
18605
  let file2 = null;
@@ -18623,7 +18626,7 @@ function priorsForFile(store, relPath) {
18623
18626
  } else if (n.attrs["resolvedAs"] == null && n.attrs["mergedInto"] === void 0 && e.attrs?.["anchorProvenance"] !== "legacy") {
18624
18627
  resolvedProblems.push(n);
18625
18628
  }
18626
- } else if (CITABLE_PRIOR_LABELS.has(n.label)) {
18629
+ } else if (CITABLE_PRIOR_LABELS.has(n.label) && !isAutoMinted(n)) {
18627
18630
  related.set(n.id, n);
18628
18631
  }
18629
18632
  }
@@ -18631,8 +18634,8 @@ function priorsForFile(store, relPath) {
18631
18634
  for (const p of [...openProblems, ...constraints, ...resolvedProblems]) {
18632
18635
  for (const e of store.outEdges(p.id, ["SOLVED_BY", "CAUSED_BY", "INSTANCE_OF"])) {
18633
18636
  const n = store.getNode(e.to);
18634
- if (n && CITABLE_PRIOR_LABELS.has(n.label)) related.set(n.id, n);
18635
- if (n && n.label === "Solution" && e.type === "SOLVED_BY") {
18637
+ if (n && CITABLE_PRIOR_LABELS.has(n.label) && !isAutoMinted(n)) related.set(n.id, n);
18638
+ if (n && n.label === "Solution" && e.type === "SOLVED_BY" && !isAutoMinted(n)) {
18636
18639
  const list = solutionsByProblem.get(p.id) ?? [];
18637
18640
  if (!list.some((s) => s.id === n.id)) list.push(n);
18638
18641
  solutionsByProblem.set(p.id, list);
@@ -18741,6 +18744,28 @@ function closeDesignProblem(store, problemId, fixNote, t) {
18741
18744
  });
18742
18745
  return true;
18743
18746
  }
18747
+ function reopenAutoClosedProblems(store, t) {
18748
+ const report = { reopened: 0, alreadySuspect: 0, agentDescribed: 0 };
18749
+ for (const p of store.findNodesByLabel("Problem")) {
18750
+ if (p.attrs["resolvedAt"] == null) continue;
18751
+ if (p.attrs["resolvedAs"] != null) continue;
18752
+ if (p.attrs["mergedInto"] !== void 0) continue;
18753
+ const sols = store.outEdges(p.id, ["SOLVED_BY"]).map((e) => store.getNode(e.to)).filter((n) => n !== null);
18754
+ if (sols.length === 0) continue;
18755
+ if (!sols.every((s) => String(s.description ?? "").startsWith(AUTO_MINT_PREFIX))) {
18756
+ report.agentDescribed++;
18757
+ continue;
18758
+ }
18759
+ if (p.attrs["resolutionSuspect"] === true) report.alreadySuspect++;
18760
+ const attrs = { ...p.attrs };
18761
+ delete attrs["resolvedAt"];
18762
+ attrs["reopenedFrom"] = "auto-close";
18763
+ attrs["reopenedAt"] = t;
18764
+ store.updateNode(p.id, { attrs, lastUpdatedAt: t });
18765
+ report.reopened++;
18766
+ }
18767
+ return report;
18768
+ }
18744
18769
  function designProblemId(statement) {
18745
18770
  return identityId({ kind: "DesignProblem", statement });
18746
18771
  }
@@ -18770,7 +18795,7 @@ function retractDesignProblem(store, problemId, resolvedAs, reason, t) {
18770
18795
  }
18771
18796
  return true;
18772
18797
  }
18773
- function resolveDesignProblems(store, t) {
18798
+ function markFixCandidates(store, t) {
18774
18799
  let resolved = 0;
18775
18800
  for (const p of store.findNodesByLabel("Problem")) {
18776
18801
  if (!p.id.startsWith("dprob_")) continue;
@@ -18781,6 +18806,7 @@ function resolveDesignProblems(store, t) {
18781
18806
  let edited = false;
18782
18807
  for (const e of store.outEdges(p.id, ["ANCHORED_AT"])) {
18783
18808
  if (e.attrs?.["mention"] === true) continue;
18809
+ if (e.attrs?.["anchorProvenance"] === "legacy") continue;
18784
18810
  const sym = store.getNode(e.to);
18785
18811
  if (sym && sym.lastUpdatedAt > p.createdAt) {
18786
18812
  edited = true;
@@ -18790,24 +18816,21 @@ function resolveDesignProblems(store, t) {
18790
18816
  }
18791
18817
  }
18792
18818
  if (!edited) continue;
18793
- if (store.outEdges(p.id, ["SOLVED_BY"]).length === 0) {
18794
- const solId = `dfix_${digest({ problemId: p.id, edit: t })}`.slice(0, 56);
18795
- store.mergeNode(
18796
- buildNode(solId, "Solution", `${AUTO_MINT_PREFIX}${symName}`, t, {
18797
- source: "convo",
18798
- provisional: false,
18799
- // AC-resolution-attrs: the resolving symbol/file as STRUCTURED data, not
18800
- // just prose — the F2 join key downstream backfills and the viz read.
18801
- resolvedSymbols: [symName],
18802
- ...symRelPath ? { resolvedRelPath: symRelPath } : {}
18803
- })
18804
- );
18805
- mergeEdge(store, p.id, solId, "SOLVED_BY", t);
18806
- for (const ae of store.outEdges(p.id, ["ANCHORED_AT"]))
18807
- mergeEdge(store, solId, ae.to, "ANCHORED_AT", t);
18808
- }
18819
+ if (p.attrs["fixCandidateAt"] !== void 0) continue;
18820
+ if (store.outEdges(p.id, ["SOLVED_BY"]).some((e) => {
18821
+ const s = store.getNode(e.to);
18822
+ return s !== null && !String(s.description ?? "").startsWith(AUTO_MINT_PREFIX);
18823
+ }))
18824
+ continue;
18809
18825
  store.updateNode(p.id, {
18810
- attrs: { ...p.attrs, provisional: false, resolvedAt: t },
18826
+ attrs: {
18827
+ ...p.attrs,
18828
+ // The cue, kept as structured data so the render can name the file it is
18829
+ // asking about. Deliberately NOT `resolvedAt` — this is a question.
18830
+ fixCandidateAt: t,
18831
+ fixCandidateSymbol: symName,
18832
+ ...symRelPath ? { fixCandidateFile: symRelPath } : {}
18833
+ },
18811
18834
  lastUpdatedAt: t
18812
18835
  });
18813
18836
  resolved++;
@@ -18864,6 +18887,80 @@ var init_design_problem = __esm({
18864
18887
  function isLegacyAnchor(attrs) {
18865
18888
  return attrs?.["captureTime"] === true && attrs["anchorProvenance"] === void 0;
18866
18889
  }
18890
+ function repairLegacyAnchorsFromStatement(store, ts) {
18891
+ const report = {
18892
+ repaired: [],
18893
+ noPathNamed: 0,
18894
+ pathUnknown: 0,
18895
+ alreadyCorrect: 0
18896
+ };
18897
+ const byPath = /* @__PURE__ */ new Map();
18898
+ for (const f of store.findNodesByLabel("File")) {
18899
+ const path2 = String(f.attrs["relPath"] ?? f.description ?? "");
18900
+ if (path2) byPath.set(path2, { id: f.id, path: path2 });
18901
+ }
18902
+ const resolvePath2 = (named) => {
18903
+ const exact = byPath.get(named);
18904
+ if (exact) return exact;
18905
+ const hits = [...byPath.values()].filter(
18906
+ (f) => f.path.endsWith(`/${named}`) || named.endsWith(`/${f.path}`)
18907
+ );
18908
+ return hits.length === 1 ? hits[0] : null;
18909
+ };
18910
+ for (const p of store.findNodesByLabel("Problem")) {
18911
+ try {
18912
+ const anchors = store.outEdges(p.id, ["ANCHORED_AT"]);
18913
+ const legacy = anchors.filter((e) => e.attrs?.["anchorProvenance"] === "legacy");
18914
+ if (legacy.length === 0) continue;
18915
+ if (anchors.some((e) => e.attrs?.["anchorProvenance"] === "restated")) continue;
18916
+ const named = [...String(p.description ?? "").matchAll(STATEMENT_PATH_RE)].map((m) => m[0]);
18917
+ if (named.length === 0) {
18918
+ report.noPathNamed++;
18919
+ continue;
18920
+ }
18921
+ const guessedPaths = legacy.map((e) => {
18922
+ const f = store.getNode(e.to);
18923
+ return String(f?.attrs["relPath"] ?? f?.description ?? "");
18924
+ });
18925
+ if (named.some((x) => guessedPaths.some((g) => g.endsWith(x) || x.endsWith(g)))) {
18926
+ report.alreadyCorrect++;
18927
+ continue;
18928
+ }
18929
+ const target = named.map(resolvePath2).find((f) => f !== null);
18930
+ if (!target) {
18931
+ report.pathUnknown++;
18932
+ continue;
18933
+ }
18934
+ store.mergeEdge({
18935
+ id: `edge_${digest({ from: p.id, type: "ANCHORED_AT", to: target.id })}`.slice(0, 24),
18936
+ from: p.id,
18937
+ to: target.id,
18938
+ type: "ANCHORED_AT",
18939
+ // Same confidence a capture-time anchor enters at: the source is the
18940
+ // agent's own statement either way, only the moment of reading differs.
18941
+ confidence: 0.4,
18942
+ extractionSource: "agent-observed",
18943
+ createdAt: ts,
18944
+ lastSeenAt: ts,
18945
+ navSuccesses: 0,
18946
+ navFailures: 0,
18947
+ attrs: {
18948
+ anchorProvenance: "restated",
18949
+ restatedFrom: guessedPaths[0] ?? "",
18950
+ restatedAt: ts
18951
+ }
18952
+ });
18953
+ report.repaired.push({
18954
+ problemId: p.id,
18955
+ problem: p.description,
18956
+ guessed: guessedPaths[0] ?? "",
18957
+ restated: target.path
18958
+ });
18959
+ } catch {
18960
+ }
18961
+ }
18962
+ return report;
18963
+ }
18867
18964
  function backfillLegacyAnchors(store, ts) {
18868
18965
  const report = {
18869
18966
  demotedEdges: 0,
@@ -18921,11 +19018,14 @@ function backfillLegacyAnchors(store, ts) {
18921
19018
  }
18922
19019
  return report;
18923
19020
  }
19021
+ var STATEMENT_PATH_RE;
18924
19022
  var init_anchor_backfill = __esm({
18925
19023
  "../../packages/local-graph/src/anchor-backfill.ts"() {
18926
19024
  "use strict";
19025
+ init_src();
18927
19026
  init_justification();
18928
19027
  init_design_problem();
19028
+ STATEMENT_PATH_RE = /(?:[\w.-]+\/)+[\w.-]+\.(?:ts|tsx|js|mjs|cjs|py|go|rs|java|rb|c|cpp|h)/g;
18929
19029
  }
18930
19030
  });
18931
19031
 
@@ -21070,6 +21170,7 @@ __export(src_exports2, {
21070
21170
  listNeedsRevisit: () => listNeedsRevisit,
21071
21171
  localEdgeViolation: () => localEdgeViolation,
21072
21172
  markDiscriminatorAsked: () => markDiscriminatorAsked,
21173
+ markFixCandidates: () => markFixCandidates,
21073
21174
  markRevisit: () => markRevisit,
21074
21175
  matchLanguagesInText: () => matchLanguagesInText,
21075
21176
  matchPackagesInText: () => matchPackagesInText,
@@ -21099,9 +21200,10 @@ __export(src_exports2, {
21099
21200
  recordTriageObservation: () => recordTriageObservation,
21100
21201
  reinforceSameAnchorProblem: () => reinforceSameAnchorProblem,
21101
21202
  relateClaims: () => relateClaims,
21203
+ reopenAutoClosedProblems: () => reopenAutoClosedProblems,
21204
+ repairLegacyAnchorsFromStatement: () => repairLegacyAnchorsFromStatement,
21102
21205
  resolveDesignProblemById: () => resolveDesignProblemById,
21103
21206
  resolveDesignProblemByStatement: () => resolveDesignProblemByStatement,
21104
- resolveDesignProblems: () => resolveDesignProblems,
21105
21207
  resolveOsNode: () => resolveOsNode,
21106
21208
  retractDesignProblemById: () => retractDesignProblemById,
21107
21209
  retractDesignProblemByStatement: () => retractDesignProblemByStatement,
@@ -21281,7 +21383,11 @@ function sliceForFile(store, relPath) {
21281
21383
  }
21282
21384
  const fp = priorsForFile(store, relPath);
21283
21385
  const priors = fp ? {
21284
- openProblems: fp.openProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.description })),
21386
+ openProblems: fp.openProblems.slice(0, 3).map((p) => ({
21387
+ id: p.id,
21388
+ description: p.description,
21389
+ ...typeof p.attrs["fixCandidateFile"] === "string" ? { fixCandidateFile: p.attrs["fixCandidateFile"] } : {}
21390
+ })),
21285
21391
  constraints: fp.constraints.slice(0, 2).map((c) => ({ id: c.id, description: c.description })),
21286
21392
  resolvedProblems: fp.resolvedProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.description })),
21287
21393
  related: fp.related.slice(0, 4).map((n) => ({ id: n.id, label: n.label, description: n.description })),
@@ -21462,6 +21568,11 @@ function renderSnapshot(s) {
21462
21568
  );
21463
21569
  for (const p of w.priors.openProblems) {
21464
21570
  lines.push(` - ${p.description} \u2192 \`(fix:[${p.id}])\` \xB7 \`([${p.id}])\``);
21571
+ if (p.fixCandidateFile) {
21572
+ lines.push(
21573
+ ` - \`${p.fixCandidateFile}\` was edited after this was flagged. If that change fixed it, say so \u2014 \`(fix:[${p.id}])\`. If it did not, leave it open; nothing closes this but you.`
21574
+ );
21575
+ }
21465
21576
  const sols = w.priors.solutionsByProblem[p.id] ?? [];
21466
21577
  for (const s2 of sols) {
21467
21578
  lines.push(
@@ -27828,7 +27939,8 @@ function runNightlyPipeline(store) {
27828
27939
  for (const [id, c] of comm.community) store.updateNode(id, { community: c });
27829
27940
  });
27830
27941
  const motifs = promoteMotifs(store, started2);
27831
- const designResolved = resolveDesignProblems(store, started2);
27942
+ reopenAutoClosedProblems(store, started2);
27943
+ const designResolved = markFixCandidates(store, started2);
27832
27944
  const cry = crystallize(store, { ts: started2 });
27833
27945
  return {
27834
27946
  scoredNodes: scored,
@@ -53035,7 +53147,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
53035
53147
  }
53036
53148
 
53037
53149
  // src/engine.ts
53038
- var DAEMON_VERSION = true ? "2.0.2-dev.334" : "2.0.0-alpha.0";
53150
+ var DAEMON_VERSION = true ? "2.0.2-dev.338" : "2.0.0-alpha.0";
53039
53151
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
53040
53152
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
53041
53153
  var GIT_OP_MUTE_MS = 4e3;
@@ -53479,7 +53591,13 @@ function createWorkspaceEngine(opts) {
53479
53591
  const runNightlyInline = () => {
53480
53592
  try {
53481
53593
  const a = backfillLegacyAnchors(store, Date.now());
53482
- if (a.demotedEdges > 0 || a.suspects.length > 0) {
53594
+ const rep = repairLegacyAnchorsFromStatement(store, Date.now());
53595
+ if (rep.repaired.length > 0) {
53596
+ console.log(
53597
+ `[errata] re-anchored ${rep.repaired.length} legacy-guessed problem(s) onto the file their statement names`
53598
+ );
53599
+ }
53600
+ if (a.demotedEdges > 0 || a.suspects.length > 0 || rep.repaired.length > 0) {
53483
53601
  logAnchorBackfill(a.demotedEdges, a.suspects.length);
53484
53602
  refreshContextNow();
53485
53603
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.334",
3
+ "version": "2.0.2-dev.338",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -25266,43 +25266,6 @@ function backfillProblemContext(store2, ts) {
25266
25266
  function isConstraintProblem(node) {
25267
25267
  return node.attrs["kind"] === "constraint";
25268
25268
  }
25269
- function buildNode(id, label, description, ts, attrs) {
25270
- return {
25271
- id,
25272
- label,
25273
- description,
25274
- extractionConfidence: 0.4,
25275
- // provisional: below the error-path's 0.6+
25276
- extractionSource: "agent-observed",
25277
- embedding: [],
25278
- cumulativeSurprise: 0,
25279
- peakSurprise: 0,
25280
- cumulativeHits: 1,
25281
- lastUpdatedAt: ts,
25282
- createdAt: ts,
25283
- memoryTier: "short-term",
25284
- pageRank: 0,
25285
- isLandmark: false,
25286
- community: null,
25287
- stability: "unstable",
25288
- attrs
25289
- };
25290
- }
25291
- function mergeEdge(store2, from, to, type, ts) {
25292
- store2.mergeEdge({
25293
- id: `edge_${digest({ from, type, to })}`.slice(0, 24),
25294
- from,
25295
- to,
25296
- type,
25297
- confidence: 0.4,
25298
- extractionSource: "agent-observed",
25299
- createdAt: ts,
25300
- lastSeenAt: ts,
25301
- navSuccesses: 0,
25302
- navFailures: 0,
25303
- attrs: { provisional: true }
25304
- });
25305
- }
25306
25269
  var CITABLE_PRIOR_LABELS = /* @__PURE__ */ new Set([
25307
25270
  "Solution",
25308
25271
  "RootCause",
@@ -25310,6 +25273,9 @@ var CITABLE_PRIOR_LABELS = /* @__PURE__ */ new Set([
25310
25273
  "Technique",
25311
25274
  "AntiPattern"
25312
25275
  ]);
25276
+ function isAutoMinted(n) {
25277
+ return n.label === "Solution" && String(n.description ?? "").startsWith(AUTO_MINT_PREFIX);
25278
+ }
25313
25279
  function priorsForFile(store2, relPath) {
25314
25280
  const want = relPath.trim().replace(/^\.?\//, "");
25315
25281
  let file2 = null;
@@ -25336,7 +25302,7 @@ function priorsForFile(store2, relPath) {
25336
25302
  } else if (n.attrs["resolvedAs"] == null && n.attrs["mergedInto"] === void 0 && e.attrs?.["anchorProvenance"] !== "legacy") {
25337
25303
  resolvedProblems.push(n);
25338
25304
  }
25339
- } else if (CITABLE_PRIOR_LABELS.has(n.label)) {
25305
+ } else if (CITABLE_PRIOR_LABELS.has(n.label) && !isAutoMinted(n)) {
25340
25306
  related.set(n.id, n);
25341
25307
  }
25342
25308
  }
@@ -25344,8 +25310,8 @@ function priorsForFile(store2, relPath) {
25344
25310
  for (const p of [...openProblems, ...constraints, ...resolvedProblems]) {
25345
25311
  for (const e of store2.outEdges(p.id, ["SOLVED_BY", "CAUSED_BY", "INSTANCE_OF"])) {
25346
25312
  const n = store2.getNode(e.to);
25347
- if (n && CITABLE_PRIOR_LABELS.has(n.label)) related.set(n.id, n);
25348
- if (n && n.label === "Solution" && e.type === "SOLVED_BY") {
25313
+ if (n && CITABLE_PRIOR_LABELS.has(n.label) && !isAutoMinted(n)) related.set(n.id, n);
25314
+ if (n && n.label === "Solution" && e.type === "SOLVED_BY" && !isAutoMinted(n)) {
25349
25315
  const list = solutionsByProblem.get(p.id) ?? [];
25350
25316
  if (!list.some((s) => s.id === n.id)) list.push(n);
25351
25317
  solutionsByProblem.set(p.id, list);
@@ -25378,8 +25344,30 @@ function recordAnchorHint(store2, problemId, relPath, provenance, ts) {
25378
25344
  });
25379
25345
  return true;
25380
25346
  }
25347
+ function reopenAutoClosedProblems(store2, t) {
25348
+ const report = { reopened: 0, alreadySuspect: 0, agentDescribed: 0 };
25349
+ for (const p of store2.findNodesByLabel("Problem")) {
25350
+ if (p.attrs["resolvedAt"] == null) continue;
25351
+ if (p.attrs["resolvedAs"] != null) continue;
25352
+ if (p.attrs["mergedInto"] !== void 0) continue;
25353
+ const sols = store2.outEdges(p.id, ["SOLVED_BY"]).map((e) => store2.getNode(e.to)).filter((n) => n !== null);
25354
+ if (sols.length === 0) continue;
25355
+ if (!sols.every((s) => String(s.description ?? "").startsWith(AUTO_MINT_PREFIX))) {
25356
+ report.agentDescribed++;
25357
+ continue;
25358
+ }
25359
+ if (p.attrs["resolutionSuspect"] === true) report.alreadySuspect++;
25360
+ const attrs = { ...p.attrs };
25361
+ delete attrs["resolvedAt"];
25362
+ attrs["reopenedFrom"] = "auto-close";
25363
+ attrs["reopenedAt"] = t;
25364
+ store2.updateNode(p.id, { attrs, lastUpdatedAt: t });
25365
+ report.reopened++;
25366
+ }
25367
+ return report;
25368
+ }
25381
25369
  var AUTO_MINT_PREFIX = "addressed by an edit to ";
25382
- function resolveDesignProblems(store2, t) {
25370
+ function markFixCandidates(store2, t) {
25383
25371
  let resolved = 0;
25384
25372
  for (const p of store2.findNodesByLabel("Problem")) {
25385
25373
  if (!p.id.startsWith("dprob_")) continue;
@@ -25390,6 +25378,7 @@ function resolveDesignProblems(store2, t) {
25390
25378
  let edited = false;
25391
25379
  for (const e of store2.outEdges(p.id, ["ANCHORED_AT"])) {
25392
25380
  if (e.attrs?.["mention"] === true) continue;
25381
+ if (e.attrs?.["anchorProvenance"] === "legacy") continue;
25393
25382
  const sym = store2.getNode(e.to);
25394
25383
  if (sym && sym.lastUpdatedAt > p.createdAt) {
25395
25384
  edited = true;
@@ -25399,24 +25388,21 @@ function resolveDesignProblems(store2, t) {
25399
25388
  }
25400
25389
  }
25401
25390
  if (!edited) continue;
25402
- if (store2.outEdges(p.id, ["SOLVED_BY"]).length === 0) {
25403
- const solId = `dfix_${digest({ problemId: p.id, edit: t })}`.slice(0, 56);
25404
- store2.mergeNode(
25405
- buildNode(solId, "Solution", `${AUTO_MINT_PREFIX}${symName}`, t, {
25406
- source: "convo",
25407
- provisional: false,
25408
- // AC-resolution-attrs: the resolving symbol/file as STRUCTURED data, not
25409
- // just prose — the F2 join key downstream backfills and the viz read.
25410
- resolvedSymbols: [symName],
25411
- ...symRelPath ? { resolvedRelPath: symRelPath } : {}
25412
- })
25413
- );
25414
- mergeEdge(store2, p.id, solId, "SOLVED_BY", t);
25415
- for (const ae of store2.outEdges(p.id, ["ANCHORED_AT"]))
25416
- mergeEdge(store2, solId, ae.to, "ANCHORED_AT", t);
25417
- }
25391
+ if (p.attrs["fixCandidateAt"] !== void 0) continue;
25392
+ if (store2.outEdges(p.id, ["SOLVED_BY"]).some((e) => {
25393
+ const s = store2.getNode(e.to);
25394
+ return s !== null && !String(s.description ?? "").startsWith(AUTO_MINT_PREFIX);
25395
+ }))
25396
+ continue;
25418
25397
  store2.updateNode(p.id, {
25419
- attrs: { ...p.attrs, provisional: false, resolvedAt: t },
25398
+ attrs: {
25399
+ ...p.attrs,
25400
+ // The cue, kept as structured data so the render can name the file it is
25401
+ // asking about. Deliberately NOT `resolvedAt` — this is a question.
25402
+ fixCandidateAt: t,
25403
+ fixCandidateSymbol: symName,
25404
+ ...symRelPath ? { fixCandidateFile: symRelPath } : {}
25405
+ },
25420
25406
  lastUpdatedAt: t
25421
25407
  });
25422
25408
  resolved++;
@@ -25425,9 +25411,85 @@ function resolveDesignProblems(store2, t) {
25425
25411
  }
25426
25412
 
25427
25413
  // ../../packages/local-graph/src/anchor-backfill.ts
25414
+ init_src();
25428
25415
  function isLegacyAnchor(attrs) {
25429
25416
  return attrs?.["captureTime"] === true && attrs["anchorProvenance"] === void 0;
25430
25417
  }
25418
+ var STATEMENT_PATH_RE = /(?:[\w.-]+\/)+[\w.-]+\.(?:ts|tsx|js|mjs|cjs|py|go|rs|java|rb|c|cpp|h)/g;
25419
+ function repairLegacyAnchorsFromStatement(store2, ts) {
25420
+ const report = {
25421
+ repaired: [],
25422
+ noPathNamed: 0,
25423
+ pathUnknown: 0,
25424
+ alreadyCorrect: 0
25425
+ };
25426
+ const byPath = /* @__PURE__ */ new Map();
25427
+ for (const f of store2.findNodesByLabel("File")) {
25428
+ const path = String(f.attrs["relPath"] ?? f.description ?? "");
25429
+ if (path) byPath.set(path, { id: f.id, path });
25430
+ }
25431
+ const resolvePath = (named) => {
25432
+ const exact = byPath.get(named);
25433
+ if (exact) return exact;
25434
+ const hits = [...byPath.values()].filter(
25435
+ (f) => f.path.endsWith(`/${named}`) || named.endsWith(`/${f.path}`)
25436
+ );
25437
+ return hits.length === 1 ? hits[0] : null;
25438
+ };
25439
+ for (const p of store2.findNodesByLabel("Problem")) {
25440
+ try {
25441
+ const anchors = store2.outEdges(p.id, ["ANCHORED_AT"]);
25442
+ const legacy = anchors.filter((e) => e.attrs?.["anchorProvenance"] === "legacy");
25443
+ if (legacy.length === 0) continue;
25444
+ if (anchors.some((e) => e.attrs?.["anchorProvenance"] === "restated")) continue;
25445
+ const named = [...String(p.description ?? "").matchAll(STATEMENT_PATH_RE)].map((m) => m[0]);
25446
+ if (named.length === 0) {
25447
+ report.noPathNamed++;
25448
+ continue;
25449
+ }
25450
+ const guessedPaths = legacy.map((e) => {
25451
+ const f = store2.getNode(e.to);
25452
+ return String(f?.attrs["relPath"] ?? f?.description ?? "");
25453
+ });
25454
+ if (named.some((x) => guessedPaths.some((g) => g.endsWith(x) || x.endsWith(g)))) {
25455
+ report.alreadyCorrect++;
25456
+ continue;
25457
+ }
25458
+ const target = named.map(resolvePath).find((f) => f !== null);
25459
+ if (!target) {
25460
+ report.pathUnknown++;
25461
+ continue;
25462
+ }
25463
+ store2.mergeEdge({
25464
+ id: `edge_${digest({ from: p.id, type: "ANCHORED_AT", to: target.id })}`.slice(0, 24),
25465
+ from: p.id,
25466
+ to: target.id,
25467
+ type: "ANCHORED_AT",
25468
+ // Same confidence a capture-time anchor enters at: the source is the
25469
+ // agent's own statement either way, only the moment of reading differs.
25470
+ confidence: 0.4,
25471
+ extractionSource: "agent-observed",
25472
+ createdAt: ts,
25473
+ lastSeenAt: ts,
25474
+ navSuccesses: 0,
25475
+ navFailures: 0,
25476
+ attrs: {
25477
+ anchorProvenance: "restated",
25478
+ restatedFrom: guessedPaths[0] ?? "",
25479
+ restatedAt: ts
25480
+ }
25481
+ });
25482
+ report.repaired.push({
25483
+ problemId: p.id,
25484
+ problem: p.description,
25485
+ guessed: guessedPaths[0] ?? "",
25486
+ restated: target.path
25487
+ });
25488
+ } catch {
25489
+ }
25490
+ }
25491
+ return report;
25492
+ }
25431
25493
  function backfillLegacyAnchors(store2, ts) {
25432
25494
  const report = {
25433
25495
  demotedEdges: 0,
@@ -26140,7 +26202,8 @@ function runNightlyPipeline(store2) {
26140
26202
  for (const [id, c] of comm.community) store2.updateNode(id, { community: c });
26141
26203
  });
26142
26204
  const motifs = promoteMotifs(store2, started);
26143
- const designResolved = resolveDesignProblems(store2, started);
26205
+ reopenAutoClosedProblems(store2, started);
26206
+ const designResolved = markFixCandidates(store2, started);
26144
26207
  const cry = crystallize(store2, { ts: started });
26145
26208
  return {
26146
26209
  scoredNodes: scored,
@@ -26333,7 +26396,11 @@ function sliceForFile(store2, relPath) {
26333
26396
  }
26334
26397
  const fp = priorsForFile(store2, relPath);
26335
26398
  const priors = fp ? {
26336
- openProblems: fp.openProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.description })),
26399
+ openProblems: fp.openProblems.slice(0, 3).map((p) => ({
26400
+ id: p.id,
26401
+ description: p.description,
26402
+ ...typeof p.attrs["fixCandidateFile"] === "string" ? { fixCandidateFile: p.attrs["fixCandidateFile"] } : {}
26403
+ })),
26337
26404
  constraints: fp.constraints.slice(0, 2).map((c) => ({ id: c.id, description: c.description })),
26338
26405
  resolvedProblems: fp.resolvedProblems.slice(0, 3).map((p) => ({ id: p.id, description: p.description })),
26339
26406
  related: fp.related.slice(0, 4).map((n) => ({ id: n.id, label: n.label, description: n.description })),
@@ -26537,6 +26604,7 @@ function runNightly() {
26537
26604
  const a = backfillLegacyAnchors(store, Date.now());
26538
26605
  anchorsDemoted = a.demotedEdges;
26539
26606
  resolutionsSuspect = a.suspects.length;
26607
+ repairLegacyAnchorsFromStatement(store, Date.now());
26540
26608
  } catch {
26541
26609
  }
26542
26610
  const report = runNightlyPipeline(store);