@inerrata-corporation/errata 2.0.22 → 2.0.23-dev.4682
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +1080 -0
- package/consolidate-worker.mjs +1868 -159
- package/errata.mjs +6935 -2403
- package/generalize-worker.mjs +1698 -97
- package/package.json +3 -2
- package/pass-worker.mjs +2307 -361
- package/resources/context-plugin/.codex-plugin/plugin.json +1 -1
- package/resources/context-plugin/SHA256SUMS +3 -3
- package/resources/context-plugin/THIRD-PARTY-NOTICES.md +5 -7
- package/resources/context-plugin/dist/mcp-server.mjs +545 -148
package/generalize-worker.mjs
CHANGED
|
@@ -1235,10 +1235,10 @@ function mergeDefs(...defs) {
|
|
|
1235
1235
|
function cloneDef(schema) {
|
|
1236
1236
|
return mergeDefs(schema._zod.def);
|
|
1237
1237
|
}
|
|
1238
|
-
function getElementAtPath(obj,
|
|
1239
|
-
if (!
|
|
1238
|
+
function getElementAtPath(obj, path2) {
|
|
1239
|
+
if (!path2)
|
|
1240
1240
|
return obj;
|
|
1241
|
-
return
|
|
1241
|
+
return path2.reduce((acc, key) => acc?.[key], obj);
|
|
1242
1242
|
}
|
|
1243
1243
|
function promiseAllObject(promisesObj) {
|
|
1244
1244
|
const keys = Object.keys(promisesObj);
|
|
@@ -1621,11 +1621,11 @@ function aborted(x, startIndex = 0) {
|
|
|
1621
1621
|
}
|
|
1622
1622
|
return false;
|
|
1623
1623
|
}
|
|
1624
|
-
function prefixIssues(
|
|
1624
|
+
function prefixIssues(path2, issues) {
|
|
1625
1625
|
return issues.map((iss) => {
|
|
1626
1626
|
var _a2;
|
|
1627
1627
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
1628
|
-
iss.path.unshift(
|
|
1628
|
+
iss.path.unshift(path2);
|
|
1629
1629
|
return iss;
|
|
1630
1630
|
});
|
|
1631
1631
|
}
|
|
@@ -1808,7 +1808,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1808
1808
|
}
|
|
1809
1809
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
1810
1810
|
const result = { errors: [] };
|
|
1811
|
-
const processError = (error49,
|
|
1811
|
+
const processError = (error49, path2 = []) => {
|
|
1812
1812
|
var _a2, _b;
|
|
1813
1813
|
for (const issue2 of error49.issues) {
|
|
1814
1814
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -1818,7 +1818,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1818
1818
|
} else if (issue2.code === "invalid_element") {
|
|
1819
1819
|
processError({ issues: issue2.issues }, issue2.path);
|
|
1820
1820
|
} else {
|
|
1821
|
-
const fullpath = [...
|
|
1821
|
+
const fullpath = [...path2, ...issue2.path];
|
|
1822
1822
|
if (fullpath.length === 0) {
|
|
1823
1823
|
result.errors.push(mapper(issue2));
|
|
1824
1824
|
continue;
|
|
@@ -1850,8 +1850,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1850
1850
|
}
|
|
1851
1851
|
function toDotPath(_path) {
|
|
1852
1852
|
const segs = [];
|
|
1853
|
-
const
|
|
1854
|
-
for (const seg of
|
|
1853
|
+
const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1854
|
+
for (const seg of path2) {
|
|
1855
1855
|
if (typeof seg === "number")
|
|
1856
1856
|
segs.push(`[${seg}]`);
|
|
1857
1857
|
else if (typeof seg === "symbol")
|
|
@@ -13828,13 +13828,13 @@ function resolveRef(ref, ctx) {
|
|
|
13828
13828
|
if (!ref.startsWith("#")) {
|
|
13829
13829
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
13830
13830
|
}
|
|
13831
|
-
const
|
|
13832
|
-
if (
|
|
13831
|
+
const path2 = ref.slice(1).split("/").filter(Boolean);
|
|
13832
|
+
if (path2.length === 0) {
|
|
13833
13833
|
return ctx.rootSchema;
|
|
13834
13834
|
}
|
|
13835
13835
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
13836
|
-
if (
|
|
13837
|
-
const key =
|
|
13836
|
+
if (path2[0] === defsKey) {
|
|
13837
|
+
const key = path2[1];
|
|
13838
13838
|
if (!key || !ctx.defs[key]) {
|
|
13839
13839
|
throw new Error(`Reference not found: ${ref}`);
|
|
13840
13840
|
}
|
|
@@ -14697,7 +14697,7 @@ var CastaliaIngestPayloadSchema = external_exports.object({
|
|
|
14697
14697
|
});
|
|
14698
14698
|
|
|
14699
14699
|
// ../../packages/shared/src/history-read.ts
|
|
14700
|
-
var CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding"];
|
|
14700
|
+
var CLOUD_ATTR_DUPLICATE_KEYS = ["attrsJson", "embedding", "structEmbedding", "structEmbeddedAt"];
|
|
14701
14701
|
|
|
14702
14702
|
// ../../packages/shared/src/history-revision.ts
|
|
14703
14703
|
var Sha = external_exports.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
|
|
@@ -14712,6 +14712,17 @@ var HistoryRevisionRequestSchema = external_exports.strictObject({
|
|
|
14712
14712
|
dependencyContext: HistoryDependencyContextSchema.optional()
|
|
14713
14713
|
})).max(50)
|
|
14714
14714
|
});
|
|
14715
|
+
var HistoryCarryRequestSchema = external_exports.strictObject({
|
|
14716
|
+
fromCommit: Sha,
|
|
14717
|
+
atCommit: Sha,
|
|
14718
|
+
parents: external_exports.array(external_exports.strictObject({ commit: Sha, parents: external_exports.array(Sha).max(32) })).max(8192),
|
|
14719
|
+
/** Every symbol in a file the range changed, both sides; `null` = too many to name, treat all as changed. */
|
|
14720
|
+
changedSymbols: external_exports.array(external_exports.string().regex(/^sym_[a-f0-9]{8,64}$/)).max(5e3).nullable(),
|
|
14721
|
+
/** Manifests the range changed; `null` = a lockfile (or too many manifests) changed, treat all as changed. */
|
|
14722
|
+
changedManifests: external_exports.array(external_exports.string().regex(/^hmanifest_[a-f0-9]{32}$/)).max(512).nullable()
|
|
14723
|
+
});
|
|
14724
|
+
var HISTORY_REVIEW_BUDGET = 48e3;
|
|
14725
|
+
var HISTORY_REVIEW_POLICY = `reviewer-view-${HISTORY_REVIEW_BUDGET}`;
|
|
14715
14726
|
|
|
14716
14727
|
// ../../packages/shared/src/edit-placeholder.ts
|
|
14717
14728
|
var AUTO_MINT_PREFIX = "addressed by an edit to ";
|
|
@@ -14773,13 +14784,13 @@ function loadNodeSqlite() {
|
|
|
14773
14784
|
}
|
|
14774
14785
|
}
|
|
14775
14786
|
var DatabaseSync = loadNodeSqlite().DatabaseSync;
|
|
14776
|
-
function openDatabase(
|
|
14777
|
-
const db = opts ? new DatabaseSync(
|
|
14787
|
+
function openDatabase(path2, opts) {
|
|
14788
|
+
const db = opts ? new DatabaseSync(path2, opts) : new DatabaseSync(path2);
|
|
14778
14789
|
try {
|
|
14779
14790
|
db.exec("PRAGMA foreign_keys = ON");
|
|
14780
14791
|
} catch {
|
|
14781
14792
|
}
|
|
14782
|
-
if (
|
|
14793
|
+
if (path2 !== ":memory:" && !opts?.readOnly) {
|
|
14783
14794
|
try {
|
|
14784
14795
|
db.exec("PRAGMA journal_mode = WAL");
|
|
14785
14796
|
db.exec("PRAGMA synchronous = NORMAL");
|
|
@@ -14788,20 +14799,20 @@ function openDatabase(path, opts) {
|
|
|
14788
14799
|
} catch {
|
|
14789
14800
|
}
|
|
14790
14801
|
}
|
|
14791
|
-
const wrapStmt = (
|
|
14802
|
+
const wrapStmt = (stmt2) => ({
|
|
14792
14803
|
run: (...params) => {
|
|
14793
|
-
const r =
|
|
14804
|
+
const r = stmt2.run(...coerceParams(params));
|
|
14794
14805
|
return {
|
|
14795
14806
|
changes: Number(r.changes),
|
|
14796
14807
|
lastInsertRowid: Number(r.lastInsertRowid)
|
|
14797
14808
|
};
|
|
14798
14809
|
},
|
|
14799
14810
|
get: (...params) => {
|
|
14800
|
-
const r =
|
|
14811
|
+
const r = stmt2.get(...coerceParams(params));
|
|
14801
14812
|
return r;
|
|
14802
14813
|
},
|
|
14803
14814
|
all: (...params) => {
|
|
14804
|
-
const r =
|
|
14815
|
+
const r = stmt2.all(...coerceParams(params));
|
|
14805
14816
|
return r;
|
|
14806
14817
|
}
|
|
14807
14818
|
});
|
|
@@ -14855,6 +14866,584 @@ function normalizeParam(v) {
|
|
|
14855
14866
|
return v;
|
|
14856
14867
|
}
|
|
14857
14868
|
|
|
14869
|
+
// ../../packages/local-shared/src/tag-grammar.ts
|
|
14870
|
+
var TAG_CANONICAL_ID_PREFIXES = ["pat", "sol", "drc", "dcause", "dfix", "dprob", "dvuln", "prob", "claim", "err"];
|
|
14871
|
+
var TAG_HANDLE_SOURCE = String.raw`\[([a-z0-9][\w-]{0,80})\]|((?:${TAG_CANONICAL_ID_PREFIXES.join("|")})_[0-9a-f]{6,})`;
|
|
14872
|
+
var TAG_THREAD_SOURCE = String.raw`#([a-z][\w-]{0,63})`;
|
|
14873
|
+
var TAG_PLACEHOLDER_HANDLES = /* @__PURE__ */ new Set([
|
|
14874
|
+
"handle",
|
|
14875
|
+
"its-handle",
|
|
14876
|
+
"another",
|
|
14877
|
+
"a-third",
|
|
14878
|
+
"h",
|
|
14879
|
+
"h1",
|
|
14880
|
+
"h2",
|
|
14881
|
+
"id",
|
|
14882
|
+
"its-id",
|
|
14883
|
+
"a",
|
|
14884
|
+
"b",
|
|
14885
|
+
"\u2026",
|
|
14886
|
+
"..."
|
|
14887
|
+
]);
|
|
14888
|
+
var TAG_EXAMPLES = {
|
|
14889
|
+
prior: (ref) => `([${ref}])`,
|
|
14890
|
+
problem: () => `[!one line on what's wrong]`,
|
|
14891
|
+
vulnerability: () => `[!vuln: who can do what; observed basis @ path:line]`,
|
|
14892
|
+
suspectedVulnerability: () => `[?vuln: consequence; missing check]`,
|
|
14893
|
+
vulnerabilityThread: () => `[!#v vuln: finding here]`,
|
|
14894
|
+
fix: (ref) => `(fix:[${ref}])`,
|
|
14895
|
+
// CONSTRAINT — the native capture path for a DIFFUSE problem (a constraint/tension/
|
|
14896
|
+
// conflict that shaped a decision). Free text, attaches to the decision sentence the
|
|
14897
|
+
// agent is already writing. Full rationale in the constraint note below + prior-tags.ts.
|
|
14898
|
+
constraint: () => `(constraint: the limitation that forced it)`,
|
|
14899
|
+
// TRIAGE — routes the problem/symptom in scope to its cause. `(cause:[handle])`
|
|
14900
|
+
// links to a cited RootCause/Solution node; `(cause: free text)` mints a RootCause
|
|
14901
|
+
// from the text. Harvested in prior-tags.ts → engine.ts (see the triage note below).
|
|
14902
|
+
triage: (ref) => `(cause:[${ref}])`,
|
|
14903
|
+
// Unlike a bare cause citation, this handle names the Problem being explained.
|
|
14904
|
+
triageExplain: (ref) => `(cause:[${ref}] the mechanism)`,
|
|
14905
|
+
// The MINT form — free-text cause that mints a RootCause. Prime-time surfacing
|
|
14906
|
+
// (recallForFile) pastes this next to an open Problem that has no cause yet, so the
|
|
14907
|
+
// diagnostic moment is cued when the agent is looking at the problem. Kept here so
|
|
14908
|
+
// the grammar can't drift from parseInlineTags' `(cause: …)` acceptance.
|
|
14909
|
+
triageMint: () => `(cause: why it happens)`,
|
|
14910
|
+
// THREAD forms — the witness-declared symptom↔cause/fix binding. A `#id` on the
|
|
14911
|
+
// flag names the diagnosis thread; the same `#id` on a later verb binds to it
|
|
14912
|
+
// explicitly (across sentences AND turns), replacing proximity guessing. Adjacent
|
|
14913
|
+
// tags never need one — the id is for diagnoses that unfold over distance.
|
|
14914
|
+
problemThread: () => `[!#slug one line on what's wrong]`,
|
|
14915
|
+
causeThread: () => `(cause:#slug the mechanism)`,
|
|
14916
|
+
fixThread: (thread = "slug") => `(fix:#${thread} what you changed)`,
|
|
14917
|
+
// ATTEMPT arc — what's being tried and what didn't work. A failed attempt is a
|
|
14918
|
+
// witnessed NEGATIVE result: it saves the next agent the same dead end.
|
|
14919
|
+
tried: () => `(tried: the approach you're attempting)`,
|
|
14920
|
+
failed: () => `(failed: what didn't work and the dead end it rules out)`,
|
|
14921
|
+
// `(failed:[h] …)` on a shown SOLUTION — the refute-by-failure form the context render teaches.
|
|
14922
|
+
failedCite: (ref) => `(failed:[${ref}] what came back)`,
|
|
14923
|
+
// LINK verbs (AC epic) — bind the problem in scope to CANONICAL priors and
|
|
14924
|
+
// abstractions. Multi-target by design: single links are weak; ~3 links to 3
|
|
14925
|
+
// DIFFERENT relevant priors is the target shape (see linkBullet).
|
|
14926
|
+
instance: (ref) => `(instance:[${ref}],[another],[a-third])`,
|
|
14927
|
+
pattern: () => `(pattern: name the general shape)`,
|
|
14928
|
+
// DOMAIN — the abstract area a problem is about; the concept layer an
|
|
14929
|
+
// anchor-less problem clusters on. Mints/resolves a Domain by name.
|
|
14930
|
+
domain: () => `(domain: The Area)`,
|
|
14931
|
+
// PACKAGE/COMPONENT — agent-named public anchors (OM-agent-anchors): the
|
|
14932
|
+
// package a problem is ABOUT (even when not a dependency) and the
|
|
14933
|
+
// framework/product-level unit it concerns. Public spine anchors → the
|
|
14934
|
+
// knowledge can cross to the collective.
|
|
14935
|
+
package: () => `(package: the-package-name)`,
|
|
14936
|
+
component: () => `(component: The Component)`,
|
|
14937
|
+
aids: (ref) => `(aids:[${ref}])`,
|
|
14938
|
+
// SOLUTION-STRUCTURE (SS-structure) — taught in the link bullet; Solution→Solution.
|
|
14939
|
+
buildsOn: (ref) => `(builds-on:[${ref}])`,
|
|
14940
|
+
supersedes: (ref) => `(supersedes:[${ref}])`,
|
|
14941
|
+
alternative: (ref) => `(alternative:[${ref}])`,
|
|
14942
|
+
// VERDICT (VS-verdict-tags) — a judgment on a SHOWN prior, distinct from leaning on it.
|
|
14943
|
+
// `confirm` rides the corroborate channel (it held); `refute` the contradict channel
|
|
14944
|
+
// (it's wrong / didn't hold) and REQUIRES a reason. This is the inline producer the
|
|
14945
|
+
// evidence channels lacked — refute sat dead (contradictionPressure 0) until now, while
|
|
14946
|
+
// lean-on corroboration lived, because only corroboration had an inline path. Grammar
|
|
14947
|
+
// single-sourced with parseInlineTags' CONFIRM_PREFIX/REFUTE_PREFIX.
|
|
14948
|
+
confirm: (ref) => `(confirm:[${ref}])`,
|
|
14949
|
+
refute: (ref) => `(refute:[${ref}] one line why)`,
|
|
14950
|
+
// RETRACT — "not a real problem": the errata-resolved block with a disposition
|
|
14951
|
+
// (parseDesignResolutions, turn.ts). No inline form exists; the block is the grammar.
|
|
14952
|
+
retract: (id) => ["```errata-resolved", `id: ${id}`, "disposition: false_positive", "reason: why it was never a real problem", "```"].join("\n")
|
|
14953
|
+
};
|
|
14954
|
+
var P = "Problem";
|
|
14955
|
+
var S = "Solution";
|
|
14956
|
+
var RC = "RootCause";
|
|
14957
|
+
var CITE_LOWERING = [
|
|
14958
|
+
{ target: RC, writes: [{ from: P, edge: "CAUSED_BY", to: RC }] },
|
|
14959
|
+
{ target: S, writes: [{ from: P, edge: "SOLVED_BY", to: S }] },
|
|
14960
|
+
{ target: "Pattern", writes: [{ from: P, edge: "INSTANCE_OF", to: "Pattern" }, { from: "Pattern", edge: "ANCHORED_AT", to: "File" }], note: "cite-anchor promotion after 2 sessions", decision: "d", knownViolation: { edge: "ANCHORED_AT", finding: "motif cite-anchor promotion writes a refused ANCHORED_AT" } },
|
|
14961
|
+
{ target: "Package", writes: [{ from: P, edge: "DEPENDS_ON", to: "Package" }] },
|
|
14962
|
+
{ target: "Language", writes: [{ from: P, edge: "OCCURS_IN", to: "Language" }] },
|
|
14963
|
+
{ target: "Tool", writes: [{ from: P, edge: "CONCERNS", to: "Tool" }] },
|
|
14964
|
+
{ target: "Component", writes: [{ from: P, edge: "CONCERNS", to: "Component" }] },
|
|
14965
|
+
{ target: P, writes: [{ from: P, edge: "RELATES_TO", to: P }, { from: P, edge: "ANCHORED_AT", to: "File" }], note: "corroboration witness + cite-anchor promotion" },
|
|
14966
|
+
{ target: "AntiPattern", writes: [{ from: P, edge: "RELATES_TO", to: "AntiPattern" }, { from: "AntiPattern", edge: "ANCHORED_AT", to: "File" }], decision: "e", knownViolation: { edge: "ANCHORED_AT", finding: "motif cite-anchor promotion writes a refused ANCHORED_AT" } },
|
|
14967
|
+
{ target: "Technique", writes: [{ from: P, edge: "RELATES_TO", to: "Technique" }, { from: "Technique", edge: "ANCHORED_AT", to: "File" }], decision: "e", knownViolation: { edge: "ANCHORED_AT", finding: "motif cite-anchor promotion writes a refused ANCHORED_AT" } },
|
|
14968
|
+
{ target: "Weakness", writes: [{ from: P, edge: "RELATES_TO", to: "Weakness" }], decision: "e" },
|
|
14969
|
+
{ target: "Domain", writes: [{ from: P, edge: "RELATES_TO", to: "Domain" }], decision: "e" },
|
|
14970
|
+
{ target: "Claim", writes: [{ from: P, edge: "RELATES_TO", to: "Claim" }, { from: "Claim", edge: "ANCHORED_AT", to: "File" }] }
|
|
14971
|
+
];
|
|
14972
|
+
var INSTANCE_LOWERING = [
|
|
14973
|
+
{ target: "Pattern", writes: [{ from: P, edge: "INSTANCE_OF", to: "Pattern" }] },
|
|
14974
|
+
{ target: P, writes: [{ from: P, edge: "RELATES_TO", to: P }] },
|
|
14975
|
+
{ target: S, writes: [{ from: P, edge: "SOLVED_BY", to: S }], note: "an instance cite on a Solution claims the problem is solved by it", decision: "a" },
|
|
14976
|
+
{ target: RC, writes: [{ from: P, edge: "CAUSED_BY", to: RC }] },
|
|
14977
|
+
{ target: "AntiPattern", writes: [{ from: P, edge: "RELATES_TO", to: "AntiPattern" }], decision: "e" },
|
|
14978
|
+
{ target: "Technique", writes: [{ from: P, edge: "RELATES_TO", to: "Technique" }], decision: "e" },
|
|
14979
|
+
{ target: "Weakness", writes: [{ from: P, edge: "RELATES_TO", to: "Weakness" }], decision: "e" },
|
|
14980
|
+
{ target: "Domain", writes: [{ from: P, edge: "RELATES_TO", to: "Domain" }], decision: "e" },
|
|
14981
|
+
{ target: "Package", writes: [{ from: P, edge: "DEPENDS_ON", to: "Package" }] },
|
|
14982
|
+
{ target: "Component", writes: [{ from: P, edge: "CONCERNS", to: "Component" }] }
|
|
14983
|
+
];
|
|
14984
|
+
var TRIAGE_TO_RC = [
|
|
14985
|
+
{ from: P, edge: "TRIAGED_BY", to: "Triage" },
|
|
14986
|
+
{ from: "Triage", edge: "INDICATES", to: RC },
|
|
14987
|
+
{ from: P, edge: "CAUSED_BY", to: RC }
|
|
14988
|
+
];
|
|
14989
|
+
var TAG_VERBS = [
|
|
14990
|
+
{
|
|
14991
|
+
verb: "cite",
|
|
14992
|
+
form: "citation",
|
|
14993
|
+
opener: "(",
|
|
14994
|
+
kinds: ["prior"],
|
|
14995
|
+
args: "handles",
|
|
14996
|
+
thread: false,
|
|
14997
|
+
handleRequired: true,
|
|
14998
|
+
lowering: CITE_LOWERING,
|
|
14999
|
+
examples: ["prior"],
|
|
15000
|
+
hint: "cite a shown prior you leaned on: ([its-handle]) \u2014 handles only, no prose inside the parens",
|
|
15001
|
+
taught: "taught"
|
|
15002
|
+
},
|
|
15003
|
+
{
|
|
15004
|
+
verb: "!",
|
|
15005
|
+
form: "flag",
|
|
15006
|
+
opener: "[!",
|
|
15007
|
+
kinds: ["problem", "triage", "vulnerability", "fix", "prior"],
|
|
15008
|
+
args: "flag-body",
|
|
15009
|
+
thread: true,
|
|
15010
|
+
minText: 8,
|
|
15011
|
+
qualifiers: [
|
|
15012
|
+
{ name: "vuln", taught: true, note: "[!vuln: \u2026] \u2192 Vulnerability (asserted)" },
|
|
15013
|
+
{ name: "fixed", taught: false, note: "[!fixed: X] \u2192 read as a self-found fix" },
|
|
15014
|
+
{ name: "resolved", taught: false, note: "[!resolved: X] \u2192 read as a self-found fix" }
|
|
15015
|
+
],
|
|
15016
|
+
lowering: [
|
|
15017
|
+
{ target: P, writes: [], note: "mints a Problem; `@ path[:line]` anchors it (ANCHORED_AT to code)" },
|
|
15018
|
+
{ target: RC, writes: [...TRIAGE_TO_RC, { from: RC, edge: "CAUSED_BY", to: RC }], note: "`<-` segments: first \u2192 triage route, later \u2192 RootCause chain" },
|
|
15019
|
+
{ target: "Vulnerability", writes: [], note: "[!vuln: \u2026]; `<-` causes stay on the finding (vulnerabilityCauses)" }
|
|
15020
|
+
],
|
|
15021
|
+
examples: ["problem", "problemThread", "vulnerability", "vulnerabilityThread"],
|
|
15022
|
+
hint: "flag a problem on one line: [!what's wrong @ path:42]; fold causes with <- ; a fix goes in (fix: \u2026), not [!fixed: \u2026]",
|
|
15023
|
+
taught: "taught"
|
|
15024
|
+
},
|
|
15025
|
+
{
|
|
15026
|
+
verb: "?",
|
|
15027
|
+
form: "flag",
|
|
15028
|
+
opener: "[?",
|
|
15029
|
+
kinds: ["todo", "vulnerability"],
|
|
15030
|
+
args: "flag-body",
|
|
15031
|
+
thread: true,
|
|
15032
|
+
minText: 8,
|
|
15033
|
+
qualifiers: [{ name: "vuln", taught: true, note: "[?vuln: \u2026] \u2192 Vulnerability (suspected)" }],
|
|
15034
|
+
lowering: [{ target: P, writes: [], note: "mints a todo Problem; a fix can close it today", decision: "g" }],
|
|
15035
|
+
examples: ["suspectedVulnerability"],
|
|
15036
|
+
hint: "[?\u2026] marks an open question / todo; [?vuln: consequence; missing check] a suspected vulnerability",
|
|
15037
|
+
taught: "untaught",
|
|
15038
|
+
taughtBy: "only the [?vuln:] form is taught; the bare [?todo] form is not",
|
|
15039
|
+
decisions: ["g"]
|
|
15040
|
+
},
|
|
15041
|
+
{
|
|
15042
|
+
verb: "fix",
|
|
15043
|
+
form: "verb",
|
|
15044
|
+
opener: "(fix:",
|
|
15045
|
+
kinds: ["fix"],
|
|
15046
|
+
args: "handles-text",
|
|
15047
|
+
thread: true,
|
|
15048
|
+
minText: 12,
|
|
15049
|
+
lowering: [
|
|
15050
|
+
{ target: P, writes: [{ from: P, edge: "SOLVED_BY", to: S }], note: "closes the Problem; a note \u226512 chars mints the Solution" },
|
|
15051
|
+
{ target: "Vulnerability", writes: [{ from: "Vulnerability", edge: "SOLVED_BY", to: S }] },
|
|
15052
|
+
{ target: RC, writes: [], note: "dropped: 'fix tag went nowhere' (not a Problem); no tag writes FIXED_BY", decision: "f" }
|
|
15053
|
+
],
|
|
15054
|
+
examples: ["fix", "fixThread"],
|
|
15055
|
+
hint: "one fix per tag: (fix:[its-handle]) or (fix:#thread what you changed) \u2014 put the cause in its own (cause: \u2026)",
|
|
15056
|
+
taught: "taught",
|
|
15057
|
+
decisions: ["f"]
|
|
15058
|
+
},
|
|
15059
|
+
{
|
|
15060
|
+
verb: "cause",
|
|
15061
|
+
form: "verb",
|
|
15062
|
+
opener: "(cause:",
|
|
15063
|
+
kinds: ["triage"],
|
|
15064
|
+
args: "handles-text",
|
|
15065
|
+
thread: true,
|
|
15066
|
+
minText: 8,
|
|
15067
|
+
lowering: [
|
|
15068
|
+
{ target: RC, writes: TRIAGE_TO_RC },
|
|
15069
|
+
{ target: P, writes: [{ from: P, edge: "TRIAGED_BY", to: "Triage" }, { from: "Triage", edge: "INDICATES", to: RC }, { from: P, edge: "CAUSED_BY", to: RC }], note: "(cause:[problem] mechanism) \u2014 the cited Problem is the symptom; the mechanism mints the RootCause" },
|
|
15070
|
+
{ target: S, writes: [{ from: P, edge: "TRIAGED_BY", to: "Triage" }, { from: "Triage", edge: "INDICATES", to: S }], decision: "b", knownViolation: { edge: "INDICATES", finding: "(cause:[solution]) writes a refused INDICATES \u2192 Solution" } }
|
|
15071
|
+
],
|
|
15072
|
+
examples: ["triage", "triageExplain", "triageMint", "causeThread"],
|
|
15073
|
+
hint: "(cause: the mechanism you'd change) or (cause:[root-cause-handle]); bind a distant symptom with (cause:#thread \u2026)",
|
|
15074
|
+
taught: "taught",
|
|
15075
|
+
decisions: ["b"]
|
|
15076
|
+
},
|
|
15077
|
+
{
|
|
15078
|
+
verb: "constraint",
|
|
15079
|
+
form: "verb",
|
|
15080
|
+
opener: "(constraint:",
|
|
15081
|
+
kinds: ["constraint"],
|
|
15082
|
+
args: "text",
|
|
15083
|
+
thread: false,
|
|
15084
|
+
minText: 8,
|
|
15085
|
+
textRequired: true,
|
|
15086
|
+
lowering: [{ target: P, writes: [], note: "mints a constraint-kind Problem" }],
|
|
15087
|
+
examples: ["constraint"],
|
|
15088
|
+
hint: "(constraint: the limitation that forced the decision)",
|
|
15089
|
+
taught: "taught"
|
|
15090
|
+
},
|
|
15091
|
+
{
|
|
15092
|
+
verb: "tried",
|
|
15093
|
+
form: "verb",
|
|
15094
|
+
opener: "(tried:",
|
|
15095
|
+
kinds: ["attempt"],
|
|
15096
|
+
args: "text",
|
|
15097
|
+
thread: true,
|
|
15098
|
+
minText: 8,
|
|
15099
|
+
textRequired: true,
|
|
15100
|
+
lowering: [{ target: P, writes: [], note: "journals an attempt on the bound problem" }],
|
|
15101
|
+
examples: ["tried"],
|
|
15102
|
+
hint: "(tried: the approach) \u2014 bind with (tried:#thread \u2026)",
|
|
15103
|
+
taught: "taught"
|
|
15104
|
+
},
|
|
15105
|
+
{
|
|
15106
|
+
verb: "failed",
|
|
15107
|
+
form: "verb",
|
|
15108
|
+
opener: "(failed:",
|
|
15109
|
+
kinds: ["failure"],
|
|
15110
|
+
args: "handles-text",
|
|
15111
|
+
thread: true,
|
|
15112
|
+
minText: 8,
|
|
15113
|
+
textRequired: true,
|
|
15114
|
+
lowering: [
|
|
15115
|
+
{ target: P, writes: [], note: "journals a failed attempt on the bound problem" },
|
|
15116
|
+
{ target: S, writes: [], note: "(failed:[h]) refutes the cited prior (contradictionPressure); skips the label gate today", decision: "j" }
|
|
15117
|
+
],
|
|
15118
|
+
examples: ["failed", "failedCite"],
|
|
15119
|
+
hint: "(failed: what didn't work); (failed:[solution] what came back) refutes a shown fix",
|
|
15120
|
+
taught: "taught",
|
|
15121
|
+
decisions: ["j"]
|
|
15122
|
+
},
|
|
15123
|
+
{
|
|
15124
|
+
verb: "instance",
|
|
15125
|
+
form: "verb",
|
|
15126
|
+
opener: "(instance:",
|
|
15127
|
+
kinds: ["instance"],
|
|
15128
|
+
args: "handles",
|
|
15129
|
+
thread: false,
|
|
15130
|
+
handleRequired: true,
|
|
15131
|
+
lowering: INSTANCE_LOWERING,
|
|
15132
|
+
examples: ["instance"],
|
|
15133
|
+
hint: "(instance:[h1],[h2],[h3]) \u2014 ~3 DIFFERENT shown priors this problem instantiates; handles only",
|
|
15134
|
+
taught: "taught",
|
|
15135
|
+
decisions: ["a", "e"]
|
|
15136
|
+
},
|
|
15137
|
+
{
|
|
15138
|
+
verb: "pattern",
|
|
15139
|
+
form: "verb",
|
|
15140
|
+
opener: "(pattern:",
|
|
15141
|
+
kinds: ["pattern"],
|
|
15142
|
+
args: "handle-or-text",
|
|
15143
|
+
thread: false,
|
|
15144
|
+
minText: 3,
|
|
15145
|
+
lowering: [{ target: "Pattern", writes: [{ from: P, edge: "INSTANCE_OF", to: "Pattern" }], note: "a handle must be a Pattern; free text resolves-or-mints one" }],
|
|
15146
|
+
examples: ["pattern"],
|
|
15147
|
+
hint: "(pattern: name the general shape) or (pattern:[shown-pattern])",
|
|
15148
|
+
taught: "taught"
|
|
15149
|
+
},
|
|
15150
|
+
{
|
|
15151
|
+
verb: "domain",
|
|
15152
|
+
form: "verb",
|
|
15153
|
+
opener: "(domain:",
|
|
15154
|
+
kinds: ["domain"],
|
|
15155
|
+
args: "text",
|
|
15156
|
+
thread: false,
|
|
15157
|
+
minText: 2,
|
|
15158
|
+
textRequired: true,
|
|
15159
|
+
lowering: [{ target: "Domain", writes: [{ from: P, edge: "PERTAIN_TO", to: "Domain" }], note: '(domain:#t X) mints a Domain named "#t X" today', decision: "k" }],
|
|
15160
|
+
examples: ["domain"],
|
|
15161
|
+
hint: "(domain: The Area) \u2014 Title Case, no thread",
|
|
15162
|
+
taught: "taught",
|
|
15163
|
+
decisions: ["k"]
|
|
15164
|
+
},
|
|
15165
|
+
{
|
|
15166
|
+
verb: "package",
|
|
15167
|
+
form: "verb",
|
|
15168
|
+
opener: "(package:",
|
|
15169
|
+
kinds: ["package"],
|
|
15170
|
+
args: "text",
|
|
15171
|
+
thread: false,
|
|
15172
|
+
minText: 2,
|
|
15173
|
+
textRequired: true,
|
|
15174
|
+
lowering: [{ target: "Package", writes: [{ from: P, edge: "DEPENDS_ON", to: "Package" }], note: "means 'concerns', written as DEPENDS_ON", decision: "c" }],
|
|
15175
|
+
examples: ["package"],
|
|
15176
|
+
hint: "(package: the-package-name) \u2014 the public package it's about",
|
|
15177
|
+
taught: "taught",
|
|
15178
|
+
decisions: ["c"]
|
|
15179
|
+
},
|
|
15180
|
+
{
|
|
15181
|
+
verb: "component",
|
|
15182
|
+
form: "verb",
|
|
15183
|
+
opener: "(component:",
|
|
15184
|
+
kinds: ["component"],
|
|
15185
|
+
args: "text",
|
|
15186
|
+
thread: false,
|
|
15187
|
+
minText: 2,
|
|
15188
|
+
textRequired: true,
|
|
15189
|
+
lowering: [{ target: "Component", writes: [{ from: P, edge: "CONCERNS", to: "Component" }] }],
|
|
15190
|
+
examples: ["component"],
|
|
15191
|
+
hint: "(component: The Component) \u2014 the framework/product unit",
|
|
15192
|
+
taught: "taught"
|
|
15193
|
+
},
|
|
15194
|
+
{
|
|
15195
|
+
verb: "aids",
|
|
15196
|
+
form: "verb",
|
|
15197
|
+
opener: "(aids:",
|
|
15198
|
+
kinds: ["transfer"],
|
|
15199
|
+
args: "handles",
|
|
15200
|
+
thread: true,
|
|
15201
|
+
handleRequired: true,
|
|
15202
|
+
lowering: [{ target: P, writes: [{ from: S, edge: "MAY_RESOLVE", to: P }], note: "source = the Solution in scope; non-Problem targets are skipped" }],
|
|
15203
|
+
examples: ["aids"],
|
|
15204
|
+
hint: "(aids:[problem-handle],\u2026) \u2014 other shown problems your fix might also resolve",
|
|
15205
|
+
taught: "taught"
|
|
15206
|
+
},
|
|
15207
|
+
{
|
|
15208
|
+
verb: "builds-on",
|
|
15209
|
+
form: "verb",
|
|
15210
|
+
opener: "(builds-on:",
|
|
15211
|
+
kinds: ["builds-on"],
|
|
15212
|
+
args: "handles",
|
|
15213
|
+
thread: true,
|
|
15214
|
+
handleRequired: true,
|
|
15215
|
+
lowering: [{ target: S, writes: [{ from: S, edge: "BUILDS_ON", to: S }] }],
|
|
15216
|
+
examples: ["buildsOn"],
|
|
15217
|
+
hint: "(builds-on:[shown-solution]) \u2014 your fix layers on it",
|
|
15218
|
+
taught: "taught",
|
|
15219
|
+
taughtBy: "linkBullet instruction text"
|
|
15220
|
+
},
|
|
15221
|
+
{
|
|
15222
|
+
verb: "supersedes",
|
|
15223
|
+
form: "verb",
|
|
15224
|
+
opener: "(supersedes:",
|
|
15225
|
+
kinds: ["supersedes"],
|
|
15226
|
+
args: "handles",
|
|
15227
|
+
thread: true,
|
|
15228
|
+
handleRequired: true,
|
|
15229
|
+
lowering: [{ target: S, writes: [{ from: S, edge: "SUPERSEDES", to: S }] }],
|
|
15230
|
+
examples: ["supersedes"],
|
|
15231
|
+
hint: "(supersedes:[shown-solution]) \u2014 your fix replaces it",
|
|
15232
|
+
taught: "taught",
|
|
15233
|
+
taughtBy: "linkBullet instruction text"
|
|
15234
|
+
},
|
|
15235
|
+
{
|
|
15236
|
+
verb: "alternative",
|
|
15237
|
+
form: "verb",
|
|
15238
|
+
opener: "(alternative:",
|
|
15239
|
+
kinds: ["alternative"],
|
|
15240
|
+
args: "handles",
|
|
15241
|
+
thread: true,
|
|
15242
|
+
handleRequired: true,
|
|
15243
|
+
lowering: [{ target: S, writes: [{ from: S, edge: "ALTERNATIVE_TO", to: S }] }],
|
|
15244
|
+
examples: ["alternative"],
|
|
15245
|
+
hint: "(alternative:[shown-solution]) \u2014 a different valid approach",
|
|
15246
|
+
taught: "taught",
|
|
15247
|
+
taughtBy: "linkBullet instruction text"
|
|
15248
|
+
},
|
|
15249
|
+
{
|
|
15250
|
+
verb: "confirm",
|
|
15251
|
+
form: "verb",
|
|
15252
|
+
opener: "(confirm:",
|
|
15253
|
+
kinds: ["confirm"],
|
|
15254
|
+
args: "handles",
|
|
15255
|
+
thread: false,
|
|
15256
|
+
handleRequired: true,
|
|
15257
|
+
lowering: [{ target: S, writes: [], note: "corroboration witness (corroborationCount), no edge" }],
|
|
15258
|
+
examples: ["confirm"],
|
|
15259
|
+
hint: "(confirm:[shown-prior]) \u2014 it held",
|
|
15260
|
+
taught: "taught",
|
|
15261
|
+
taughtBy: "verdictBullet + context render",
|
|
15262
|
+
decisions: ["j"]
|
|
15263
|
+
},
|
|
15264
|
+
{
|
|
15265
|
+
verb: "refute",
|
|
15266
|
+
form: "verb",
|
|
15267
|
+
opener: "(refute:",
|
|
15268
|
+
kinds: ["refute"],
|
|
15269
|
+
args: "handles-text",
|
|
15270
|
+
thread: false,
|
|
15271
|
+
minText: 4,
|
|
15272
|
+
handleRequired: true,
|
|
15273
|
+
textRequired: true,
|
|
15274
|
+
lowering: [{ target: S, writes: [], note: "contradiction witness (contradictionPressure), no edge; reason stays local" }],
|
|
15275
|
+
examples: ["refute"],
|
|
15276
|
+
hint: "(refute:[shown-prior] one line why) \u2014 the reason is required",
|
|
15277
|
+
taught: "taught",
|
|
15278
|
+
taughtBy: "verdictBullet + context render",
|
|
15279
|
+
decisions: ["j"]
|
|
15280
|
+
}
|
|
15281
|
+
];
|
|
15282
|
+
var TAG_FENCES = [
|
|
15283
|
+
{
|
|
15284
|
+
fence: "errata-problem",
|
|
15285
|
+
fields: ["problem", "cause", "fix", "anchor", "kind"],
|
|
15286
|
+
required: [["problem"]],
|
|
15287
|
+
taught: "untaught",
|
|
15288
|
+
parser: "apps/daemon/src/turn.ts parseDesignFlags",
|
|
15289
|
+
decisions: ["h"],
|
|
15290
|
+
hint: "use an inline [!\u2026] flag instead (errata-problem cause ids never meet inline ids)"
|
|
15291
|
+
},
|
|
15292
|
+
{
|
|
15293
|
+
fence: "errata-resolved",
|
|
15294
|
+
fields: ["id", "problem", "fix", "disposition", "reason"],
|
|
15295
|
+
required: [["id", "problem"]],
|
|
15296
|
+
taught: "taught",
|
|
15297
|
+
taughtBy: "retract form only (TAG_EXAMPLE.retract); the fix form is untaught",
|
|
15298
|
+
parser: "apps/daemon/src/turn.ts parseDesignResolutions",
|
|
15299
|
+
hint: "```errata-resolved with id: and disposition: false_positive | invalid, reason: \u2026"
|
|
15300
|
+
},
|
|
15301
|
+
{
|
|
15302
|
+
fence: "errata-triage",
|
|
15303
|
+
fields: ["presenting", "cause", "test", "fix", "drift", "route"],
|
|
15304
|
+
required: [["presenting", "route"], ["cause", "test"]],
|
|
15305
|
+
taught: "taught",
|
|
15306
|
+
taughtBy: "MCP errata_triage tool copy",
|
|
15307
|
+
parser: "packages/local-graph/src/triage.ts parseTriageFences",
|
|
15308
|
+
hint: "```errata-triage with presenting: and cause: (or route: + test: to answer one)"
|
|
15309
|
+
},
|
|
15310
|
+
{
|
|
15311
|
+
fence: "errata-abstraction",
|
|
15312
|
+
fields: ["candidate", "principle", "covers"],
|
|
15313
|
+
required: [["candidate"], ["principle"], ["covers"]],
|
|
15314
|
+
taught: "taught",
|
|
15315
|
+
taughtBy: "MCP errata_pending_abstractions tool copy",
|
|
15316
|
+
parser: "packages/local-graph/src/abstraction.ts parseAbstractionFences",
|
|
15317
|
+
hint: "```errata-abstraction with candidate:, principle:, covers: (all required)"
|
|
15318
|
+
},
|
|
15319
|
+
{
|
|
15320
|
+
fence: "errata-intent",
|
|
15321
|
+
fields: ["intent"],
|
|
15322
|
+
required: [["intent"]],
|
|
15323
|
+
taught: "taught",
|
|
15324
|
+
parser: "packages/local-graph/src/intent.ts parseIntentFences",
|
|
15325
|
+
hint: "```errata-intent with intent: what you're about to do"
|
|
15326
|
+
},
|
|
15327
|
+
{
|
|
15328
|
+
fence: "errata-share",
|
|
15329
|
+
fields: ["priors"],
|
|
15330
|
+
required: [["priors"]],
|
|
15331
|
+
taught: "taught",
|
|
15332
|
+
taughtBy: "subagent hand-off copy",
|
|
15333
|
+
parser: "packages/local-graph/src/intent.ts parseShareFence",
|
|
15334
|
+
hint: "```errata-share with priors: all | [h1], [h2]"
|
|
15335
|
+
}
|
|
15336
|
+
];
|
|
15337
|
+
var TAG_VERB_BY_NAME = new Map(
|
|
15338
|
+
TAG_VERBS.filter((v) => v.form === "verb").map((v) => [v.verb, v])
|
|
15339
|
+
);
|
|
15340
|
+
var TAG_PAREN_VERBS = [...TAG_VERB_BY_NAME.keys()].sort((a, b) => b.length - a.length);
|
|
15341
|
+
var TAG_GRAMMAR_OPENERS = [
|
|
15342
|
+
...new Set(TAG_VERBS.filter((v) => v.form !== "citation").map((v) => v.opener))
|
|
15343
|
+
];
|
|
15344
|
+
var TAG_QUOTED_PAREN_SOURCE = String.raw`\(\s*(?:${TAG_PAREN_VERBS.map((v) => v.replace(/-/g, "\\-")).join("|")})\s*:|\(\[`;
|
|
15345
|
+
var TAG_QUOTED_RE = new RegExp(String.raw`\[[!?]|` + TAG_QUOTED_PAREN_SOURCE, "i");
|
|
15346
|
+
var TAG_QUOTED_PAREN_RE = new RegExp(TAG_QUOTED_PAREN_SOURCE, "i");
|
|
15347
|
+
function tagGrammarCopy() {
|
|
15348
|
+
const copy = [];
|
|
15349
|
+
for (const ref of ["its-handle", "its-id", "h", "id"]) {
|
|
15350
|
+
for (const build of Object.values(TAG_EXAMPLES)) copy.push(build(ref));
|
|
15351
|
+
}
|
|
15352
|
+
for (const v of TAG_VERBS) {
|
|
15353
|
+
copy.push(v.hint);
|
|
15354
|
+
if (v.taughtBy) copy.push(v.taughtBy);
|
|
15355
|
+
for (const q of v.qualifiers ?? []) copy.push(q.note);
|
|
15356
|
+
for (const l of v.lowering) {
|
|
15357
|
+
if (l.note) copy.push(l.note);
|
|
15358
|
+
if (l.knownViolation) copy.push(l.knownViolation.finding);
|
|
15359
|
+
}
|
|
15360
|
+
}
|
|
15361
|
+
for (const f of TAG_FENCES) copy.push(f.hint);
|
|
15362
|
+
return copy;
|
|
15363
|
+
}
|
|
15364
|
+
var TEMPLATE_VERB_GROUP = new RegExp(
|
|
15365
|
+
String.raw`\(\s*(${TAG_PAREN_VERBS.map((v) => v.replace(/-/g, "\\-")).join("|")})\s*:((?:[^()\n]|\([^()\n]*\))*)\)`,
|
|
15366
|
+
"gi"
|
|
15367
|
+
);
|
|
15368
|
+
var BRACKET_TOKEN = /\[([^\]\n]{1,80})\]/g;
|
|
15369
|
+
var REAL_HANDLE_SHAPE = new RegExp(
|
|
15370
|
+
String.raw`^(?:[a-z0-9]+(?:-[a-z0-9]+)*-(?=[0-9a-f]{0,3}\d)[0-9a-f]{4}|(?:${TAG_CANONICAL_ID_PREFIXES.join("|")})_[0-9a-f]{6,})$`,
|
|
15371
|
+
"i"
|
|
15372
|
+
);
|
|
15373
|
+
function normalizeTemplateBody(body) {
|
|
15374
|
+
return body.replace(/…|\.{3}/g, " ").replace(/\s+/g, " ").trim().replace(/^[\s,;:—–-]+|[\s,;:.—–-]+$/g, "").toLowerCase();
|
|
15375
|
+
}
|
|
15376
|
+
function deriveTagTemplates(copy) {
|
|
15377
|
+
const bodies = /* @__PURE__ */ new Set();
|
|
15378
|
+
const handles = new Set([...TAG_PLACEHOLDER_HANDLES].map((h) => h.toLowerCase()));
|
|
15379
|
+
for (const text of copy) {
|
|
15380
|
+
for (const m of text.matchAll(BRACKET_TOKEN)) {
|
|
15381
|
+
const token = m[1].trim().toLowerCase();
|
|
15382
|
+
if (!/^[!?]/.test(token) && !REAL_HANDLE_SHAPE.test(token)) handles.add(token);
|
|
15383
|
+
}
|
|
15384
|
+
}
|
|
15385
|
+
for (const text of copy) {
|
|
15386
|
+
for (const m of text.matchAll(TEMPLATE_VERB_GROUP)) {
|
|
15387
|
+
const body = normalizeTemplateBody(m[2]);
|
|
15388
|
+
bodies.add(body);
|
|
15389
|
+
const unthreaded = normalizeTemplateBody(body.replace(/^#[a-z][\w-]*/i, ""));
|
|
15390
|
+
bodies.add(unthreaded);
|
|
15391
|
+
const rest = normalizeTemplateBody(unthreaded.replace(BRACKET_TOKEN, (t, h) => handles.has(h.trim().toLowerCase()) ? " " : t).replace(/,/g, " "));
|
|
15392
|
+
bodies.add(rest);
|
|
15393
|
+
}
|
|
15394
|
+
}
|
|
15395
|
+
bodies.delete("");
|
|
15396
|
+
return { bodies, handles };
|
|
15397
|
+
}
|
|
15398
|
+
var TAG_TEMPLATES = deriveTagTemplates(tagGrammarCopy());
|
|
15399
|
+
var VERB_WORDS_SOURCE = TAG_PAREN_VERBS.map((v) => v.replace(/-/g, "\\-")).join("|");
|
|
15400
|
+
var JOINED_VERB = new RegExp(String.raw`[,;]\s*(?=(?:${VERB_WORDS_SOURCE})\s*:)`, "i");
|
|
15401
|
+
var LEADING_VERB = new RegExp(String.raw`^\s*(${VERB_WORDS_SOURCE})\s*:`, "i");
|
|
15402
|
+
var ANY_VERB_WORD = new RegExp(String.raw`\b(?:${VERB_WORDS_SOURCE})\s*:`, "gi");
|
|
15403
|
+
var QUOTED_TAG_OPENER = new RegExp(String.raw`\(\s*(?:(${VERB_WORDS_SOURCE})\s*:|(?=\[))`, "gi");
|
|
15404
|
+
|
|
15405
|
+
// ../../packages/local-graph/src/wm-calibration.ts
|
|
15406
|
+
var WM_CALIBRATION_KEYS = [
|
|
15407
|
+
"shownCount",
|
|
15408
|
+
"evictedCount",
|
|
15409
|
+
"reinforceShownCount",
|
|
15410
|
+
"reinforceRecentCount",
|
|
15411
|
+
"reinforceEvictedCount",
|
|
15412
|
+
"reinforceUnshownCount",
|
|
15413
|
+
"reinforceUnshownQueryOnlyCount"
|
|
15414
|
+
];
|
|
15415
|
+
var WM_COUNTED_PREDICATE = WM_CALIBRATION_KEYS.map((k) => `json_type(attrs_json, '$.${k}') IS NOT NULL`).join(" OR ");
|
|
15416
|
+
var wmCounterSql = (k) => `CASE WHEN json_type(attrs_json, '$.${k}') IN ('integer', 'real') THEN json_extract(attrs_json, '$.${k}') END`;
|
|
15417
|
+
|
|
15418
|
+
// ../../packages/local-graph/src/exposure-attrs.ts
|
|
15419
|
+
var EXPOSURE_ATTRS = [
|
|
15420
|
+
"shownCount",
|
|
15421
|
+
"evictedCount",
|
|
15422
|
+
"firstShownAt",
|
|
15423
|
+
"firstEvictedAt",
|
|
15424
|
+
"firstShownAtSeq",
|
|
15425
|
+
"lastShownAtSeq",
|
|
15426
|
+
"lastEvictedAtSeq",
|
|
15427
|
+
"shownSinceTouch",
|
|
15428
|
+
"shownSinceTouchAt",
|
|
15429
|
+
"exposureLog",
|
|
15430
|
+
"exposureLogStart",
|
|
15431
|
+
"exposureLogSince",
|
|
15432
|
+
"exposureFirst",
|
|
15433
|
+
"exposureTrimmed"
|
|
15434
|
+
];
|
|
15435
|
+
var EXPOSURE_SCOPE = "exposure";
|
|
15436
|
+
var EXPOSURE_KEYS = new Set(EXPOSURE_ATTRS);
|
|
15437
|
+
var isExposureAttr = (key) => EXPOSURE_KEYS.has(key);
|
|
15438
|
+
function pickExposure(attrs) {
|
|
15439
|
+
const out = {};
|
|
15440
|
+
for (const k of EXPOSURE_ATTRS) if (attrs[k] !== void 0) out[k] = attrs[k];
|
|
15441
|
+
return out;
|
|
15442
|
+
}
|
|
15443
|
+
function exposureDocSql(key) {
|
|
15444
|
+
return `coalesce((SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id AND json_type(x.attrs_json, '$.${key}') IS NOT NULL), attrs_json)`;
|
|
15445
|
+
}
|
|
15446
|
+
|
|
14858
15447
|
// ../../packages/local-graph/src/history-cache-floor.ts
|
|
14859
15448
|
var HISTORY_CACHE_FLOOR_KEY = "history-cache-withdrawal-floor-3";
|
|
14860
15449
|
function installHistoryCacheFloor(db) {
|
|
@@ -14895,7 +15484,7 @@ function installHistoryCacheFloor(db) {
|
|
|
14895
15484
|
OR EXISTS(SELECT 1 FROM history_cache_withdrawals w WHERE w.id = nodes.id))
|
|
14896
15485
|
AND (embedding_blob IS NOT NULL OR embedding_json <> '[]' OR is_landmark <> 0
|
|
14897
15486
|
OR description <> ${description} OR attrs_json <> ${attrs});`;
|
|
14898
|
-
const internal = `key IN ('orgId','teamId','groupId','projectId','aliasTexts','aliasCanonicalIds')
|
|
15487
|
+
const internal = `key IN ('orgId','teamId','groupId','projectId','aliasTexts','aliasCanonicalIds','residueAudienceVerified')
|
|
14899
15488
|
OR key GLOB 'mergedFrom*' OR key GLOB '*WitnessKeys' OR key GLOB '*Sessions'
|
|
14900
15489
|
OR key GLOB 'authoringSession*' OR key GLOB 'contributor*' OR key GLOB 'witnessKeys*'`;
|
|
14901
15490
|
const stripMetadata = (scope) => `UPDATE nodes SET attrs_json=(
|
|
@@ -15029,7 +15618,9 @@ function installEditPlaceholderRetirement(db) {
|
|
|
15029
15618
|
}
|
|
15030
15619
|
|
|
15031
15620
|
// ../../packages/local-graph/src/cloud-attr-compaction.ts
|
|
15032
|
-
var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-
|
|
15621
|
+
var CLOUD_ATTR_COMPACTION_KEY = "cloud-attr-compaction-2";
|
|
15622
|
+
var V1_KEY = "cloud-attr-compaction-1";
|
|
15623
|
+
var STRUCT_LABELS = "('Problem', 'Solution', 'RootCause')";
|
|
15033
15624
|
var DUPES = CLOUD_ATTR_DUPLICATE_KEYS.map((k) => `'${k}'`).join(",");
|
|
15034
15625
|
function compacted(row) {
|
|
15035
15626
|
const packed = `json_extract(${row}.attrs_json, '$.attrsJson')`;
|
|
@@ -15048,14 +15639,17 @@ function dirty(row) {
|
|
|
15048
15639
|
return `json_extract(${row}.attrs_json, '$.source') = 'cloud' AND (${has})`;
|
|
15049
15640
|
}
|
|
15050
15641
|
function installCloudAttrCompaction(db) {
|
|
15051
|
-
|
|
15642
|
+
const marked = (key) => Boolean(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(key));
|
|
15643
|
+
if (marked(CLOUD_ATTR_COMPACTION_KEY)) return;
|
|
15644
|
+
const scope = marked(V1_KEY) ? ` AND label IN ${STRUCT_LABELS}` : "";
|
|
15052
15645
|
db.transaction(() => {
|
|
15053
15646
|
for (const operation of ["INSERT", "UPDATE"]) {
|
|
15054
|
-
db.exec(`
|
|
15647
|
+
db.exec(`DROP TRIGGER IF EXISTS cloud_attr_compact_${operation.toLowerCase()}`);
|
|
15648
|
+
db.exec(`CREATE TRIGGER cloud_attr_compact_${operation.toLowerCase()}
|
|
15055
15649
|
AFTER ${operation} ON nodes WHEN ${dirty("NEW")}
|
|
15056
15650
|
BEGIN UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE id = NEW.id; END;`);
|
|
15057
15651
|
}
|
|
15058
|
-
db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}`);
|
|
15652
|
+
db.exec(`UPDATE nodes SET attrs_json = ${compacted("nodes")} WHERE ${dirty("nodes")}${scope}`);
|
|
15059
15653
|
db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(CLOUD_ATTR_COMPACTION_KEY, "installed");
|
|
15060
15654
|
});
|
|
15061
15655
|
}
|
|
@@ -15093,6 +15687,313 @@ function installHistoryAttrSlim(db) {
|
|
|
15093
15687
|
db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(HISTORY_ATTR_SLIM_KEY, "done");
|
|
15094
15688
|
}
|
|
15095
15689
|
|
|
15690
|
+
// ../../packages/local-graph/src/text-index.ts
|
|
15691
|
+
var TEXT_INDEX_KEY = "text-index-trigram-2";
|
|
15692
|
+
var TEXT_INDEX_MIN_TERM = 3;
|
|
15693
|
+
function installTextIndex(db) {
|
|
15694
|
+
if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)) return;
|
|
15695
|
+
try {
|
|
15696
|
+
db.exec(`CREATE VIRTUAL TABLE temp.fts5_probe USING fts5(x, tokenize='trigram'); DROP TABLE temp.fts5_probe`);
|
|
15697
|
+
} catch {
|
|
15698
|
+
db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(TEXT_INDEX_KEY, "unavailable");
|
|
15699
|
+
return;
|
|
15700
|
+
}
|
|
15701
|
+
db.transaction(() => {
|
|
15702
|
+
db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS nodes_text
|
|
15703
|
+
USING fts5(description, content='nodes', content_rowid='rowid', tokenize='trigram')`);
|
|
15704
|
+
db.exec(`DROP TRIGGER IF EXISTS nodes_text_update_out; DROP TRIGGER IF EXISTS nodes_text_update_in;
|
|
15705
|
+
DROP TRIGGER IF EXISTS nodes_text_update`);
|
|
15706
|
+
db.exec(`CREATE TRIGGER IF NOT EXISTS nodes_text_insert AFTER INSERT ON nodes WHEN NEW.valid_to IS NULL
|
|
15707
|
+
BEGIN INSERT INTO nodes_text(rowid, description) VALUES (NEW.rowid, NEW.description); END;`);
|
|
15708
|
+
db.exec(`CREATE TRIGGER nodes_text_update AFTER UPDATE OF description, valid_to ON nodes
|
|
15709
|
+
WHEN OLD.description IS NOT NEW.description OR OLD.valid_to IS NOT NEW.valid_to
|
|
15710
|
+
BEGIN
|
|
15711
|
+
INSERT INTO nodes_text(nodes_text, rowid, description)
|
|
15712
|
+
SELECT 'delete', OLD.rowid, OLD.description WHERE OLD.valid_to IS NULL;
|
|
15713
|
+
INSERT INTO nodes_text(rowid, description)
|
|
15714
|
+
SELECT NEW.rowid, NEW.description WHERE NEW.valid_to IS NULL;
|
|
15715
|
+
END;`);
|
|
15716
|
+
db.exec(`CREATE TRIGGER IF NOT EXISTS nodes_text_delete AFTER DELETE ON nodes WHEN OLD.valid_to IS NULL
|
|
15717
|
+
BEGIN INSERT INTO nodes_text(nodes_text, rowid, description) VALUES ('delete', OLD.rowid, OLD.description); END;`);
|
|
15718
|
+
db.exec(`INSERT INTO nodes_text(nodes_text) VALUES ('delete-all')`);
|
|
15719
|
+
db.exec(`INSERT INTO nodes_text(rowid, description) SELECT rowid, description FROM nodes WHERE valid_to IS NULL`);
|
|
15720
|
+
db.prepare("INSERT OR REPLACE INTO store_meta(key,value) VALUES (?,?)").run("text-index-trigram-1", "installed");
|
|
15721
|
+
db.prepare("INSERT INTO store_meta(key,value) VALUES (?,?)").run(TEXT_INDEX_KEY, "installed");
|
|
15722
|
+
});
|
|
15723
|
+
}
|
|
15724
|
+
function textIndexReady(db) {
|
|
15725
|
+
return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(TEXT_INDEX_KEY)?.["value"] === "installed";
|
|
15726
|
+
}
|
|
15727
|
+
|
|
15728
|
+
// ../../packages/local-graph/src/change-feed.ts
|
|
15729
|
+
var CHANGE_FEED_KEY = "change-feed";
|
|
15730
|
+
var ANY_CHANGE = "*";
|
|
15731
|
+
var nodeScope = (label) => `node:${label}`;
|
|
15732
|
+
var CHANGE_LOG_KEEP = 25e4;
|
|
15733
|
+
var PRUNED_TO_KEY = "change-log-pruned-to";
|
|
15734
|
+
function installChangeFeed(db) {
|
|
15735
|
+
if (changeFeedInstalled(db)) return;
|
|
15736
|
+
db.transaction(() => {
|
|
15737
|
+
db.exec("CREATE TABLE IF NOT EXISTS change_seq (scope TEXT PRIMARY KEY, seq INTEGER NOT NULL) WITHOUT ROWID");
|
|
15738
|
+
db.exec(`CREATE TABLE IF NOT EXISTS change_log (change_seq INTEGER PRIMARY KEY, kind TEXT NOT NULL, id TEXT NOT NULL,
|
|
15739
|
+
label TEXT, from_id TEXT, to_id TEXT)`);
|
|
15740
|
+
for (const name of ["nodes", "edges"].flatMap((t) => ["insert", "update", "delete"].map((op) => `change_feed_${t}_${op}`))) {
|
|
15741
|
+
db.exec(`DROP TRIGGER IF EXISTS ${name}`);
|
|
15742
|
+
}
|
|
15743
|
+
const changed = (table) => columns(db, table).map((c) => `OLD."${c}" IS NOT NEW."${c}"`).join(" OR ");
|
|
15744
|
+
const strip = (row) => `json_remove(${row}.attrs_json, ${EXPOSURE_ATTRS.map((a) => `'$.${a}'`).join(", ")})`;
|
|
15745
|
+
const exposureOnly = `(${columns(db, "nodes").filter((c) => c !== "attrs_json").map((c) => `OLD."${c}" IS NEW."${c}"`).join(" AND ")}
|
|
15746
|
+
AND CASE WHEN json_valid(OLD.attrs_json) AND json_valid(NEW.attrs_json) THEN ${strip("OLD")} IS ${strip("NEW")} ELSE 0 END)`;
|
|
15747
|
+
const bump = (...scopes) => `INSERT INTO change_seq(scope, seq) VALUES ${scopes.map((s) => `(${s}, 1)`).join(", ")}
|
|
15748
|
+
ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;`;
|
|
15749
|
+
const seq = `(SELECT seq FROM change_seq WHERE scope = '*')`;
|
|
15750
|
+
const logNode = (row) => `INSERT INTO change_log(change_seq, kind, id, label) VALUES (${seq}, 'node', ${row}.id, ${row}.label);`;
|
|
15751
|
+
const logEdge = (row) => `INSERT INTO change_log(change_seq, kind, id, label, from_id, to_id) VALUES (${seq}, 'edge', ${row}.id, ${row}.type, ${row}.from_id, ${row}.to_id);`;
|
|
15752
|
+
const node = (row) => `'node:' || ${row}.label`;
|
|
15753
|
+
const edge = (row) => `'edge:' || ${row}.type`;
|
|
15754
|
+
db.exec(`CREATE TRIGGER change_feed_nodes_insert AFTER INSERT ON nodes BEGIN ${bump(`'*'`, node("NEW"))} ${logNode("NEW")} END;`);
|
|
15755
|
+
db.exec(`CREATE TRIGGER change_feed_nodes_update AFTER UPDATE ON nodes WHEN ${changed("nodes")} BEGIN
|
|
15756
|
+
${bump(`'*'`)}
|
|
15757
|
+
INSERT INTO change_seq(scope, seq) SELECT CASE WHEN ${exposureOnly} THEN '${EXPOSURE_SCOPE}' ELSE ${node("NEW")} END, 1 WHERE 1
|
|
15758
|
+
ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
|
|
15759
|
+
INSERT INTO change_seq(scope, seq) SELECT ${node("OLD")}, 1 WHERE OLD.label IS NOT NEW.label
|
|
15760
|
+
ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
|
|
15761
|
+
${logNode("NEW")}
|
|
15762
|
+
END;`);
|
|
15763
|
+
db.exec(`CREATE TRIGGER change_feed_nodes_delete AFTER DELETE ON nodes BEGIN ${bump(`'*'`, node("OLD"))} ${logNode("OLD")} END;`);
|
|
15764
|
+
db.exec(`CREATE TRIGGER change_feed_edges_insert AFTER INSERT ON edges BEGIN ${bump(`'*'`, edge("NEW"))} ${logEdge("NEW")} END;`);
|
|
15765
|
+
db.exec(`CREATE TRIGGER change_feed_edges_update AFTER UPDATE ON edges WHEN ${changed("edges")} BEGIN
|
|
15766
|
+
${bump(`'*'`, edge("NEW"))}
|
|
15767
|
+
INSERT INTO change_seq(scope, seq) SELECT ${edge("OLD")}, 1 WHERE OLD.type IS NOT NEW.type
|
|
15768
|
+
ON CONFLICT(scope) DO UPDATE SET seq = seq + 1;
|
|
15769
|
+
${logEdge("NEW")}
|
|
15770
|
+
END;`);
|
|
15771
|
+
db.exec(`CREATE TRIGGER change_feed_edges_delete AFTER DELETE ON edges BEGIN ${bump(`'*'`, edge("OLD"))} ${logEdge("OLD")} END;`);
|
|
15772
|
+
db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(PRUNED_TO_KEY, String(readChangeSeq(db)));
|
|
15773
|
+
db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(CHANGE_FEED_KEY, columnSignature(db));
|
|
15774
|
+
});
|
|
15775
|
+
}
|
|
15776
|
+
function readChangeSeq(db, scopes = [ANY_CHANGE]) {
|
|
15777
|
+
if (!scopes.length) return 0;
|
|
15778
|
+
const row = db.prepare(`SELECT coalesce(sum(seq), 0) AS n FROM change_seq WHERE scope IN (${scopes.map(() => "?").join(",")})`).get(...scopes);
|
|
15779
|
+
return Number(row?.n ?? 0);
|
|
15780
|
+
}
|
|
15781
|
+
function changeLogCovers(db, sinceSeq) {
|
|
15782
|
+
const prunedTo = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? NaN);
|
|
15783
|
+
return Number.isFinite(prunedTo) && sinceSeq >= prunedTo;
|
|
15784
|
+
}
|
|
15785
|
+
function readChangedNodeIds(db, sinceSeq, labels) {
|
|
15786
|
+
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
15787
|
+
if (!labels.length) return [];
|
|
15788
|
+
const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND label IN (${labels.map(() => "?").join(",")})`).all(sinceSeq, ...labels);
|
|
15789
|
+
return rows.map((r) => r.id);
|
|
15790
|
+
}
|
|
15791
|
+
function readChangedAmong(db, sinceSeq, ids) {
|
|
15792
|
+
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
15793
|
+
const out = [];
|
|
15794
|
+
for (let i = 0; i < ids.length; i += 500) {
|
|
15795
|
+
const chunk = ids.slice(i, i + 500);
|
|
15796
|
+
const rows = db.prepare(`SELECT DISTINCT id FROM change_log WHERE change_seq > ? AND kind = 'node' AND id IN (${chunk.map(() => "?").join(",")})`).all(sinceSeq, ...chunk);
|
|
15797
|
+
for (const r of rows) out.push(r.id);
|
|
15798
|
+
}
|
|
15799
|
+
return out;
|
|
15800
|
+
}
|
|
15801
|
+
function readLineageChangedFiles(db, sinceSeq) {
|
|
15802
|
+
if (!changeLogCovers(db, sinceSeq)) return null;
|
|
15803
|
+
const rows = db.prepare(LINEAGE_CHANGED_FILES_SQL).all({ s: sinceSeq });
|
|
15804
|
+
return rows.map((r) => r.id);
|
|
15805
|
+
}
|
|
15806
|
+
var LINEAGE_CHANGED_FILES_SQL = `
|
|
15807
|
+
SELECT id FROM change_log WHERE change_seq > :s AND kind = 'node' AND label = 'File'
|
|
15808
|
+
UNION SELECT from_id FROM change_log WHERE change_seq > :s AND kind = 'edge' AND label IN ('DEFINES','CONTAINS')
|
|
15809
|
+
UNION SELECT e.from_id FROM (SELECT DISTINCT id FROM change_log WHERE change_seq > :s AND kind = 'node') c
|
|
15810
|
+
CROSS JOIN edges e ON e.to_id = c.id AND e.type IN ('DEFINES','CONTAINS')
|
|
15811
|
+
`;
|
|
15812
|
+
function pruneChangeLog(db) {
|
|
15813
|
+
const max = Number(db.prepare("SELECT coalesce(max(change_seq), 0) AS n FROM change_log").get()?.["n"] ?? 0);
|
|
15814
|
+
const cut = max - CHANGE_LOG_KEEP;
|
|
15815
|
+
if (cut <= 0) return 0;
|
|
15816
|
+
let removed = 0;
|
|
15817
|
+
db.transaction(() => {
|
|
15818
|
+
removed = Number(db.prepare("DELETE FROM change_log WHERE change_seq <= ?").run(cut).changes);
|
|
15819
|
+
const prev = Number(db.prepare("SELECT value FROM store_meta WHERE key = ?").get(PRUNED_TO_KEY)?.["value"] ?? 0);
|
|
15820
|
+
db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, ?)").run(PRUNED_TO_KEY, String(Math.max(prev, cut)));
|
|
15821
|
+
});
|
|
15822
|
+
return removed;
|
|
15823
|
+
}
|
|
15824
|
+
function changeFeedInstalled(db) {
|
|
15825
|
+
return db.prepare("SELECT value FROM store_meta WHERE key = ?").get(CHANGE_FEED_KEY)?.["value"] === columnSignature(db);
|
|
15826
|
+
}
|
|
15827
|
+
function columns(db, table) {
|
|
15828
|
+
return db.prepare(`SELECT name FROM pragma_table_info('${table}') ORDER BY cid`).all().map((r) => r.name);
|
|
15829
|
+
}
|
|
15830
|
+
function columnSignature(db) {
|
|
15831
|
+
return `5|${columns(db, "nodes").join(",")}|${columns(db, "edges").join(",")}|${EXPOSURE_ATTRS.join(",")}`;
|
|
15832
|
+
}
|
|
15833
|
+
|
|
15834
|
+
// ../../packages/local-graph/src/lineage-integrity.ts
|
|
15835
|
+
var statements = /* @__PURE__ */ new WeakMap();
|
|
15836
|
+
function stmt(db, sql) {
|
|
15837
|
+
let byDb = statements.get(db);
|
|
15838
|
+
if (!byDb) statements.set(db, byDb = /* @__PURE__ */ new Map());
|
|
15839
|
+
let s = byDb.get(sql);
|
|
15840
|
+
if (!s) byDb.set(sql, s = db.prepare(sql));
|
|
15841
|
+
return s;
|
|
15842
|
+
}
|
|
15843
|
+
var HISTORY_EDGE_TYPES = [
|
|
15844
|
+
"SUPERSEDED_BY",
|
|
15845
|
+
"CONTINUES",
|
|
15846
|
+
"FORKED_FROM",
|
|
15847
|
+
"REGRESSION_OF",
|
|
15848
|
+
"SPLIT_INTO",
|
|
15849
|
+
"REVEALED_BY"
|
|
15850
|
+
];
|
|
15851
|
+
var LINEAGE_INTEGRITY_KEY = "lineage-integrity-v1";
|
|
15852
|
+
var HISTORY_SQL = HISTORY_EDGE_TYPES.map((t) => `'${t}'`).join(",");
|
|
15853
|
+
var NOT_HISTORY = `type NOT IN (${HISTORY_SQL})`;
|
|
15854
|
+
var NOT_HISTORICAL_ROW = `json_type(attrs_json, '$.historical') IS NOT 'true'`;
|
|
15855
|
+
var RETAINS_EDGES = (n) => `(${n}.label = 'Problem'
|
|
15856
|
+
AND json_type(${n}.attrs_json, '$.resolvedAt') IS NOT NULL
|
|
15857
|
+
AND COALESCE(json_extract(${n}.attrs_json, '$.resolvedAs'), '') NOT IN ('duplicate', 'false_positive'))`;
|
|
15858
|
+
var PRESENT = (n) => `(${n}.valid_to IS NULL OR ${RETAINS_EDGES(n)})`;
|
|
15859
|
+
var STRANDED_EDGE_PREDICATE = `e.valid_to IS NULL AND e.${NOT_HISTORY}
|
|
15860
|
+
AND (NOT EXISTS (SELECT 1 FROM nodes f WHERE f.id = e.from_id AND ${PRESENT("f")})
|
|
15861
|
+
OR NOT EXISTS (SELECT 1 FROM nodes t WHERE t.id = e.to_id AND ${PRESENT("t")}))`;
|
|
15862
|
+
function countStrandedEdges(db) {
|
|
15863
|
+
const rows = db.prepare(`SELECT e.type AS type, count(*) AS n FROM edges e WHERE ${STRANDED_EDGE_PREDICATE} GROUP BY e.type ORDER BY n DESC`).all();
|
|
15864
|
+
const byType = {};
|
|
15865
|
+
let total = 0;
|
|
15866
|
+
for (const r of rows) {
|
|
15867
|
+
byType[r.type] = Number(r.n);
|
|
15868
|
+
total += Number(r.n);
|
|
15869
|
+
}
|
|
15870
|
+
return { total, byType };
|
|
15871
|
+
}
|
|
15872
|
+
function countLiveHeadConflicts(db) {
|
|
15873
|
+
const row = db.prepare(
|
|
15874
|
+
`SELECT count(*) AS n FROM (
|
|
15875
|
+
SELECT COALESCE(logical_id, id) AS lid FROM nodes
|
|
15876
|
+
WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
|
|
15877
|
+
GROUP BY lid HAVING count(*) > 1)`
|
|
15878
|
+
).get();
|
|
15879
|
+
return Number(row?.n ?? 0);
|
|
15880
|
+
}
|
|
15881
|
+
function closeIncidentEdges(db, id, t) {
|
|
15882
|
+
let closed = 0;
|
|
15883
|
+
for (const side of ["from_id", "to_id"]) {
|
|
15884
|
+
const r = stmt(db, `UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE ${side} = ? AND valid_to IS NULL AND ${NOT_HISTORY}`).run(t, id);
|
|
15885
|
+
closed += Number(r.changes);
|
|
15886
|
+
}
|
|
15887
|
+
return closed;
|
|
15888
|
+
}
|
|
15889
|
+
function reopenCascadeClosedEdges(db, id, closedAt) {
|
|
15890
|
+
let reopened = 0;
|
|
15891
|
+
for (const [side, other] of [["from_id", "to_id"], ["to_id", "from_id"]]) {
|
|
15892
|
+
const r = stmt(
|
|
15893
|
+
db,
|
|
15894
|
+
`UPDATE edges SET valid_to = NULL
|
|
15895
|
+
WHERE ${side} = ? AND valid_to = ? AND ${NOT_HISTORY}
|
|
15896
|
+
AND EXISTS (SELECT 1 FROM nodes o WHERE o.id = edges.${other} AND o.valid_to IS NULL)`
|
|
15897
|
+
).run(id, closedAt);
|
|
15898
|
+
reopened += Number(r.changes);
|
|
15899
|
+
}
|
|
15900
|
+
return reopened;
|
|
15901
|
+
}
|
|
15902
|
+
function collapseLiveHeads(db, logicalIds, t) {
|
|
15903
|
+
const headsOf = db.prepare(
|
|
15904
|
+
`SELECT id, version, valid_from FROM (
|
|
15905
|
+
SELECT id, version, valid_from, attrs_json FROM nodes WHERE logical_id = ?1 AND valid_to IS NULL
|
|
15906
|
+
UNION
|
|
15907
|
+
SELECT id, version, valid_from, attrs_json FROM nodes WHERE id = ?1 AND logical_id IS NULL AND valid_to IS NULL
|
|
15908
|
+
) WHERE ${NOT_HISTORICAL_ROW}`
|
|
15909
|
+
);
|
|
15910
|
+
const continuesInto = db.prepare(
|
|
15911
|
+
`SELECT to_id FROM edges WHERE from_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
|
|
15912
|
+
);
|
|
15913
|
+
const rekeyHead = db.prepare(`UPDATE nodes SET logical_id = ?1 WHERE id = ?1`);
|
|
15914
|
+
const rekeyCopies = db.prepare(
|
|
15915
|
+
`UPDATE nodes SET logical_id = ?1 WHERE logical_id = ?2 AND substr(id, 1, length(?1) + 2) = ?1 || '@v'`
|
|
15916
|
+
);
|
|
15917
|
+
const lineageMembers = db.prepare(
|
|
15918
|
+
`SELECT id FROM nodes WHERE logical_id = ?1 UNION SELECT id FROM nodes WHERE id = ?1`
|
|
15919
|
+
);
|
|
15920
|
+
const closeContinues = db.prepare(
|
|
15921
|
+
`UPDATE edges SET valid_to = MAX(valid_from, ?) WHERE from_id = ? AND to_id = ? AND type = 'CONTINUES' AND valid_to IS NULL`
|
|
15922
|
+
);
|
|
15923
|
+
let split = 0;
|
|
15924
|
+
const queue = [...new Set(logicalIds)];
|
|
15925
|
+
for (let i = 0; i < queue.length; i++) {
|
|
15926
|
+
const lid = queue[i];
|
|
15927
|
+
const heads = headsOf.all(lid);
|
|
15928
|
+
if (heads.length < 2) continue;
|
|
15929
|
+
const members = new Set(lineageMembers.all(lid).map((r) => r.id));
|
|
15930
|
+
const splitOff = (h) => {
|
|
15931
|
+
for (const { to_id } of continuesInto.all(h.id)) {
|
|
15932
|
+
if (members.has(to_id)) closeContinues.run(t, h.id, to_id);
|
|
15933
|
+
}
|
|
15934
|
+
rekeyHead.run(h.id);
|
|
15935
|
+
rekeyCopies.run(h.id, lid);
|
|
15936
|
+
queue.push(h.id);
|
|
15937
|
+
split++;
|
|
15938
|
+
};
|
|
15939
|
+
const kept = new Set(heads.map((h) => h.id));
|
|
15940
|
+
for (const h of heads) {
|
|
15941
|
+
if (h.id === lid) continue;
|
|
15942
|
+
const refuted = continuesInto.all(h.id).some(
|
|
15943
|
+
(e) => e.to_id !== h.id && kept.has(e.to_id)
|
|
15944
|
+
);
|
|
15945
|
+
if (refuted && kept.size > 1) {
|
|
15946
|
+
splitOff(h);
|
|
15947
|
+
kept.delete(h.id);
|
|
15948
|
+
}
|
|
15949
|
+
}
|
|
15950
|
+
let remaining = heads.filter((h) => kept.has(h.id));
|
|
15951
|
+
if (remaining.length < 2) continue;
|
|
15952
|
+
remaining = [...remaining].sort(
|
|
15953
|
+
(a, b) => Number(b.id === lid) - Number(a.id === lid) || Number(b.version) - Number(a.version) || Number(b.valid_from) - Number(a.valid_from) || (a.id < b.id ? -1 : 1)
|
|
15954
|
+
);
|
|
15955
|
+
for (const h of remaining.slice(1)) splitOff(h);
|
|
15956
|
+
}
|
|
15957
|
+
return split;
|
|
15958
|
+
}
|
|
15959
|
+
function repairLineageIntegrity(db, now) {
|
|
15960
|
+
const historicalRowsClosed = Number(
|
|
15961
|
+
db.prepare(
|
|
15962
|
+
`UPDATE nodes SET valid_to = MAX(valid_from, COALESCE(
|
|
15963
|
+
(SELECT s.valid_from FROM edges s WHERE s.id = 'edge_superseded_' || nodes.id), ?))
|
|
15964
|
+
WHERE valid_to IS NULL AND json_type(attrs_json, '$.historical') = 'true'`
|
|
15965
|
+
).run(now).changes
|
|
15966
|
+
);
|
|
15967
|
+
const conflicted = db.prepare(
|
|
15968
|
+
`SELECT COALESCE(logical_id, id) AS lid FROM nodes
|
|
15969
|
+
WHERE valid_to IS NULL AND ${NOT_HISTORICAL_ROW}
|
|
15970
|
+
GROUP BY lid HAVING count(*) > 1`
|
|
15971
|
+
).all().map((r) => r.lid);
|
|
15972
|
+
const headsSplit = collapseLiveHeads(db, conflicted, now);
|
|
15973
|
+
const strandedEdgesClosed = Number(
|
|
15974
|
+
db.prepare(
|
|
15975
|
+
`UPDATE edges SET valid_to = MAX(valid_from, COALESCE(
|
|
15976
|
+
(SELECT min(v) FROM (
|
|
15977
|
+
SELECT f.valid_to AS v FROM nodes f WHERE f.id = edges.from_id AND NOT ${PRESENT("f")}
|
|
15978
|
+
UNION ALL
|
|
15979
|
+
SELECT t.valid_to FROM nodes t WHERE t.id = edges.to_id AND NOT ${PRESENT("t")})), ?))
|
|
15980
|
+
WHERE id IN (SELECT e.id FROM edges e WHERE ${STRANDED_EDGE_PREDICATE})`
|
|
15981
|
+
).run(now).changes
|
|
15982
|
+
);
|
|
15983
|
+
return { historicalRowsClosed, headsSplit, strandedEdgesClosed };
|
|
15984
|
+
}
|
|
15985
|
+
function installLineageIntegrityRepair(db, now = Date.now()) {
|
|
15986
|
+
if (db.prepare("SELECT value FROM store_meta WHERE key = ?").get(LINEAGE_INTEGRITY_KEY)) return null;
|
|
15987
|
+
return db.transaction(() => {
|
|
15988
|
+
const report = repairLineageIntegrity(db, now);
|
|
15989
|
+
db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, ?)").run(
|
|
15990
|
+
LINEAGE_INTEGRITY_KEY,
|
|
15991
|
+
JSON.stringify({ at: now, ...report })
|
|
15992
|
+
);
|
|
15993
|
+
return report;
|
|
15994
|
+
});
|
|
15995
|
+
}
|
|
15996
|
+
|
|
15096
15997
|
// ../../packages/math/src/relevance.ts
|
|
15097
15998
|
function pageRankScale(ranks) {
|
|
15098
15999
|
const positive = [...ranks].filter((r) => Number.isFinite(r) && r > 0).sort((a, b) => a - b);
|
|
@@ -15103,6 +16004,177 @@ function pageRankScale(ranks) {
|
|
|
15103
16004
|
var DEMAND_HOT = Math.log1p(10);
|
|
15104
16005
|
var DEMAND_WARM = Math.log1p(5);
|
|
15105
16006
|
|
|
16007
|
+
// ../../packages/local-graph/src/node-select.ts
|
|
16008
|
+
var SELECT_INDEXES = [
|
|
16009
|
+
"nodes_live_local_community",
|
|
16010
|
+
"nodes_live_fold_candidate",
|
|
16011
|
+
"nodes_live_resolved",
|
|
16012
|
+
"nodes_live_fix_candidate",
|
|
16013
|
+
"nodes_live_anchor_hint"
|
|
16014
|
+
];
|
|
16015
|
+
var EMBEDDING_DIM_SQL = "(CASE WHEN length(embedding_blob) >= 4 THEN length(embedding_blob) / 4 ELSE COALESCE(json_array_length(embedding_json), 0) END)";
|
|
16016
|
+
var KEY = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
16017
|
+
var COLUMNS = /* @__PURE__ */ new Set([
|
|
16018
|
+
"created_at",
|
|
16019
|
+
"last_updated_at",
|
|
16020
|
+
"page_rank",
|
|
16021
|
+
"created_at_seq",
|
|
16022
|
+
"last_reinforced_at_seq",
|
|
16023
|
+
"cumulative_hits",
|
|
16024
|
+
"extraction_confidence"
|
|
16025
|
+
]);
|
|
16026
|
+
function path(key) {
|
|
16027
|
+
if (!KEY.test(key)) throw new Error(`selectNodes: bad attr key ${key}`);
|
|
16028
|
+
return `'$.${key}'`;
|
|
16029
|
+
}
|
|
16030
|
+
function selectIndex(i) {
|
|
16031
|
+
if (!SELECT_INDEXES.includes(i)) throw new Error(`selectNodes: bad index ${i}`);
|
|
16032
|
+
return i;
|
|
16033
|
+
}
|
|
16034
|
+
function column(c) {
|
|
16035
|
+
if (!COLUMNS.has(c)) throw new Error(`selectNodes: bad column ${c}`);
|
|
16036
|
+
return c;
|
|
16037
|
+
}
|
|
16038
|
+
function compileNodeWhere(q) {
|
|
16039
|
+
if (q.labels.length === 0) throw new Error("selectNodes: no labels");
|
|
16040
|
+
const params = [...q.labels];
|
|
16041
|
+
const byId = (q.where ?? []).some((w) => "idIn" in w || "idPrefix" in w && w.idPrefix.length > 0);
|
|
16042
|
+
const unindexed = byId ? "+" : "";
|
|
16043
|
+
const conds = [`${unindexed}label IN (${q.labels.map(() => "?").join(", ")})`];
|
|
16044
|
+
if (!q.includeClosed) conds.push(`${unindexed}valid_to IS NULL`);
|
|
16045
|
+
for (const w of q.where ?? []) {
|
|
16046
|
+
if ("embedding" in w) {
|
|
16047
|
+
conds.push(`${EMBEDDING_DIM_SQL} ${w.embedding === "present" ? ">" : "="} 0`);
|
|
16048
|
+
} else if ("idPrefix" in w) {
|
|
16049
|
+
conds.push("substr(id, 1, ?) = ?");
|
|
16050
|
+
params.push(w.idPrefix.length, w.idPrefix);
|
|
16051
|
+
if (w.idPrefix.length > 0) {
|
|
16052
|
+
const last = w.idPrefix.charCodeAt(w.idPrefix.length - 1);
|
|
16053
|
+
conds.push("id >= ? AND id < ?");
|
|
16054
|
+
params.push(w.idPrefix, w.idPrefix.slice(0, -1) + String.fromCharCode(last + 1));
|
|
16055
|
+
}
|
|
16056
|
+
} else if ("idIn" in w) {
|
|
16057
|
+
if (w.idIn.length === 0) conds.push("0");
|
|
16058
|
+
else {
|
|
16059
|
+
conds.push(`id IN (${w.idIn.map(() => "?").join(", ")})`);
|
|
16060
|
+
params.push(...w.idIn);
|
|
16061
|
+
}
|
|
16062
|
+
} else if ("descriptionIn" in w) {
|
|
16063
|
+
if (w.descriptionIn.length === 0) conds.push("0");
|
|
16064
|
+
else {
|
|
16065
|
+
conds.push(`description IN (${w.descriptionIn.map(() => "?").join(", ")})`);
|
|
16066
|
+
params.push(...w.descriptionIn);
|
|
16067
|
+
}
|
|
16068
|
+
} else if ("column" in w) {
|
|
16069
|
+
const c = column(w.column);
|
|
16070
|
+
for (const [op, sym] of [["gte", ">="], ["gt", ">"], ["lte", "<="], ["lt", "<"]]) {
|
|
16071
|
+
const v = w[op];
|
|
16072
|
+
if (v !== void 0) {
|
|
16073
|
+
conds.push(`${c} ${sym} ?`);
|
|
16074
|
+
params.push(v);
|
|
16075
|
+
}
|
|
16076
|
+
}
|
|
16077
|
+
} else {
|
|
16078
|
+
const p = path(w.attr);
|
|
16079
|
+
if ("absent" in w) conds.push(`json_type(attrs_json, ${p}) IS NULL`);
|
|
16080
|
+
else if ("present" in w) conds.push(`json_type(attrs_json, ${p}) IS NOT NULL`);
|
|
16081
|
+
else if ("nullish" in w) conds.push(`json_extract(attrs_json, ${p}) IS NULL`);
|
|
16082
|
+
else if ("notNullish" in w) conds.push(`json_extract(attrs_json, ${p}) IS NOT NULL`);
|
|
16083
|
+
else if ("eq" in w || "ne" in w) {
|
|
16084
|
+
const v = "eq" in w ? w.eq : w.ne;
|
|
16085
|
+
const neg = "ne" in w;
|
|
16086
|
+
if (typeof v === "boolean") {
|
|
16087
|
+
conds.push(`json_type(attrs_json, ${p}) ${neg ? "IS NOT" : "="} ?`);
|
|
16088
|
+
params.push(v ? "true" : "false");
|
|
16089
|
+
} else {
|
|
16090
|
+
conds.push(neg ? `json_extract(attrs_json, ${p}) IS NOT ?` : `json_extract(attrs_json, ${p}) = ?`);
|
|
16091
|
+
params.push(v);
|
|
16092
|
+
}
|
|
16093
|
+
}
|
|
16094
|
+
}
|
|
16095
|
+
}
|
|
16096
|
+
return { where: conds.join(" AND "), params };
|
|
16097
|
+
}
|
|
16098
|
+
function attrValueSql(p, doc = "attrs_json") {
|
|
16099
|
+
return `CASE json_type(${doc}, ${p}) WHEN 'integer' THEN CAST(json_extract(${doc}, ${p}) AS REAL) ELSE json_extract(${doc}, ${p}) END`;
|
|
16100
|
+
}
|
|
16101
|
+
function attrDocSql(key) {
|
|
16102
|
+
return isExposureAttr(key) ? exposureDocSql(key) : "attrs_json";
|
|
16103
|
+
}
|
|
16104
|
+
function nodeSelectFrom(q) {
|
|
16105
|
+
if (q.indexedBy) return `nodes INDEXED BY ${selectIndex(q.indexedBy)}`;
|
|
16106
|
+
const where = q.where ?? [];
|
|
16107
|
+
if (!q.includeClosed && where.some((w) => "descriptionIn" in w && w.descriptionIn.length > 0) && !where.some((w) => "idIn" in w)) {
|
|
16108
|
+
return "nodes INDEXED BY nodes_live_description";
|
|
16109
|
+
}
|
|
16110
|
+
return "nodes";
|
|
16111
|
+
}
|
|
16112
|
+
function compileNodeSelect(q) {
|
|
16113
|
+
const { where, params } = compileNodeWhere(q);
|
|
16114
|
+
const attrs = q.attrs ?? [];
|
|
16115
|
+
const attrCols = attrs.map((k, i) => `, ${attrValueSql(path(k), attrDocSql(k))} AS a${i}, json_type(${attrDocSql(k)}, ${path(k)}) AS t${i}`).join("");
|
|
16116
|
+
let order;
|
|
16117
|
+
if (q.orderBy && "column" in q.orderBy) {
|
|
16118
|
+
order = `${column(q.orderBy.column)} ${q.orderBy.desc ? "DESC" : "ASC"}, rowid`;
|
|
16119
|
+
} else if (q.orderBy) {
|
|
16120
|
+
const p = path(q.orderBy.attr);
|
|
16121
|
+
order = `json_extract(attrs_json, ${p}) IS NULL, json_extract(attrs_json, ${p}) ${q.orderBy.desc ? "DESC" : "ASC"}, rowid`;
|
|
16122
|
+
} else if (q.labels.length > 1) {
|
|
16123
|
+
order = `CASE label ${q.labels.map((_, i) => `WHEN ? THEN ${i}`).join(" ")} END, rowid`;
|
|
16124
|
+
} else {
|
|
16125
|
+
order = "rowid";
|
|
16126
|
+
}
|
|
16127
|
+
const orderParams = !q.orderBy && q.labels.length > 1 ? [...q.labels] : [];
|
|
16128
|
+
const limit = q.limit !== void 0 ? ` LIMIT ${Math.max(0, Math.floor(q.limit))}` : "";
|
|
16129
|
+
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}`;
|
|
16130
|
+
return { sql, params: [...params, ...orderParams] };
|
|
16131
|
+
}
|
|
16132
|
+
function decodeAttr(value, type) {
|
|
16133
|
+
switch (type) {
|
|
16134
|
+
case null:
|
|
16135
|
+
case void 0:
|
|
16136
|
+
return void 0;
|
|
16137
|
+
case "true":
|
|
16138
|
+
return true;
|
|
16139
|
+
case "false":
|
|
16140
|
+
return false;
|
|
16141
|
+
case "null":
|
|
16142
|
+
return null;
|
|
16143
|
+
case "object":
|
|
16144
|
+
case "array":
|
|
16145
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
16146
|
+
default:
|
|
16147
|
+
return value;
|
|
16148
|
+
}
|
|
16149
|
+
}
|
|
16150
|
+
function decodeSelectedRow(r, attrs, decodeVector) {
|
|
16151
|
+
const out = {};
|
|
16152
|
+
attrs.forEach((k, i) => {
|
|
16153
|
+
const v = decodeAttr(r[`a${i}`], r[`t${i}`]);
|
|
16154
|
+
if (v !== void 0) out[k] = v;
|
|
16155
|
+
});
|
|
16156
|
+
return {
|
|
16157
|
+
id: r["id"],
|
|
16158
|
+
label: r["label"],
|
|
16159
|
+
description: r["description"] ?? "",
|
|
16160
|
+
createdAt: r["created_at"],
|
|
16161
|
+
lastUpdatedAt: r["last_updated_at"],
|
|
16162
|
+
pageRank: r["page_rank"],
|
|
16163
|
+
cumulativeHits: r["cumulative_hits"],
|
|
16164
|
+
extractionConfidence: r["extraction_confidence"],
|
|
16165
|
+
extractionSource: r["extraction_source"],
|
|
16166
|
+
community: r["community"] ?? null,
|
|
16167
|
+
...r["created_at_seq"] != null ? { createdAtSeq: r["created_at_seq"] } : {},
|
|
16168
|
+
...r["last_reinforced_at_seq"] != null ? { lastReinforcedAtSeq: r["last_reinforced_at_seq"] } : {},
|
|
16169
|
+
validTo: r["valid_to"] ?? null,
|
|
16170
|
+
logicalId: r["logical_id"] ?? r["id"],
|
|
16171
|
+
version: Number(r["version"] ?? 1),
|
|
16172
|
+
embeddingDim: Number(r["embedding_dim"] ?? 0),
|
|
16173
|
+
...decodeVector ? { embedding: decodeVector(r["embedding_blob"], r["embedding_json"] ?? "[]") } : {},
|
|
16174
|
+
attrs: out
|
|
16175
|
+
};
|
|
16176
|
+
}
|
|
16177
|
+
|
|
15106
16178
|
// ../../packages/local-graph/src/store.ts
|
|
15107
16179
|
var PAGE_RANK_SCALE_KEY = "pageRankScale.p95.v1";
|
|
15108
16180
|
var TEXT_SEARCH_COLUMNS = `id, label, description, page_rank,
|
|
@@ -15157,6 +16229,7 @@ function localEdgeViolation(fromLabel, type, toLabel) {
|
|
|
15157
16229
|
}
|
|
15158
16230
|
var SCHEMA_VERSION = 6;
|
|
15159
16231
|
var SESSION_REPAIR_KEY = "session-evidence-v1";
|
|
16232
|
+
var EXPOSURE_SIDE_KEY = "exposure-side-rows-1";
|
|
15160
16233
|
var SESSION_EVIDENCE_PREDICATE = SESSION_EVIDENCE_FIELDS.map((field) => `instr(attrs_json, '${JSON.stringify(field)}') > 0`).join(" OR ");
|
|
15161
16234
|
var SCHEMA_SQL = `
|
|
15162
16235
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -15220,8 +16293,13 @@ CREATE TABLE IF NOT EXISTS nodes (
|
|
|
15220
16293
|
);
|
|
15221
16294
|
|
|
15222
16295
|
CREATE INDEX IF NOT EXISTS nodes_label ON nodes(label);
|
|
15223
|
-
|
|
15224
|
-
|
|
16296
|
+
-- Dropped: no query plans through them (every prepared statement across the
|
|
16297
|
+
-- local-graph, indexer, context-writer, generalizer and daemon suites was
|
|
16298
|
+
-- planned with and without them, 2026-10-21), and each write that touches
|
|
16299
|
+
-- memory_tier / page_rank (every node upsert, every rescore) paid to maintain
|
|
16300
|
+
-- them. nodes_live_page_rank serves the live page-rank orderings.
|
|
16301
|
+
DROP INDEX IF EXISTS nodes_memory_tier;
|
|
16302
|
+
DROP INDEX IF EXISTS nodes_page_rank;
|
|
15225
16303
|
|
|
15226
16304
|
CREATE TABLE IF NOT EXISTS edges (
|
|
15227
16305
|
id TEXT PRIMARY KEY,
|
|
@@ -15243,7 +16321,10 @@ CREATE TABLE IF NOT EXISTS edges (
|
|
|
15243
16321
|
FOREIGN KEY (to_id) REFERENCES nodes(id) ON DELETE CASCADE
|
|
15244
16322
|
);
|
|
15245
16323
|
|
|
15246
|
-
|
|
16324
|
+
-- Dropped: a prefix of edges_triple (from_id, type, to_id), which every
|
|
16325
|
+
-- from-side lookup and foreign-key check already plans through; it cost 47 MB
|
|
16326
|
+
-- and an index write on every edge write.
|
|
16327
|
+
DROP INDEX IF EXISTS edges_from;
|
|
15247
16328
|
CREATE INDEX IF NOT EXISTS edges_to ON edges(to_id, type);
|
|
15248
16329
|
CREATE INDEX IF NOT EXISTS edges_type ON edges(type);
|
|
15249
16330
|
CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
@@ -15251,8 +16332,41 @@ CREATE UNIQUE INDEX IF NOT EXISTS edges_triple ON edges(from_id, type, to_id);
|
|
|
15251
16332
|
var TEMPORAL_INDEX_SQL = `
|
|
15252
16333
|
CREATE INDEX IF NOT EXISTS nodes_live ON nodes(label, valid_to);
|
|
15253
16334
|
CREATE INDEX IF NOT EXISTS nodes_live_count ON nodes(valid_to);
|
|
15254
|
-
|
|
16335
|
+
-- Problems by recency (the only label read newest-first). Over every live row,
|
|
16336
|
+
-- each write that stamps last_updated_at moved a code symbol's entry from a
|
|
16337
|
+
-- scattered leaf: ~19 KB per position-only symbol rewrite on a 1.1 GB store.
|
|
16338
|
+
-- label stays the leading column so label = 'Problem' plans without a sort.
|
|
16339
|
+
DROP INDEX IF EXISTS nodes_live_recent;
|
|
16340
|
+
CREATE INDEX IF NOT EXISTS nodes_live_problem_recent ON nodes(label, last_updated_at DESC) WHERE valid_to IS NULL AND label = 'Problem';
|
|
16341
|
+
-- "Anything created since the watermark?" guards (the fold, regression and
|
|
16342
|
+
-- canonical-bind sweeps run one per session boundary). Without it the planner
|
|
16343
|
+
-- walks nodes_live and reads every live row of the label to test created_at:
|
|
16344
|
+
-- 56 MB per guard on a 1.1 GB store, to learn there was nothing to do. valid_to
|
|
16345
|
+
-- is a key column so the planner prefers this over nodes_live (one more term).
|
|
16346
|
+
CREATE INDEX IF NOT EXISTS nodes_live_created ON nodes(label, valid_to, created_at) WHERE valid_to IS NULL;
|
|
16347
|
+
-- Community members (the skills pull reads them twice per session boundary):
|
|
16348
|
+
-- ~15% of semantic nodes carry a localCommunity, and without this every live
|
|
16349
|
+
-- semantic row was read to find them (168 MB \u2192 18 MB per read).
|
|
16350
|
+
CREATE INDEX IF NOT EXISTS nodes_live_local_community ON nodes(label, valid_to, json_extract(attrs_json, '$.localCommunity')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.localCommunity') IS NOT NULL;
|
|
16351
|
+
-- Problems parked for the fold judge (read every settle boundary): ~1 in 5 open
|
|
16352
|
+
-- Problems; without it every live Problem was read to find them (56 MB).
|
|
16353
|
+
CREATE INDEX IF NOT EXISTS nodes_live_fold_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.foldCandidate') IS NOT NULL;
|
|
16354
|
+
-- Render impressions, one row per node id (exposure-attrs.ts): a render bumps
|
|
16355
|
+
-- ~40 of these, which in the node row rewrote each whole row and its indexes.
|
|
16356
|
+
CREATE TABLE IF NOT EXISTS node_exposure (
|
|
16357
|
+
id TEXT PRIMARY KEY,
|
|
16358
|
+
attrs_json TEXT NOT NULL DEFAULT '{}'
|
|
16359
|
+
) WITHOUT ROWID;
|
|
16360
|
+
-- The semantic-maintenance sweeps' own filters (closed, asked, hinted): each read
|
|
16361
|
+
-- every Problem row (~30 MB on a 1.1 GB store) for the few hundred that match,
|
|
16362
|
+
-- several times a run, and a run follows most writes (2026-10-20).
|
|
16363
|
+
CREATE INDEX IF NOT EXISTS nodes_live_resolved ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.resolvedAt') IS NOT NULL;
|
|
16364
|
+
CREATE INDEX IF NOT EXISTS nodes_live_fix_candidate ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_type(attrs_json, '$.fixCandidateAt') IS NOT NULL;
|
|
16365
|
+
CREATE INDEX IF NOT EXISTS nodes_live_anchor_hint ON nodes(label, valid_to) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint') IS NOT NULL;
|
|
15255
16366
|
CREATE INDEX IF NOT EXISTS nodes_session_evidence ON nodes(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
|
|
16367
|
+
-- Rows carrying a calibration counter, any version: the logical nodes wm
|
|
16368
|
+
-- calibration can count (~3k), found without reading every citable row.
|
|
16369
|
+
CREATE INDEX IF NOT EXISTS nodes_wm_counted ON nodes(logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map(wmCounterSql).join(", ")}) WHERE ${WM_COUNTED_PREDICATE};
|
|
15256
16370
|
CREATE INDEX IF NOT EXISTS edges_session_evidence ON edges(id) WHERE ${SESSION_EVIDENCE_PREDICATE};
|
|
15257
16371
|
-- Covering + partial index for labelFingerprint over the instance labels: the
|
|
15258
16372
|
-- live-push probe runs it per workspace on a timer, and each hit used to read
|
|
@@ -15266,31 +16380,70 @@ CREATE INDEX IF NOT EXISTS nodes_instance_fingerprint_v2 ON nodes(label, last_up
|
|
|
15266
16380
|
CREATE INDEX IF NOT EXISTS nodes_live_page_rank ON nodes(valid_to, page_rank);
|
|
15267
16381
|
CREATE INDEX IF NOT EXISTS nodes_logical ON nodes(logical_id, version);
|
|
15268
16382
|
CREATE INDEX IF NOT EXISTS edges_live ON edges(valid_to);
|
|
16383
|
+
-- Newest live edges first, so a "recent N matching edges" read stops early instead
|
|
16384
|
+
-- of matching and sorting every live edge (the hourly liveness check: 163 MB \u2192
|
|
16385
|
+
-- 69 MB). Named by recentEdgeAttrCoverage; the planner keeps edges_live otherwise.
|
|
16386
|
+
CREATE INDEX IF NOT EXISTS edges_live_created ON edges(created_at) WHERE valid_to IS NULL;
|
|
16387
|
+
-- Live prior-tag edges by recency: the liveness corroboration check reads the
|
|
16388
|
+
-- newest 100 of ~300, and walking all live edges newest-first to find them read
|
|
16389
|
+
-- 69 MB. Its predicate is the one recentEdgeAttrCoverage uses for that marker.
|
|
16390
|
+
CREATE INDEX IF NOT EXISTS edges_live_prior_tag ON edges(created_at) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1;
|
|
15269
16391
|
-- Expression index on the per-node relPath (stored in attrs_json) so the
|
|
15270
16392
|
-- incremental reindex can fetch just the changed files' symbols instead of
|
|
15271
16393
|
-- scanning every versioned node. Partial: only live rows are ever queried.
|
|
15272
16394
|
CREATE INDEX IF NOT EXISTS nodes_relpath ON nodes(json_extract(attrs_json, '$.relPath')) WHERE valid_to IS NULL;
|
|
16395
|
+
-- Symbols by qname and doc comments by the symbol they attach to: the drain's
|
|
16396
|
+
-- symbol lexicon resolves the names in the text it ships one at a time, instead
|
|
16397
|
+
-- of reading every Function/Method/Class/Interface/Const and Comment row on each
|
|
16398
|
+
-- live push (~300 MB of a 1.1 GB store per push, 2026-10-20).
|
|
16399
|
+
CREATE INDEX IF NOT EXISTS nodes_live_qname ON nodes(json_extract(attrs_json, '$.qname')) WHERE valid_to IS NULL;
|
|
16400
|
+
CREATE INDEX IF NOT EXISTS nodes_live_attached_to ON nodes(json_extract(attrs_json, '$.attachedTo')) WHERE valid_to IS NULL AND label = 'Comment';
|
|
16401
|
+
-- The attrs the cloud drain gates every instance row on, so it reads them from
|
|
16402
|
+
-- the index alone: pulled cloud rows are most of these labels (7,105 of 7,708
|
|
16403
|
+
-- Solutions on the live store) at ~10 KB of attrs each, and five full scans for
|
|
16404
|
+
-- two attrs read ~220 MB per push to find the few local rows that can ship.
|
|
16405
|
+
-- created_at leads so the index order is findNodeFieldsByLabel's.
|
|
16406
|
+
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');
|
|
16407
|
+
-- Closed rows by relPath, for reviveReobserved (every incremental reindex): the
|
|
16408
|
+
-- live-only index above cannot serve it, so it scanned the whole table (~275 MB
|
|
16409
|
+
-- of a 1.1 GB store per reindex, 2026-10-20).
|
|
16410
|
+
CREATE INDEX IF NOT EXISTS nodes_closed_relpath ON nodes(json_extract(attrs_json, '$.relPath'), last_updated_at) WHERE valid_to IS NOT NULL;
|
|
15273
16411
|
-- Live rows by name, for looking up the few symbols a statement mentions
|
|
15274
16412
|
-- instead of hydrating every code symbol per minted problem.
|
|
15275
16413
|
CREATE INDEX IF NOT EXISTS nodes_live_description ON nodes(description, label) WHERE valid_to IS NULL;
|
|
16414
|
+
-- The six single-attr lookups below are partial on the attr being present: each
|
|
16415
|
+
-- is read only by equality (or IN), which implies it. Keyed on NULL for the ~95%
|
|
16416
|
+
-- of rows that lack the attr, every write to such a row's attrs rewrote its NULL
|
|
16417
|
+
-- entry: ~25 KB per position-only symbol rewrite on a 1.1 GB store (2026-10-21).
|
|
16418
|
+
DROP INDEX IF EXISTS nodes_anchor_hint_path;
|
|
16419
|
+
DROP INDEX IF EXISTS nodes_command_signature;
|
|
16420
|
+
DROP INDEX IF EXISTS nodes_diag_command;
|
|
16421
|
+
DROP INDEX IF EXISTS nodes_lsp_rel_path;
|
|
16422
|
+
DROP INDEX IF EXISTS nodes_cloud_node_id;
|
|
16423
|
+
DROP INDEX IF EXISTS nodes_enrichment_pending;
|
|
15276
16424
|
-- Problems by the file their anchor hint names: the harvest upgrades hints for
|
|
15277
16425
|
-- every edited file of every turn, and a scan parses every Problem's attrs.
|
|
15278
|
-
CREATE INDEX IF NOT EXISTS
|
|
16426
|
+
CREATE INDEX IF NOT EXISTS nodes_anchor_hint_path_v2 ON nodes(json_extract(attrs_json, '$.anchorHint.path')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.anchorHint.path') IS NOT NULL;
|
|
15279
16427
|
-- Problems by the command / checker / file that opened them. The generalizer
|
|
15280
16428
|
-- looked these up per shell or tool EVENT by hydrating every Problem: ~12s of
|
|
15281
16429
|
-- every minute of daemon main-thread time on a 3.4k-Problem store (2026-10-05).
|
|
15282
|
-
CREATE INDEX IF NOT EXISTS
|
|
15283
|
-
CREATE INDEX IF NOT EXISTS
|
|
15284
|
-
CREATE INDEX IF NOT EXISTS
|
|
16430
|
+
CREATE INDEX IF NOT EXISTS nodes_command_signature_v2 ON nodes(json_extract(attrs_json, '$.commandSignature')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.commandSignature') IS NOT NULL;
|
|
16431
|
+
CREATE INDEX IF NOT EXISTS nodes_diag_command_v2 ON nodes(json_extract(attrs_json, '$.diagCommand')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.diagCommand') IS NOT NULL;
|
|
16432
|
+
CREATE INDEX IF NOT EXISTS nodes_lsp_rel_path_v2 ON nodes(json_extract(attrs_json, '$.lspRelPath')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.lspRelPath') IS NOT NULL;
|
|
15285
16433
|
-- Local nodes by the cloud twin their upload receipt recorded. Every burst /
|
|
15286
16434
|
-- search fusion folds the Network copy of the caller's own knowledge back onto
|
|
15287
16435
|
-- the local node; unindexed, the reverse lookup was a ~300ms attrs scan per call.
|
|
15288
|
-
CREATE INDEX IF NOT EXISTS
|
|
16436
|
+
CREATE INDEX IF NOT EXISTS nodes_cloud_node_id_v2 ON nodes(json_extract(attrs_json, '$.cloudNodeId')) WHERE valid_to IS NULL AND json_extract(attrs_json, '$.cloudNodeId') IS NOT NULL;
|
|
15289
16437
|
-- Nodes still waiting on a root-cause ask. The MCP server checks this on EVERY
|
|
15290
16438
|
-- tool response (the enrichment piggyback), so it must not parse every
|
|
15291
|
-
-- Solution's and Problem's attrs per call.
|
|
16439
|
+
-- Solution's and Problem's attrs per call. Any version: the Problem pull reads
|
|
15292
16440
|
-- closed versions too.
|
|
15293
|
-
CREATE INDEX IF NOT EXISTS
|
|
16441
|
+
CREATE INDEX IF NOT EXISTS nodes_enrichment_pending_v2 ON nodes(json_extract(attrs_json, '$.enrichmentPending')) WHERE json_extract(attrs_json, '$.enrichmentPending') IS NOT NULL;
|
|
16442
|
+
-- Revisit flags, any label, any version: listNeedsRevisit's as-of read runs on
|
|
16443
|
+
-- every context render and otherwise json-checks every row (10.3 s on a 324k-row
|
|
16444
|
+
-- store, to return zero flags). The predicate must stay textually identical to
|
|
16445
|
+
-- nodesAsOfWhereTrue's or the planner will not use it.
|
|
16446
|
+
CREATE INDEX IF NOT EXISTS nodes_revisit_true ON nodes(valid_from) WHERE json_type(attrs_json, '$.revisit') = 'true';
|
|
15294
16447
|
`;
|
|
15295
16448
|
function encodeEmbedding(emb) {
|
|
15296
16449
|
if (!emb || emb.length === 0) return null;
|
|
@@ -15351,12 +16504,39 @@ function rowToEdge(r) {
|
|
|
15351
16504
|
}
|
|
15352
16505
|
var FINGERPRINT_INDEXED_LABELS = ["Problem", "Vulnerability", "Solution", "RootCause"];
|
|
15353
16506
|
var NODE_LABELS_CHUNK = 256;
|
|
16507
|
+
var NODE_UPSERT_COLUMNS = [
|
|
16508
|
+
"label",
|
|
16509
|
+
"description",
|
|
16510
|
+
"extraction_confidence",
|
|
16511
|
+
"embedding_json",
|
|
16512
|
+
"embedding_blob",
|
|
16513
|
+
"cumulative_surprise",
|
|
16514
|
+
"peak_surprise",
|
|
16515
|
+
"cumulative_hits",
|
|
16516
|
+
"last_updated_at",
|
|
16517
|
+
"memory_tier",
|
|
16518
|
+
"page_rank",
|
|
16519
|
+
"is_landmark",
|
|
16520
|
+
"community",
|
|
16521
|
+
"stability",
|
|
16522
|
+
"last_reinforced_at_seq",
|
|
16523
|
+
"attrs_json"
|
|
16524
|
+
];
|
|
16525
|
+
var EDGE_UPSERT_COLUMNS = ["confidence", "last_seen_at", "nav_successes", "nav_failures", "valid_to", "attrs_json"];
|
|
16526
|
+
function sameSqlValue(a, b) {
|
|
16527
|
+
if (a === b || a == null && b == null) return true;
|
|
16528
|
+
if (a instanceof Uint8Array && b instanceof Uint8Array) return Buffer.compare(a, b) === 0;
|
|
16529
|
+
return false;
|
|
16530
|
+
}
|
|
16531
|
+
var PRIOR_TAG_MARKER = '"priorTag":true';
|
|
15354
16532
|
var SqliteGraphStore = class {
|
|
15355
16533
|
db;
|
|
15356
16534
|
// Typed edge lookups build their SQL from the number of types asked for, and
|
|
15357
16535
|
// used to prepare it on every call: ~600ms of a 2.5s drain build on a live
|
|
15358
16536
|
// 225k-node store was statement compilation alone. Keyed by the SQL text.
|
|
15359
16537
|
typedEdgeStmts = /* @__PURE__ */ new Map();
|
|
16538
|
+
/** Narrow merge updates, keyed by table and the columns they set. */
|
|
16539
|
+
changedColumnStmts = /* @__PURE__ */ new Map();
|
|
15360
16540
|
stmts;
|
|
15361
16541
|
/** Unified graph-mutation counter (see GraphStore.mutationCount). Bumped on
|
|
15362
16542
|
* every node/edge upsert — code-index and capture alike — so the consolidation
|
|
@@ -15375,6 +16555,8 @@ var SqliteGraphStore = class {
|
|
|
15375
16555
|
initialPageRankScale;
|
|
15376
16556
|
constructor(opts) {
|
|
15377
16557
|
this.db = openDatabase(opts.path);
|
|
16558
|
+
this.db.exec("PRAGMA cache_size = -32768");
|
|
16559
|
+
this.db.exec("PRAGMA mmap_size = 2147483648");
|
|
15378
16560
|
this.db.exec(SCHEMA_SQL);
|
|
15379
16561
|
const versionRow = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
|
|
15380
16562
|
if (!versionRow) {
|
|
@@ -15391,12 +16573,15 @@ var SqliteGraphStore = class {
|
|
|
15391
16573
|
installEditPlaceholderRetirement(this.db);
|
|
15392
16574
|
installCloudAttrCompaction(this.db);
|
|
15393
16575
|
installHistoryAttrSlim(this.db);
|
|
16576
|
+
installTextIndex(this.db);
|
|
15394
16577
|
if (!this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(SESSION_REPAIR_KEY)) {
|
|
15395
16578
|
this.db.transaction(() => {
|
|
15396
16579
|
this.repairSessionAliases();
|
|
15397
16580
|
this.db.prepare("INSERT OR REPLACE INTO store_meta (key, value) VALUES (?, 'done')").run(SESSION_REPAIR_KEY);
|
|
15398
16581
|
});
|
|
15399
16582
|
}
|
|
16583
|
+
installLineageIntegrityRepair(this.db);
|
|
16584
|
+
installChangeFeed(this.db);
|
|
15400
16585
|
this.stmts = {
|
|
15401
16586
|
insertNode: this.db.prepare(`
|
|
15402
16587
|
INSERT INTO nodes (
|
|
@@ -15435,7 +16620,19 @@ var SqliteGraphStore = class {
|
|
|
15435
16620
|
last_reinforced_at_seq = excluded.last_reinforced_at_seq,
|
|
15436
16621
|
attrs_json = excluded.attrs_json
|
|
15437
16622
|
`),
|
|
15438
|
-
|
|
16623
|
+
nodeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${NODE_UPSERT_COLUMNS.join(", ")} FROM nodes WHERE id = ?`),
|
|
16624
|
+
edgeUpsertTarget: this.db.prepare(`SELECT rowid AS rid, ${EDGE_UPSERT_COLUMNS.join(", ")} FROM edges WHERE from_id = ? AND type = ? AND to_id = ?`),
|
|
16625
|
+
getNode: this.db.prepare("SELECT nodes.*, (SELECT x.attrs_json FROM node_exposure x WHERE x.id = nodes.id) AS exposure_json FROM nodes WHERE id = ?"),
|
|
16626
|
+
exposureOf: this.db.prepare("SELECT attrs_json FROM node_exposure WHERE id = ?"),
|
|
16627
|
+
writeExposure: this.db.prepare("INSERT INTO node_exposure(id, attrs_json) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET attrs_json = excluded.attrs_json"),
|
|
16628
|
+
lastUpdatedOf: this.db.prepare("SELECT last_updated_at AS t, attrs_json FROM nodes WHERE id = ?"),
|
|
16629
|
+
labelOf: this.db.prepare("SELECT label FROM nodes WHERE id = ?"),
|
|
16630
|
+
nodeContent: this.db.prepare(
|
|
16631
|
+
"SELECT label, description, extraction_confidence, extraction_source, stability, valid_to, attrs_json FROM nodes WHERE id = ?"
|
|
16632
|
+
),
|
|
16633
|
+
edgeContent: this.db.prepare(
|
|
16634
|
+
"SELECT confidence, valid_to, attrs_json FROM edges WHERE from_id = ? AND type = ? AND to_id = ?"
|
|
16635
|
+
),
|
|
15439
16636
|
// Single-column fast paths for the nightly pass — writing 100k pageRank
|
|
15440
16637
|
// values through the 18-column COALESCE updateNode took ~30s; these are ~10x
|
|
15441
16638
|
// cheaper (one bound column, no COALESCE evaluation per row).
|
|
@@ -15592,6 +16789,7 @@ var SqliteGraphStore = class {
|
|
|
15592
16789
|
"INSERT INTO store_meta (key, value) VALUES ('ingest_seq', :v) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
15593
16790
|
)
|
|
15594
16791
|
};
|
|
16792
|
+
this.moveExposureToSideRows();
|
|
15595
16793
|
const seqRow = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
|
|
15596
16794
|
this.ingestSeq = seqRow ? Number(seqRow.value) : 0;
|
|
15597
16795
|
}
|
|
@@ -15607,6 +16805,93 @@ var SqliteGraphStore = class {
|
|
|
15607
16805
|
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
15608
16806
|
`).all();
|
|
15609
16807
|
}
|
|
16808
|
+
strandedSymbolsForFiles(fileIds) {
|
|
16809
|
+
const out = [];
|
|
16810
|
+
for (let i = 0; i < fileIds.length; i += 500) {
|
|
16811
|
+
const slice = fileIds.slice(i, i + 500);
|
|
16812
|
+
if (slice.length === 0) continue;
|
|
16813
|
+
out.push(...this.db.prepare(`
|
|
16814
|
+
SELECT f.id AS fileId, e.to_id AS symbolId
|
|
16815
|
+
FROM nodes f CROSS JOIN edges e ON e.from_id = f.id
|
|
16816
|
+
LEFT JOIN nodes t ON t.id = e.to_id
|
|
16817
|
+
WHERE f.id IN (${slice.map(() => "?").join(",")}) AND f.label = 'File' AND f.valid_to IS NULL AND e.valid_to IS NULL
|
|
16818
|
+
AND e.type IN ('DEFINES', 'CONTAINS')
|
|
16819
|
+
AND (t.id IS NULL OR t.valid_to IS NOT NULL)
|
|
16820
|
+
`).all(...slice));
|
|
16821
|
+
}
|
|
16822
|
+
return out;
|
|
16823
|
+
}
|
|
16824
|
+
firstLiveByNameOrQname(labels, name) {
|
|
16825
|
+
if (labels.length === 0) return null;
|
|
16826
|
+
const inLabels = labels.map(() => "?").join(",");
|
|
16827
|
+
const probe = (index, match) => this.db.prepare(
|
|
16828
|
+
`SELECT rowid AS rid, created_at AS at, label, description, json_extract(attrs_json, '$.qname') AS qname FROM nodes INDEXED BY ${index}
|
|
16829
|
+
WHERE ${match} = ? AND label IN (${inLabels}) AND valid_to IS NULL`
|
|
16830
|
+
).all(name, ...labels);
|
|
16831
|
+
const rank = (r) => [labels.indexOf(r.label), r.at, r.rid];
|
|
16832
|
+
const before = (a, b) => a[0] < b[0] || a[0] === b[0] && (a[1] < b[1] || a[1] === b[1] && a[2] < b[2]);
|
|
16833
|
+
let best;
|
|
16834
|
+
for (const r of [...probe("nodes_live_description", "description"), ...probe("nodes_live_qname", "json_extract(attrs_json, '$.qname')")]) {
|
|
16835
|
+
if (!best || before(rank(r), rank(best))) best = r;
|
|
16836
|
+
}
|
|
16837
|
+
return best ? { label: best.label, description: best.description, qname: typeof best.qname === "string" ? best.qname : null } : null;
|
|
16838
|
+
}
|
|
16839
|
+
instanceGateRows(label) {
|
|
16840
|
+
return this.db.prepare(
|
|
16841
|
+
`SELECT id, json_extract(attrs_json, '$.source') AS source, json_extract(attrs_json, '$.privateFinding') AS privateFinding,
|
|
16842
|
+
json_extract(attrs_json, '$.resolvedAs') AS resolvedAs
|
|
16843
|
+
FROM nodes INDEXED BY nodes_live_instance_gate
|
|
16844
|
+
WHERE label IN ('Problem', 'Vulnerability', 'Solution', 'RootCause') AND label = ? AND valid_to IS NULL
|
|
16845
|
+
ORDER BY label, created_at, rowid`
|
|
16846
|
+
).all(label);
|
|
16847
|
+
}
|
|
16848
|
+
wmCalibrationRows(labels) {
|
|
16849
|
+
const counted = this.db.prepare(
|
|
16850
|
+
`SELECT rowid AS rid, logical_id, version, valid_to, label, ${WM_CALIBRATION_KEYS.map((k, i) => `${wmCounterSql(k)} AS c${i}`).join(", ")}
|
|
16851
|
+
FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`
|
|
16852
|
+
).all();
|
|
16853
|
+
const exposed = this.db.prepare(
|
|
16854
|
+
`SELECT n.rowid AS rid, n.logical_id, n.version, n.valid_to, n.label, x.attrs_json AS exposure
|
|
16855
|
+
FROM node_exposure x JOIN nodes n ON n.id = x.id`
|
|
16856
|
+
).all();
|
|
16857
|
+
const others = this.db.prepare(
|
|
16858
|
+
`SELECT rowid AS rid, logical_id, version, valid_to, label FROM nodes WHERE rowid IN (
|
|
16859
|
+
SELECT n.rowid FROM nodes AS n INDEXED BY nodes_logical
|
|
16860
|
+
WHERE n.logical_id IN (SELECT logical_id FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
|
|
16861
|
+
UNION SELECT n2.logical_id FROM node_exposure x2 JOIN nodes n2 ON n2.id = x2.id)
|
|
16862
|
+
EXCEPT SELECT rowid FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}
|
|
16863
|
+
EXCEPT SELECT n3.rowid FROM node_exposure x3 JOIN nodes n3 ON n3.id = x3.id)`
|
|
16864
|
+
).all();
|
|
16865
|
+
const byRid = /* @__PURE__ */ new Map();
|
|
16866
|
+
for (const r of [...counted, ...others]) {
|
|
16867
|
+
const attrs = {};
|
|
16868
|
+
WM_CALIBRATION_KEYS.forEach((k, i) => {
|
|
16869
|
+
const v = r[`c${i}`];
|
|
16870
|
+
if (typeof v === "number") attrs[k] = v;
|
|
16871
|
+
});
|
|
16872
|
+
byRid.set(r.rid, { row: r, attrs });
|
|
16873
|
+
}
|
|
16874
|
+
for (const r of exposed) {
|
|
16875
|
+
const side = JSON.parse(r.exposure);
|
|
16876
|
+
const entry = byRid.get(r.rid) ?? { row: r, attrs: {} };
|
|
16877
|
+
for (const k of WM_CALIBRATION_KEYS) if (typeof side[k] === "number" && Number.isFinite(side[k])) entry.attrs[k] = side[k];
|
|
16878
|
+
byRid.set(r.rid, entry);
|
|
16879
|
+
}
|
|
16880
|
+
const order = new Map(labels.map((l, i) => [l, i]));
|
|
16881
|
+
return [...byRid.values()].filter(({ row }) => order.has(row.label)).sort((a, b) => order.get(a.row.label) - order.get(b.row.label) || a.row.rid - b.row.rid).map(({ row, attrs }) => ({ logicalId: row.logical_id, version: Number(row.version), validTo: row.valid_to ?? null, attrs }));
|
|
16882
|
+
}
|
|
16883
|
+
liveCommentsAttachedTo(name) {
|
|
16884
|
+
return this.db.prepare(
|
|
16885
|
+
`SELECT json_extract(attrs_json, '$.kind') AS kind, json_extract(attrs_json, '$.text') AS text, description FROM nodes INDEXED BY nodes_live_attached_to
|
|
16886
|
+
WHERE label = 'Comment' AND valid_to IS NULL AND json_extract(attrs_json, '$.attachedTo') = ? ORDER BY created_at, rowid`
|
|
16887
|
+
).all(name);
|
|
16888
|
+
}
|
|
16889
|
+
strandedEdges() {
|
|
16890
|
+
return countStrandedEdges(this.db);
|
|
16891
|
+
}
|
|
16892
|
+
liveHeadConflicts() {
|
|
16893
|
+
return countLiveHeadConflicts(this.db);
|
|
16894
|
+
}
|
|
15610
16895
|
fileIndexStates(ids) {
|
|
15611
16896
|
if (ids.length === 0) return [];
|
|
15612
16897
|
if (ids.length > 500) throw new RangeError("fileIndexStates accepts at most 500 ids per read");
|
|
@@ -15705,7 +16990,7 @@ var SqliteGraphStore = class {
|
|
|
15705
16990
|
}
|
|
15706
16991
|
mergeNode(node) {
|
|
15707
16992
|
this.mutations++;
|
|
15708
|
-
|
|
16993
|
+
const values = {
|
|
15709
16994
|
id: node.id,
|
|
15710
16995
|
label: node.label,
|
|
15711
16996
|
description: node.description,
|
|
@@ -15731,8 +17016,37 @@ var SqliteGraphStore = class {
|
|
|
15731
17016
|
logical_id: node.logicalId ?? node.id,
|
|
15732
17017
|
created_at_seq: node.createdAtSeq ?? null,
|
|
15733
17018
|
last_reinforced_at_seq: node.lastReinforcedAtSeq ?? null,
|
|
15734
|
-
attrs_json:
|
|
15735
|
-
}
|
|
17019
|
+
attrs_json: this.nodeAttrsJson(node.id, node.attrs ?? {})
|
|
17020
|
+
};
|
|
17021
|
+
const current = this.stmts.nodeUpsertTarget.get(node.id);
|
|
17022
|
+
if (current) this.writeChangedColumns("nodes", NODE_UPSERT_COLUMNS, current, values);
|
|
17023
|
+
else this.stmts.insertNode.run(values);
|
|
17024
|
+
}
|
|
17025
|
+
/** What the upsert's ON CONFLICT would write over `current`, setting only the
|
|
17026
|
+
* columns whose value moves (NODE_UPSERT_COLUMNS says why). */
|
|
17027
|
+
writeChangedColumns(table, columns2, current, next) {
|
|
17028
|
+
const changed = columns2.filter((c) => !sameSqlValue(current[c], next[c]));
|
|
17029
|
+
if (changed.length === 0) return;
|
|
17030
|
+
const key = `${table}:${changed.join(",")}`;
|
|
17031
|
+
let stmt2 = this.changedColumnStmts.get(key);
|
|
17032
|
+
if (!stmt2) {
|
|
17033
|
+
stmt2 = this.db.prepare(`UPDATE ${table} SET ${changed.map((c) => `${c} = :${c}`).join(", ")} WHERE rowid = :rid`);
|
|
17034
|
+
this.changedColumnStmts.set(key, stmt2);
|
|
17035
|
+
}
|
|
17036
|
+
stmt2.run({ rid: current["rid"], ...Object.fromEntries(changed.map((c) => [c, next[c]])) });
|
|
17037
|
+
}
|
|
17038
|
+
/** mergeNode, unless the live row already holds this content. Content is what
|
|
17039
|
+
* the columns a merge rewrites from the producer (label, description, attrs,
|
|
17040
|
+
* confidence, stability);
|
|
17041
|
+
* rank, community, landmark, surprise, hits, tier and the vector belong to
|
|
17042
|
+
* the passes that compute them, and a skipped write leaves them as they are
|
|
17043
|
+
* where a merge would have reset them to the producer's defaults. Returns
|
|
17044
|
+
* whether it wrote. */
|
|
17045
|
+
mergeNodeIfChanged(node) {
|
|
17046
|
+
const row = this.stmts.nodeContent.get(node.id);
|
|
17047
|
+
if (row && row.valid_to === null && row.label === node.label && row.description === node.description && row.extraction_confidence === node.extractionConfidence && row.stability === node.stability && !EXPOSURE_ATTRS.some((k) => node.attrs?.[k] !== void 0) && row.attrs_json === JSON.stringify(node.attrs ?? {})) return false;
|
|
17048
|
+
this.mergeNode(node);
|
|
17049
|
+
return true;
|
|
15736
17050
|
}
|
|
15737
17051
|
updateNode(id, patch) {
|
|
15738
17052
|
const hasEmbedding = patch.embedding !== void 0 && patch.embedding.length > 0;
|
|
@@ -15757,7 +17071,37 @@ var SqliteGraphStore = class {
|
|
|
15757
17071
|
logical_id: patch.logicalId ?? null,
|
|
15758
17072
|
created_at_seq: patch.createdAtSeq ?? null,
|
|
15759
17073
|
last_reinforced_at_seq: patch.lastReinforcedAtSeq ?? null,
|
|
15760
|
-
attrs_json: patch.attrs ?
|
|
17074
|
+
attrs_json: patch.attrs ? this.nodeAttrsJson(id, patch.attrs) : null
|
|
17075
|
+
});
|
|
17076
|
+
}
|
|
17077
|
+
/**
|
|
17078
|
+
* The node-row attrs of a write, after handing any exposure keys to the side
|
|
17079
|
+
* row (exposure-attrs.ts). `getNode` returns exposure merged into attrs, so a
|
|
17080
|
+
* read-modify-write carries it; kept in the row it would re-bloat the row and
|
|
17081
|
+
* drift from the side row. Unchanged exposure costs no write.
|
|
17082
|
+
*/
|
|
17083
|
+
nodeAttrsJson(id, attrs) {
|
|
17084
|
+
if (!EXPOSURE_ATTRS.some((k) => attrs[k] !== void 0)) return JSON.stringify(attrs);
|
|
17085
|
+
const current = this.stmts.exposureOf.get(id);
|
|
17086
|
+
const next = JSON.stringify({ ...current ? JSON.parse(current.attrs_json) : {}, ...pickExposure(attrs) });
|
|
17087
|
+
if (current?.attrs_json !== next) this.stmts.writeExposure.run(id, next);
|
|
17088
|
+
const rest = { ...attrs };
|
|
17089
|
+
for (const k of EXPOSURE_ATTRS) delete rest[k];
|
|
17090
|
+
return JSON.stringify(rest);
|
|
17091
|
+
}
|
|
17092
|
+
/** One-time: exposure keys written to node rows before the side table existed
|
|
17093
|
+
* move to it. Only rows carrying a counter can carry exposure, and those are
|
|
17094
|
+
* what nodes_wm_counted holds, so this reads the index, not the table. */
|
|
17095
|
+
moveExposureToSideRows() {
|
|
17096
|
+
if (this.db.prepare("SELECT value FROM store_meta WHERE key = ?").get(EXPOSURE_SIDE_KEY)) return;
|
|
17097
|
+
const rows = this.db.prepare(`SELECT id, attrs_json FROM nodes INDEXED BY nodes_wm_counted WHERE ${WM_COUNTED_PREDICATE}`).all();
|
|
17098
|
+
const move = this.db.prepare("UPDATE nodes SET attrs_json = ? WHERE id = ?");
|
|
17099
|
+
this.db.transaction(() => {
|
|
17100
|
+
for (const r of rows) {
|
|
17101
|
+
const stripped = this.nodeAttrsJson(r.id, JSON.parse(r.attrs_json));
|
|
17102
|
+
if (stripped !== r.attrs_json) move.run(stripped, r.id);
|
|
17103
|
+
}
|
|
17104
|
+
this.db.prepare("INSERT OR REPLACE INTO store_meta(key, value) VALUES (?, 'done')").run(EXPOSURE_SIDE_KEY);
|
|
15761
17105
|
});
|
|
15762
17106
|
}
|
|
15763
17107
|
/** Fast single-column writes for the nightly pass (avoid the 18-column COALESCE
|
|
@@ -15768,26 +17112,30 @@ var SqliteGraphStore = class {
|
|
|
15768
17112
|
setLandmark(id, isLandmark) {
|
|
15769
17113
|
this.stmts.setLandmark.run({ id, v: isLandmark ? 1 : 0 });
|
|
15770
17114
|
}
|
|
15771
|
-
setLocalCommunity(id, community,
|
|
15772
|
-
this.stmts.setLocalCommunity.run({ id, v: community, col:
|
|
17115
|
+
setLocalCommunity(id, community, column2) {
|
|
17116
|
+
this.stmts.setLocalCommunity.run({ id, v: community, col: column2 ? 1 : 0 });
|
|
15773
17117
|
}
|
|
15774
17118
|
fieldStmts = /* @__PURE__ */ new Map();
|
|
15775
17119
|
findNodeFieldsByLabel(label, attrKeys) {
|
|
15776
17120
|
for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeFieldsByLabel: bad attr key ${k}`);
|
|
15777
17121
|
const cacheKey = attrKeys.join(",");
|
|
15778
|
-
let
|
|
15779
|
-
if (!
|
|
15780
|
-
const cols = attrKeys.map((k, i) => `,
|
|
15781
|
-
|
|
15782
|
-
|
|
17122
|
+
let stmt2 = this.fieldStmts.get(cacheKey);
|
|
17123
|
+
if (!stmt2) {
|
|
17124
|
+
const cols = attrKeys.map((k, i) => `, ${attrValueSql(`'$.${k}'`)} AS a${i}, json_type(attrs_json, '$.${k}') AS t${i}`).join("");
|
|
17125
|
+
stmt2 = this.db.prepare(
|
|
17126
|
+
// Ordered, not left to the planner: readers take "first row wins" (the
|
|
17127
|
+
// symbol lexicon's name winner and doc pick), and the order otherwise
|
|
17128
|
+
// followed whichever partial index the planner chose. nodes_live_created
|
|
17129
|
+
// serves this order without a sort.
|
|
17130
|
+
`SELECT id, description, community, extraction_confidence${cols} FROM nodes WHERE label = ? AND valid_to IS NULL ORDER BY created_at, rowid`
|
|
15783
17131
|
);
|
|
15784
|
-
this.fieldStmts.set(cacheKey,
|
|
17132
|
+
this.fieldStmts.set(cacheKey, stmt2);
|
|
15785
17133
|
}
|
|
15786
|
-
return
|
|
17134
|
+
return stmt2.all(label).map((r) => {
|
|
15787
17135
|
const attrs = {};
|
|
15788
17136
|
attrKeys.forEach((k, i) => {
|
|
15789
|
-
const v = r[`a${i}`];
|
|
15790
|
-
if (v !==
|
|
17137
|
+
const v = decodeAttr(r[`a${i}`], r[`t${i}`]);
|
|
17138
|
+
if (v !== void 0) attrs[k] = v;
|
|
15791
17139
|
});
|
|
15792
17140
|
return {
|
|
15793
17141
|
id: r["id"],
|
|
@@ -15798,6 +17146,15 @@ var SqliteGraphStore = class {
|
|
|
15798
17146
|
};
|
|
15799
17147
|
});
|
|
15800
17148
|
}
|
|
17149
|
+
selectNodes(q) {
|
|
17150
|
+
const { sql, params } = compileNodeSelect(q);
|
|
17151
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
17152
|
+
return rows.map((r) => decodeSelectedRow(r, q.attrs ?? [], q.withEmbedding ? decodeEmbedding : void 0));
|
|
17153
|
+
}
|
|
17154
|
+
countNodes(q) {
|
|
17155
|
+
const { where, params } = compileNodeWhere(q);
|
|
17156
|
+
return Number(this.db.prepare(`SELECT COUNT(*) AS n FROM ${nodeSelectFrom(q)} WHERE ${where}`).get(...params).n);
|
|
17157
|
+
}
|
|
15801
17158
|
setCommunity(id, community) {
|
|
15802
17159
|
this.stmts.setCommunity.run({ id, v: community });
|
|
15803
17160
|
}
|
|
@@ -15810,7 +17167,7 @@ var SqliteGraphStore = class {
|
|
|
15810
17167
|
return;
|
|
15811
17168
|
}
|
|
15812
17169
|
this.mutations++;
|
|
15813
|
-
|
|
17170
|
+
const values = {
|
|
15814
17171
|
id: edge.id,
|
|
15815
17172
|
from_id: edge.from,
|
|
15816
17173
|
to_id: edge.to,
|
|
@@ -15824,8 +17181,33 @@ var SqliteGraphStore = class {
|
|
|
15824
17181
|
valid_from: edge.validFrom ?? edge.createdAt,
|
|
15825
17182
|
valid_to: edge.validTo ?? null,
|
|
15826
17183
|
attrs_json: JSON.stringify(edge.attrs ?? {})
|
|
17184
|
+
};
|
|
17185
|
+
const current = this.stmts.edgeUpsertTarget.get(edge.from, edge.type, edge.to);
|
|
17186
|
+
if (!current) {
|
|
17187
|
+
this.stmts.insertEdge.run(values);
|
|
17188
|
+
return;
|
|
17189
|
+
}
|
|
17190
|
+
this.writeChangedColumns("edges", EDGE_UPSERT_COLUMNS, current, {
|
|
17191
|
+
...values,
|
|
17192
|
+
confidence: Math.max(current["confidence"], edge.confidence),
|
|
17193
|
+
valid_to: null
|
|
15827
17194
|
});
|
|
15828
17195
|
}
|
|
17196
|
+
/** mergeEdge, unless the live edge already holds this content: same attrs, and
|
|
17197
|
+
* a confidence the upsert's MAX would keep. Its navigation posteriors and
|
|
17198
|
+
* last-seen stamp stay as they are where a merge would have reset them. The
|
|
17199
|
+
* ontology gate runs first either way, so refusals are recorded as before.
|
|
17200
|
+
* Returns whether it wrote (or refused). */
|
|
17201
|
+
mergeEdgeIfChanged(edge) {
|
|
17202
|
+
if (!this.edgeRuleViolation(edge)) {
|
|
17203
|
+
const row = this.stmts.edgeContent.get(edge.from, edge.type, edge.to);
|
|
17204
|
+
if (row && row.valid_to === null && row.confidence >= edge.confidence && row.attrs_json === JSON.stringify(edge.attrs ?? {})) {
|
|
17205
|
+
return false;
|
|
17206
|
+
}
|
|
17207
|
+
}
|
|
17208
|
+
this.mergeEdge(edge);
|
|
17209
|
+
return true;
|
|
17210
|
+
}
|
|
15829
17211
|
/** EDGE_RULES check against the stored endpoints' labels, with the LOCAL
|
|
15830
17212
|
* overlay consulted first. Returns the reason string on a documented-
|
|
15831
17213
|
* forbidden combination, else null. Point lookups on the id PK — negligible
|
|
@@ -15870,11 +17252,13 @@ var SqliteGraphStore = class {
|
|
|
15870
17252
|
}
|
|
15871
17253
|
}
|
|
15872
17254
|
edgeRuleViolation(edge) {
|
|
15873
|
-
return localEdgeViolation(
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
17255
|
+
return localEdgeViolation(this.labelOf(edge.from), edge.type, this.labelOf(edge.to));
|
|
17256
|
+
}
|
|
17257
|
+
/** An endpoint's label, any version, as `getNode(id)?.label` reads it. Every
|
|
17258
|
+
* edge write checks both endpoints, and hydrating them (attrs + vector) to
|
|
17259
|
+
* read one leading column was most of an incremental reindex's reads. */
|
|
17260
|
+
labelOf(id) {
|
|
17261
|
+
return this.stmts.labelOf.get(id)?.label;
|
|
15878
17262
|
}
|
|
15879
17263
|
updateEdge(id, patch) {
|
|
15880
17264
|
this.stmts.updateEdge.run({
|
|
@@ -15890,18 +17274,38 @@ var SqliteGraphStore = class {
|
|
|
15890
17274
|
}
|
|
15891
17275
|
getNode(id) {
|
|
15892
17276
|
const row = this.stmts.getNode.get(id);
|
|
15893
|
-
|
|
17277
|
+
if (!row) return null;
|
|
17278
|
+
const node = rowToNode(row);
|
|
17279
|
+
if (row.exposure_json) Object.assign(node.attrs, JSON.parse(row.exposure_json));
|
|
17280
|
+
return node;
|
|
17281
|
+
}
|
|
17282
|
+
/** A node's render exposure: the side row's keys over the node row's own
|
|
17283
|
+
* (exposure-attrs.ts), plus the node's `lastUpdatedAt` (the "shown since
|
|
17284
|
+
* touch" counter is relative to it) — without reading the node's attrs. */
|
|
17285
|
+
exposureOf(id) {
|
|
17286
|
+
const row = this.stmts.lastUpdatedOf.get(id);
|
|
17287
|
+
if (!row) return null;
|
|
17288
|
+
const side = this.stmts.exposureOf.get(id);
|
|
17289
|
+
return {
|
|
17290
|
+
attrs: { ...pickExposure(JSON.parse(row.attrs_json)), ...side ? JSON.parse(side.attrs_json) : {} },
|
|
17291
|
+
lastUpdatedAt: row.t
|
|
17292
|
+
};
|
|
17293
|
+
}
|
|
17294
|
+
/** Replace a node's exposure (only EXPOSURE_ATTRS keys belong here). Touches
|
|
17295
|
+
* neither the node row nor its indexes, nor the change feed. */
|
|
17296
|
+
writeExposure(id, attrs) {
|
|
17297
|
+
this.stmts.writeExposure.run(id, JSON.stringify(attrs));
|
|
15894
17298
|
}
|
|
15895
17299
|
nodeLabels(ids) {
|
|
15896
17300
|
const width = NODE_LABELS_CHUNK;
|
|
15897
|
-
const
|
|
17301
|
+
const stmt2 = this.typedEdgeStmt(
|
|
15898
17302
|
`SELECT id, label FROM nodes WHERE id IN (${Array.from({ length: width }, () => "?").join(",")})`
|
|
15899
17303
|
);
|
|
15900
17304
|
const labels = /* @__PURE__ */ new Map();
|
|
15901
17305
|
for (let i = 0; i < ids.length; i += width) {
|
|
15902
17306
|
const chunk = ids.slice(i, i + width);
|
|
15903
17307
|
while (chunk.length < width) chunk.push(null);
|
|
15904
|
-
for (const row of
|
|
17308
|
+
for (const row of stmt2.all(...chunk)) labels.set(row.id, row.label);
|
|
15905
17309
|
}
|
|
15906
17310
|
return labels;
|
|
15907
17311
|
}
|
|
@@ -15918,7 +17322,21 @@ var SqliteGraphStore = class {
|
|
|
15918
17322
|
* per project per request; the materializing form held the daemon's event
|
|
15919
17323
|
* loop for seconds at scale. */
|
|
15920
17324
|
countNodesByLabel(label) {
|
|
15921
|
-
return Number(this.stmts.countByLabel.get(label).n);
|
|
17325
|
+
return this.countAt(`label:${label}`, [nodeScope(label)], () => Number(this.stmts.countByLabel.get(label).n));
|
|
17326
|
+
}
|
|
17327
|
+
/** Counts asked for on every status poll (/health, `errata status`, the
|
|
17328
|
+
* dashboards): a live-node COUNT(*) reads the whole liveness index, ~3 MB per
|
|
17329
|
+
* call on a 1.1 GB store, to recount a set that moves only on a write. Each is
|
|
17330
|
+
* kept with the change-feed reading of the rows it counts — triggers bump it on
|
|
17331
|
+
* every write from any connection, so a hit is exact, not a TTL guess. */
|
|
17332
|
+
counts = /* @__PURE__ */ new Map();
|
|
17333
|
+
countAt(key, scopes, count) {
|
|
17334
|
+
const seq = readChangeSeq(this.db, scopes);
|
|
17335
|
+
const hit = this.counts.get(key);
|
|
17336
|
+
if (hit && hit.seq === seq) return hit.n;
|
|
17337
|
+
const n = count();
|
|
17338
|
+
this.counts.set(key, { seq, n });
|
|
17339
|
+
return n;
|
|
15922
17340
|
}
|
|
15923
17341
|
/** Live rows for a label with attrs and creation stamps only — for scans that
|
|
15924
17342
|
* read attrs and never the vector. `findNodesByLabel` decodes every embedding
|
|
@@ -15927,16 +17345,39 @@ var SqliteGraphStore = class {
|
|
|
15927
17345
|
findNodeStampsByLabel(label, attrKey) {
|
|
15928
17346
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attrKey)) throw new Error(`findNodeStampsByLabel: bad attr key ${attrKey}`);
|
|
15929
17347
|
const rows = this.db.prepare(
|
|
15930
|
-
`SELECT id, created_at, created_at_seq, json_type(
|
|
17348
|
+
`SELECT id, created_at, created_at_seq, json_type(${attrDocSql(attrKey)}, '$.${attrKey}') IS NOT NULL AS has,
|
|
17349
|
+
COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
|
|
15931
17350
|
FROM nodes WHERE label = ? AND valid_to IS NULL`
|
|
15932
17351
|
).all(label);
|
|
15933
17352
|
return rows.map((r) => ({
|
|
15934
17353
|
id: r.id,
|
|
15935
17354
|
createdAt: r.created_at,
|
|
15936
17355
|
...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
|
|
15937
|
-
has: r.has === 1
|
|
17356
|
+
has: r.has === 1,
|
|
17357
|
+
cloud: r.cloud === 1
|
|
15938
17358
|
}));
|
|
15939
17359
|
}
|
|
17360
|
+
findNodeStampsByLabelAttrs(label, attrKeys) {
|
|
17361
|
+
for (const k of attrKeys) if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) throw new Error(`findNodeStampsByLabelAttrs: bad attr key ${k}`);
|
|
17362
|
+
const keys = [...new Set(attrKeys)];
|
|
17363
|
+
const rows = this.db.prepare(
|
|
17364
|
+
`SELECT id, created_at, created_at_seq,
|
|
17365
|
+
${keys.map((k, i) => `json_type(${attrDocSql(k)}, '$.${k}') IS NOT NULL AS h${i}`).join(", ")}${keys.length ? "," : ""}
|
|
17366
|
+
COALESCE(json_extract(attrs_json, '$.source') = 'cloud', 0) AS cloud
|
|
17367
|
+
FROM nodes WHERE label = ? AND valid_to IS NULL`
|
|
17368
|
+
).all(label);
|
|
17369
|
+
const out = new Map(keys.map((k) => [k, []]));
|
|
17370
|
+
for (const r of rows) {
|
|
17371
|
+
keys.forEach((k, i) => out.get(k).push({
|
|
17372
|
+
id: r.id,
|
|
17373
|
+
createdAt: r.created_at,
|
|
17374
|
+
...r.created_at_seq != null ? { createdAtSeq: r.created_at_seq } : {},
|
|
17375
|
+
has: r[`h${i}`] === 1,
|
|
17376
|
+
cloud: r.cloud === 1
|
|
17377
|
+
}));
|
|
17378
|
+
}
|
|
17379
|
+
return out;
|
|
17380
|
+
}
|
|
15940
17381
|
findNodeAttrsByLabel(label) {
|
|
15941
17382
|
const rows = this.stmts.attrsByLabel.all(label);
|
|
15942
17383
|
return rows.map((r) => ({
|
|
@@ -15964,8 +17405,8 @@ var SqliteGraphStore = class {
|
|
|
15964
17405
|
after = rows[rows.length - 1].rid;
|
|
15965
17406
|
}
|
|
15966
17407
|
}
|
|
15967
|
-
|
|
15968
|
-
const rows = this.db.prepare("SELECT id FROM nodes WHERE label =
|
|
17408
|
+
recentProblemIds(limit) {
|
|
17409
|
+
const rows = this.db.prepare("SELECT id FROM nodes WHERE label = 'Problem' AND valid_to IS NULL ORDER BY last_updated_at DESC LIMIT ?").all(Math.max(0, limit));
|
|
15969
17410
|
return rows.map((r) => r.id);
|
|
15970
17411
|
}
|
|
15971
17412
|
recentOpenProblemCandidates(opts) {
|
|
@@ -16045,6 +17486,20 @@ var SqliteGraphStore = class {
|
|
|
16045
17486
|
}
|
|
16046
17487
|
searchTextCandidates(terms) {
|
|
16047
17488
|
if (terms.length === 0) return [];
|
|
17489
|
+
const indexed = terms.filter((t) => t.length >= TEXT_INDEX_MIN_TERM);
|
|
17490
|
+
if (indexed.length > 0 && textIndexReady(this.db)) {
|
|
17491
|
+
const short = terms.filter((t) => t.length < TEXT_INDEX_MIN_TERM).map((t) => t.toLowerCase());
|
|
17492
|
+
const rows2 = this.db.prepare(`WITH hits AS MATERIALIZED (
|
|
17493
|
+
SELECT t.rowid AS rid, COUNT(*) AS matched FROM json_each(:terms) q, nodes_text t
|
|
17494
|
+
WHERE t.description LIKE '%' || q.value || '%' GROUP BY t.rowid
|
|
17495
|
+
) SELECT ${TEXT_SEARCH_COLUMNS}, hits.matched FROM hits JOIN nodes ON nodes.rowid = hits.rid
|
|
17496
|
+
WHERE nodes.valid_to IS NULL ORDER BY page_rank DESC, last_updated_at DESC`).all({ terms: JSON.stringify(indexed) });
|
|
17497
|
+
return rows2.map((row) => {
|
|
17498
|
+
const text = row.description.toLowerCase();
|
|
17499
|
+
const extra = short.filter((t) => text.includes(t)).length;
|
|
17500
|
+
return { node: textSearchNode(row), matched: row.matched + extra };
|
|
17501
|
+
});
|
|
17502
|
+
}
|
|
16048
17503
|
const rows = this.db.prepare(`WITH matches AS MATERIALIZED (
|
|
16049
17504
|
SELECT n.id, COUNT(*) AS matched FROM nodes n INDEXED BY nodes_live_description, json_each(:terms) t
|
|
16050
17505
|
WHERE n.valid_to IS NULL AND n.description LIKE '%' || t.value || '%'
|
|
@@ -16053,6 +17508,18 @@ var SqliteGraphStore = class {
|
|
|
16053
17508
|
JOIN matches USING (id) ORDER BY page_rank DESC, last_updated_at DESC`).all({ terms: JSON.stringify(terms) });
|
|
16054
17509
|
return rows.map((row) => ({ node: textSearchNode(row), matched: row.matched }));
|
|
16055
17510
|
}
|
|
17511
|
+
commentAnchors(commentIds) {
|
|
17512
|
+
const out = /* @__PURE__ */ new Map();
|
|
17513
|
+
if (commentIds.length === 0) return out;
|
|
17514
|
+
const rows = this.db.prepare(`WITH a AS MATERIALIZED (
|
|
17515
|
+
SELECT q.value AS cid, COALESCE(
|
|
17516
|
+
(SELECT to_id FROM edges WHERE from_id = q.value AND type IN ('EXPLAINS', 'ANNOTATES') AND valid_to IS NULL LIMIT 1),
|
|
17517
|
+
(SELECT from_id FROM edges WHERE to_id = q.value AND type = 'DOCUMENTED_BY' AND valid_to IS NULL LIMIT 1)
|
|
17518
|
+
) AS aid FROM json_each(:ids) q
|
|
17519
|
+
) SELECT a.cid AS cid, ${TEXT_SEARCH_COLUMNS} FROM a JOIN nodes ON nodes.id = a.aid AND nodes.valid_to IS NULL`).all({ ids: JSON.stringify(commentIds) });
|
|
17520
|
+
for (const row of rows) out.set(row.cid, textSearchNode(row));
|
|
17521
|
+
return out;
|
|
17522
|
+
}
|
|
16056
17523
|
getTextSearchNode(id) {
|
|
16057
17524
|
const row = this.db.prepare(`SELECT ${TEXT_SEARCH_COLUMNS} FROM nodes WHERE id = ? AND valid_to IS NULL`).get(id);
|
|
16058
17525
|
return row ? textSearchNode(row) : null;
|
|
@@ -16072,12 +17539,12 @@ var SqliteGraphStore = class {
|
|
|
16072
17539
|
return scale;
|
|
16073
17540
|
}
|
|
16074
17541
|
typedEdgeStmt(sql) {
|
|
16075
|
-
let
|
|
16076
|
-
if (!
|
|
16077
|
-
|
|
16078
|
-
this.typedEdgeStmts.set(sql,
|
|
17542
|
+
let stmt2 = this.typedEdgeStmts.get(sql);
|
|
17543
|
+
if (!stmt2) {
|
|
17544
|
+
stmt2 = this.db.prepare(sql);
|
|
17545
|
+
this.typedEdgeStmts.set(sql, stmt2);
|
|
16079
17546
|
}
|
|
16080
|
-
return
|
|
17547
|
+
return stmt2;
|
|
16081
17548
|
}
|
|
16082
17549
|
outEdges(nodeId, types) {
|
|
16083
17550
|
if (!types || types.length === 0) {
|
|
@@ -16136,21 +17603,54 @@ var SqliteGraphStore = class {
|
|
|
16136
17603
|
return out;
|
|
16137
17604
|
}
|
|
16138
17605
|
nodeCount() {
|
|
16139
|
-
return Number(this.stmts.nodeCount.get().n);
|
|
17606
|
+
return this.countAt("nodes", [ANY_CHANGE], () => Number(this.stmts.nodeCount.get().n));
|
|
16140
17607
|
}
|
|
16141
17608
|
mutationCount() {
|
|
16142
17609
|
return this.mutations;
|
|
16143
17610
|
}
|
|
17611
|
+
changeStamp() {
|
|
17612
|
+
const { data_version } = this.db.pragma("data_version");
|
|
17613
|
+
const { n } = this.db.prepare("SELECT total_changes() AS n").get();
|
|
17614
|
+
return `${data_version}:${n}`;
|
|
17615
|
+
}
|
|
17616
|
+
changeSeq(scopes) {
|
|
17617
|
+
return readChangeSeq(this.db, scopes);
|
|
17618
|
+
}
|
|
17619
|
+
lineageChangedFiles(sinceSeq) {
|
|
17620
|
+
return readLineageChangedFiles(this.db, sinceSeq);
|
|
17621
|
+
}
|
|
17622
|
+
changedNodeIds(sinceSeq, labels) {
|
|
17623
|
+
return readChangedNodeIds(this.db, sinceSeq, labels);
|
|
17624
|
+
}
|
|
17625
|
+
changedAmong(sinceSeq, ids) {
|
|
17626
|
+
return readChangedAmong(this.db, sinceSeq, ids);
|
|
17627
|
+
}
|
|
17628
|
+
pruneChangeLog() {
|
|
17629
|
+
return pruneChangeLog(this.db);
|
|
17630
|
+
}
|
|
17631
|
+
fileRowStates(ids) {
|
|
17632
|
+
if (ids.length === 0) return [];
|
|
17633
|
+
if (ids.length > 500) throw new RangeError("fileRowStates accepts at most 500 ids per read");
|
|
17634
|
+
return this.db.prepare(`SELECT id, last_updated_at AS lastUpdatedAt, valid_to AS validTo FROM nodes WHERE id IN (${ids.map(() => "?").join(",")})`).all(...ids);
|
|
17635
|
+
}
|
|
17636
|
+
// Read and advanced in SQL, never from a per-connection copy: pass workers and
|
|
17637
|
+
// MCP processes open their own connections and advance the same row, so a
|
|
17638
|
+
// copy loaded at open went stale, and `copy + 1` wrote a duplicate or a
|
|
17639
|
+
// backwards seq from whichever connection had fallen behind.
|
|
16144
17640
|
currentIngestSeq() {
|
|
17641
|
+
const row = this.db.prepare("SELECT value FROM store_meta WHERE key = 'ingest_seq'").get();
|
|
17642
|
+
this.ingestSeq = row ? Number(row.value) : 0;
|
|
16145
17643
|
return this.ingestSeq;
|
|
16146
17644
|
}
|
|
16147
17645
|
nextIngestSeq() {
|
|
16148
|
-
this.
|
|
16149
|
-
|
|
17646
|
+
const row = this.db.prepare(
|
|
17647
|
+
"INSERT INTO store_meta (key, value) VALUES ('ingest_seq', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(value AS INTEGER) + 1 RETURNING value"
|
|
17648
|
+
).get();
|
|
17649
|
+
this.ingestSeq = Number(row.value);
|
|
16150
17650
|
return this.ingestSeq;
|
|
16151
17651
|
}
|
|
16152
17652
|
edgeCount() {
|
|
16153
|
-
return Number(this.stmts.edgeCount.get().n);
|
|
17653
|
+
return this.countAt("edges", [ANY_CHANGE], () => Number(this.stmts.edgeCount.get().n));
|
|
16154
17654
|
}
|
|
16155
17655
|
// Lightweight whole-graph scans — select only the columns the caller needs so a
|
|
16156
17656
|
// 100k-node pass skips the per-row attrs JSON.parse + embedding decode rowToNode
|
|
@@ -16278,28 +17778,63 @@ var SqliteGraphStore = class {
|
|
|
16278
17778
|
}
|
|
16279
17779
|
}
|
|
16280
17780
|
reviveReobserved(relPaths, workspaceId, since) {
|
|
16281
|
-
|
|
17781
|
+
const revived = [];
|
|
16282
17782
|
const CHUNK = 400;
|
|
16283
17783
|
for (let i = 0; i < relPaths.length; i += CHUNK) {
|
|
16284
17784
|
const slice = relPaths.slice(i, i + CHUNK);
|
|
16285
17785
|
if (slice.length === 0) continue;
|
|
16286
|
-
const
|
|
16287
|
-
`
|
|
17786
|
+
const rows = this.db.prepare(
|
|
17787
|
+
`SELECT id, valid_to AS closedAt, COALESCE(logical_id, id) AS lid FROM nodes
|
|
16288
17788
|
WHERE valid_to IS NOT NULL
|
|
16289
17789
|
AND last_updated_at >= ?
|
|
16290
17790
|
AND json_extract(attrs_json, '$.workspaceId') = ?
|
|
16291
|
-
AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})
|
|
16292
|
-
|
|
16293
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
return
|
|
17791
|
+
AND json_extract(attrs_json, '$.relPath') IN (${slice.map(() => "?").join(",")})
|
|
17792
|
+
AND json_type(attrs_json, '$.historical') IS NOT 'true'`
|
|
17793
|
+
).all(since, workspaceId, ...slice);
|
|
17794
|
+
revived.push(...rows);
|
|
17795
|
+
}
|
|
17796
|
+
if (revived.length === 0) return 0;
|
|
17797
|
+
const reopen = this.db.prepare("UPDATE nodes SET valid_to = NULL WHERE id = ? AND valid_to IS NOT NULL");
|
|
17798
|
+
for (const r of revived) reopen.run(r.id);
|
|
17799
|
+
for (const r of revived) reopenCascadeClosedEdges(this.db, r.id, Number(r.closedAt));
|
|
17800
|
+
collapseLiveHeads(this.db, revived.map((r) => r.lid), since);
|
|
17801
|
+
this.mutations++;
|
|
17802
|
+
return revived.length;
|
|
17803
|
+
}
|
|
17804
|
+
incomingEdgeCoverage(label, edgeTypes, cloudOnly) {
|
|
17805
|
+
if (edgeTypes.length === 0) return { count: 0, total: 0 };
|
|
17806
|
+
const r = this.db.prepare(
|
|
17807
|
+
`SELECT COUNT(*) AS total,
|
|
17808
|
+
COALESCE(SUM(CASE WHEN EXISTS (
|
|
17809
|
+
SELECT 1 FROM edges e WHERE e.to_id = n.id AND e.valid_to IS NULL
|
|
17810
|
+
AND e.type IN (${edgeTypes.map(() => "?").join(",")})
|
|
17811
|
+
) THEN 1 ELSE 0 END), 0) AS count
|
|
17812
|
+
FROM nodes n
|
|
17813
|
+
WHERE n.label = ? AND n.valid_to IS NULL
|
|
17814
|
+
AND (? = 0 OR json_extract(n.attrs_json, '$.source') = 'cloud')`
|
|
17815
|
+
).get(...edgeTypes, label, cloudOnly ? 1 : 0);
|
|
17816
|
+
return { count: Number(r.count), total: Number(r.total) };
|
|
17817
|
+
}
|
|
17818
|
+
liveEdgeTargets(edgeTypes) {
|
|
17819
|
+
if (edgeTypes.length === 0) return [];
|
|
17820
|
+
const rows = this.db.prepare(`SELECT DISTINCT to_id FROM edges INDEXED BY edges_type WHERE type IN (${edgeTypes.map(() => "?").join(",")}) AND valid_to IS NULL`).all(...edgeTypes);
|
|
17821
|
+
return rows.map((r) => r.to_id);
|
|
16297
17822
|
}
|
|
16298
17823
|
recentEdgeAttrCoverage(marker, attr, limit) {
|
|
17824
|
+
if (marker === PRIOR_TAG_MARKER) {
|
|
17825
|
+
const r2 = this.db.prepare(
|
|
17826
|
+
`SELECT COUNT(*) AS total,
|
|
17827
|
+
COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
|
|
17828
|
+
FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_prior_tag
|
|
17829
|
+
WHERE valid_to IS NULL AND json_extract(attrs_json, '$.priorTag') = 1
|
|
17830
|
+
ORDER BY created_at DESC LIMIT ?)`
|
|
17831
|
+
).get(attr, limit);
|
|
17832
|
+
return { count: Number(r2.count), total: Number(r2.total) };
|
|
17833
|
+
}
|
|
16299
17834
|
const r = this.db.prepare(
|
|
16300
17835
|
`SELECT COUNT(*) AS total,
|
|
16301
17836
|
COALESCE(SUM(CASE WHEN json_extract(attrs_json, '$.' || ?) = 1 THEN 1 ELSE 0 END), 0) AS count
|
|
16302
|
-
FROM (SELECT attrs_json FROM edges
|
|
17837
|
+
FROM (SELECT attrs_json FROM edges INDEXED BY edges_live_created
|
|
16303
17838
|
WHERE valid_to IS NULL AND attrs_json LIKE ?
|
|
16304
17839
|
ORDER BY created_at DESC LIMIT ?)`
|
|
16305
17840
|
).get(attr, `%${marker}%`, limit);
|
|
@@ -16312,7 +17847,7 @@ var SqliteGraphStore = class {
|
|
|
16312
17847
|
const slice = cloudIds.slice(i, i + CHUNK);
|
|
16313
17848
|
if (slice.length === 0) continue;
|
|
16314
17849
|
const rows = this.db.prepare(
|
|
16315
|
-
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY
|
|
17850
|
+
`SELECT id, json_extract(attrs_json, '$.cloudNodeId') AS cloud FROM nodes INDEXED BY nodes_cloud_node_id_v2 WHERE json_extract(attrs_json, '$.cloudNodeId') IN (${slice.map(() => "?").join(",")}) AND valid_to IS NULL ORDER BY id`
|
|
16316
17851
|
).all(...slice);
|
|
16317
17852
|
for (const r of rows) out.set(r.cloud, [...out.get(r.cloud) ?? [], r.id]);
|
|
16318
17853
|
}
|
|
@@ -16425,8 +17960,9 @@ var SqliteGraphStore = class {
|
|
|
16425
17960
|
this.stmts.advanceLive.run({ live_id: liveId, t });
|
|
16426
17961
|
return frozenId;
|
|
16427
17962
|
}
|
|
16428
|
-
closeNode(id, t) {
|
|
16429
|
-
this.stmts.closeNode.run({ id, t });
|
|
17963
|
+
closeNode(id, t, opts = {}) {
|
|
17964
|
+
const r = this.stmts.closeNode.run({ id, t });
|
|
17965
|
+
if (Number(r.changes) > 0 && !opts.retainEdges) closeIncidentEdges(this.db, id, t);
|
|
16430
17966
|
}
|
|
16431
17967
|
closeEdge(id, t) {
|
|
16432
17968
|
this.stmts.closeEdge.run({ id, t });
|
|
@@ -16446,9 +17982,11 @@ var SqliteGraphStore = class {
|
|
|
16446
17982
|
* so it must be a plain identifier. */
|
|
16447
17983
|
nodesAsOfWhereTrue(t, attr) {
|
|
16448
17984
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(attr)) throw new Error(`nodesAsOfWhereTrue: bad attr ${JSON.stringify(attr)}`);
|
|
17985
|
+
const indexed = attr === "revisit" ? " INDEXED BY nodes_revisit_true" : "";
|
|
16449
17986
|
const rows = this.db.prepare(
|
|
16450
|
-
`SELECT * FROM nodes WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
|
|
16451
|
-
AND json_type(attrs_json, '$.${attr}') = 'true'
|
|
17987
|
+
`SELECT * FROM nodes${indexed} WHERE valid_from <= :t AND (valid_to IS NULL OR valid_to > :t)
|
|
17988
|
+
AND json_type(attrs_json, '$.${attr}') = 'true'
|
|
17989
|
+
ORDER BY rowid`
|
|
16452
17990
|
).all({ t });
|
|
16453
17991
|
return rows.map(rowToNode);
|
|
16454
17992
|
}
|
|
@@ -17035,7 +18573,14 @@ function summaryFromDoc(doc, kind) {
|
|
|
17035
18573
|
}
|
|
17036
18574
|
if (verb !== null) return null;
|
|
17037
18575
|
const noun = clipped.replace(/^(?:the|a|an)\s+/i, "");
|
|
17038
|
-
|
|
18576
|
+
const head = noun.split(/[\s-]/, 1)[0].replace(/[.,;:!?]+$/, "");
|
|
18577
|
+
return tidy(`${lead(kind)} for ${isOrdinaryCapital(head) ? noun.charAt(0).toLowerCase() + noun.slice(1) : noun}`);
|
|
18578
|
+
}
|
|
18579
|
+
var CONTRACTION_BASES = /* @__PURE__ */ new Set(["it", "that", "this", "there", "here", "what", "who", "where", "when", "why", "how", "let", "he", "she"]);
|
|
18580
|
+
function isOrdinaryCapital(word) {
|
|
18581
|
+
const contraction = /^([A-Za-z]+)['’]s$/.exec(word);
|
|
18582
|
+
if (contraction) return CONTRACTION_BASES.has(contraction[1].toLowerCase()) && /^[A-Z][a-z]*$/.test(contraction[1]);
|
|
18583
|
+
return /^[A-Z][a-z]+$/.test(word);
|
|
17039
18584
|
}
|
|
17040
18585
|
var VERB_PHRASE_KINDS = /* @__PURE__ */ new Set(["function", "method"]);
|
|
17041
18586
|
function summaryFromName(name, kind) {
|
|
@@ -17152,6 +18697,62 @@ function buildSymbolLexicon(store) {
|
|
|
17152
18697
|
}
|
|
17153
18698
|
return lex;
|
|
17154
18699
|
}
|
|
18700
|
+
function lazySymbolLexicon(store) {
|
|
18701
|
+
if (!store.firstLiveByNameOrQname || !store.liveCommentsAttachedTo) return null;
|
|
18702
|
+
const labels = SYMBOL_LABELS.map(([label]) => label);
|
|
18703
|
+
const kindOf = new Map(SYMBOL_LABELS.map(([label, kind]) => [label, kind]));
|
|
18704
|
+
const members = /* @__PURE__ */ new Map();
|
|
18705
|
+
const docs = /* @__PURE__ */ new Map();
|
|
18706
|
+
const entries = /* @__PURE__ */ new Map();
|
|
18707
|
+
const member = (name) => {
|
|
18708
|
+
if (!members.has(name)) {
|
|
18709
|
+
const hit = isDistinctiveIdentifier(name) ? store.firstLiveByNameOrQname(labels, name) : null;
|
|
18710
|
+
members.set(name, hit ? { kind: kindOf.get(hit.label), description: hit.description, qname: hit.qname } : null);
|
|
18711
|
+
}
|
|
18712
|
+
return members.get(name);
|
|
18713
|
+
};
|
|
18714
|
+
const docFor = (name) => {
|
|
18715
|
+
if (!docs.has(name)) {
|
|
18716
|
+
let found;
|
|
18717
|
+
for (const c of store.liveCommentsAttachedTo(name)) {
|
|
18718
|
+
if (typeof c.kind !== "string" || !DOC_COMMENT_KINDS.has(c.kind)) continue;
|
|
18719
|
+
const text = typeof c.text === "string" ? c.text : c.description;
|
|
18720
|
+
if (!text?.trim()) continue;
|
|
18721
|
+
found = text.trim().slice(0, MAX_DOC_CHARS);
|
|
18722
|
+
break;
|
|
18723
|
+
}
|
|
18724
|
+
docs.set(name, found);
|
|
18725
|
+
}
|
|
18726
|
+
return docs.get(name);
|
|
18727
|
+
};
|
|
18728
|
+
const isKnown = (tok) => member(tok) !== null;
|
|
18729
|
+
let anySymbol;
|
|
18730
|
+
return {
|
|
18731
|
+
has: isKnown,
|
|
18732
|
+
get(name) {
|
|
18733
|
+
if (!entries.has(name)) {
|
|
18734
|
+
const m = member(name);
|
|
18735
|
+
let entry = null;
|
|
18736
|
+
if (m) {
|
|
18737
|
+
entry = { kind: m.kind };
|
|
18738
|
+
const doc = (m.description ? docFor(m.description) : void 0) ?? (m.qname ? docFor(m.qname) : void 0);
|
|
18739
|
+
for (const phrase of summaryCandidates(name, m.kind, doc)) {
|
|
18740
|
+
if (summaryRejectionReason(phrase, isKnown) === null) {
|
|
18741
|
+
entry.summary = phrase;
|
|
18742
|
+
break;
|
|
18743
|
+
}
|
|
18744
|
+
}
|
|
18745
|
+
}
|
|
18746
|
+
entries.set(name, entry);
|
|
18747
|
+
}
|
|
18748
|
+
return entries.get(name) ?? void 0;
|
|
18749
|
+
},
|
|
18750
|
+
get size() {
|
|
18751
|
+
anySymbol ??= labels.some((label) => store.countNodesByLabel(label) > 0);
|
|
18752
|
+
return anySymbol ? 1 : 0;
|
|
18753
|
+
}
|
|
18754
|
+
};
|
|
18755
|
+
}
|
|
17155
18756
|
var LEXICON_SYMBOL_LABELS = ["Function", "Method", "Class", "Interface", "Const", "Comment"];
|
|
17156
18757
|
function codeGraphKey(store) {
|
|
17157
18758
|
const files = store.labelFingerprint?.(["File"]);
|
|
@@ -17163,7 +18764,7 @@ function lexiconsFor(store) {
|
|
|
17163
18764
|
const key = codeGraphKey(store);
|
|
17164
18765
|
const hit = key !== null ? lexiconCache.get(store) : void 0;
|
|
17165
18766
|
if (hit && hit.key === key) return hit;
|
|
17166
|
-
const built = { symbols: buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
18767
|
+
const built = { symbols: lazySymbolLexicon(store) ?? buildSymbolLexicon(store), files: buildFileLexicon(store) };
|
|
17167
18768
|
if (key !== null) lexiconCache.set(store, { key, ...built });
|
|
17168
18769
|
return built;
|
|
17169
18770
|
}
|
|
@@ -17409,11 +19010,11 @@ function generalizeSymbols(text, lexicon, level = 1, fileLexicon) {
|
|
|
17409
19010
|
|
|
17410
19011
|
// src/generalize-worker.ts
|
|
17411
19012
|
var stores = /* @__PURE__ */ new Map();
|
|
17412
|
-
function storeAt(
|
|
17413
|
-
let s = stores.get(
|
|
19013
|
+
function storeAt(path2) {
|
|
19014
|
+
let s = stores.get(path2);
|
|
17414
19015
|
if (!s) {
|
|
17415
|
-
s = openGraphStore({ path });
|
|
17416
|
-
stores.set(
|
|
19016
|
+
s = openGraphStore({ path: path2 });
|
|
19017
|
+
stores.set(path2, s);
|
|
17417
19018
|
}
|
|
17418
19019
|
return s;
|
|
17419
19020
|
}
|