@inerrata-corporation/errata 2.0.6-dev.2656 → 2.0.6-dev.2674
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 +66 -11
- package/errata.mjs +432 -53
- package/package.json +1 -1
- package/pass-worker.mjs +23 -2
package/consolidate-worker.mjs
CHANGED
|
@@ -16206,6 +16206,10 @@ function rowToEdge(r) {
|
|
|
16206
16206
|
}
|
|
16207
16207
|
var SqliteGraphStore = class {
|
|
16208
16208
|
db;
|
|
16209
|
+
// Typed edge lookups build their SQL from the number of types asked for, and
|
|
16210
|
+
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
16211
|
+
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
16212
|
+
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
16209
16213
|
stmts;
|
|
16210
16214
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
16211
16215
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -16710,6 +16714,14 @@ var SqliteGraphStore = class {
|
|
|
16710
16714
|
const rows = this.db.prepare(sql).all(params);
|
|
16711
16715
|
return rows.map(rowToNode);
|
|
16712
16716
|
}
|
|
16717
|
+
typedEdgeStmt(sql) {
|
|
16718
|
+
let stmt = this.typedEdgeStmts.get(sql);
|
|
16719
|
+
if (!stmt) {
|
|
16720
|
+
stmt = this.db.prepare(sql);
|
|
16721
|
+
this.typedEdgeStmts.set(sql, stmt);
|
|
16722
|
+
}
|
|
16723
|
+
return stmt;
|
|
16724
|
+
}
|
|
16713
16725
|
outEdges(nodeId, types) {
|
|
16714
16726
|
if (!types || types.length === 0) {
|
|
16715
16727
|
const rows2 = this.stmts.outEdges.all(nodeId);
|
|
@@ -16720,7 +16732,7 @@ var SqliteGraphStore = class {
|
|
|
16720
16732
|
types.forEach((t, i) => {
|
|
16721
16733
|
params[`t${i}`] = t;
|
|
16722
16734
|
});
|
|
16723
|
-
const rows = this.
|
|
16735
|
+
const rows = this.typedEdgeStmt(
|
|
16724
16736
|
`SELECT * FROM edges WHERE from_id = :from_id AND type IN (${placeholders}) AND valid_to IS NULL`
|
|
16725
16737
|
).all(params);
|
|
16726
16738
|
return rows.map(rowToEdge);
|
|
@@ -16735,7 +16747,7 @@ var SqliteGraphStore = class {
|
|
|
16735
16747
|
types.forEach((t, i) => {
|
|
16736
16748
|
params[`t${i}`] = t;
|
|
16737
16749
|
});
|
|
16738
|
-
const rows = this.
|
|
16750
|
+
const rows = this.typedEdgeStmt(
|
|
16739
16751
|
`SELECT * FROM edges WHERE to_id = :to_id AND type IN (${placeholders}) AND valid_to IS NULL`
|
|
16740
16752
|
).all(params);
|
|
16741
16753
|
return rows.map(rowToEdge);
|
|
@@ -16813,6 +16825,15 @@ var SqliteGraphStore = class {
|
|
|
16813
16825
|
const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
|
|
16814
16826
|
return rows.map((r) => r.id);
|
|
16815
16827
|
}
|
|
16828
|
+
labelFingerprint(labels) {
|
|
16829
|
+
if (labels.length === 0) return "0";
|
|
16830
|
+
const r = this.db.prepare(
|
|
16831
|
+
`SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
|
|
16832
|
+
MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
|
|
16833
|
+
FROM nodes WHERE valid_to IS NULL AND label IN (${labels.map(() => "?").join(",")})`
|
|
16834
|
+
).get(...labels);
|
|
16835
|
+
return `${r.n}:${r.t ?? 0}:${r.s ?? 0}`;
|
|
16836
|
+
}
|
|
16816
16837
|
/** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
|
|
16817
16838
|
* Closures matter as much as additions: a node that lost inflow is rank-dirty
|
|
16818
16839
|
* while its own row never updated, so removal endpoints must seed the
|
|
@@ -18254,10 +18275,16 @@ function internalPackageNames(workspaceRoot) {
|
|
|
18254
18275
|
return names;
|
|
18255
18276
|
}
|
|
18256
18277
|
var cache = /* @__PURE__ */ new Map();
|
|
18278
|
+
var cacheKeys = /* @__PURE__ */ new Map();
|
|
18257
18279
|
function publicSymbolIndex(store, workspaceRoot, deps = {}) {
|
|
18258
18280
|
const now = deps.nowMs ?? Date.now();
|
|
18281
|
+
const fileRows = store.labelFingerprint?.(["File"]);
|
|
18282
|
+
const key = fileRows === void 0 ? void 0 : `${fileRows}|${store.countNodesByLabel("Module")}`;
|
|
18259
18283
|
const cached2 = cache.get(workspaceRoot);
|
|
18260
|
-
if (cached2
|
|
18284
|
+
if (cached2) {
|
|
18285
|
+
const fresh = key !== void 0 ? cacheKeys.get(workspaceRoot) === key : now - cached2.builtAt < INDEX_TTL_MS;
|
|
18286
|
+
if (fresh) return cached2;
|
|
18287
|
+
}
|
|
18261
18288
|
const readHead = deps.readHead ?? ((absPath) => {
|
|
18262
18289
|
try {
|
|
18263
18290
|
return readFileSync(absPath, "utf8").slice(0, HEAD_BYTES);
|
|
@@ -18294,6 +18321,8 @@ function publicSymbolIndex(store, workspaceRoot, deps = {}) {
|
|
|
18294
18321
|
}
|
|
18295
18322
|
const index = { symbols, builtAt: now };
|
|
18296
18323
|
cache.set(workspaceRoot, index);
|
|
18324
|
+
if (key !== void 0) cacheKeys.set(workspaceRoot, key);
|
|
18325
|
+
else cacheKeys.delete(workspaceRoot);
|
|
18297
18326
|
return index;
|
|
18298
18327
|
}
|
|
18299
18328
|
var PRESERVED_SYMBOLS_CAP = 8;
|
|
@@ -19082,7 +19111,16 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19082
19111
|
...profile.repoLocatorAliases ? { aliases: profile.repoLocatorAliases } : {},
|
|
19083
19112
|
...profile.aka ? { aka: profile.aka } : {}
|
|
19084
19113
|
};
|
|
19085
|
-
const fileLexicon = buildFileLexicon(store);
|
|
19114
|
+
const fileLexicon = opts.fileLexicon ?? buildFileLexicon(store);
|
|
19115
|
+
const nodeMemo = /* @__PURE__ */ new Map();
|
|
19116
|
+
const nodeOf = (id) => {
|
|
19117
|
+
let n = nodeMemo.get(id);
|
|
19118
|
+
if (n === void 0) {
|
|
19119
|
+
n = store.getNode(id) ?? opts.sharedStore?.getNode(id) ?? null;
|
|
19120
|
+
nodeMemo.set(id, n);
|
|
19121
|
+
}
|
|
19122
|
+
return n;
|
|
19123
|
+
};
|
|
19086
19124
|
const shareable = (n) => {
|
|
19087
19125
|
const preserved = symbolIndex ? preservedSymbolsFor(n.description, symbolIndex) : [];
|
|
19088
19126
|
const fileRefs = generalizeFileRefs(n.description, fileLexicon, level);
|
|
@@ -19207,7 +19245,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19207
19245
|
for (const sourceId of anchorSources) {
|
|
19208
19246
|
const targets = [];
|
|
19209
19247
|
for (const e of store.outEdges(sourceId, [...ANCHOR_EDGES])) {
|
|
19210
|
-
const t =
|
|
19248
|
+
const t = nodeOf(e.to);
|
|
19211
19249
|
if (!t || !ANCHOR_TARGET_LABELS.includes(t.label)) continue;
|
|
19212
19250
|
if (e.type === "OCCURS_IN" && t.label !== "Language") continue;
|
|
19213
19251
|
if (e.type === "DEPENDS_ON" && t.label !== "Package") continue;
|
|
@@ -19230,7 +19268,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19230
19268
|
}
|
|
19231
19269
|
}
|
|
19232
19270
|
const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
|
|
19233
|
-
const sourceNode =
|
|
19271
|
+
const sourceNode = nodeOf(sourceId);
|
|
19234
19272
|
if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
|
|
19235
19273
|
anchorDigests[sourceId] = dg;
|
|
19236
19274
|
const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode);
|
|
@@ -19260,12 +19298,12 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19260
19298
|
if (project) {
|
|
19261
19299
|
const now = Date.now();
|
|
19262
19300
|
for (const sourceId of anchorSources) {
|
|
19263
|
-
const source =
|
|
19301
|
+
const source = nodeOf(sourceId);
|
|
19264
19302
|
if (!source) continue;
|
|
19265
19303
|
const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source);
|
|
19266
19304
|
const planned = [];
|
|
19267
19305
|
for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
|
|
19268
|
-
const target =
|
|
19306
|
+
const target = nodeOf(e.to);
|
|
19269
19307
|
if (!target) continue;
|
|
19270
19308
|
if (!isAnchorLabelKind(target.label)) continue;
|
|
19271
19309
|
const isFile = target.label === "File";
|
|
@@ -19342,7 +19380,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19342
19380
|
const outs = [...dedup.values()].map((e) => ({ e, wireTo: semanticEndpoints.get(e.to) })).filter((x) => x.wireTo != null);
|
|
19343
19381
|
if (outs.length === 0) continue;
|
|
19344
19382
|
const dg = digest(outs.map(({ e, wireTo }) => `${e.type}>${wireTo}`).sort());
|
|
19345
|
-
const owner =
|
|
19383
|
+
const owner = nodeOf(sourceId);
|
|
19346
19384
|
const digestSkip = owner?.attrs["semanticEdgesContributedDigest"] === dg;
|
|
19347
19385
|
if (!digestSkip) semanticEdgeDigests[sourceId] = dg;
|
|
19348
19386
|
for (const { e, wireTo } of outs) {
|
|
@@ -19399,7 +19437,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19399
19437
|
const heldOrphans = /* @__PURE__ */ new Set();
|
|
19400
19438
|
for (const wn of nodes) {
|
|
19401
19439
|
if (wn.label !== "RootCause" && wn.label !== "Solution") continue;
|
|
19402
|
-
const src =
|
|
19440
|
+
const src = nodeOf(wn.id);
|
|
19403
19441
|
if (typeof src?.attrs["contributedAtSeq"] === "number") continue;
|
|
19404
19442
|
if (bindingEndpoints.has(wn.id)) continue;
|
|
19405
19443
|
heldOrphans.add(wn.id);
|
|
@@ -19444,6 +19482,21 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
19444
19482
|
}
|
|
19445
19483
|
|
|
19446
19484
|
// src/drain-build.ts
|
|
19485
|
+
var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
|
|
19486
|
+
function codeGraphKey(store) {
|
|
19487
|
+
const files = store.labelFingerprint?.(["File"]);
|
|
19488
|
+
if (files === void 0) return null;
|
|
19489
|
+
return [files, ...LEXICON_SYMBOL_LABELS.map((l) => store.countNodesByLabel(l))].join("|");
|
|
19490
|
+
}
|
|
19491
|
+
var lexiconCache = /* @__PURE__ */ new WeakMap();
|
|
19492
|
+
function lexiconsFor(store) {
|
|
19493
|
+
const key = codeGraphKey(store);
|
|
19494
|
+
const hit = key !== null ? lexiconCache.get(store) : void 0;
|
|
19495
|
+
if (hit && hit.key === key) return hit;
|
|
19496
|
+
const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
19497
|
+
if (key !== null) lexiconCache.set(store, { key, ...built });
|
|
19498
|
+
return built;
|
|
19499
|
+
}
|
|
19447
19500
|
function buildDrainContext(store, input) {
|
|
19448
19501
|
return buildContextIngest(store, input.profile, input.daemonVersion, input.ignorePatterns, {
|
|
19449
19502
|
...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {}
|
|
@@ -19451,9 +19504,10 @@ function buildDrainContext(store, input) {
|
|
|
19451
19504
|
}
|
|
19452
19505
|
function buildDrainInstances(store, sharedStore, input) {
|
|
19453
19506
|
let lexicon;
|
|
19507
|
+
let fileLexicon;
|
|
19454
19508
|
let lexiconError;
|
|
19455
19509
|
try {
|
|
19456
|
-
lexicon =
|
|
19510
|
+
({ symbols: lexicon, files: fileLexicon } = lexiconsFor(store));
|
|
19457
19511
|
} catch (err) {
|
|
19458
19512
|
lexiconError = err instanceof Error ? err.message : String(err);
|
|
19459
19513
|
}
|
|
@@ -19466,6 +19520,7 @@ function buildDrainInstances(store, sharedStore, input) {
|
|
|
19466
19520
|
composition = c;
|
|
19467
19521
|
},
|
|
19468
19522
|
...lexicon ? { lexicon } : {},
|
|
19523
|
+
...fileLexicon ? { fileLexicon } : {},
|
|
19469
19524
|
...input.project ? { project: input.project } : {}
|
|
19470
19525
|
});
|
|
19471
19526
|
return {
|
package/errata.mjs
CHANGED
|
@@ -17566,6 +17566,10 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
|
|
|
17566
17566
|
`;
|
|
17567
17567
|
SqliteGraphStore = class {
|
|
17568
17568
|
db;
|
|
17569
|
+
// Typed edge lookups build their SQL from the number of types asked for, and
|
|
17570
|
+
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
17571
|
+
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
17572
|
+
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
17569
17573
|
stmts;
|
|
17570
17574
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
17571
17575
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -18070,6 +18074,14 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
|
|
|
18070
18074
|
const rows = this.db.prepare(sql).all(params);
|
|
18071
18075
|
return rows.map(rowToNode);
|
|
18072
18076
|
}
|
|
18077
|
+
typedEdgeStmt(sql) {
|
|
18078
|
+
let stmt = this.typedEdgeStmts.get(sql);
|
|
18079
|
+
if (!stmt) {
|
|
18080
|
+
stmt = this.db.prepare(sql);
|
|
18081
|
+
this.typedEdgeStmts.set(sql, stmt);
|
|
18082
|
+
}
|
|
18083
|
+
return stmt;
|
|
18084
|
+
}
|
|
18073
18085
|
outEdges(nodeId, types) {
|
|
18074
18086
|
if (!types || types.length === 0) {
|
|
18075
18087
|
const rows2 = this.stmts.outEdges.all(nodeId);
|
|
@@ -18080,7 +18092,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
|
|
|
18080
18092
|
types.forEach((t, i2) => {
|
|
18081
18093
|
params[`t${i2}`] = t;
|
|
18082
18094
|
});
|
|
18083
|
-
const rows = this.
|
|
18095
|
+
const rows = this.typedEdgeStmt(
|
|
18084
18096
|
`SELECT * FROM edges WHERE from_id = :from_id AND type IN (${placeholders}) AND valid_to IS NULL`
|
|
18085
18097
|
).all(params);
|
|
18086
18098
|
return rows.map(rowToEdge);
|
|
@@ -18095,7 +18107,7 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
|
|
|
18095
18107
|
types.forEach((t, i2) => {
|
|
18096
18108
|
params[`t${i2}`] = t;
|
|
18097
18109
|
});
|
|
18098
|
-
const rows = this.
|
|
18110
|
+
const rows = this.typedEdgeStmt(
|
|
18099
18111
|
`SELECT * FROM edges WHERE to_id = :to_id AND type IN (${placeholders}) AND valid_to IS NULL`
|
|
18100
18112
|
).all(params);
|
|
18101
18113
|
return rows.map(rowToEdge);
|
|
@@ -18173,6 +18185,15 @@ CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) W
|
|
|
18173
18185
|
const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
|
|
18174
18186
|
return rows.map((r) => r.id);
|
|
18175
18187
|
}
|
|
18188
|
+
labelFingerprint(labels) {
|
|
18189
|
+
if (labels.length === 0) return "0";
|
|
18190
|
+
const r = this.db.prepare(
|
|
18191
|
+
`SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
|
|
18192
|
+
MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
|
|
18193
|
+
FROM nodes WHERE valid_to IS NULL AND label IN (${labels.map(() => "?").join(",")})`
|
|
18194
|
+
).get(...labels);
|
|
18195
|
+
return `${r.n}:${r.t ?? 0}:${r.s ?? 0}`;
|
|
18196
|
+
}
|
|
18176
18197
|
/** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
|
|
18177
18198
|
* Closures matter as much as additions: a node that lost inflow is rank-dirty
|
|
18178
18199
|
* while its own row never updated, so removal endpoints must seed the
|
|
@@ -71163,7 +71184,7 @@ var init_report_render = __esm({
|
|
|
71163
71184
|
init_src6();
|
|
71164
71185
|
import { closeSync as closeSync2, existsSync as existsSync34, openSync as openSync2, readFileSync as readFileSync34, renameSync as renameSync4, statSync as statSync8 } from "node:fs";
|
|
71165
71186
|
import { join as join39, resolve as pathResolve2 } from "node:path";
|
|
71166
|
-
import { spawn as
|
|
71187
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
71167
71188
|
|
|
71168
71189
|
// src/daemon.ts
|
|
71169
71190
|
import { existsSync as existsSync26, writeFileSync as writeFileSync22 } from "node:fs";
|
|
@@ -78515,7 +78536,7 @@ function createWatchBreaker(deps) {
|
|
|
78515
78536
|
}
|
|
78516
78537
|
|
|
78517
78538
|
// src/engine.ts
|
|
78518
|
-
var DAEMON_VERSION = true ? "2.0.6-dev.
|
|
78539
|
+
var DAEMON_VERSION = true ? "2.0.6-dev.2674" : "2.0.0-alpha.0";
|
|
78519
78540
|
var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
|
|
78520
78541
|
var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
|
|
78521
78542
|
var GIT_OP_MUTE_MS = 4e3;
|
|
@@ -81236,10 +81257,16 @@ function internalPackageNames(workspaceRoot) {
|
|
|
81236
81257
|
return names;
|
|
81237
81258
|
}
|
|
81238
81259
|
var cache = /* @__PURE__ */ new Map();
|
|
81260
|
+
var cacheKeys = /* @__PURE__ */ new Map();
|
|
81239
81261
|
function publicSymbolIndex(store, workspaceRoot, deps = {}) {
|
|
81240
81262
|
const now = deps.nowMs ?? Date.now();
|
|
81263
|
+
const fileRows = store.labelFingerprint?.(["File"]);
|
|
81264
|
+
const key = fileRows === void 0 ? void 0 : `${fileRows}|${store.countNodesByLabel("Module")}`;
|
|
81241
81265
|
const cached2 = cache.get(workspaceRoot);
|
|
81242
|
-
if (cached2
|
|
81266
|
+
if (cached2) {
|
|
81267
|
+
const fresh = key !== void 0 ? cacheKeys.get(workspaceRoot) === key : now - cached2.builtAt < INDEX_TTL_MS;
|
|
81268
|
+
if (fresh) return cached2;
|
|
81269
|
+
}
|
|
81243
81270
|
const readHead = deps.readHead ?? ((absPath) => {
|
|
81244
81271
|
try {
|
|
81245
81272
|
return readFileSync28(absPath, "utf8").slice(0, HEAD_BYTES);
|
|
@@ -81276,6 +81303,8 @@ function publicSymbolIndex(store, workspaceRoot, deps = {}) {
|
|
|
81276
81303
|
}
|
|
81277
81304
|
const index = { symbols, builtAt: now };
|
|
81278
81305
|
cache.set(workspaceRoot, index);
|
|
81306
|
+
if (key !== void 0) cacheKeys.set(workspaceRoot, key);
|
|
81307
|
+
else cacheKeys.delete(workspaceRoot);
|
|
81279
81308
|
return index;
|
|
81280
81309
|
}
|
|
81281
81310
|
var PRESERVED_SYMBOLS_CAP = 8;
|
|
@@ -81410,7 +81439,16 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
81410
81439
|
...profile.repoLocatorAliases ? { aliases: profile.repoLocatorAliases } : {},
|
|
81411
81440
|
...profile.aka ? { aka: profile.aka } : {}
|
|
81412
81441
|
};
|
|
81413
|
-
const fileLexicon = buildFileLexicon(store);
|
|
81442
|
+
const fileLexicon = opts.fileLexicon ?? buildFileLexicon(store);
|
|
81443
|
+
const nodeMemo = /* @__PURE__ */ new Map();
|
|
81444
|
+
const nodeOf = (id) => {
|
|
81445
|
+
let n = nodeMemo.get(id);
|
|
81446
|
+
if (n === void 0) {
|
|
81447
|
+
n = store.getNode(id) ?? opts.sharedStore?.getNode(id) ?? null;
|
|
81448
|
+
nodeMemo.set(id, n);
|
|
81449
|
+
}
|
|
81450
|
+
return n;
|
|
81451
|
+
};
|
|
81414
81452
|
const shareable = (n) => {
|
|
81415
81453
|
const preserved = symbolIndex ? preservedSymbolsFor(n.description, symbolIndex) : [];
|
|
81416
81454
|
const fileRefs = generalizeFileRefs(n.description, fileLexicon, level);
|
|
@@ -81535,7 +81573,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
81535
81573
|
for (const sourceId of anchorSources) {
|
|
81536
81574
|
const targets = [];
|
|
81537
81575
|
for (const e of store.outEdges(sourceId, [...ANCHOR_EDGES])) {
|
|
81538
|
-
const t =
|
|
81576
|
+
const t = nodeOf(e.to);
|
|
81539
81577
|
if (!t || !ANCHOR_TARGET_LABELS.includes(t.label)) continue;
|
|
81540
81578
|
if (e.type === "OCCURS_IN" && t.label !== "Language") continue;
|
|
81541
81579
|
if (e.type === "DEPENDS_ON" && t.label !== "Package") continue;
|
|
@@ -81558,7 +81596,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
81558
81596
|
}
|
|
81559
81597
|
}
|
|
81560
81598
|
const dg = digest(targets.map(({ e, wireId }) => `${e.type}>${wireId}`).sort());
|
|
81561
|
-
const sourceNode =
|
|
81599
|
+
const sourceNode = nodeOf(sourceId);
|
|
81562
81600
|
if (sourceNode?.attrs["anchorsContributedDigest"] === dg) continue;
|
|
81563
81601
|
anchorDigests[sourceId] = dg;
|
|
81564
81602
|
const wireFrom = seen.has(sourceId) || !sourceNode ? sourceId : contributedWireId(sourceNode);
|
|
@@ -81588,12 +81626,12 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
81588
81626
|
if (project) {
|
|
81589
81627
|
const now = Date.now();
|
|
81590
81628
|
for (const sourceId of anchorSources) {
|
|
81591
|
-
const source =
|
|
81629
|
+
const source = nodeOf(sourceId);
|
|
81592
81630
|
if (!source) continue;
|
|
81593
81631
|
const wireFrom = seen.has(sourceId) ? sourceId : contributedWireId(source);
|
|
81594
81632
|
const planned = [];
|
|
81595
81633
|
for (const e of store.outEdges(sourceId, ["ANCHORED_AT"])) {
|
|
81596
|
-
const target =
|
|
81634
|
+
const target = nodeOf(e.to);
|
|
81597
81635
|
if (!target) continue;
|
|
81598
81636
|
if (!isAnchorLabelKind(target.label)) continue;
|
|
81599
81637
|
const isFile = target.label === "File";
|
|
@@ -81670,7 +81708,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
81670
81708
|
const outs = [...dedup.values()].map((e) => ({ e, wireTo: semanticEndpoints.get(e.to) })).filter((x) => x.wireTo != null);
|
|
81671
81709
|
if (outs.length === 0) continue;
|
|
81672
81710
|
const dg = digest(outs.map(({ e, wireTo }) => `${e.type}>${wireTo}`).sort());
|
|
81673
|
-
const owner =
|
|
81711
|
+
const owner = nodeOf(sourceId);
|
|
81674
81712
|
const digestSkip = owner?.attrs["semanticEdgesContributedDigest"] === dg;
|
|
81675
81713
|
if (!digestSkip) semanticEdgeDigests[sourceId] = dg;
|
|
81676
81714
|
for (const { e, wireTo } of outs) {
|
|
@@ -81727,7 +81765,7 @@ function buildInstanceIngest(store, profile, daemonVersion, ignorePatterns = [],
|
|
|
81727
81765
|
const heldOrphans = /* @__PURE__ */ new Set();
|
|
81728
81766
|
for (const wn of nodes) {
|
|
81729
81767
|
if (wn.label !== "RootCause" && wn.label !== "Solution") continue;
|
|
81730
|
-
const src =
|
|
81768
|
+
const src = nodeOf(wn.id);
|
|
81731
81769
|
if (typeof src?.attrs["contributedAtSeq"] === "number") continue;
|
|
81732
81770
|
if (bindingEndpoints.has(wn.id)) continue;
|
|
81733
81771
|
heldOrphans.add(wn.id);
|
|
@@ -81811,8 +81849,58 @@ function buildCausalEdgeBackfill(store, profile, daemonVersion) {
|
|
|
81811
81849
|
return { ...base, payloadDigest: digest(base) };
|
|
81812
81850
|
}
|
|
81813
81851
|
|
|
81852
|
+
// src/live-sync.ts
|
|
81853
|
+
var LIVE_SYNC_DEFAULTS = {
|
|
81854
|
+
enabled: true,
|
|
81855
|
+
debounceMs: 1e4,
|
|
81856
|
+
maxWaitMs: 6e4
|
|
81857
|
+
};
|
|
81858
|
+
var BACKOFF_BASE_MS = 5 * 6e4;
|
|
81859
|
+
var BACKOFF_MAX_MS = 60 * 6e4;
|
|
81860
|
+
function positiveMs(raw2, fallback) {
|
|
81861
|
+
if (raw2 === void 0 || raw2.trim() === "") return fallback;
|
|
81862
|
+
const n = Number(raw2);
|
|
81863
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
81864
|
+
}
|
|
81865
|
+
function liveSyncConfig(env2 = process.env) {
|
|
81866
|
+
const flag = (env2["ERRATA_LIVE_SYNC"] ?? "").trim().toLowerCase();
|
|
81867
|
+
const debounceMs = positiveMs(env2["ERRATA_LIVE_SYNC_DEBOUNCE_MS"], LIVE_SYNC_DEFAULTS.debounceMs);
|
|
81868
|
+
return {
|
|
81869
|
+
enabled: !["0", "false", "off", "no"].includes(flag),
|
|
81870
|
+
debounceMs,
|
|
81871
|
+
// A ceiling below the debounce would make the debounce meaningless.
|
|
81872
|
+
maxWaitMs: Math.max(debounceMs, positiveMs(env2["ERRATA_LIVE_SYNC_MAX_WAIT_MS"], LIVE_SYNC_DEFAULTS.maxWaitMs))
|
|
81873
|
+
};
|
|
81874
|
+
}
|
|
81875
|
+
function liveSyncEnabled(env2 = process.env) {
|
|
81876
|
+
return liveSyncConfig(env2).enabled;
|
|
81877
|
+
}
|
|
81878
|
+
function liveShipDelay(now, dirtySince, cfg) {
|
|
81879
|
+
const ceiling = dirtySince + cfg.maxWaitMs - now;
|
|
81880
|
+
return Math.max(0, Math.min(cfg.debounceMs, ceiling));
|
|
81881
|
+
}
|
|
81882
|
+
function liveBackoffMs(failures) {
|
|
81883
|
+
if (failures <= 0) return 0;
|
|
81884
|
+
return Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** (failures - 1));
|
|
81885
|
+
}
|
|
81886
|
+
|
|
81814
81887
|
// src/drain-build.ts
|
|
81815
81888
|
init_generalize_graph();
|
|
81889
|
+
var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
|
|
81890
|
+
function codeGraphKey(store) {
|
|
81891
|
+
const files = store.labelFingerprint?.(["File"]);
|
|
81892
|
+
if (files === void 0) return null;
|
|
81893
|
+
return [files, ...LEXICON_SYMBOL_LABELS.map((l) => store.countNodesByLabel(l))].join("|");
|
|
81894
|
+
}
|
|
81895
|
+
var lexiconCache = /* @__PURE__ */ new WeakMap();
|
|
81896
|
+
function lexiconsFor(store) {
|
|
81897
|
+
const key = codeGraphKey(store);
|
|
81898
|
+
const hit = key !== null ? lexiconCache.get(store) : void 0;
|
|
81899
|
+
if (hit && hit.key === key) return hit;
|
|
81900
|
+
const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
81901
|
+
if (key !== null) lexiconCache.set(store, { key, ...built });
|
|
81902
|
+
return built;
|
|
81903
|
+
}
|
|
81816
81904
|
function buildDrainContext(store, input) {
|
|
81817
81905
|
return buildContextIngest(store, input.profile, input.daemonVersion, input.ignorePatterns, {
|
|
81818
81906
|
...input.includePackages !== void 0 ? { includePackages: input.includePackages } : {}
|
|
@@ -81820,9 +81908,10 @@ function buildDrainContext(store, input) {
|
|
|
81820
81908
|
}
|
|
81821
81909
|
function buildDrainInstances(store, sharedStore, input) {
|
|
81822
81910
|
let lexicon;
|
|
81911
|
+
let fileLexicon;
|
|
81823
81912
|
let lexiconError;
|
|
81824
81913
|
try {
|
|
81825
|
-
lexicon =
|
|
81914
|
+
({ symbols: lexicon, files: fileLexicon } = lexiconsFor(store));
|
|
81826
81915
|
} catch (err2) {
|
|
81827
81916
|
lexiconError = err2 instanceof Error ? err2.message : String(err2);
|
|
81828
81917
|
}
|
|
@@ -81835,6 +81924,7 @@ function buildDrainInstances(store, sharedStore, input) {
|
|
|
81835
81924
|
composition = c;
|
|
81836
81925
|
},
|
|
81837
81926
|
...lexicon ? { lexicon } : {},
|
|
81927
|
+
...fileLexicon ? { fileLexicon } : {},
|
|
81838
81928
|
...input.project ? { project: input.project } : {}
|
|
81839
81929
|
});
|
|
81840
81930
|
return {
|
|
@@ -82620,6 +82710,17 @@ function isGeneratedPath(path2) {
|
|
|
82620
82710
|
function isReleaseSubject(subject) {
|
|
82621
82711
|
return RELEASE_SUBJECT_RE.test(subject.trim());
|
|
82622
82712
|
}
|
|
82713
|
+
var FORGE_MARK_RES = [/^See merge request \S+!\d+$/i, /^Merge pull request #\d+ from \S+$/i];
|
|
82714
|
+
var TRAILER_RES = [
|
|
82715
|
+
/^(Closes|Fixes|Resolves|Related to|Part of)\b.*$/i,
|
|
82716
|
+
/^(Co-Authored-By|Signed-off-by|Reviewed-by|Approved-by):/i
|
|
82717
|
+
];
|
|
82718
|
+
function isForgeTrailerBody(body2) {
|
|
82719
|
+
const lines = body2.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
82720
|
+
if (!lines.some((l) => FORGE_MARK_RES.some((re) => re.test(l)))) return false;
|
|
82721
|
+
const own = lines.filter((l) => !FORGE_MARK_RES.some((re) => re.test(l)) && !TRAILER_RES.some((re) => re.test(l)));
|
|
82722
|
+
return own.length <= 1;
|
|
82723
|
+
}
|
|
82623
82724
|
function classifySkip(subject, files, tags) {
|
|
82624
82725
|
if (files.length > 0 && files.every((f) => f.binary || isLockfilePath(f.path) || isGeneratedPath(f.path))) {
|
|
82625
82726
|
return "lockfile-only";
|
|
@@ -82628,6 +82729,7 @@ function classifySkip(subject, files, tags) {
|
|
|
82628
82729
|
return void 0;
|
|
82629
82730
|
}
|
|
82630
82731
|
var GIT_OPTS = ["-c", "core.quotepath=false", "-c", "color.ui=never", "--no-pager"];
|
|
82732
|
+
var MERGE_SIDE_CONCURRENCY = 6;
|
|
82631
82733
|
var META_FMT = ["%H", "%P", "%aI", "%s", "%b"].join("%x00") + "%x00";
|
|
82632
82734
|
function parseMeta(head2) {
|
|
82633
82735
|
const [sha2, parents, authorDate, subject, body2] = head2.split("\0");
|
|
@@ -82666,6 +82768,38 @@ async function readCommit(root, sha2) {
|
|
|
82666
82768
|
const rest2 = parts2.slice(5).join("\0");
|
|
82667
82769
|
return { ...parseMeta(head2), files: parseNumstat(rest2) };
|
|
82668
82770
|
}
|
|
82771
|
+
async function readCommitsBulk(root, branch, since) {
|
|
82772
|
+
const out2 = await git(root, [
|
|
82773
|
+
...GIT_OPTS,
|
|
82774
|
+
"log",
|
|
82775
|
+
"--numstat",
|
|
82776
|
+
"-M",
|
|
82777
|
+
"--diff-merges=first-parent",
|
|
82778
|
+
`--format=%x1e${META_FMT}`,
|
|
82779
|
+
...since ? [`^${since}`] : [],
|
|
82780
|
+
branch
|
|
82781
|
+
]);
|
|
82782
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
82783
|
+
for (const rec of out2.split("")) {
|
|
82784
|
+
if (!rec) continue;
|
|
82785
|
+
const parts2 = rec.split("\0");
|
|
82786
|
+
const meta3 = parseMeta(parts2.slice(0, 5).join("\0"));
|
|
82787
|
+
if (meta3.sha) map2.set(meta3.sha, { ...meta3, files: parseNumstat(parts2.slice(5).join("\0")) });
|
|
82788
|
+
}
|
|
82789
|
+
return map2;
|
|
82790
|
+
}
|
|
82791
|
+
async function mapBounded(items, limit, fn) {
|
|
82792
|
+
const out2 = new Array(items.length);
|
|
82793
|
+
let next = 0;
|
|
82794
|
+
const worker = async () => {
|
|
82795
|
+
while (next < items.length) {
|
|
82796
|
+
const i2 = next++;
|
|
82797
|
+
out2[i2] = await fn(items[i2]);
|
|
82798
|
+
}
|
|
82799
|
+
};
|
|
82800
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
82801
|
+
return out2;
|
|
82802
|
+
}
|
|
82669
82803
|
async function firstParentLine(root, branch) {
|
|
82670
82804
|
const out2 = await git(root, [...GIT_OPTS, "rev-list", "--first-parent", "--reverse", "--parents", branch]);
|
|
82671
82805
|
return out2.split(/\r?\n/).filter((l) => l.trim().length > 0).map((l) => {
|
|
@@ -82705,11 +82839,18 @@ async function enumerateUnits(root, opts = {}) {
|
|
|
82705
82839
|
}
|
|
82706
82840
|
const tags = await tagsByCommit(root);
|
|
82707
82841
|
const tagsOf = (sha2) => tags.get(sha2) ?? [];
|
|
82842
|
+
if (start2 >= line.length) return [];
|
|
82843
|
+
const commits = await readCommitsBulk(root, branch, start2 > 0 ? line[start2 - 1].sha : null);
|
|
82844
|
+
const commit = async (sha2) => commits.get(sha2) ?? await readCommit(root, sha2);
|
|
82845
|
+
const merges = line.slice(start2).filter((c) => c.parents.length >= 2);
|
|
82846
|
+
const sides = /* @__PURE__ */ new Map();
|
|
82847
|
+
const sideLists = await mapBounded(merges, MERGE_SIDE_CONCURRENCY, (m) => mergeSide(root, m.parents[0], m.parents[1]));
|
|
82848
|
+
merges.forEach((m, i2) => sides.set(m.sha, sideLists[i2]));
|
|
82708
82849
|
const units = [];
|
|
82709
82850
|
for (let index = start2; index < line.length; index++) {
|
|
82710
82851
|
const { sha: sha2, parents } = line[index];
|
|
82711
82852
|
if (parents.length < 2) {
|
|
82712
|
-
const c = await
|
|
82853
|
+
const c = await commit(sha2);
|
|
82713
82854
|
const skip = classifySkip(c.subject, c.files, tagsOf(sha2));
|
|
82714
82855
|
units.push({
|
|
82715
82856
|
index,
|
|
@@ -82725,11 +82866,11 @@ async function enumerateUnits(root, opts = {}) {
|
|
|
82725
82866
|
});
|
|
82726
82867
|
continue;
|
|
82727
82868
|
}
|
|
82728
|
-
const side = await mergeSide(root, parents[0], parents[1]);
|
|
82869
|
+
const side = sides.get(sha2) ?? await mergeSide(root, parents[0], parents[1]);
|
|
82729
82870
|
const group = [];
|
|
82730
82871
|
for (const s of side) {
|
|
82731
82872
|
if (s.parents.length >= 2) continue;
|
|
82732
|
-
const c = await
|
|
82873
|
+
const c = await commit(s.sha);
|
|
82733
82874
|
const skip = classifySkip(c.subject, c.files, tagsOf(s.sha));
|
|
82734
82875
|
group.push({
|
|
82735
82876
|
index,
|
|
@@ -82745,8 +82886,9 @@ async function enumerateUnits(root, opts = {}) {
|
|
|
82745
82886
|
groupSha: sha2
|
|
82746
82887
|
});
|
|
82747
82888
|
}
|
|
82748
|
-
const merge2 = await
|
|
82749
|
-
|
|
82889
|
+
const merge2 = await commit(sha2);
|
|
82890
|
+
const hasMembers = group.some((g) => !g.skip);
|
|
82891
|
+
if (merge2.body.length > 0 && !(hasMembers && isForgeTrailerBody(merge2.body))) {
|
|
82750
82892
|
const skip = classifySkip(merge2.subject, merge2.files, tagsOf(sha2));
|
|
82751
82893
|
group.push({
|
|
82752
82894
|
index,
|
|
@@ -83978,10 +84120,111 @@ async function buildUnitBundle(root, unit, opts = {}) {
|
|
|
83978
84120
|
};
|
|
83979
84121
|
}
|
|
83980
84122
|
|
|
84123
|
+
// src/git-batch.ts
|
|
84124
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
84125
|
+
var GitBlobReader = class {
|
|
84126
|
+
constructor(root, maxCached = 4096) {
|
|
84127
|
+
this.root = root;
|
|
84128
|
+
this.maxCached = maxCached;
|
|
84129
|
+
}
|
|
84130
|
+
proc = null;
|
|
84131
|
+
buf = Buffer.alloc(0);
|
|
84132
|
+
queue = [];
|
|
84133
|
+
cache = /* @__PURE__ */ new Map();
|
|
84134
|
+
failed = null;
|
|
84135
|
+
start() {
|
|
84136
|
+
if (this.proc) return this.proc;
|
|
84137
|
+
const proc = spawn3("git", ["-C", this.root, "cat-file", "--batch"], { windowsHide: true });
|
|
84138
|
+
proc.stdout.on("data", (chunk) => {
|
|
84139
|
+
this.buf = this.buf.length === 0 ? chunk : Buffer.concat([this.buf, chunk]);
|
|
84140
|
+
this.drain();
|
|
84141
|
+
});
|
|
84142
|
+
const fail = (err2) => {
|
|
84143
|
+
this.failed = err2;
|
|
84144
|
+
this.proc = null;
|
|
84145
|
+
for (const p of this.queue.splice(0)) p.reject(err2);
|
|
84146
|
+
};
|
|
84147
|
+
proc.on("error", fail);
|
|
84148
|
+
proc.stdin.on("error", fail);
|
|
84149
|
+
proc.on("exit", (code) => {
|
|
84150
|
+
if (this.queue.length > 0) fail(new Error(`git cat-file --batch exited (${code}) with ${this.queue.length} reads pending`));
|
|
84151
|
+
this.proc = null;
|
|
84152
|
+
});
|
|
84153
|
+
this.proc = proc;
|
|
84154
|
+
return proc;
|
|
84155
|
+
}
|
|
84156
|
+
drain() {
|
|
84157
|
+
for (; ; ) {
|
|
84158
|
+
const nl = this.buf.indexOf(10);
|
|
84159
|
+
if (nl < 0 || this.queue.length === 0) return;
|
|
84160
|
+
const header = this.buf.subarray(0, nl).toString("utf8");
|
|
84161
|
+
if (header.endsWith(" missing") || header.endsWith(" ambiguous")) {
|
|
84162
|
+
this.buf = this.buf.subarray(nl + 1);
|
|
84163
|
+
this.queue.shift().resolve(null);
|
|
84164
|
+
continue;
|
|
84165
|
+
}
|
|
84166
|
+
const size = Number(header.slice(header.lastIndexOf(" ") + 1));
|
|
84167
|
+
if (!Number.isFinite(size)) {
|
|
84168
|
+
const err2 = new Error(`git cat-file --batch: unexpected header ${JSON.stringify(header.slice(0, 120))}`);
|
|
84169
|
+
this.queue.shift().reject(err2);
|
|
84170
|
+
this.buf = this.buf.subarray(nl + 1);
|
|
84171
|
+
continue;
|
|
84172
|
+
}
|
|
84173
|
+
if (this.buf.length < nl + 1 + size + 1) return;
|
|
84174
|
+
const body2 = this.buf.subarray(nl + 1, nl + 1 + size).toString("utf8");
|
|
84175
|
+
this.buf = this.buf.subarray(nl + 1 + size + 1);
|
|
84176
|
+
const oid = header.slice(0, header.indexOf(" "));
|
|
84177
|
+
this.remember(oid, body2);
|
|
84178
|
+
this.queue.shift().resolve(body2);
|
|
84179
|
+
}
|
|
84180
|
+
}
|
|
84181
|
+
remember(oid, text) {
|
|
84182
|
+
if (this.cache.size >= this.maxCached) {
|
|
84183
|
+
const first = this.cache.keys().next().value;
|
|
84184
|
+
if (first !== void 0) this.cache.delete(first);
|
|
84185
|
+
}
|
|
84186
|
+
this.cache.set(oid, text);
|
|
84187
|
+
}
|
|
84188
|
+
/** Read one object by blob id (served from cache when seen) or `<rev>:<path>`. */
|
|
84189
|
+
read(spec) {
|
|
84190
|
+
const hit = this.cache.get(spec);
|
|
84191
|
+
if (hit !== void 0) return Promise.resolve(hit);
|
|
84192
|
+
if (this.failed) return Promise.reject(this.failed);
|
|
84193
|
+
if (/[\r\n]/.test(spec)) return Promise.resolve(null);
|
|
84194
|
+
const proc = this.start();
|
|
84195
|
+
return new Promise((resolve7, reject) => {
|
|
84196
|
+
this.queue.push({ resolve: resolve7, reject });
|
|
84197
|
+
proc.stdin.write(`${spec}
|
|
84198
|
+
`);
|
|
84199
|
+
});
|
|
84200
|
+
}
|
|
84201
|
+
/**
|
|
84202
|
+
* End the process and wait for it to exit: on Windows a live cat-file keeps
|
|
84203
|
+
* the repo's pack files open, so a caller that removes or moves the checkout
|
|
84204
|
+
* right after the walk (EPERM) must not race it. Bounded, never throws.
|
|
84205
|
+
*/
|
|
84206
|
+
close(timeoutMs = 5e3) {
|
|
84207
|
+
const proc = this.proc;
|
|
84208
|
+
this.proc = null;
|
|
84209
|
+
if (!proc || proc.exitCode !== null) return Promise.resolve();
|
|
84210
|
+
return new Promise((resolve7) => {
|
|
84211
|
+
const timer = setTimeout(() => {
|
|
84212
|
+
proc.kill();
|
|
84213
|
+
resolve7();
|
|
84214
|
+
}, timeoutMs);
|
|
84215
|
+
proc.once("exit", () => {
|
|
84216
|
+
clearTimeout(timer);
|
|
84217
|
+
resolve7();
|
|
84218
|
+
});
|
|
84219
|
+
proc.stdin.end();
|
|
84220
|
+
});
|
|
84221
|
+
}
|
|
84222
|
+
};
|
|
84223
|
+
|
|
83981
84224
|
// src/project-history.ts
|
|
83982
84225
|
init_profile2();
|
|
83983
84226
|
var execFileAsync = promisify(execFile3);
|
|
83984
|
-
var HISTORY_EXTRACTOR_VERSION = "history-
|
|
84227
|
+
var HISTORY_EXTRACTOR_VERSION = "history-2";
|
|
83985
84228
|
var LEASE_POLL_MS = 2e4;
|
|
83986
84229
|
var RECOVERY_DEADLINE_MS = 15 * 6e4;
|
|
83987
84230
|
var MAX_ANCHORS_PER_TOKEN = 20;
|
|
@@ -83997,27 +84240,38 @@ function readIgnoreLines(root, profile) {
|
|
|
83997
84240
|
}
|
|
83998
84241
|
return [...lines, ...profile.ignoreExtra ?? []].map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
83999
84242
|
}
|
|
84000
|
-
|
|
84001
|
-
|
|
84002
|
-
const manifestPaths2 = listing.split("\n").map((l) => l.trim()).filter((p) => p && isManifestPath(p));
|
|
84003
|
-
if (manifestPaths2.length === 0) return void 0;
|
|
84004
|
-
const texts = /* @__PURE__ */ new Map();
|
|
84005
|
-
for (const p of manifestPaths2) {
|
|
84243
|
+
function readBlobViaGit(git3, root) {
|
|
84244
|
+
return async (spec) => {
|
|
84006
84245
|
try {
|
|
84007
|
-
|
|
84246
|
+
return await git3(root, ["show", spec]);
|
|
84008
84247
|
} catch {
|
|
84248
|
+
return null;
|
|
84009
84249
|
}
|
|
84250
|
+
};
|
|
84251
|
+
}
|
|
84252
|
+
function manifestBlobs(listing) {
|
|
84253
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
84254
|
+
for (const line of listing.split("\n")) {
|
|
84255
|
+
const tab = line.indexOf(" ");
|
|
84256
|
+
if (tab < 0) continue;
|
|
84257
|
+
const path2 = line.slice(tab + 1).trim();
|
|
84258
|
+
const [, type, oid] = line.slice(0, tab).split(" ");
|
|
84259
|
+
if (type === "blob" && oid && path2 && isManifestPath(path2)) out2.set(path2, oid);
|
|
84010
84260
|
}
|
|
84261
|
+
return out2;
|
|
84262
|
+
}
|
|
84263
|
+
async function spineForUnit(git3, root, unit, readBlob) {
|
|
84264
|
+
const blobs = manifestBlobs(await git3(root, ["ls-tree", "-r", unit.sha]));
|
|
84265
|
+
if (blobs.size === 0) return void 0;
|
|
84266
|
+
const texts = /* @__PURE__ */ new Map();
|
|
84267
|
+
const read2 = await Promise.all([...blobs].map(async ([p, oid]) => [p, await readBlob(oid).catch(() => null)]));
|
|
84268
|
+
for (const [p, text] of read2) if (text !== null) texts.set(p, text);
|
|
84011
84269
|
const spine = buildSpine(unit.sha, [...texts.keys()], (p) => texts.get(p) ?? "");
|
|
84012
84270
|
const parentTexts = /* @__PURE__ */ new Map();
|
|
84013
84271
|
const readParent = (p) => parentTexts.get(p) ?? null;
|
|
84014
84272
|
for (const f of unit.files) {
|
|
84015
84273
|
if (!isManifestPath(f.path) || parentTexts.has(f.path)) continue;
|
|
84016
|
-
|
|
84017
|
-
parentTexts.set(f.path, await git3(root, ["show", `${unit.sha}^:${f.path}`]));
|
|
84018
|
-
} catch {
|
|
84019
|
-
parentTexts.set(f.path, null);
|
|
84020
|
-
}
|
|
84274
|
+
parentTexts.set(f.path, await readBlob(`${unit.sha}^:${f.path}`).catch(() => null));
|
|
84021
84275
|
}
|
|
84022
84276
|
const rings = depsForUnit(spine, unit.files.map((f) => f.path), readParent);
|
|
84023
84277
|
const wire = (d) => ({ ecosystem: d.ecosystem, name: d.name, version: d.version, kind: d.kind, manifest: d.manifest });
|
|
@@ -84060,16 +84314,36 @@ function stage(profile, patch, now) {
|
|
|
84060
84314
|
headSha: patch.headSha !== void 0 ? patch.headSha : prev?.headSha ?? null,
|
|
84061
84315
|
startedAt: prev?.startedAt ?? at,
|
|
84062
84316
|
updatedAt: at,
|
|
84063
|
-
|
|
84317
|
+
// `lastError: undefined` in the patch CLEARS it (a resumed walk must not
|
|
84318
|
+
// keep showing the error it recovered from); an absent key keeps it.
|
|
84319
|
+
..."lastError" in patch ? patch.lastError ? { lastError: patch.lastError } : {} : prev?.lastError ? { lastError: prev.lastError } : {},
|
|
84064
84320
|
...patch.counts ? { counts: patch.counts } : prev?.counts ? { counts: prev.counts } : {}
|
|
84065
84321
|
}
|
|
84066
84322
|
};
|
|
84067
84323
|
}
|
|
84324
|
+
function unitTiming(postStarted, prepMs, receipt) {
|
|
84325
|
+
const s = (ms) => `${(ms / 1e3).toFixed(1)}s`;
|
|
84326
|
+
const parts2 = [`local ${s(prepMs)}`];
|
|
84327
|
+
const g = receipt.gate;
|
|
84328
|
+
if (g) parts2.push(g.pRoutine === null ? `jev ${g.error ?? "n/a"}` : `jev ${s(g.ms)} p=${g.pRoutine}${g.skipped ? " skipped" : ""}`);
|
|
84329
|
+
if (receipt.timing?.modelMs !== void 0) parts2.push(`model ${s(receipt.timing.modelMs)}`);
|
|
84330
|
+
if (receipt.timing?.ingestMs !== void 0) parts2.push(`ingest ${s(receipt.timing.ingestMs)}`);
|
|
84331
|
+
return ` in ${s(Date.now() - postStarted + prepMs)} (${parts2.join(", ")})`;
|
|
84332
|
+
}
|
|
84068
84333
|
function addCounts(a, b) {
|
|
84069
84334
|
const sum = (k) => (a[k] ?? 0) + (b[k] ?? 0);
|
|
84070
84335
|
return { commitsWalked: sum("commitsWalked"), changesExtracted: sum("changesExtracted"), pairs: sum("pairs"), claims: sum("claims"), anchors: sum("anchors"), packages: sum("packages") };
|
|
84071
84336
|
}
|
|
84072
84337
|
async function runHistoryWalk(root, input, client, opts = {}) {
|
|
84338
|
+
const reader = opts.git ? null : new GitBlobReader(root);
|
|
84339
|
+
const readBlob = reader ? (spec) => reader.read(spec) : readBlobViaGit(opts.git ?? defaultGit, root);
|
|
84340
|
+
try {
|
|
84341
|
+
return await walk10(root, input, client, opts, readBlob);
|
|
84342
|
+
} finally {
|
|
84343
|
+
await reader?.close();
|
|
84344
|
+
}
|
|
84345
|
+
}
|
|
84346
|
+
async function walk10(root, input, client, opts, readBlob) {
|
|
84073
84347
|
const log = opts.log ?? ((line) => console.warn(line));
|
|
84074
84348
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
84075
84349
|
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -84124,6 +84398,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
|
|
|
84124
84398
|
log(`[errata] history ${label} skipped (${u.skip})`);
|
|
84125
84399
|
continue;
|
|
84126
84400
|
}
|
|
84401
|
+
const prepStarted = Date.now();
|
|
84127
84402
|
const bundle = await buildUnitBundle(root, u, {
|
|
84128
84403
|
ignorePatterns: ignoreLines,
|
|
84129
84404
|
narrative: narrative(u, u.isMerge ? group : void 0)
|
|
@@ -84145,7 +84420,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
|
|
|
84145
84420
|
...opts.frame ? { frame: opts.frame } : {},
|
|
84146
84421
|
...dryRun ? { dryRun: true } : {}
|
|
84147
84422
|
};
|
|
84148
|
-
const spine = await spineForUnit(git3, root, u);
|
|
84423
|
+
const spine = await spineForUnit(git3, root, u, readBlob);
|
|
84149
84424
|
if (spine) body2.spine = spine;
|
|
84150
84425
|
const anchors = anchorsFor(bundle.pathTokens, ignoreLines, projectId, opts.anchors);
|
|
84151
84426
|
if (anchors) {
|
|
@@ -84161,6 +84436,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
|
|
|
84161
84436
|
await reportHistoryStage(client, profile, log);
|
|
84162
84437
|
return { profile, processed, state: "refused" };
|
|
84163
84438
|
}
|
|
84439
|
+
const prepMs = Date.now() - prepStarted;
|
|
84164
84440
|
const started2 = Date.now();
|
|
84165
84441
|
let outcome = null;
|
|
84166
84442
|
let attempt = 0;
|
|
@@ -84206,7 +84482,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
|
|
|
84206
84482
|
}
|
|
84207
84483
|
const packages = new Set(outcome.result.nodes.filter((n) => n.decision !== "rejected" && n.canonicalId.startsWith("pkg:")).map((n) => n.canonicalId)).size;
|
|
84208
84484
|
counts = addCounts(counts, { commitsWalked: 1, changesExtracted: 1, pairs: outcome.receipt.keptPairs, claims: outcome.receipt.claims, anchors: outcome.receipt.anchors, packages });
|
|
84209
|
-
log(`[errata] history ${label}: ${outcome.result.nodes.length} nodes, ${outcome.result.edges.length} edges (${outcome.receipt.keptPairs} pairs, ${outcome.receipt.claims} claims)`);
|
|
84485
|
+
log(`[errata] history ${label}: ${outcome.result.nodes.length} nodes, ${outcome.result.edges.length} edges (${outcome.receipt.keptPairs} pairs, ${outcome.receipt.claims} claims)${unitTiming(started2, prepMs, outcome.receipt)}`);
|
|
84210
84486
|
break;
|
|
84211
84487
|
}
|
|
84212
84488
|
case "replayed": {
|
|
@@ -84217,7 +84493,7 @@ async function runHistoryWalk(root, input, client, opts = {}) {
|
|
|
84217
84493
|
case "empty":
|
|
84218
84494
|
case "dry-run":
|
|
84219
84495
|
counts = addCounts(counts, { commitsWalked: 1, changesExtracted: outcome.status === "dry-run" ? 1 : 0, pairs: outcome.receipt.keptPairs, claims: outcome.receipt.claims });
|
|
84220
|
-
log(`[errata] history ${label}: ${outcome.status} (${outcome.receipt.keptPairs} pairs, dropped ${outcome.receipt.droppedByEvidence} by evidence, ${outcome.receipt.droppedByAnchor} by anchor)`);
|
|
84496
|
+
log(`[errata] history ${label}: ${outcome.status} (${outcome.receipt.keptPairs} pairs, dropped ${outcome.receipt.droppedByEvidence} by evidence, ${outcome.receipt.droppedByAnchor} by anchor)${unitTiming(started2, prepMs, outcome.receipt)}`);
|
|
84221
84497
|
break;
|
|
84222
84498
|
case "refused":
|
|
84223
84499
|
profile = stage(profile, { state: "refused", lastError: `unit ${label} refused: ${outcome.reason}` }, now);
|
|
@@ -84299,12 +84575,12 @@ function applyStopRequest(profile, now = () => /* @__PURE__ */ new Date()) {
|
|
|
84299
84575
|
}
|
|
84300
84576
|
};
|
|
84301
84577
|
}
|
|
84302
|
-
async function startHistoryStage(root, input, client,
|
|
84578
|
+
async function startHistoryStage(root, input, client, walk11 = {}) {
|
|
84303
84579
|
let profile = input;
|
|
84304
84580
|
if (!profile.projectId) return { started: false, reason: "no-project", profile };
|
|
84305
84581
|
const response = await reportHistoryStage(client, profile);
|
|
84306
|
-
if (response?.consents &&
|
|
84307
|
-
profile =
|
|
84582
|
+
if (response?.consents && walk11.applyConsents) {
|
|
84583
|
+
profile = walk11.applyConsents(profile, response.consents);
|
|
84308
84584
|
}
|
|
84309
84585
|
const consents = historyConsents(profile);
|
|
84310
84586
|
if (response?.stopRequested && profile.historyStage && profile.historyStage.state !== "refused") {
|
|
@@ -84313,7 +84589,7 @@ async function startHistoryStage(root, input, client, walk10 = {}) {
|
|
|
84313
84589
|
return { started: false, reason: "stopped", profile: stopped };
|
|
84314
84590
|
}
|
|
84315
84591
|
if (!consents.mineHistory) return { started: false, reason: "consent-pending", profile };
|
|
84316
|
-
const outcome = await runHistoryWalk(root, profile, client,
|
|
84592
|
+
const outcome = await runHistoryWalk(root, profile, client, walk11);
|
|
84317
84593
|
return { started: true, profile: outcome.profile };
|
|
84318
84594
|
}
|
|
84319
84595
|
|
|
@@ -85215,6 +85491,8 @@ async function startMultiDaemon(opts = {}) {
|
|
|
85215
85491
|
let serverPending = null;
|
|
85216
85492
|
const boundaryListeners = [];
|
|
85217
85493
|
let boundaryFlushing = false;
|
|
85494
|
+
let pushRequested = false;
|
|
85495
|
+
let shippedFingerprint = "";
|
|
85218
85496
|
let settleRunning = false;
|
|
85219
85497
|
let boundaryFlushStartedAt = 0;
|
|
85220
85498
|
let boundaryFlushStage = "";
|
|
@@ -85284,6 +85562,36 @@ async function startMultiDaemon(opts = {}) {
|
|
|
85284
85562
|
boundaryFlushStage = "";
|
|
85285
85563
|
endFlushPass?.();
|
|
85286
85564
|
endFlushPass = null;
|
|
85565
|
+
if (pushRequested) {
|
|
85566
|
+
pushRequested = false;
|
|
85567
|
+
setImmediate(() => {
|
|
85568
|
+
void daemon.liveShip().catch(() => {
|
|
85569
|
+
});
|
|
85570
|
+
});
|
|
85571
|
+
}
|
|
85572
|
+
};
|
|
85573
|
+
const instanceFingerprint = () => {
|
|
85574
|
+
const cfg2 = loadConfig();
|
|
85575
|
+
const parts2 = [];
|
|
85576
|
+
for (const r of records) {
|
|
85577
|
+
if (!effectiveConsent(cfg2, r.engine.profile).sync) continue;
|
|
85578
|
+
parts2.push(`${r.id}=${r.engine.store.labelFingerprint?.(INSTANCE_LABELS) ?? `?${Date.now()}`}`);
|
|
85579
|
+
}
|
|
85580
|
+
if (parts2.length === 0) return "";
|
|
85581
|
+
parts2.push(`shared=${sharedStore.labelFingerprint?.(["RootCause"]) ?? `?${Date.now()}`}`);
|
|
85582
|
+
return parts2.join("|");
|
|
85583
|
+
};
|
|
85584
|
+
const runSettle = async () => {
|
|
85585
|
+
if (settleRunning) return;
|
|
85586
|
+
settleRunning = true;
|
|
85587
|
+
try {
|
|
85588
|
+
for (const r of records) {
|
|
85589
|
+
const donePass = markPass(`settle:${r.engine.profile.name}`);
|
|
85590
|
+
await r.engine.embedSettled().finally(donePass);
|
|
85591
|
+
}
|
|
85592
|
+
} finally {
|
|
85593
|
+
settleRunning = false;
|
|
85594
|
+
}
|
|
85287
85595
|
};
|
|
85288
85596
|
const fireBoundary = () => {
|
|
85289
85597
|
for (const l of boundaryListeners) {
|
|
@@ -86565,22 +86873,17 @@ async function startMultiDaemon(opts = {}) {
|
|
|
86565
86873
|
return { written, pruned: pruned2 };
|
|
86566
86874
|
},
|
|
86567
86875
|
async sessionBoundaryFlush() {
|
|
86568
|
-
|
|
86569
|
-
|
|
86570
|
-
|
|
86571
|
-
|
|
86572
|
-
const donePass = markPass(`settle:${r.engine.profile.name}`);
|
|
86573
|
-
await r.engine.embedSettled().finally(donePass);
|
|
86574
|
-
}
|
|
86575
|
-
} finally {
|
|
86576
|
-
settleRunning = false;
|
|
86577
|
-
}
|
|
86876
|
+
await runSettle();
|
|
86877
|
+
if (boundaryFlushing) {
|
|
86878
|
+
pushRequested = true;
|
|
86879
|
+
return { uploaded: 0, written: 0, pruned: 0, skipped: "in-flight" };
|
|
86578
86880
|
}
|
|
86579
|
-
if (boundaryFlushing) return { uploaded: 0, written: 0, pruned: 0, skipped: "in-flight" };
|
|
86580
86881
|
beginFlush("instances");
|
|
86581
86882
|
try {
|
|
86582
86883
|
flushStage("instances");
|
|
86884
|
+
const startedFrom = instanceFingerprint();
|
|
86583
86885
|
const inst = await daemon.syncInstancesPublic();
|
|
86886
|
+
shippedFingerprint = startedFrom;
|
|
86584
86887
|
flushStage("skills");
|
|
86585
86888
|
const skills = await daemon.syncSkillsAll();
|
|
86586
86889
|
return {
|
|
@@ -86593,6 +86896,36 @@ async function startMultiDaemon(opts = {}) {
|
|
|
86593
86896
|
endFlush();
|
|
86594
86897
|
}
|
|
86595
86898
|
},
|
|
86899
|
+
pendingLiveFingerprint() {
|
|
86900
|
+
const fp = instanceFingerprint();
|
|
86901
|
+
return fp && fp !== shippedFingerprint ? fp : null;
|
|
86902
|
+
},
|
|
86903
|
+
async liveShip() {
|
|
86904
|
+
if (!liveSyncEnabled()) return { uploaded: 0, skipped: "disabled" };
|
|
86905
|
+
if (daemon.pendingLiveFingerprint() === null) return { uploaded: 0, skipped: "unchanged" };
|
|
86906
|
+
if (boundaryFlushing) {
|
|
86907
|
+
pushRequested = true;
|
|
86908
|
+
return { uploaded: 0, skipped: "in-flight" };
|
|
86909
|
+
}
|
|
86910
|
+
await runSettle();
|
|
86911
|
+
if (boundaryFlushing) {
|
|
86912
|
+
pushRequested = true;
|
|
86913
|
+
return { uploaded: 0, skipped: "in-flight" };
|
|
86914
|
+
}
|
|
86915
|
+
beginFlush("live");
|
|
86916
|
+
try {
|
|
86917
|
+
const startedFrom = instanceFingerprint();
|
|
86918
|
+
const inst = await daemon.syncInstancesPublic();
|
|
86919
|
+
shippedFingerprint = startedFrom;
|
|
86920
|
+
return {
|
|
86921
|
+
uploaded: inst.uploaded,
|
|
86922
|
+
...inst.skipped ? { skipped: inst.skipped } : {},
|
|
86923
|
+
...inst.errors ? { errors: inst.errors } : {}
|
|
86924
|
+
};
|
|
86925
|
+
} finally {
|
|
86926
|
+
endFlush();
|
|
86927
|
+
}
|
|
86928
|
+
},
|
|
86596
86929
|
onSessionBoundary(cb) {
|
|
86597
86930
|
boundaryListeners.push(cb);
|
|
86598
86931
|
},
|
|
@@ -86953,9 +87286,9 @@ function deriveAuthBase(verificationUri) {
|
|
|
86953
87286
|
}
|
|
86954
87287
|
function openInBrowser(url2) {
|
|
86955
87288
|
try {
|
|
86956
|
-
void import("node:child_process").then(({ spawn:
|
|
87289
|
+
void import("node:child_process").then(({ spawn: spawn5 }) => {
|
|
86957
87290
|
const [cmd2, args2] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : process.platform === "darwin" ? ["open", [url2]] : ["xdg-open", [url2]];
|
|
86958
|
-
|
|
87291
|
+
spawn5(cmd2, args2, { detached: true, stdio: "ignore" }).unref();
|
|
86959
87292
|
});
|
|
86960
87293
|
} catch {
|
|
86961
87294
|
}
|
|
@@ -89598,7 +89931,7 @@ function spawnDaemonDetached(options = {}) {
|
|
|
89598
89931
|
out2 = openSync2(logPath, "a");
|
|
89599
89932
|
} catch {
|
|
89600
89933
|
}
|
|
89601
|
-
|
|
89934
|
+
spawn4(cmd2, args2, {
|
|
89602
89935
|
detached: true,
|
|
89603
89936
|
stdio: ["ignore", out2, out2],
|
|
89604
89937
|
windowsHide: true,
|
|
@@ -90059,8 +90392,53 @@ async function cmdDash(args2) {
|
|
|
90059
90392
|
void flushed.finally(() => triggerConsolidation(force));
|
|
90060
90393
|
};
|
|
90061
90394
|
handle2.onSessionBoundary(() => settleThenConsolidate("session-end", true));
|
|
90395
|
+
const liveCfg = liveSyncConfig();
|
|
90396
|
+
let liveTimer = null;
|
|
90397
|
+
let liveDirtySince = 0;
|
|
90398
|
+
let liveSeenFingerprint = "";
|
|
90399
|
+
let liveFailures = 0;
|
|
90400
|
+
let liveBackoffUntil = 0;
|
|
90401
|
+
const fireLiveShip = () => {
|
|
90402
|
+
liveTimer = null;
|
|
90403
|
+
void handle2.liveShip().then((r) => {
|
|
90404
|
+
if (r.skipped === "in-flight") return;
|
|
90405
|
+
liveDirtySince = 0;
|
|
90406
|
+
if (r.errors && r.errors.length > 0) {
|
|
90407
|
+
liveFailures++;
|
|
90408
|
+
const pause = liveBackoffMs(liveFailures);
|
|
90409
|
+
liveBackoffUntil = Date.now() + pause;
|
|
90410
|
+
console.warn(
|
|
90411
|
+
`[live] push had ${r.errors.length} error(s); pausing the live push ${Math.round(pause / 6e4)}m (quiescence / session-end still flush)`
|
|
90412
|
+
);
|
|
90413
|
+
return;
|
|
90414
|
+
}
|
|
90415
|
+
liveFailures = 0;
|
|
90416
|
+
if (r.uploaded > 0) console.log(`[live] uploaded ${r.uploaded} node(s)/edge(s)`);
|
|
90417
|
+
}).catch(() => {
|
|
90418
|
+
});
|
|
90419
|
+
};
|
|
90420
|
+
const probeLivePush = () => {
|
|
90421
|
+
if (!liveCfg.enabled || Date.now() < liveBackoffUntil) return;
|
|
90422
|
+
let fp;
|
|
90423
|
+
try {
|
|
90424
|
+
fp = handle2.pendingLiveFingerprint();
|
|
90425
|
+
} catch {
|
|
90426
|
+
return;
|
|
90427
|
+
}
|
|
90428
|
+
if (fp === null) {
|
|
90429
|
+
liveSeenFingerprint = "";
|
|
90430
|
+
return;
|
|
90431
|
+
}
|
|
90432
|
+
if (fp === liveSeenFingerprint) return;
|
|
90433
|
+
liveSeenFingerprint = fp;
|
|
90434
|
+
const now = Date.now();
|
|
90435
|
+
if (!liveDirtySince) liveDirtySince = now;
|
|
90436
|
+
if (liveTimer) clearTimeout(liveTimer);
|
|
90437
|
+
liveTimer = setTimeout(fireLiveShip, liveShipDelay(now, liveDirtySince, liveCfg));
|
|
90438
|
+
};
|
|
90062
90439
|
let ticking = false;
|
|
90063
90440
|
const interval = setInterval(() => {
|
|
90441
|
+
probeLivePush();
|
|
90064
90442
|
if (ticking) return;
|
|
90065
90443
|
ticking = true;
|
|
90066
90444
|
void handle2.tickAll().then((m) => {
|
|
@@ -90301,6 +90679,7 @@ shutting down\u2026 (${signal})`);
|
|
|
90301
90679
|
clearInterval(interval);
|
|
90302
90680
|
clearInterval(nightlyInterval);
|
|
90303
90681
|
if (quiesceTimer) clearTimeout(quiesceTimer);
|
|
90682
|
+
if (liveTimer) clearTimeout(liveTimer);
|
|
90304
90683
|
await handle2.stop();
|
|
90305
90684
|
process.exit(0);
|
|
90306
90685
|
};
|
package/package.json
CHANGED
package/pass-worker.mjs
CHANGED
|
@@ -24588,6 +24588,10 @@ function rowToEdge(r) {
|
|
|
24588
24588
|
}
|
|
24589
24589
|
var SqliteGraphStore = class {
|
|
24590
24590
|
db;
|
|
24591
|
+
// Typed edge lookups build their SQL from the number of types asked for, and
|
|
24592
|
+
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
24593
|
+
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
24594
|
+
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
24591
24595
|
stmts;
|
|
24592
24596
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
24593
24597
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -25092,6 +25096,14 @@ var SqliteGraphStore = class {
|
|
|
25092
25096
|
const rows = this.db.prepare(sql).all(params);
|
|
25093
25097
|
return rows.map(rowToNode);
|
|
25094
25098
|
}
|
|
25099
|
+
typedEdgeStmt(sql) {
|
|
25100
|
+
let stmt = this.typedEdgeStmts.get(sql);
|
|
25101
|
+
if (!stmt) {
|
|
25102
|
+
stmt = this.db.prepare(sql);
|
|
25103
|
+
this.typedEdgeStmts.set(sql, stmt);
|
|
25104
|
+
}
|
|
25105
|
+
return stmt;
|
|
25106
|
+
}
|
|
25095
25107
|
outEdges(nodeId, types) {
|
|
25096
25108
|
if (!types || types.length === 0) {
|
|
25097
25109
|
const rows2 = this.stmts.outEdges.all(nodeId);
|
|
@@ -25102,7 +25114,7 @@ var SqliteGraphStore = class {
|
|
|
25102
25114
|
types.forEach((t, i2) => {
|
|
25103
25115
|
params[`t${i2}`] = t;
|
|
25104
25116
|
});
|
|
25105
|
-
const rows = this.
|
|
25117
|
+
const rows = this.typedEdgeStmt(
|
|
25106
25118
|
`SELECT * FROM edges WHERE from_id = :from_id AND type IN (${placeholders}) AND valid_to IS NULL`
|
|
25107
25119
|
).all(params);
|
|
25108
25120
|
return rows.map(rowToEdge);
|
|
@@ -25117,7 +25129,7 @@ var SqliteGraphStore = class {
|
|
|
25117
25129
|
types.forEach((t, i2) => {
|
|
25118
25130
|
params[`t${i2}`] = t;
|
|
25119
25131
|
});
|
|
25120
|
-
const rows = this.
|
|
25132
|
+
const rows = this.typedEdgeStmt(
|
|
25121
25133
|
`SELECT * FROM edges WHERE to_id = :to_id AND type IN (${placeholders}) AND valid_to IS NULL`
|
|
25122
25134
|
).all(params);
|
|
25123
25135
|
return rows.map(rowToEdge);
|
|
@@ -25195,6 +25207,15 @@ var SqliteGraphStore = class {
|
|
|
25195
25207
|
const rows = this.db.prepare("SELECT id FROM nodes WHERE valid_to IS NULL AND last_updated_at > ?").all(ts);
|
|
25196
25208
|
return rows.map((r) => r.id);
|
|
25197
25209
|
}
|
|
25210
|
+
labelFingerprint(labels) {
|
|
25211
|
+
if (labels.length === 0) return "0";
|
|
25212
|
+
const r = this.db.prepare(
|
|
25213
|
+
`SELECT COUNT(*) AS n, MAX(last_updated_at) AS t,
|
|
25214
|
+
MAX(COALESCE(last_reinforced_at_seq, created_at_seq, 0)) AS s
|
|
25215
|
+
FROM nodes WHERE valid_to IS NULL AND label IN (${labels.map(() => "?").join(",")})`
|
|
25216
|
+
).get(...labels);
|
|
25217
|
+
return `${r.n}:${r.t ?? 0}:${r.s ?? 0}`;
|
|
25218
|
+
}
|
|
25198
25219
|
/** Endpoints of edges TOUCHED since `ts` — created, re-seen, or CLOSED.
|
|
25199
25220
|
* Closures matter as much as additions: a node that lost inflow is rank-dirty
|
|
25200
25221
|
* while its own row never updated, so removal endpoints must seed the
|