@inerrata-corporation/errata 2.0.2-dev.479 → 2.0.2-dev.497

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.
@@ -673,7 +673,7 @@ function lemma(token) {
673
673
  }
674
674
  return token;
675
675
  }
676
- function conceptTokens(text) {
676
+ function tokenize(text, resolve) {
677
677
  const matches = text.normalize("NFC").toLowerCase().match(TOKEN_RE) ?? [];
678
678
  const out = /* @__PURE__ */ new Set();
679
679
  for (const raw of matches) {
@@ -683,11 +683,14 @@ function conceptTokens(text) {
683
683
  out.add(token);
684
684
  continue;
685
685
  }
686
- const canonical = resolveCanonicalId(token);
686
+ const canonical = resolve(token);
687
687
  out.add(canonical ?? lemma(token));
688
688
  }
689
689
  return [...out].sort();
690
690
  }
691
+ function conceptTokens(text) {
692
+ return tokenize(text, resolveCanonicalId);
693
+ }
691
694
  function conceptBag(text) {
692
695
  return conceptTokens(text).join(" ");
693
696
  }
package/errata.mjs CHANGED
@@ -619,6 +619,11 @@ function resolveCanonical(label) {
619
619
  function resolveCanonicalId(label) {
620
620
  return resolveCanonical(label)?.id;
621
621
  }
622
+ function resolveUnambiguousCanonicalId(label) {
623
+ const key = label.trim().toLowerCase();
624
+ if (AMBIGUOUS_ALIASES.has(key)) return void 0;
625
+ return resolveCanonicalId(key);
626
+ }
622
627
  function allEntities() {
623
628
  return REGISTRY;
624
629
  }
@@ -628,7 +633,7 @@ function aliasesLongestFirst() {
628
633
  out2.sort((x, y) => y.alias.length - x.alias.length);
629
634
  return out2;
630
635
  }
631
- var REGISTRY, BY_ALIAS;
636
+ var REGISTRY, AMBIGUOUS_ALIASES, BY_ALIAS;
632
637
  var init_taxonomy = __esm({
633
638
  "../../packages/shared/src/nlp/taxonomy.ts"() {
634
639
  "use strict";
@@ -677,6 +682,13 @@ var init_taxonomy = __esm({
677
682
  { id: "concept:retry", name: "retry", category: "concept", aliases: ["retry"] },
678
683
  { id: "concept:migration", name: "migration", category: "concept", aliases: ["migration"] }
679
684
  ];
685
+ AMBIGUOUS_ALIASES = /* @__PURE__ */ new Set([
686
+ "node",
687
+ "go",
688
+ "pool",
689
+ "spring",
690
+ "ws"
691
+ ]);
680
692
  BY_ALIAS = /* @__PURE__ */ new Map();
681
693
  for (const e of REGISTRY) {
682
694
  for (const a of e.aliases) {
@@ -785,7 +797,7 @@ function lemma(token) {
785
797
  }
786
798
  return token;
787
799
  }
788
- function conceptTokens(text) {
800
+ function tokenize(text, resolve6) {
789
801
  const matches = text.normalize("NFC").toLowerCase().match(TOKEN_RE) ?? [];
790
802
  const out2 = /* @__PURE__ */ new Set();
791
803
  for (const raw2 of matches) {
@@ -795,11 +807,17 @@ function conceptTokens(text) {
795
807
  out2.add(token);
796
808
  continue;
797
809
  }
798
- const canonical = resolveCanonicalId(token);
810
+ const canonical = resolve6(token);
799
811
  out2.add(canonical ?? lemma(token));
800
812
  }
801
813
  return [...out2].sort();
802
814
  }
815
+ function conceptTokens(text) {
816
+ return tokenize(text, resolveCanonicalId);
817
+ }
818
+ function retrievalTokens(text) {
819
+ return tokenize(text, resolveUnambiguousCanonicalId);
820
+ }
803
821
  function conceptBag(text) {
804
822
  return conceptTokens(text).join(" ");
805
823
  }
@@ -15935,6 +15953,7 @@ __export(src_exports, {
15935
15953
  ABSTRACTION_LEVEL: () => ABSTRACTION_LEVEL,
15936
15954
  ALL_EDGE_TYPES: () => ALL_EDGE_TYPES,
15937
15955
  ALL_NODE_LABELS: () => ALL_NODE_LABELS,
15956
+ AMBIGUOUS_ALIASES: () => AMBIGUOUS_ALIASES,
15938
15957
  BELIEF_EDGES: () => BELIEF_EDGES,
15939
15958
  BRIDGE_EDGES: () => BRIDGE_EDGES,
15940
15959
  CAUSAL_EDGES: () => CAUSAL_EDGES,
@@ -16035,6 +16054,8 @@ __export(src_exports, {
16035
16054
  provenanceBand: () => provenanceBand,
16036
16055
  resolveCanonical: () => resolveCanonical,
16037
16056
  resolveCanonicalId: () => resolveCanonicalId,
16057
+ resolveUnambiguousCanonicalId: () => resolveUnambiguousCanonicalId,
16058
+ retrievalTokens: () => retrievalTokens,
16038
16059
  sha256: () => sha256,
16039
16060
  sha256Short: () => sha256Short,
16040
16061
  splitPnpmKey: () => splitPnpmKey,
@@ -17639,13 +17660,46 @@ function addDependency(store, opts) {
17639
17660
  store.mergeEdge(edge2);
17640
17661
  return edge2;
17641
17662
  }
17642
- function markRevisit(store, node2, reason, ts) {
17663
+ function markRevisit(store, node2, reason, ts, answeredWhen) {
17643
17664
  const fresh = store.getNode(node2.id) ?? node2;
17644
17665
  store.updateNode(node2.id, {
17645
- attrs: { ...fresh.attrs, revisit: true, revisitReason: reason, revisitSinceTs: ts },
17666
+ attrs: {
17667
+ ...fresh.attrs,
17668
+ revisit: true,
17669
+ revisitReason: reason,
17670
+ revisitSinceTs: ts,
17671
+ ...answeredWhen ? { revisitAnsweredWhen: answeredWhen } : {}
17672
+ },
17646
17673
  lastUpdatedAt: ts
17647
17674
  });
17648
17675
  }
17676
+ function conditionOf(node2) {
17677
+ const explicit = node2.attrs["revisitAnsweredWhen"];
17678
+ if (explicit === "parentProblemOpen") return explicit;
17679
+ if (String(node2.attrs["revisitReason"] ?? "").startsWith(LEGACY_AUTO_CLOSE_ASK)) {
17680
+ return "parentProblemOpen";
17681
+ }
17682
+ return void 0;
17683
+ }
17684
+ function clearAnsweredRevisits(store, ts) {
17685
+ const report = { cleared: 0, standing: 0 };
17686
+ for (const node2 of store.findNodesByLabel("Solution")) {
17687
+ if (node2.attrs["revisit"] !== true) continue;
17688
+ if (conditionOf(node2) !== "parentProblemOpen") {
17689
+ report.standing++;
17690
+ continue;
17691
+ }
17692
+ const parents = store.inEdges(node2.id, ["SOLVED_BY"]).map((e) => store.getNode(e.from)).filter((n) => n !== null);
17693
+ const answered = parents.every((p) => p.attrs["resolvedAt"] == null);
17694
+ if (!answered) {
17695
+ report.standing++;
17696
+ continue;
17697
+ }
17698
+ clearRevisit(store, node2.id, ts);
17699
+ report.cleared++;
17700
+ }
17701
+ return report;
17702
+ }
17649
17703
  function clearRevisit(store, nodeId, ts) {
17650
17704
  const n = store.getNode(nodeId);
17651
17705
  if (!n) return;
@@ -17718,10 +17772,12 @@ function propagateFactChange(store, opts) {
17718
17772
  cascade(store, seeds, visited, flagged, opts.ts);
17719
17773
  return flagged;
17720
17774
  }
17775
+ var LEGACY_AUTO_CLOSE_ASK;
17721
17776
  var init_justification = __esm({
17722
17777
  "../../packages/local-graph/src/justification.ts"() {
17723
17778
  "use strict";
17724
17779
  init_src();
17780
+ LEGACY_AUTO_CLOSE_ASK = "auto-closed off a pre-provenance anchor";
17725
17781
  }
17726
17782
  });
17727
17783
 
@@ -18192,7 +18248,7 @@ function djb2Signed(s) {
18192
18248
  }
18193
18249
  return h;
18194
18250
  }
18195
- function tokenize(text) {
18251
+ function tokenize2(text) {
18196
18252
  return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0 && t.length <= 40);
18197
18253
  }
18198
18254
  function* charTrigrams(text) {
@@ -18204,7 +18260,7 @@ function* charTrigrams(text) {
18204
18260
  function embed(text) {
18205
18261
  const vec = new Array(EMBEDDING_DIM).fill(0);
18206
18262
  if (!text) return vec;
18207
- for (const tok of tokenize(text)) {
18263
+ for (const tok of tokenize2(text)) {
18208
18264
  const idx = djb2(tok) % EMBEDDING_DIM;
18209
18265
  const sign = djb2Signed(tok) & 1 ? 1 : -1;
18210
18266
  vec[idx] = vec[idx] + sign;
@@ -18578,7 +18634,7 @@ function mergeDuplicateProblems(store, opts) {
18578
18634
  const report = { clusters: 0, merged: 0 };
18579
18635
  const open2 = store.findNodesByLabel("Problem").filter((p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0);
18580
18636
  const tokens = /* @__PURE__ */ new Map();
18581
- for (const p of open2) tokens.set(p.id, new Set(conceptTokens(p.description)));
18637
+ for (const p of open2) tokens.set(p.id, new Set(retrievalTokens(p.description)));
18582
18638
  const consumed = /* @__PURE__ */ new Set();
18583
18639
  store.transaction(() => {
18584
18640
  for (let i2 = 0; i2 < open2.length; i2++) {
@@ -18656,12 +18712,12 @@ function findMintTimeDuplicate(store, statement, kind, opts) {
18656
18712
  if (isConstraintProblem(p) !== incomingIsConstraint) continue;
18657
18713
  if (statementIdentityKey(p.description) === key) return p.id;
18658
18714
  }
18659
- const ta = new Set(conceptTokens(statement));
18715
+ const ta = new Set(retrievalTokens(statement));
18660
18716
  if (ta.size < minTokens) return null;
18661
18717
  let best = null;
18662
18718
  for (const p of open2) {
18663
18719
  if (isConstraintProblem(p) !== incomingIsConstraint) continue;
18664
- const tb = new Set(conceptTokens(p.description));
18720
+ const tb = new Set(retrievalTokens(p.description));
18665
18721
  if (Math.min(ta.size, tb.size) < minTokens) continue;
18666
18722
  const score2 = overlap(ta, tb);
18667
18723
  if (score2 >= minOverlap && (best === null || score2 > best.score)) {
@@ -19368,7 +19424,7 @@ function parseIntentFences(text) {
19368
19424
  function searchByIntent(store, intent, opts) {
19369
19425
  const limit = opts?.limit ?? 5;
19370
19426
  const minCoverage = opts?.minCoverage ?? 0.25;
19371
- const want = new Set(conceptTokens(intent));
19427
+ const want = new Set(retrievalTokens(intent));
19372
19428
  if (want.size < MIN_INTENT_TOKENS) return [];
19373
19429
  const hits = [];
19374
19430
  for (const label of RECALLABLE) {
@@ -19377,7 +19433,7 @@ function searchByIntent(store, intent, opts) {
19377
19433
  if (label === "Problem" && node2.attrs["resolvedAt"] != null && !isConstraintProblem(node2)) {
19378
19434
  continue;
19379
19435
  }
19380
- const bag = conceptTokens(node2.description ?? "");
19436
+ const bag = retrievalTokens(node2.description ?? "");
19381
19437
  if (bag.length === 0) continue;
19382
19438
  const matched = bag.filter((t) => want.has(t));
19383
19439
  if (matched.length === 0) continue;
@@ -19539,7 +19595,11 @@ function backfillLegacyAnchors(store, ts) {
19539
19595
  store,
19540
19596
  sol,
19541
19597
  `auto-closed off a pre-provenance anchor (guessed ${guessedAnchor}) \u2014 confirm the problem is really fixed, or reopen it`,
19542
- ts
19598
+ ts,
19599
+ // "or reopen it" is half the ask, and the nightly reopen takes that
19600
+ // branch — so record what would answer this, or the flag can never be
19601
+ // lowered and the band fills with settled questions.
19602
+ "parentProblemOpen"
19543
19603
  );
19544
19604
  store.updateNode(p.id, {
19545
19605
  attrs: { ...p.attrs, resolutionSuspect: true },
@@ -21690,6 +21750,7 @@ __export(src_exports2, {
21690
21750
  canonicalizePatternText: () => canonicalizePatternText,
21691
21751
  causalChain: () => causalChain,
21692
21752
  claimId: () => claimId,
21753
+ clearAnsweredRevisits: () => clearAnsweredRevisits,
21693
21754
  clearRevisit: () => clearRevisit,
21694
21755
  communityInductionRequests: () => communityInductionRequests,
21695
21756
  communitySeeds: () => communitySeeds,
@@ -28413,6 +28474,7 @@ function runNightlyPipeline(store) {
28413
28474
  });
28414
28475
  const motifs = promoteMotifs(store, started2);
28415
28476
  reopenAutoClosedProblems(store, started2);
28477
+ const revisits = clearAnsweredRevisits(store, started2);
28416
28478
  const designResolved = markFixCandidates(store, started2);
28417
28479
  const cry = crystallize(store, { ts: started2 });
28418
28480
  return {
@@ -28428,6 +28490,7 @@ function runNightlyPipeline(store) {
28428
28490
  // skills are cloud-induced now (see above)
28429
28491
  skillsRefreshed: 0,
28430
28492
  designResolved,
28493
+ revisitsCleared: revisits.cleared,
28431
28494
  claimsEvaluated: cry.evaluated,
28432
28495
  claimsDerived: cry.derived.length,
28433
28496
  durationMs: Date.now() - started2
@@ -29032,7 +29095,7 @@ function popcount(x) {
29032
29095
  }
29033
29096
  return c;
29034
29097
  }
29035
- function tokenize2(text) {
29098
+ function tokenize3(text) {
29036
29099
  return text.match(/[A-Za-z_$][A-Za-z0-9_$]*|\d+|[^\s\w]/g) ?? [];
29037
29100
  }
29038
29101
  function shingle(tokens, n = SHINGLE_N) {
@@ -29061,7 +29124,7 @@ function simhashFeatures(features) {
29061
29124
  return out2;
29062
29125
  }
29063
29126
  function simhash(text) {
29064
- return simhashFeatures(shingle(tokenize2(text)));
29127
+ return simhashFeatures(shingle(tokenize3(text)));
29065
29128
  }
29066
29129
  function hammingDistance(a, b) {
29067
29130
  return popcount((a ^ b) & MASK64);
@@ -37584,7 +37647,7 @@ __export(src_exports5, {
37584
37647
  simhashFeatures: () => simhashFeatures,
37585
37648
  symbolNodeId: () => symbolNodeId,
37586
37649
  toHex: () => toHex,
37587
- tokenize: () => tokenize2
37650
+ tokenize: () => tokenize3
37588
37651
  });
37589
37652
  function defaultProviders() {
37590
37653
  if (providersCache) return providersCache;
@@ -53683,7 +53746,7 @@ function startLoopLagMonitor(thresholdMs = 1e3) {
53683
53746
  }
53684
53747
 
53685
53748
  // src/engine.ts
53686
- var DAEMON_VERSION = true ? "2.0.2-dev.479" : "2.0.0-alpha.0";
53749
+ var DAEMON_VERSION = true ? "2.0.2-dev.497" : "2.0.0-alpha.0";
53687
53750
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
53688
53751
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
53689
53752
  var GIT_OP_MUTE_MS = 4e3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.2-dev.479",
3
+ "version": "2.0.2-dev.497",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -458,13 +458,18 @@ function resolveCanonical(label) {
458
458
  function resolveCanonicalId(label) {
459
459
  return resolveCanonical(label)?.id;
460
460
  }
461
+ function resolveUnambiguousCanonicalId(label) {
462
+ const key = label.trim().toLowerCase();
463
+ if (AMBIGUOUS_ALIASES.has(key)) return void 0;
464
+ return resolveCanonicalId(key);
465
+ }
461
466
  function aliasesLongestFirst() {
462
467
  const out2 = [];
463
468
  for (const e of REGISTRY) for (const a of e.aliases) out2.push({ alias: a.toLowerCase(), entity: e });
464
469
  out2.sort((x, y) => y.alias.length - x.alias.length);
465
470
  return out2;
466
471
  }
467
- var REGISTRY, BY_ALIAS;
472
+ var REGISTRY, AMBIGUOUS_ALIASES, BY_ALIAS;
468
473
  var init_taxonomy = __esm({
469
474
  "../../packages/shared/src/nlp/taxonomy.ts"() {
470
475
  "use strict";
@@ -513,6 +518,13 @@ var init_taxonomy = __esm({
513
518
  { id: "concept:retry", name: "retry", category: "concept", aliases: ["retry"] },
514
519
  { id: "concept:migration", name: "migration", category: "concept", aliases: ["migration"] }
515
520
  ];
521
+ AMBIGUOUS_ALIASES = /* @__PURE__ */ new Set([
522
+ "node",
523
+ "go",
524
+ "pool",
525
+ "spring",
526
+ "ws"
527
+ ]);
516
528
  BY_ALIAS = /* @__PURE__ */ new Map();
517
529
  for (const e of REGISTRY) {
518
530
  for (const a of e.aliases) {
@@ -574,7 +586,7 @@ function lemma(token) {
574
586
  }
575
587
  return token;
576
588
  }
577
- function conceptTokens(text) {
589
+ function tokenize(text, resolve) {
578
590
  const matches = text.normalize("NFC").toLowerCase().match(TOKEN_RE) ?? [];
579
591
  const out2 = /* @__PURE__ */ new Set();
580
592
  for (const raw of matches) {
@@ -584,11 +596,14 @@ function conceptTokens(text) {
584
596
  out2.add(token);
585
597
  continue;
586
598
  }
587
- const canonical = resolveCanonicalId(token);
599
+ const canonical = resolve(token);
588
600
  out2.add(canonical ?? lemma(token));
589
601
  }
590
602
  return [...out2].sort();
591
603
  }
604
+ function retrievalTokens(text) {
605
+ return tokenize(text, resolveUnambiguousCanonicalId);
606
+ }
592
607
  var STOPWORDS, NEGATION_TOKENS, TOKEN_RE;
593
608
  var init_concept_bag = __esm({
594
609
  "../../packages/shared/src/nlp/concept-bag.ts"() {
@@ -15533,7 +15548,7 @@ function popcount(x) {
15533
15548
  }
15534
15549
  return c;
15535
15550
  }
15536
- function tokenize(text) {
15551
+ function tokenize2(text) {
15537
15552
  return text.match(/[A-Za-z_$][A-Za-z0-9_$]*|\d+|[^\s\w]/g) ?? [];
15538
15553
  }
15539
15554
  function shingle(tokens, n = SHINGLE_N) {
@@ -15562,7 +15577,7 @@ function simhashFeatures(features) {
15562
15577
  return out2;
15563
15578
  }
15564
15579
  function simhash(text) {
15565
- return simhashFeatures(shingle(tokenize(text)));
15580
+ return simhashFeatures(shingle(tokenize2(text)));
15566
15581
  }
15567
15582
  function hammingDistance(a, b) {
15568
15583
  return popcount((a ^ b) & MASK64);
@@ -24085,7 +24100,7 @@ __export(src_exports, {
24085
24100
  simhashFeatures: () => simhashFeatures,
24086
24101
  symbolNodeId: () => symbolNodeId,
24087
24102
  toHex: () => toHex,
24088
- tokenize: () => tokenize
24103
+ tokenize: () => tokenize2
24089
24104
  });
24090
24105
  function defaultProviders() {
24091
24106
  if (providersCache) return providersCache;
@@ -24992,13 +25007,56 @@ function addDependency(store2, opts) {
24992
25007
  store2.mergeEdge(edge);
24993
25008
  return edge;
24994
25009
  }
24995
- function markRevisit(store2, node, reason, ts) {
25010
+ function markRevisit(store2, node, reason, ts, answeredWhen) {
24996
25011
  const fresh = store2.getNode(node.id) ?? node;
24997
25012
  store2.updateNode(node.id, {
24998
- attrs: { ...fresh.attrs, revisit: true, revisitReason: reason, revisitSinceTs: ts },
25013
+ attrs: {
25014
+ ...fresh.attrs,
25015
+ revisit: true,
25016
+ revisitReason: reason,
25017
+ revisitSinceTs: ts,
25018
+ ...answeredWhen ? { revisitAnsweredWhen: answeredWhen } : {}
25019
+ },
24999
25020
  lastUpdatedAt: ts
25000
25021
  });
25001
25022
  }
25023
+ var LEGACY_AUTO_CLOSE_ASK = "auto-closed off a pre-provenance anchor";
25024
+ function conditionOf(node) {
25025
+ const explicit = node.attrs["revisitAnsweredWhen"];
25026
+ if (explicit === "parentProblemOpen") return explicit;
25027
+ if (String(node.attrs["revisitReason"] ?? "").startsWith(LEGACY_AUTO_CLOSE_ASK)) {
25028
+ return "parentProblemOpen";
25029
+ }
25030
+ return void 0;
25031
+ }
25032
+ function clearAnsweredRevisits(store2, ts) {
25033
+ const report = { cleared: 0, standing: 0 };
25034
+ for (const node of store2.findNodesByLabel("Solution")) {
25035
+ if (node.attrs["revisit"] !== true) continue;
25036
+ if (conditionOf(node) !== "parentProblemOpen") {
25037
+ report.standing++;
25038
+ continue;
25039
+ }
25040
+ const parents = store2.inEdges(node.id, ["SOLVED_BY"]).map((e) => store2.getNode(e.from)).filter((n) => n !== null);
25041
+ const answered = parents.every((p) => p.attrs["resolvedAt"] == null);
25042
+ if (!answered) {
25043
+ report.standing++;
25044
+ continue;
25045
+ }
25046
+ clearRevisit(store2, node.id, ts);
25047
+ report.cleared++;
25048
+ }
25049
+ return report;
25050
+ }
25051
+ function clearRevisit(store2, nodeId, ts) {
25052
+ const n = store2.getNode(nodeId);
25053
+ if (!n) return;
25054
+ const attrs = { ...n.attrs };
25055
+ delete attrs["revisit"];
25056
+ delete attrs["revisitReason"];
25057
+ delete attrs["revisitSinceTs"];
25058
+ store2.updateNode(nodeId, { attrs, lastUpdatedAt: ts });
25059
+ }
25002
25060
  function listNeedsRevisit(store2, asOf) {
25003
25061
  return store2.nodesAsOf(asOf).filter((n) => n.attrs["revisit"] === true).map((n) => ({
25004
25062
  id: n.id,
@@ -25342,7 +25400,7 @@ function mergeDuplicateProblems(store2, opts) {
25342
25400
  const report = { clusters: 0, merged: 0 };
25343
25401
  const open = store2.findNodesByLabel("Problem").filter((p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0);
25344
25402
  const tokens = /* @__PURE__ */ new Map();
25345
- for (const p of open) tokens.set(p.id, new Set(conceptTokens(p.description)));
25403
+ for (const p of open) tokens.set(p.id, new Set(retrievalTokens(p.description)));
25346
25404
  const consumed = /* @__PURE__ */ new Set();
25347
25405
  store2.transaction(() => {
25348
25406
  for (let i2 = 0; i2 < open.length; i2++) {
@@ -25701,7 +25759,11 @@ function backfillLegacyAnchors(store2, ts) {
25701
25759
  store2,
25702
25760
  sol,
25703
25761
  `auto-closed off a pre-provenance anchor (guessed ${guessedAnchor}) \u2014 confirm the problem is really fixed, or reopen it`,
25704
- ts
25762
+ ts,
25763
+ // "or reopen it" is half the ask, and the nightly reopen takes that
25764
+ // branch — so record what would answer this, or the flag can never be
25765
+ // lowered and the band fills with settled questions.
25766
+ "parentProblemOpen"
25705
25767
  );
25706
25768
  store2.updateNode(p.id, {
25707
25769
  attrs: { ...p.attrs, resolutionSuspect: true },
@@ -26284,6 +26346,7 @@ function runNightlyPipeline(store2) {
26284
26346
  });
26285
26347
  const motifs = promoteMotifs(store2, started);
26286
26348
  reopenAutoClosedProblems(store2, started);
26349
+ const revisits = clearAnsweredRevisits(store2, started);
26287
26350
  const designResolved = markFixCandidates(store2, started);
26288
26351
  const cry = crystallize(store2, { ts: started });
26289
26352
  return {
@@ -26299,6 +26362,7 @@ function runNightlyPipeline(store2) {
26299
26362
  // skills are cloud-induced now (see above)
26300
26363
  skillsRefreshed: 0,
26301
26364
  designResolved,
26365
+ revisitsCleared: revisits.cleared,
26302
26366
  claimsEvaluated: cry.evaluated,
26303
26367
  claimsDerived: cry.derived.length,
26304
26368
  durationMs: Date.now() - started