@inerrata-corporation/errata 2.0.22-dev.4638 → 2.0.22-dev.4652
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 +180 -15
- package/errata.mjs +218 -28
- package/generalize-worker.mjs +134 -6
- package/package.json +1 -1
- package/pass-worker.mjs +77 -5
- package/resources/context-plugin/.codex-plugin/plugin.json +1 -1
- package/resources/context-plugin/SHA256SUMS +1 -1
package/consolidate-worker.mjs
CHANGED
|
@@ -17596,8 +17596,10 @@ function column(c) {
|
|
|
17596
17596
|
function compileNodeWhere(q) {
|
|
17597
17597
|
if (q.labels.length === 0) throw new Error("selectNodes: no labels");
|
|
17598
17598
|
const params = [...q.labels];
|
|
17599
|
-
const
|
|
17600
|
-
|
|
17599
|
+
const byId = (q.where ?? []).some((w) => "idIn" in w);
|
|
17600
|
+
const unindexed = byId ? "+" : "";
|
|
17601
|
+
const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
|
|
17602
|
+
if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
|
|
17601
17603
|
for (const w of q.where ?? []) {
|
|
17602
17604
|
if ("embedding" in w) {
|
|
17603
17605
|
conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
|
|
@@ -17649,6 +17651,14 @@ function compileNodeWhere(q) {
|
|
|
17649
17651
|
function attrValueSql(p) {
|
|
17650
17652
|
return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
|
|
17651
17653
|
}
|
|
17654
|
+
function nodeSelectFrom(q) {
|
|
17655
|
+
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
17656
|
+
const where = q.where ?? [];
|
|
17657
|
+
if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
|
|
17658
|
+
return "nodes INDEXED BY nodes_live_description";
|
|
17659
|
+
}
|
|
17660
|
+
return "nodes";
|
|
17661
|
+
}
|
|
17652
17662
|
function compileNodeSelect(q) {
|
|
17653
17663
|
const { where, params } = compileNodeWhere(q);
|
|
17654
17664
|
const attrs = q.attrs ?? [];
|
|
@@ -17666,7 +17676,7 @@ function compileNodeSelect(q) {
|
|
|
17666
17676
|
}
|
|
17667
17677
|
const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
|
|
17668
17678
|
const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
|
|
17669
|
-
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM
|
|
17679
|
+
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
|
|
17670
17680
|
return { sql, params: [...params, ...orderParams] };
|
|
17671
17681
|
}
|
|
17672
17682
|
function decodeAttr(value, type) {
|
|
@@ -17903,6 +17913,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
|
|
|
17903
17913
|
-- incremental reindex can fetch just the changed files' symbols instead of
|
|
17904
17914
|
-- scanning every versioned node. Partial: only live rows are ever queried.
|
|
17905
17915
|
CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
|
|
17916
|
+
-- Symbols by qname and doc comments by the symbol they attach to: the drain's
|
|
17917
|
+
-- symbol lexicon resolves the names in the text it ships one at a time, instead
|
|
17918
|
+
-- of reading every Function/Method/Class/Interface/Const and Comment row on each
|
|
17919
|
+
-- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
|
|
17920
|
+
CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
|
|
17921
|
+
CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
|
|
17922
|
+
-- The attrs the cloud drain gates every instance row on, so it reads them from
|
|
17923
|
+
-- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
|
|
17924
|
+
-- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
|
|
17925
|
+
-- two attrs read ~220 MB per push to find the few local rows that can ship.
|
|
17926
|
+
-- created_at leads so the index order is findNodeFieldsByLabel's.
|
|
17927
|
+
CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
|
|
17906
17928
|
-- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
|
|
17907
17929
|
-- live-only index above cannot serve it, so it scanned the whole table (~275 MB
|
|
17908
17930
|
-- of a 1.1 GB store per reindex, 2026-10-20).
|
|
@@ -18253,6 +18275,52 @@ var SqliteGraphStore = class {
|
|
|
18253
18275
|
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
18254
18276
|
`).all();
|
|
18255
18277
|
}
|
|
18278
|
+
strandedSymbolsForFiles(fileIds) {
|
|
18279
|
+
const out = [];
|
|
18280
|
+
for (let i = 0; i < fileIds.length; i += 500) {
|
|
18281
|
+
const slice = fileIds.slice(i, i + 500);
|
|
18282
|
+
if (slice.length === 0) continue;
|
|
18283
|
+
out.push(...this.db.prepare(`
|
|
18284
|
+
SELECT f.id AS fileId, e.to_id AS symbolId
|
|
18285
|
+
FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
|
|
18286
|
+
LEFT JOIN nodes t ON t.id = e.to_id
|
|
18287
|
+
WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
|
|
18288
|
+
AND e.type IN ('DEFINES', 'CONTAINS')
|
|
18289
|
+
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
18290
|
+
`).all(...slice));
|
|
18291
|
+
}
|
|
18292
|
+
return out;
|
|
18293
|
+
}
|
|
18294
|
+
firstLiveByNameOrQname(labels, name) {
|
|
18295
|
+
if (labels.length === 0) return null;
|
|
18296
|
+
const inLabels = labels.map(() => "?").join(",");
|
|
18297
|
+
const probe = (index, match) => this.db.prepare(
|
|
18298
|
+
`SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
|
|
18299
|
+
WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
|
|
18300
|
+
).all(name, ...labels);
|
|
18301
|
+
const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
|
|
18302
|
+
const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
|
|
18303
|
+
let best;
|
|
18304
|
+
for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
|
|
18305
|
+
if (!best || before(rank(r), rank(best))) best = r;
|
|
18306
|
+
}
|
|
18307
|
+
return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
|
|
18308
|
+
}
|
|
18309
|
+
instanceGateRows(label) {
|
|
18310
|
+
return this.db.prepare(
|
|
18311
|
+
`SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
|
|
18312
|
+
json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
|
|
18313
|
+
FROM nodes INDEXED BY nodes_live_instance_gate
|
|
18314
|
+
WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
|
|
18315
|
+
ORDER BY label, created_at, rowid`
|
|
18316
|
+
).all(label);
|
|
18317
|
+
}
|
|
18318
|
+
liveCommentsAttachedTo(name) {
|
|
18319
|
+
return this.db.prepare(
|
|
18320
|
+
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
18321
|
+
WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
|
|
18322
|
+
).all(name);
|
|
18323
|
+
}
|
|
18256
18324
|
strandedEdges() {
|
|
18257
18325
|
return countStrandedEdges(this.db);
|
|
18258
18326
|
}
|
|
@@ -18431,7 +18499,11 @@ var SqliteGraphStore = class {
|
|
|
18431
18499
|
if (!stmt2) {
|
|
18432
18500
|
const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
|
|
18433
18501
|
stmt2 = this.db.prepare(
|
|
18434
|
-
|
|
18502
|
+
// Ordered, not left to the planner: readers take "first row wins" (the
|
|
18503
|
+
// symbol lexicon's name winner and doc pick), and the order otherwise
|
|
18504
|
+
// followed whichever partial index the planner chose. nodes_live_created
|
|
18505
|
+
// serves this order without a sort.
|
|
18506
|
+
`SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
|
|
18435
18507
|
);
|
|
18436
18508
|
this.fieldStmts.set(cacheKey, stmt2);
|
|
18437
18509
|
}
|
|
@@ -18457,7 +18529,7 @@ var SqliteGraphStore = class {
|
|
|
18457
18529
|
}
|
|
18458
18530
|
countNodes(q) {
|
|
18459
18531
|
const { where, params } = compileNodeWhere(q);
|
|
18460
|
-
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM
|
|
18532
|
+
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
|
|
18461
18533
|
}
|
|
18462
18534
|
setCommunity(id, community) {
|
|
18463
18535
|
this.stmts.setCommunity.run({ id, v: community });
|
|
@@ -20877,6 +20949,62 @@ function buildSymbolLexicon(store) {
|
|
|
20877
20949
|
}
|
|
20878
20950
|
return lex;
|
|
20879
20951
|
}
|
|
20952
|
+
function lazySymbolLexicon(store) {
|
|
20953
|
+
if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
|
|
20954
|
+
const labels = SYMBOL_LABELS.map(([label]) => label);
|
|
20955
|
+
const kindOf = new Map(SYMBOL_LABELS.map(([label, kind]) => [label, kind]));
|
|
20956
|
+
const members = /* @__PURE__ */ new Map();
|
|
20957
|
+
const docs = /* @__PURE__ */ new Map();
|
|
20958
|
+
const entries = /* @__PURE__ */ new Map();
|
|
20959
|
+
const member = (name) => {
|
|
20960
|
+
if (!members.has(name)) {
|
|
20961
|
+
const hit = isDistinctiveIdentifier(name) ? store.firstLiveByNameOrQname(labels, name) : null;
|
|
20962
|
+
members.set(name, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
|
|
20963
|
+
}
|
|
20964
|
+
return members.get(name);
|
|
20965
|
+
};
|
|
20966
|
+
const docFor = (name) => {
|
|
20967
|
+
if (!docs.has(name)) {
|
|
20968
|
+
let found;
|
|
20969
|
+
for (const c of store.liveCommentsAttachedTo(name)) {
|
|
20970
|
+
if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
|
|
20971
|
+
const text = typeof c.text === "string" ? c.text : c.description;
|
|
20972
|
+
if (!text?.trim()) continue;
|
|
20973
|
+
found = text.trim().slice(0, MAX_DOC_CHARS);
|
|
20974
|
+
break;
|
|
20975
|
+
}
|
|
20976
|
+
docs.set(name, found);
|
|
20977
|
+
}
|
|
20978
|
+
return docs.get(name);
|
|
20979
|
+
};
|
|
20980
|
+
const isKnown = (tok) => member(tok) !== null;
|
|
20981
|
+
let anySymbol;
|
|
20982
|
+
return {
|
|
20983
|
+
has: isKnown,
|
|
20984
|
+
get(name) {
|
|
20985
|
+
if (!entries.has(name)) {
|
|
20986
|
+
const m = member(name);
|
|
20987
|
+
let entry = null;
|
|
20988
|
+
if (m) {
|
|
20989
|
+
entry = { kind: m.kind };
|
|
20990
|
+
const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
|
|
20991
|
+
for (const phrase of summaryCandidates(name, m.kind, doc)) {
|
|
20992
|
+
if (summaryRejectionReason(phrase, isKnown) === null) {
|
|
20993
|
+
entry.summary = phrase;
|
|
20994
|
+
break;
|
|
20995
|
+
}
|
|
20996
|
+
}
|
|
20997
|
+
}
|
|
20998
|
+
entries.set(name, entry);
|
|
20999
|
+
}
|
|
21000
|
+
return entries.get(name) ?? void 0;
|
|
21001
|
+
},
|
|
21002
|
+
get size() {
|
|
21003
|
+
anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
|
|
21004
|
+
return anySymbol ? 1 : 0;
|
|
21005
|
+
}
|
|
21006
|
+
};
|
|
21007
|
+
}
|
|
20880
21008
|
var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
|
|
20881
21009
|
function codeGraphKey(store) {
|
|
20882
21010
|
const files = store.labelFingerprint?.(["File"]);
|
|
@@ -20888,7 +21016,7 @@ function lexiconsFor(store) {
|
|
|
20888
21016
|
const key = codeGraphKey(store);
|
|
20889
21017
|
const hit = key !== null ? lexiconCache.get(store) : void 0;
|
|
20890
21018
|
if (hit && hit.key === key) return hit;
|
|
20891
|
-
const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
21019
|
+
const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
20892
21020
|
if (key !== null) lexiconCache.set(store, { key, ...built });
|
|
20893
21021
|
return built;
|
|
20894
21022
|
}
|
|
@@ -21127,6 +21255,38 @@ function stripCodebaseScope(scope) {
|
|
|
21127
21255
|
delete s["codebase"];
|
|
21128
21256
|
return s;
|
|
21129
21257
|
}
|
|
21258
|
+
function instanceGateRows(store, label) {
|
|
21259
|
+
return store.instanceGateRows?.(label) ?? store.findNodeFieldsByLabel(label, ["source", "privateFinding", "resolvedAs"]).map((r) => ({
|
|
21260
|
+
id: r.id,
|
|
21261
|
+
source: r.attrs["source"],
|
|
21262
|
+
privateFinding: r.attrs["privateFinding"],
|
|
21263
|
+
resolvedAs: r.attrs["resolvedAs"]
|
|
21264
|
+
}));
|
|
21265
|
+
}
|
|
21266
|
+
var GATE_VIEW_ATTRS = [
|
|
21267
|
+
"privateFinding",
|
|
21268
|
+
"source",
|
|
21269
|
+
"sources",
|
|
21270
|
+
"resolvedAs",
|
|
21271
|
+
"instanceReceipts",
|
|
21272
|
+
"cloudWithdrawn",
|
|
21273
|
+
"withdrawnAtCorrectionSeq",
|
|
21274
|
+
"contributedAtSeq",
|
|
21275
|
+
"cloudNodeId"
|
|
21276
|
+
];
|
|
21277
|
+
var GATE_VIEW_CHUNK = 500;
|
|
21278
|
+
var gateView = /* @__PURE__ */ new WeakSet();
|
|
21279
|
+
function gateViews(store, label, ids) {
|
|
21280
|
+
const byId = /* @__PURE__ */ new Map();
|
|
21281
|
+
for (let i = 0; i < ids.length; i += GATE_VIEW_CHUNK) {
|
|
21282
|
+
for (const r of store.selectNodes({ labels: [label], attrs: GATE_VIEW_ATTRS, where: [{ idIn: ids.slice(i, i + GATE_VIEW_CHUNK) }] })) {
|
|
21283
|
+
const view = { ...r, embedding: [] };
|
|
21284
|
+
gateView.add(view);
|
|
21285
|
+
byId.set(r.id, view);
|
|
21286
|
+
}
|
|
21287
|
+
}
|
|
21288
|
+
return ids.flatMap((id) => byId.get(id) ?? []);
|
|
21289
|
+
}
|
|
21130
21290
|
function contributedWireId(n, destinationKey) {
|
|
21131
21291
|
const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
|
|
21132
21292
|
return typeof cloudId === "string" && cloudId ? cloudId : n.id;
|
|
@@ -21285,7 +21445,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
21285
21445
|
edgesHeldDropped: 0
|
|
21286
21446
|
};
|
|
21287
21447
|
const sharedCauses = /* @__PURE__ */ new Map();
|
|
21288
|
-
const causalQueue = store
|
|
21448
|
+
const causalQueue = instanceGateRows(store, "Problem").filter((r) => r.source !== "cloud" && r.resolvedAs !== PROBLEM_RESOLUTION.FALSE_POSITIVE).map((r) => r.id).filter((id) => ignorePatterns.length === 0 || !ignored(store.getNode(id)?.description ?? ""));
|
|
21289
21449
|
const causalSeen = new Set(causalQueue);
|
|
21290
21450
|
for (let i = 0; i < causalQueue.length; i++) {
|
|
21291
21451
|
const id = causalQueue[i];
|
|
@@ -21301,21 +21461,21 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
21301
21461
|
for (const label of INSTANCE_LABELS) {
|
|
21302
21462
|
const ref = [];
|
|
21303
21463
|
includedByLabel.set(label, ref);
|
|
21304
|
-
const
|
|
21305
|
-
for (const row of store
|
|
21306
|
-
if (row.
|
|
21307
|
-
|
|
21308
|
-
if (n) local.push(n);
|
|
21464
|
+
const localIds = [];
|
|
21465
|
+
for (const row of instanceGateRows(store, label)) {
|
|
21466
|
+
if (row.source !== "cloud") {
|
|
21467
|
+
localIds.push(row.id);
|
|
21309
21468
|
continue;
|
|
21310
21469
|
}
|
|
21311
21470
|
if (seen.has(row.id) || walked.has(row.id)) continue;
|
|
21312
21471
|
walked.add(row.id);
|
|
21313
21472
|
composition.candidates++;
|
|
21314
|
-
if ((row.
|
|
21473
|
+
if ((row.privateFinding || label === "Vulnerability") && !opts.project) composition.privateTargetMissing++;
|
|
21315
21474
|
else composition.cloudSourced++;
|
|
21316
21475
|
}
|
|
21476
|
+
const local = gateViews(store, label, localIds);
|
|
21317
21477
|
const population = label === "RootCause" && opts.sharedStore ? [...local, ...sharedCauses.values()] : local;
|
|
21318
|
-
for (
|
|
21478
|
+
for (let n of population) {
|
|
21319
21479
|
if (seen.has(n.id)) continue;
|
|
21320
21480
|
if (walked.has(n.id)) continue;
|
|
21321
21481
|
walked.add(n.id);
|
|
@@ -21351,6 +21511,11 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
21351
21511
|
semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
|
|
21352
21512
|
continue;
|
|
21353
21513
|
}
|
|
21514
|
+
if (gateView.has(n)) {
|
|
21515
|
+
const full = store.getNode(n.id);
|
|
21516
|
+
if (!full) continue;
|
|
21517
|
+
n = full;
|
|
21518
|
+
}
|
|
21354
21519
|
if (isEditPlaceholderSolution(n)) {
|
|
21355
21520
|
composition.autoMint++;
|
|
21356
21521
|
continue;
|
|
@@ -21754,7 +21919,7 @@ function runPass(msg) {
|
|
|
21754
21919
|
return revisitContradictedPrinciples(shared, msg.ts ?? Date.now());
|
|
21755
21920
|
case "strandedCount": {
|
|
21756
21921
|
if (!msg.castaliaPath) throw new Error("strandedCount: castaliaPath required");
|
|
21757
|
-
return findStrandedSymbols(storeAt(msg.castaliaPath))
|
|
21922
|
+
return findStrandedSymbols(storeAt(msg.castaliaPath));
|
|
21758
21923
|
}
|
|
21759
21924
|
case "drainContext": {
|
|
21760
21925
|
if (!msg.castaliaPath) throw new Error("drainContext: castaliaPath required");
|
package/errata.mjs
CHANGED
|
@@ -20393,8 +20393,10 @@ function column(c) {
|
|
|
20393
20393
|
function compileNodeWhere(q) {
|
|
20394
20394
|
if (q.labels.length === 0) throw new Error("selectNodes: no labels");
|
|
20395
20395
|
const params = [...q.labels];
|
|
20396
|
-
const
|
|
20397
|
-
|
|
20396
|
+
const byId = (q.where ?? []).some((w) => "idIn" in w);
|
|
20397
|
+
const unindexed = byId ? "+" : "";
|
|
20398
|
+
const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
|
|
20399
|
+
if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
|
|
20398
20400
|
for (const w of q.where ?? []) {
|
|
20399
20401
|
if ("embedding" in w) {
|
|
20400
20402
|
conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
|
|
@@ -20446,6 +20448,14 @@ function compileNodeWhere(q) {
|
|
|
20446
20448
|
function attrValueSql(p) {
|
|
20447
20449
|
return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
|
|
20448
20450
|
}
|
|
20451
|
+
function nodeSelectFrom(q) {
|
|
20452
|
+
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
20453
|
+
const where = q.where ?? [];
|
|
20454
|
+
if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
|
|
20455
|
+
return "nodes INDEXED BY nodes_live_description";
|
|
20456
|
+
}
|
|
20457
|
+
return "nodes";
|
|
20458
|
+
}
|
|
20449
20459
|
function compileNodeSelect(q) {
|
|
20450
20460
|
const { where, params } = compileNodeWhere(q);
|
|
20451
20461
|
const attrs = q.attrs ?? [];
|
|
@@ -20463,7 +20473,7 @@ function compileNodeSelect(q) {
|
|
|
20463
20473
|
}
|
|
20464
20474
|
const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
|
|
20465
20475
|
const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
|
|
20466
|
-
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM
|
|
20476
|
+
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
|
|
20467
20477
|
return { sql, params: [...params, ...orderParams] };
|
|
20468
20478
|
}
|
|
20469
20479
|
function decodeAttr(value, type) {
|
|
@@ -20812,6 +20822,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
|
|
|
20812
20822
|
-- incremental reindex can fetch just the changed files' symbols instead of
|
|
20813
20823
|
-- scanning every versioned node. Partial: only live rows are ever queried.
|
|
20814
20824
|
CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
|
|
20825
|
+
-- Symbols by qname and doc comments by the symbol they attach to: the drain's
|
|
20826
|
+
-- symbol lexicon resolves the names in the text it ships one at a time, instead
|
|
20827
|
+
-- of reading every Function/Method/Class/Interface/Const and Comment row on each
|
|
20828
|
+
-- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
|
|
20829
|
+
CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
|
|
20830
|
+
CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
|
|
20831
|
+
-- The attrs the cloud drain gates every instance row on, so it reads them from
|
|
20832
|
+
-- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
|
|
20833
|
+
-- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
|
|
20834
|
+
-- two attrs read ~220 MB per push to find the few local rows that can ship.
|
|
20835
|
+
-- created_at leads so the index order is findNodeFieldsByLabel's.
|
|
20836
|
+
CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
|
|
20815
20837
|
-- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
|
|
20816
20838
|
-- live-only index above cannot serve it, so it scanned the whole table (~275 MB
|
|
20817
20839
|
-- of a 1.1 GB store per reindex, 2026-10-20).
|
|
@@ -21105,6 +21127,52 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21105
21127
|
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
21106
21128
|
`).all();
|
|
21107
21129
|
}
|
|
21130
|
+
strandedSymbolsForFiles(fileIds) {
|
|
21131
|
+
const out2 = [];
|
|
21132
|
+
for (let i2 = 0; i2 < fileIds.length; i2 += 500) {
|
|
21133
|
+
const slice = fileIds.slice(i2, i2 + 500);
|
|
21134
|
+
if (slice.length === 0) continue;
|
|
21135
|
+
out2.push(...this.db.prepare(`
|
|
21136
|
+
SELECT f.id AS fileId, e.to_id AS symbolId
|
|
21137
|
+
FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
|
|
21138
|
+
LEFT JOIN nodes t ON t.id = e.to_id
|
|
21139
|
+
WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
|
|
21140
|
+
AND e.type IN ('DEFINES', 'CONTAINS')
|
|
21141
|
+
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
21142
|
+
`).all(...slice));
|
|
21143
|
+
}
|
|
21144
|
+
return out2;
|
|
21145
|
+
}
|
|
21146
|
+
firstLiveByNameOrQname(labels, name2) {
|
|
21147
|
+
if (labels.length === 0) return null;
|
|
21148
|
+
const inLabels = labels.map(() => "?").join(",");
|
|
21149
|
+
const probe = (index, match2) => this.db.prepare(
|
|
21150
|
+
`SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
|
|
21151
|
+
WHERE ${match2} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
|
|
21152
|
+
).all(name2, ...labels);
|
|
21153
|
+
const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
|
|
21154
|
+
const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
|
|
21155
|
+
let best;
|
|
21156
|
+
for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
|
|
21157
|
+
if (!best || before(rank(r), rank(best))) best = r;
|
|
21158
|
+
}
|
|
21159
|
+
return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
|
|
21160
|
+
}
|
|
21161
|
+
instanceGateRows(label) {
|
|
21162
|
+
return this.db.prepare(
|
|
21163
|
+
`SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
|
|
21164
|
+
json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
|
|
21165
|
+
FROM nodes INDEXED BY nodes_live_instance_gate
|
|
21166
|
+
WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
|
|
21167
|
+
ORDER BY label, created_at, rowid`
|
|
21168
|
+
).all(label);
|
|
21169
|
+
}
|
|
21170
|
+
liveCommentsAttachedTo(name2) {
|
|
21171
|
+
return this.db.prepare(
|
|
21172
|
+
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
21173
|
+
WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
|
|
21174
|
+
).all(name2);
|
|
21175
|
+
}
|
|
21108
21176
|
strandedEdges() {
|
|
21109
21177
|
return countStrandedEdges(this.db);
|
|
21110
21178
|
}
|
|
@@ -21283,7 +21351,11 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21283
21351
|
if (!stmt2) {
|
|
21284
21352
|
const cols = attrKeys.map((k, i2) => `, ${attrValueSql(`'$.${k}'`)} AS a${i2}, json_type(attrs_json, '$.${k}') AS t${i2}`).join("");
|
|
21285
21353
|
stmt2 = this.db.prepare(
|
|
21286
|
-
|
|
21354
|
+
// Ordered, not left to the planner: readers take "first row wins" (the
|
|
21355
|
+
// symbol lexicon's name winner and doc pick), and the order otherwise
|
|
21356
|
+
// followed whichever partial index the planner chose. nodes_live_created
|
|
21357
|
+
// serves this order without a sort.
|
|
21358
|
+
`SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
|
|
21287
21359
|
);
|
|
21288
21360
|
this.fieldStmts.set(cacheKey2, stmt2);
|
|
21289
21361
|
}
|
|
@@ -21309,7 +21381,7 @@ CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_ty
|
|
|
21309
21381
|
}
|
|
21310
21382
|
countNodes(q) {
|
|
21311
21383
|
const { where, params } = compileNodeWhere(q);
|
|
21312
|
-
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM
|
|
21384
|
+
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
|
|
21313
21385
|
}
|
|
21314
21386
|
setCommunity(id, community) {
|
|
21315
21387
|
this.stmts.setCommunity.run({ id, v: community });
|
|
@@ -37286,6 +37358,62 @@ function buildSymbolLexicon(store) {
|
|
|
37286
37358
|
}
|
|
37287
37359
|
return lex;
|
|
37288
37360
|
}
|
|
37361
|
+
function lazySymbolLexicon(store) {
|
|
37362
|
+
if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
|
|
37363
|
+
const labels = SYMBOL_LABELS2.map(([label]) => label);
|
|
37364
|
+
const kindOf = new Map(SYMBOL_LABELS2.map(([label, kind]) => [label, kind]));
|
|
37365
|
+
const members = /* @__PURE__ */ new Map();
|
|
37366
|
+
const docs = /* @__PURE__ */ new Map();
|
|
37367
|
+
const entries = /* @__PURE__ */ new Map();
|
|
37368
|
+
const member = (name2) => {
|
|
37369
|
+
if (!members.has(name2)) {
|
|
37370
|
+
const hit = isDistinctiveIdentifier(name2) ? store.firstLiveByNameOrQname(labels, name2) : null;
|
|
37371
|
+
members.set(name2, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
|
|
37372
|
+
}
|
|
37373
|
+
return members.get(name2);
|
|
37374
|
+
};
|
|
37375
|
+
const docFor = (name2) => {
|
|
37376
|
+
if (!docs.has(name2)) {
|
|
37377
|
+
let found;
|
|
37378
|
+
for (const c of store.liveCommentsAttachedTo(name2)) {
|
|
37379
|
+
if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
|
|
37380
|
+
const text = typeof c.text === "string" ? c.text : c.description;
|
|
37381
|
+
if (!text?.trim()) continue;
|
|
37382
|
+
found = text.trim().slice(0, MAX_DOC_CHARS);
|
|
37383
|
+
break;
|
|
37384
|
+
}
|
|
37385
|
+
docs.set(name2, found);
|
|
37386
|
+
}
|
|
37387
|
+
return docs.get(name2);
|
|
37388
|
+
};
|
|
37389
|
+
const isKnown = (tok) => member(tok) !== null;
|
|
37390
|
+
let anySymbol;
|
|
37391
|
+
return {
|
|
37392
|
+
has: isKnown,
|
|
37393
|
+
get(name2) {
|
|
37394
|
+
if (!entries.has(name2)) {
|
|
37395
|
+
const m = member(name2);
|
|
37396
|
+
let entry = null;
|
|
37397
|
+
if (m) {
|
|
37398
|
+
entry = { kind: m.kind };
|
|
37399
|
+
const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
|
|
37400
|
+
for (const phrase2 of summaryCandidates(name2, m.kind, doc)) {
|
|
37401
|
+
if (summaryRejectionReason(phrase2, isKnown) === null) {
|
|
37402
|
+
entry.summary = phrase2;
|
|
37403
|
+
break;
|
|
37404
|
+
}
|
|
37405
|
+
}
|
|
37406
|
+
}
|
|
37407
|
+
entries.set(name2, entry);
|
|
37408
|
+
}
|
|
37409
|
+
return entries.get(name2) ?? void 0;
|
|
37410
|
+
},
|
|
37411
|
+
get size() {
|
|
37412
|
+
anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
|
|
37413
|
+
return anySymbol ? 1 : 0;
|
|
37414
|
+
}
|
|
37415
|
+
};
|
|
37416
|
+
}
|
|
37289
37417
|
function codeGraphKey(store) {
|
|
37290
37418
|
const files = store.labelFingerprint?.(["File"]);
|
|
37291
37419
|
if (files === void 0) return null;
|
|
@@ -37295,7 +37423,7 @@ function lexiconsFor(store) {
|
|
|
37295
37423
|
const key = codeGraphKey(store);
|
|
37296
37424
|
const hit = key !== null ? lexiconCache.get(store) : void 0;
|
|
37297
37425
|
if (hit && hit.key === key) return hit;
|
|
37298
|
-
const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
37426
|
+
const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
37299
37427
|
if (key !== null) lexiconCache.set(store, { key, ...built });
|
|
37300
37428
|
return built;
|
|
37301
37429
|
}
|
|
@@ -47122,14 +47250,31 @@ function standingStrandedCount(store) {
|
|
|
47122
47250
|
try {
|
|
47123
47251
|
const last = JSON.parse(store.getMeta?.(STRANDED_KEY) ?? "null");
|
|
47124
47252
|
if (!last) return null;
|
|
47125
|
-
|
|
47126
|
-
|
|
47253
|
+
const seq = store.changeSeq?.();
|
|
47254
|
+
if ((seq ?? Infinity) - last.seq > STRANDED_WINDOW_MAX) return null;
|
|
47255
|
+
const changed = store.lineageChangedFiles?.(last.seq);
|
|
47256
|
+
if (!changed) return null;
|
|
47257
|
+
if (changed.length === 0) return last.count;
|
|
47258
|
+
if (!last.byFile || !store.strandedSymbolsForFiles || seq === void 0) return null;
|
|
47259
|
+
const byFile = { ...last.byFile };
|
|
47260
|
+
for (const f of changed) delete byFile[f];
|
|
47261
|
+
for (const s of store.strandedSymbolsForFiles(changed)) byFile[s.fileId] = (byFile[s.fileId] ?? 0) + 1;
|
|
47262
|
+
const count = Object.values(byFile).reduce((a, b) => a + b, 0);
|
|
47263
|
+
store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count, byFile }));
|
|
47264
|
+
return count;
|
|
47127
47265
|
} catch {
|
|
47128
47266
|
return null;
|
|
47129
47267
|
}
|
|
47130
47268
|
}
|
|
47131
|
-
function recordStrandedCount(store, seq,
|
|
47132
|
-
if (seq
|
|
47269
|
+
function recordStrandedCount(store, seq, stranded) {
|
|
47270
|
+
if (seq === void 0) return;
|
|
47271
|
+
if (typeof stranded === "number") {
|
|
47272
|
+
store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count: stranded }));
|
|
47273
|
+
return;
|
|
47274
|
+
}
|
|
47275
|
+
const byFile = {};
|
|
47276
|
+
for (const s of stranded) byFile[s.fileId] = (byFile[s.fileId] ?? 0) + 1;
|
|
47277
|
+
store.setMeta?.(STRANDED_KEY, JSON.stringify({ seq, count: stranded.length, byFile }));
|
|
47133
47278
|
}
|
|
47134
47279
|
function reapOrphanedFiles(store, rootPath, workspaceId2, ignoreDirs = []) {
|
|
47135
47280
|
const ignores = /* @__PURE__ */ new Set([...DEFAULT_IGNORES, ...ignoreDirs]);
|
|
@@ -80199,7 +80344,7 @@ import { readFileSync as readFileSync18, renameSync as renameSync3, statSync as
|
|
|
80199
80344
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
80200
80345
|
import { dirname as dirname11, join as join22 } from "node:path";
|
|
80201
80346
|
function snapshotReusable(held, want) {
|
|
80202
|
-
return held !== null && held.key === want.key && held.atCommit === want.atCommit
|
|
80347
|
+
return held !== null && held.key === want.key && held.atCommit === want.atCommit;
|
|
80203
80348
|
}
|
|
80204
80349
|
var codeIdentityMemo;
|
|
80205
80350
|
function snapshotCodeIdentity(version3) {
|
|
@@ -86861,7 +87006,7 @@ function createWatchBreaker(deps) {
|
|
|
86861
87006
|
}
|
|
86862
87007
|
|
|
86863
87008
|
// src/engine.ts
|
|
86864
|
-
var DAEMON_VERSION = true ? "2.0.22-dev.
|
|
87009
|
+
var DAEMON_VERSION = true ? "2.0.22-dev.4652" : "2.0.0-alpha.0";
|
|
86865
87010
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
86866
87011
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
86867
87012
|
var GIT_OP_MUTE_MS = 4e3;
|
|
@@ -87374,10 +87519,18 @@ function createWorkspaceEngine(opts) {
|
|
|
87374
87519
|
};
|
|
87375
87520
|
const changes = store.changeSeq ? store.changeSeq() - store.changeSeq([EXPOSURE_SCOPE]) : store.changeStamp();
|
|
87376
87521
|
const { id, name: name2, stack, languages, domains, goals } = snapOpts.profile;
|
|
87377
|
-
const
|
|
87522
|
+
const { workingFile: _workingFile, ...keyedOpts } = snapOpts;
|
|
87523
|
+
const snapshotKey = `${snapshotCodeIdentity(DAEMON_VERSION)}|${changes}|${JSON.stringify({ ...keyedOpts, profile: { id, name: name2, stack, languages, domains, goals } })}`;
|
|
87378
87524
|
lastWorkerSnapshot ??= loadHeldSnapshot(heldSnapshotPath(paths.configDir));
|
|
87379
87525
|
const held = lastWorkerSnapshot;
|
|
87380
|
-
const
|
|
87526
|
+
const withWorkingFile = (snap, builtFor, want, atCommit) => {
|
|
87527
|
+
if (builtFor === want) return snap;
|
|
87528
|
+
const slice = want ? sliceForFile(store, want, atCommit) : void 0;
|
|
87529
|
+
if (slice) snap.workingFile = slice;
|
|
87530
|
+
else delete snap.workingFile;
|
|
87531
|
+
return snap;
|
|
87532
|
+
};
|
|
87533
|
+
const prebuilt = snapshotReusable(held, { key: snapshotKey, atCommit: snapOpts.atCommit }) ? withWorkingFile(structuredClone(held.snapshot), held.workingFile, snapOpts.workingFile, snapOpts.atCommit) : passWorker ? await passWorker.snapshot(snapOpts).then((snapshot2) => {
|
|
87381
87534
|
lastWorkerSnapshot = { key: snapshotKey, at: Date.now(), atCommit: snapOpts.atCommit, workingFile: snapOpts.workingFile, snapshot: structuredClone(snapshot2) };
|
|
87382
87535
|
saveHeldSnapshot(heldSnapshotPath(paths.configDir), lastWorkerSnapshot);
|
|
87383
87536
|
return snapshot2;
|
|
@@ -90691,6 +90844,38 @@ function compositionCounters(c) {
|
|
|
90691
90844
|
...Object.fromEntries(buckets.map((b) => [`sidecarDrop_${b}`, sidecarDropReasons[b] ?? 0]))
|
|
90692
90845
|
};
|
|
90693
90846
|
}
|
|
90847
|
+
function instanceGateRows(store, label) {
|
|
90848
|
+
return store.instanceGateRows?.(label) ?? store.findNodeFieldsByLabel(label, ["source", "privateFinding", "resolvedAs"]).map((r) => ({
|
|
90849
|
+
id: r.id,
|
|
90850
|
+
source: r.attrs["source"],
|
|
90851
|
+
privateFinding: r.attrs["privateFinding"],
|
|
90852
|
+
resolvedAs: r.attrs["resolvedAs"]
|
|
90853
|
+
}));
|
|
90854
|
+
}
|
|
90855
|
+
var GATE_VIEW_ATTRS = [
|
|
90856
|
+
"privateFinding",
|
|
90857
|
+
"source",
|
|
90858
|
+
"sources",
|
|
90859
|
+
"resolvedAs",
|
|
90860
|
+
"instanceReceipts",
|
|
90861
|
+
"cloudWithdrawn",
|
|
90862
|
+
"withdrawnAtCorrectionSeq",
|
|
90863
|
+
"contributedAtSeq",
|
|
90864
|
+
"cloudNodeId"
|
|
90865
|
+
];
|
|
90866
|
+
var GATE_VIEW_CHUNK = 500;
|
|
90867
|
+
var gateView = /* @__PURE__ */ new WeakSet();
|
|
90868
|
+
function gateViews(store, label, ids) {
|
|
90869
|
+
const byId = /* @__PURE__ */ new Map();
|
|
90870
|
+
for (let i2 = 0; i2 < ids.length; i2 += GATE_VIEW_CHUNK) {
|
|
90871
|
+
for (const r of store.selectNodes({ labels: [label], attrs: GATE_VIEW_ATTRS, where: [{ idIn: ids.slice(i2, i2 + GATE_VIEW_CHUNK) }] })) {
|
|
90872
|
+
const view = { ...r, embedding: [] };
|
|
90873
|
+
gateView.add(view);
|
|
90874
|
+
byId.set(r.id, view);
|
|
90875
|
+
}
|
|
90876
|
+
}
|
|
90877
|
+
return ids.flatMap((id) => byId.get(id) ?? []);
|
|
90878
|
+
}
|
|
90694
90879
|
function contributedWireId(n, destinationKey) {
|
|
90695
90880
|
const cloudId = instanceReceipt(n, destinationKey)["cloudNodeId"];
|
|
90696
90881
|
return typeof cloudId === "string" && cloudId ? cloudId : n.id;
|
|
@@ -90849,7 +91034,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
90849
91034
|
edgesHeldDropped: 0
|
|
90850
91035
|
};
|
|
90851
91036
|
const sharedCauses = /* @__PURE__ */ new Map();
|
|
90852
|
-
const causalQueue = store
|
|
91037
|
+
const causalQueue = instanceGateRows(store, "Problem").filter((r) => r.source !== "cloud" && r.resolvedAs !== PROBLEM_RESOLUTION.FALSE_POSITIVE).map((r) => r.id).filter((id) => ignorePatterns.length === 0 || !ignored(store.getNode(id)?.description ?? ""));
|
|
90853
91038
|
const causalSeen = new Set(causalQueue);
|
|
90854
91039
|
for (let i2 = 0; i2 < causalQueue.length; i2++) {
|
|
90855
91040
|
const id = causalQueue[i2];
|
|
@@ -90865,21 +91050,21 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
90865
91050
|
for (const label of INSTANCE_LABELS) {
|
|
90866
91051
|
const ref = [];
|
|
90867
91052
|
includedByLabel.set(label, ref);
|
|
90868
|
-
const
|
|
90869
|
-
for (const row of store
|
|
90870
|
-
if (row.
|
|
90871
|
-
|
|
90872
|
-
if (n) local.push(n);
|
|
91053
|
+
const localIds = [];
|
|
91054
|
+
for (const row of instanceGateRows(store, label)) {
|
|
91055
|
+
if (row.source !== "cloud") {
|
|
91056
|
+
localIds.push(row.id);
|
|
90873
91057
|
continue;
|
|
90874
91058
|
}
|
|
90875
91059
|
if (seen.has(row.id) || walked.has(row.id)) continue;
|
|
90876
91060
|
walked.add(row.id);
|
|
90877
91061
|
composition.candidates++;
|
|
90878
|
-
if ((row.
|
|
91062
|
+
if ((row.privateFinding || label === "Vulnerability") && !opts.project) composition.privateTargetMissing++;
|
|
90879
91063
|
else composition.cloudSourced++;
|
|
90880
91064
|
}
|
|
91065
|
+
const local = gateViews(store, label, localIds);
|
|
90881
91066
|
const population = label === "RootCause" && opts.sharedStore ? [...local, ...sharedCauses.values()] : local;
|
|
90882
|
-
for (
|
|
91067
|
+
for (let n of population) {
|
|
90883
91068
|
if (seen.has(n.id)) continue;
|
|
90884
91069
|
if (walked.has(n.id)) continue;
|
|
90885
91070
|
walked.add(n.id);
|
|
@@ -90915,6 +91100,11 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
90915
91100
|
semanticEndpoints.set(n.id, contributedWireId(n, opts.destinationKey));
|
|
90916
91101
|
continue;
|
|
90917
91102
|
}
|
|
91103
|
+
if (gateView.has(n)) {
|
|
91104
|
+
const full = store.getNode(n.id);
|
|
91105
|
+
if (!full) continue;
|
|
91106
|
+
n = full;
|
|
91107
|
+
}
|
|
90918
91108
|
if (isEditPlaceholderSolution(n)) {
|
|
90919
91109
|
composition.autoMint++;
|
|
90920
91110
|
continue;
|
|
@@ -95314,17 +95504,17 @@ async function startMultiDaemon(opts = {}) {
|
|
|
95314
95504
|
if (!strandedRefreshing.has(r.id)) {
|
|
95315
95505
|
strandedRefreshing.add(r.id);
|
|
95316
95506
|
const seq = store.changeSeq?.();
|
|
95317
|
-
const finish = (
|
|
95318
|
-
strandedCache.set(r.id, { at: Date.now(), count });
|
|
95507
|
+
const finish = (stranded) => {
|
|
95508
|
+
strandedCache.set(r.id, { at: Date.now(), count: stranded.length });
|
|
95319
95509
|
strandedRefreshing.delete(r.id);
|
|
95320
|
-
recordStrandedCount(store, seq,
|
|
95510
|
+
recordStrandedCount(store, seq, stranded);
|
|
95321
95511
|
};
|
|
95322
95512
|
if (consolidateWorker && r.engine.paths.castalia !== ":memory:") {
|
|
95323
|
-
consolidateWorker.run({ kind: "strandedCount", sharedDbPath, castaliaPath: r.engine.paths.castalia }).then((
|
|
95513
|
+
consolidateWorker.run({ kind: "strandedCount", sharedDbPath, castaliaPath: r.engine.paths.castalia }).then((stranded) => finish(Array.isArray(stranded) ? stranded : [])).catch(() => {
|
|
95324
95514
|
setImmediate(() => {
|
|
95325
95515
|
const done = markPass(`stranded-scan:${r.entry.name}`);
|
|
95326
95516
|
try {
|
|
95327
|
-
finish(findStrandedSymbols(r.engine.store)
|
|
95517
|
+
finish(findStrandedSymbols(r.engine.store));
|
|
95328
95518
|
} catch {
|
|
95329
95519
|
strandedRefreshing.delete(r.id);
|
|
95330
95520
|
} finally {
|
|
@@ -95336,7 +95526,7 @@ async function startMultiDaemon(opts = {}) {
|
|
|
95336
95526
|
setImmediate(() => {
|
|
95337
95527
|
const done = markPass(`stranded-scan:${r.entry.name}`);
|
|
95338
95528
|
try {
|
|
95339
|
-
finish(findStrandedSymbols(r.engine.store)
|
|
95529
|
+
finish(findStrandedSymbols(r.engine.store));
|
|
95340
95530
|
} catch {
|
|
95341
95531
|
strandedRefreshing.delete(r.id);
|
|
95342
95532
|
} finally {
|
package/generalize-worker.mjs
CHANGED
|
@@ -15993,8 +15993,10 @@ function column(c) {
|
|
|
15993
15993
|
function compileNodeWhere(q) {
|
|
15994
15994
|
if (q.labels.length === 0) throw new Error("selectNodes: no labels");
|
|
15995
15995
|
const params = [...q.labels];
|
|
15996
|
-
const
|
|
15997
|
-
|
|
15996
|
+
const byId = (q.where ?? []).some((w) => "idIn" in w);
|
|
15997
|
+
const unindexed = byId ? "+" : "";
|
|
15998
|
+
const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
|
|
15999
|
+
if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
|
|
15998
16000
|
for (const w of q.where ?? []) {
|
|
15999
16001
|
if ("embedding" in w) {
|
|
16000
16002
|
conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
|
|
@@ -16046,6 +16048,14 @@ function compileNodeWhere(q) {
|
|
|
16046
16048
|
function attrValueSql(p) {
|
|
16047
16049
|
return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
|
|
16048
16050
|
}
|
|
16051
|
+
function nodeSelectFrom(q) {
|
|
16052
|
+
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
16053
|
+
const where = q.where ?? [];
|
|
16054
|
+
if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
|
|
16055
|
+
return "nodes INDEXED BY nodes_live_description";
|
|
16056
|
+
}
|
|
16057
|
+
return "nodes";
|
|
16058
|
+
}
|
|
16049
16059
|
function compileNodeSelect(q) {
|
|
16050
16060
|
const { where, params } = compileNodeWhere(q);
|
|
16051
16061
|
const attrs = q.attrs ?? [];
|
|
@@ -16063,7 +16073,7 @@ function compileNodeSelect(q) {
|
|
|
16063
16073
|
}
|
|
16064
16074
|
const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
|
|
16065
16075
|
const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
|
|
16066
|
-
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM
|
|
16076
|
+
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
|
|
16067
16077
|
return { sql, params: [...params, ...orderParams] };
|
|
16068
16078
|
}
|
|
16069
16079
|
function decodeAttr(value, type) {
|
|
@@ -16300,6 +16310,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
|
|
|
16300
16310
|
-- incremental reindex can fetch just the changed files' symbols instead of
|
|
16301
16311
|
-- scanning every versioned node. Partial: only live rows are ever queried.
|
|
16302
16312
|
CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
|
|
16313
|
+
-- Symbols by qname and doc comments by the symbol they attach to: the drain's
|
|
16314
|
+
-- symbol lexicon resolves the names in the text it ships one at a time, instead
|
|
16315
|
+
-- of reading every Function/Method/Class/Interface/Const and Comment row on each
|
|
16316
|
+
-- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
|
|
16317
|
+
CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
|
|
16318
|
+
CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
|
|
16319
|
+
-- The attrs the cloud drain gates every instance row on, so it reads them from
|
|
16320
|
+
-- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
|
|
16321
|
+
-- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
|
|
16322
|
+
-- two attrs read ~220 MB per push to find the few local rows that can ship.
|
|
16323
|
+
-- created_at leads so the index order is findNodeFieldsByLabel's.
|
|
16324
|
+
CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
|
|
16303
16325
|
-- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
|
|
16304
16326
|
-- live-only index above cannot serve it, so it scanned the whole table (~275 MB
|
|
16305
16327
|
-- of a 1.1 GB store per reindex, 2026-10-20).
|
|
@@ -16650,6 +16672,52 @@ var SqliteGraphStore = class {
|
|
|
16650
16672
|
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
16651
16673
|
`).all();
|
|
16652
16674
|
}
|
|
16675
|
+
strandedSymbolsForFiles(fileIds) {
|
|
16676
|
+
const out = [];
|
|
16677
|
+
for (let i = 0; i < fileIds.length; i += 500) {
|
|
16678
|
+
const slice = fileIds.slice(i, i + 500);
|
|
16679
|
+
if (slice.length === 0) continue;
|
|
16680
|
+
out.push(...this.db.prepare(`
|
|
16681
|
+
SELECT f.id AS fileId, e.to_id AS symbolId
|
|
16682
|
+
FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
|
|
16683
|
+
LEFT JOIN nodes t ON t.id = e.to_id
|
|
16684
|
+
WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
|
|
16685
|
+
AND e.type IN ('DEFINES', 'CONTAINS')
|
|
16686
|
+
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
16687
|
+
`).all(...slice));
|
|
16688
|
+
}
|
|
16689
|
+
return out;
|
|
16690
|
+
}
|
|
16691
|
+
firstLiveByNameOrQname(labels, name) {
|
|
16692
|
+
if (labels.length === 0) return null;
|
|
16693
|
+
const inLabels = labels.map(() => "?").join(",");
|
|
16694
|
+
const probe = (index, match) => this.db.prepare(
|
|
16695
|
+
`SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
|
|
16696
|
+
WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
|
|
16697
|
+
).all(name, ...labels);
|
|
16698
|
+
const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
|
|
16699
|
+
const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
|
|
16700
|
+
let best;
|
|
16701
|
+
for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
|
|
16702
|
+
if (!best || before(rank(r), rank(best))) best = r;
|
|
16703
|
+
}
|
|
16704
|
+
return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
|
|
16705
|
+
}
|
|
16706
|
+
instanceGateRows(label) {
|
|
16707
|
+
return this.db.prepare(
|
|
16708
|
+
`SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
|
|
16709
|
+
json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
|
|
16710
|
+
FROM nodes INDEXED BY nodes_live_instance_gate
|
|
16711
|
+
WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
|
|
16712
|
+
ORDER BY label, created_at, rowid`
|
|
16713
|
+
).all(label);
|
|
16714
|
+
}
|
|
16715
|
+
liveCommentsAttachedTo(name) {
|
|
16716
|
+
return this.db.prepare(
|
|
16717
|
+
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
16718
|
+
WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
|
|
16719
|
+
).all(name);
|
|
16720
|
+
}
|
|
16653
16721
|
strandedEdges() {
|
|
16654
16722
|
return countStrandedEdges(this.db);
|
|
16655
16723
|
}
|
|
@@ -16828,7 +16896,11 @@ var SqliteGraphStore = class {
|
|
|
16828
16896
|
if (!stmt2) {
|
|
16829
16897
|
const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
|
|
16830
16898
|
stmt2 = this.db.prepare(
|
|
16831
|
-
|
|
16899
|
+
// Ordered, not left to the planner: readers take "first row wins" (the
|
|
16900
|
+
// symbol lexicon's name winner and doc pick), and the order otherwise
|
|
16901
|
+
// followed whichever partial index the planner chose. nodes_live_created
|
|
16902
|
+
// serves this order without a sort.
|
|
16903
|
+
`SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
|
|
16832
16904
|
);
|
|
16833
16905
|
this.fieldStmts.set(cacheKey, stmt2);
|
|
16834
16906
|
}
|
|
@@ -16854,7 +16926,7 @@ var SqliteGraphStore = class {
|
|
|
16854
16926
|
}
|
|
16855
16927
|
countNodes(q) {
|
|
16856
16928
|
const { where, params } = compileNodeWhere(q);
|
|
16857
|
-
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM
|
|
16929
|
+
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
|
|
16858
16930
|
}
|
|
16859
16931
|
setCommunity(id, community) {
|
|
16860
16932
|
this.stmts.setCommunity.run({ id, v: community });
|
|
@@ -18334,6 +18406,62 @@ function buildSymbolLexicon(store) {
|
|
|
18334
18406
|
}
|
|
18335
18407
|
return lex;
|
|
18336
18408
|
}
|
|
18409
|
+
function lazySymbolLexicon(store) {
|
|
18410
|
+
if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
|
|
18411
|
+
const labels = SYMBOL_LABELS.map(([label]) => label);
|
|
18412
|
+
const kindOf = new Map(SYMBOL_LABELS.map(([label, kind]) => [label, kind]));
|
|
18413
|
+
const members = /* @__PURE__ */ new Map();
|
|
18414
|
+
const docs = /* @__PURE__ */ new Map();
|
|
18415
|
+
const entries = /* @__PURE__ */ new Map();
|
|
18416
|
+
const member = (name) => {
|
|
18417
|
+
if (!members.has(name)) {
|
|
18418
|
+
const hit = isDistinctiveIdentifier(name) ? store.firstLiveByNameOrQname(labels, name) : null;
|
|
18419
|
+
members.set(name, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
|
|
18420
|
+
}
|
|
18421
|
+
return members.get(name);
|
|
18422
|
+
};
|
|
18423
|
+
const docFor = (name) => {
|
|
18424
|
+
if (!docs.has(name)) {
|
|
18425
|
+
let found;
|
|
18426
|
+
for (const c of store.liveCommentsAttachedTo(name)) {
|
|
18427
|
+
if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
|
|
18428
|
+
const text = typeof c.text === "string" ? c.text : c.description;
|
|
18429
|
+
if (!text?.trim()) continue;
|
|
18430
|
+
found = text.trim().slice(0, MAX_DOC_CHARS);
|
|
18431
|
+
break;
|
|
18432
|
+
}
|
|
18433
|
+
docs.set(name, found);
|
|
18434
|
+
}
|
|
18435
|
+
return docs.get(name);
|
|
18436
|
+
};
|
|
18437
|
+
const isKnown = (tok) => member(tok) !== null;
|
|
18438
|
+
let anySymbol;
|
|
18439
|
+
return {
|
|
18440
|
+
has: isKnown,
|
|
18441
|
+
get(name) {
|
|
18442
|
+
if (!entries.has(name)) {
|
|
18443
|
+
const m = member(name);
|
|
18444
|
+
let entry = null;
|
|
18445
|
+
if (m) {
|
|
18446
|
+
entry = { kind: m.kind };
|
|
18447
|
+
const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
|
|
18448
|
+
for (const phrase of summaryCandidates(name, m.kind, doc)) {
|
|
18449
|
+
if (summaryRejectionReason(phrase, isKnown) === null) {
|
|
18450
|
+
entry.summary = phrase;
|
|
18451
|
+
break;
|
|
18452
|
+
}
|
|
18453
|
+
}
|
|
18454
|
+
}
|
|
18455
|
+
entries.set(name, entry);
|
|
18456
|
+
}
|
|
18457
|
+
return entries.get(name) ?? void 0;
|
|
18458
|
+
},
|
|
18459
|
+
get size() {
|
|
18460
|
+
anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
|
|
18461
|
+
return anySymbol ? 1 : 0;
|
|
18462
|
+
}
|
|
18463
|
+
};
|
|
18464
|
+
}
|
|
18337
18465
|
var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
|
|
18338
18466
|
function codeGraphKey(store) {
|
|
18339
18467
|
const files = store.labelFingerprint?.(["File"]);
|
|
@@ -18345,7 +18473,7 @@ function lexiconsFor(store) {
|
|
|
18345
18473
|
const key = codeGraphKey(store);
|
|
18346
18474
|
const hit = key !== null ? lexiconCache.get(store) : void 0;
|
|
18347
18475
|
if (hit && hit.key === key) return hit;
|
|
18348
|
-
const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
18476
|
+
const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
18349
18477
|
if (key !== null) lexiconCache.set(store, { key, ...built });
|
|
18350
18478
|
return built;
|
|
18351
18479
|
}
|
package/package.json
CHANGED
package/pass-worker.mjs
CHANGED
|
@@ -26871,8 +26871,10 @@ function column(c) {
|
|
|
26871
26871
|
function compileNodeWhere(q) {
|
|
26872
26872
|
if (q.labels.length === 0) throw new Error("selectNodes: no labels");
|
|
26873
26873
|
const params = [...q.labels];
|
|
26874
|
-
const
|
|
26875
|
-
|
|
26874
|
+
const byId = (q.where ?? []).some((w) => "idIn" in w);
|
|
26875
|
+
const unindexed = byId ? "+" : "";
|
|
26876
|
+
const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
|
|
26877
|
+
if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
|
|
26876
26878
|
for (const w of q.where ?? []) {
|
|
26877
26879
|
if ("embedding" in w) {
|
|
26878
26880
|
conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
|
|
@@ -26924,6 +26926,14 @@ function compileNodeWhere(q) {
|
|
|
26924
26926
|
function attrValueSql(p) {
|
|
26925
26927
|
return `CASE json_type(attrs_json, ${p}) WHEN 'integer' THEN CAST(json_extract(attrs_json, ${p}) AS REAL) ELSE json_extract(attrs_json, ${p}) END`;
|
|
26926
26928
|
}
|
|
26929
|
+
function nodeSelectFrom(q) {
|
|
26930
|
+
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
26931
|
+
const where = q.where ?? [];
|
|
26932
|
+
if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
|
|
26933
|
+
return "nodes INDEXED BY nodes_live_description";
|
|
26934
|
+
}
|
|
26935
|
+
return "nodes";
|
|
26936
|
+
}
|
|
26927
26937
|
function compileNodeSelect(q) {
|
|
26928
26938
|
const { where, params } = compileNodeWhere(q);
|
|
26929
26939
|
const attrs = q.attrs ?? [];
|
|
@@ -26941,7 +26951,7 @@ function compileNodeSelect(q) {
|
|
|
26941
26951
|
}
|
|
26942
26952
|
const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
|
|
26943
26953
|
const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
|
|
26944
|
-
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM
|
|
26954
|
+
const sql = `SELECT id, label, description, created_at, last_updated_at, page_rank, cumulative_hits, extraction_confidence, extraction_source, community, created_at_seq, last_reinforced_at_seq, valid_to, logical_id, version, ${EMBEDDING_DIM_SQL} AS embedding_dim${q.withEmbedding ? ", embedding_blob, embedding_json" : ""}${attrCols} FROM ${nodeSelectFrom(q)} WHERE ${where} ORDER BY ${order}${limit}`;
|
|
26945
26955
|
return { sql, params: [...params, ...orderParams] };
|
|
26946
26956
|
}
|
|
26947
26957
|
function decodeAttr(value, type) {
|
|
@@ -27178,6 +27188,18 @@ CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid
|
|
|
27178
27188
|
-- incremental reindex can fetch just the changed files' symbols instead of
|
|
27179
27189
|
-- scanning every versioned node. Partial: only live rows are ever queried.
|
|
27180
27190
|
CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
|
|
27191
|
+
-- Symbols by qname and doc comments by the symbol they attach to: the drain's
|
|
27192
|
+
-- symbol lexicon resolves the names in the text it ships one at a time, instead
|
|
27193
|
+
-- of reading every Function/Method/Class/Interface/Const and Comment row on each
|
|
27194
|
+
-- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
|
|
27195
|
+
CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
|
|
27196
|
+
CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
|
|
27197
|
+
-- The attrs the cloud drain gates every instance row on, so it reads them from
|
|
27198
|
+
-- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
|
|
27199
|
+
-- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
|
|
27200
|
+
-- two attrs read ~220 MB per push to find the few local rows that can ship.
|
|
27201
|
+
-- created_at leads so the index order is findNodeFieldsByLabel's.
|
|
27202
|
+
CREATE INDEX IF NOT EXISTS nodes_live_instance_gate ON nodes(label, created_at, json_extract(attrs_json, '$.source'), json_extract(attrs_json, '$.privateFinding'), json_extract(attrs_json, '$.resolvedAs'), id) WHERE valid_to IS NULL AND label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause');
|
|
27181
27203
|
-- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
|
|
27182
27204
|
-- live-only index above cannot serve it, so it scanned the whole table (~275 MB
|
|
27183
27205
|
-- of a 1.1 GB store per reindex, 2026-10-20).
|
|
@@ -27528,6 +27550,52 @@ var SqliteGraphStore = class {
|
|
|
27528
27550
|
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
27529
27551
|
`).all();
|
|
27530
27552
|
}
|
|
27553
|
+
strandedSymbolsForFiles(fileIds) {
|
|
27554
|
+
const out2 = [];
|
|
27555
|
+
for (let i2 = 0; i2 < fileIds.length; i2 += 500) {
|
|
27556
|
+
const slice = fileIds.slice(i2, i2 + 500);
|
|
27557
|
+
if (slice.length === 0) continue;
|
|
27558
|
+
out2.push(...this.db.prepare(`
|
|
27559
|
+
SELECT f.id AS fileId, e.to_id AS symbolId
|
|
27560
|
+
FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
|
|
27561
|
+
LEFT JOIN nodes t ON t.id = e.to_id
|
|
27562
|
+
WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
|
|
27563
|
+
AND e.type IN ('DEFINES', 'CONTAINS')
|
|
27564
|
+
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
27565
|
+
`).all(...slice));
|
|
27566
|
+
}
|
|
27567
|
+
return out2;
|
|
27568
|
+
}
|
|
27569
|
+
firstLiveByNameOrQname(labels, name2) {
|
|
27570
|
+
if (labels.length === 0) return null;
|
|
27571
|
+
const inLabels = labels.map(() => "?").join(",");
|
|
27572
|
+
const probe = (index, match) => this.db.prepare(
|
|
27573
|
+
`SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
|
|
27574
|
+
WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
|
|
27575
|
+
).all(name2, ...labels);
|
|
27576
|
+
const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
|
|
27577
|
+
const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
|
|
27578
|
+
let best;
|
|
27579
|
+
for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
|
|
27580
|
+
if (!best || before(rank(r), rank(best))) best = r;
|
|
27581
|
+
}
|
|
27582
|
+
return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
|
|
27583
|
+
}
|
|
27584
|
+
instanceGateRows(label) {
|
|
27585
|
+
return this.db.prepare(
|
|
27586
|
+
`SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
|
|
27587
|
+
json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
|
|
27588
|
+
FROM nodes INDEXED BY nodes_live_instance_gate
|
|
27589
|
+
WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
|
|
27590
|
+
ORDER BY label, created_at, rowid`
|
|
27591
|
+
).all(label);
|
|
27592
|
+
}
|
|
27593
|
+
liveCommentsAttachedTo(name2) {
|
|
27594
|
+
return this.db.prepare(
|
|
27595
|
+
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
27596
|
+
WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
|
|
27597
|
+
).all(name2);
|
|
27598
|
+
}
|
|
27531
27599
|
strandedEdges() {
|
|
27532
27600
|
return countStrandedEdges(this.db);
|
|
27533
27601
|
}
|
|
@@ -27706,7 +27774,11 @@ var SqliteGraphStore = class {
|
|
|
27706
27774
|
if (!stmt2) {
|
|
27707
27775
|
const cols = attrKeys.map((k, i2) => `, ${attrValueSql(`'$.${k}'`)} AS a${i2}, json_type(attrs_json, '$.${k}') AS t${i2}`).join("");
|
|
27708
27776
|
stmt2 = this.db.prepare(
|
|
27709
|
-
|
|
27777
|
+
// Ordered, not left to the planner: readers take "first row wins" (the
|
|
27778
|
+
// symbol lexicon's name winner and doc pick), and the order otherwise
|
|
27779
|
+
// followed whichever partial index the planner chose. nodes_live_created
|
|
27780
|
+
// serves this order without a sort.
|
|
27781
|
+
`SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
|
|
27710
27782
|
);
|
|
27711
27783
|
this.fieldStmts.set(cacheKey, stmt2);
|
|
27712
27784
|
}
|
|
@@ -27732,7 +27804,7 @@ var SqliteGraphStore = class {
|
|
|
27732
27804
|
}
|
|
27733
27805
|
countNodes(q) {
|
|
27734
27806
|
const { where, params } = compileNodeWhere(q);
|
|
27735
|
-
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM
|
|
27807
|
+
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
|
|
27736
27808
|
}
|
|
27737
27809
|
setCommunity(id, community) {
|
|
27738
27810
|
this.stmts.setCommunity.run({ id, v: community });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
ba1065832fc364902b70117e8568ae7f0d76cd3af48ed4d0929f03d9d3d55c7f .codex-plugin/plugin.json
|
|
2
2
|
a7bebae9fb01c838d6c75fc784129e28be571233fa66ff5ee8039b574f35ccf7 assets/icon.png
|
|
3
3
|
fc38b64e4df1e0b72c35764bef6c4c05430d97c9009ad0ecc8a9e75b4deab232 configure-local.mjs
|
|
4
4
|
73dc6028b3b070257c7c31f252d11e55a9e096f4aed0f187f10cb21716e1e6d7 dist/client.js
|