@inerrata-corporation/errata 2.0.2-dev.1263 → 2.0.2-dev.1274

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/errata.mjs +528 -20
  2. package/package.json +1 -1
  3. package/pass-worker.mjs +16 -1
package/errata.mjs CHANGED
@@ -18962,11 +18962,16 @@ function foldDuplicateProblem(store, dup, survivor, ts) {
18962
18962
  if (!surv) return;
18963
18963
  const sources = new Set(surv.attrs["sources"] ?? []);
18964
18964
  for (const s of dup.attrs["sources"] ?? []) sources.add(s);
18965
+ const dupSeq = dup.attrs["createdAtSeq"] ?? store.currentIngestSeq();
18966
+ const exposure = priorExposure(store, survivor.id, dupSeq);
18967
+ const expField = `reinforce${exposure[0].toUpperCase()}${exposure.slice(1)}Count`;
18965
18968
  store.updateNode(survivor.id, {
18966
18969
  attrs: {
18967
18970
  ...surv.attrs,
18968
18971
  sources: [...sources],
18969
- corroborations: corroborations(surv) + corroborations(dup) + 1
18972
+ corroborations: corroborations(surv) + corroborations(dup) + 1,
18973
+ [expField]: (surv.attrs[expField] ?? 0) + 1,
18974
+ lastReinforceExposure: exposure
18970
18975
  },
18971
18976
  cumulativeHits: (surv.cumulativeHits ?? 0) + (dup.cumulativeHits ?? 0),
18972
18977
  lastUpdatedAt: ts
@@ -19400,22 +19405,61 @@ function tokenJaccard(a, b) {
19400
19405
  return inter / (sa.size + sb.size - inter);
19401
19406
  }
19402
19407
  function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
19408
+ return foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind).foldedId;
19409
+ }
19410
+ function foldSameAnchorProblem(store, relPath, workspaceId2, statement, source, ts, kind = "problem") {
19411
+ const report = { ...EMPTY_FOLD_REPORT };
19403
19412
  const stmt = statement.trim();
19404
- if (!isAnchorableCodePath(relPath)) return null;
19413
+ if (!isAnchorableCodePath(relPath)) {
19414
+ report.noFile = true;
19415
+ return report;
19416
+ }
19405
19417
  const file2 = resolveFileNode(store, relPath, workspaceId2);
19406
- if (!file2) return null;
19418
+ if (!file2) {
19419
+ report.noFile = true;
19420
+ return report;
19421
+ }
19407
19422
  const selfId = identityId({ kind: "DesignProblem", statement: stmt });
19423
+ let stmtVec = null;
19408
19424
  let best = null;
19425
+ let bestScout = null;
19409
19426
  for (const e of store.inEdges(file2.id, ["ANCHORED_AT"])) {
19410
19427
  const cand = store.getNode(e.from);
19411
19428
  if (!cand || cand.label !== "Problem" || cand.attrs["resolvedAt"]) continue;
19412
19429
  if (cand.id === selfId) continue;
19413
19430
  const candKind = cand.attrs["kind"] === "constraint" ? "constraint" : "problem";
19414
- if (candKind !== (kind === "constraint" ? "constraint" : "problem")) continue;
19431
+ if (candKind !== (kind === "constraint" ? "constraint" : "problem")) {
19432
+ report.kindVetoed++;
19433
+ continue;
19434
+ }
19435
+ report.candidates++;
19415
19436
  const score2 = tokenJaccard(stmt, cand.description);
19437
+ if (score2 > report.bestJaccard) report.bestJaccard = score2;
19416
19438
  if (score2 >= SAME_ANCHOR_DEDUP_JACCARD && (!best || score2 > best.score)) best = { node: cand, score: score2 };
19439
+ if (!best) {
19440
+ stmtVec ??= embed(stmt);
19441
+ const hc = cosine(stmtVec, embed(cand.description));
19442
+ if (hc > report.bestHashCos) report.bestHashCos = hc;
19443
+ if ((hc >= SAME_ANCHOR_SCOUT_MIN_COSINE || score2 >= SAME_ANCHOR_NOMINATE_JACCARD) && (!bestScout || hc > bestScout.hashCos)) {
19444
+ bestScout = { node: cand, hashCos: hc, jaccard: score2 };
19445
+ }
19446
+ }
19417
19447
  }
19418
- if (!best) return null;
19448
+ if (!best && bestScout && bestScout.hashCos >= SAME_ANCHOR_SCOUT_AUTO_COSINE) {
19449
+ best = { node: bestScout.node, score: bestScout.jaccard };
19450
+ report.foldedBy = "scout";
19451
+ }
19452
+ if (!best) {
19453
+ if (bestScout) {
19454
+ report.nominate = {
19455
+ targetId: bestScout.node.id,
19456
+ jaccard: bestScout.jaccard,
19457
+ hashCos: bestScout.hashCos
19458
+ };
19459
+ }
19460
+ return report;
19461
+ }
19462
+ report.foldedBy ??= "jaccard";
19419
19463
  const existing = best.node;
19420
19464
  const sources = existing.attrs["sources"] ?? [];
19421
19465
  if (!sources.includes(source)) {
@@ -19437,7 +19481,8 @@ function reinforceSameAnchorProblem(store, relPath, workspaceId2, statement, sou
19437
19481
  lastUpdatedAt: ts
19438
19482
  });
19439
19483
  }
19440
- return existing.id;
19484
+ report.foldedId = existing.id;
19485
+ return report;
19441
19486
  }
19442
19487
  function findResolvedAnchorMatch(store, relPath, workspaceId2, statement, kind = "problem") {
19443
19488
  const stmt = statement.trim();
@@ -19911,7 +19956,7 @@ function priorExposure(store, nodeId, atSeq) {
19911
19956
  if ((n.attrs["evictedCount"] ?? 0) > 0) return "evicted";
19912
19957
  return "unshown";
19913
19958
  }
19914
- var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, RECURRENCE_DEBOUNCE_MS, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, REOPEN_MIN_SOURCES, RECURRENCE_SOURCES_CAP, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, AUTO_MINT_PREFIX, FIX_CANDIDATE_ASK_LIMIT;
19959
+ var DESIGN_PROMOTE_AT, STATEMENT_ALIAS_CAP, RECURRENCE_DEBOUNCE_MS, PATTERN_DEDUP_COSINE, PATTERN_ALIAS_CAP, DEDUP_STOPWORDS, SAME_ANCHOR_DEDUP_JACCARD, EMPTY_FOLD_REPORT, SAME_ANCHOR_SCOUT_AUTO_COSINE, SAME_ANCHOR_SCOUT_MIN_COSINE, SAME_ANCHOR_NOMINATE_JACCARD, REOPEN_MIN_SOURCES, RECURRENCE_SOURCES_CAP, CITABLE_PRIOR_LABELS, MAX_ANCHOR_FILES, AUTO_MINT_PREFIX, FIX_CANDIDATE_ASK_LIMIT;
19915
19960
  var init_design_problem = __esm({
19916
19961
  "../../packages/local-graph/src/design-problem.ts"() {
19917
19962
  "use strict";
@@ -19951,6 +19996,19 @@ var init_design_problem = __esm({
19951
19996
  "where"
19952
19997
  ]);
19953
19998
  SAME_ANCHOR_DEDUP_JACCARD = 0.6;
19999
+ EMPTY_FOLD_REPORT = {
20000
+ foldedId: null,
20001
+ foldedBy: null,
20002
+ noFile: false,
20003
+ candidates: 0,
20004
+ kindVetoed: 0,
20005
+ bestJaccard: 0,
20006
+ bestHashCos: 0,
20007
+ nominate: null
20008
+ };
20009
+ SAME_ANCHOR_SCOUT_AUTO_COSINE = 0.85;
20010
+ SAME_ANCHOR_SCOUT_MIN_COSINE = 0.5;
20011
+ SAME_ANCHOR_NOMINATE_JACCARD = 0.3;
19954
20012
  REOPEN_MIN_SOURCES = 2;
19955
20013
  RECURRENCE_SOURCES_CAP = 8;
19956
20014
  CITABLE_PRIOR_LABELS = /* @__PURE__ */ new Set([
@@ -22195,6 +22253,160 @@ var init_community2 = __esm({
22195
22253
  }
22196
22254
  });
22197
22255
 
22256
+ // ../../packages/local-graph/src/fold-candidates.ts
22257
+ function markFoldCandidate(store, problemId, targetId, info2) {
22258
+ if (problemId === targetId) return false;
22259
+ const n = store.getNode(problemId);
22260
+ if (!openProblem(n)) return false;
22261
+ if (n.attrs["foldCandidate"] !== void 0) return false;
22262
+ const rejected = n.attrs["foldRejected"] ?? [];
22263
+ if (rejected.includes(targetId)) return false;
22264
+ if (!openProblem(store.getNode(targetId))) return false;
22265
+ const mark = {
22266
+ targetId,
22267
+ ...info2.hashCos !== void 0 ? { hashCos: info2.hashCos } : {},
22268
+ ...info2.jaccard !== void 0 ? { jaccard: info2.jaccard } : {},
22269
+ source: info2.source,
22270
+ ts: info2.ts,
22271
+ markedAtSeq: store.currentIngestSeq()
22272
+ };
22273
+ store.updateNode(problemId, { attrs: { ...n.attrs, foldCandidate: mark }, lastUpdatedAt: info2.ts });
22274
+ return true;
22275
+ }
22276
+ function findWorkspaceFoldCandidate(store, workspaceId2, statement, kind, opts) {
22277
+ const stmtVec = embed(statement);
22278
+ const wantConstraint = kind === "constraint";
22279
+ const open2 = store.findNodesByLabel("Problem").filter(
22280
+ (p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.id !== opts?.excludeId && (p.attrs["workspaceId"] === void 0 || p.attrs["workspaceId"] === workspaceId2) && isConstraintProblem(p) === wantConstraint
22281
+ ).sort((a, b) => (b.lastUpdatedAt ?? b.createdAt) - (a.lastUpdatedAt ?? a.createdAt)).slice(0, opts?.scanCap ?? WORKSPACE_SCAN_CAP);
22282
+ let best = null;
22283
+ for (const p of open2) {
22284
+ const c = cosine(stmtVec, embed(p.description));
22285
+ if (c >= FOLD_SCOUT_MIN_COSINE && (!best || c > best.hashCos)) best = { targetId: p.id, hashCos: c };
22286
+ }
22287
+ return best;
22288
+ }
22289
+ function followMerges(store, id) {
22290
+ let cur = store.getNode(id);
22291
+ for (let hop = 0; hop < 3 && cur && cur.attrs["mergedInto"] !== void 0; hop++) {
22292
+ cur = store.getNode(String(cur.attrs["mergedInto"]));
22293
+ }
22294
+ return cur ?? null;
22295
+ }
22296
+ function clearCandidate(store, node2, ts, rejectTargetId) {
22297
+ const attrs = { ...node2.attrs };
22298
+ delete attrs["foldCandidate"];
22299
+ if (rejectTargetId) {
22300
+ const rejected = attrs["foldRejected"] ?? [];
22301
+ if (!rejected.includes(rejectTargetId)) {
22302
+ attrs["foldRejected"] = [...rejected, rejectTargetId].slice(-FOLD_REJECTED_CAP);
22303
+ }
22304
+ }
22305
+ store.updateNode(node2.id, { attrs, lastUpdatedAt: ts });
22306
+ }
22307
+ async function resolveFoldCandidates(store, opts) {
22308
+ const report = {
22309
+ examined: 0,
22310
+ folded: 0,
22311
+ foldedEmbedding: 0,
22312
+ foldedJudge: 0,
22313
+ rejected: 0,
22314
+ standing: 0,
22315
+ judgeErrors: 0,
22316
+ retargeted: 0
22317
+ };
22318
+ const limit = opts.limit ?? 8;
22319
+ const marked = store.findNodesByLabel("Problem").filter((p) => openProblem(p) && p.attrs["foldCandidate"] !== void 0).sort((a, b) => {
22320
+ const ma = a.attrs["foldCandidate"].ts ?? 0;
22321
+ const mb = b.attrs["foldCandidate"].ts ?? 0;
22322
+ return ma - mb;
22323
+ }).slice(0, limit);
22324
+ for (const node2 of marked) {
22325
+ const fresh = store.getNode(node2.id);
22326
+ if (!openProblem(fresh)) continue;
22327
+ report.examined++;
22328
+ const mark = fresh.attrs["foldCandidate"];
22329
+ let target = followMerges(store, mark.targetId);
22330
+ if (target && target.id !== mark.targetId) report.retargeted++;
22331
+ if (!openProblem(target)) {
22332
+ clearCandidate(store, fresh, opts.ts);
22333
+ report.rejected++;
22334
+ continue;
22335
+ }
22336
+ if (isConstraintProblem(fresh) !== isConstraintProblem(target)) {
22337
+ clearCandidate(store, fresh, opts.ts, target.id);
22338
+ report.rejected++;
22339
+ continue;
22340
+ }
22341
+ let cos = null;
22342
+ if (fresh.embedding.length > 0 && fresh.embedding.length === target.embedding.length && (fresh.attrs["embeddingVersion"] ?? "") === (target.attrs["embeddingVersion"] ?? "")) {
22343
+ cos = cosine(fresh.embedding, target.embedding);
22344
+ }
22345
+ if (cos !== null && cos >= FOLD_EMBED_AUTO_COSINE) {
22346
+ foldPair(store, fresh, target, opts.ts);
22347
+ report.folded++;
22348
+ report.foldedEmbedding++;
22349
+ continue;
22350
+ }
22351
+ if (cos !== null && cos < FOLD_JUDGE_MIN_COSINE) {
22352
+ clearCandidate(store, fresh, opts.ts, target.id);
22353
+ report.rejected++;
22354
+ continue;
22355
+ }
22356
+ if (!opts.judge) {
22357
+ report.standing++;
22358
+ continue;
22359
+ }
22360
+ let verdict = null;
22361
+ try {
22362
+ verdict = await opts.judge(fresh.description, target.description);
22363
+ } catch {
22364
+ verdict = null;
22365
+ }
22366
+ if (verdict === "same") {
22367
+ foldPair(store, fresh, target, opts.ts);
22368
+ report.folded++;
22369
+ report.foldedJudge++;
22370
+ } else if (verdict === "distinct") {
22371
+ clearCandidate(store, fresh, opts.ts, target.id);
22372
+ report.rejected++;
22373
+ } else {
22374
+ report.judgeErrors++;
22375
+ report.standing++;
22376
+ }
22377
+ }
22378
+ return report;
22379
+ }
22380
+ function foldPair(store, a, b, ts) {
22381
+ const corro = (n) => Number(n.attrs["corroborations"] ?? 0);
22382
+ const survivor = corro(a) !== corro(b) ? corro(a) > corro(b) ? a : b : a.createdAt <= b.createdAt ? a : b;
22383
+ const dup = survivor === a ? b : a;
22384
+ for (const n of [a, b]) {
22385
+ if (n.attrs["foldCandidate"] !== void 0) clearCandidate(store, n, ts);
22386
+ }
22387
+ const freshSurvivor = store.getNode(survivor.id);
22388
+ const freshDup = store.getNode(dup.id);
22389
+ if (!freshSurvivor || !freshDup) return;
22390
+ foldDuplicateProblem(store, freshDup, freshSurvivor, ts);
22391
+ }
22392
+ var FOLD_SCOUT_AUTO_COSINE, FOLD_SCOUT_MIN_COSINE, FOLD_SCOUT_MIN_JACCARD, FOLD_EMBED_AUTO_COSINE, FOLD_JUDGE_MIN_COSINE, FOLD_REJECTED_CAP, WORKSPACE_SCAN_CAP, openProblem;
22393
+ var init_fold_candidates = __esm({
22394
+ "../../packages/local-graph/src/fold-candidates.ts"() {
22395
+ "use strict";
22396
+ init_src3();
22397
+ init_design_problem();
22398
+ init_problem_dedup();
22399
+ FOLD_SCOUT_AUTO_COSINE = 0.85;
22400
+ FOLD_SCOUT_MIN_COSINE = 0.5;
22401
+ FOLD_SCOUT_MIN_JACCARD = 0.3;
22402
+ FOLD_EMBED_AUTO_COSINE = 0.88;
22403
+ FOLD_JUDGE_MIN_COSINE = 0.5;
22404
+ FOLD_REJECTED_CAP = 8;
22405
+ WORKSPACE_SCAN_CAP = 400;
22406
+ openProblem = (n) => !!n && n.label === "Problem" && n.attrs["resolvedAt"] == null && n.attrs["mergedInto"] === void 0;
22407
+ }
22408
+ });
22409
+
22198
22410
  // ../../packages/local-graph/src/tools.ts
22199
22411
  function toolNodeId(name2) {
22200
22412
  return `tool:${name2}`;
@@ -22649,6 +22861,32 @@ var init_mechanism_liveness = __esm({
22649
22861
  backlogCounter: "anchorHintsStanding",
22650
22862
  note: "drains as hinted files change; contentChangedAt is forward-only so the backlog moves with real edits"
22651
22863
  },
22864
+ {
22865
+ id: "fold-candidate-resolve",
22866
+ what: "resolves parked paraphrase-fold nominations (embedding-certain bands auto, ambiguous band via the equivalence judge)",
22867
+ // The settle boundary's fold-judge pass is the sole consumer of
22868
+ // attrs.foldCandidate; producers are the file/workspace scouts (capture)
22869
+ // and the settle near-miss band (mergeProblemsByEmbedding).
22870
+ pass: "fold-judge",
22871
+ // Conditional mark, rare by design (a nomination is a near-miss event) —
22872
+ // the bar is "the field is written at all", the revisit-sweep precedent.
22873
+ fed: { labels: ["Problem"], attr: "foldCandidate", minFraction: 0 },
22874
+ effectCounter: "folded",
22875
+ // Standing nominations ARE the judge-less backlog — without the gauge a
22876
+ // deployment with no judge configured reads as "nothing to fold".
22877
+ backlogCounter: "standing",
22878
+ note: "standing > 0 with folded 0 = the judge lane is unconfigured (ERRATA_FOLD_JUDGE / azure env), not an empty queue"
22879
+ },
22880
+ {
22881
+ id: "cite-anchor-accrual",
22882
+ what: "accrues cite-context anchor evidence on anchorless cited priors; two distinct sessions promote a cite-confirmed ANCHORED_AT",
22883
+ pass: "capture",
22884
+ // Conditional attr: only anchorless semantic priors cited from a known
22885
+ // working file carry hints — "written at all" is the honest floor.
22886
+ fed: { labels: ["Problem"], attr: "citeAnchorHints", minFraction: 0 },
22887
+ effectCounter: "citeAnchorsPromoted",
22888
+ note: "the supply-side fix for corroboration 0-for-8: promoted anchors are what the overlap gate intersects on the NEXT citation"
22889
+ },
22652
22890
  {
22653
22891
  id: "wm-reinforce-exposure",
22654
22892
  what: "labels each paraphrase re-derivation with the prior's exposure (shown/evicted/unshown)",
@@ -22769,6 +23007,12 @@ __export(src_exports2, {
22769
23007
  CREDIT_FAMILY: () => CREDIT_FAMILY,
22770
23008
  DEFAULT_MIN_COVERAGE: () => DEFAULT_MIN_COVERAGE,
22771
23009
  FIX_CANDIDATE_ASK_LIMIT: () => FIX_CANDIDATE_ASK_LIMIT,
23010
+ FOLD_EMBED_AUTO_COSINE: () => FOLD_EMBED_AUTO_COSINE,
23011
+ FOLD_JUDGE_MIN_COSINE: () => FOLD_JUDGE_MIN_COSINE,
23012
+ FOLD_REJECTED_CAP: () => FOLD_REJECTED_CAP,
23013
+ FOLD_SCOUT_AUTO_COSINE: () => FOLD_SCOUT_AUTO_COSINE,
23014
+ FOLD_SCOUT_MIN_COSINE: () => FOLD_SCOUT_MIN_COSINE,
23015
+ FOLD_SCOUT_MIN_JACCARD: () => FOLD_SCOUT_MIN_JACCARD,
22772
23016
  JOINT_COMMUNITY_EDGES: () => JOINT_COMMUNITY_EDGES,
22773
23017
  JOINT_COMMUNITY_LABELS: () => JOINT_COMMUNITY_LABELS,
22774
23018
  MAX_ANCHOR_FILES: () => MAX_ANCHOR_FILES,
@@ -22781,10 +23025,14 @@ __export(src_exports2, {
22781
23025
  RECURRENCE_DEBOUNCE_MS: () => RECURRENCE_DEBOUNCE_MS,
22782
23026
  REOPEN_MIN_SOURCES: () => REOPEN_MIN_SOURCES,
22783
23027
  SAME_ANCHOR_DEDUP_JACCARD: () => SAME_ANCHOR_DEDUP_JACCARD,
23028
+ SAME_ANCHOR_NOMINATE_JACCARD: () => SAME_ANCHOR_NOMINATE_JACCARD,
23029
+ SAME_ANCHOR_SCOUT_AUTO_COSINE: () => SAME_ANCHOR_SCOUT_AUTO_COSINE,
23030
+ SAME_ANCHOR_SCOUT_MIN_COSINE: () => SAME_ANCHOR_SCOUT_MIN_COSINE,
22784
23031
  STALL_MIN_RUNS: () => STALL_MIN_RUNS,
22785
23032
  STALL_MIN_SPAN_MS: () => STALL_MIN_SPAN_MS,
22786
23033
  SqliteGraphStore: () => SqliteGraphStore,
22787
23034
  WM_CALIBRATION_LABELS: () => WM_CALIBRATION_LABELS,
23035
+ WORKSPACE_SCAN_CAP: () => WORKSPACE_SCAN_CAP,
22788
23036
  addDependency: () => addDependency,
22789
23037
  aggregateClaimConfidence: () => aggregateClaimConfidence,
22790
23038
  anchorProblemToDiff: () => anchorProblemToDiff,
@@ -22825,8 +23073,10 @@ __export(src_exports2, {
22825
23073
  findPath: () => findPath,
22826
23074
  findResolvedAnchorMatch: () => findResolvedAnchorMatch,
22827
23075
  findReusableSolution: () => findReusableSolution,
23076
+ findWorkspaceFoldCandidate: () => findWorkspaceFoldCandidate,
22828
23077
  flattenCloudCounts: () => flattenCloudCounts,
22829
23078
  foldDuplicateProblem: () => foldDuplicateProblem,
23079
+ foldSameAnchorProblem: () => foldSameAnchorProblem,
22830
23080
  formatMechanismStatus: () => formatMechanismStatus,
22831
23081
  getClaim: () => getClaim,
22832
23082
  harvestAbstractionFences: () => harvestAbstractionFences,
@@ -22846,6 +23096,7 @@ __export(src_exports2, {
22846
23096
  localEdgeViolation: () => localEdgeViolation,
22847
23097
  markDiscriminatorAsked: () => markDiscriminatorAsked,
22848
23098
  markFixCandidates: () => markFixCandidates,
23099
+ markFoldCandidate: () => markFoldCandidate,
22849
23100
  markResolvedRecurrence: () => markResolvedRecurrence,
22850
23101
  markRevisit: () => markRevisit,
22851
23102
  matchLanguagesInText: () => matchLanguagesInText,
@@ -22887,6 +23138,7 @@ __export(src_exports2, {
22887
23138
  repairLegacyAnchorsFromStatement: () => repairLegacyAnchorsFromStatement,
22888
23139
  resolveDesignProblemById: () => resolveDesignProblemById,
22889
23140
  resolveDesignProblemByStatement: () => resolveDesignProblemByStatement,
23141
+ resolveFoldCandidates: () => resolveFoldCandidates,
22890
23142
  resolveOsNode: () => resolveOsNode,
22891
23143
  retractDesignProblemById: () => retractDesignProblemById,
22892
23144
  retractDesignProblemByStatement: () => retractDesignProblemByStatement,
@@ -22925,6 +23177,7 @@ var init_src5 = __esm({
22925
23177
  init_community2();
22926
23178
  init_triage2();
22927
23179
  init_problem_dedup();
23180
+ init_fold_candidates();
22928
23181
  init_problem_package_link();
22929
23182
  init_tools();
22930
23183
  init_principle_sync();
@@ -29579,7 +29832,8 @@ function rankOpenProblemNeighbors(store, embedding, limit = 5) {
29579
29832
  }
29580
29833
  function mergeProblemsByEmbedding(store, opts) {
29581
29834
  const minCosine = opts.minCosine ?? NEMORI_DEDUP_COSINE;
29582
- const report = { clusters: 0, merged: 0 };
29835
+ const NEAR_MISS_FLOOR = 0.72;
29836
+ const report = { clusters: 0, merged: 0, nearMissMarked: 0 };
29583
29837
  const open2 = store.findNodesByLabel("Problem").filter(
29584
29838
  (p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0 && p.embedding.length > 0
29585
29839
  );
@@ -29594,9 +29848,15 @@ function mergeProblemsByEmbedding(store, opts) {
29594
29848
  if (b.embedding.length !== a.embedding.length) continue;
29595
29849
  if ((a.attrs["embeddingVersion"] ?? "") !== (b.attrs["embeddingVersion"] ?? "")) continue;
29596
29850
  if (isConstraintProblem(a) !== isConstraintProblem(b)) continue;
29597
- if (cosine(a.embedding, b.embedding) >= minCosine) {
29851
+ const cos = cosine(a.embedding, b.embedding);
29852
+ if (cos >= minCosine) {
29598
29853
  cluster.push(b);
29599
29854
  consumed.add(b.id);
29855
+ } else if (cos >= NEAR_MISS_FLOOR) {
29856
+ const [younger, elder] = a.createdAt >= b.createdAt ? [a, b] : [b, a];
29857
+ if (markFoldCandidate(store, younger.id, elder.id, { hashCos: cos, source: "settle-nearmiss", ts: opts.ts })) {
29858
+ report.nearMissMarked = (report.nearMissMarked ?? 0) + 1;
29859
+ }
29600
29860
  }
29601
29861
  }
29602
29862
  if (cluster.length < 2) continue;
@@ -53256,6 +53516,91 @@ function openEventLog(opts) {
53256
53516
  return new EventLog(opts);
53257
53517
  }
53258
53518
 
53519
+ // src/llm-provider.ts
53520
+ function chatProvider() {
53521
+ return (process.env["EXTRACTION_PROVIDER"] ?? "azure").toLowerCase() === "anthropic" ? "anthropic" : "azure";
53522
+ }
53523
+ function deploymentFor(model) {
53524
+ const key = `MODEL_${model.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_OPENAI`;
53525
+ return process.env[key] ?? process.env["AZURE_OPENAI_DEPLOYMENT"];
53526
+ }
53527
+ function isChatConfigured(model) {
53528
+ if (chatProvider() === "azure") {
53529
+ return Boolean(
53530
+ process.env["AZURE_OPENAI_API_KEY"] && process.env["AZURE_OPENAI_ENDPOINT"] && deploymentFor(model)
53531
+ );
53532
+ }
53533
+ return Boolean(process.env["ANTHROPIC_API_KEY"]);
53534
+ }
53535
+ async function chat(req) {
53536
+ const provider = chatProvider();
53537
+ try {
53538
+ if (provider === "azure") {
53539
+ const endpoint = (process.env["AZURE_OPENAI_ENDPOINT"] ?? "").replace(/\/+$/, "");
53540
+ const version2 = process.env["AZURE_OPENAI_API_VERSION"] ?? "2024-10-21";
53541
+ const deployment = deploymentFor(req.model);
53542
+ if (!endpoint || !deployment) {
53543
+ console.warn("[errata] llm: azure selected but endpoint/deployment missing \u2014 skipping");
53544
+ return null;
53545
+ }
53546
+ const resp2 = await fetch(
53547
+ `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${version2}`,
53548
+ {
53549
+ method: "POST",
53550
+ headers: {
53551
+ "api-key": process.env["AZURE_OPENAI_API_KEY"] ?? "",
53552
+ "content-type": "application/json"
53553
+ },
53554
+ body: JSON.stringify({
53555
+ messages: [
53556
+ ...req.system ? [{ role: "system", content: req.system }] : [],
53557
+ { role: "user", content: req.user }
53558
+ ],
53559
+ // `max_completion_tokens`: the newer deployments reject `max_tokens`.
53560
+ max_completion_tokens: req.maxTokens
53561
+ })
53562
+ }
53563
+ );
53564
+ if (!resp2.ok) {
53565
+ console.warn(
53566
+ `[errata] llm: azure ${resp2.status} \u2014 ${(await resp2.text().catch(() => "")).slice(0, 200)}`
53567
+ );
53568
+ return null;
53569
+ }
53570
+ const json3 = await resp2.json();
53571
+ return json3.choices?.[0]?.message?.content ?? null;
53572
+ }
53573
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
53574
+ method: "POST",
53575
+ headers: {
53576
+ "x-api-key": process.env["ANTHROPIC_API_KEY"] ?? "",
53577
+ "anthropic-version": "2023-06-01",
53578
+ "content-type": "application/json"
53579
+ },
53580
+ body: JSON.stringify({
53581
+ model: req.model,
53582
+ max_tokens: req.maxTokens,
53583
+ ...req.system ? { system: req.system } : {},
53584
+ messages: [{ role: "user", content: req.user }]
53585
+ })
53586
+ });
53587
+ if (!resp.ok) {
53588
+ console.warn(
53589
+ `[errata] llm: anthropic ${resp.status} \u2014 ${(await resp.text().catch(() => "")).slice(0, 200)}`
53590
+ );
53591
+ return null;
53592
+ }
53593
+ const json2 = await resp.json();
53594
+ return json2.content?.find((c) => c.type === "text")?.text ?? null;
53595
+ } catch (err2) {
53596
+ console.warn(
53597
+ `[errata] llm: ${provider} transport failed \u2014`,
53598
+ err2 instanceof Error ? err2.message : err2
53599
+ );
53600
+ return null;
53601
+ }
53602
+ }
53603
+
53259
53604
  // src/engine.ts
53260
53605
  init_src11();
53261
53606
  init_src6();
@@ -54478,7 +54823,52 @@ function readPrimingHandles(path2) {
54478
54823
  function resolveHandle(store, handle2, handleMap) {
54479
54824
  return handleMap[handle2]?.id ?? (store.getNode(handle2) ? handle2 : void 0);
54480
54825
  }
54481
- function mintPriorEdge(store, source, target, sentence, touched, ts) {
54826
+ var CITE_ANCHOR_LABELS = /* @__PURE__ */ new Set(["Problem", "Solution", "RootCause", "Pattern", "Claim", "Technique", "AntiPattern"]);
54827
+ var CITE_ANCHOR_PROMOTE_SESSIONS = 2;
54828
+ var CITE_ANCHOR_MAX_FILES = 4;
54829
+ var CITE_ANCHOR_MAX_SESSIONS = 6;
54830
+ function accrueCiteAnchor(store, targetId, touched, sessionId, ts) {
54831
+ let fileId;
54832
+ for (const tid of touched) {
54833
+ if (store.getNode(tid)?.label === "File") {
54834
+ fileId = tid;
54835
+ break;
54836
+ }
54837
+ }
54838
+ if (!fileId) return { accrued: false, promoted: false };
54839
+ const node2 = store.getNode(targetId);
54840
+ if (!node2) return { accrued: false, promoted: false };
54841
+ const hints = { ...node2.attrs["citeAnchorHints"] ?? {} };
54842
+ const entry = hints[fileId] ?? [];
54843
+ if (entry.includes(sessionId)) return { accrued: false, promoted: false };
54844
+ if (hints[fileId] === void 0 && Object.keys(hints).length >= CITE_ANCHOR_MAX_FILES) {
54845
+ return { accrued: false, promoted: false };
54846
+ }
54847
+ const grown = [...entry, sessionId].slice(-CITE_ANCHOR_MAX_SESSIONS);
54848
+ let promoted = false;
54849
+ if (grown.length >= CITE_ANCHOR_PROMOTE_SESSIONS) {
54850
+ store.mergeEdge({
54851
+ id: `edge_${digest({ from: targetId, type: "ANCHORED_AT", to: fileId })}`.slice(0, 24),
54852
+ from: targetId,
54853
+ to: fileId,
54854
+ type: "ANCHORED_AT",
54855
+ confidence: 0.3,
54856
+ extractionSource: "agent-observed",
54857
+ createdAt: ts,
54858
+ lastSeenAt: ts,
54859
+ navSuccesses: 0,
54860
+ navFailures: 0,
54861
+ attrs: { provisional: true, captureTime: true, anchorProvenance: "cite-confirmed", citeSessions: grown }
54862
+ });
54863
+ delete hints[fileId];
54864
+ promoted = true;
54865
+ } else {
54866
+ hints[fileId] = grown;
54867
+ }
54868
+ store.updateNode(targetId, { attrs: { ...node2.attrs, citeAnchorHints: hints }, lastUpdatedAt: ts });
54869
+ return { accrued: true, promoted };
54870
+ }
54871
+ function mintPriorEdge(store, source, target, sentence, touched, ts, sessionId) {
54482
54872
  if (target.id === source.id) return false;
54483
54873
  const type = typePriorEdge(source.label, target.label, sentence);
54484
54874
  const anchorIds = new Set(
@@ -54522,13 +54912,24 @@ function mintPriorEdge(store, source, target, sentence, touched, ts) {
54522
54912
  } catch {
54523
54913
  }
54524
54914
  }
54525
- return { corroborated };
54915
+ let citeAnchorAccrued = false;
54916
+ let citeAnchorPromoted = false;
54917
+ if (!corroborated && sessionId && CITE_ANCHOR_LABELS.has(target.label)) {
54918
+ try {
54919
+ const acc = accrueCiteAnchor(store, target.id, touched, sessionId, ts);
54920
+ citeAnchorAccrued = acc.accrued;
54921
+ citeAnchorPromoted = acc.promoted;
54922
+ } catch (err2) {
54923
+ console.warn("[errata] cite-anchor accrual failed (citation kept):", err2 instanceof Error ? err2.message : err2);
54924
+ }
54925
+ }
54926
+ return { corroborated, citeAnchorAccrued, citeAnchorPromoted };
54526
54927
  }
54527
54928
  function harvestInlineTags(store, text, opts) {
54528
54929
  const source = opts.sourceId ? store.getNode(opts.sourceId) : null;
54529
54930
  const mintPriors = opts.mintPriors ?? true;
54530
54931
  const touched = opts.sessionTouchedIds ?? /* @__PURE__ */ new Set();
54531
- const plan = { priorEdges: 0, corroboratedEdges: 0, exposureShown: 0, exposureEvicted: 0, exposureUnshown: 0, problems: [], fixes: [], triages: [], causalLinks: [], attempts: [], unresolvedFixes: [], unresolvedHandles: [], priorEdgesSuppressed: { flagOff: 0, noSource: 0 }, dispositions: emptyTagDispositions(), transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
54932
+ const plan = { priorEdges: 0, corroboratedEdges: 0, citeAnchorsAccrued: 0, citeAnchorsPromoted: 0, exposureShown: 0, exposureEvicted: 0, exposureUnshown: 0, problems: [], fixes: [], triages: [], causalLinks: [], attempts: [], unresolvedFixes: [], unresolvedHandles: [], priorEdgesSuppressed: { flagOff: 0, noSource: 0 }, dispositions: emptyTagDispositions(), transfers: [], solutionLinks: [], instances: [], patterns: [], domains: [], packages: [], components: [], refutes: [], corroborations: [] };
54532
54933
  const parsed = parseInlineTagsWithDispositions(text);
54533
54934
  const tags = parsed.tags;
54534
54935
  plan.dispositions = parsed.dispositions;
@@ -54756,10 +55157,12 @@ function harvestInlineTags(store, text, opts) {
54756
55157
  }
54757
55158
  }
54758
55159
  if (mintPriors && source && target) {
54759
- const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts);
55160
+ const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts, opts.sessionId);
54760
55161
  if (m) {
54761
55162
  plan.priorEdges++;
54762
55163
  if (m.corroborated) plan.corroboratedEdges++;
55164
+ if (m.citeAnchorAccrued) plan.citeAnchorsAccrued++;
55165
+ if (m.citeAnchorPromoted) plan.citeAnchorsPromoted++;
54763
55166
  }
54764
55167
  } else if (target) {
54765
55168
  if (!mintPriors) plan.priorEdgesSuppressed.flagOff++;
@@ -54769,10 +55172,12 @@ function harvestInlineTags(store, text, opts) {
54769
55172
  const targetId = resolveCited(tag.kind, tag.handle);
54770
55173
  const target = targetId ? store.getNode(targetId) : null;
54771
55174
  if (target) {
54772
- const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts);
55175
+ const m = mintPriorEdge(store, source, target, tag.sentence, touched, opts.ts, opts.sessionId);
54773
55176
  if (m) {
54774
55177
  plan.priorEdges++;
54775
55178
  if (m.corroborated) plan.corroboratedEdges++;
55179
+ if (m.citeAnchorAccrued) plan.citeAnchorsAccrued++;
55180
+ if (m.citeAnchorPromoted) plan.citeAnchorsPromoted++;
54776
55181
  }
54777
55182
  }
54778
55183
  } else if (tag.handle) {
@@ -57323,7 +57728,7 @@ function createWatchBreaker(deps) {
57323
57728
  }
57324
57729
 
57325
57730
  // src/engine.ts
57326
- var DAEMON_VERSION = true ? "2.0.2-dev.1263" : "2.0.0-alpha.0";
57731
+ var DAEMON_VERSION = true ? "2.0.2-dev.1274" : "2.0.0-alpha.0";
57327
57732
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
57328
57733
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
57329
57734
  var GIT_OP_MUTE_MS = 4e3;
@@ -58169,6 +58574,17 @@ function createWorkspaceEngine(opts) {
58169
58574
  let touchedFileTurns = 0;
58170
58575
  let touchedToolTurns = 0;
58171
58576
  let anchorHintsUpgraded = 0;
58577
+ let foldAttempts = 0;
58578
+ let foldNoFile = 0;
58579
+ let foldNoCandidates = 0;
58580
+ let foldKindVeto = 0;
58581
+ let foldJaccardHit = 0;
58582
+ let foldScoutHit = 0;
58583
+ let foldNearMiss30 = 0;
58584
+ let foldNearMiss45 = 0;
58585
+ let foldCandidatesMarked = 0;
58586
+ let citeAnchorsAccrued = 0;
58587
+ let citeAnchorsPromoted = 0;
58172
58588
  let linked = 0;
58173
58589
  const t = Date.now();
58174
58590
  let processedTurns = 0;
@@ -58312,10 +58728,16 @@ function createWorkspaceEngine(opts) {
58312
58728
  handleMap,
58313
58729
  ts: t,
58314
58730
  mintPriors: elicit,
58731
+ // WM-fold phase 3: cite-time anchor accrual keys on the session — two
58732
+ // DISTINCT sessions co-citing a prior from the same file promote a
58733
+ // real anchor; one session, or none, accrues silently.
58734
+ sessionId,
58315
58735
  ...touched.size > 0 ? { sessionTouchedIds: touched } : {}
58316
58736
  });
58317
58737
  priorEdges += plan.priorEdges;
58318
58738
  corroboratedEdges += plan.corroboratedEdges;
58739
+ citeAnchorsAccrued += plan.citeAnchorsAccrued;
58740
+ citeAnchorsPromoted += plan.citeAnchorsPromoted;
58319
58741
  const refuteResult = applyLocalRefutations(store, plan.refutes, t);
58320
58742
  refutesStamped += refuteResult.stamped;
58321
58743
  for (const c of plan.corroborations) {
@@ -58361,21 +58783,43 @@ function createWorkspaceEngine(opts) {
58361
58783
  const anchorProvenance = p.location?.path ? "witnessed" : "edited";
58362
58784
  const hintPath = anchorPath ? void 0 : turnFile ?? wf;
58363
58785
  const dedupPath = anchorPath ?? hintPath;
58786
+ let pendingNomination = null;
58364
58787
  if (dedupPath) {
58365
58788
  try {
58366
- const dupId = reinforceSameAnchorProblem(store, dedupPath, profile.id, p.statement, sessionId, t, p.kind);
58367
- if (dupId) {
58789
+ foldAttempts++;
58790
+ const fold = foldSameAnchorProblem(store, dedupPath, profile.id, p.statement, sessionId, t, p.kind);
58791
+ if (fold.noFile) foldNoFile++;
58792
+ else if (fold.candidates === 0) foldNoCandidates++;
58793
+ foldKindVeto += fold.kindVetoed;
58794
+ if (!fold.foldedId) {
58795
+ if (fold.bestJaccard >= 0.45) foldNearMiss45++;
58796
+ else if (fold.bestJaccard >= 0.3) foldNearMiss30++;
58797
+ if (fold.nominate) {
58798
+ pendingNomination = { ...fold.nominate, source: "file-scout" };
58799
+ }
58800
+ }
58801
+ if (fold.foldedId) {
58802
+ if (fold.foldedBy === "scout") foldScoutHit++;
58803
+ else foldJaccardHit++;
58368
58804
  minted++;
58369
58805
  if (p.kind !== "constraint") {
58370
- sessionLastProblem.set(sessionId, dupId);
58371
- inScopeProblemId = dupId;
58806
+ sessionLastProblem.set(sessionId, fold.foldedId);
58807
+ inScopeProblemId = fold.foldedId;
58372
58808
  }
58373
- if (p.threadId) threads.set(p.threadId, dupId);
58809
+ if (p.threadId) threads.set(p.threadId, fold.foldedId);
58374
58810
  continue;
58375
58811
  }
58376
58812
  } catch {
58377
58813
  }
58378
58814
  }
58815
+ if (!pendingNomination) {
58816
+ try {
58817
+ const ws = findWorkspaceFoldCandidate(store, profile.id, p.statement, p.kind);
58818
+ if (ws) pendingNomination = { ...ws, source: "workspace-scout" };
58819
+ } catch (err2) {
58820
+ console.warn("[errata] workspace fold scout failed (mint unaffected):", err2 instanceof Error ? err2.message : err2);
58821
+ }
58822
+ }
58379
58823
  const r = ingestDesignProblem(store, { problem: p.statement, kind: p.kind }, {
58380
58824
  workspaceId: profile.id,
58381
58825
  source: sessionId,
@@ -58413,6 +58857,19 @@ function createWorkspaceEngine(opts) {
58413
58857
  );
58414
58858
  }
58415
58859
  }
58860
+ if (r.created && pendingNomination) {
58861
+ try {
58862
+ const marked = markFoldCandidate(store, r.problemId, pendingNomination.targetId, {
58863
+ ...pendingNomination.hashCos !== void 0 ? { hashCos: pendingNomination.hashCos } : {},
58864
+ ...pendingNomination.jaccard !== void 0 ? { jaccard: pendingNomination.jaccard } : {},
58865
+ source: pendingNomination.source,
58866
+ ts: t
58867
+ });
58868
+ if (marked) foldCandidatesMarked++;
58869
+ } catch (err2) {
58870
+ console.warn("[errata] fold nomination failed (mint unaffected):", err2 instanceof Error ? err2.message : err2);
58871
+ }
58872
+ }
58416
58873
  if (r.created || r.corroborated) {
58417
58874
  minted++;
58418
58875
  if (p.kind !== "constraint") {
@@ -58834,6 +59291,20 @@ function createWorkspaceEngine(opts) {
58834
59291
  exposureOutcomes: exposureShown + exposureEvicted + exposureUnshown,
58835
59292
  // AC-hint-upgrade (harvest seam): edited-this-turn hint promotions.
58836
59293
  anchorHintsUpgraded,
59294
+ // WM-fold funnel (phase 0) — which gate of the same-anchor fold
59295
+ // conjunction eats the mass, and the near-miss bands under the bars.
59296
+ foldAttempts,
59297
+ foldNoFile,
59298
+ foldNoCandidates,
59299
+ foldKindVeto,
59300
+ foldJaccardHit,
59301
+ foldScoutHit,
59302
+ foldNearMiss30,
59303
+ foldNearMiss45,
59304
+ foldCandidatesMarked,
59305
+ // WM-fold phase 3 — cite-time anchor accrual on cited priors.
59306
+ citeAnchorsAccrued,
59307
+ citeAnchorsPromoted,
58837
59308
  touchedFileTurns,
58838
59309
  touchedToolTurns,
58839
59310
  // Seam diagnostic: sessions holding tool-run records at harvest time.
@@ -59006,6 +59477,22 @@ function createWorkspaceEngine(opts) {
59006
59477
  });
59007
59478
  };
59008
59479
  const designRollup = opts.designRollup ?? (process.env["ERRATA_ROLLUP"] === "1" ? haikuDesignRollup(process.env["ANTHROPIC_API_KEY"] ?? null) : void 0);
59480
+ const foldJudgeModel = process.env["ERRATA_FOLD_JUDGE_MODEL"] ?? "gpt-5-mini";
59481
+ const foldJudge = process.env["ERRATA_FOLD_JUDGE"] === "1" && isChatConfigured(foldJudgeModel) ? async (a, b) => {
59482
+ const out2 = await chat({
59483
+ model: foldJudgeModel,
59484
+ system: "You judge whether two independently-written one-line problem reports from the same codebase describe the SAME underlying problem. Reworded restatements of one problem are SAME; related-but-different defects, or a defect vs a design constraint, are DISTINCT. Answer with exactly one word: SAME or DISTINCT.",
59485
+ user: `A: ${a}
59486
+ B: ${b}`,
59487
+ maxTokens: 8
59488
+ });
59489
+ if (!out2) return null;
59490
+ const same = /\bSAME\b/i.test(out2);
59491
+ const distinct = /\bDISTINCT\b/i.test(out2);
59492
+ if (same && !distinct) return "same";
59493
+ if (distinct && !same) return "distinct";
59494
+ return null;
59495
+ } : void 0;
59009
59496
  const onSessionEnd = (e) => {
59010
59497
  if (!designRollup) return;
59011
59498
  setImmediate(() => {
@@ -59297,6 +59784,27 @@ function createWorkspaceEngine(opts) {
59297
59784
  console.warn(`[errata] REGRESSION: bound ${rg.bound} open problem(s) to resolved predecessors${rg.full ? " (first full sweep)" : ""}`);
59298
59785
  refreshContextNow();
59299
59786
  }
59787
+ const fjStart = Date.now();
59788
+ const fj = await resolveFoldCandidates(store, {
59789
+ ts: fjStart,
59790
+ ...foldJudge ? { judge: foldJudge } : {}
59791
+ });
59792
+ if (fj.examined > 0) {
59793
+ appendPassLedger(paths.configDir, "fold-judge", Date.now() - fjStart, {
59794
+ examined: fj.examined,
59795
+ folded: fj.folded,
59796
+ foldedEmbedding: fj.foldedEmbedding,
59797
+ foldedJudge: fj.foldedJudge,
59798
+ rejected: fj.rejected,
59799
+ standing: fj.standing,
59800
+ judgeErrors: fj.judgeErrors,
59801
+ retargeted: fj.retargeted
59802
+ });
59803
+ if (fj.folded > 0) {
59804
+ console.log(`[errata] fold-judge: folded ${fj.folded} paraphrase pair(s) (${fj.foldedEmbedding} by embedding, ${fj.foldedJudge} by judge), ${fj.standing} standing`);
59805
+ refreshContextNow();
59806
+ }
59807
+ }
59300
59808
  return { embedded: sem.embedded };
59301
59809
  } catch (err2) {
59302
59810
  console.warn("[errata] settled embed failed:", err2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.1263",
3
+ "version": "2.0.2-dev.1274",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -25734,11 +25734,16 @@ function foldDuplicateProblem(store2, dup, survivor, ts) {
25734
25734
  if (!surv) return;
25735
25735
  const sources = new Set(surv.attrs["sources"] ?? []);
25736
25736
  for (const s of dup.attrs["sources"] ?? []) sources.add(s);
25737
+ const dupSeq = dup.attrs["createdAtSeq"] ?? store2.currentIngestSeq();
25738
+ const exposure = priorExposure(store2, survivor.id, dupSeq);
25739
+ const expField = `reinforce${exposure[0].toUpperCase()}${exposure.slice(1)}Count`;
25737
25740
  store2.updateNode(survivor.id, {
25738
25741
  attrs: {
25739
25742
  ...surv.attrs,
25740
25743
  sources: [...sources],
25741
- corroborations: corroborations(surv) + corroborations(dup) + 1
25744
+ corroborations: corroborations(surv) + corroborations(dup) + 1,
25745
+ [expField]: (surv.attrs[expField] ?? 0) + 1,
25746
+ lastReinforceExposure: exposure
25742
25747
  },
25743
25748
  cumulativeHits: (surv.cumulativeHits ?? 0) + (dup.cumulativeHits ?? 0),
25744
25749
  lastUpdatedAt: ts
@@ -25954,6 +25959,16 @@ function clearSettledFixCandidates(store2, t) {
25954
25959
  }
25955
25960
  return report;
25956
25961
  }
25962
+ function priorExposure(store2, nodeId, atSeq) {
25963
+ const n = store2.getNode(nodeId);
25964
+ if (!n || !n.attrs) return "unshown";
25965
+ const shownSeq = n.attrs["lastShownAtSeq"];
25966
+ if (shownSeq !== void 0 && shownSeq <= atSeq) return "shown";
25967
+ const evictedSeq = n.attrs["lastEvictedAtSeq"];
25968
+ if (evictedSeq !== void 0 && evictedSeq <= atSeq) return "evicted";
25969
+ if ((n.attrs["evictedCount"] ?? 0) > 0) return "evicted";
25970
+ return "unshown";
25971
+ }
25957
25972
 
25958
25973
  // ../../packages/local-graph/src/intent.ts
25959
25974
  init_src();