@inerrata-corporation/errata 2.0.22-dev.4676 → 2.0.23-dev.4682
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/consolidate-worker.mjs +83 -14
- package/errata.mjs +86 -17
- package/generalize-worker.mjs +83 -14
- package/package.json +1 -1
- package/pass-worker.mjs +83 -14
- package/resources/context-plugin/.codex-plugin/plugin.json +1 -1
- package/resources/context-plugin/SHA256SUMS +1 -1
package/consolidate-worker.mjs
CHANGED
|
@@ -17935,7 +17935,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
|
17935
17935
|
var TEMPORAL_INDEX_SQL = `
|
|
17936
17936
|
CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
|
|
17937
17937
|
CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
|
|
17938
|
-
|
|
17938
|
+
-- Problems by recency (the only label read newest-first). Over every live row,
|
|
17939
|
+
-- each write that stamps last_updated_at moved a code symbol's entry from a
|
|
17940
|
+
-- scattered leaf: ~19 KB per position-only symbol rewrite on a 1.1 GB store.
|
|
17941
|
+
-- label stays the leading column so label = 'Problem' plans without a sort.
|
|
17942
|
+
DROP INDEX IF EXISTS nodes_live_recent;
|
|
17943
|
+
CREATE INDEX IF NOT EXISTS nodes_live_problem_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL AND label = 'Problem';
|
|
17939
17944
|
-- "Anything created since the watermark?" guards (the fold, regression and
|
|
17940
17945
|
-- canonical-bind sweeps run one per session boundary). Without it the planner
|
|
17941
17946
|
-- walks nodes_live and reads every live row of the label to test created_at:
|
|
@@ -18009,24 +18014,34 @@ CREATE INDEX IF NOT EXISTS nodes_closed_relpath ON nodes(json_extract(attrs_json
|
|
|
18009
18014
|
-- Live rows by name, for looking up the few symbols a statement mentions
|
|
18010
18015
|
-- instead of hydrating every code symbol per minted problem.
|
|
18011
18016
|
CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
|
|
18017
|
+
-- The six single-attr lookups below are partial on the attr being present: each
|
|
18018
|
+
-- is read only by equality (or IN), which implies it. Keyed on NULL for the ~95%
|
|
18019
|
+
-- of rows that lack the attr, every write to such a row's attrs rewrote its NULL
|
|
18020
|
+
-- entry: ~25 KB per position-only symbol rewrite on a 1.1 GB store (2026-10-21).
|
|
18021
|
+
DROP INDEX IF EXISTS nodes_anchor_hint_path;
|
|
18022
|
+
DROP INDEX IF EXISTS nodes_command_signature;
|
|
18023
|
+
DROP INDEX IF EXISTS nodes_diag_command;
|
|
18024
|
+
DROP INDEX IF EXISTS nodes_lsp_rel_path;
|
|
18025
|
+
DROP INDEX IF EXISTS nodes_cloud_node_id;
|
|
18026
|
+
DROP INDEX IF EXISTS nodes_enrichment_pending;
|
|
18012
18027
|
-- Problems by the file their anchor hint names: the harvest upgrades hints for
|
|
18013
18028
|
-- every edited file of every turn, and a scan parses every Problem's attrs.
|
|
18014
|
-
CREATE INDEX IF NOT EXISTS
|
|
18029
|
+
CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path_v2 ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint.path') IS NOT NULL;
|
|
18015
18030
|
-- Problems by the command / checker / file that opened them. The generalizer
|
|
18016
18031
|
-- looked these up per shell or tool EVENT by hydrating every Problem: ~12s of
|
|
18017
18032
|
-- every minute of daemon main-thread time on a 3.4k-Problem store (2026-10-05).
|
|
18018
|
-
CREATE INDEX IF NOT EXISTS
|
|
18019
|
-
CREATE INDEX IF NOT EXISTS
|
|
18020
|
-
CREATE INDEX IF NOT EXISTS
|
|
18033
|
+
CREATE INDEX IF NOT EXISTS nodes_command_signature_v2 ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.commandSignature') IS NOT NULL;
|
|
18034
|
+
CREATE INDEX IF NOT EXISTS nodes_diag_command_v2 ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.diagCommand') IS NOT NULL;
|
|
18035
|
+
CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path_v2 ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.lspRelPath') IS NOT NULL;
|
|
18021
18036
|
-- Local nodes by the cloud twin their upload receipt recorded. Every burst /
|
|
18022
18037
|
-- search fusion folds the Network copy of the caller's own knowledge back onto
|
|
18023
18038
|
-- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
|
|
18024
|
-
CREATE INDEX IF NOT EXISTS
|
|
18039
|
+
CREATE INDEX IF NOT EXISTS nodes_cloud_node_id_v2 ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.cloudNodeId') IS NOT NULL;
|
|
18025
18040
|
-- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
|
|
18026
18041
|
-- tool response (the enrichment piggyback), so it must not parse every
|
|
18027
|
-
-- Solution's and Problem's attrs per call.
|
|
18042
|
+
-- Solution's and Problem's attrs per call. Any version: the Problem pull reads
|
|
18028
18043
|
-- closed versions too.
|
|
18029
|
-
CREATE INDEX IF NOT EXISTS
|
|
18044
|
+
CREATE INDEX IF NOT EXISTS nodes_enrichment_pending_v2 ON nodes(json_extract(attrs_json, '$.enrichmentPending')) WHERE json_extract(attrs_json, '$.enrichmentPending') IS NOT NULL;
|
|
18030
18045
|
-- Revisit flags, any label, any version: listNeedsRevisit's as-of read runs on
|
|
18031
18046
|
-- every context render and otherwise json-checks every row (10.3 s on a 324k-row
|
|
18032
18047
|
-- store, to return zero flags). The predicate must stay textually identical to
|
|
@@ -18092,6 +18107,30 @@ function rowToEdge(r) {
|
|
|
18092
18107
|
}
|
|
18093
18108
|
var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
|
|
18094
18109
|
var NODE_LABELS_CHUNK = 256;
|
|
18110
|
+
var NODE_UPSERT_COLUMNS = [
|
|
18111
|
+
"label",
|
|
18112
|
+
"description",
|
|
18113
|
+
"extraction_confidence",
|
|
18114
|
+
"embedding_json",
|
|
18115
|
+
"embedding_blob",
|
|
18116
|
+
"cumulative_surprise",
|
|
18117
|
+
"peak_surprise",
|
|
18118
|
+
"cumulative_hits",
|
|
18119
|
+
"last_updated_at",
|
|
18120
|
+
"memory_tier",
|
|
18121
|
+
"page_rank",
|
|
18122
|
+
"is_landmark",
|
|
18123
|
+
"community",
|
|
18124
|
+
"stability",
|
|
18125
|
+
"last_reinforced_at_seq",
|
|
18126
|
+
"attrs_json"
|
|
18127
|
+
];
|
|
18128
|
+
var EDGE_UPSERT_COLUMNS = ["confidence", "last_seen_at", "nav_successes", "nav_failures", "valid_to", "attrs_json"];
|
|
18129
|
+
function sameSqlValue(a, b) {
|
|
18130
|
+
if (a === b || a == null && b == null) return true;
|
|
18131
|
+
if (a instanceof Uint8Array && b instanceof Uint8Array) return Buffer.compare(a, b) === 0;
|
|
18132
|
+
return false;
|
|
18133
|
+
}
|
|
18095
18134
|
var PRIOR_TAG_MARKER = '"priorTag":true';
|
|
18096
18135
|
var SqliteGraphStore = class {
|
|
18097
18136
|
db;
|
|
@@ -18099,6 +18138,8 @@ var SqliteGraphStore = class {
|
|
|
18099
18138
|
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
18100
18139
|
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
18101
18140
|
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
18141
|
+
/** Narrow merge updates, keyed by table and the columns they set. */
|
|
18142
|
+
changedColumnStmts = /* @__PURE__ */ new Map();
|
|
18102
18143
|
stmts;
|
|
18103
18144
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
18104
18145
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -18182,6 +18223,8 @@ var SqliteGraphStore = class {
|
|
|
18182
18223
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
18183
18224
|
attrs_json = excluded.attrs_json
|
|
18184
18225
|
`),
|
|
18226
|
+
nodeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${NODE_UPSERT_COLUMNS.join(", ")} FROM nodes WHERE id = ?`),
|
|
18227
|
+
edgeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${EDGE_UPSERT_COLUMNS.join(", ")} FROM edges WHERE from_id = ? AND type = ? AND to_id = ?`),
|
|
18185
18228
|
getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
|
|
18186
18229
|
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
18187
18230
|
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
@@ -18550,7 +18593,7 @@ var SqliteGraphStore = class {
|
|
|
18550
18593
|
}
|
|
18551
18594
|
mergeNode(node) {
|
|
18552
18595
|
this.mutations++;
|
|
18553
|
-
|
|
18596
|
+
const values = {
|
|
18554
18597
|
id: node.id,
|
|
18555
18598
|
label: node.label,
|
|
18556
18599
|
description: node.description,
|
|
@@ -18577,7 +18620,23 @@ var SqliteGraphStore = class {
|
|
|
18577
18620
|
created_at_seq: node.createdAtSeq ?? null,
|
|
18578
18621
|
last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
|
|
18579
18622
|
attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
|
|
18580
|
-
}
|
|
18623
|
+
};
|
|
18624
|
+
const current = this.stmts.nodeUpsertTarget.get(node.id);
|
|
18625
|
+
if (current) this.writeChangedColumns("nodes", NODE_UPSERT_COLUMNS, current, values);
|
|
18626
|
+
else this.stmts.insertNode.run(values);
|
|
18627
|
+
}
|
|
18628
|
+
/** What the upsert's ON CONFLICT would write over `current`, setting only the
|
|
18629
|
+
* columns whose value moves (NODE_UPSERT_COLUMNS says why). */
|
|
18630
|
+
writeChangedColumns(table, columns2, current, next) {
|
|
18631
|
+
const changed = columns2.filter((c) => !sameSqlValue(current[c], next[c]));
|
|
18632
|
+
if (changed.length === 0) return;
|
|
18633
|
+
const key = `${table}:${changed.join(",")}`;
|
|
18634
|
+
let stmt2 = this.changedColumnStmts.get(key);
|
|
18635
|
+
if (!stmt2) {
|
|
18636
|
+
stmt2 = this.db.prepare(`UPDATE ${table} SET ${changed.map((c) => `${c} = :${c}`).join(", ")} WHERE rowid = :rid`);
|
|
18637
|
+
this.changedColumnStmts.set(key, stmt2);
|
|
18638
|
+
}
|
|
18639
|
+
stmt2.run({ rid: current["rid"], ...Object.fromEntries(changed.map((c) => [c, next[c]])) });
|
|
18581
18640
|
}
|
|
18582
18641
|
/** mergeNode, unless the live row already holds this content. Content is what
|
|
18583
18642
|
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
@@ -18711,7 +18770,7 @@ var SqliteGraphStore = class {
|
|
|
18711
18770
|
return;
|
|
18712
18771
|
}
|
|
18713
18772
|
this.mutations++;
|
|
18714
|
-
|
|
18773
|
+
const values = {
|
|
18715
18774
|
id: edge.id,
|
|
18716
18775
|
from_id: edge.from,
|
|
18717
18776
|
to_id: edge.to,
|
|
@@ -18725,6 +18784,16 @@ var SqliteGraphStore = class {
|
|
|
18725
18784
|
valid_from: edge.validFrom ?? edge.createdAt,
|
|
18726
18785
|
valid_to: edge.validTo ?? null,
|
|
18727
18786
|
attrs_json: JSON.stringify(edge.attrs ?? {})
|
|
18787
|
+
};
|
|
18788
|
+
const current = this.stmts.edgeUpsertTarget.get(edge.from, edge.type, edge.to);
|
|
18789
|
+
if (!current) {
|
|
18790
|
+
this.stmts.insertEdge.run(values);
|
|
18791
|
+
return;
|
|
18792
|
+
}
|
|
18793
|
+
this.writeChangedColumns("edges", EDGE_UPSERT_COLUMNS, current, {
|
|
18794
|
+
...values,
|
|
18795
|
+
confidence: Math.max(current["confidence"], edge.confidence),
|
|
18796
|
+
valid_to: null
|
|
18728
18797
|
});
|
|
18729
18798
|
}
|
|
18730
18799
|
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
@@ -18939,8 +19008,8 @@ var SqliteGraphStore = class {
|
|
|
18939
19008
|
after = rows[rows.length - 1].rid;
|
|
18940
19009
|
}
|
|
18941
19010
|
}
|
|
18942
|
-
|
|
18943
|
-
const rows = this.db.prepare("SELECT id FROM nodes WHERE label =
|
|
19011
|
+
recentProblemIds(limit) {
|
|
19012
|
+
const rows = this.db.prepare("SELECT id FROM nodes WHERE label = 'Problem' AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(Math.max(0, limit));
|
|
18944
19013
|
return rows.map((r) => r.id);
|
|
18945
19014
|
}
|
|
18946
19015
|
recentOpenProblemCandidates(opts) {
|
|
@@ -19381,7 +19450,7 @@ var SqliteGraphStore = class {
|
|
|
19381
19450
|
const slice = cloudIds.slice(i, i + CHUNK);
|
|
19382
19451
|
if (slice.length === 0) continue;
|
|
19383
19452
|
const rows = this.db.prepare(
|
|
19384
|
-
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY
|
|
19453
|
+
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id_v2 WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
|
|
19385
19454
|
).all(...slice);
|
|
19386
19455
|
for (const r of rows) out.set(r.cloud, [...out.get(r.cloud) ?? [], r.id]);
|
|
19387
19456
|
}
|
package/errata.mjs
CHANGED
|
@@ -20756,10 +20756,15 @@ function rowToEdge(r) {
|
|
|
20756
20756
|
attrs: JSON.parse(r.attrs_json)
|
|
20757
20757
|
};
|
|
20758
20758
|
}
|
|
20759
|
+
function sameSqlValue(a, b) {
|
|
20760
|
+
if (a === b || a == null && b == null) return true;
|
|
20761
|
+
if (a instanceof Uint8Array && b instanceof Uint8Array) return Buffer.compare(a, b) === 0;
|
|
20762
|
+
return false;
|
|
20763
|
+
}
|
|
20759
20764
|
function openGraphStore(opts) {
|
|
20760
20765
|
return new SqliteGraphStore(opts);
|
|
20761
20766
|
}
|
|
20762
|
-
var PAGE_RANK_SCALE_KEY, TEXT_SEARCH_COLUMNS, LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_REPAIR_KEY, EXPOSURE_SIDE_KEY, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, NODE_LABELS_CHUNK, PRIOR_TAG_MARKER, SqliteGraphStore;
|
|
20767
|
+
var PAGE_RANK_SCALE_KEY, TEXT_SEARCH_COLUMNS, LOCAL_RULE_OVERRIDES, SCHEMA_VERSION, SESSION_REPAIR_KEY, EXPOSURE_SIDE_KEY, SESSION_EVIDENCE_PREDICATE, SCHEMA_SQL, TEMPORAL_INDEX_SQL, FINGERPRINT_INDEXED_LABELS, NODE_LABELS_CHUNK, NODE_UPSERT_COLUMNS, EDGE_UPSERT_COLUMNS, PRIOR_TAG_MARKER, SqliteGraphStore;
|
|
20763
20768
|
var init_store = __esm({
|
|
20764
20769
|
"../../packages/local-graph/src/store.ts"() {
|
|
20765
20770
|
"use strict";
|
|
@@ -20914,7 +20919,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
|
20914
20919
|
TEMPORAL_INDEX_SQL = `
|
|
20915
20920
|
CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
|
|
20916
20921
|
CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
|
|
20917
|
-
|
|
20922
|
+
-- Problems by recency (the only label read newest-first). Over every live row,
|
|
20923
|
+
-- each write that stamps last_updated_at moved a code symbol's entry from a
|
|
20924
|
+
-- scattered leaf: ~19 KB per position-only symbol rewrite on a 1.1 GB store.
|
|
20925
|
+
-- label stays the leading column so label = 'Problem' plans without a sort.
|
|
20926
|
+
DROP INDEX IF EXISTS nodes_live_recent;
|
|
20927
|
+
CREATE INDEX IF NOT EXISTS nodes_live_problem_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL AND label = 'Problem';
|
|
20918
20928
|
-- "Anything created since the watermark?" guards (the fold, regression and
|
|
20919
20929
|
-- canonical-bind sweeps run one per session boundary). Without it the planner
|
|
20920
20930
|
-- walks nodes_live and reads every live row of the label to test created_at:
|
|
@@ -20988,24 +20998,34 @@ CREATE INDEX IF NOT EXISTS nodes_closed_relpath ON nodes(json_extract(attrs_json
|
|
|
20988
20998
|
-- Live rows by name, for looking up the few symbols a statement mentions
|
|
20989
20999
|
-- instead of hydrating every code symbol per minted problem.
|
|
20990
21000
|
CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
|
|
21001
|
+
-- The six single-attr lookups below are partial on the attr being present: each
|
|
21002
|
+
-- is read only by equality (or IN), which implies it. Keyed on NULL for the ~95%
|
|
21003
|
+
-- of rows that lack the attr, every write to such a row's attrs rewrote its NULL
|
|
21004
|
+
-- entry: ~25 KB per position-only symbol rewrite on a 1.1 GB store (2026-10-21).
|
|
21005
|
+
DROP INDEX IF EXISTS nodes_anchor_hint_path;
|
|
21006
|
+
DROP INDEX IF EXISTS nodes_command_signature;
|
|
21007
|
+
DROP INDEX IF EXISTS nodes_diag_command;
|
|
21008
|
+
DROP INDEX IF EXISTS nodes_lsp_rel_path;
|
|
21009
|
+
DROP INDEX IF EXISTS nodes_cloud_node_id;
|
|
21010
|
+
DROP INDEX IF EXISTS nodes_enrichment_pending;
|
|
20991
21011
|
-- Problems by the file their anchor hint names: the harvest upgrades hints for
|
|
20992
21012
|
-- every edited file of every turn, and a scan parses every Problem's attrs.
|
|
20993
|
-
CREATE INDEX IF NOT EXISTS
|
|
21013
|
+
CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path_v2 ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint.path') IS NOT NULL;
|
|
20994
21014
|
-- Problems by the command / checker / file that opened them. The generalizer
|
|
20995
21015
|
-- looked these up per shell or tool EVENT by hydrating every Problem: ~12s of
|
|
20996
21016
|
-- every minute of daemon main-thread time on a 3.4k-Problem store (2026-10-05).
|
|
20997
|
-
CREATE INDEX IF NOT EXISTS
|
|
20998
|
-
CREATE INDEX IF NOT EXISTS
|
|
20999
|
-
CREATE INDEX IF NOT EXISTS
|
|
21017
|
+
CREATE INDEX IF NOT EXISTS nodes_command_signature_v2 ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.commandSignature') IS NOT NULL;
|
|
21018
|
+
CREATE INDEX IF NOT EXISTS nodes_diag_command_v2 ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.diagCommand') IS NOT NULL;
|
|
21019
|
+
CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path_v2 ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.lspRelPath') IS NOT NULL;
|
|
21000
21020
|
-- Local nodes by the cloud twin their upload receipt recorded. Every burst /
|
|
21001
21021
|
-- search fusion folds the Network copy of the caller's own knowledge back onto
|
|
21002
21022
|
-- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
|
|
21003
|
-
CREATE INDEX IF NOT EXISTS
|
|
21023
|
+
CREATE INDEX IF NOT EXISTS nodes_cloud_node_id_v2 ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.cloudNodeId') IS NOT NULL;
|
|
21004
21024
|
-- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
|
|
21005
21025
|
-- tool response (the enrichment piggyback), so it must not parse every
|
|
21006
|
-
-- Solution's and Problem's attrs per call.
|
|
21026
|
+
-- Solution's and Problem's attrs per call. Any version: the Problem pull reads
|
|
21007
21027
|
-- closed versions too.
|
|
21008
|
-
CREATE INDEX IF NOT EXISTS
|
|
21028
|
+
CREATE INDEX IF NOT EXISTS nodes_enrichment_pending_v2 ON nodes(json_extract(attrs_json, '$.enrichmentPending')) WHERE json_extract(attrs_json, '$.enrichmentPending') IS NOT NULL;
|
|
21009
21029
|
-- Revisit flags, any label, any version: listNeedsRevisit's as-of read runs on
|
|
21010
21030
|
-- every context render and otherwise json-checks every row (10.3 s on a 324k-row
|
|
21011
21031
|
-- store, to return zero flags). The predicate must stay textually identical to
|
|
@@ -21014,6 +21034,25 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21014
21034
|
`;
|
|
21015
21035
|
FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
|
|
21016
21036
|
NODE_LABELS_CHUNK = 256;
|
|
21037
|
+
NODE_UPSERT_COLUMNS = [
|
|
21038
|
+
"label",
|
|
21039
|
+
"description",
|
|
21040
|
+
"extraction_confidence",
|
|
21041
|
+
"embedding_json",
|
|
21042
|
+
"embedding_blob",
|
|
21043
|
+
"cumulative_surprise",
|
|
21044
|
+
"peak_surprise",
|
|
21045
|
+
"cumulative_hits",
|
|
21046
|
+
"last_updated_at",
|
|
21047
|
+
"memory_tier",
|
|
21048
|
+
"page_rank",
|
|
21049
|
+
"is_landmark",
|
|
21050
|
+
"community",
|
|
21051
|
+
"stability",
|
|
21052
|
+
"last_reinforced_at_seq",
|
|
21053
|
+
"attrs_json"
|
|
21054
|
+
];
|
|
21055
|
+
EDGE_UPSERT_COLUMNS = ["confidence", "last_seen_at", "nav_successes", "nav_failures", "valid_to", "attrs_json"];
|
|
21017
21056
|
PRIOR_TAG_MARKER = '"priorTag":true';
|
|
21018
21057
|
SqliteGraphStore = class {
|
|
21019
21058
|
db;
|
|
@@ -21021,6 +21060,8 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21021
21060
|
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
21022
21061
|
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
21023
21062
|
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
21063
|
+
/** Narrow merge updates, keyed by table and the columns they set. */
|
|
21064
|
+
changedColumnStmts = /* @__PURE__ */ new Map();
|
|
21024
21065
|
stmts;
|
|
21025
21066
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
21026
21067
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -21104,6 +21145,8 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21104
21145
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
21105
21146
|
attrs_json = excluded.attrs_json
|
|
21106
21147
|
`),
|
|
21148
|
+
nodeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${NODE_UPSERT_COLUMNS.join(", ")} FROM nodes WHERE id = ?`),
|
|
21149
|
+
edgeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${EDGE_UPSERT_COLUMNS.join(", ")} FROM edges WHERE from_id = ? AND type = ? AND to_id = ?`),
|
|
21107
21150
|
getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
|
|
21108
21151
|
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
21109
21152
|
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
@@ -21472,7 +21515,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21472
21515
|
}
|
|
21473
21516
|
mergeNode(node2) {
|
|
21474
21517
|
this.mutations++;
|
|
21475
|
-
|
|
21518
|
+
const values = {
|
|
21476
21519
|
id: node2.id,
|
|
21477
21520
|
label: node2.label,
|
|
21478
21521
|
description: node2.description,
|
|
@@ -21499,7 +21542,23 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21499
21542
|
created_at_seq: node2.createdAtSeq ?? null,
|
|
21500
21543
|
last_reinforced_at_seq: node2.lastReinforcedAtSeq ?? null,
|
|
21501
21544
|
attrs_json: this.nodeAttrsJson(node2.id, node2.attrs ?? {})
|
|
21502
|
-
}
|
|
21545
|
+
};
|
|
21546
|
+
const current = this.stmts.nodeUpsertTarget.get(node2.id);
|
|
21547
|
+
if (current) this.writeChangedColumns("nodes", NODE_UPSERT_COLUMNS, current, values);
|
|
21548
|
+
else this.stmts.insertNode.run(values);
|
|
21549
|
+
}
|
|
21550
|
+
/** What the upsert's ON CONFLICT would write over `current`, setting only the
|
|
21551
|
+
* columns whose value moves (NODE_UPSERT_COLUMNS says why). */
|
|
21552
|
+
writeChangedColumns(table, columns2, current, next) {
|
|
21553
|
+
const changed = columns2.filter((c) => !sameSqlValue(current[c], next[c]));
|
|
21554
|
+
if (changed.length === 0) return;
|
|
21555
|
+
const key = `${table}:${changed.join(",")}`;
|
|
21556
|
+
let stmt2 = this.changedColumnStmts.get(key);
|
|
21557
|
+
if (!stmt2) {
|
|
21558
|
+
stmt2 = this.db.prepare(`UPDATE ${table} SET ${changed.map((c) => `${c} = :${c}`).join(", ")} WHERE rowid = :rid`);
|
|
21559
|
+
this.changedColumnStmts.set(key, stmt2);
|
|
21560
|
+
}
|
|
21561
|
+
stmt2.run({ rid: current["rid"], ...Object.fromEntries(changed.map((c) => [c, next[c]])) });
|
|
21503
21562
|
}
|
|
21504
21563
|
/** mergeNode, unless the live row already holds this content. Content is what
|
|
21505
21564
|
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
@@ -21633,7 +21692,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21633
21692
|
return;
|
|
21634
21693
|
}
|
|
21635
21694
|
this.mutations++;
|
|
21636
|
-
|
|
21695
|
+
const values = {
|
|
21637
21696
|
id: edge2.id,
|
|
21638
21697
|
from_id: edge2.from,
|
|
21639
21698
|
to_id: edge2.to,
|
|
@@ -21647,6 +21706,16 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21647
21706
|
valid_from: edge2.validFrom ?? edge2.createdAt,
|
|
21648
21707
|
valid_to: edge2.validTo ?? null,
|
|
21649
21708
|
attrs_json: JSON.stringify(edge2.attrs ?? {})
|
|
21709
|
+
};
|
|
21710
|
+
const current = this.stmts.edgeUpsertTarget.get(edge2.from, edge2.type, edge2.to);
|
|
21711
|
+
if (!current) {
|
|
21712
|
+
this.stmts.insertEdge.run(values);
|
|
21713
|
+
return;
|
|
21714
|
+
}
|
|
21715
|
+
this.writeChangedColumns("edges", EDGE_UPSERT_COLUMNS, current, {
|
|
21716
|
+
...values,
|
|
21717
|
+
confidence: Math.max(current["confidence"], edge2.confidence),
|
|
21718
|
+
valid_to: null
|
|
21650
21719
|
});
|
|
21651
21720
|
}
|
|
21652
21721
|
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
@@ -21861,8 +21930,8 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21861
21930
|
after = rows[rows.length - 1].rid;
|
|
21862
21931
|
}
|
|
21863
21932
|
}
|
|
21864
|
-
|
|
21865
|
-
const rows = this.db.prepare("SELECT id FROM nodes WHERE label =
|
|
21933
|
+
recentProblemIds(limit) {
|
|
21934
|
+
const rows = this.db.prepare("SELECT id FROM nodes WHERE label = 'Problem' AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(Math.max(0, limit));
|
|
21866
21935
|
return rows.map((r) => r.id);
|
|
21867
21936
|
}
|
|
21868
21937
|
recentOpenProblemCandidates(opts) {
|
|
@@ -22303,7 +22372,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
22303
22372
|
const slice = cloudIds.slice(i2, i2 + CHUNK2);
|
|
22304
22373
|
if (slice.length === 0) continue;
|
|
22305
22374
|
const rows = this.db.prepare(
|
|
22306
|
-
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY
|
|
22375
|
+
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id_v2 WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
|
|
22307
22376
|
).all(...slice);
|
|
22308
22377
|
for (const r of rows) out2.set(r.cloud, [...out2.get(r.cloud) ?? [], r.id]);
|
|
22309
22378
|
}
|
|
@@ -87394,7 +87463,7 @@ function createWatchBreaker(deps) {
|
|
|
87394
87463
|
}
|
|
87395
87464
|
|
|
87396
87465
|
// src/engine.ts
|
|
87397
|
-
var DAEMON_VERSION = true ? "2.0.
|
|
87466
|
+
var DAEMON_VERSION = true ? "2.0.23-dev.4682" : "2.0.0-alpha.0";
|
|
87398
87467
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
87399
87468
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
87400
87469
|
var GIT_OP_MUTE_MS = 4e3;
|
|
@@ -88036,7 +88105,7 @@ function createWorkspaceEngine(opts) {
|
|
|
88036
88105
|
void outboundStack().then((stack) => {
|
|
88037
88106
|
if (!(opts.syncConsent ?? consentNow().sync)) return;
|
|
88038
88107
|
const norm = normStackTokens;
|
|
88039
|
-
const seed = store.
|
|
88108
|
+
const seed = store.recentProblemIds(6);
|
|
88040
88109
|
const q = {
|
|
88041
88110
|
stack,
|
|
88042
88111
|
domains: norm(profile.domains),
|
package/generalize-worker.mjs
CHANGED
|
@@ -16332,7 +16332,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
|
16332
16332
|
var TEMPORAL_INDEX_SQL = `
|
|
16333
16333
|
CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
|
|
16334
16334
|
CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
|
|
16335
|
-
|
|
16335
|
+
-- Problems by recency (the only label read newest-first). Over every live row,
|
|
16336
|
+
-- each write that stamps last_updated_at moved a code symbol's entry from a
|
|
16337
|
+
-- scattered leaf: ~19 KB per position-only symbol rewrite on a 1.1 GB store.
|
|
16338
|
+
-- label stays the leading column so label = 'Problem' plans without a sort.
|
|
16339
|
+
DROP INDEX IF EXISTS nodes_live_recent;
|
|
16340
|
+
CREATE INDEX IF NOT EXISTS nodes_live_problem_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL AND label = 'Problem';
|
|
16336
16341
|
-- "Anything created since the watermark?" guards (the fold, regression and
|
|
16337
16342
|
-- canonical-bind sweeps run one per session boundary). Without it the planner
|
|
16338
16343
|
-- walks nodes_live and reads every live row of the label to test created_at:
|
|
@@ -16406,24 +16411,34 @@ CREATE INDEX IF NOT EXISTS nodes_closed_relpath ON nodes(json_extract(attrs_json
|
|
|
16406
16411
|
-- Live rows by name, for looking up the few symbols a statement mentions
|
|
16407
16412
|
-- instead of hydrating every code symbol per minted problem.
|
|
16408
16413
|
CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
|
|
16414
|
+
-- The six single-attr lookups below are partial on the attr being present: each
|
|
16415
|
+
-- is read only by equality (or IN), which implies it. Keyed on NULL for the ~95%
|
|
16416
|
+
-- of rows that lack the attr, every write to such a row's attrs rewrote its NULL
|
|
16417
|
+
-- entry: ~25 KB per position-only symbol rewrite on a 1.1 GB store (2026-10-21).
|
|
16418
|
+
DROP INDEX IF EXISTS nodes_anchor_hint_path;
|
|
16419
|
+
DROP INDEX IF EXISTS nodes_command_signature;
|
|
16420
|
+
DROP INDEX IF EXISTS nodes_diag_command;
|
|
16421
|
+
DROP INDEX IF EXISTS nodes_lsp_rel_path;
|
|
16422
|
+
DROP INDEX IF EXISTS nodes_cloud_node_id;
|
|
16423
|
+
DROP INDEX IF EXISTS nodes_enrichment_pending;
|
|
16409
16424
|
-- Problems by the file their anchor hint names: the harvest upgrades hints for
|
|
16410
16425
|
-- every edited file of every turn, and a scan parses every Problem's attrs.
|
|
16411
|
-
CREATE INDEX IF NOT EXISTS
|
|
16426
|
+
CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path_v2 ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint.path') IS NOT NULL;
|
|
16412
16427
|
-- Problems by the command / checker / file that opened them. The generalizer
|
|
16413
16428
|
-- looked these up per shell or tool EVENT by hydrating every Problem: ~12s of
|
|
16414
16429
|
-- every minute of daemon main-thread time on a 3.4k-Problem store (2026-10-05).
|
|
16415
|
-
CREATE INDEX IF NOT EXISTS
|
|
16416
|
-
CREATE INDEX IF NOT EXISTS
|
|
16417
|
-
CREATE INDEX IF NOT EXISTS
|
|
16430
|
+
CREATE INDEX IF NOT EXISTS nodes_command_signature_v2 ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.commandSignature') IS NOT NULL;
|
|
16431
|
+
CREATE INDEX IF NOT EXISTS nodes_diag_command_v2 ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.diagCommand') IS NOT NULL;
|
|
16432
|
+
CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path_v2 ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.lspRelPath') IS NOT NULL;
|
|
16418
16433
|
-- Local nodes by the cloud twin their upload receipt recorded. Every burst /
|
|
16419
16434
|
-- search fusion folds the Network copy of the caller's own knowledge back onto
|
|
16420
16435
|
-- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
|
|
16421
|
-
CREATE INDEX IF NOT EXISTS
|
|
16436
|
+
CREATE INDEX IF NOT EXISTS nodes_cloud_node_id_v2 ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.cloudNodeId') IS NOT NULL;
|
|
16422
16437
|
-- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
|
|
16423
16438
|
-- tool response (the enrichment piggyback), so it must not parse every
|
|
16424
|
-
-- Solution's and Problem's attrs per call.
|
|
16439
|
+
-- Solution's and Problem's attrs per call. Any version: the Problem pull reads
|
|
16425
16440
|
-- closed versions too.
|
|
16426
|
-
CREATE INDEX IF NOT EXISTS
|
|
16441
|
+
CREATE INDEX IF NOT EXISTS nodes_enrichment_pending_v2 ON nodes(json_extract(attrs_json, '$.enrichmentPending')) WHERE json_extract(attrs_json, '$.enrichmentPending') IS NOT NULL;
|
|
16427
16442
|
-- Revisit flags, any label, any version: listNeedsRevisit's as-of read runs on
|
|
16428
16443
|
-- every context render and otherwise json-checks every row (10.3 s on a 324k-row
|
|
16429
16444
|
-- store, to return zero flags). The predicate must stay textually identical to
|
|
@@ -16489,6 +16504,30 @@ function rowToEdge(r) {
|
|
|
16489
16504
|
}
|
|
16490
16505
|
var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
|
|
16491
16506
|
var NODE_LABELS_CHUNK = 256;
|
|
16507
|
+
var NODE_UPSERT_COLUMNS = [
|
|
16508
|
+
"label",
|
|
16509
|
+
"description",
|
|
16510
|
+
"extraction_confidence",
|
|
16511
|
+
"embedding_json",
|
|
16512
|
+
"embedding_blob",
|
|
16513
|
+
"cumulative_surprise",
|
|
16514
|
+
"peak_surprise",
|
|
16515
|
+
"cumulative_hits",
|
|
16516
|
+
"last_updated_at",
|
|
16517
|
+
"memory_tier",
|
|
16518
|
+
"page_rank",
|
|
16519
|
+
"is_landmark",
|
|
16520
|
+
"community",
|
|
16521
|
+
"stability",
|
|
16522
|
+
"last_reinforced_at_seq",
|
|
16523
|
+
"attrs_json"
|
|
16524
|
+
];
|
|
16525
|
+
var EDGE_UPSERT_COLUMNS = ["confidence", "last_seen_at", "nav_successes", "nav_failures", "valid_to", "attrs_json"];
|
|
16526
|
+
function sameSqlValue(a, b) {
|
|
16527
|
+
if (a === b || a == null && b == null) return true;
|
|
16528
|
+
if (a instanceof Uint8Array && b instanceof Uint8Array) return Buffer.compare(a, b) === 0;
|
|
16529
|
+
return false;
|
|
16530
|
+
}
|
|
16492
16531
|
var PRIOR_TAG_MARKER = '"priorTag":true';
|
|
16493
16532
|
var SqliteGraphStore = class {
|
|
16494
16533
|
db;
|
|
@@ -16496,6 +16535,8 @@ var SqliteGraphStore = class {
|
|
|
16496
16535
|
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
16497
16536
|
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
16498
16537
|
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
16538
|
+
/** Narrow merge updates, keyed by table and the columns they set. */
|
|
16539
|
+
changedColumnStmts = /* @__PURE__ */ new Map();
|
|
16499
16540
|
stmts;
|
|
16500
16541
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
16501
16542
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -16579,6 +16620,8 @@ var SqliteGraphStore = class {
|
|
|
16579
16620
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
16580
16621
|
attrs_json = excluded.attrs_json
|
|
16581
16622
|
`),
|
|
16623
|
+
nodeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${NODE_UPSERT_COLUMNS.join(", ")} FROM nodes WHERE id = ?`),
|
|
16624
|
+
edgeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${EDGE_UPSERT_COLUMNS.join(", ")} FROM edges WHERE from_id = ? AND type = ? AND to_id = ?`),
|
|
16582
16625
|
getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
|
|
16583
16626
|
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
16584
16627
|
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
@@ -16947,7 +16990,7 @@ var SqliteGraphStore = class {
|
|
|
16947
16990
|
}
|
|
16948
16991
|
mergeNode(node) {
|
|
16949
16992
|
this.mutations++;
|
|
16950
|
-
|
|
16993
|
+
const values = {
|
|
16951
16994
|
id: node.id,
|
|
16952
16995
|
label: node.label,
|
|
16953
16996
|
description: node.description,
|
|
@@ -16974,7 +17017,23 @@ var SqliteGraphStore = class {
|
|
|
16974
17017
|
created_at_seq: node.createdAtSeq ?? null,
|
|
16975
17018
|
last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
|
|
16976
17019
|
attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
|
|
16977
|
-
}
|
|
17020
|
+
};
|
|
17021
|
+
const current = this.stmts.nodeUpsertTarget.get(node.id);
|
|
17022
|
+
if (current) this.writeChangedColumns("nodes", NODE_UPSERT_COLUMNS, current, values);
|
|
17023
|
+
else this.stmts.insertNode.run(values);
|
|
17024
|
+
}
|
|
17025
|
+
/** What the upsert's ON CONFLICT would write over `current`, setting only the
|
|
17026
|
+
* columns whose value moves (NODE_UPSERT_COLUMNS says why). */
|
|
17027
|
+
writeChangedColumns(table, columns2, current, next) {
|
|
17028
|
+
const changed = columns2.filter((c) => !sameSqlValue(current[c], next[c]));
|
|
17029
|
+
if (changed.length === 0) return;
|
|
17030
|
+
const key = `${table}:${changed.join(",")}`;
|
|
17031
|
+
let stmt2 = this.changedColumnStmts.get(key);
|
|
17032
|
+
if (!stmt2) {
|
|
17033
|
+
stmt2 = this.db.prepare(`UPDATE ${table} SET ${changed.map((c) => `${c} = :${c}`).join(", ")} WHERE rowid = :rid`);
|
|
17034
|
+
this.changedColumnStmts.set(key, stmt2);
|
|
17035
|
+
}
|
|
17036
|
+
stmt2.run({ rid: current["rid"], ...Object.fromEntries(changed.map((c) => [c, next[c]])) });
|
|
16978
17037
|
}
|
|
16979
17038
|
/** mergeNode, unless the live row already holds this content. Content is what
|
|
16980
17039
|
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
@@ -17108,7 +17167,7 @@ var SqliteGraphStore = class {
|
|
|
17108
17167
|
return;
|
|
17109
17168
|
}
|
|
17110
17169
|
this.mutations++;
|
|
17111
|
-
|
|
17170
|
+
const values = {
|
|
17112
17171
|
id: edge.id,
|
|
17113
17172
|
from_id: edge.from,
|
|
17114
17173
|
to_id: edge.to,
|
|
@@ -17122,6 +17181,16 @@ var SqliteGraphStore = class {
|
|
|
17122
17181
|
valid_from: edge.validFrom ?? edge.createdAt,
|
|
17123
17182
|
valid_to: edge.validTo ?? null,
|
|
17124
17183
|
attrs_json: JSON.stringify(edge.attrs ?? {})
|
|
17184
|
+
};
|
|
17185
|
+
const current = this.stmts.edgeUpsertTarget.get(edge.from, edge.type, edge.to);
|
|
17186
|
+
if (!current) {
|
|
17187
|
+
this.stmts.insertEdge.run(values);
|
|
17188
|
+
return;
|
|
17189
|
+
}
|
|
17190
|
+
this.writeChangedColumns("edges", EDGE_UPSERT_COLUMNS, current, {
|
|
17191
|
+
...values,
|
|
17192
|
+
confidence: Math.max(current["confidence"], edge.confidence),
|
|
17193
|
+
valid_to: null
|
|
17125
17194
|
});
|
|
17126
17195
|
}
|
|
17127
17196
|
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
@@ -17336,8 +17405,8 @@ var SqliteGraphStore = class {
|
|
|
17336
17405
|
after = rows[rows.length - 1].rid;
|
|
17337
17406
|
}
|
|
17338
17407
|
}
|
|
17339
|
-
|
|
17340
|
-
const rows = this.db.prepare("SELECT id FROM nodes WHERE label =
|
|
17408
|
+
recentProblemIds(limit) {
|
|
17409
|
+
const rows = this.db.prepare("SELECT id FROM nodes WHERE label = 'Problem' AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(Math.max(0, limit));
|
|
17341
17410
|
return rows.map((r) => r.id);
|
|
17342
17411
|
}
|
|
17343
17412
|
recentOpenProblemCandidates(opts) {
|
|
@@ -17778,7 +17847,7 @@ var SqliteGraphStore = class {
|
|
|
17778
17847
|
const slice = cloudIds.slice(i, i + CHUNK);
|
|
17779
17848
|
if (slice.length === 0) continue;
|
|
17780
17849
|
const rows = this.db.prepare(
|
|
17781
|
-
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY
|
|
17850
|
+
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id_v2 WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
|
|
17782
17851
|
).all(...slice);
|
|
17783
17852
|
for (const r of rows) out.set(r.cloud, [...out.get(r.cloud) ?? [], r.id]);
|
|
17784
17853
|
}
|
package/package.json
CHANGED
package/pass-worker.mjs
CHANGED
|
@@ -27304,7 +27304,12 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
|
27304
27304
|
var TEMPORAL_INDEX_SQL = `
|
|
27305
27305
|
CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
|
|
27306
27306
|
CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
|
|
27307
|
-
|
|
27307
|
+
-- Problems by recency (the only label read newest-first). Over every live row,
|
|
27308
|
+
-- each write that stamps last_updated_at moved a code symbol's entry from a
|
|
27309
|
+
-- scattered leaf: ~19 KB per position-only symbol rewrite on a 1.1 GB store.
|
|
27310
|
+
-- label stays the leading column so label = 'Problem' plans without a sort.
|
|
27311
|
+
DROP INDEX IF EXISTS nodes_live_recent;
|
|
27312
|
+
CREATE INDEX IF NOT EXISTS nodes_live_problem_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL AND label = 'Problem';
|
|
27308
27313
|
-- "Anything created since the watermark?" guards (the fold, regression and
|
|
27309
27314
|
-- canonical-bind sweeps run one per session boundary). Without it the planner
|
|
27310
27315
|
-- walks nodes_live and reads every live row of the label to test created_at:
|
|
@@ -27378,24 +27383,34 @@ CREATE INDEX IF NOT EXISTS nodes_closed_relpath ON nodes(json_extract(attrs_json
|
|
|
27378
27383
|
-- Live rows by name, for looking up the few symbols a statement mentions
|
|
27379
27384
|
-- instead of hydrating every code symbol per minted problem.
|
|
27380
27385
|
CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
|
|
27386
|
+
-- The six single-attr lookups below are partial on the attr being present: each
|
|
27387
|
+
-- is read only by equality (or IN), which implies it. Keyed on NULL for the ~95%
|
|
27388
|
+
-- of rows that lack the attr, every write to such a row's attrs rewrote its NULL
|
|
27389
|
+
-- entry: ~25 KB per position-only symbol rewrite on a 1.1 GB store (2026-10-21).
|
|
27390
|
+
DROP INDEX IF EXISTS nodes_anchor_hint_path;
|
|
27391
|
+
DROP INDEX IF EXISTS nodes_command_signature;
|
|
27392
|
+
DROP INDEX IF EXISTS nodes_diag_command;
|
|
27393
|
+
DROP INDEX IF EXISTS nodes_lsp_rel_path;
|
|
27394
|
+
DROP INDEX IF EXISTS nodes_cloud_node_id;
|
|
27395
|
+
DROP INDEX IF EXISTS nodes_enrichment_pending;
|
|
27381
27396
|
-- Problems by the file their anchor hint names: the harvest upgrades hints for
|
|
27382
27397
|
-- every edited file of every turn, and a scan parses every Problem's attrs.
|
|
27383
|
-
CREATE INDEX IF NOT EXISTS
|
|
27398
|
+
CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path_v2 ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint.path') IS NOT NULL;
|
|
27384
27399
|
-- Problems by the command / checker / file that opened them. The generalizer
|
|
27385
27400
|
-- looked these up per shell or tool EVENT by hydrating every Problem: ~12s of
|
|
27386
27401
|
-- every minute of daemon main-thread time on a 3.4k-Problem store (2026-10-05).
|
|
27387
|
-
CREATE INDEX IF NOT EXISTS
|
|
27388
|
-
CREATE INDEX IF NOT EXISTS
|
|
27389
|
-
CREATE INDEX IF NOT EXISTS
|
|
27402
|
+
CREATE INDEX IF NOT EXISTS nodes_command_signature_v2 ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.commandSignature') IS NOT NULL;
|
|
27403
|
+
CREATE INDEX IF NOT EXISTS nodes_diag_command_v2 ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.diagCommand') IS NOT NULL;
|
|
27404
|
+
CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path_v2 ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.lspRelPath') IS NOT NULL;
|
|
27390
27405
|
-- Local nodes by the cloud twin their upload receipt recorded. Every burst /
|
|
27391
27406
|
-- search fusion folds the Network copy of the caller's own knowledge back onto
|
|
27392
27407
|
-- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
|
|
27393
|
-
CREATE INDEX IF NOT EXISTS
|
|
27408
|
+
CREATE INDEX IF NOT EXISTS nodes_cloud_node_id_v2 ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.cloudNodeId') IS NOT NULL;
|
|
27394
27409
|
-- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
|
|
27395
27410
|
-- tool response (the enrichment piggyback), so it must not parse every
|
|
27396
|
-
-- Solution's and Problem's attrs per call.
|
|
27411
|
+
-- Solution's and Problem's attrs per call. Any version: the Problem pull reads
|
|
27397
27412
|
-- closed versions too.
|
|
27398
|
-
CREATE INDEX IF NOT EXISTS
|
|
27413
|
+
CREATE INDEX IF NOT EXISTS nodes_enrichment_pending_v2 ON nodes(json_extract(attrs_json, '$.enrichmentPending')) WHERE json_extract(attrs_json, '$.enrichmentPending') IS NOT NULL;
|
|
27399
27414
|
-- Revisit flags, any label, any version: listNeedsRevisit's as-of read runs on
|
|
27400
27415
|
-- every context render and otherwise json-checks every row (10.3 s on a 324k-row
|
|
27401
27416
|
-- store, to return zero flags). The predicate must stay textually identical to
|
|
@@ -27461,6 +27476,30 @@ function rowToEdge(r) {
|
|
|
27461
27476
|
}
|
|
27462
27477
|
var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
|
|
27463
27478
|
var NODE_LABELS_CHUNK = 256;
|
|
27479
|
+
var NODE_UPSERT_COLUMNS = [
|
|
27480
|
+
"label",
|
|
27481
|
+
"description",
|
|
27482
|
+
"extraction_confidence",
|
|
27483
|
+
"embedding_json",
|
|
27484
|
+
"embedding_blob",
|
|
27485
|
+
"cumulative_surprise",
|
|
27486
|
+
"peak_surprise",
|
|
27487
|
+
"cumulative_hits",
|
|
27488
|
+
"last_updated_at",
|
|
27489
|
+
"memory_tier",
|
|
27490
|
+
"page_rank",
|
|
27491
|
+
"is_landmark",
|
|
27492
|
+
"community",
|
|
27493
|
+
"stability",
|
|
27494
|
+
"last_reinforced_at_seq",
|
|
27495
|
+
"attrs_json"
|
|
27496
|
+
];
|
|
27497
|
+
var EDGE_UPSERT_COLUMNS = ["confidence", "last_seen_at", "nav_successes", "nav_failures", "valid_to", "attrs_json"];
|
|
27498
|
+
function sameSqlValue(a, b) {
|
|
27499
|
+
if (a === b || a == null && b == null) return true;
|
|
27500
|
+
if (a instanceof Uint8Array && b instanceof Uint8Array) return Buffer.compare(a, b) === 0;
|
|
27501
|
+
return false;
|
|
27502
|
+
}
|
|
27464
27503
|
var PRIOR_TAG_MARKER = '"priorTag":true';
|
|
27465
27504
|
var SqliteGraphStore = class {
|
|
27466
27505
|
db;
|
|
@@ -27468,6 +27507,8 @@ var SqliteGraphStore = class {
|
|
|
27468
27507
|
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
27469
27508
|
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
27470
27509
|
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
27510
|
+
/** Narrow merge updates, keyed by table and the columns they set. */
|
|
27511
|
+
changedColumnStmts = /* @__PURE__ */ new Map();
|
|
27471
27512
|
stmts;
|
|
27472
27513
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
27473
27514
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -27551,6 +27592,8 @@ var SqliteGraphStore = class {
|
|
|
27551
27592
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
27552
27593
|
attrs_json = excluded.attrs_json
|
|
27553
27594
|
`),
|
|
27595
|
+
nodeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${NODE_UPSERT_COLUMNS.join(", ")} FROM nodes WHERE id = ?`),
|
|
27596
|
+
edgeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${EDGE_UPSERT_COLUMNS.join(", ")} FROM edges WHERE from_id = ? AND type = ? AND to_id = ?`),
|
|
27554
27597
|
getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
|
|
27555
27598
|
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
27556
27599
|
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
@@ -27919,7 +27962,7 @@ var SqliteGraphStore = class {
|
|
|
27919
27962
|
}
|
|
27920
27963
|
mergeNode(node) {
|
|
27921
27964
|
this.mutations++;
|
|
27922
|
-
|
|
27965
|
+
const values = {
|
|
27923
27966
|
id: node.id,
|
|
27924
27967
|
label: node.label,
|
|
27925
27968
|
description: node.description,
|
|
@@ -27946,7 +27989,23 @@ var SqliteGraphStore = class {
|
|
|
27946
27989
|
created_at_seq: node.createdAtSeq ?? null,
|
|
27947
27990
|
last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
|
|
27948
27991
|
attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
|
|
27949
|
-
}
|
|
27992
|
+
};
|
|
27993
|
+
const current = this.stmts.nodeUpsertTarget.get(node.id);
|
|
27994
|
+
if (current) this.writeChangedColumns("nodes", NODE_UPSERT_COLUMNS, current, values);
|
|
27995
|
+
else this.stmts.insertNode.run(values);
|
|
27996
|
+
}
|
|
27997
|
+
/** What the upsert's ON CONFLICT would write over `current`, setting only the
|
|
27998
|
+
* columns whose value moves (NODE_UPSERT_COLUMNS says why). */
|
|
27999
|
+
writeChangedColumns(table, columns2, current, next) {
|
|
28000
|
+
const changed = columns2.filter((c) => !sameSqlValue(current[c], next[c]));
|
|
28001
|
+
if (changed.length === 0) return;
|
|
28002
|
+
const key = `${table}:${changed.join(",")}`;
|
|
28003
|
+
let stmt2 = this.changedColumnStmts.get(key);
|
|
28004
|
+
if (!stmt2) {
|
|
28005
|
+
stmt2 = this.db.prepare(`UPDATE ${table} SET ${changed.map((c) => `${c} = :${c}`).join(", ")} WHERE rowid = :rid`);
|
|
28006
|
+
this.changedColumnStmts.set(key, stmt2);
|
|
28007
|
+
}
|
|
28008
|
+
stmt2.run({ rid: current["rid"], ...Object.fromEntries(changed.map((c) => [c, next[c]])) });
|
|
27950
28009
|
}
|
|
27951
28010
|
/** mergeNode, unless the live row already holds this content. Content is what
|
|
27952
28011
|
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
@@ -28080,7 +28139,7 @@ var SqliteGraphStore = class {
|
|
|
28080
28139
|
return;
|
|
28081
28140
|
}
|
|
28082
28141
|
this.mutations++;
|
|
28083
|
-
|
|
28142
|
+
const values = {
|
|
28084
28143
|
id: edge.id,
|
|
28085
28144
|
from_id: edge.from,
|
|
28086
28145
|
to_id: edge.to,
|
|
@@ -28094,6 +28153,16 @@ var SqliteGraphStore = class {
|
|
|
28094
28153
|
valid_from: edge.validFrom ?? edge.createdAt,
|
|
28095
28154
|
valid_to: edge.validTo ?? null,
|
|
28096
28155
|
attrs_json: JSON.stringify(edge.attrs ?? {})
|
|
28156
|
+
};
|
|
28157
|
+
const current = this.stmts.edgeUpsertTarget.get(edge.from, edge.type, edge.to);
|
|
28158
|
+
if (!current) {
|
|
28159
|
+
this.stmts.insertEdge.run(values);
|
|
28160
|
+
return;
|
|
28161
|
+
}
|
|
28162
|
+
this.writeChangedColumns("edges", EDGE_UPSERT_COLUMNS, current, {
|
|
28163
|
+
...values,
|
|
28164
|
+
confidence: Math.max(current["confidence"], edge.confidence),
|
|
28165
|
+
valid_to: null
|
|
28097
28166
|
});
|
|
28098
28167
|
}
|
|
28099
28168
|
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
@@ -28308,8 +28377,8 @@ var SqliteGraphStore = class {
|
|
|
28308
28377
|
after = rows[rows.length - 1].rid;
|
|
28309
28378
|
}
|
|
28310
28379
|
}
|
|
28311
|
-
|
|
28312
|
-
const rows = this.db.prepare("SELECT id FROM nodes WHERE label =
|
|
28380
|
+
recentProblemIds(limit) {
|
|
28381
|
+
const rows = this.db.prepare("SELECT id FROM nodes WHERE label = 'Problem' AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(Math.max(0, limit));
|
|
28313
28382
|
return rows.map((r) => r.id);
|
|
28314
28383
|
}
|
|
28315
28384
|
recentOpenProblemCandidates(opts) {
|
|
@@ -28750,7 +28819,7 @@ var SqliteGraphStore = class {
|
|
|
28750
28819
|
const slice = cloudIds.slice(i2, i2 + CHUNK);
|
|
28751
28820
|
if (slice.length === 0) continue;
|
|
28752
28821
|
const rows = this.db.prepare(
|
|
28753
|
-
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY
|
|
28822
|
+
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id_v2 WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
|
|
28754
28823
|
).all(...slice);
|
|
28755
28824
|
for (const r of rows) out2.set(r.cloud, [...out2.get(r.cloud) ?? [], r.id]);
|
|
28756
28825
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
51434f3e703060bee5abb7c7eebe1446b66132c9cb7e43ae630daf855d83bc02 .codex-plugin/plugin.json
|
|
2
2
|
a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
|
|
3
3
|
fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
|
|
4
4
|
73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js
|