@inerrata-corporation/errata 2.0.21-dev.3918 → 2.0.21-dev.3930

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.
@@ -16810,6 +16810,10 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
16810
16810
  CREATE INDEX IF NOT EXISTS nodes_command_signature ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL;
16811
16811
  CREATE INDEX IF NOT EXISTS nodes_diag_command ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL;
16812
16812
  CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL;
16813
+ -- Local nodes by the cloud twin their upload receipt recorded. Every burst /
16814
+ -- search fusion folds the Network copy of the caller's own knowledge back onto
16815
+ -- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
16816
+ CREATE INDEX IF NOT EXISTS nodes_cloud_node_id ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL;
16813
16817
  -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
16814
16818
  -- tool response (the enrichment piggyback), so it must not parse every
16815
16819
  -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
@@ -17779,6 +17783,19 @@ var SqliteGraphStore = class {
17779
17783
  ).get(attr, `%${marker}%`, limit);
17780
17784
  return { count: Number(r.count), total: Number(r.total) };
17781
17785
  }
17786
+ liveNodeIdsByCloudNodeId(cloudIds) {
17787
+ const out = /* @__PURE__ */ new Map();
17788
+ const CHUNK = 900;
17789
+ for (let i = 0; i < cloudIds.length; i += CHUNK) {
17790
+ const slice = cloudIds.slice(i, i + CHUNK);
17791
+ if (slice.length === 0) continue;
17792
+ const rows = this.db.prepare(
17793
+ `SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
17794
+ ).all(...slice);
17795
+ for (const r of rows) out.set(r.cloud, [...out.get(r.cloud) ?? [], r.id]);
17796
+ }
17797
+ return out;
17798
+ }
17782
17799
  liveNodeIds(ids) {
17783
17800
  const live = /* @__PURE__ */ new Set();
17784
17801
  const CHUNK = 900;
@@ -17840,7 +17857,10 @@ var SqliteGraphStore = class {
17840
17857
  if (relPaths.length === 0) return [];
17841
17858
  const placeholders = relPaths.map(() => "?").join(",");
17842
17859
  const rows = this.db.prepare(
17843
- `SELECT * FROM nodes WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
17860
+ // INDEXED BY: unforced, the planner took nodes_live_page_rank and
17861
+ // json-parsed every live row — 11–25s per reindexed file pair on the
17862
+ // live store vs 7ms here (2026-10-08).
17863
+ `SELECT * FROM nodes INDEXED BY nodes_relpath WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
17844
17864
  ).all(...relPaths);
17845
17865
  return rows.map(rowToNode);
17846
17866
  }
package/errata.mjs CHANGED
@@ -19568,6 +19568,10 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
19568
19568
  CREATE INDEX IF NOT EXISTS nodes_command_signature ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL;
19569
19569
  CREATE INDEX IF NOT EXISTS nodes_diag_command ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL;
19570
19570
  CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL;
19571
+ -- Local nodes by the cloud twin their upload receipt recorded. Every burst /
19572
+ -- search fusion folds the Network copy of the caller's own knowledge back onto
19573
+ -- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
19574
+ CREATE INDEX IF NOT EXISTS nodes_cloud_node_id ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL;
19571
19575
  -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
19572
19576
  -- tool response (the enrichment piggyback), so it must not parse every
19573
19577
  -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
@@ -20480,6 +20484,19 @@ CREATE INDEX IF NOT EXISTS nodes_enrichment_pending ON nodes(json_extract(attrs_
20480
20484
  ).get(attr, `%${marker}%`, limit);
20481
20485
  return { count: Number(r.count), total: Number(r.total) };
20482
20486
  }
20487
+ liveNodeIdsByCloudNodeId(cloudIds) {
20488
+ const out2 = /* @__PURE__ */ new Map();
20489
+ const CHUNK = 900;
20490
+ for (let i2 = 0; i2 < cloudIds.length; i2 += CHUNK) {
20491
+ const slice = cloudIds.slice(i2, i2 + CHUNK);
20492
+ if (slice.length === 0) continue;
20493
+ const rows = this.db.prepare(
20494
+ `SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
20495
+ ).all(...slice);
20496
+ for (const r of rows) out2.set(r.cloud, [...out2.get(r.cloud) ?? [], r.id]);
20497
+ }
20498
+ return out2;
20499
+ }
20483
20500
  liveNodeIds(ids) {
20484
20501
  const live3 = /* @__PURE__ */ new Set();
20485
20502
  const CHUNK = 900;
@@ -20541,7 +20558,10 @@ CREATE INDEX IF NOT EXISTS nodes_enrichment_pending ON nodes(json_extract(attrs_
20541
20558
  if (relPaths.length === 0) return [];
20542
20559
  const placeholders = relPaths.map(() => "?").join(",");
20543
20560
  const rows = this.db.prepare(
20544
- `SELECT * FROM nodes WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
20561
+ // INDEXED BY: unforced, the planner took nodes_live_page_rank and
20562
+ // json-parsed every live row — 11–25s per reindexed file pair on the
20563
+ // live store vs 7ms here (2026-10-08).
20564
+ `SELECT * FROM nodes INDEXED BY nodes_relpath WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
20545
20565
  ).all(...relPaths);
20546
20566
  return rows.map(rowToNode);
20547
20567
  }
@@ -34982,33 +35002,90 @@ function collectiveScore(hit, cap) {
34982
35002
  function mergeDualBurst(local, cloud, opts = {}) {
34983
35003
  const boost = opts.corroborationBoost ?? 0.15;
34984
35004
  const cap = opts.collectiveScoreCap ?? 0.5;
34985
- const cloudById = new Map(cloud.map((c) => [c.id, c]));
34986
- const localIds = new Set(local.map((l) => l.id));
35005
+ const twins = opts.twins;
35006
+ const cloudIdsOf = (id) => twins?.cloudIdsOf(id) ?? [];
35007
+ const seedSet = /* @__PURE__ */ new Set();
35008
+ if (opts.seedId) {
35009
+ seedSet.add(opts.seedId);
35010
+ for (const c of cloudIdsOf(opts.seedId)) seedSet.add(c);
35011
+ }
35012
+ const isSeedTwin = (id) => seedSet.has(id) || opts.seedId != null && twins?.localOf(id)?.id === opts.seedId;
34987
35013
  const nodes = [];
35014
+ const byId = /* @__PURE__ */ new Map();
35015
+ const owner = /* @__PURE__ */ new Map();
34988
35016
  let corroboratedCount = 0;
34989
- for (const l of local) {
34990
- const twin = cloudById.get(l.id);
34991
- const corroborated = twin != null;
34992
- if (corroborated) corroboratedCount++;
34993
- nodes.push({
35017
+ let collectiveCount = 0;
35018
+ const keptLocal = local.filter((l) => !isSeedTwin(l.id)).sort((a, b) => b.score - a.score);
35019
+ const recordedBy = /* @__PURE__ */ new Map();
35020
+ for (const l of keptLocal) {
35021
+ for (const c of cloudIdsOf(l.id)) if (c !== l.id && !recordedBy.has(c)) recordedBy.set(c, l.id);
35022
+ }
35023
+ const copies = [];
35024
+ for (const l of keptLocal) {
35025
+ if (recordedBy.has(l.id)) {
35026
+ copies.push(l);
35027
+ continue;
35028
+ }
35029
+ const n = {
34994
35030
  id: l.id,
34995
35031
  label: l.node.label,
34996
35032
  name: l.node.description,
34997
- score: corroborated ? l.score * (1 + boost) : l.score,
35033
+ score: l.score,
34998
35034
  hops: l.hops,
34999
- provenance: corroborated ? "corroborated" : "local",
35000
- ...twin?.usageCount != null ? { usageCount: twin.usageCount } : {},
35001
- ...twin?.rel ? { rel: twin.rel } : {},
35002
- ...twin?.community ? { community: twin.community } : {},
35035
+ provenance: "local",
35003
35036
  why: l.components,
35004
35037
  ...l.via ? { via: l.via } : {}
35005
- });
35038
+ };
35039
+ nodes.push(n);
35040
+ byId.set(n.id, n);
35041
+ for (const c of cloudIdsOf(l.id)) if (!owner.has(c)) owner.set(c, n);
35042
+ }
35043
+ for (const l of copies) {
35044
+ const host = byId.get(recordedBy.get(l.id));
35045
+ if (host) addTwin(host, l.id);
35006
35046
  }
35007
- let collectiveCount = 0;
35008
35047
  for (const c of cloud) {
35009
- if (localIds.has(c.id)) continue;
35048
+ if (isSeedTwin(c.id)) continue;
35049
+ const recorded = owner.get(c.id);
35050
+ if (recorded) {
35051
+ foldSelfUpload(recorded, c);
35052
+ continue;
35053
+ }
35054
+ const exact = byId.get(c.id);
35055
+ if (exact && exact.provenance !== "collective") {
35056
+ absorb(exact, c);
35057
+ continue;
35058
+ }
35059
+ const mine = twins?.localOf(c.id);
35060
+ if (mine && !isSeedTwin(mine.id)) {
35061
+ const existing = byId.get(mine.id);
35062
+ if (existing) {
35063
+ if (existing.provenance === "collective") {
35064
+ existing.provenance = "local";
35065
+ collectiveCount--;
35066
+ }
35067
+ owner.set(c.id, existing);
35068
+ absorb(existing, c);
35069
+ continue;
35070
+ }
35071
+ const n2 = {
35072
+ id: mine.id,
35073
+ label: mine.label,
35074
+ name: mine.description,
35075
+ score: collectiveScore(c, cap),
35076
+ hops: 1,
35077
+ provenance: "local"
35078
+ };
35079
+ nodes.push(n2);
35080
+ byId.set(n2.id, n2);
35081
+ for (const cid of cloudIdsOf(mine.id)) if (!owner.has(cid)) owner.set(cid, n2);
35082
+ owner.set(c.id, n2);
35083
+ absorb(n2, c);
35084
+ continue;
35085
+ }
35086
+ if (exact) continue;
35010
35087
  collectiveCount++;
35011
- nodes.push({
35088
+ const n = {
35012
35089
  id: c.id,
35013
35090
  label: c.label,
35014
35091
  name: c.description,
@@ -35020,10 +35097,53 @@ function mergeDualBurst(local, cloud, opts = {}) {
35020
35097
  ...c.rel ? { rel: c.rel } : {},
35021
35098
  ...c.community ? { community: c.community } : {},
35022
35099
  ...c.via ? { via: c.via } : {}
35023
- });
35100
+ };
35101
+ nodes.push(n);
35102
+ byId.set(n.id, n);
35103
+ }
35104
+ for (const n of nodes) {
35105
+ if (!n.via) continue;
35106
+ const parent = canonicalId(n.via.parentId);
35107
+ if (parent === n.id) delete n.via;
35108
+ else if (parent !== n.via.parentId) n.via = { ...n.via, parentId: parent };
35024
35109
  }
35025
35110
  nodes.sort((a, b) => b.score - a.score);
35026
35111
  return { nodes, collectiveCount, corroboratedCount };
35112
+ function canonicalId(id) {
35113
+ if (opts.seedId && isSeedTwin(id)) return opts.seedId;
35114
+ return owner.get(id)?.id ?? recordedBy.get(id) ?? twins?.localOf(id)?.id ?? id;
35115
+ }
35116
+ function absorb(n, c) {
35117
+ if (c.id === n.id && cloudIdsOf(n.id).length === 0) corroborate(n, c);
35118
+ else foldSelfUpload(n, c);
35119
+ }
35120
+ function corroborate(n, c) {
35121
+ if (n.provenance !== "corroborated") {
35122
+ corroboratedCount++;
35123
+ n.provenance = "corroborated";
35124
+ n.score = n.score * (1 + boost);
35125
+ delete n.uploaded;
35126
+ }
35127
+ attachCloudFields(n, c);
35128
+ }
35129
+ function foldSelfUpload(n, c) {
35130
+ addTwin(n, c.id);
35131
+ if ((c.usageCount ?? 0) >= 2) return corroborate(n, c);
35132
+ if (n.provenance === "local") n.uploaded = true;
35133
+ attachCloudFields(n, c);
35134
+ }
35135
+ function attachCloudFields(n, c) {
35136
+ if (c.usageCount != null && (n.usageCount ?? -1) < c.usageCount) n.usageCount = c.usageCount;
35137
+ if (c.rel && !n.rel) n.rel = c.rel;
35138
+ if (c.community && !n.community) n.community = c.community;
35139
+ const v = c.via;
35140
+ if (v && !n.via) n.via = v;
35141
+ }
35142
+ }
35143
+ function addTwin(n, id) {
35144
+ if (id === n.id) return;
35145
+ const t = n.twins ?? (n.twins = []);
35146
+ if (!t.includes(id)) t.push(id);
35027
35147
  }
35028
35148
  function packageNeighborhoodToCloudHits(pn) {
35029
35149
  const mk = (rel) => (r) => ({
@@ -35078,6 +35198,69 @@ var init_dual_burst = __esm({
35078
35198
  }
35079
35199
  });
35080
35200
 
35201
+ // src/twin-index.ts
35202
+ function recordedCloudIds(n) {
35203
+ if (n.attrs["source"] === "cloud") return [];
35204
+ const ids = [];
35205
+ const add = (v) => {
35206
+ if (typeof v === "string" && v.length > 0 && v !== n.id && !ids.includes(v)) ids.push(v);
35207
+ };
35208
+ add(n.attrs["cloudNodeId"]);
35209
+ const receipts = n.attrs["instanceReceipts"];
35210
+ if (receipts && typeof receipts === "object" && !Array.isArray(receipts)) {
35211
+ for (const r of Object.values(receipts)) {
35212
+ if (r && typeof r === "object") add(r["cloudNodeId"]);
35213
+ }
35214
+ }
35215
+ return ids;
35216
+ }
35217
+ function localTwinOf(store, cloudId) {
35218
+ return localTwinsOf(store, [cloudId]).get(cloudId) ?? null;
35219
+ }
35220
+ function localTwinsOf(store, cloudIds) {
35221
+ const out2 = /* @__PURE__ */ new Map();
35222
+ if (cloudIds.length === 0) return out2;
35223
+ for (const [cloudId, localIds] of store.liveNodeIdsByCloudNodeId(cloudIds)) {
35224
+ for (const id of localIds) {
35225
+ if (id === cloudId) continue;
35226
+ const n = store.getNode(id);
35227
+ if (!n || n.attrs["source"] === "cloud") continue;
35228
+ out2.set(cloudId, n);
35229
+ break;
35230
+ }
35231
+ }
35232
+ const rest2 = cloudIds.filter((id) => !out2.has(id));
35233
+ for (const id of rest2.length ? store.liveNodeIds(rest2) : []) {
35234
+ const n = store.getNode(id);
35235
+ if (n && recordedCloudIds(n).length > 0) out2.set(id, n);
35236
+ }
35237
+ return out2;
35238
+ }
35239
+ function storeTwinIndex(store, cloudIds) {
35240
+ const reverse = localTwinsOf(store, [...new Set(cloudIds)]);
35241
+ const forward = /* @__PURE__ */ new Map();
35242
+ return {
35243
+ cloudIdsOf(localId) {
35244
+ let ids = forward.get(localId);
35245
+ if (!ids) {
35246
+ const n = store.getNode(localId);
35247
+ ids = n ? recordedCloudIds(n) : [];
35248
+ forward.set(localId, ids);
35249
+ }
35250
+ return ids;
35251
+ },
35252
+ localOf(cloudId) {
35253
+ const n = reverse.get(cloudId);
35254
+ return n ? { id: n.id, label: n.label, description: n.description } : void 0;
35255
+ }
35256
+ };
35257
+ }
35258
+ var init_twin_index = __esm({
35259
+ "src/twin-index.ts"() {
35260
+ "use strict";
35261
+ }
35262
+ });
35263
+
35081
35264
  // src/dual-augment.ts
35082
35265
  function isHardBridge(n) {
35083
35266
  return n.label === "Package" || n.label === "Language" || n.label === "Claim" && Number(n.attrs["abstractionLevel"] ?? 0) >= 2;
@@ -35098,6 +35281,8 @@ async function dualAugment(opts) {
35098
35281
  const n = seedNode && nid === seedNode.id ? seedNode : store.getNode(nid);
35099
35282
  return !!n && isHardBridge(n);
35100
35283
  });
35284
+ const seedTwinIds = seedNode ? recordedCloudIds(seedNode) : [];
35285
+ const cloudSeeds = [.../* @__PURE__ */ new Set([...seedTwinIds, ...hardIds])];
35101
35286
  const softText = queryText ?? (seedNode && BRIDGE_LABELS.has(seedNode.label) ? seedNode.description : void 0);
35102
35287
  const softQ = softText ? await generalizeSoft?.(softText) ?? generalizeSymbols(softText, lexiconsFor(store).symbols, 1) : void 0;
35103
35288
  if (seedNode?.label === "Package") {
@@ -35111,9 +35296,9 @@ async function dualAugment(opts) {
35111
35296
  })
35112
35297
  );
35113
35298
  }
35114
- if (hardIds.length > 0 || softQ) {
35299
+ if (cloudSeeds.length > 0 || softQ) {
35115
35300
  calls.push(
35116
- cloud.search({ stack: [], domains: [], kinds: [], seed: hardIds, ...softQ ? { q: softQ } : {}, limit, community: true, via: true }).then((res) => {
35301
+ cloud.search({ stack: [], domains: [], kinds: [], seed: cloudSeeds, ...softQ ? { q: softQ } : {}, limit, community: true, via: true }).then((res) => {
35117
35302
  cloudHits.push(...searchResultToCloudHits(res));
35118
35303
  })
35119
35304
  );
@@ -35134,9 +35319,18 @@ async function dualAugment(opts) {
35134
35319
  return { status: "offline" };
35135
35320
  }
35136
35321
  if (cloudHits.length === 0) return { status: "empty" };
35322
+ const deduped = dedupCloudHits(cloudHits);
35137
35323
  return {
35138
35324
  status: "merged",
35139
- merged: mergeDualBurst(localHits, dedupCloudHits(cloudHits)),
35325
+ merged: mergeDualBurst(localHits, deduped, {
35326
+ // A hit's `via` parent is resolved too, so an edge from a folded twin
35327
+ // can be re-pointed at the entity it folded into.
35328
+ twins: storeTwinIndex(store, deduped.flatMap((c) => {
35329
+ const parent = c.via?.parentId;
35330
+ return parent ? [c.id, parent] : [c.id];
35331
+ })),
35332
+ ...seedNode ? { seedId: seedNode.id } : {}
35333
+ }),
35140
35334
  seedProvenance,
35141
35335
  seedUsageCount
35142
35336
  };
@@ -35291,7 +35485,9 @@ function formatDualNodes(merged) {
35291
35485
  trust: n.why.trust
35292
35486
  }
35293
35487
  } : {},
35294
- ...n.via ? { via: n.via } : {}
35488
+ ...n.via ? { via: n.via } : {},
35489
+ ...n.twins?.length ? { twins: n.twins } : {},
35490
+ ...n.uploaded ? { uploaded: true } : {}
35295
35491
  }));
35296
35492
  }
35297
35493
  var BRIDGE_LABELS, CANONICAL_KNOWLEDGE_ID, UUID_ID;
@@ -35301,6 +35497,7 @@ var init_dual_augment = __esm({
35301
35497
  init_src2();
35302
35498
  init_generalize_graph();
35303
35499
  init_dual_burst();
35500
+ init_twin_index();
35304
35501
  BRIDGE_LABELS = new Set(SEMANTIC_NODE_LABELS);
35305
35502
  CANONICAL_KNOWLEDGE_ID = /^[a-z][a-z0-9]*_[0-9a-f]{8,}$/;
35306
35503
  UUID_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -44593,6 +44790,10 @@ function collectiveSeedMiss(args2, outcome) {
44593
44790
  function resolveSymbol2(store, args2) {
44594
44791
  const id = args2["nodeId"];
44595
44792
  if (typeof id === "string" && store.getNode(id)) return id;
44793
+ if (typeof id === "string" && hasCloudNodeShape(id)) {
44794
+ const twin = localTwinOf(store, id);
44795
+ if (twin) return twin.id;
44796
+ }
44596
44797
  const qname = args2["qname"];
44597
44798
  if (typeof qname === "string") {
44598
44799
  const hits = store.searchNodes({ descriptionLike: qname, limit: 5 });
@@ -44602,6 +44803,14 @@ function resolveSymbol2(store, args2) {
44602
44803
  }
44603
44804
  return null;
44604
44805
  }
44806
+ function seedTwinFields(args2, seed) {
44807
+ const twins = seed ? recordedCloudIds(seed) : [];
44808
+ const asked = args2["nodeId"];
44809
+ return {
44810
+ ...twins.length ? { seedTwins: twins } : {},
44811
+ ...typeof asked === "string" && seed && asked !== seed.id ? { requestedSeed: asked } : {}
44812
+ };
44813
+ }
44605
44814
  function isSynthesizedScope(name2) {
44606
44815
  return name2.includes(":") || name2.startsWith("anon@");
44607
44816
  }
@@ -44783,6 +44992,8 @@ var init_mcp = __esm({
44783
44992
  init_identity2();
44784
44993
  init_registry();
44785
44994
  init_dual_augment();
44995
+ init_dual_burst();
44996
+ init_twin_index();
44786
44997
  init_src5();
44787
44998
  init_src2();
44788
44999
  init_src8();
@@ -45065,6 +45276,7 @@ var init_mcp = __esm({
45065
45276
  if (dual.status === "merged") {
45066
45277
  return {
45067
45278
  seed: id,
45279
+ ...seedTwinFields(args2, seedNode),
45068
45280
  seedProvenance: dual.seedProvenance,
45069
45281
  ...dual.seedUsageCount != null ? { seedUsageCount: dual.seedUsageCount } : {},
45070
45282
  collectiveReachable: true,
@@ -45079,25 +45291,17 @@ var init_mcp = __esm({
45079
45291
  }
45080
45292
  return {
45081
45293
  seed: id,
45294
+ ...seedTwinFields(args2, seedNode),
45082
45295
  // reached-but-empty counts as reachable; offline/no-portal/local-only do not.
45083
45296
  collectiveReachable: collectiveStatus === "empty",
45084
45297
  collectiveStatus,
45085
45298
  ...federationHint(collectiveStatus) ? { collectiveHint: federationHint(collectiveStatus) } : {},
45086
- count: ranked.length,
45087
- results: ranked.map((r) => ({
45088
- id: r.id,
45089
- label: r.node.label,
45090
- name: r.node.description,
45091
- score: Number(r.score.toFixed(4)),
45092
- hops: r.hops,
45093
- provenance: "local",
45094
- why: {
45095
- semantic: Number(r.components.semantic.toFixed(3)),
45096
- centrality: Number(r.components.centrality.toFixed(3)),
45097
- trust: r.components.trust
45098
- },
45099
- ...r.via ? { via: r.via } : {}
45100
- }))
45299
+ // Same twin fold as the merged path: a hydrated in-store copy of a hit
45300
+ // (or of the seed) is not a second entity.
45301
+ ...(() => {
45302
+ const localOnly = mergeDualBurst(localHits, [], { twins: storeTwinIndex(store, []), seedId: id });
45303
+ return { count: localOnly.nodes.length, results: formatDualNodes(localOnly) };
45304
+ })()
45101
45305
  };
45102
45306
  }
45103
45307
  },
@@ -77678,11 +77882,26 @@ var CONFIRM_PREFIX = /^\s*confirm:\s*/i;
77678
77882
  var REFUTE_PREFIX = /^\s*refute:\s*/i;
77679
77883
  var REFUTE_REASON_MIN = 4;
77680
77884
  var PATTERN_RE = /\(\s*pattern:\s*([^()\n]{3,}?)\s*\)/gi;
77681
- var DOMAIN_RE = /\(\s*domain:\s*([^()\n]{3,}?)\s*\)/gi;
77885
+ var DOMAIN_RE = /\(\s*domain:\s*([^()\n]{2,}?)\s*\)/gi;
77682
77886
  var PACKAGE_RE = /\(\s*package:\s*([^()\n]{2,}?)\s*\)/gi;
77683
77887
  var COMPONENT_RE = /\(\s*component:\s*([^()\n]{2,}?)\s*\)/gi;
77684
77888
  var CAUSE_TEXT_MIN = 8;
77685
- var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*((?:\[[^\]\n]*\]|[^\]\n])*?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
77889
+ var FLAG_RE = /\[([!?])(?:#([a-z][\w-]{0,63})\s+)?\s*((?:\[[^[\]\n]*\]|[^\]\n])*?)\s*(?:@\s*([^\s\]]+?)(?::(\d+))?\s*)?\]/g;
77890
+ function flagScanView(text) {
77891
+ let out2 = "";
77892
+ let from = 0;
77893
+ for (; ; ) {
77894
+ const nl = text.indexOf("\n", from);
77895
+ const end = nl < 0 ? text.length : nl;
77896
+ const close = text.lastIndexOf("]", end - 1);
77897
+ const keep = close >= from ? close + 1 : from;
77898
+ out2 += text.slice(from, keep) + " ".repeat(end - keep);
77899
+ if (nl < 0) return out2;
77900
+ out2 += "\n";
77901
+ from = nl + 1;
77902
+ }
77903
+ }
77904
+ var MARKDOWN_LINK_RE = /\[([^[\]\n]+)\]\(([^()\s[\]]*[./][^()\s[\]]*)\)/g;
77686
77905
  var CONSTRAINT_MIN = 8;
77687
77906
  function clauseBefore(sentence, idx) {
77688
77907
  const win = sentence.slice(Math.max(0, idx - 80), idx);
@@ -77702,13 +77921,15 @@ var SIBLING_MARKUP_SOURCES = [
77702
77921
  CONSTRAINT_RE
77703
77922
  ].map((re) => ({ source: re.source, flags: re.flags.includes("g") ? re.flags : `${re.flags}g` }));
77704
77923
  function maskSiblingMarkup(sentence, tagStart, tagEnd) {
77705
- let masked = sentence;
77924
+ let masked = sentence.replace(MARKDOWN_LINK_RE, (m, label, _target, at) => at <= tagStart && at + m.length >= tagEnd ? m : ` ${label}${" ".repeat(m.length - label.length - 1)}`);
77706
77925
  for (const { source, flags: flags2 } of SIBLING_MARKUP_SOURCES) {
77707
- masked = masked.replace(new RegExp(source, flags2), (m, ...rest2) => {
77708
- const at = rest2.find((v) => typeof v === "number");
77709
- if (at <= tagStart && at + m.length >= tagEnd) return m;
77710
- return " ".repeat(m.length);
77711
- });
77926
+ const view = source === FLAG_RE.source ? flagScanView(masked) : masked;
77927
+ const spans = [];
77928
+ for (const m of view.matchAll(new RegExp(source, flags2))) {
77929
+ const at = m.index;
77930
+ if (!(at <= tagStart && at + m[0].length >= tagEnd)) spans.push([at, at + m[0].length]);
77931
+ }
77932
+ for (const [from, to] of spans) masked = masked.slice(0, from) + " ".repeat(to - from) + masked.slice(to);
77712
77933
  }
77713
77934
  return masked;
77714
77935
  }
@@ -77724,7 +77945,7 @@ function fixRationaleBefore(sentence, idx) {
77724
77945
  start2 = ws < 0 ? idx : start2 + ws;
77725
77946
  }
77726
77947
  const win = masked.slice(start2, idx).replace(/\s+$/, "").replace(/[.!?]$/, "");
77727
- const scan2 = win.replace(/`[^`]*`/g, (s) => s.replace(/[.;!?—]/g, "_"));
77948
+ const scan2 = win.replace(/`[^`]*`/g, (s) => s.replace(/[.;!?—]/g, "_")).replace(/\.(?=[\w/])/g, "_");
77728
77949
  const m = /[.;!?—][^.;!?—]*$/.exec(scan2);
77729
77950
  const clause = (m ? win.slice(m.index + 1) : win).replace(/\*\*|__/g, "").replace(/^\s*(?:[-*+]|\d+[.)])\s+/, "").replace(/\s+([:;,])/g, "$1").replace(/\s+/g, " ").trim();
77730
77951
  if (clause.endsWith(":")) return "";
@@ -77742,7 +77963,7 @@ function fixRationaleAfter(sentence, endIdx) {
77742
77963
  const residual = RESIDUAL_OPENER_RE.exec(win);
77743
77964
  if (residual) win = win.slice(0, residual.index);
77744
77965
  const stripped = win.replace(/^[\s—:–,-]+/, "");
77745
- const m = /^[^.;!?—]*/.exec(stripped);
77966
+ const m = /^(?:[^.;!?]|\.(?=[\w/]))*/.exec(stripped);
77746
77967
  const clause = (m ? m[0] : stripped).replace(/\s+/g, " ").trim();
77747
77968
  if (/^(and|but|or|nor|so|yet|also|plus)\b/i.test(clause)) return "";
77748
77969
  return clause;
@@ -77885,7 +78106,7 @@ function* captureSentences(text) {
77885
78106
  yield line;
77886
78107
  continue;
77887
78108
  }
77888
- const flags2 = [...line.matchAll(new RegExp(FLAG_RE.source, FLAG_RE.flags))].map((m) => ({ start: m.index, end: m.index + m[0].length }));
78109
+ const flags2 = [...flagScanView(line).matchAll(new RegExp(FLAG_RE.source, FLAG_RE.flags))].map((m) => ({ start: m.index, end: m.index + m[0].length }));
77889
78110
  let flag = 0;
77890
78111
  let start2 = 0;
77891
78112
  for (const boundary of line.matchAll(/(?<=[.!?])\s+(?!\(fix:)/g)) {
@@ -77917,12 +78138,14 @@ function parseInlineTagsWithDispositions(text) {
77917
78138
  });
77918
78139
  const sfield = sentence.replace(/\s+/g, " ").trim().slice(0, 160);
77919
78140
  const citeMatched = /* @__PURE__ */ new Set();
78141
+ const flagSpans = [...flagScanView(sentence).matchAll(new RegExp(FLAG_RE.source, FLAG_RE.flags))].map((m) => [m.index, m.index + m[0].length]);
77920
78142
  CITE_GROUP_RE.lastIndex = 0;
77921
78143
  let g;
77922
78144
  while ((g = CITE_GROUP_RE.exec(sentence)) !== null) {
77923
78145
  citeMatched.add(g.index);
77924
78146
  let content = g[1];
77925
78147
  if (/^\s*pattern:/i.test(content)) continue;
78148
+ if (/^\s*failed:/i.test(content)) continue;
77926
78149
  const fixM = FIX_PREFIX.exec(content);
77927
78150
  const causeM = fixM ? null : CAUSE_PREFIX.exec(content);
77928
78151
  const aidsM = fixM || causeM ? null : AIDS_PREFIX.exec(content);
@@ -77985,6 +78208,17 @@ function parseInlineTagsWithDispositions(text) {
77985
78208
  const verbThread = fixM?.[1] ?? causeM?.[1] ?? void 0;
77986
78209
  if (isFix) content = content.replace(FIX_PREFIX, "");
77987
78210
  else if (isCause) content = content.replace(CAUSE_PREFIX, "");
78211
+ if (isFix) {
78212
+ const joined = /[,;]\s*cause:\s*/i.exec(content);
78213
+ if (joined) {
78214
+ const causeBody = content.slice(joined.index + joined[0].length);
78215
+ content = content.slice(0, joined.index);
78216
+ const fixed = new RegExp(HANDLE_RE.source, "i").exec(content);
78217
+ const symptom = fixed && !/^\s*\[/.test(causeBody) ? `${fixed[0]} ` : "";
78218
+ const cause = parseCauseTag(`${symptom}${causeBody}`, sfield, verbThread, dispositions);
78219
+ if (cause) out2.push(cause);
78220
+ }
78221
+ }
77988
78222
  if (isCause) {
77989
78223
  const cause = parseCauseTag(content, sfield, verbThread, dispositions);
77990
78224
  if (cause) out2.push(cause);
@@ -78023,6 +78257,8 @@ function parseInlineTagsWithDispositions(text) {
78023
78257
  const note = content.replace(/\s+/g, " ").trim();
78024
78258
  if (note.length >= FIX_RATIONALE_MIN)
78025
78259
  out2.push({ kind: "fix", fixRationale: note, sentence: sfield, ...verbThread ? { threadId: verbThread } : {} });
78260
+ else if (verbThread && fixRationale && !flagSpans.some(([a2, b]) => a2 < g.index && g.index < b))
78261
+ out2.push({ kind: "fix", fixRationale, sentence: sfield, threadId: verbThread });
78026
78262
  else dispositions.unparseable++;
78027
78263
  } else if (!isFix && !emittedHandle && BRACKETED_TOKEN_RE.test(content)) {
78028
78264
  dispositions.unparseable++;
@@ -78106,8 +78342,9 @@ function parseInlineTagsWithDispositions(text) {
78106
78342
  }
78107
78343
  }
78108
78344
  FLAG_RE.lastIndex = 0;
78345
+ const flagView = flagScanView(sentence);
78109
78346
  let f;
78110
- while ((f = FLAG_RE.exec(sentence)) !== null) {
78347
+ while ((f = FLAG_RE.exec(flagView)) !== null) {
78111
78348
  const flagThread = f[2];
78112
78349
  const raw3 = f[3].trim();
78113
78350
  if (/^vuln\b/i.test(raw3)) {
@@ -78224,7 +78461,7 @@ function parseInlineTagsWithDispositions(text) {
78224
78461
  let dm;
78225
78462
  while ((dm = DOMAIN_RE.exec(sentence)) !== null) {
78226
78463
  const raw3 = dm[1].replace(/\s+/g, " ").trim();
78227
- if (raw3.length >= 3) out2.push({ kind: "domain", domainText: raw3, sentence: sfield });
78464
+ if (raw3.length >= 2) out2.push({ kind: "domain", domainText: raw3, sentence: sfield });
78228
78465
  }
78229
78466
  PACKAGE_RE.lastIndex = 0;
78230
78467
  let pk;
@@ -83212,7 +83449,7 @@ function createWatchBreaker(deps) {
83212
83449
  }
83213
83450
 
83214
83451
  // src/engine.ts
83215
- var DAEMON_VERSION = true ? "2.0.21-dev.3918" : "2.0.0-alpha.0";
83452
+ var DAEMON_VERSION = true ? "2.0.21-dev.3930" : "2.0.0-alpha.0";
83216
83453
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
83217
83454
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
83218
83455
  var GIT_OP_MUTE_MS = 4e3;
@@ -15204,6 +15204,10 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
15204
15204
  CREATE INDEX IF NOT EXISTS nodes_command_signature ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL;
15205
15205
  CREATE INDEX IF NOT EXISTS nodes_diag_command ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL;
15206
15206
  CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL;
15207
+ -- Local nodes by the cloud twin their upload receipt recorded. Every burst /
15208
+ -- search fusion folds the Network copy of the caller's own knowledge back onto
15209
+ -- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
15210
+ CREATE INDEX IF NOT EXISTS nodes_cloud_node_id ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL;
15207
15211
  -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
15208
15212
  -- tool response (the enrichment piggyback), so it must not parse every
15209
15213
  -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
@@ -16173,6 +16177,19 @@ var SqliteGraphStore = class {
16173
16177
  ).get(attr, `%${marker}%`, limit);
16174
16178
  return { count: Number(r.count), total: Number(r.total) };
16175
16179
  }
16180
+ liveNodeIdsByCloudNodeId(cloudIds) {
16181
+ const out = /* @__PURE__ */ new Map();
16182
+ const CHUNK = 900;
16183
+ for (let i = 0; i < cloudIds.length; i += CHUNK) {
16184
+ const slice = cloudIds.slice(i, i + CHUNK);
16185
+ if (slice.length === 0) continue;
16186
+ const rows = this.db.prepare(
16187
+ `SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
16188
+ ).all(...slice);
16189
+ for (const r of rows) out.set(r.cloud, [...out.get(r.cloud) ?? [], r.id]);
16190
+ }
16191
+ return out;
16192
+ }
16176
16193
  liveNodeIds(ids) {
16177
16194
  const live = /* @__PURE__ */ new Set();
16178
16195
  const CHUNK = 900;
@@ -16234,7 +16251,10 @@ var SqliteGraphStore = class {
16234
16251
  if (relPaths.length === 0) return [];
16235
16252
  const placeholders = relPaths.map(() => "?").join(",");
16236
16253
  const rows = this.db.prepare(
16237
- `SELECT * FROM nodes WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
16254
+ // INDEXED BY: unforced, the planner took nodes_live_page_rank and
16255
+ // json-parsed every live row — 11–25s per reindexed file pair on the
16256
+ // live store vs 7ms here (2026-10-08).
16257
+ `SELECT * FROM nodes INDEXED BY nodes_relpath WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
16238
16258
  ).all(...relPaths);
16239
16259
  return rows.map(rowToNode);
16240
16260
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.21-dev.3918",
3
+ "version": "2.0.21-dev.3930",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -26063,6 +26063,10 @@ CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path ON nodes(json_extract(attrs_js
26063
26063
  CREATE INDEX IF NOT EXISTS nodes_command_signature ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL;
26064
26064
  CREATE INDEX IF NOT EXISTS nodes_diag_command ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL;
26065
26065
  CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL;
26066
+ -- Local nodes by the cloud twin their upload receipt recorded. Every burst /
26067
+ -- search fusion folds the Network copy of the caller's own knowledge back onto
26068
+ -- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
26069
+ CREATE INDEX IF NOT EXISTS nodes_cloud_node_id ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL;
26066
26070
  -- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
26067
26071
  -- tool response (the enrichment piggyback), so it must not parse every
26068
26072
  -- Solution's and Problem's attrs per call. Not partial: the Problem pull reads
@@ -27032,6 +27036,19 @@ var SqliteGraphStore = class {
27032
27036
  ).get(attr, `%${marker}%`, limit);
27033
27037
  return { count: Number(r.count), total: Number(r.total) };
27034
27038
  }
27039
+ liveNodeIdsByCloudNodeId(cloudIds) {
27040
+ const out2 = /* @__PURE__ */ new Map();
27041
+ const CHUNK = 900;
27042
+ for (let i2 = 0; i2 < cloudIds.length; i2 += CHUNK) {
27043
+ const slice = cloudIds.slice(i2, i2 + CHUNK);
27044
+ if (slice.length === 0) continue;
27045
+ const rows = this.db.prepare(
27046
+ `SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
27047
+ ).all(...slice);
27048
+ for (const r of rows) out2.set(r.cloud, [...out2.get(r.cloud) ?? [], r.id]);
27049
+ }
27050
+ return out2;
27051
+ }
27035
27052
  liveNodeIds(ids) {
27036
27053
  const live = /* @__PURE__ */ new Set();
27037
27054
  const CHUNK = 900;
@@ -27093,7 +27110,10 @@ var SqliteGraphStore = class {
27093
27110
  if (relPaths.length === 0) return [];
27094
27111
  const placeholders = relPaths.map(() => "?").join(",");
27095
27112
  const rows = this.db.prepare(
27096
- `SELECT * FROM nodes WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
27113
+ // INDEXED BY: unforced, the planner took nodes_live_page_rank and
27114
+ // json-parsed every live row — 11–25s per reindexed file pair on the
27115
+ // live store vs 7ms here (2026-10-08).
27116
+ `SELECT * FROM nodes INDEXED BY nodes_relpath WHERE valid_to IS NULL AND json_extract(attrs_json, '$.relPath') IN (${placeholders})`
27097
27117
  ).all(...relPaths);
27098
27118
  return rows.map(rowToNode);
27099
27119
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "errata-context",
3
- "version": "2.0.21-dev.3918",
3
+ "version": "2.0.21-dev.3930",
4
4
  "description": "A minimal 3D view of loaded Errata priors and recent graph bursts.",
5
5
  "author": {
6
6
  "name": "inErrata"
@@ -1,4 +1,4 @@
1
- b107a94e4e9828c1c6f57f4acb3e914b4e2ed683a0c75446defb85835e5010ce .codex-plugin/plugin.json
1
+ 7e11496cf4ef348e9e19cd5d191ad21dd9e54860dcb0c7b67fccd266dde126d1 .codex-plugin/plugin.json
2
2
  a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
3
3
  fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
4
4
  73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js