@inerrata-corporation/errata 2.0.8-dev.2889 → 2.0.8-dev.2895

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.
@@ -16146,6 +16146,9 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
16146
16146
  -- Live rows by name, for looking up the few symbols a statement mentions
16147
16147
  -- instead of hydrating every code symbol per minted problem.
16148
16148
  CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
16149
+ -- Problems by the file their anchor hint names: the harvest upgrades hints for
16150
+ -- every edited file of every turn, and a scan parses every Problem's attrs.
16151
+ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
16149
16152
  `;
16150
16153
  function encodeEmbedding(emb) {
16151
16154
  if (!emb || emb.length === 0) return null;
@@ -17004,6 +17007,13 @@ var SqliteGraphStore = class {
17004
17007
  ).all(...relPaths);
17005
17008
  return rows.map(rowToNode);
17006
17009
  }
17010
+ findLiveNodesByAttr(label, attrPath, value) {
17011
+ if (!/^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(attrPath)) throw new Error(`invalid attr path: ${attrPath}`);
17012
+ const rows = this.db.prepare(
17013
+ `SELECT * FROM nodes WHERE json_extract(attrs_json, '$.${attrPath}') = ? AND valid_to IS NULL AND label = ?`
17014
+ ).all(value, label);
17015
+ return rows.map(rowToNode);
17016
+ }
17007
17017
  // ── Bi-temporal versioning ───────────────────────────────────────────
17008
17018
  // NOTE: the adapter's transaction() is non-reentrant (raw BEGIN/COMMIT), and
17009
17019
  // the indexer calls freezeNode inside its own store.transaction(). So this
package/errata.mjs CHANGED
@@ -17568,6 +17568,9 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
17568
17568
  -- Live rows by name, for looking up the few symbols a statement mentions
17569
17569
  -- instead of hydrating every code symbol per minted problem.
17570
17570
  CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
17571
+ -- Problems by the file their anchor hint names: the harvest upgrades hints for
17572
+ -- every edited file of every turn, and a scan parses every Problem's attrs.
17573
+ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
17571
17574
  `;
17572
17575
  SqliteGraphStore = class {
17573
17576
  db;
@@ -18369,6 +18372,13 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
18369
18372
  ).all(...relPaths);
18370
18373
  return rows.map(rowToNode);
18371
18374
  }
18375
+ findLiveNodesByAttr(label, attrPath, value) {
18376
+ if (!/^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(attrPath)) throw new Error(`invalid attr path: ${attrPath}`);
18377
+ const rows = this.db.prepare(
18378
+ `SELECT * FROM nodes WHERE json_extract(attrs_json, '$.${attrPath}') = ? AND valid_to IS NULL AND label = ?`
18379
+ ).all(value, label);
18380
+ return rows.map(rowToNode);
18381
+ }
18372
18382
  // ── Bi-temporal versioning ───────────────────────────────────────────
18373
18383
  // NOTE: the adapter's transaction() is non-reentrant (raw BEGIN/COMMIT), and
18374
18384
  // the indexer calls freezeNode inside its own store.transaction(). So this
@@ -20645,7 +20655,7 @@ function consumeAnchorHint(store, p, ts) {
20645
20655
  }
20646
20656
  function upgradeAnchorHintsForFile(store, relPath, workspaceId2, ts) {
20647
20657
  let upgraded = 0;
20648
- for (const p of store.findNodesByLabel("Problem")) {
20658
+ for (const p of store.findLiveNodesByAttr("Problem", "anchorHint.path", relPath)) {
20649
20659
  if (p.validTo != null || p.attrs["resolvedAt"]) continue;
20650
20660
  const hint = openAnchorHint(p);
20651
20661
  if (!hint || hint.path !== relPath) continue;
@@ -80114,7 +80124,7 @@ function createWatchBreaker(deps) {
80114
80124
  }
80115
80125
 
80116
80126
  // src/engine.ts
80117
- var DAEMON_VERSION = true ? "2.0.8-dev.2889" : "2.0.0-alpha.0";
80127
+ var DAEMON_VERSION = true ? "2.0.8-dev.2895" : "2.0.0-alpha.0";
80118
80128
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
80119
80129
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
80120
80130
  var GIT_OP_MUTE_MS = 4e3;
@@ -84483,6 +84493,57 @@ async function mergeSide(root, p1, p2) {
84483
84493
  return { sha: sha2, parents };
84484
84494
  });
84485
84495
  }
84496
+ async function commitGraph(root, branch) {
84497
+ const out2 = await git(root, [...GIT_OPTS, "rev-list", "--parents", branch]);
84498
+ const graph = /* @__PURE__ */ new Map();
84499
+ for (const l of out2.split(/\r?\n/)) {
84500
+ const [sha2, ...parents] = l.trim().split(" ");
84501
+ if (sha2) graph.set(sha2, parents);
84502
+ }
84503
+ return graph;
84504
+ }
84505
+ function mergeSidesFromGraph(line, start2, graph) {
84506
+ const seen = /* @__PURE__ */ new Set();
84507
+ const reach = (from, into) => {
84508
+ const stack = [from];
84509
+ while (stack.length > 0) {
84510
+ const sha2 = stack.pop();
84511
+ if (seen.has(sha2) || into.has(sha2)) continue;
84512
+ into.add(sha2);
84513
+ for (const p of graph.get(sha2) ?? []) stack.push(p);
84514
+ }
84515
+ };
84516
+ if (start2 > 0) {
84517
+ const base = /* @__PURE__ */ new Set();
84518
+ reach(line[start2 - 1].sha, base);
84519
+ for (const sha2 of base) seen.add(sha2);
84520
+ }
84521
+ const sides = /* @__PURE__ */ new Map();
84522
+ for (let i2 = start2; i2 < line.length; i2++) {
84523
+ const { sha: sha2, parents } = line[i2];
84524
+ if (parents.length >= 2) {
84525
+ const side = /* @__PURE__ */ new Set();
84526
+ reach(parents[1], side);
84527
+ const chain2 = [];
84528
+ let at = side.has(parents[1]) ? parents[1] : void 0;
84529
+ let linear = true;
84530
+ while (at) {
84531
+ const ps = graph.get(at) ?? [];
84532
+ chain2.push({ sha: at, parents: [...ps] });
84533
+ const inSide = ps.filter((p) => side.has(p));
84534
+ if (inSide.length > 1) {
84535
+ linear = false;
84536
+ break;
84537
+ }
84538
+ at = inSide[0];
84539
+ }
84540
+ sides.set(sha2, linear && chain2.length === side.size ? chain2.reverse() : null);
84541
+ for (const s of side) seen.add(s);
84542
+ }
84543
+ seen.add(sha2);
84544
+ }
84545
+ return sides;
84546
+ }
84486
84547
  async function tagsByCommit(root) {
84487
84548
  const out2 = await git(root, [...GIT_OPTS, "for-each-ref", "--format=%(refname:short)%00%(objectname)%00%(*objectname)", "refs/tags"]);
84488
84549
  const map2 = /* @__PURE__ */ new Map();
@@ -84511,8 +84572,10 @@ async function enumerateUnits(root, opts = {}) {
84511
84572
  if (start2 >= line.length) return [];
84512
84573
  const commits = await readCommitsBulk(root, branch, start2 > 0 ? line[start2 - 1].sha : null);
84513
84574
  const commit = async (sha2) => commits.get(sha2) ?? await readCommit(root, sha2);
84514
- const merges = line.slice(start2).filter((c) => c.parents.length >= 2);
84575
+ const fromGraph = mergeSidesFromGraph(line, start2, await commitGraph(root, branch));
84515
84576
  const sides = /* @__PURE__ */ new Map();
84577
+ for (const [sha2, side] of fromGraph) if (side) sides.set(sha2, side);
84578
+ const merges = line.slice(start2).filter((c) => c.parents.length >= 2 && !sides.has(c.sha));
84516
84579
  const sideLists = await mapBounded(merges, MERGE_SIDE_CONCURRENCY, (m) => mergeSide(root, m.parents[0], m.parents[1]));
84517
84580
  merges.forEach((m, i2) => sides.set(m.sha, sideLists[i2]));
84518
84581
  const units = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.8-dev.2889",
3
+ "version": "2.0.8-dev.2895",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24528,6 +24528,9 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
24528
24528
  -- Live rows by name, for looking up the few symbols a statement mentions
24529
24529
  -- instead of hydrating every code symbol per minted problem.
24530
24530
  CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
24531
+ -- Problems by the file their anchor hint names: the harvest upgrades hints for
24532
+ -- every edited file of every turn, and a scan parses every Problem's attrs.
24533
+ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL;
24531
24534
  `;
24532
24535
  function encodeEmbedding(emb) {
24533
24536
  if (!emb || emb.length === 0) return null;
@@ -25386,6 +25389,13 @@ var SqliteGraphStore = class {
25386
25389
  ).all(...relPaths);
25387
25390
  return rows.map(rowToNode);
25388
25391
  }
25392
+ findLiveNodesByAttr(label, attrPath, value) {
25393
+ if (!/^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$/.test(attrPath)) throw new Error(`invalid attr path: ${attrPath}`);
25394
+ const rows = this.db.prepare(
25395
+ `SELECT * FROM nodes WHERE json_extract(attrs_json, '$.${attrPath}') = ? AND valid_to IS NULL AND label = ?`
25396
+ ).all(value, label);
25397
+ return rows.map(rowToNode);
25398
+ }
25389
25399
  // ── Bi-temporal versioning ───────────────────────────────────────────
25390
25400
  // NOTE: the adapter's transaction() is non-reentrant (raw BEGIN/COMMIT), and
25391
25401
  // the indexer calls freezeNode inside its own store.transaction(). So this