@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/pass-worker.mjs CHANGED
@@ -17450,10 +17450,20 @@ function isReExportBarrel(relPath) {
17450
17450
  function graphExportMap(store2, workspaceId, relPath) {
17451
17451
  const out2 = /* @__PURE__ */ new Map();
17452
17452
  const fileId = fileNodeId(workspaceId, relPath);
17453
- if (!store2.getNode(fileId)) return out2;
17454
- for (const e of store2.outEdges(fileId, ["DEFINES"])) {
17455
- const n = store2.getNode(e.to);
17456
- if (!n || !EXPORTABLE_LABELS.has(n.label)) continue;
17453
+ if (store2.nodeLabels([fileId]).size === 0) return out2;
17454
+ const defines = store2.outEdges(fileId, ["DEFINES"]);
17455
+ const rows = /* @__PURE__ */ new Map();
17456
+ for (let i2 = 0; i2 < defines.length; i2 += 500) {
17457
+ for (const r of store2.selectNodes({
17458
+ labels: [...EXPORTABLE_LABELS],
17459
+ attrs: ["workspaceId", "name"],
17460
+ where: [{ idIn: defines.slice(i2, i2 + 500).map((e) => e.to) }],
17461
+ includeClosed: true
17462
+ })) rows.set(r.id, r);
17463
+ }
17464
+ for (const e of defines) {
17465
+ const n = rows.get(e.to);
17466
+ if (!n) continue;
17457
17467
  if (n.attrs["workspaceId"] !== workspaceId) continue;
17458
17468
  const name2 = n.attrs["name"] ?? n.description;
17459
17469
  if (!name2 || name2.startsWith("<") || name2.includes(":")) continue;
@@ -17629,7 +17639,28 @@ async function incrementalReindex(store2, rootPath, workspaceId, changedAbsPaths
17629
17639
  store2.closeNode(nid, t);
17630
17640
  }
17631
17641
  });
17632
- const r = await runIndexer(store2, {
17642
+ const seenNodes = /* @__PURE__ */ new Set();
17643
+ const seenEdges = /* @__PURE__ */ new Set();
17644
+ const writeStore = store2.mergeNodeIfChanged && store2.mergeEdgeIfChanged ? new Proxy(store2, {
17645
+ get(target, prop) {
17646
+ if (prop === "mergeNode") {
17647
+ return (n) => {
17648
+ seenNodes.add(n.id);
17649
+ if (n.label === "File" || n.label === "Folder") target.mergeNode(n);
17650
+ else target.mergeNodeIfChanged(n);
17651
+ };
17652
+ }
17653
+ if (prop === "mergeEdge") {
17654
+ return (e) => {
17655
+ seenEdges.add(edgeId(e.from, e.type, e.to));
17656
+ target.mergeEdgeIfChanged(e);
17657
+ };
17658
+ }
17659
+ const v = Reflect.get(target, prop, target);
17660
+ return typeof v === "function" ? v.bind(target) : v;
17661
+ }
17662
+ }) : store2;
17663
+ const r = await runIndexer(writeStore, {
17633
17664
  workspaceId,
17634
17665
  rootPath,
17635
17666
  providers,
@@ -17647,14 +17678,14 @@ async function incrementalReindex(store2, rootPath, workspaceId, changedAbsPaths
17647
17678
  if (typeof rel !== "string" || !changedRelPaths.has(rel)) continue;
17648
17679
  if (versionedAndFile.has(n.label)) {
17649
17680
  ownedLive.push(n.id);
17650
- } else if (n.label === "Comment" && n.lastUpdatedAt < t) {
17681
+ } else if (n.label === "Comment" && n.lastUpdatedAt < t && !seenNodes.has(n.id)) {
17651
17682
  store2.closeNode(n.id, t);
17652
17683
  }
17653
17684
  }
17654
17685
  for (const id of ownedLive) {
17655
17686
  for (const e of store2.outEdges(id)) {
17656
17687
  if (e.type === "SUPERSEDED_BY") continue;
17657
- if (e.lastSeenAt < t) {
17688
+ if (e.lastSeenAt < t && !seenEdges.has(edgeId(e.from, e.type, e.to))) {
17658
17689
  store2.closeEdge(e.id, t);
17659
17690
  delta.edgesRemoved++;
17660
17691
  } else if (e.createdAt >= t) {
@@ -25842,6 +25873,110 @@ import { parentPort, workerData } from "node:worker_threads";
25842
25873
  init_src2();
25843
25874
  init_src();
25844
25875
 
25876
+ // ../../packages/local-graph/src/wm-calibration.ts
25877
+ var WM_CALIBRATION_LABELS = [
25878
+ "Problem",
25879
+ "Solution",
25880
+ "RootCause",
25881
+ "Pattern",
25882
+ "Technique",
25883
+ "AntiPattern"
25884
+ ];
25885
+ var WM_CALIBRATION_SCHEMA = 2;
25886
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
25887
+ var WM_CALIBRATION_KEYS = [
25888
+ "shownCount",
25889
+ "evictedCount",
25890
+ "reinforceShownCount",
25891
+ "reinforceRecentCount",
25892
+ "reinforceEvictedCount",
25893
+ "reinforceUnshownCount",
25894
+ "reinforceUnshownQueryOnlyCount"
25895
+ ];
25896
+ var WM_COUNTED_PREDICATE = WM_CALIBRATION_KEYS.map((k) => `json_type(attrs_json, '$.${k}') IS NOT NULL`).join(" OR ");
25897
+ var wmCounterSql = (k) => `CASE WHEN json_type(attrs_json, '$.${k}') IN ('integer', 'real') THEN json_extract(attrs_json, '$.${k}') END`;
25898
+ function wmCalibrationCells(store2) {
25899
+ const cells = {
25900
+ calibrationSchema: WM_CALIBRATION_SCHEMA,
25901
+ shownNodes: 0,
25902
+ shownImpressions: 0,
25903
+ evictedNodes: 0,
25904
+ evictedImpressions: 0,
25905
+ reinforceShown: 0,
25906
+ reinforceRecent: 0,
25907
+ reinforceEvicted: 0,
25908
+ reinforceUnshown: 0,
25909
+ reinforceUnshownQueryOnly: 0,
25910
+ reinforcedNodes: 0,
25911
+ reinforcedShownNodes: 0
25912
+ };
25913
+ const newest = /* @__PURE__ */ new Map();
25914
+ const rows = store2.wmCalibrationRows?.(WM_CALIBRATION_LABELS) ?? (function* () {
25915
+ for (const label of WM_CALIBRATION_LABELS) {
25916
+ yield* store2.selectNodes({ labels: [label], attrs: WM_CALIBRATION_KEYS, includeClosed: true });
25917
+ }
25918
+ })();
25919
+ for (const n of rows) {
25920
+ const live = n.validTo === null;
25921
+ const prev = newest.get(n.logicalId);
25922
+ if (prev && (prev.live || !live && prev.version >= n.version)) continue;
25923
+ newest.set(n.logicalId, { version: n.version, live, attrs: n.attrs });
25924
+ }
25925
+ for (const { attrs } of newest.values()) {
25926
+ const shown = num(attrs["shownCount"]);
25927
+ const evicted = num(attrs["evictedCount"]);
25928
+ if (shown > 0) {
25929
+ cells.shownNodes++;
25930
+ cells.shownImpressions += shown;
25931
+ }
25932
+ if (evicted > 0) {
25933
+ cells.evictedNodes++;
25934
+ cells.evictedImpressions += evicted;
25935
+ }
25936
+ const rs = num(attrs["reinforceShownCount"]);
25937
+ const rr = num(attrs["reinforceRecentCount"]);
25938
+ const re = num(attrs["reinforceEvictedCount"]);
25939
+ const ru = num(attrs["reinforceUnshownCount"]);
25940
+ cells.reinforceShown += rs;
25941
+ cells.reinforceRecent += rr;
25942
+ cells.reinforceEvicted += re;
25943
+ cells.reinforceUnshown += ru;
25944
+ cells.reinforceUnshownQueryOnly += num(attrs["reinforceUnshownQueryOnlyCount"]);
25945
+ if (rs + rr + re + ru > 0) cells.reinforcedNodes++;
25946
+ if (rs > 0) cells.reinforcedShownNodes++;
25947
+ }
25948
+ return cells;
25949
+ }
25950
+
25951
+ // ../../packages/local-graph/src/exposure-attrs.ts
25952
+ var EXPOSURE_ATTRS = [
25953
+ "shownCount",
25954
+ "evictedCount",
25955
+ "firstShownAt",
25956
+ "firstEvictedAt",
25957
+ "firstShownAtSeq",
25958
+ "lastShownAtSeq",
25959
+ "lastEvictedAtSeq",
25960
+ "shownSinceTouch",
25961
+ "shownSinceTouchAt",
25962
+ "exposureLog",
25963
+ "exposureLogStart",
25964
+ "exposureLogSince",
25965
+ "exposureFirst",
25966
+ "exposureTrimmed"
25967
+ ];
25968
+ var EXPOSURE_SCOPE = "exposure";
25969
+ var EXPOSURE_KEYS = new Set(EXPOSURE_ATTRS);
25970
+ var isExposureAttr = (key) => EXPOSURE_KEYS.has(key);
25971
+ function pickExposure(attrs) {
25972
+ const out2 = {};
25973
+ for (const k of EXPOSURE_ATTRS) if (attrs[k] !== void 0) out2[k] = attrs[k];
25974
+ return out2;
25975
+ }
25976
+ function exposureDocSql(key) {
25977
+ 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)`;
25978
+ }
25979
+
25845
25980
  // ../../packages/local-graph/src/history-cache-floor.ts
25846
25981
  var HISTORY_CACHE_FLOOR_KEY = "history-cache-withdrawal-floor-3";
25847
25982
  function installHistoryCacheFloor(db) {
@@ -26126,25 +26261,6 @@ function textIndexReady(db) {
26126
26261
  return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
26127
26262
  }
26128
26263
 
26129
- // ../../packages/local-graph/src/exposure-attrs.ts
26130
- var EXPOSURE_ATTRS = [
26131
- "shownCount",
26132
- "evictedCount",
26133
- "firstShownAt",
26134
- "firstEvictedAt",
26135
- "firstShownAtSeq",
26136
- "lastShownAtSeq",
26137
- "lastEvictedAtSeq",
26138
- "shownSinceTouch",
26139
- "shownSinceTouchAt",
26140
- "exposureLog",
26141
- "exposureLogStart",
26142
- "exposureLogSince",
26143
- "exposureFirst",
26144
- "exposureTrimmed"
26145
- ];
26146
- var EXPOSURE_SCOPE = "exposure";
26147
-
26148
26264
  // ../../packages/local-graph/src/change-feed.ts
26149
26265
  var CHANGE_FEED_KEY = "change-feed";
26150
26266
  var ANY_CHANGE = "*";
@@ -26208,6 +26324,16 @@ function readChangedNodeIds(db, sinceSeq, labels) {
26208
26324
  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);
26209
26325
  return rows.map((r) => r.id);
26210
26326
  }
26327
+ function readChangedAmong(db, sinceSeq, ids) {
26328
+ if (!changeLogCovers(db, sinceSeq)) return null;
26329
+ const out2 = [];
26330
+ for (let i2 = 0; i2 < ids.length; i2 += 500) {
26331
+ const chunk = ids.slice(i2, i2 + 500);
26332
+ 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);
26333
+ for (const r of rows) out2.push(r.id);
26334
+ }
26335
+ return out2;
26336
+ }
26211
26337
  function readLineageChangedFiles(db, sinceSeq) {
26212
26338
  if (!changeLogCovers(db, sinceSeq)) return null;
26213
26339
  const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
@@ -26941,8 +27067,11 @@ function compileNodeWhere(q) {
26941
27067
  }
26942
27068
  return { where: conds.join(" AND "), params };
26943
27069
  }
26944
- function attrValueSql(p) {
26945
- 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`;
27070
+ function attrValueSql(p, doc = "attrs_json") {
27071
+ return `CASE json_type(${doc}, ${p}) WHEN 'integer' THEN CAST(json_extract(${doc}, ${p}) AS REAL) ELSE json_extract(${doc}, ${p}) END`;
27072
+ }
27073
+ function attrDocSql(key) {
27074
+ return isExposureAttr(key) ? exposureDocSql(key) : "attrs_json";
26946
27075
  }
26947
27076
  function nodeSelectFrom(q) {
26948
27077
  if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
@@ -26955,7 +27084,7 @@ function nodeSelectFrom(q) {
26955
27084
  function compileNodeSelect(q) {
26956
27085
  const { where, params } = compileNodeWhere(q);
26957
27086
  const attrs = q.attrs ?? [];
26958
- const attrCols = attrs.map((k, i2) => `, ${attrValueSql(path(k))} AS a${i2}, json_type(attrs_json, ${path(k)}) AS t${i2}`).join("");
27087
+ const attrCols = attrs.map((k, i2) => `, ${attrValueSql(path(k), attrDocSql(k))} AS a${i2}, json_type(${attrDocSql(k)}, ${path(k)}) AS t${i2}`).join("");
26959
27088
  let order;
26960
27089
  if (q.orderBy && "column" in q.orderBy) {
26961
27090
  order = `${column(q.orderBy.column)} ${q.orderBy.desc ? "DESC" : "ASC"}, rowid`;
@@ -27072,6 +27201,7 @@ function localEdgeViolation(fromLabel, type, toLabel) {
27072
27201
  }
27073
27202
  var SCHEMA_VERSION = 6;
27074
27203
  var SESSION_REPAIR_KEY = "session-evidence-v1";
27204
+ var EXPOSURE_SIDE_KEY = "exposure-side-rows-1";
27075
27205
  var SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
27076
27206
  var SCHEMA_SQL = `
27077
27207
  CREATE TABLE IF NOT EXISTS schema_version (
@@ -27135,8 +27265,13 @@ CREATE TABLE IF NOT EXISTS nodes (
27135
27265
  );
27136
27266
 
27137
27267
  CREATE INDEX IF NOT EXISTS nodes_label ON nodes(label);
27138
- CREATE INDEX IF NOT EXISTS nodes_memory_tier ON nodes(memory_tier);
27139
- CREATE INDEX IF NOT EXISTS nodes_page_rank ON nodes(page_rank DESC);
27268
+ -- Dropped: no query plans through them (every prepared statement across the
27269
+ -- local-graph, indexer, context-writer, generalizer and daemon suites was
27270
+ -- planned with and without them, 2026-10-21), and each write that touches
27271
+ -- memory_tier / page_rank (every node upsert, every rescore) paid to maintain
27272
+ -- them. nodes_live_page_rank serves the live page-rank orderings.
27273
+ DROP INDEX IF EXISTS nodes_memory_tier;
27274
+ DROP INDEX IF EXISTS nodes_page_rank;
27140
27275
 
27141
27276
  CREATE TABLE IF NOT EXISTS edges (
27142
27277
  id TEXT PRIMARY KEY,
@@ -27158,7 +27293,10 @@ CREATE TABLE IF NOT EXISTS edges (
27158
27293
  FOREIGN KEY (to_id) REFERENCES nodes(id) ON DELETE CASCADE
27159
27294
  );
27160
27295
 
27161
- CREATE INDEX IF NOT EXISTS edges_from ON edges(from_id, type);
27296
+ -- Dropped: a prefix of edges_triple (from_id, type, to_id), which every
27297
+ -- from-side lookup and foreign-key check already plans through; it cost 47 MB
27298
+ -- and an index write on every edge write.
27299
+ DROP INDEX IF EXISTS edges_from;
27162
27300
  CREATE INDEX IF NOT EXISTS edges_to ON edges(to_id, type);
27163
27301
  CREATE INDEX IF NOT EXISTS edges_type ON edges(type);
27164
27302
  CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
@@ -27180,6 +27318,12 @@ CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to,
27180
27318
  -- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
27181
27319
  -- Problems; without it every live Problem was read to find them (56 MB).
27182
27320
  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;
27321
+ -- Render impressions, one row per node id (exposure-attrs.ts): a render bumps
27322
+ -- ~40 of these, which in the node row rewrote each whole row and its indexes.
27323
+ CREATE TABLE IF NOT EXISTS node_exposure (
27324
+ id TEXT PRIMARY KEY,
27325
+ attrs_json TEXT NOT NULL DEFAULT '{}'
27326
+ ) WITHOUT ROWID;
27183
27327
  -- The semantic-maintenance sweeps' own filters (closed, asked, hinted): each read
27184
27328
  -- every Problem row (~30 MB on a 1.1 GB store) for the few hundred that match,
27185
27329
  -- several times a run, and a run follows most writes (2026-10-20).
@@ -27187,6 +27331,9 @@ CREATE INDEX IF NOT EXISTS nodes_live_resolved ON nodes(label, valid_to) WHERE v
27187
27331
  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;
27188
27332
  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;
27189
27333
  CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
27334
+ -- Rows carrying a calibration counter, any version: the logical nodes wm
27335
+ -- calibration can count (~3k), found without reading every citable row.
27336
+ 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};
27190
27337
  CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
27191
27338
  -- Covering + partial index for labelFingerprint over the instance labels: the
27192
27339
  -- live-push probe runs it per workspace on a timer, and each hit used to read
@@ -27340,6 +27487,7 @@ var SqliteGraphStore = class {
27340
27487
  constructor(opts) {
27341
27488
  this.db = openDatabase(opts.path);
27342
27489
  this.db.exec("PRAGMA cache_size = -32768");
27490
+ this.db.exec("PRAGMA mmap_size = 2147483648");
27343
27491
  this.db.exec(SCHEMA_SQL);
27344
27492
  const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
27345
27493
  if (!versionRow) {
@@ -27403,8 +27551,17 @@ var SqliteGraphStore = class {
27403
27551
  last_reinforced_at_seq = excluded.last_reinforced_at_seq,
27404
27552
  attrs_json = excluded.attrs_json
27405
27553
  `),
27406
- getNode: this.db.prepare("SELECT * FROM nodes WHERE id = ?"),
27554
+ 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 = ?"),
27555
+ exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
27556
+ writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
27557
+ lastUpdatedOf: this.db.prepare("SELECT last_updated_at AS t, attrs_json FROM nodes WHERE id = ?"),
27407
27558
  labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
27559
+ nodeContent: this.db.prepare(
27560
+ "SELECT label, description, extraction_confidence, extraction_source, stability, valid_to, attrs_json FROM nodes WHERE id = ?"
27561
+ ),
27562
+ edgeContent: this.db.prepare(
27563
+ "SELECT confidence, valid_to, attrs_json FROM edges WHERE from_id = ? AND type = ? AND to_id = ?"
27564
+ ),
27408
27565
  // Single-column fast paths for the nightly pass — writing 100k pageRank
27409
27566
  // values through the 18-column COALESCE updateNode took ~30s; these are ~10x
27410
27567
  // cheaper (one bound column, no COALESCE evaluation per row).
@@ -27561,6 +27718,7 @@ var SqliteGraphStore = class {
27561
27718
  "INSERT INTO store_meta (key, value) VALUES ('ingest_seq', :v) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
27562
27719
  )
27563
27720
  };
27721
+ this.moveExposureToSideRows();
27564
27722
  const seqRow = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
27565
27723
  this.ingestSeq = seqRow ? Number(seqRow.value) : 0;
27566
27724
  }
@@ -27616,6 +27774,41 @@ var SqliteGraphStore = class {
27616
27774
  ORDER BY label, created_at, rowid`
27617
27775
  ).all(label);
27618
27776
  }
27777
+ wmCalibrationRows(labels) {
27778
+ const counted = this.db.prepare(
27779
+ `SELECT rowid AS rid, logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map((k, i2) => `${wmCounterSql(k)} AS c${i2}`).join(", ")}
27780
+ FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`
27781
+ ).all();
27782
+ const exposed = this.db.prepare(
27783
+ `SELECT n.rowid AS rid, n.logical_id, n.version, n.valid_to, n.label, x.attrs_json AS exposure
27784
+ FROM node_exposure x JOIN nodes n ON n.id = x.id`
27785
+ ).all();
27786
+ const others = this.db.prepare(
27787
+ `SELECT rowid AS rid, logical_id, version, valid_to, label FROM nodes WHERE rowid IN (
27788
+ SELECT n.rowid FROM nodes AS n INDEXED BY nodes_logical
27789
+ WHERE n.logical_id IN (SELECT logical_id FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
27790
+ UNION SELECT n2.logical_id FROM node_exposure x2 JOIN nodes n2 ON n2.id = x2.id)
27791
+ EXCEPT SELECT rowid FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
27792
+ EXCEPT SELECT n3.rowid FROM node_exposure x3 JOIN nodes n3 ON n3.id = x3.id)`
27793
+ ).all();
27794
+ const byRid = /* @__PURE__ */ new Map();
27795
+ for (const r of [...counted, ...others]) {
27796
+ const attrs = {};
27797
+ WM_CALIBRATION_KEYS.forEach((k, i2) => {
27798
+ const v = r[`c${i2}`];
27799
+ if (typeof v === "number") attrs[k] = v;
27800
+ });
27801
+ byRid.set(r.rid, { row: r, attrs });
27802
+ }
27803
+ for (const r of exposed) {
27804
+ const side = JSON.parse(r.exposure);
27805
+ const entry = byRid.get(r.rid) ?? { row: r, attrs: {} };
27806
+ for (const k of WM_CALIBRATION_KEYS) if (typeof side[k] === "number" && Number.isFinite(side[k])) entry.attrs[k] = side[k];
27807
+ byRid.set(r.rid, entry);
27808
+ }
27809
+ const order = new Map(labels.map((l, i2) => [l, i2]));
27810
+ 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 }));
27811
+ }
27619
27812
  liveCommentsAttachedTo(name2) {
27620
27813
  return this.db.prepare(
27621
27814
  `SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
@@ -27752,9 +27945,22 @@ var SqliteGraphStore = class {
27752
27945
  logical_id: node.logicalId ?? node.id,
27753
27946
  created_at_seq: node.createdAtSeq ?? null,
27754
27947
  last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
27755
- attrs_json: JSON.stringify(node.attrs ?? {})
27948
+ attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
27756
27949
  });
27757
27950
  }
27951
+ /** mergeNode, unless the live row already holds this content. Content is what
27952
+ * the columns a merge rewrites from the producer (label, description, attrs,
27953
+ * confidence, stability);
27954
+ * rank, community, landmark, surprise, hits, tier and the vector belong to
27955
+ * the passes that compute them, and a skipped write leaves them as they are
27956
+ * where a merge would have reset them to the producer's defaults. Returns
27957
+ * whether it wrote. */
27958
+ mergeNodeIfChanged(node) {
27959
+ const row = this.stmts.nodeContent.get(node.id);
27960
+ if (row && row.valid_to === null && row.label === node.label && row.description === node.description && row.extraction_confidence === node.extractionConfidence && row.stability === node.stability && !EXPOSURE_ATTRS.some((k) => node.attrs?.[k] !== void 0) && row.attrs_json === JSON.stringify(node.attrs ?? {})) return false;
27961
+ this.mergeNode(node);
27962
+ return true;
27963
+ }
27758
27964
  updateNode(id, patch) {
27759
27965
  const hasEmbedding = patch.embedding !== void 0 && patch.embedding.length > 0;
27760
27966
  this.stmts.updateNode.run({
@@ -27778,7 +27984,37 @@ var SqliteGraphStore = class {
27778
27984
  logical_id: patch.logicalId ?? null,
27779
27985
  created_at_seq: patch.createdAtSeq ?? null,
27780
27986
  last_reinforced_at_seq: patch.lastReinforcedAtSeq ?? null,
27781
- attrs_json: patch.attrs ? JSON.stringify(patch.attrs) : null
27987
+ attrs_json: patch.attrs ? this.nodeAttrsJson(id, patch.attrs) : null
27988
+ });
27989
+ }
27990
+ /**
27991
+ * The node-row attrs of a write, after handing any exposure keys to the side
27992
+ * row (exposure-attrs.ts). `getNode` returns exposure merged into attrs, so a
27993
+ * read-modify-write carries it; kept in the row it would re-bloat the row and
27994
+ * drift from the side row. Unchanged exposure costs no write.
27995
+ */
27996
+ nodeAttrsJson(id, attrs) {
27997
+ if (!EXPOSURE_ATTRS.some((k) => attrs[k] !== void 0)) return JSON.stringify(attrs);
27998
+ const current = this.stmts.exposureOf.get(id);
27999
+ const next = JSON.stringify({ ...current ? JSON.parse(current.attrs_json) : {}, ...pickExposure(attrs) });
28000
+ if (current?.attrs_json !== next) this.stmts.writeExposure.run(id, next);
28001
+ const rest = { ...attrs };
28002
+ for (const k of EXPOSURE_ATTRS) delete rest[k];
28003
+ return JSON.stringify(rest);
28004
+ }
28005
+ /** One-time: exposure keys written to node rows before the side table existed
28006
+ * move to it. Only rows carrying a counter can carry exposure, and those are
28007
+ * what nodes_wm_counted holds, so this reads the index, not the table. */
28008
+ moveExposureToSideRows() {
28009
+ if (this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(EXPOSURE_SIDE_KEY)) return;
28010
+ const rows = this.db.prepare(`SELECT id, attrs_json FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`).all();
28011
+ const move = this.db.prepare("UPDATE nodes SET attrs_json = ? WHERE id = ?");
28012
+ this.db.transaction(() => {
28013
+ for (const r of rows) {
28014
+ const stripped = this.nodeAttrsJson(r.id, JSON.parse(r.attrs_json));
28015
+ if (stripped !== r.attrs_json) move.run(stripped, r.id);
28016
+ }
28017
+ this.db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, 'done')").run(EXPOSURE_SIDE_KEY);
27782
28018
  });
27783
28019
  }
27784
28020
  /** Fast single-column writes for the nightly pass (avoid the 18-column COALESCE
@@ -27860,6 +28096,21 @@ var SqliteGraphStore = class {
27860
28096
  attrs_json: JSON.stringify(edge.attrs ?? {})
27861
28097
  });
27862
28098
  }
28099
+ /** mergeEdge, unless the live edge already holds this content: same attrs, and
28100
+ * a confidence the upsert's MAX would keep. Its navigation posteriors and
28101
+ * last-seen stamp stay as they are where a merge would have reset them. The
28102
+ * ontology gate runs first either way, so refusals are recorded as before.
28103
+ * Returns whether it wrote (or refused). */
28104
+ mergeEdgeIfChanged(edge) {
28105
+ if (!this.edgeRuleViolation(edge)) {
28106
+ const row = this.stmts.edgeContent.get(edge.from, edge.type, edge.to);
28107
+ if (row && row.valid_to === null && row.confidence >= edge.confidence && row.attrs_json === JSON.stringify(edge.attrs ?? {})) {
28108
+ return false;
28109
+ }
28110
+ }
28111
+ this.mergeEdge(edge);
28112
+ return true;
28113
+ }
27863
28114
  /** EDGE_RULES check against the stored endpoints' labels, with the LOCAL
27864
28115
  * overlay consulted first. Returns the reason string on a documented-
27865
28116
  * forbidden combination, else null. Point lookups on the id PK — negligible
@@ -27926,7 +28177,27 @@ var SqliteGraphStore = class {
27926
28177
  }
27927
28178
  getNode(id) {
27928
28179
  const row = this.stmts.getNode.get(id);
27929
- return row ? rowToNode(row) : null;
28180
+ if (!row) return null;
28181
+ const node = rowToNode(row);
28182
+ if (row.exposure_json) Object.assign(node.attrs, JSON.parse(row.exposure_json));
28183
+ return node;
28184
+ }
28185
+ /** A node's render exposure: the side row's keys over the node row's own
28186
+ * (exposure-attrs.ts), plus the node's `lastUpdatedAt` (the "shown since
28187
+ * touch" counter is relative to it) — without reading the node's attrs. */
28188
+ exposureOf(id) {
28189
+ const row = this.stmts.lastUpdatedOf.get(id);
28190
+ if (!row) return null;
28191
+ const side = this.stmts.exposureOf.get(id);
28192
+ return {
28193
+ attrs: { ...pickExposure(JSON.parse(row.attrs_json)), ...side ? JSON.parse(side.attrs_json) : {} },
28194
+ lastUpdatedAt: row.t
28195
+ };
28196
+ }
28197
+ /** Replace a node's exposure (only EXPOSURE_ATTRS keys belong here). Touches
28198
+ * neither the node row nor its indexes, nor the change feed. */
28199
+ writeExposure(id, attrs) {
28200
+ this.stmts.writeExposure.run(id, JSON.stringify(attrs));
27930
28201
  }
27931
28202
  nodeLabels(ids) {
27932
28203
  const width = NODE_LABELS_CHUNK;
@@ -27977,7 +28248,7 @@ var SqliteGraphStore = class {
27977
28248
  findNodeStampsByLabel(label, attrKey) {
27978
28249
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attrKey)) throw new Error(`findNodeStampsByLabel: bad attr key ${attrKey}`);
27979
28250
  const rows = this.db.prepare(
27980
- `SELECT id, created_at, created_at_seq, json_type(attrs_json, '$.${attrKey}') IS NOT NULL AS has,
28251
+ `SELECT id, created_at, created_at_seq, json_type(${attrDocSql(attrKey)}, '$.${attrKey}') IS NOT NULL AS has,
27981
28252
  COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
27982
28253
  FROM nodes WHERE label = ? AND valid_to IS NULL`
27983
28254
  ).all(label);
@@ -27994,7 +28265,7 @@ var SqliteGraphStore = class {
27994
28265
  const keys = [...new Set(attrKeys)];
27995
28266
  const rows = this.db.prepare(
27996
28267
  `SELECT id, created_at, created_at_seq,
27997
- ${keys.map((k, i2) => `json_type(attrs_json, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
28268
+ ${keys.map((k, i2) => `json_type(${attrDocSql(k)}, '$.${k}') IS NOT NULL AS h${i2}`).join(", ")}${keys.length ? "," : ""}
27998
28269
  COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
27999
28270
  FROM nodes WHERE label = ? AND valid_to IS NULL`
28000
28271
  ).all(label);
@@ -28254,6 +28525,9 @@ var SqliteGraphStore = class {
28254
28525
  changedNodeIds(sinceSeq, labels) {
28255
28526
  return readChangedNodeIds(this.db, sinceSeq, labels);
28256
28527
  }
28528
+ changedAmong(sinceSeq, ids) {
28529
+ return readChangedAmong(this.db, sinceSeq, ids);
28530
+ }
28257
28531
  pruneChangeLog() {
28258
28532
  return pruneChangeLog(this.db);
28259
28533
  }
@@ -30415,76 +30689,6 @@ function minFractionOf(d) {
30415
30689
  return d.fedIncoming?.minFraction ?? d.fedEdges?.minFraction ?? d.fed?.minFraction ?? 0;
30416
30690
  }
30417
30691
 
30418
- // ../../packages/local-graph/src/wm-calibration.ts
30419
- var WM_CALIBRATION_LABELS = [
30420
- "Problem",
30421
- "Solution",
30422
- "RootCause",
30423
- "Pattern",
30424
- "Technique",
30425
- "AntiPattern"
30426
- ];
30427
- var WM_CALIBRATION_SCHEMA = 2;
30428
- var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
30429
- var WM_CALIBRATION_KEYS = [
30430
- "shownCount",
30431
- "evictedCount",
30432
- "reinforceShownCount",
30433
- "reinforceRecentCount",
30434
- "reinforceEvictedCount",
30435
- "reinforceUnshownCount",
30436
- "reinforceUnshownQueryOnlyCount"
30437
- ];
30438
- function wmCalibrationCells(store2) {
30439
- const cells = {
30440
- calibrationSchema: WM_CALIBRATION_SCHEMA,
30441
- shownNodes: 0,
30442
- shownImpressions: 0,
30443
- evictedNodes: 0,
30444
- evictedImpressions: 0,
30445
- reinforceShown: 0,
30446
- reinforceRecent: 0,
30447
- reinforceEvicted: 0,
30448
- reinforceUnshown: 0,
30449
- reinforceUnshownQueryOnly: 0,
30450
- reinforcedNodes: 0,
30451
- reinforcedShownNodes: 0
30452
- };
30453
- const newest = /* @__PURE__ */ new Map();
30454
- for (const label of WM_CALIBRATION_LABELS) {
30455
- for (const n of store2.selectNodes({ labels: [label], attrs: WM_CALIBRATION_KEYS, includeClosed: true })) {
30456
- const live = n.validTo === null;
30457
- const prev = newest.get(n.logicalId);
30458
- if (prev && (prev.live || !live && prev.version >= n.version)) continue;
30459
- newest.set(n.logicalId, { version: n.version, live, attrs: n.attrs });
30460
- }
30461
- }
30462
- for (const { attrs } of newest.values()) {
30463
- const shown = num(attrs["shownCount"]);
30464
- const evicted = num(attrs["evictedCount"]);
30465
- if (shown > 0) {
30466
- cells.shownNodes++;
30467
- cells.shownImpressions += shown;
30468
- }
30469
- if (evicted > 0) {
30470
- cells.evictedNodes++;
30471
- cells.evictedImpressions += evicted;
30472
- }
30473
- const rs = num(attrs["reinforceShownCount"]);
30474
- const rr = num(attrs["reinforceRecentCount"]);
30475
- const re = num(attrs["reinforceEvictedCount"]);
30476
- const ru = num(attrs["reinforceUnshownCount"]);
30477
- cells.reinforceShown += rs;
30478
- cells.reinforceRecent += rr;
30479
- cells.reinforceEvicted += re;
30480
- cells.reinforceUnshown += ru;
30481
- cells.reinforceUnshownQueryOnly += num(attrs["reinforceUnshownQueryOnlyCount"]);
30482
- if (rs + rr + re + ru > 0) cells.reinforcedNodes++;
30483
- if (rs > 0) cells.reinforcedShownNodes++;
30484
- }
30485
- return cells;
30486
- }
30487
-
30488
30692
  // ../../packages/generalizer/src/generalizer.ts
30489
30693
  init_src2();
30490
30694
  init_src();
@@ -31013,6 +31217,91 @@ var AGENTS_POINTER_BODY = [
31013
31217
  // ../../packages/context-writer/src/render.ts
31014
31218
  init_src2();
31015
31219
  init_src();
31220
+
31221
+ // ../../packages/context-writer/src/snapshot-inputs.ts
31222
+ var SNAPSHOT_NODE_LABELS = [
31223
+ "Problem",
31224
+ "Vulnerability",
31225
+ "Solution",
31226
+ "Pattern",
31227
+ "Technique",
31228
+ "AntiPattern",
31229
+ "Language",
31230
+ "Package",
31231
+ "Tool"
31232
+ ];
31233
+ var SNAPSHOT_EDGE_TYPES = [
31234
+ "ANCHORED_AT",
31235
+ "CAUSED_BY",
31236
+ "CONCERNS",
31237
+ "DEPENDS_ON",
31238
+ "EVIDENCED_BY",
31239
+ "MANIFESTED_IN",
31240
+ "OCCURS_IN",
31241
+ "PERTAIN_TO",
31242
+ "REGRESSION_OF",
31243
+ "SOLVED_BY"
31244
+ ];
31245
+ var SNAPSHOT_READ_GUARD = typeof process !== "undefined" && Boolean(process.env["VITEST"]);
31246
+ var LABELS = new Set(SNAPSHOT_NODE_LABELS);
31247
+ var EDGES = new Set(SNAPSHOT_EDGE_TYPES);
31248
+ function guardSnapshotReads(store2) {
31249
+ const fail = (what) => {
31250
+ throw new Error(`buildSnapshot read ${what}, outside SNAPSHOT_NODE_LABELS/SNAPSHOT_EDGE_TYPES (snapshot-inputs.ts)`);
31251
+ };
31252
+ const label = (l, via) => {
31253
+ if (typeof l === "string" && !LABELS.has(l)) fail(`label ${l} via ${via}`);
31254
+ };
31255
+ const types = (t, via) => {
31256
+ if (!Array.isArray(t) || t.length === 0) fail(`every edge type via ${via}`);
31257
+ for (const x of t) if (!EDGES.has(String(x))) fail(`edge type ${String(x)} via ${via}`);
31258
+ };
31259
+ const nodes = (r, via) => {
31260
+ for (const n of Array.isArray(r) ? r : r ? [r] : []) label(n.label, via);
31261
+ return r;
31262
+ };
31263
+ const allowed = {
31264
+ getNode: (id) => nodes(store2.getNode(id), "getNode"),
31265
+ selectNodes: (q) => {
31266
+ for (const l of q.labels) label(l, "selectNodes");
31267
+ return store2.selectNodes(q);
31268
+ },
31269
+ countNodes: (q) => {
31270
+ for (const l of q.labels) label(l, "countNodes");
31271
+ return store2.countNodes(q);
31272
+ },
31273
+ findLiveNodesByAttr: (l, ...rest) => {
31274
+ label(l, "findLiveNodesByAttr");
31275
+ return store2.findLiveNodesByAttr(l, ...rest);
31276
+ },
31277
+ outEdges: (id, t) => {
31278
+ types(t, "outEdges");
31279
+ return store2.outEdges(id, t);
31280
+ },
31281
+ inEdges: (id, t) => {
31282
+ types(t, "inEdges");
31283
+ return store2.inEdges(id, t);
31284
+ },
31285
+ inDegreeByType: (t) => {
31286
+ types(t, "inDegreeByType");
31287
+ return store2.inDegreeByType(t);
31288
+ },
31289
+ nodesAsOfWhereTrue: (asOf, attr) => {
31290
+ if (attr !== "revisit") fail(`nodesAsOfWhereTrue(${String(attr)})`);
31291
+ return store2.nodesAsOfWhereTrue(asOf, attr);
31292
+ }
31293
+ };
31294
+ return new Proxy(store2, {
31295
+ get(target, prop) {
31296
+ if (typeof prop === "string" && prop in allowed) return allowed[prop];
31297
+ const v = Reflect.get(target, prop, target);
31298
+ if (typeof v === "function") return () => fail(`store.${String(prop)}()`);
31299
+ return v;
31300
+ }
31301
+ });
31302
+ }
31303
+
31304
+ // ../../packages/context-writer/src/render.ts
31016
31305
  var MOTIF_BAND = 10;
31017
31306
  var MOTIF_POOL = 1e3;
31018
31307
  function rankMotifs(pool, signal, k = MOTIF_BAND) {
@@ -31033,6 +31322,8 @@ var PROBLEM_BAND_ATTRS = [
31033
31322
  ...HISTORY_POLICY_ATTRS
31034
31323
  ];
31035
31324
  function buildSnapshot(opts) {
31325
+ const codeStore = opts.store;
31326
+ if (SNAPSHOT_READ_GUARD) opts = { ...opts, store: guardSnapshotReads(opts.store) };
31036
31327
  const hydrate = (id) => opts.store.getNode(id);
31037
31328
  const problems = opts.store.selectNodes({ labels: ["Problem", "Vulnerability"], attrs: PROBLEM_BAND_ATTRS }).filter((p) => p.attrs["mergedInto"] === void 0 && passiveAllowed(p, opts.atCommit));
31038
31329
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
@@ -31068,7 +31359,7 @@ function buildSnapshot(opts) {
31068
31359
  const recentResolved = resolvedPool.slice(0, RECENT_RESOLVED);
31069
31360
  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);
31070
31361
  const motifs = rankMotifs(motifPool, void 0);
31071
- const workingFile = opts.workingFile ? sliceForFile(opts.store, opts.workingFile, opts.atCommit) : void 0;
31362
+ const workingFile = opts.workingFile ? sliceForFile(codeStore, opts.workingFile, opts.atCommit) : void 0;
31072
31363
  const byEpisode = /* @__PURE__ */ new Map();
31073
31364
  for (const sol of opts.store.findLiveNodesByAttr("Solution", "enrichmentPending", 1)) {
31074
31365
  if (!historyReadPolicy(sol, { mode: "passive", atCommit: opts.atCommit }).allow) continue;