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

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.
@@ -16143,6 +16143,9 @@ CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
16143
16143
  -- incremental reindex can fetch just the changed files' symbols instead of
16144
16144
  -- scanning every versioned node. Partial: only live rows are ever queried.
16145
16145
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
16146
+ -- Live rows by name, for looking up the few symbols a statement mentions
16147
+ -- instead of hydrating every code symbol per minted problem.
16148
+ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
16146
16149
  `;
16147
16150
  function encodeEmbedding(emb) {
16148
16151
  if (!emb || emb.length === 0) return null;
@@ -16957,6 +16960,21 @@ var SqliteGraphStore = class {
16957
16960
  /** Live nodes whose attrs.relPath is one of `relPaths`. Uses the nodes_relpath
16958
16961
  * expression index so the incremental reindex fetches only the changed files'
16959
16962
  * symbols instead of scanning every versioned node. */
16963
+ /** Live nodes of `labels` whose description is exactly one of `names`, via
16964
+ * the nodes_live_description index. Chunked under the bound-parameter cap. */
16965
+ liveNodesByDescription(names, labels) {
16966
+ if (names.length === 0 || labels.length === 0) return [];
16967
+ const out = [];
16968
+ const labelPh = labels.map(() => "?").join(",");
16969
+ for (let i = 0; i < names.length; i += 400) {
16970
+ const chunk = names.slice(i, i + 400);
16971
+ const rows = this.db.prepare(
16972
+ `SELECT * FROM nodes WHERE valid_to IS NULL AND description IN (${chunk.map(() => "?").join(",")}) AND label IN (${labelPh})`
16973
+ ).all(...chunk, ...labels);
16974
+ for (const r of rows) out.push(rowToNode(r));
16975
+ }
16976
+ return out;
16977
+ }
16960
16978
  nodesByRelPath(relPaths) {
16961
16979
  if (relPaths.length === 0) return [];
16962
16980
  const placeholders = relPaths.map(() => "?").join(",");
package/errata.mjs CHANGED
@@ -17560,6 +17560,9 @@ CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
17560
17560
  -- incremental reindex can fetch just the changed files' symbols instead of
17561
17561
  -- scanning every versioned node. Partial: only live rows are ever queried.
17562
17562
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
17563
+ -- Live rows by name, for looking up the few symbols a statement mentions
17564
+ -- instead of hydrating every code symbol per minted problem.
17565
+ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
17563
17566
  `;
17564
17567
  SqliteGraphStore = class {
17565
17568
  db;
@@ -18317,6 +18320,21 @@ CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.re
18317
18320
  /** Live nodes whose attrs.relPath is one of `relPaths`. Uses the nodes_relpath
18318
18321
  * expression index so the incremental reindex fetches only the changed files'
18319
18322
  * symbols instead of scanning every versioned node. */
18323
+ /** Live nodes of `labels` whose description is exactly one of `names`, via
18324
+ * the nodes_live_description index. Chunked under the bound-parameter cap. */
18325
+ liveNodesByDescription(names, labels) {
18326
+ if (names.length === 0 || labels.length === 0) return [];
18327
+ const out2 = [];
18328
+ const labelPh = labels.map(() => "?").join(",");
18329
+ for (let i2 = 0; i2 < names.length; i2 += 400) {
18330
+ const chunk = names.slice(i2, i2 + 400);
18331
+ const rows = this.db.prepare(
18332
+ `SELECT * FROM nodes WHERE valid_to IS NULL AND description IN (${chunk.map(() => "?").join(",")}) AND label IN (${labelPh})`
18333
+ ).all(...chunk, ...labels);
18334
+ for (const r of rows) out2.push(rowToNode(r));
18335
+ }
18336
+ return out2;
18337
+ }
18320
18338
  nodesByRelPath(relPaths) {
18321
18339
  if (relPaths.length === 0) return [];
18322
18340
  const placeholders = relPaths.map(() => "?").join(",");
@@ -19303,6 +19321,33 @@ function buildSymbolIndex(store) {
19303
19321
  }
19304
19322
  return idx;
19305
19323
  }
19324
+ function symbolCandidatesInText(text) {
19325
+ const out2 = /* @__PURE__ */ new Set();
19326
+ for (const run3 of text.match(/[\w$.]+/g) ?? []) {
19327
+ const segs = run3.split(".");
19328
+ for (let i2 = 0; i2 < segs.length; i2++) {
19329
+ if (!segs[i2]) continue;
19330
+ let name2 = "";
19331
+ for (let j = i2; j < segs.length && segs[j]; j++) {
19332
+ name2 = j === i2 ? segs[j] : `${name2}.${segs[j]}`;
19333
+ if (isShapedSymbol(name2)) out2.add(name2);
19334
+ }
19335
+ }
19336
+ }
19337
+ return [...out2];
19338
+ }
19339
+ function symbolIndexForText(store, text) {
19340
+ const counts = /* @__PURE__ */ new Map();
19341
+ for (const n of store.liveNodesByDescription(symbolCandidatesInText(text), SYMBOL_LABELS)) {
19342
+ const name2 = n.description.trim();
19343
+ const list = counts.get(name2);
19344
+ if (list) list.push(n);
19345
+ else counts.set(name2, [n]);
19346
+ }
19347
+ const idx = /* @__PURE__ */ new Map();
19348
+ for (const [name2, nodes] of counts) if (nodes.length === 1) idx.set(name2, nodes[0]);
19349
+ return idx;
19350
+ }
19306
19351
  function matchSymbolsInText(text, index) {
19307
19352
  const out2 = [];
19308
19353
  for (const [name2, node2] of index) {
@@ -19315,19 +19360,9 @@ function matchSymbolsInText(text, index) {
19315
19360
  function linkProblemToSymbols(store, problemId, text, ts, index) {
19316
19361
  const p = store.getNode(problemId);
19317
19362
  if (!p || p.label !== "Problem") return [];
19318
- const idx = index ?? buildSymbolIndex(store);
19363
+ const idx = index ?? symbolIndexForText(store, text);
19319
19364
  if (idx.size === 0) return [];
19320
- let fileByPath = null;
19321
- const fileFor2 = (relPath) => {
19322
- if (!fileByPath) {
19323
- fileByPath = /* @__PURE__ */ new Map();
19324
- for (const f of store.findNodesByLabel("File")) {
19325
- const rp = String(f.attrs["relPath"] ?? f.description ?? "");
19326
- if (rp) fileByPath.set(rp, f);
19327
- }
19328
- }
19329
- return fileByPath.get(relPath) ?? null;
19330
- };
19365
+ const fileFor2 = (relPath) => store.nodesByRelPath([relPath]).find((f) => f.label === "File") ?? null;
19331
19366
  const already = new Set(store.outEdges(problemId, ["ANCHORED_AT"]).map((e) => e.to));
19332
19367
  const linked = [];
19333
19368
  for (const sym of matchSymbolsInText(text, idx)) {
@@ -24052,6 +24087,8 @@ __export(src_exports2, {
24052
24087
  stampObservedOs: () => stampObservedOs,
24053
24088
  statementIdentityKey: () => statementIdentityKey,
24054
24089
  staticConductance: () => staticConductance,
24090
+ symbolCandidatesInText: () => symbolCandidatesInText,
24091
+ symbolIndexForText: () => symbolIndexForText,
24055
24092
  toolNodeId: () => toolNodeId,
24056
24093
  toolPriorsFor: () => toolPriorsFor,
24057
24094
  triageOf: () => triageOf,
@@ -78478,7 +78515,7 @@ function createWatchBreaker(deps) {
78478
78515
  }
78479
78516
 
78480
78517
  // src/engine.ts
78481
- var DAEMON_VERSION = true ? "2.0.6-dev.2627" : "2.0.0-alpha.0";
78518
+ var DAEMON_VERSION = true ? "2.0.6-dev.2635" : "2.0.0-alpha.0";
78482
78519
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
78483
78520
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
78484
78521
  var GIT_OP_MUTE_MS = 4e3;
@@ -82211,26 +82248,40 @@ function classifyAnchorVersion(cloudAttrs, localSymbol) {
82211
82248
  return seen === have ? "current" : "pre-change";
82212
82249
  }
82213
82250
  function buildReverseSymbolMap(opts) {
82251
+ return collectFirstWins(reverseSymbolEntries(opts));
82252
+ }
82253
+ function* reverseSymbolEntries(opts) {
82214
82254
  const { store, salt, projectId } = opts;
82215
- const reverse = /* @__PURE__ */ new Map();
82216
82255
  for (const kind of ANCHOR_SYMBOL_KINDS2) {
82217
82256
  for (const n of store.findNodeIdentitiesByLabel(kind)) {
82218
82257
  const { relPath, qname } = n;
82219
82258
  if (relPath === null || qname === null) continue;
82220
82259
  const sym = projectSymbolId(salt, projectId, relPath, qname, kind);
82221
- if (!reverse.has(sym)) reverse.set(sym, n.id);
82222
- const twin = projectSaltedNodeId(sym, salt);
82223
- if (!reverse.has(twin)) reverse.set(twin, n.id);
82260
+ yield [sym, n.id];
82261
+ yield [projectSaltedNodeId(sym, salt), n.id];
82224
82262
  }
82225
82263
  }
82226
82264
  for (const n of store.findNodeIdentitiesByLabel("File")) {
82227
82265
  if (n.relPath === null) continue;
82228
82266
  const sym = projectSymbolId(salt, projectId, n.relPath, FILE_ANCHOR_QNAME, "File");
82229
- if (!reverse.has(sym)) reverse.set(sym, n.id);
82230
- const twin = projectSaltedNodeId(sym, salt);
82231
- if (!reverse.has(twin)) reverse.set(twin, n.id);
82267
+ yield [sym, n.id];
82268
+ yield [projectSaltedNodeId(sym, salt), n.id];
82269
+ }
82270
+ }
82271
+ function collectFirstWins(entries) {
82272
+ const m = /* @__PURE__ */ new Map();
82273
+ for (const [k, v] of entries) if (!m.has(k)) m.set(k, v);
82274
+ return m;
82275
+ }
82276
+ var MAP_BUILD_BATCH = 2e3;
82277
+ async function collectFirstWinsYielding(entries) {
82278
+ const m = /* @__PURE__ */ new Map();
82279
+ let i2 = 0;
82280
+ for (const [k, v] of entries) {
82281
+ if (++i2 % MAP_BUILD_BATCH === 0) await new Promise((r) => setImmediate(r));
82282
+ if (!m.has(k)) m.set(k, v);
82232
82283
  }
82233
- return reverse;
82284
+ return m;
82234
82285
  }
82235
82286
  function anchorConfidence(attrs) {
82236
82287
  const v = typeof attrs["extractionConfidence"] === "number" ? attrs["extractionConfidence"] : attrs["confidence"];
@@ -82245,15 +82296,25 @@ var ANCHOR_SOURCE_KINDS = [
82245
82296
  "AntiPattern"
82246
82297
  ];
82247
82298
  function buildOwnKnowledgeMap(opts) {
82299
+ return new Map(ownKnowledgeEntries(opts));
82300
+ }
82301
+ function* ownKnowledgeEntries(opts) {
82248
82302
  const { store, salt } = opts;
82249
- const own = /* @__PURE__ */ new Map();
82250
82303
  for (const kind of ANCHOR_SOURCE_KINDS) {
82251
82304
  for (const n of store.findNodeIdentitiesByLabel(kind)) {
82252
82305
  if (n.id.startsWith("projn_")) continue;
82253
- own.set(projectSaltedNodeId(n.id, salt), n.id);
82306
+ yield [projectSaltedNodeId(n.id, salt), n.id];
82254
82307
  }
82255
82308
  }
82256
- return own;
82309
+ }
82310
+ async function collectLastWinsYielding(entries) {
82311
+ const m = /* @__PURE__ */ new Map();
82312
+ let i2 = 0;
82313
+ for (const [k, v] of entries) {
82314
+ if (++i2 % MAP_BUILD_BATCH === 0) await new Promise((r) => setImmediate(r));
82315
+ m.set(k, v);
82316
+ }
82317
+ return m;
82257
82318
  }
82258
82319
  function resolveAnchorSource(store, from, own) {
82259
82320
  if (store.getNode(from)) return from;
@@ -82266,30 +82327,27 @@ function existingAnchor(store, from, to) {
82266
82327
  var reverseCache = /* @__PURE__ */ new WeakMap();
82267
82328
  var ownCache = /* @__PURE__ */ new WeakMap();
82268
82329
  function cachedMap(cache2, store, key, build) {
82269
- let refreshed = false;
82330
+ let rebuild = null;
82270
82331
  const put = () => {
82271
- const map2 = build();
82272
- cache2.set(store, { key, map: map2 });
82273
- return map2;
82332
+ rebuild ??= build().then((map2) => {
82333
+ cache2.set(store, { key, map: map2 });
82334
+ return map2;
82335
+ });
82336
+ return rebuild;
82274
82337
  };
82275
82338
  return {
82276
- get: () => {
82339
+ get: async () => {
82277
82340
  const hit = cache2.get(store);
82278
82341
  if (hit && hit.key === key) return hit.map;
82279
- refreshed = true;
82280
82342
  return put();
82281
82343
  },
82282
- refresh: () => {
82283
- if (refreshed) return cache2.get(store)?.map ?? put();
82284
- refreshed = true;
82285
- return put();
82286
- }
82344
+ refresh: () => put()
82287
82345
  };
82288
82346
  }
82289
- function lookupLive(store, m, id) {
82290
- const first = m.get().get(id);
82347
+ async function lookupLive(store, m, id) {
82348
+ const first = (await m.get()).get(id);
82291
82349
  if (first && isLive2(store, first)) return first;
82292
- const second = m.refresh().get(id);
82350
+ const second = (await m.refresh()).get(id);
82293
82351
  return second && isLive2(store, second) ? second : null;
82294
82352
  }
82295
82353
  function isLive2(store, id) {
@@ -82370,8 +82428,13 @@ async function syncProjectAnchors(opts) {
82370
82428
  if (!profile.projectId) return { synced: false, reason: "no-project" };
82371
82429
  const projectId = profile.projectId;
82372
82430
  const key = `${projectId}|${salt}`;
82373
- const reverse = cachedMap(reverseCache, store, key, () => buildReverseSymbolMap({ store, salt, projectId }));
82374
- const own = cachedMap(ownCache, store, salt, () => buildOwnKnowledgeMap({ store, salt }));
82431
+ const reverse = cachedMap(
82432
+ reverseCache,
82433
+ store,
82434
+ key,
82435
+ () => collectFirstWinsYielding(reverseSymbolEntries({ store, salt, projectId }))
82436
+ );
82437
+ const own = cachedMap(ownCache, store, salt, () => collectLastWinsYielding(ownKnowledgeEntries({ store, salt })));
82375
82438
  const limit = opts.limit ?? SYNC_PAGE;
82376
82439
  const maxPages = opts.maxPages ?? SYNC_MAX_PAGES;
82377
82440
  let anchors = 0;
@@ -82383,19 +82446,19 @@ async function syncProjectAnchors(opts) {
82383
82446
  for (let page = 0; page < maxPages; page++) {
82384
82447
  const res = await cloud.getProjectAnchors(projectId, cursor, limit);
82385
82448
  if (res.anchors.length === 0) break;
82386
- if (reverse.get().size === 0 && reverse.refresh().size === 0) {
82449
+ if ((await reverse.get()).size === 0 && (await reverse.refresh()).size === 0) {
82387
82450
  return { synced: false, reason: "no-local-symbols" };
82388
82451
  }
82389
82452
  const byId = new Map(res.nodes.map((n) => [n.id, n]));
82390
82453
  const now = Date.now();
82391
82454
  for (const a of res.anchors) {
82392
82455
  if (a.type !== "ANCHORED_AT") continue;
82393
- const localTo = lookupLive(store, reverse, a.toSymbolId);
82456
+ const localTo = await lookupLive(store, reverse, a.toSymbolId);
82394
82457
  if (!localTo) {
82395
82458
  dropped++;
82396
82459
  continue;
82397
82460
  }
82398
- let from = store.getNode(a.from) ? a.from : lookupLive(store, own, a.from);
82461
+ let from = store.getNode(a.from) ? a.from : await lookupLive(store, own, a.from);
82399
82462
  if (!from) {
82400
82463
  const node2 = byId.get(a.from);
82401
82464
  if (!node2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.2627",
3
+ "version": "2.0.6-dev.2635",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -24525,6 +24525,9 @@ CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
24525
24525
  -- incremental reindex can fetch just the changed files' symbols instead of
24526
24526
  -- scanning every versioned node. Partial: only live rows are ever queried.
24527
24527
  CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
24528
+ -- Live rows by name, for looking up the few symbols a statement mentions
24529
+ -- instead of hydrating every code symbol per minted problem.
24530
+ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
24528
24531
  `;
24529
24532
  function encodeEmbedding(emb) {
24530
24533
  if (!emb || emb.length === 0) return null;
@@ -25339,6 +25342,21 @@ var SqliteGraphStore = class {
25339
25342
  /** Live nodes whose attrs.relPath is one of `relPaths`. Uses the nodes_relpath
25340
25343
  * expression index so the incremental reindex fetches only the changed files'
25341
25344
  * symbols instead of scanning every versioned node. */
25345
+ /** Live nodes of `labels` whose description is exactly one of `names`, via
25346
+ * the nodes_live_description index. Chunked under the bound-parameter cap. */
25347
+ liveNodesByDescription(names, labels) {
25348
+ if (names.length === 0 || labels.length === 0) return [];
25349
+ const out2 = [];
25350
+ const labelPh = labels.map(() => "?").join(",");
25351
+ for (let i2 = 0; i2 < names.length; i2 += 400) {
25352
+ const chunk = names.slice(i2, i2 + 400);
25353
+ const rows = this.db.prepare(
25354
+ `SELECT * FROM nodes WHERE valid_to IS NULL AND description IN (${chunk.map(() => "?").join(",")}) AND label IN (${labelPh})`
25355
+ ).all(...chunk, ...labels);
25356
+ for (const r of rows) out2.push(rowToNode(r));
25357
+ }
25358
+ return out2;
25359
+ }
25342
25360
  nodesByRelPath(relPaths) {
25343
25361
  if (relPaths.length === 0) return [];
25344
25362
  const placeholders = relPaths.map(() => "?").join(",");