@inerrata-corporation/errata 2.0.6-dev.2618 → 2.0.6-dev.2627

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.
@@ -16343,6 +16343,10 @@ var SqliteGraphStore = class {
16343
16343
  countByLabel: this.db.prepare(
16344
16344
  "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
16345
16345
  ),
16346
+ // Attrs twin of findByLabel — same live rows, no embedding blob read.
16347
+ attrsByLabel: this.db.prepare(
16348
+ "SELECT id, created_at, created_at_seq, attrs_json FROM nodes WHERE label = ? AND valid_to IS NULL"
16349
+ ),
16346
16350
  // Identity twin of findByLabel — same live-row semantics, two attrs read in
16347
16351
  // SQL, no attrs JSON.parse or embedding decode (PJ-anchor-reach).
16348
16352
  identitiesByLabel: this.db.prepare(
@@ -16396,8 +16400,16 @@ var SqliteGraphStore = class {
16396
16400
  closeEdge: this.db.prepare(
16397
16401
  "UPDATE edges SET valid_to = :t WHERE id = :id AND valid_to IS NULL"
16398
16402
  ),
16403
+ // Same rows as `COALESCE(logical_id, id) = ?`, split so each branch can use
16404
+ // an index (nodes_logical, then the id primary key). The COALESCE form made
16405
+ // SQLite SCAN nodes: 0.4-0.9s per call on a 227k-node store, once per new
16406
+ // diagnostic via attributeRootCause (2026-09-24).
16399
16407
  nodeHistory: this.db.prepare(
16400
- "SELECT * FROM nodes WHERE COALESCE(logical_id, id) = ? ORDER BY version ASC"
16408
+ `SELECT * FROM (
16409
+ SELECT * FROM nodes WHERE logical_id = ?1
16410
+ UNION ALL
16411
+ SELECT * FROM nodes WHERE id = ?1 AND logical_id IS NULL
16412
+ ) ORDER BY version ASC`
16401
16413
  ),
16402
16414
  nodesAsOf: this.db.prepare(
16403
16415
  "SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)"
@@ -16648,6 +16660,19 @@ var SqliteGraphStore = class {
16648
16660
  countNodesByLabel(label) {
16649
16661
  return Number(this.stmts.countByLabel.get(label).n);
16650
16662
  }
16663
+ /** Live rows for a label with attrs and creation stamps only — for scans that
16664
+ * read attrs and never the vector. `findNodesByLabel` decodes every embedding
16665
+ * blob; the liveness watch's coverage pass spent 3.4s of a 4.3s main-thread
16666
+ * hold doing exactly that on a 227k-node store (2026-09-24). */
16667
+ findNodeAttrsByLabel(label) {
16668
+ const rows = this.stmts.attrsByLabel.all(label);
16669
+ return rows.map((r) => ({
16670
+ id: r.id,
16671
+ createdAt: r.created_at,
16672
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
16673
+ attrs: JSON.parse(r.attrs_json)
16674
+ }));
16675
+ }
16651
16676
  findNodeIdentitiesByLabel(label) {
16652
16677
  const rows = this.stmts.identitiesByLabel.all(label);
16653
16678
  return rows.map((r) => ({
@@ -16712,6 +16737,18 @@ var SqliteGraphStore = class {
16712
16737
  ).all(params);
16713
16738
  return rows.map(rowToEdge);
16714
16739
  }
16740
+ /** Live in-degree per target over `types`, in one grouped query — the counts
16741
+ * `inEdges(id, types).length` gives, for every node at once. A per-node
16742
+ * inEdges loop over ~48k code nodes held a PreToolUse hook 12.6s when the
16743
+ * hotspots vfile materialized (2026-09-24). */
16744
+ inDegreeByType(types) {
16745
+ if (types.length === 0) return /* @__PURE__ */ new Map();
16746
+ const placeholders = types.map(() => "?").join(",");
16747
+ const rows = this.db.prepare(
16748
+ `SELECT to_id, COUNT(*) AS n FROM edges WHERE type IN (${placeholders}) AND valid_to IS NULL GROUP BY to_id`
16749
+ ).all(...types);
16750
+ return new Map(rows.map((r) => [r.to_id, Number(r.n)]));
16751
+ }
16715
16752
  neighbors(nodeId, types) {
16716
16753
  const out = [];
16717
16754
  const edges = this.outEdges(nodeId, types);
@@ -16974,6 +17011,19 @@ var SqliteGraphStore = class {
16974
17011
  const rows = this.stmts.nodesAsOf.all({ t });
16975
17012
  return rows.map(rowToNode);
16976
17013
  }
17014
+ /** `nodesAsOf(t).filter((n) => n.attrs[attr] === true)` with the attr test in
17015
+ * SQL, so only the flagged rows are hydrated. The filter-after form hydrated
17016
+ * the whole graph for a handful of revisit flags: 6.7s per context render on
17017
+ * a 227k-node store (2026-09-24). `attr` is interpolated into a JSON path,
17018
+ * so it must be a plain identifier. */
17019
+ nodesAsOfWhereTrue(t, attr) {
17020
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
17021
+ const rows = this.db.prepare(
17022
+ `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
17023
+ AND json_type(attrs_json, '$.${attr}') = 'true'`
17024
+ ).all({ t });
17025
+ return rows.map(rowToNode);
17026
+ }
16977
17027
  edgesAsOf(t) {
16978
17028
  const rows = this.stmts.edgesAsOf.all({ t });
16979
17029
  return rows.map(rowToEdge);
package/errata.mjs CHANGED
@@ -17703,6 +17703,10 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
17703
17703
  countByLabel: this.db.prepare(
17704
17704
  "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
17705
17705
  ),
17706
+ // Attrs twin of findByLabel — same live rows, no embedding blob read.
17707
+ attrsByLabel: this.db.prepare(
17708
+ "SELECT id, created_at, created_at_seq, attrs_json FROM nodes WHERE label = ? AND valid_to IS NULL"
17709
+ ),
17706
17710
  // Identity twin of findByLabel — same live-row semantics, two attrs read in
17707
17711
  // SQL, no attrs JSON.parse or embedding decode (PJ-anchor-reach).
17708
17712
  identitiesByLabel: this.db.prepare(
@@ -17756,8 +17760,16 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
17756
17760
  closeEdge: this.db.prepare(
17757
17761
  "UPDATE edges SET valid_to = :t WHERE id = :id AND valid_to IS NULL"
17758
17762
  ),
17763
+ // Same rows as `COALESCE(logical_id, id) = ?`, split so each branch can use
17764
+ // an index (nodes_logical, then the id primary key). The COALESCE form made
17765
+ // SQLite SCAN nodes: 0.4-0.9s per call on a 227k-node store, once per new
17766
+ // diagnostic via attributeRootCause (2026-09-24).
17759
17767
  nodeHistory: this.db.prepare(
17760
- "SELECT * FROM nodes WHERE COALESCE(logical_id, id) = ? ORDER BY version ASC"
17768
+ `SELECT * FROM (
17769
+ SELECT * FROM nodes WHERE logical_id = ?1
17770
+ UNION ALL
17771
+ SELECT * FROM nodes WHERE id = ?1 AND logical_id IS NULL
17772
+ ) ORDER BY version ASC`
17761
17773
  ),
17762
17774
  nodesAsOf: this.db.prepare(
17763
17775
  "SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)"
@@ -18008,6 +18020,19 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
18008
18020
  countNodesByLabel(label) {
18009
18021
  return Number(this.stmts.countByLabel.get(label).n);
18010
18022
  }
18023
+ /** Live rows for a label with attrs and creation stamps only — for scans that
18024
+ * read attrs and never the vector. `findNodesByLabel` decodes every embedding
18025
+ * blob; the liveness watch's coverage pass spent 3.4s of a 4.3s main-thread
18026
+ * hold doing exactly that on a 227k-node store (2026-09-24). */
18027
+ findNodeAttrsByLabel(label) {
18028
+ const rows = this.stmts.attrsByLabel.all(label);
18029
+ return rows.map((r) => ({
18030
+ id: r.id,
18031
+ createdAt: r.created_at,
18032
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
18033
+ attrs: JSON.parse(r.attrs_json)
18034
+ }));
18035
+ }
18011
18036
  findNodeIdentitiesByLabel(label) {
18012
18037
  const rows = this.stmts.identitiesByLabel.all(label);
18013
18038
  return rows.map((r) => ({
@@ -18072,6 +18097,18 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
18072
18097
  ).all(params);
18073
18098
  return rows.map(rowToEdge);
18074
18099
  }
18100
+ /** Live in-degree per target over `types`, in one grouped query — the counts
18101
+ * `inEdges(id, types).length` gives, for every node at once. A per-node
18102
+ * inEdges loop over ~48k code nodes held a PreToolUse hook 12.6s when the
18103
+ * hotspots vfile materialized (2026-09-24). */
18104
+ inDegreeByType(types) {
18105
+ if (types.length === 0) return /* @__PURE__ */ new Map();
18106
+ const placeholders = types.map(() => "?").join(",");
18107
+ const rows = this.db.prepare(
18108
+ `SELECT to_id, COUNT(*) AS n FROM edges WHERE type IN (${placeholders}) AND valid_to IS NULL GROUP BY to_id`
18109
+ ).all(...types);
18110
+ return new Map(rows.map((r) => [r.to_id, Number(r.n)]));
18111
+ }
18075
18112
  neighbors(nodeId, types) {
18076
18113
  const out2 = [];
18077
18114
  const edges = this.outEdges(nodeId, types);
@@ -18334,6 +18371,19 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
18334
18371
  const rows = this.stmts.nodesAsOf.all({ t });
18335
18372
  return rows.map(rowToNode);
18336
18373
  }
18374
+ /** `nodesAsOf(t).filter((n) => n.attrs[attr] === true)` with the attr test in
18375
+ * SQL, so only the flagged rows are hydrated. The filter-after form hydrated
18376
+ * the whole graph for a handful of revisit flags: 6.7s per context render on
18377
+ * a 227k-node store (2026-09-24). `attr` is interpolated into a JSON path,
18378
+ * so it must be a plain identifier. */
18379
+ nodesAsOfWhereTrue(t, attr) {
18380
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
18381
+ const rows = this.db.prepare(
18382
+ `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
18383
+ AND json_type(attrs_json, '$.${attr}') = 'true'`
18384
+ ).all({ t });
18385
+ return rows.map(rowToNode);
18386
+ }
18337
18387
  edgesAsOf(t) {
18338
18388
  const rows = this.stmts.edgesAsOf.all({ t });
18339
18389
  return rows.map(rowToEdge);
@@ -18740,7 +18790,7 @@ function clearRevisit(store, nodeId, ts) {
18740
18790
  store.updateNode(nodeId, { attrs, lastUpdatedAt: ts });
18741
18791
  }
18742
18792
  function listNeedsRevisit(store, asOf) {
18743
- return store.nodesAsOf(asOf).filter((n) => n.attrs["revisit"] === true).map((n) => ({
18793
+ return store.nodesAsOfWhereTrue(asOf, "revisit").map((n) => ({
18744
18794
  id: n.id,
18745
18795
  label: n.label,
18746
18796
  description: n.description,
@@ -23458,7 +23508,7 @@ function evaluateFed(store, d) {
23458
23508
  const column = COLUMN_INPUTS[d.fed.attr];
23459
23509
  const fed = d.fed;
23460
23510
  const has = (n) => column ? column(n) : n.attrs[fed.attr] !== void 0;
23461
- const nodes = fed.labels.flatMap((label) => store.findNodesByLabel(label));
23511
+ const nodes = fed.labels.flatMap((label) => store.findNodeAttrsByLabel(label));
23462
23512
  let window2 = nodes;
23463
23513
  if (fed.recent !== void 0) {
23464
23514
  window2 = [...nodes].sort((a, b) => b.createdAt - a.createdAt).slice(0, fed.recent);
@@ -31722,16 +31772,17 @@ function diagnosticKey(relPath, d) {
31722
31772
  function anchorByLine(store, workspaceId2, relPath, line) {
31723
31773
  let best = null;
31724
31774
  let bestRow = -1;
31725
- for (const label of ["Function", "Method", "Class"]) {
31726
- for (const n of store.findNodesByLabel(label)) {
31727
- if (n.attrs["workspaceId"] !== workspaceId2) continue;
31728
- if (n.attrs["relPath"] !== relPath) continue;
31729
- const row = n.attrs["startRow"];
31730
- if (typeof row !== "number" || row > line) continue;
31731
- if (row > bestRow) {
31732
- bestRow = row;
31733
- best = n;
31734
- }
31775
+ const labelRank = { Function: 0, Method: 1, Class: 2 };
31776
+ for (const n of store.nodesByRelPath([relPath])) {
31777
+ const rank = labelRank[n.label];
31778
+ if (rank === void 0) continue;
31779
+ if (n.attrs["workspaceId"] !== workspaceId2) continue;
31780
+ if (n.attrs["relPath"] !== relPath) continue;
31781
+ const row = n.attrs["startRow"];
31782
+ if (typeof row !== "number" || row > line) continue;
31783
+ if (row > bestRow || row === bestRow && best !== null && rank < labelRank[best.label]) {
31784
+ bestRow = row;
31785
+ best = n;
31735
31786
  }
31736
31787
  }
31737
31788
  return best ? best.logicalId ?? best.id : null;
@@ -42783,7 +42834,8 @@ var init_mcp = __esm({
42783
42834
  items: dead.slice(0, limit)
42784
42835
  };
42785
42836
  }
42786
- const ranked = codeNodes.map((n) => ({ id: n.id, label: n.label, name: n.description, fanIn: store.inEdges(n.id, [...CODE_REACH_EDGES]).length })).sort((a, b) => b.fanIn - a.fanIn).slice(0, limit);
42837
+ const fanIn = store.inDegreeByType([...CODE_REACH_EDGES]);
42838
+ const ranked = codeNodes.map((n) => ({ id: n.id, label: n.label, name: n.description, fanIn: fanIn.get(n.id) ?? 0 })).sort((a, b) => b.fanIn - a.fanIn).slice(0, limit);
42787
42839
  return { kind: "fan-in", items: ranked };
42788
42840
  }
42789
42841
  },
@@ -78426,7 +78478,7 @@ function createWatchBreaker(deps) {
78426
78478
  }
78427
78479
 
78428
78480
  // src/engine.ts
78429
- var DAEMON_VERSION = true ? "2.0.6-dev.2618" : "2.0.0-alpha.0";
78481
+ var DAEMON_VERSION = true ? "2.0.6-dev.2627" : "2.0.0-alpha.0";
78430
78482
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
78431
78483
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
78432
78484
  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.6-dev.2618",
3
+ "version": "2.0.6-dev.2627",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24725,6 +24725,10 @@ var SqliteGraphStore = class {
24725
24725
  countByLabel: this.db.prepare(
24726
24726
  "SELECT COUNT(*) AS n FROM nodes WHERE label = ? AND valid_to IS NULL"
24727
24727
  ),
24728
+ // Attrs twin of findByLabel — same live rows, no embedding blob read.
24729
+ attrsByLabel: this.db.prepare(
24730
+ "SELECT id, created_at, created_at_seq, attrs_json FROM nodes WHERE label = ? AND valid_to IS NULL"
24731
+ ),
24728
24732
  // Identity twin of findByLabel — same live-row semantics, two attrs read in
24729
24733
  // SQL, no attrs JSON.parse or embedding decode (PJ-anchor-reach).
24730
24734
  identitiesByLabel: this.db.prepare(
@@ -24778,8 +24782,16 @@ var SqliteGraphStore = class {
24778
24782
  closeEdge: this.db.prepare(
24779
24783
  "UPDATE edges SET valid_to = :t WHERE id = :id AND valid_to IS NULL"
24780
24784
  ),
24785
+ // Same rows as `COALESCE(logical_id, id) = ?`, split so each branch can use
24786
+ // an index (nodes_logical, then the id primary key). The COALESCE form made
24787
+ // SQLite SCAN nodes: 0.4-0.9s per call on a 227k-node store, once per new
24788
+ // diagnostic via attributeRootCause (2026-09-24).
24781
24789
  nodeHistory: this.db.prepare(
24782
- "SELECT * FROM nodes WHERE COALESCE(logical_id, id) = ? ORDER BY version ASC"
24790
+ `SELECT * FROM (
24791
+ SELECT * FROM nodes WHERE logical_id = ?1
24792
+ UNION ALL
24793
+ SELECT * FROM nodes WHERE id = ?1 AND logical_id IS NULL
24794
+ ) ORDER BY version ASC`
24783
24795
  ),
24784
24796
  nodesAsOf: this.db.prepare(
24785
24797
  "SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)"
@@ -25030,6 +25042,19 @@ var SqliteGraphStore = class {
25030
25042
  countNodesByLabel(label) {
25031
25043
  return Number(this.stmts.countByLabel.get(label).n);
25032
25044
  }
25045
+ /** Live rows for a label with attrs and creation stamps only — for scans that
25046
+ * read attrs and never the vector. `findNodesByLabel` decodes every embedding
25047
+ * blob; the liveness watch's coverage pass spent 3.4s of a 4.3s main-thread
25048
+ * hold doing exactly that on a 227k-node store (2026-09-24). */
25049
+ findNodeAttrsByLabel(label) {
25050
+ const rows = this.stmts.attrsByLabel.all(label);
25051
+ return rows.map((r) => ({
25052
+ id: r.id,
25053
+ createdAt: r.created_at,
25054
+ ...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
25055
+ attrs: JSON.parse(r.attrs_json)
25056
+ }));
25057
+ }
25033
25058
  findNodeIdentitiesByLabel(label) {
25034
25059
  const rows = this.stmts.identitiesByLabel.all(label);
25035
25060
  return rows.map((r) => ({
@@ -25094,6 +25119,18 @@ var SqliteGraphStore = class {
25094
25119
  ).all(params);
25095
25120
  return rows.map(rowToEdge);
25096
25121
  }
25122
+ /** Live in-degree per target over `types`, in one grouped query — the counts
25123
+ * `inEdges(id, types).length` gives, for every node at once. A per-node
25124
+ * inEdges loop over ~48k code nodes held a PreToolUse hook 12.6s when the
25125
+ * hotspots vfile materialized (2026-09-24). */
25126
+ inDegreeByType(types) {
25127
+ if (types.length === 0) return /* @__PURE__ */ new Map();
25128
+ const placeholders = types.map(() => "?").join(",");
25129
+ const rows = this.db.prepare(
25130
+ `SELECT to_id, COUNT(*) AS n FROM edges WHERE type IN (${placeholders}) AND valid_to IS NULL GROUP BY to_id`
25131
+ ).all(...types);
25132
+ return new Map(rows.map((r) => [r.to_id, Number(r.n)]));
25133
+ }
25097
25134
  neighbors(nodeId, types) {
25098
25135
  const out2 = [];
25099
25136
  const edges = this.outEdges(nodeId, types);
@@ -25356,6 +25393,19 @@ var SqliteGraphStore = class {
25356
25393
  const rows = this.stmts.nodesAsOf.all({ t });
25357
25394
  return rows.map(rowToNode);
25358
25395
  }
25396
+ /** `nodesAsOf(t).filter((n) => n.attrs[attr] === true)` with the attr test in
25397
+ * SQL, so only the flagged rows are hydrated. The filter-after form hydrated
25398
+ * the whole graph for a handful of revisit flags: 6.7s per context render on
25399
+ * a 227k-node store (2026-09-24). `attr` is interpolated into a JSON path,
25400
+ * so it must be a plain identifier. */
25401
+ nodesAsOfWhereTrue(t, attr) {
25402
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
25403
+ const rows = this.db.prepare(
25404
+ `SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
25405
+ AND json_type(attrs_json, '$.${attr}') = 'true'`
25406
+ ).all({ t });
25407
+ return rows.map(rowToNode);
25408
+ }
25359
25409
  edgesAsOf(t) {
25360
25410
  const rows = this.stmts.edgesAsOf.all({ t });
25361
25411
  return rows.map(rowToEdge);
@@ -25471,7 +25521,7 @@ function clearRevisit(store2, nodeId, ts) {
25471
25521
  store2.updateNode(nodeId, { attrs, lastUpdatedAt: ts });
25472
25522
  }
25473
25523
  function listNeedsRevisit(store2, asOf) {
25474
- return store2.nodesAsOf(asOf).filter((n) => n.attrs["revisit"] === true).map((n) => ({
25524
+ return store2.nodesAsOfWhereTrue(asOf, "revisit").map((n) => ({
25475
25525
  id: n.id,
25476
25526
  label: n.label,
25477
25527
  description: n.description,