@inerrata-corporation/errata 2.0.17-dev.3421 → 2.0.17-dev.3433

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.17-dev.3421",
3
+ "version": "2.0.17-dev.3433",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -16283,13 +16283,13 @@ function loadNodeSqlite() {
16283
16283
  process.emitWarning = original;
16284
16284
  }
16285
16285
  }
16286
- function openDatabase(path) {
16287
- const db = new DatabaseSync(path);
16286
+ function openDatabase(path, opts) {
16287
+ const db = opts ? new DatabaseSync(path, opts) : new DatabaseSync(path);
16288
16288
  try {
16289
16289
  db.exec("PRAGMA foreign_keys = ON");
16290
16290
  } catch {
16291
16291
  }
16292
- if (path !== ":memory:") {
16292
+ if (path !== ":memory:" && !opts?.readOnly) {
16293
16293
  try {
16294
16294
  db.exec("PRAGMA journal_mode = WAL");
16295
16295
  db.exec("PRAGMA synchronous = NORMAL");
@@ -25171,8 +25171,9 @@ init_src2();
25171
25171
  init_src();
25172
25172
 
25173
25173
  // ../../packages/local-graph/src/history-cache-floor.ts
25174
+ var HISTORY_CACHE_FLOOR_KEY = "history-cache-withdrawal-floor-3";
25174
25175
  function installHistoryCacheFloor(db) {
25175
- const key = "history-cache-withdrawal-floor-3";
25176
+ const key = HISTORY_CACHE_FLOOR_KEY;
25176
25177
  if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key)) return;
25177
25178
  db.transaction(() => {
25178
25179
  db.exec("DROP TRIGGER IF EXISTS history_cache_nodes_insert; DROP TRIGGER IF EXISTS history_cache_nodes_update;");
@@ -25334,6 +25335,7 @@ function localEdgeViolation(fromLabel, type, toLabel) {
25334
25335
  return verdict.ok ? null : verdict.reason ?? "edge rule violation";
25335
25336
  }
25336
25337
  var SCHEMA_VERSION = 6;
25338
+ var SESSION_REPAIR_KEY = "session-evidence-v1";
25337
25339
  var SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
25338
25340
  var SCHEMA_SQL = `
25339
25341
  CREATE TABLE IF NOT EXISTS schema_version (
@@ -25545,10 +25547,10 @@ var SqliteGraphStore = class {
25545
25547
  }
25546
25548
  this.db.exec(TEMPORAL_INDEX_SQL);
25547
25549
  installHistoryCacheFloor(this.db);
25548
- if (!this.db.prepare("SELECT value FROM store_meta WHERE key = 'session-evidence-v1'").get()) {
25550
+ if (!this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(SESSION_REPAIR_KEY)) {
25549
25551
  this.db.transaction(() => {
25550
25552
  this.repairSessionAliases();
25551
- this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES ('session-evidence-v1', 'done')").run();
25553
+ this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, 'done')").run(SESSION_REPAIR_KEY);
25552
25554
  });
25553
25555
  }
25554
25556
  this.stmts = {
@@ -25743,6 +25745,34 @@ var SqliteGraphStore = class {
25743
25745
  const seqRow = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
25744
25746
  this.ingestSeq = seqRow ? Number(seqRow.value) : 0;
25745
25747
  }
25748
+ strandedSymbols() {
25749
+ return this.db.prepare(`
25750
+ SELECT f.id AS fileId, e.to_id AS symbolId
25751
+ -- Start from the small File population. An unconstrained JOIN lets
25752
+ -- SQLite start at edges_live and visit every live edge in the graph.
25753
+ FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
25754
+ LEFT JOIN nodes t ON t.id = e.to_id
25755
+ WHERE f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
25756
+ AND e.type IN ('DEFINES', 'CONTAINS')
25757
+ AND (t.id IS NULL OR t.valid_to IS NOT NULL)
25758
+ `).all();
25759
+ }
25760
+ fileIndexStates(ids) {
25761
+ if (ids.length === 0) return [];
25762
+ if (ids.length > 500) throw new RangeError("fileIndexStates accepts at most 500 ids per read");
25763
+ const rows = this.db.prepare(`
25764
+ SELECT f.id, f.last_updated_at AS lastUpdatedAt, f.valid_to AS validTo,
25765
+ CASE WHEN count(e.id) = 0 THEN
25766
+ (json_type(f.attrs_json, '$.symbolCount') IN ('integer','real') AND json_extract(f.attrs_json, '$.symbolCount') = 0)
25767
+ OR json_type(f.attrs_json, '$.indexSkipped') = 'text'
25768
+ ELSE max(CASE WHEN t.id IS NULL OR t.valid_to IS NOT NULL THEN 1 ELSE 0 END) = 0 END AS complete
25769
+ FROM nodes f
25770
+ LEFT JOIN edges e ON e.from_id = f.id AND e.valid_to IS NULL AND e.type IN ('DEFINES','CONTAINS')
25771
+ LEFT JOIN nodes t ON t.id = e.to_id
25772
+ WHERE f.id IN (${ids.map(() => "?").join(",")}) GROUP BY f.id
25773
+ `).all(...ids);
25774
+ return rows.map((row) => ({ ...row, complete: row.complete === 1 }));
25775
+ }
25746
25776
  /** Forward-migrate the on-disk schema to the current SCHEMA_VERSION. */
25747
25777
  migrate(from) {
25748
25778
  this.db.transaction(() => {
@@ -28833,11 +28863,13 @@ function buildSnapshot(opts) {
28833
28863
  const stillOpen = problems.filter((p) => p.attrs["resolvedAt"] == null);
28834
28864
  const allProblems = stillOpen.filter((p) => !isConstraintProblem(p));
28835
28865
  allProblems.sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt);
28836
- const recent = allProblems.slice(0, 8).map((p) => ({
28866
+ const openPool = allProblems.slice(0, RECENT_OPEN * SCOPE_POOL_FACTOR).map((p) => ({
28837
28867
  node: p,
28838
28868
  anchors: opts.store.outEdges(p.id, ["MANIFESTED_IN", "EVIDENCED_BY"])
28839
28869
  }));
28840
- const recentConstraints = stillOpen.filter((p) => isConstraintProblem(p)).sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, 3);
28870
+ const recent = openPool.slice(0, RECENT_OPEN);
28871
+ const constraintPool = stillOpen.filter((p) => isConstraintProblem(p)).sort((a, b) => b.lastUpdatedAt - a.lastUpdatedAt).slice(0, RECENT_CONSTRAINTS * SCOPE_POOL_FACTOR);
28872
+ const recentConstraints = constraintPool.slice(0, RECENT_CONSTRAINTS);
28841
28873
  const regressions = problems.filter((p) => p.attrs["resolvedAt"] == null && p.attrs["mergedInto"] === void 0).flatMap(
28842
28874
  (open) => opts.store.outEdges(open.id, ["REGRESSION_OF"]).flatMap((e) => {
28843
28875
  const closed = opts.store.getNode(e.to);
@@ -28850,11 +28882,12 @@ function buildSnapshot(opts) {
28850
28882
  })
28851
28883
  ).sort((a, b) => Number(b.closed.attrs["recurredAt"] ?? 0) - Number(a.closed.attrs["recurredAt"] ?? 0)).slice(0, 2);
28852
28884
  const regressionClosedIds = new Set(regressions.map((r) => r.closed.id));
28853
- const recentResolved = problems.filter((p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null).filter((p) => !regressionClosedIds.has(p.id)).sort((a, b) => Number(b.attrs["resolvedAt"] ?? 0) - Number(a.attrs["resolvedAt"] ?? 0)).slice(0, 4).map((p) => {
28885
+ const resolvedPool = problems.filter((p) => p.attrs["resolvedAt"] != null && p.attrs["resolvedAs"] == null).filter((p) => !regressionClosedIds.has(p.id)).sort((a, b) => Number(b.attrs["resolvedAt"] ?? 0) - Number(a.attrs["resolvedAt"] ?? 0)).slice(0, RECENT_RESOLVED * SCOPE_POOL_FACTOR).map((p) => {
28854
28886
  const solEdge = opts.store.outEdges(p.id, ["SOLVED_BY"])[0];
28855
28887
  const solution = solEdge ? opts.store.getNode(solEdge.to) ?? void 0 : void 0;
28856
28888
  return { node: p, ...solution ? { solution } : {} };
28857
28889
  });
28890
+ const recentResolved = resolvedPool.slice(0, RECENT_RESOLVED);
28858
28891
  const motifs = [
28859
28892
  ...opts.store.findNodesByLabel("Pattern"),
28860
28893
  ...opts.store.findNodesByLabel("Technique"),
@@ -28932,9 +28965,14 @@ function buildSnapshot(opts) {
28932
28965
  pendingEnrichment,
28933
28966
  skills: opts.skills ?? [],
28934
28967
  needsRevisit,
28968
+ scopePool: { open: openPool, resolved: resolvedPool, constraints: constraintPool },
28935
28969
  generatedAt: (opts.now ?? /* @__PURE__ */ new Date()).toISOString()
28936
28970
  };
28937
28971
  }
28972
+ var RECENT_OPEN = 8;
28973
+ var RECENT_RESOLVED = 4;
28974
+ var RECENT_CONSTRAINTS = 3;
28975
+ var SCOPE_POOL_FACTOR = 3;
28938
28976
  function sliceForFile(store2, relPath, atCommit) {
28939
28977
  const candidates = store2.findNodesByLabel("File");
28940
28978
  const file2 = candidates.find((n) => n.description === relPath);
@@ -29110,48 +29148,30 @@ function listSourceFiles(root) {
29110
29148
  return gitSourceFiles(root) ?? walkSource(root);
29111
29149
  }
29112
29150
  var SCAN_YIELD_EVERY = 100;
29113
- var LIVENESS_CHUNK = 4e3;
29114
29151
  async function findStaleFiles(store2, rootPath, workspaceId) {
29115
29152
  const stale = [];
29116
- const pending = [];
29117
- const allTargets = [];
29153
+ let pending = [];
29154
+ const check2 = () => {
29155
+ const states = new Map(store2.fileIndexStates(pending.map((f) => f.id)).map((s) => [s.id, s]));
29156
+ for (const f of pending) {
29157
+ const state = states.get(f.id);
29158
+ if (!state || state.validTo !== null || state.lastUpdatedAt < f.mtimeMs || !state.complete) stale.push(f.abs);
29159
+ }
29160
+ pending = [];
29161
+ };
29118
29162
  let scanned = 0;
29119
29163
  for (const abs of listSourceFiles(rootPath)) {
29120
- if (++scanned % SCAN_YIELD_EVERY === 0) await new Promise((r) => setImmediate(r));
29121
- let mtimeMs;
29122
29164
  try {
29123
- mtimeMs = statSync3(abs).mtimeMs;
29165
+ const rel = relative2(rootPath, abs).split(sep2).join("/");
29166
+ pending.push({ abs, id: fileNodeId(workspaceId, rel), mtimeMs: statSync3(abs).mtimeMs });
29124
29167
  } catch {
29125
- continue;
29126
29168
  }
29127
- const rel = relative2(rootPath, abs).split(sep2).join("/");
29128
- const fnode = store2.getNode(fileNodeId(workspaceId, rel));
29129
- if (!fnode) {
29130
- stale.push(abs);
29131
- } else if ((fnode.validTo ?? null) !== null) {
29132
- stale.push(abs);
29133
- } else if (fnode.lastUpdatedAt < mtimeMs) {
29134
- stale.push(abs);
29135
- } else {
29136
- const edges = store2.outEdges(fnode.id, ["DEFINES", "CONTAINS"]);
29137
- if (edges.length === 0) {
29138
- const converged = fnode.attrs["symbolCount"] === 0 || typeof fnode.attrs["indexSkipped"] === "string";
29139
- if (!converged) stale.push(abs);
29140
- } else {
29141
- const targets = edges.map((e) => e.to);
29142
- pending.push({ abs, targets });
29143
- allTargets.push(...targets);
29144
- }
29169
+ if (++scanned % SCAN_YIELD_EVERY === 0) {
29170
+ check2();
29171
+ await new Promise((resolve) => setImmediate(resolve));
29145
29172
  }
29146
29173
  }
29147
- const live = /* @__PURE__ */ new Set();
29148
- for (let i2 = 0; i2 < allTargets.length; i2 += LIVENESS_CHUNK) {
29149
- for (const id of store2.liveNodeIds(allTargets.slice(i2, i2 + LIVENESS_CHUNK))) live.add(id);
29150
- if (i2 + LIVENESS_CHUNK < allTargets.length) await new Promise((r) => setImmediate(r));
29151
- }
29152
- for (const { abs, targets } of pending) {
29153
- if (targets.some((id) => !live.has(id))) stale.push(abs);
29154
- }
29174
+ check2();
29155
29175
  return stale;
29156
29176
  }
29157
29177
  async function reconcileStaleFiles(store2, rootPath, workspaceId) {
@@ -29566,6 +29586,9 @@ port.on("message", (msg) => {
29566
29586
  });
29567
29587
  function runPass(msg) {
29568
29588
  switch (msg.kind) {
29589
+ case "prepareStore":
29590
+ return void 0;
29591
+ // opening the connection above completed all preparation
29569
29592
  case "semanticMaintenance":
29570
29593
  return runWorkspaceMaintenance(store, init2.workspaceId);
29571
29594
  case "liveness":