@inerrata-corporation/errata 2.0.22-dev.4665 → 2.0.22-dev.4676

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
@@ -18851,6 +18851,122 @@ var init_src2 = __esm({
18851
18851
  }
18852
18852
  });
18853
18853
 
18854
+ // ../../packages/local-graph/src/wm-calibration.ts
18855
+ function wmCalibrationCells(store) {
18856
+ const cells = {
18857
+ calibrationSchema: WM_CALIBRATION_SCHEMA,
18858
+ shownNodes: 0,
18859
+ shownImpressions: 0,
18860
+ evictedNodes: 0,
18861
+ evictedImpressions: 0,
18862
+ reinforceShown: 0,
18863
+ reinforceRecent: 0,
18864
+ reinforceEvicted: 0,
18865
+ reinforceUnshown: 0,
18866
+ reinforceUnshownQueryOnly: 0,
18867
+ reinforcedNodes: 0,
18868
+ reinforcedShownNodes: 0
18869
+ };
18870
+ const newest = /* @__PURE__ */ new Map();
18871
+ const rows = store.wmCalibrationRows?.(WM_CALIBRATION_LABELS) ?? (function* () {
18872
+ for (const label of WM_CALIBRATION_LABELS) {
18873
+ yield* store.selectNodes({ labels: [label], attrs: WM_CALIBRATION_KEYS, includeClosed: true });
18874
+ }
18875
+ })();
18876
+ for (const n of rows) {
18877
+ const live3 = n.validTo === null;
18878
+ const prev = newest.get(n.logicalId);
18879
+ if (prev && (prev.live || !live3 && prev.version >= n.version)) continue;
18880
+ newest.set(n.logicalId, { version: n.version, live: live3, attrs: n.attrs });
18881
+ }
18882
+ for (const { attrs } of newest.values()) {
18883
+ const shown = num(attrs["shownCount"]);
18884
+ const evicted = num(attrs["evictedCount"]);
18885
+ if (shown > 0) {
18886
+ cells.shownNodes++;
18887
+ cells.shownImpressions += shown;
18888
+ }
18889
+ if (evicted > 0) {
18890
+ cells.evictedNodes++;
18891
+ cells.evictedImpressions += evicted;
18892
+ }
18893
+ const rs = num(attrs["reinforceShownCount"]);
18894
+ const rr = num(attrs["reinforceRecentCount"]);
18895
+ const re = num(attrs["reinforceEvictedCount"]);
18896
+ const ru = num(attrs["reinforceUnshownCount"]);
18897
+ cells.reinforceShown += rs;
18898
+ cells.reinforceRecent += rr;
18899
+ cells.reinforceEvicted += re;
18900
+ cells.reinforceUnshown += ru;
18901
+ cells.reinforceUnshownQueryOnly += num(attrs["reinforceUnshownQueryOnlyCount"]);
18902
+ if (rs + rr + re + ru > 0) cells.reinforcedNodes++;
18903
+ if (rs > 0) cells.reinforcedShownNodes++;
18904
+ }
18905
+ return cells;
18906
+ }
18907
+ var WM_CALIBRATION_LABELS, WM_CALIBRATION_SCHEMA, num, WM_CALIBRATION_KEYS, WM_COUNTED_PREDICATE, wmCounterSql;
18908
+ var init_wm_calibration = __esm({
18909
+ "../../packages/local-graph/src/wm-calibration.ts"() {
18910
+ "use strict";
18911
+ WM_CALIBRATION_LABELS = [
18912
+ "Problem",
18913
+ "Solution",
18914
+ "RootCause",
18915
+ "Pattern",
18916
+ "Technique",
18917
+ "AntiPattern"
18918
+ ];
18919
+ WM_CALIBRATION_SCHEMA = 2;
18920
+ num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
18921
+ WM_CALIBRATION_KEYS = [
18922
+ "shownCount",
18923
+ "evictedCount",
18924
+ "reinforceShownCount",
18925
+ "reinforceRecentCount",
18926
+ "reinforceEvictedCount",
18927
+ "reinforceUnshownCount",
18928
+ "reinforceUnshownQueryOnlyCount"
18929
+ ];
18930
+ WM_COUNTED_PREDICATE = WM_CALIBRATION_KEYS.map((k) => `json_type(attrs_json, '$.${k}') IS NOT NULL`).join(" OR ");
18931
+ wmCounterSql = (k) => `CASE WHEN json_type(attrs_json, '$.${k}') IN ('integer', 'real') THEN json_extract(attrs_json, '$.${k}') END`;
18932
+ }
18933
+ });
18934
+
18935
+ // ../../packages/local-graph/src/exposure-attrs.ts
18936
+ function pickExposure(attrs) {
18937
+ const out2 = {};
18938
+ for (const k of EXPOSURE_ATTRS) if (attrs[k] !== void 0) out2[k] = attrs[k];
18939
+ return out2;
18940
+ }
18941
+ function exposureDocSql(key) {
18942
+ return `coalesce((SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id AND json_type(x.attrs_json, '$.${key}') IS NOT NULL), attrs_json)`;
18943
+ }
18944
+ var EXPOSURE_ATTRS, EXPOSURE_SCOPE, EXPOSURE_KEYS, isExposureAttr;
18945
+ var init_exposure_attrs = __esm({
18946
+ "../../packages/local-graph/src/exposure-attrs.ts"() {
18947
+ "use strict";
18948
+ EXPOSURE_ATTRS = [
18949
+ "shownCount",
18950
+ "evictedCount",
18951
+ "firstShownAt",
18952
+ "firstEvictedAt",
18953
+ "firstShownAtSeq",
18954
+ "lastShownAtSeq",
18955
+ "lastEvictedAtSeq",
18956
+ "shownSinceTouch",
18957
+ "shownSinceTouchAt",
18958
+ "exposureLog",
18959
+ "exposureLogStart",
18960
+ "exposureLogSince",
18961
+ "exposureFirst",
18962
+ "exposureTrimmed"
18963
+ ];
18964
+ EXPOSURE_SCOPE = "exposure";
18965
+ EXPOSURE_KEYS = new Set(EXPOSURE_ATTRS);
18966
+ isExposureAttr = (key) => EXPOSURE_KEYS.has(key);
18967
+ }
18968
+ });
18969
+
18854
18970
  // ../../packages/local-graph/src/history-cache-floor.ts
18855
18971
  function installHistoryCacheFloor(db) {
18856
18972
  const key = HISTORY_CACHE_FLOOR_KEY;
@@ -19171,31 +19287,6 @@ var init_text_index = __esm({
19171
19287
  }
19172
19288
  });
19173
19289
 
19174
- // ../../packages/local-graph/src/exposure-attrs.ts
19175
- var EXPOSURE_ATTRS, EXPOSURE_SCOPE;
19176
- var init_exposure_attrs = __esm({
19177
- "../../packages/local-graph/src/exposure-attrs.ts"() {
19178
- "use strict";
19179
- EXPOSURE_ATTRS = [
19180
- "shownCount",
19181
- "evictedCount",
19182
- "firstShownAt",
19183
- "firstEvictedAt",
19184
- "firstShownAtSeq",
19185
- "lastShownAtSeq",
19186
- "lastEvictedAtSeq",
19187
- "shownSinceTouch",
19188
- "shownSinceTouchAt",
19189
- "exposureLog",
19190
- "exposureLogStart",
19191
- "exposureLogSince",
19192
- "exposureFirst",
19193
- "exposureTrimmed"
19194
- ];
19195
- EXPOSURE_SCOPE = "exposure";
19196
- }
19197
- });
19198
-
19199
19290
  // ../../packages/local-graph/src/change-feed.ts
19200
19291
  function installChangeFeed(db) {
19201
19292
  if (changeFeedInstalled(db)) return;
@@ -19254,6 +19345,16 @@ function readChangedNodeIds(db, sinceSeq, labels) {
19254
19345
  const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
19255
19346
  return rows.map((r) => r.id);
19256
19347
  }
19348
+ function readChangedAmong(db, sinceSeq, ids) {
19349
+ if (!changeLogCovers(db, sinceSeq)) return null;
19350
+ const out2 = [];
19351
+ for (let i2 = 0; i2 < ids.length; i2 += 500) {
19352
+ const chunk = ids.slice(i2, i2 + 500);
19353
+ const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND id IN (${chunk.map(() => "?").join(",")})`).all(sinceSeq, ...chunk);
19354
+ for (const r of rows) out2.push(r.id);
19355
+ }
19356
+ return out2;
19357
+ }
19257
19358
  function readLineageChangedFiles(db, sinceSeq) {
19258
19359
  if (!changeLogCovers(db, sinceSeq)) return null;
19259
19360
  const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
@@ -20455,8 +20556,11 @@ function compileNodeWhere(q) {
20455
20556
  }
20456
20557
  return { where: conds.join(" AND "), params };
20457
20558
  }
20458
- function attrValueSql(p) {
20459
- return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
20559
+ function attrValueSql(p, doc = "attrs_json") {
20560
+ return `CASE json_type(${doc}, ${p}) WHEN 'integer' THEN CAST(json_extract(${doc}, ${p}) AS REAL) ELSE json_extract(${doc}, ${p}) END`;
20561
+ }
20562
+ function attrDocSql(key) {
20563
+ return isExposureAttr(key) ? exposureDocSql(key) : "attrs_json";
20460
20564
  }
20461
20565
  function nodeSelectFrom(q) {
20462
20566
  if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
@@ -20469,7 +20573,7 @@ function nodeSelectFrom(q) {
20469
20573
  function compileNodeSelect(q) {
20470
20574
  const { where, params } = compileNodeWhere(q);
20471
20575
  const attrs = q.attrs ?? [];
20472
- const attrCols = attrs.map((k, i2) => `, ${attrValueSql(path(k))} AS a${i2}, json_type(attrs_json, ${path(k)}) AS t${i2}`).join("");
20576
+ const attrCols = attrs.map((k, i2) => `, ${attrValueSql(path(k), attrDocSql(k))} AS a${i2}, json_type(${attrDocSql(k)}, ${path(k)}) AS t${i2}`).join("");
20473
20577
  let order;
20474
20578
  if (q.orderBy && "column" in q.orderBy) {
20475
20579
  order = `${column(q.orderBy.column)} ${q.orderBy.desc ? "DESC" : "ASC"}, rowid`;
@@ -20535,6 +20639,7 @@ var SELECT_INDEXES, EMBEDDING_DIM_SQL, KEY, COLUMNS;
20535
20639
  var init_node_select = __esm({
20536
20640
  "../../packages/local-graph/src/node-select.ts"() {
20537
20641
  "use strict";
20642
+ init_exposure_attrs();
20538
20643
  SELECT_INDEXES = [
20539
20644
  "nodes_live_local_community",
20540
20645
  "nodes_live_fold_candidate",
@@ -20587,7 +20692,7 @@ function graphStoreNeedsPreparation(path3) {
20587
20692
  if (!indexes.has(match2[1])) return true;
20588
20693
  }
20589
20694
  if (!changeFeedInstalled(db)) return true;
20590
- return [SESSION_REPAIR_KEY, HISTORY_CACHE_FLOOR_KEY, EDIT_PLACEHOLDER_FLOOR_KEY, CLOUD_ATTR_COMPACTION_KEY, HISTORY_ATTR_SLIM_KEY, TEXT_INDEX_KEY, LINEAGE_INTEGRITY_KEY].some((key) => !db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
20695
+ return [SESSION_REPAIR_KEY, HISTORY_CACHE_FLOOR_KEY, EDIT_PLACEHOLDER_FLOOR_KEY, CLOUD_ATTR_COMPACTION_KEY, HISTORY_ATTR_SLIM_KEY, TEXT_INDEX_KEY, LINEAGE_INTEGRITY_KEY, EXPOSURE_SIDE_KEY].some((key) => !db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
20591
20696
  } catch {
20592
20697
  return true;
20593
20698
  } finally {
@@ -20654,12 +20759,14 @@ function rowToEdge(r) {
20654
20759
  function openGraphStore(opts) {
20655
20760
  return new SqliteGraphStore(opts);
20656
20761
  }
20657
- var PAGE_RANK_SCALE_KEY, TEXT_SEARCH_COLUMNS, LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_REPAIR_KEY, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, NODE_LABELS_CHUNK, PRIOR_TAG_MARKER, SqliteGraphStore;
20762
+ var PAGE_RANK_SCALE_KEY, TEXT_SEARCH_COLUMNS, LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_REPAIR_KEY, EXPOSURE_SIDE_KEY, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, NODE_LABELS_CHUNK, PRIOR_TAG_MARKER, SqliteGraphStore;
20658
20763
  var init_store = __esm({
20659
20764
  "../../packages/local-graph/src/store.ts"() {
20660
20765
  "use strict";
20661
20766
  init_src2();
20662
20767
  init_src();
20768
+ init_wm_calibration();
20769
+ init_exposure_attrs();
20663
20770
  init_history_cache_floor();
20664
20771
  init_session_evidence();
20665
20772
  init_edit_placeholder_retirement();
@@ -20704,6 +20811,7 @@ var init_store = __esm({
20704
20811
  };
20705
20812
  SCHEMA_VERSION = 6;
20706
20813
  SESSION_REPAIR_KEY = "session-evidence-v1";
20814
+ EXPOSURE_SIDE_KEY = "exposure-side-rows-1";
20707
20815
  SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
20708
20816
  SCHEMA_SQL = `
20709
20817
  CREATE TABLE IF NOT EXISTS schema_version (
@@ -20767,8 +20875,13 @@ CREATE TABLE IF NOT EXISTS nodes (
20767
20875
  );
20768
20876
 
20769
20877
  CREATE INDEX IF NOT EXISTS nodes_label ON nodes(label);
20770
- CREATE INDEX IF NOT EXISTS nodes_memory_tier ON nodes(memory_tier);
20771
- CREATE INDEX IF NOT EXISTS nodes_page_rank ON nodes(page_rank DESC);
20878
+ -- Dropped: no query plans through them (every prepared statement across the
20879
+ -- local-graph, indexer, context-writer, generalizer and daemon suites was
20880
+ -- planned with and without them, 2026-10-21), and each write that touches
20881
+ -- memory_tier / page_rank (every node upsert, every rescore) paid to maintain
20882
+ -- them. nodes_live_page_rank serves the live page-rank orderings.
20883
+ DROP INDEX IF EXISTS nodes_memory_tier;
20884
+ DROP INDEX IF EXISTS nodes_page_rank;
20772
20885
 
20773
20886
  CREATE TABLE IF NOT EXISTS edges (
20774
20887
  id TEXT PRIMARY KEY,
@@ -20790,7 +20903,10 @@ CREATE TABLE IF NOT EXISTS edges (
20790
20903
  FOREIGN KEY (to_id) REFERENCES nodes(id) ON DELETE CASCADE
20791
20904
  );
20792
20905
 
20793
- CREATE INDEX IF NOT EXISTS edges_from ON edges(from_id, type);
20906
+ -- Dropped: a prefix of edges_triple (from_id, type, to_id), which every
20907
+ -- from-side lookup and foreign-key check already plans through; it cost 47 MB
20908
+ -- and an index write on every edge write.
20909
+ DROP INDEX IF EXISTS edges_from;
20794
20910
  CREATE INDEX IF NOT EXISTS edges_to ON edges(to_id, type);
20795
20911
  CREATE INDEX IF NOT EXISTS edges_type ON edges(type);
20796
20912
  CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
@@ -20812,6 +20928,12 @@ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to,
20812
20928
  -- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
20813
20929
  -- Problems; without it every live Problem was read to find them (56 MB).
20814
20930
  CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
20931
+ -- Render impressions, one row per node id (exposure-attrs.ts): a render bumps
20932
+ -- ~40 of these, which in the node row rewrote each whole row and its indexes.
20933
+ CREATE TABLE IF NOT EXISTS node_exposure (
20934
+ id TEXT PRIMARY KEY,
20935
+ attrs_json TEXT NOT NULL DEFAULT '{}'
20936
+ ) WITHOUT ROWID;
20815
20937
  -- The semantic-maintenance sweeps' own filters (closed, asked, hinted): each read
20816
20938
  -- every Problem row (~30 MB on a 1.1 GB store) for the few hundred that match,
20817
20939
  -- several times a run, and a run follows most writes (2026-10-20).
@@ -20819,6 +20941,9 @@ CREATE INDEX IF NOT EXISTS nodes_live_resolved ON nodes(label, valid_to) WHERE v
20819
20941
  CREATE INDEX IF NOT EXISTS nodes_live_fix_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.fixCandidateAt') IS NOT NULL;
20820
20942
  CREATE INDEX IF NOT EXISTS nodes_live_anchor_hint ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint') IS NOT NULL;
20821
20943
  CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
20944
+ -- Rows carrying a calibration counter, any version: the logical nodes wm
20945
+ -- calibration can count (~3k), found without reading every citable row.
20946
+ CREATE INDEX IF NOT EXISTS nodes_wm_counted ON nodes(logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map(wmCounterSql).join(", ")}) WHERE ${WM_COUNTED_PREDICATE};
20822
20947
  CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
20823
20948
  -- Covering + partial index for labelFingerprint over the instance labels: the
20824
20949
  -- live-push probe runs it per workspace on a timer, and each hit used to read
@@ -20915,6 +21040,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20915
21040
  constructor(opts) {
20916
21041
  this.db = openDatabase(opts.path);
20917
21042
  this.db.exec("PRAGMA cache_size = -32768");
21043
+ this.db.exec("PRAGMA mmap_size = 2147483648");
20918
21044
  this.db.exec(SCHEMA_SQL);
20919
21045
  const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
20920
21046
  if (!versionRow) {
@@ -20978,8 +21104,17 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
20978
21104
  last_reinforced_at_seq = excluded.last_reinforced_at_seq,
20979
21105
  attrs_json = excluded.attrs_json
20980
21106
  `),
20981
- getNode: this.db.prepare("SELECT * FROM nodes WHERE id = ?"),
21107
+ getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
21108
+ exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
21109
+ writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
21110
+ lastUpdatedOf: this.db.prepare("SELECT last_updated_at AS t, attrs_json FROM nodes WHERE id = ?"),
20982
21111
  labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
21112
+ nodeContent: this.db.prepare(
21113
+ "SELECT label, description, extraction_confidence, extraction_source, stability, valid_to, attrs_json FROM nodes WHERE id = ?"
21114
+ ),
21115
+ edgeContent: this.db.prepare(
21116
+ "SELECT confidence, valid_to, attrs_json FROM edges WHERE from_id = ? AND type = ? AND to_id = ?"
21117
+ ),
20983
21118
  // Single-column fast paths for the nightly pass — writing 100k pageRank
20984
21119
  // values through the 18-column COALESCE updateNode took ~30s; these are ~10x
20985
21120
  // cheaper (one bound column, no COALESCE evaluation per row).
@@ -21136,6 +21271,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21136
21271
  "INSERT INTO store_meta (key, value) VALUES ('ingest_seq', :v) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
21137
21272
  )
21138
21273
  };
21274
+ this.moveExposureToSideRows();
21139
21275
  const seqRow = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
21140
21276
  this.ingestSeq = seqRow ? Number(seqRow.value) : 0;
21141
21277
  }
@@ -21191,6 +21327,41 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21191
21327
  ORDER BY label, created_at, rowid`
21192
21328
  ).all(label);
21193
21329
  }
21330
+ wmCalibrationRows(labels) {
21331
+ const counted = this.db.prepare(
21332
+ `SELECT rowid AS rid, logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map((k, i2) => `${wmCounterSql(k)} AS c${i2}`).join(", ")}
21333
+ FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`
21334
+ ).all();
21335
+ const exposed = this.db.prepare(
21336
+ `SELECT n.rowid AS rid, n.logical_id, n.version, n.valid_to, n.label, x.attrs_json AS exposure
21337
+ FROM node_exposure x JOIN nodes n ON n.id = x.id`
21338
+ ).all();
21339
+ const others = this.db.prepare(
21340
+ `SELECT rowid AS rid, logical_id, version, valid_to, label FROM nodes WHERE rowid IN (
21341
+ SELECT n.rowid FROM nodes AS n INDEXED BY nodes_logical
21342
+ WHERE n.logical_id IN (SELECT logical_id FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
21343
+ UNION SELECT n2.logical_id FROM node_exposure x2 JOIN nodes n2 ON n2.id = x2.id)
21344
+ EXCEPT SELECT rowid FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
21345
+ EXCEPT SELECT n3.rowid FROM node_exposure x3 JOIN nodes n3 ON n3.id = x3.id)`
21346
+ ).all();
21347
+ const byRid = /* @__PURE__ */ new Map();
21348
+ for (const r of [...counted, ...others]) {
21349
+ const attrs = {};
21350
+ WM_CALIBRATION_KEYS.forEach((k, i2) => {
21351
+ const v = r[`c${i2}`];
21352
+ if (typeof v === "number") attrs[k] = v;
21353
+ });
21354
+ byRid.set(r.rid, { row: r, attrs });
21355
+ }
21356
+ for (const r of exposed) {
21357
+ const side = JSON.parse(r.exposure);
21358
+ const entry = byRid.get(r.rid) ?? { row: r, attrs: {} };
21359
+ for (const k of WM_CALIBRATION_KEYS) if (typeof side[k] === "number" && Number.isFinite(side[k])) entry.attrs[k] = side[k];
21360
+ byRid.set(r.rid, entry);
21361
+ }
21362
+ const order = new Map(labels.map((l, i2) => [l, i2]));
21363
+ return [...byRid.values()].filter(({ row }) => order.has(row.label)).sort((a, b) => order.get(a.row.label) - order.get(b.row.label) || a.row.rid - b.row.rid).map(({ row, attrs }) => ({ logicalId: row.logical_id, version: Number(row.version), validTo: row.valid_to ?? null, attrs }));
21364
+ }
21194
21365
  liveCommentsAttachedTo(name2) {
21195
21366
  return this.db.prepare(
21196
21367
  `SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
@@ -21327,9 +21498,22 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21327
21498
  logical_id: node2.logicalId ?? node2.id,
21328
21499
  created_at_seq: node2.createdAtSeq ?? null,
21329
21500
  last_reinforced_at_seq: node2.lastReinforcedAtSeq ?? null,
21330
- attrs_json: JSON.stringify(node2.attrs ?? {})
21501
+ attrs_json: this.nodeAttrsJson(node2.id, node2.attrs ?? {})
21331
21502
  });
21332
21503
  }
21504
+ /** mergeNode, unless the live row already holds this content. Content is what
21505
+ * the columns a merge rewrites from the producer (label, description, attrs,
21506
+ * confidence, stability);
21507
+ * rank, community, landmark, surprise, hits, tier and the vector belong to
21508
+ * the passes that compute them, and a skipped write leaves them as they are
21509
+ * where a merge would have reset them to the producer's defaults. Returns
21510
+ * whether it wrote. */
21511
+ mergeNodeIfChanged(node2) {
21512
+ const row = this.stmts.nodeContent.get(node2.id);
21513
+ if (row && row.valid_to === null && row.label === node2.label && row.description === node2.description && row.extraction_confidence === node2.extractionConfidence && row.stability === node2.stability && !EXPOSURE_ATTRS.some((k) => node2.attrs?.[k] !== void 0) && row.attrs_json === JSON.stringify(node2.attrs ?? {})) return false;
21514
+ this.mergeNode(node2);
21515
+ return true;
21516
+ }
21333
21517
  updateNode(id, patch) {
21334
21518
  const hasEmbedding = patch.embedding !== void 0 && patch.embedding.length > 0;
21335
21519
  this.stmts.updateNode.run({
@@ -21353,7 +21537,37 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21353
21537
  logical_id: patch.logicalId ?? null,
21354
21538
  created_at_seq: patch.createdAtSeq ?? null,
21355
21539
  last_reinforced_at_seq: patch.lastReinforcedAtSeq ?? null,
21356
- attrs_json: patch.attrs ? JSON.stringify(patch.attrs) : null
21540
+ attrs_json: patch.attrs ? this.nodeAttrsJson(id, patch.attrs) : null
21541
+ });
21542
+ }
21543
+ /**
21544
+ * The node-row attrs of a write, after handing any exposure keys to the side
21545
+ * row (exposure-attrs.ts). `getNode` returns exposure merged into attrs, so a
21546
+ * read-modify-write carries it; kept in the row it would re-bloat the row and
21547
+ * drift from the side row. Unchanged exposure costs no write.
21548
+ */
21549
+ nodeAttrsJson(id, attrs) {
21550
+ if (!EXPOSURE_ATTRS.some((k) => attrs[k] !== void 0)) return JSON.stringify(attrs);
21551
+ const current = this.stmts.exposureOf.get(id);
21552
+ const next = JSON.stringify({ ...current ? JSON.parse(current.attrs_json) : {}, ...pickExposure(attrs) });
21553
+ if (current?.attrs_json !== next) this.stmts.writeExposure.run(id, next);
21554
+ const rest2 = { ...attrs };
21555
+ for (const k of EXPOSURE_ATTRS) delete rest2[k];
21556
+ return JSON.stringify(rest2);
21557
+ }
21558
+ /** One-time: exposure keys written to node rows before the side table existed
21559
+ * move to it. Only rows carrying a counter can carry exposure, and those are
21560
+ * what nodes_wm_counted holds, so this reads the index, not the table. */
21561
+ moveExposureToSideRows() {
21562
+ if (this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(EXPOSURE_SIDE_KEY)) return;
21563
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`).all();
21564
+ const move = this.db.prepare("UPDATE nodes SET attrs_json = ? WHERE id = ?");
21565
+ this.db.transaction(() => {
21566
+ for (const r of rows) {
21567
+ const stripped = this.nodeAttrsJson(r.id, JSON.parse(r.attrs_json));
21568
+ if (stripped !== r.attrs_json) move.run(stripped, r.id);
21569
+ }
21570
+ this.db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, 'done')").run(EXPOSURE_SIDE_KEY);
21357
21571
  });
21358
21572
  }
21359
21573
  /** Fast single-column writes for the nightly pass (avoid the 18-column COALESCE
@@ -21435,6 +21649,21 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21435
21649
  attrs_json: JSON.stringify(edge2.attrs ?? {})
21436
21650
  });
21437
21651
  }
21652
+ /** mergeEdge, unless the live edge already holds this content: same attrs, and
21653
+ * a confidence the upsert's MAX would keep. Its navigation posteriors and
21654
+ * last-seen stamp stay as they are where a merge would have reset them. The
21655
+ * ontology gate runs first either way, so refusals are recorded as before.
21656
+ * Returns whether it wrote (or refused). */
21657
+ mergeEdgeIfChanged(edge2) {
21658
+ if (!this.edgeRuleViolation(edge2)) {
21659
+ const row = this.stmts.edgeContent.get(edge2.from, edge2.type, edge2.to);
21660
+ if (row && row.valid_to === null && row.confidence >= edge2.confidence && row.attrs_json === JSON.stringify(edge2.attrs ?? {})) {
21661
+ return false;
21662
+ }
21663
+ }
21664
+ this.mergeEdge(edge2);
21665
+ return true;
21666
+ }
21438
21667
  /** EDGE_RULES check against the stored endpoints' labels, with the LOCAL
21439
21668
  * overlay consulted first. Returns the reason string on a documented-
21440
21669
  * forbidden combination, else null. Point lookups on the id PK — negligible
@@ -21501,7 +21730,27 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21501
21730
  }
21502
21731
  getNode(id) {
21503
21732
  const row = this.stmts.getNode.get(id);
21504
- return row ? rowToNode(row) : null;
21733
+ if (!row) return null;
21734
+ const node2 = rowToNode(row);
21735
+ if (row.exposure_json) Object.assign(node2.attrs, JSON.parse(row.exposure_json));
21736
+ return node2;
21737
+ }
21738
+ /** A node's render exposure: the side row's keys over the node row's own
21739
+ * (exposure-attrs.ts), plus the node's `lastUpdatedAt` (the "shown since
21740
+ * touch" counter is relative to it) — without reading the node's attrs. */
21741
+ exposureOf(id) {
21742
+ const row = this.stmts.lastUpdatedOf.get(id);
21743
+ if (!row) return null;
21744
+ const side = this.stmts.exposureOf.get(id);
21745
+ return {
21746
+ attrs: { ...pickExposure(JSON.parse(row.attrs_json)), ...side ? JSON.parse(side.attrs_json) : {} },
21747
+ lastUpdatedAt: row.t
21748
+ };
21749
+ }
21750
+ /** Replace a node's exposure (only EXPOSURE_ATTRS keys belong here). Touches
21751
+ * neither the node row nor its indexes, nor the change feed. */
21752
+ writeExposure(id, attrs) {
21753
+ this.stmts.writeExposure.run(id, JSON.stringify(attrs));
21505
21754
  }
21506
21755
  nodeLabels(ids) {
21507
21756
  const width = NODE_LABELS_CHUNK;
@@ -21552,7 +21801,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21552
21801
  findNodeStampsByLabel(label, attrKey) {
21553
21802
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attrKey)) throw new Error(`findNodeStampsByLabel: bad attr key ${attrKey}`);
21554
21803
  const rows = this.db.prepare(
21555
- `SELECT id, created_at, created_at_seq, json_type(attrs_json, '$.${attrKey}') IS NOT NULL AS has,
21804
+ `SELECT id, created_at, created_at_seq, json_type(${attrDocSql(attrKey)}, '$.${attrKey}') IS NOT NULL AS has,
21556
21805
  COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
21557
21806
  FROM nodes WHERE label = ? AND valid_to IS NULL`
21558
21807
  ).all(label);
@@ -21569,7 +21818,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21569
21818
  const keys = [...new Set(attrKeys)];
21570
21819
  const rows = this.db.prepare(
21571
21820
  `SELECT id, created_at, created_at_seq,
21572
- ${keys.map((k, i2) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
21821
+ ${keys.map((k, i2) => `json_type(${attrDocSql(k)}, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
21573
21822
  COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
21574
21823
  FROM nodes WHERE label = ? AND valid_to IS NULL`
21575
21824
  ).all(label);
@@ -21829,6 +22078,9 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
21829
22078
  changedNodeIds(sinceSeq, labels) {
21830
22079
  return readChangedNodeIds(this.db, sinceSeq, labels);
21831
22080
  }
22081
+ changedAmong(sinceSeq, ids) {
22082
+ return readChangedAmong(this.db, sinceSeq, ids);
22083
+ }
21832
22084
  pruneChangeLog() {
21833
22085
  return pruneChangeLog(this.db);
21834
22086
  }
@@ -24791,9 +25043,11 @@ function recordRenderLedger(store, ledger, seq, ts) {
24791
25043
  const bump = (ids, field) => {
24792
25044
  let n = 0;
24793
25045
  for (const id of new Set(ids.map((i2) => i2.id))) {
24794
- const node2 = store.getNode(id);
24795
- if (!node2) continue;
24796
- const attrs = { ...node2.attrs };
25046
+ const side = store.exposureOf?.(id);
25047
+ const node2 = side ? null : store.getNode(id);
25048
+ if (!side && !node2) continue;
25049
+ const lastUpdatedAt = side ? side.lastUpdatedAt : node2.lastUpdatedAt;
25050
+ const attrs = side ? { ...side.attrs } : { ...node2.attrs };
24797
25051
  if (field === "shownCount" && attrs["firstShownAt"] === void 0) {
24798
25052
  attrs["firstShownAt"] = attrs["firstShownAtSeq"] !== void 0 || attrs["lastShownAtSeq"] !== void 0 ? 0 : ts;
24799
25053
  }
@@ -24804,15 +25058,16 @@ function recordRenderLedger(store, ledger, seq, ts) {
24804
25058
  if (field === "shownCount") {
24805
25059
  attrs["lastShownAtSeq"] = seq;
24806
25060
  if (attrs["firstShownAtSeq"] === void 0) attrs["firstShownAtSeq"] = seq;
24807
- const counted = attrs["shownSinceTouchAt"] === node2.lastUpdatedAt ? Number(attrs["shownSinceTouch"] ?? 0) : 0;
25061
+ const counted = attrs["shownSinceTouchAt"] === lastUpdatedAt ? Number(attrs["shownSinceTouch"] ?? 0) : 0;
24808
25062
  attrs["shownSinceTouch"] = counted + 1;
24809
- attrs["shownSinceTouchAt"] = node2.lastUpdatedAt;
25063
+ attrs["shownSinceTouchAt"] = lastUpdatedAt;
24810
25064
  logImpression(attrs, ts, retrieved.has(id) ? "r" : "c");
24811
25065
  } else {
24812
25066
  attrs["lastEvictedAtSeq"] = seq;
24813
25067
  logImpression(attrs, ts, "e");
24814
25068
  }
24815
- store.updateNode(id, { attrs, lastUpdatedAt: node2.lastUpdatedAt });
25069
+ if (side) store.writeExposure(id, pickExposure(attrs));
25070
+ else store.updateNode(id, { attrs, lastUpdatedAt });
24816
25071
  n++;
24817
25072
  }
24818
25073
  return n;
@@ -24921,6 +25176,7 @@ var init_design_problem = __esm({
24921
25176
  init_src();
24922
25177
  init_src();
24923
25178
  init_problem_package_link();
25179
+ init_exposure_attrs();
24924
25180
  init_src4();
24925
25181
  init_src2();
24926
25182
  init_problem_package_link();
@@ -27438,82 +27694,6 @@ var init_mechanism_liveness = __esm({
27438
27694
  }
27439
27695
  });
27440
27696
 
27441
- // ../../packages/local-graph/src/wm-calibration.ts
27442
- function wmCalibrationCells(store) {
27443
- const cells = {
27444
- calibrationSchema: WM_CALIBRATION_SCHEMA,
27445
- shownNodes: 0,
27446
- shownImpressions: 0,
27447
- evictedNodes: 0,
27448
- evictedImpressions: 0,
27449
- reinforceShown: 0,
27450
- reinforceRecent: 0,
27451
- reinforceEvicted: 0,
27452
- reinforceUnshown: 0,
27453
- reinforceUnshownQueryOnly: 0,
27454
- reinforcedNodes: 0,
27455
- reinforcedShownNodes: 0
27456
- };
27457
- const newest = /* @__PURE__ */ new Map();
27458
- for (const label of WM_CALIBRATION_LABELS) {
27459
- for (const n of store.selectNodes({ labels: [label], attrs: WM_CALIBRATION_KEYS, includeClosed: true })) {
27460
- const live3 = n.validTo === null;
27461
- const prev = newest.get(n.logicalId);
27462
- if (prev && (prev.live || !live3 && prev.version >= n.version)) continue;
27463
- newest.set(n.logicalId, { version: n.version, live: live3, attrs: n.attrs });
27464
- }
27465
- }
27466
- for (const { attrs } of newest.values()) {
27467
- const shown = num(attrs["shownCount"]);
27468
- const evicted = num(attrs["evictedCount"]);
27469
- if (shown > 0) {
27470
- cells.shownNodes++;
27471
- cells.shownImpressions += shown;
27472
- }
27473
- if (evicted > 0) {
27474
- cells.evictedNodes++;
27475
- cells.evictedImpressions += evicted;
27476
- }
27477
- const rs = num(attrs["reinforceShownCount"]);
27478
- const rr = num(attrs["reinforceRecentCount"]);
27479
- const re = num(attrs["reinforceEvictedCount"]);
27480
- const ru = num(attrs["reinforceUnshownCount"]);
27481
- cells.reinforceShown += rs;
27482
- cells.reinforceRecent += rr;
27483
- cells.reinforceEvicted += re;
27484
- cells.reinforceUnshown += ru;
27485
- cells.reinforceUnshownQueryOnly += num(attrs["reinforceUnshownQueryOnlyCount"]);
27486
- if (rs + rr + re + ru > 0) cells.reinforcedNodes++;
27487
- if (rs > 0) cells.reinforcedShownNodes++;
27488
- }
27489
- return cells;
27490
- }
27491
- var WM_CALIBRATION_LABELS, WM_CALIBRATION_SCHEMA, num, WM_CALIBRATION_KEYS;
27492
- var init_wm_calibration = __esm({
27493
- "../../packages/local-graph/src/wm-calibration.ts"() {
27494
- "use strict";
27495
- WM_CALIBRATION_LABELS = [
27496
- "Problem",
27497
- "Solution",
27498
- "RootCause",
27499
- "Pattern",
27500
- "Technique",
27501
- "AntiPattern"
27502
- ];
27503
- WM_CALIBRATION_SCHEMA = 2;
27504
- num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
27505
- WM_CALIBRATION_KEYS = [
27506
- "shownCount",
27507
- "evictedCount",
27508
- "reinforceShownCount",
27509
- "reinforceRecentCount",
27510
- "reinforceEvictedCount",
27511
- "reinforceUnshownCount",
27512
- "reinforceUnshownQueryOnlyCount"
27513
- ];
27514
- }
27515
- });
27516
-
27517
27697
  // ../../packages/local-graph/src/index.ts
27518
27698
  var src_exports2 = {};
27519
27699
  __export(src_exports2, {
@@ -27560,8 +27740,10 @@ __export(src_exports2, {
27560
27740
  STALL_MIN_RUNS: () => STALL_MIN_RUNS,
27561
27741
  STALL_MIN_SPAN_MS: () => STALL_MIN_SPAN_MS,
27562
27742
  SqliteGraphStore: () => SqliteGraphStore,
27743
+ WM_CALIBRATION_KEYS: () => WM_CALIBRATION_KEYS,
27563
27744
  WM_CALIBRATION_LABELS: () => WM_CALIBRATION_LABELS,
27564
27745
  WM_CALIBRATION_SCHEMA: () => WM_CALIBRATION_SCHEMA,
27746
+ WM_COUNTED_PREDICATE: () => WM_COUNTED_PREDICATE,
27565
27747
  WORKSPACE_SCAN_CAP: () => WORKSPACE_SCAN_CAP,
27566
27748
  addDependency: () => addDependency,
27567
27749
  aggregateClaimConfidence: () => aggregateClaimConfidence,
@@ -27708,7 +27890,8 @@ __export(src_exports2, {
27708
27890
  upgradeAnchorHints: () => upgradeAnchorHints,
27709
27891
  upgradeAnchorHintsForFile: () => upgradeAnchorHintsForFile,
27710
27892
  walk: () => walk,
27711
- wmCalibrationCells: () => wmCalibrationCells
27893
+ wmCalibrationCells: () => wmCalibrationCells,
27894
+ wmCounterSql: () => wmCounterSql
27712
27895
  });
27713
27896
  var init_src5 = __esm({
27714
27897
  "../../packages/local-graph/src/index.ts"() {
@@ -27741,6 +27924,95 @@ var init_src5 = __esm({
27741
27924
  }
27742
27925
  });
27743
27926
 
27927
+ // ../../packages/context-writer/src/snapshot-inputs.ts
27928
+ function guardSnapshotReads(store) {
27929
+ const fail = (what) => {
27930
+ throw new Error(`buildSnapshot read ${what}, outside SNAPSHOT_NODE_LABELS/SNAPSHOT_EDGE_TYPES (snapshot-inputs.ts)`);
27931
+ };
27932
+ const label = (l, via) => {
27933
+ if (typeof l === "string" && !LABELS.has(l)) fail(`label ${l} via ${via}`);
27934
+ };
27935
+ const types = (t, via) => {
27936
+ if (!Array.isArray(t) || t.length === 0) fail(`every edge type via ${via}`);
27937
+ for (const x of t) if (!EDGES.has(String(x))) fail(`edge type ${String(x)} via ${via}`);
27938
+ };
27939
+ const nodes = (r, via) => {
27940
+ for (const n of Array.isArray(r) ? r : r ? [r] : []) label(n.label, via);
27941
+ return r;
27942
+ };
27943
+ const allowed = {
27944
+ getNode: (id) => nodes(store.getNode(id), "getNode"),
27945
+ selectNodes: (q) => {
27946
+ for (const l of q.labels) label(l, "selectNodes");
27947
+ return store.selectNodes(q);
27948
+ },
27949
+ countNodes: (q) => {
27950
+ for (const l of q.labels) label(l, "countNodes");
27951
+ return store.countNodes(q);
27952
+ },
27953
+ findLiveNodesByAttr: (l, ...rest2) => {
27954
+ label(l, "findLiveNodesByAttr");
27955
+ return store.findLiveNodesByAttr(l, ...rest2);
27956
+ },
27957
+ outEdges: (id, t) => {
27958
+ types(t, "outEdges");
27959
+ return store.outEdges(id, t);
27960
+ },
27961
+ inEdges: (id, t) => {
27962
+ types(t, "inEdges");
27963
+ return store.inEdges(id, t);
27964
+ },
27965
+ inDegreeByType: (t) => {
27966
+ types(t, "inDegreeByType");
27967
+ return store.inDegreeByType(t);
27968
+ },
27969
+ nodesAsOfWhereTrue: (asOf, attr) => {
27970
+ if (attr !== "revisit") fail(`nodesAsOfWhereTrue(${String(attr)})`);
27971
+ return store.nodesAsOfWhereTrue(asOf, attr);
27972
+ }
27973
+ };
27974
+ return new Proxy(store, {
27975
+ get(target, prop) {
27976
+ if (typeof prop === "string" && prop in allowed) return allowed[prop];
27977
+ const v = Reflect.get(target, prop, target);
27978
+ if (typeof v === "function") return () => fail(`store.${String(prop)}()`);
27979
+ return v;
27980
+ }
27981
+ });
27982
+ }
27983
+ var SNAPSHOT_NODE_LABELS, SNAPSHOT_EDGE_TYPES, SNAPSHOT_READ_GUARD, LABELS, EDGES;
27984
+ var init_snapshot_inputs = __esm({
27985
+ "../../packages/context-writer/src/snapshot-inputs.ts"() {
27986
+ "use strict";
27987
+ SNAPSHOT_NODE_LABELS = [
27988
+ "Problem",
27989
+ "Vulnerability",
27990
+ "Solution",
27991
+ "Pattern",
27992
+ "Technique",
27993
+ "AntiPattern",
27994
+ "Language",
27995
+ "Package",
27996
+ "Tool"
27997
+ ];
27998
+ SNAPSHOT_EDGE_TYPES = [
27999
+ "ANCHORED_AT",
28000
+ "CAUSED_BY",
28001
+ "CONCERNS",
28002
+ "DEPENDS_ON",
28003
+ "EVIDENCED_BY",
28004
+ "MANIFESTED_IN",
28005
+ "OCCURS_IN",
28006
+ "PERTAIN_TO",
28007
+ "REGRESSION_OF",
28008
+ "SOLVED_BY"
28009
+ ];
28010
+ SNAPSHOT_READ_GUARD = typeof process !== "undefined" && Boolean(process.env["VITEST"]);
28011
+ LABELS = new Set(SNAPSHOT_NODE_LABELS);
28012
+ EDGES = new Set(SNAPSHOT_EDGE_TYPES);
28013
+ }
28014
+ });
28015
+
27744
28016
  // ../../packages/context-writer/src/render.ts
27745
28017
  function isPassiveInjectable(node2, atCommit) {
27746
28018
  if (!historyReadPolicy(node2, { mode: "passive", atCommit }).allow) return false;
@@ -27766,6 +28038,8 @@ function passiveAllowed(row, atCommit) {
27766
28038
  return historyReadPolicy({ extractionSource: row.extractionSource, attrs: row.attrs }, { mode: "passive", atCommit }).allow;
27767
28039
  }
27768
28040
  function buildSnapshot(opts) {
28041
+ const codeStore = opts.store;
28042
+ if (SNAPSHOT_READ_GUARD) opts = { ...opts, store: guardSnapshotReads(opts.store) };
27769
28043
  const hydrate = (id) => opts.store.getNode(id);
27770
28044
  const problems = opts.store.selectNodes({ labels: ["Problem", "Vulnerability"], attrs: PROBLEM_BAND_ATTRS }).filter((p) => p.attrs["mergedInto"] === void 0 && passiveAllowed(p, opts.atCommit));
27771
28045
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
@@ -27801,7 +28075,7 @@ function buildSnapshot(opts) {
27801
28075
  const recentResolved = resolvedPool.slice(0, RECENT_RESOLVED);
27802
28076
  const motifPool = opts.store.selectNodes({ labels: ["Pattern", "Technique", "AntiPattern"], attrs: ["missedRuns", ...HISTORY_POLICY_ATTRS] }).filter((n) => passiveAllowed(n, opts.atCommit)).filter((n) => !(Number(n.attrs["missedRuns"] ?? 0) > 0)).sort((a, b) => b.pageRank - a.pageRank).slice(0, MOTIF_POOL).map((r) => hydrate(r.id)).map((n) => n.embedding.length > 0 ? { ...n, embedding: [] } : n);
27803
28077
  const motifs = rankMotifs(motifPool, void 0);
27804
- const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile, opts.atCommit) : void 0;
28078
+ const workingFile = opts.workingFile ? sliceForFile(codeStore, opts.workingFile, opts.atCommit) : void 0;
27805
28079
  const byEpisode = /* @__PURE__ */ new Map();
27806
28080
  for (const sol of opts.store.findLiveNodesByAttr("Solution", "enrichmentPending", 1)) {
27807
28081
  if (!historyReadPolicy(sol, { mode: "passive", atCommit: opts.atCommit }).allow) continue;
@@ -28482,6 +28756,7 @@ var init_render = __esm({
28482
28756
  init_src5();
28483
28757
  init_src2();
28484
28758
  init_src();
28759
+ init_snapshot_inputs();
28485
28760
  MOTIF_BAND = 10;
28486
28761
  MOTIF_POOL = 1e3;
28487
28762
  MOTIF_SEEDS = 8;
@@ -28811,6 +29086,7 @@ var init_src6 = __esm({
28811
29086
  init_context_file();
28812
29087
  init_redirectors();
28813
29088
  init_render();
29089
+ init_snapshot_inputs();
28814
29090
  init_bleed_memory();
28815
29091
  init_bleed_rules();
28816
29092
  init_select_durable_memory();
@@ -38815,10 +39091,20 @@ function isReExportBarrel(relPath) {
38815
39091
  function graphExportMap(store, workspaceId2, relPath) {
38816
39092
  const out2 = /* @__PURE__ */ new Map();
38817
39093
  const fileId = fileNodeId(workspaceId2, relPath);
38818
- if (!store.getNode(fileId)) return out2;
38819
- for (const e of store.outEdges(fileId, ["DEFINES"])) {
38820
- const n = store.getNode(e.to);
38821
- if (!n || !EXPORTABLE_LABELS.has(n.label)) continue;
39094
+ if (store.nodeLabels([fileId]).size === 0) return out2;
39095
+ const defines = store.outEdges(fileId, ["DEFINES"]);
39096
+ const rows = /* @__PURE__ */ new Map();
39097
+ for (let i2 = 0; i2 < defines.length; i2 += 500) {
39098
+ for (const r of store.selectNodes({
39099
+ labels: [...EXPORTABLE_LABELS],
39100
+ attrs: ["workspaceId", "name"],
39101
+ where: [{ idIn: defines.slice(i2, i2 + 500).map((e) => e.to) }],
39102
+ includeClosed: true
39103
+ })) rows.set(r.id, r);
39104
+ }
39105
+ for (const e of defines) {
39106
+ const n = rows.get(e.to);
39107
+ if (!n) continue;
38822
39108
  if (n.attrs["workspaceId"] !== workspaceId2) continue;
38823
39109
  const name2 = n.attrs["name"] ?? n.description;
38824
39110
  if (!name2 || name2.startsWith("<") || name2.includes(":")) continue;
@@ -38994,7 +39280,28 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
38994
39280
  store.closeNode(nid, t);
38995
39281
  }
38996
39282
  });
38997
- const r = await runIndexer(store, {
39283
+ const seenNodes = /* @__PURE__ */ new Set();
39284
+ const seenEdges = /* @__PURE__ */ new Set();
39285
+ const writeStore = store.mergeNodeIfChanged && store.mergeEdgeIfChanged ? new Proxy(store, {
39286
+ get(target, prop) {
39287
+ if (prop === "mergeNode") {
39288
+ return (n) => {
39289
+ seenNodes.add(n.id);
39290
+ if (n.label === "File" || n.label === "Folder") target.mergeNode(n);
39291
+ else target.mergeNodeIfChanged(n);
39292
+ };
39293
+ }
39294
+ if (prop === "mergeEdge") {
39295
+ return (e) => {
39296
+ seenEdges.add(edgeId(e.from, e.type, e.to));
39297
+ target.mergeEdgeIfChanged(e);
39298
+ };
39299
+ }
39300
+ const v = Reflect.get(target, prop, target);
39301
+ return typeof v === "function" ? v.bind(target) : v;
39302
+ }
39303
+ }) : store;
39304
+ const r = await runIndexer(writeStore, {
38998
39305
  workspaceId: workspaceId2,
38999
39306
  rootPath,
39000
39307
  providers,
@@ -39012,14 +39319,14 @@ async function incrementalReindex(store, rootPath, workspaceId2, changedAbsPaths
39012
39319
  if (typeof rel !== "string" || !changedRelPaths.has(rel)) continue;
39013
39320
  if (versionedAndFile.has(n.label)) {
39014
39321
  ownedLive.push(n.id);
39015
- } else if (n.label === "Comment" && n.lastUpdatedAt < t) {
39322
+ } else if (n.label === "Comment" && n.lastUpdatedAt < t && !seenNodes.has(n.id)) {
39016
39323
  store.closeNode(n.id, t);
39017
39324
  }
39018
39325
  }
39019
39326
  for (const id of ownedLive) {
39020
39327
  for (const e of store.outEdges(id)) {
39021
39328
  if (e.type === "SUPERSEDED_BY") continue;
39022
- if (e.lastSeenAt < t) {
39329
+ if (e.lastSeenAt < t && !seenEdges.has(edgeId(e.from, e.type, e.to))) {
39023
39330
  store.closeEdge(e.id, t);
39024
39331
  delta.edgesRemoved++;
39025
39332
  } else if (e.createdAt >= t) {
@@ -80386,6 +80693,13 @@ init_session_registry();
80386
80693
  import { readFileSync as readFileSync18, renameSync as renameSync3, statSync as statSync8, writeFileSync as writeFileSync14 } from "node:fs";
80387
80694
  import { fileURLToPath as fileURLToPath2 } from "node:url";
80388
80695
  import { dirname as dirname11, join as join22 } from "node:path";
80696
+ function revisitFlagsUnchanged(store, held, flaggedNow) {
80697
+ if (held.anySeq === void 0 || held.revisitIds === void 0 || !store.changedAmong) return false;
80698
+ const watched = [.../* @__PURE__ */ new Set([...held.revisitIds, ...flaggedNow])];
80699
+ if (watched.length === 0) return true;
80700
+ const changed = store.changedAmong(held.anySeq, watched);
80701
+ return changed !== null && changed.length === 0;
80702
+ }
80389
80703
  function snapshotReusable(held, want) {
80390
80704
  return held !== null && held.key === want.key && held.atCommit === want.atCommit;
80391
80705
  }
@@ -83238,12 +83552,40 @@ function withHeavyPassSlot(label, fn) {
83238
83552
  return run3;
83239
83553
  }
83240
83554
 
83555
+ // src/warm-worker.ts
83556
+ var WARM_HEAP_CEILING_BYTES = 256 * 1024 * 1024;
83557
+ var WARM_IDLE_MS = 2 * 60 * 1e3;
83558
+ async function mayStayWarm(worker, ceiling = WARM_HEAP_CEILING_BYTES) {
83559
+ const stats = await worker.getHeapStatistics?.().catch(() => void 0);
83560
+ return stats !== void 0 && stats.total_heap_size <= ceiling;
83561
+ }
83562
+ var IdleRecycle = class {
83563
+ constructor(recycle, idleMs = WARM_IDLE_MS) {
83564
+ this.recycle = recycle;
83565
+ this.idleMs = idleMs;
83566
+ }
83567
+ timer = null;
83568
+ arm() {
83569
+ this.clear();
83570
+ this.timer = setTimeout(() => {
83571
+ this.timer = null;
83572
+ this.recycle();
83573
+ }, this.idleMs);
83574
+ this.timer.unref?.();
83575
+ }
83576
+ clear() {
83577
+ if (this.timer) clearTimeout(this.timer);
83578
+ this.timer = null;
83579
+ }
83580
+ };
83581
+
83241
83582
  // src/pass-worker-client.ts
83242
83583
  var PassWorker = class {
83243
83584
  constructor(init2) {
83244
83585
  this.init = init2;
83245
83586
  }
83246
83587
  worker = null;
83588
+ idle = new IdleRecycle(() => void this.recycleIdle());
83247
83589
  ready = null;
83248
83590
  pending = /* @__PURE__ */ new Map();
83249
83591
  nextId = 1;
@@ -83290,6 +83632,7 @@ var PassWorker = class {
83290
83632
  if (this.stopped) return Promise.reject(new Error("pass worker stopped"));
83291
83633
  return withHeavyPassSlot(`${kind}:${this.init.workspaceId}`, async () => {
83292
83634
  if (this.stopped) throw new Error("pass worker stopped");
83635
+ this.idle.clear();
83293
83636
  this.ensureWorker();
83294
83637
  const id = this.nextId++;
83295
83638
  try {
@@ -83299,7 +83642,8 @@ var PassWorker = class {
83299
83642
  this.worker.postMessage({ id, kind, ...payload !== void 0 ? { payload } : {} });
83300
83643
  });
83301
83644
  } finally {
83302
- await this.recycleIdle();
83645
+ if (this.worker && this.pending.size === 0 && !this.stopped && await mayStayWarm(this.worker)) this.idle.arm();
83646
+ else await this.recycleIdle();
83303
83647
  }
83304
83648
  });
83305
83649
  }
@@ -83363,6 +83707,7 @@ var PassWorker = class {
83363
83707
  }
83364
83708
  async stop() {
83365
83709
  this.stopped = true;
83710
+ this.idle.clear();
83366
83711
  const w = this.worker;
83367
83712
  this.worker = null;
83368
83713
  this.ready = null;
@@ -87049,7 +87394,7 @@ function createWatchBreaker(deps) {
87049
87394
  }
87050
87395
 
87051
87396
  // src/engine.ts
87052
- var DAEMON_VERSION = true ? "2.0.22-dev.4665" : "2.0.0-alpha.0";
87397
+ var DAEMON_VERSION = true ? "2.0.22-dev.4676" : "2.0.0-alpha.0";
87053
87398
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
87054
87399
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
87055
87400
  var GIT_OP_MUTE_MS = 4e3;
@@ -87475,6 +87820,7 @@ function createWorkspaceEngine(opts) {
87475
87820
  let ctxRefreshInFlight = false;
87476
87821
  let ctxRefreshQueued = false;
87477
87822
  let lastWorkerSnapshot = null;
87823
+ const SNAPSHOT_SCOPES = [...SNAPSHOT_NODE_LABELS.map(nodeScope), ...SNAPSHOT_EDGE_TYPES.map(edgeScope)];
87478
87824
  let lastSelfCiteSkips = 0;
87479
87825
  const refreshContextNow = () => {
87480
87826
  void refreshContextAsync().catch((err2) => {
@@ -87560,21 +87906,30 @@ function createWorkspaceEngine(opts) {
87560
87906
  skills: readSkillManifest(paths.skillsManifest),
87561
87907
  ...lastSelfCiteSkips > 0 ? { selfCiteSkips: lastSelfCiteSkips } : {}
87562
87908
  };
87563
- const changes = store.changeSeq ? store.changeSeq() - store.changeSeq([EXPOSURE_SCOPE]) : store.changeStamp();
87909
+ const changes = store.changeSeq ? store.changeSeq(SNAPSHOT_SCOPES) : store.changeStamp();
87910
+ const anySeq = store.changeSeq?.();
87564
87911
  const { id, name: name2, stack, languages, domains, goals } = snapOpts.profile;
87565
87912
  const { workingFile: _workingFile, ...keyedOpts } = snapOpts;
87566
87913
  const snapshotKey = `${snapshotCodeIdentity(DAEMON_VERSION)}|${changes}|${JSON.stringify({ ...keyedOpts, profile: { id, name: name2, stack, languages, domains, goals } })}`;
87567
87914
  lastWorkerSnapshot ??= loadHeldSnapshot(heldSnapshotPath(paths.configDir));
87568
87915
  const held = lastWorkerSnapshot;
87569
- const withWorkingFile = (snap, builtFor, want, atCommit) => {
87570
- if (builtFor === want) return snap;
87916
+ const withWorkingFile = (snap, want, atCommit) => {
87571
87917
  const slice = want ? sliceForFile(store, want, atCommit) : void 0;
87572
87918
  if (slice) snap.workingFile = slice;
87573
87919
  else delete snap.workingFile;
87574
87920
  return snap;
87575
87921
  };
87576
- const prebuilt = snapshotReusable(held, { key: snapshotKey, atCommit: snapOpts.atCommit }) ? withWorkingFile(structuredClone(held.snapshot), held.workingFile, snapOpts.workingFile, snapOpts.atCommit) : passWorker ? await passWorker.snapshot(snapOpts).then((snapshot2) => {
87577
- lastWorkerSnapshot = { key: snapshotKey, at: Date.now(), atCommit: snapOpts.atCommit, workingFile: snapOpts.workingFile, snapshot: structuredClone(snapshot2) };
87922
+ const revisitIds = store.nodesAsOfWhereTrue(Date.now(), "revisit").map((n) => n.id);
87923
+ const prebuilt = snapshotReusable(held, { key: snapshotKey, atCommit: snapOpts.atCommit }) && revisitFlagsUnchanged(store, held, revisitIds) ? withWorkingFile(structuredClone(held.snapshot), snapOpts.workingFile, snapOpts.atCommit) : passWorker ? await passWorker.snapshot(snapOpts).then((snapshot2) => {
87924
+ lastWorkerSnapshot = {
87925
+ key: snapshotKey,
87926
+ at: Date.now(),
87927
+ atCommit: snapOpts.atCommit,
87928
+ workingFile: snapOpts.workingFile,
87929
+ ...anySeq !== void 0 ? { anySeq } : {},
87930
+ revisitIds,
87931
+ snapshot: structuredClone(snapshot2)
87932
+ };
87578
87933
  saveHeldSnapshot(heldSnapshotPath(paths.configDir), lastWorkerSnapshot);
87579
87934
  return snapshot2;
87580
87935
  }).catch((err2) => {
@@ -91598,6 +91953,7 @@ init_session_registry();
91598
91953
  import { Worker as Worker2 } from "node:worker_threads";
91599
91954
  var ConsolidateWorker = class {
91600
91955
  worker = null;
91956
+ idle = new IdleRecycle(() => void this.recycleIdle());
91601
91957
  ready = null;
91602
91958
  pending = /* @__PURE__ */ new Map();
91603
91959
  nextId = 1;
@@ -91608,6 +91964,7 @@ var ConsolidateWorker = class {
91608
91964
  if (this.stopped) return Promise.reject(new Error("consolidate worker stopped"));
91609
91965
  return withHeavyPassSlot(`consolidate:${req.kind ?? "pass"}`, async () => {
91610
91966
  if (this.stopped) throw new Error("consolidate worker stopped");
91967
+ this.idle.clear();
91611
91968
  this.ensureWorker();
91612
91969
  const id = this.nextId++;
91613
91970
  try {
@@ -91617,7 +91974,8 @@ var ConsolidateWorker = class {
91617
91974
  this.worker.postMessage({ ...req, id });
91618
91975
  });
91619
91976
  } finally {
91620
- await this.recycleIdle();
91977
+ if (this.worker && this.pending.size === 0 && !this.stopped && await mayStayWarm(this.worker)) this.idle.arm();
91978
+ else await this.recycleIdle();
91621
91979
  }
91622
91980
  });
91623
91981
  }
@@ -91680,6 +92038,7 @@ var ConsolidateWorker = class {
91680
92038
  }
91681
92039
  async stop() {
91682
92040
  this.stopped = true;
92041
+ this.idle.clear();
91683
92042
  if (this.worker) {
91684
92043
  await this.worker.terminate().catch(() => {
91685
92044
  });
@@ -93526,7 +93885,7 @@ var GitBlobReader = class {
93526
93885
  init_src11();
93527
93886
  init_src();
93528
93887
  import { extname as extname4 } from "node:path";
93529
- var LABELS = {
93888
+ var LABELS2 = {
93530
93889
  function: "Function",
93531
93890
  class: "Class",
93532
93891
  method: "Method",
@@ -93560,7 +93919,7 @@ async function historicalAnchors(input) {
93560
93919
  const source = await input.readBlob(spec);
93561
93920
  if (source === null || source.includes("\0") || Buffer.byteLength(source) > MAX_SOURCE_BYTES) return null;
93562
93921
  await provider.preload?.();
93563
- const symbols = provider.extractSymbols(source, path3).filter((s) => LABELS[s.kind] && s.startByte >= 0 && s.endByte > s.startByte);
93922
+ const symbols = provider.extractSymbols(source, path3).filter((s) => LABELS2[s.kind] && s.startByte >= 0 && s.endByte > s.startByte);
93564
93923
  const offsets = [0];
93565
93924
  for (let i2 = 0; i2 < source.length; i2++) if (source[i2] === "\n") offsets.push(i2 + 1);
93566
93925
  offsets.push(source.length + 1);
@@ -93595,7 +93954,7 @@ async function historicalAnchors(input) {
93595
93954
  const symbols = file2.symbols.filter((s) => s.startByte < stop2 && s.endByte > start2).sort((a, b) => a.endByte - a.startByte - (b.endByte - b.startByte));
93596
93955
  const symbol2 = symbols[0];
93597
93956
  if (symbol2) {
93598
- const kind = LABELS[symbol2.kind];
93957
+ const kind = LABELS2[symbol2.kind];
93599
93958
  const symbolId = projectSymbolId(input.salt, input.projectId, input.bundle.pathTokens[token], symbol2.qname ?? symbol2.name, kind);
93600
93959
  const prev = list.at(-1);
93601
93960
  if (prev?.symbolId === symbolId && prev.historical?.side === side && prev.historical.blockDigest === block.digest && prev.historical.endByte + 1 === offset) {