@hraness/wordcell 0.20.0-bootstrap.1 → 0.21.0

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.
Files changed (81) hide show
  1. package/README.md +29 -15
  2. package/dist/agent-context.js +1 -0
  3. package/dist/agent-guide-audit.js +1 -0
  4. package/dist/attachments.js +1 -0
  5. package/dist/authoring.js +1 -0
  6. package/dist/authority-4sp9d552.js +663 -0
  7. package/dist/benchmark.js +1 -0
  8. package/dist/capture.js +1 -0
  9. package/dist/cli.js +3781 -32
  10. package/dist/clip/acquire.js +1 -0
  11. package/dist/clip/args.js +1 -0
  12. package/dist/clip/bounded-byte-buffer.js +1 -0
  13. package/dist/clip/browser-profiles.js +1 -0
  14. package/dist/clip/bundle-reader.js +1 -0
  15. package/dist/clip/cli.js +1 -0
  16. package/dist/clip/cookies.js +1 -0
  17. package/dist/clip/doctor.js +1 -0
  18. package/dist/clip/jobs.js +1 -0
  19. package/dist/clip/network-proxy.js +1 -0
  20. package/dist/clip/network.js +1 -0
  21. package/dist/clip/persist.js +1 -0
  22. package/dist/clip/refresh.js +1 -0
  23. package/dist/clip/terminal.js +1 -0
  24. package/dist/evaluation-builder.js +13 -5
  25. package/dist/evaluation-kb.js +11 -4
  26. package/dist/evaluation.js +1 -0
  27. package/dist/git.js +1 -0
  28. package/dist/graph-authority.js +29 -0
  29. package/dist/graph-percolation.js +16 -0
  30. package/dist/graph.js +1 -0
  31. package/dist/index-0k2x4nn9.js +393 -0
  32. package/dist/{index-n05s3wsb.js → index-115b07ap.js} +156 -541
  33. package/dist/index-11621h23.js +160 -0
  34. package/dist/{index-tkpxp48t.js → index-51bap0mq.js} +2 -2
  35. package/dist/{index-8a08v6x8.js → index-59ssmqqm.js} +1 -1
  36. package/dist/index-bcfn9xah.js +472 -0
  37. package/dist/index-bgfzwt4h.js +51 -0
  38. package/dist/{index-bs0yf4pp.js → index-pbr6rv47.js} +2 -2
  39. package/dist/{index-m7et4rz4.js → index-vdw5yjfa.js} +61 -9
  40. package/dist/index-z1w83f81.js +4 -0
  41. package/dist/index.js +61 -34
  42. package/dist/navigation.js +1 -0
  43. package/dist/pdf.js +1 -0
  44. package/dist/percolate.js +1 -0
  45. package/dist/portfolio.js +11 -4
  46. package/dist/query.js +1 -0
  47. package/dist/repository-memory.js +1 -0
  48. package/dist/sdk.js +10 -3
  49. package/dist/search-rules.js +1 -0
  50. package/dist/search.js +1 -0
  51. package/dist/semantic.js +3 -1
  52. package/dist/source-inbox.js +1 -0
  53. package/dist/untrusted-content.js +1 -0
  54. package/dist/url-intelligence.js +1 -0
  55. package/dist/workflow.js +1 -0
  56. package/dist/workflows/decision-context.js +11 -4
  57. package/dist/workflows/explain-change.js +1 -0
  58. package/dist/workflows/index.js +11 -4
  59. package/dist/workflows/plan-radar.js +1 -0
  60. package/package.json +24 -6
  61. package/skills/wordcell/SKILL.md +2 -2
  62. package/skills/wordcell/references/percolate.md +4 -0
  63. package/skills/wordcell/references/query.md +6 -0
  64. package/src/cli-program.ts +45 -2
  65. package/src/graph-authority-model.ts +116 -0
  66. package/src/graph-authority.ts +219 -0
  67. package/src/graph-cache-filesystem.ts +4 -0
  68. package/src/graph-cli.ts +96 -0
  69. package/src/graph-facts.ts +206 -0
  70. package/src/graph-percolation.ts +39 -0
  71. package/src/graph-query.ts +122 -0
  72. package/src/index.ts +2 -0
  73. package/src/oh/authority.ts +193 -0
  74. package/src/oh/programs.ts +99 -0
  75. package/src/oh/schema.ts +40 -0
  76. package/src/oh/snapshot.ts +114 -0
  77. package/src/oh/validation.ts +61 -0
  78. package/src/sdk.ts +51 -6
  79. package/dist/index-sy0zety7.js +0 -3630
  80. package/dist/kb-alias.js +0 -55
  81. package/src/kb-alias.ts +0 -14
@@ -0,0 +1,160 @@
1
+ // @bun
2
+ import {
3
+ validateRepositoryScopeSelection
4
+ } from "./index-06c9ctr6.js";
5
+ import {
6
+ isCanonicalNoteId,
7
+ isCanonicalRelationPredicate
8
+ } from "./index-ekpwvbra.js";
9
+
10
+ // src/graph-authority-model.ts
11
+ var GRAPH_LIMITS = Object.freeze({
12
+ notes: 4000,
13
+ facts: 1e5,
14
+ sourceBytes: 64 * 1024 * 1024,
15
+ atomBytes: 16 * 1024,
16
+ depth: 8,
17
+ rows: 1000,
18
+ workUnits: 8000000,
19
+ derivedTuples: 1e5,
20
+ rounds: 64,
21
+ proofDepth: 64,
22
+ proofNodes: 4096,
23
+ totalProofNodes: 32768,
24
+ resultBytes: 8 * 1024 * 1024
25
+ });
26
+
27
+ class GraphAuthorityError extends Error {
28
+ code;
29
+ constructor(code, message) {
30
+ super(message);
31
+ this.name = "GraphAuthorityError";
32
+ this.code = code;
33
+ }
34
+ }
35
+
36
+ // src/graph-query.ts
37
+ import { isProxy } from "util/types";
38
+ function invalid(message) {
39
+ throw new GraphAuthorityError("invalid-input", message);
40
+ }
41
+ function dataObject(value, label) {
42
+ if (value === null || typeof value !== "object" || isProxy(value) || Array.isArray(value)) {
43
+ return invalid(`${label} must be a plain data object.`);
44
+ }
45
+ const prototype = Object.getPrototypeOf(value);
46
+ if (prototype !== Object.prototype && prototype !== null) {
47
+ return invalid(`${label} must be a plain data object.`);
48
+ }
49
+ const keys = Reflect.ownKeys(value);
50
+ if (keys.length > 16)
51
+ return invalid(`${label} contains too many fields.`);
52
+ const result = Object.create(null);
53
+ for (const key of keys) {
54
+ if (typeof key !== "string")
55
+ return invalid(`${label} must have only named data fields.`);
56
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
57
+ if (descriptor === undefined || !("value" in descriptor) || !descriptor.enumerable) {
58
+ return invalid(`${label} must have only enumerable data fields.`);
59
+ }
60
+ result[key] = descriptor.value;
61
+ }
62
+ return result;
63
+ }
64
+ function boundedText(value, label) {
65
+ if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > GRAPH_LIMITS.atomBytes) {
66
+ return invalid(`${label} must be a bounded string.`);
67
+ }
68
+ return value;
69
+ }
70
+ function boundedInteger(value, maximum, label) {
71
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > maximum) {
72
+ return invalid(`${label} must be an integer from 1 through ${maximum}.`);
73
+ }
74
+ return value;
75
+ }
76
+ function checkedLimits(value) {
77
+ const input = dataObject(value, "Graph query limits");
78
+ const result = {};
79
+ const keys = [
80
+ "rows",
81
+ "workUnits",
82
+ "derivedTuples",
83
+ "rounds",
84
+ "proofDepth",
85
+ "proofNodes",
86
+ "totalProofNodes",
87
+ "resultBytes"
88
+ ];
89
+ for (const key of Object.keys(input)) {
90
+ if (!keys.includes(key))
91
+ return invalid("Unknown graph query limit.");
92
+ }
93
+ for (const key of keys) {
94
+ if (Object.hasOwn(input, key)) {
95
+ result[key] = boundedInteger(input[key], GRAPH_LIMITS[key], `Graph query ${key}`);
96
+ }
97
+ }
98
+ return Object.freeze(result);
99
+ }
100
+ function validateGraphQueryRequest(value) {
101
+ const input = dataObject(value, "Graph query");
102
+ const program = input.program;
103
+ if (program !== "backlinks" && program !== "reachability" && program !== "scope-route" && program !== "relation-closure" && program !== "shared-tags" && program !== "shared-concepts") {
104
+ return invalid("Graph query requires a supported named program.");
105
+ }
106
+ const allowed = new Set(["program", "limits", program === "scope-route" ? "scope" : "note"]);
107
+ if (program === "reachability" || program === "relation-closure")
108
+ allowed.add("depth");
109
+ if (program === "relation-closure")
110
+ allowed.add("predicate");
111
+ if (Object.keys(input).some((key) => !allowed.has(key))) {
112
+ return invalid("Graph query contains an option unsupported by its named program.");
113
+ }
114
+ const limits = Object.hasOwn(input, "limits") ? checkedLimits(input.limits) : undefined;
115
+ const extras = limits === undefined ? {} : { limits };
116
+ if (program === "scope-route") {
117
+ const scope = boundedText(input.scope, "Graph query scope");
118
+ try {
119
+ validateRepositoryScopeSelection([scope]);
120
+ } catch {
121
+ return invalid("Graph query scope must be an exact canonical repository path.");
122
+ }
123
+ return Object.freeze({ program, scope, ...extras });
124
+ }
125
+ const note = boundedText(input.note, "Graph query note");
126
+ if (!isCanonicalNoteId(note))
127
+ return invalid("Graph query note must be an exact canonical note ID.");
128
+ const depth = Object.hasOwn(input, "depth") ? boundedInteger(input.depth, GRAPH_LIMITS.depth, "Graph query depth") : undefined;
129
+ const depthOption = depth === undefined ? {} : { depth };
130
+ if (program === "relation-closure") {
131
+ const predicate = boundedText(input.predicate, "Graph query predicate");
132
+ if (!isCanonicalRelationPredicate(predicate)) {
133
+ return invalid("Graph query predicate must be an exact lower-kebab relation predicate.");
134
+ }
135
+ return Object.freeze({ program, note, predicate, ...depthOption, ...extras });
136
+ }
137
+ if (program === "reachability")
138
+ return Object.freeze({ program, note, ...depthOption, ...extras });
139
+ return Object.freeze({ program, note, ...extras });
140
+ }
141
+ function validateGraphPercolationOptions(value) {
142
+ const input = dataObject(value, "Graph percolation options");
143
+ if (Object.keys(input).some((key) => !["note", "minSupport", "limit"].includes(key))) {
144
+ return invalid("Unknown graph percolation option.");
145
+ }
146
+ const note = boundedText(input.note, "Graph percolation note");
147
+ if (note.trim() === "")
148
+ return invalid("Graph percolation requires one note.");
149
+ const minSupport = Object.hasOwn(input, "minSupport") ? boundedInteger(input.minSupport, 1000, "Graph percolation minSupport") : undefined;
150
+ if (minSupport === 1)
151
+ return invalid("Graph percolation minSupport must be at least 2.");
152
+ const limit = Object.hasOwn(input, "limit") ? boundedInteger(input.limit, 1000, "Graph percolation limit") : undefined;
153
+ return Object.freeze({
154
+ note,
155
+ ...minSupport === undefined ? {} : { minSupport },
156
+ ...limit === undefined ? {} : { limit }
157
+ });
158
+ }
159
+
160
+ export { GRAPH_LIMITS, GraphAuthorityError, validateGraphQueryRequest, validateGraphPercolationOptions };
@@ -5,14 +5,14 @@ import {
5
5
  MAX_SEARCH_RESULTS,
6
6
  openKnowledgeBase,
7
7
  validateKnowledgeBaseSearchHistory
8
- } from "./index-m7et4rz4.js";
8
+ } from "./index-vdw5yjfa.js";
9
9
  import {
10
10
  expandSearchRequest,
11
11
  parseSearchRules
12
12
  } from "./index-adx6khj5.js";
13
13
  import {
14
14
  scanVault
15
- } from "./index-n05s3wsb.js";
15
+ } from "./index-0k2x4nn9.js";
16
16
  import {
17
17
  indexGitHistory
18
18
  } from "./index-1gwbassd.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-h170byqw.js";
5
5
  import {
6
6
  packUntrustedSearchContext
7
- } from "./index-m7et4rz4.js";
7
+ } from "./index-vdw5yjfa.js";
8
8
 
9
9
  // src/workflows/decision-context.ts
10
10
  var decisionContextWorkflow = defineWorkflow({
@@ -0,0 +1,472 @@
1
+ // @bun
2
+ import {
3
+ GRAPH_LIMITS,
4
+ GraphAuthorityError,
5
+ validateGraphQueryRequest
6
+ } from "./index-11621h23.js";
7
+ import {
8
+ scanVault
9
+ } from "./index-0k2x4nn9.js";
10
+ import {
11
+ acquireFileLease
12
+ } from "./index-3rm7cz6h.js";
13
+ import {
14
+ analyzeAuthoredRepositoryScopes
15
+ } from "./index-06c9ctr6.js";
16
+ import {
17
+ VaultAnalysisBudgetError,
18
+ analyzeVault,
19
+ documentIdState,
20
+ isCanonicalNoteId,
21
+ parseNote
22
+ } from "./index-ekpwvbra.js";
23
+ import {
24
+ __require
25
+ } from "./index-z1w83f81.js";
26
+
27
+ // src/graph-authority.ts
28
+ import { createHash as createHash2 } from "crypto";
29
+ import { constants } from "fs";
30
+ import { mkdir, mkdtemp, realpath, rename, rm } from "fs/promises";
31
+ import { join, resolve as resolve2 } from "path";
32
+
33
+ // src/graph-facts.ts
34
+ import { createHash } from "crypto";
35
+ import { isAbsolute, relative, resolve, sep } from "path";
36
+ var compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
37
+ var sha256 = (value) => createHash("sha256").update(value, "utf8").digest("hex");
38
+ function canonicalJson(value) {
39
+ if (value === null || typeof value !== "object")
40
+ return JSON.stringify(value);
41
+ if (Array.isArray(value))
42
+ return `[${value.map(canonicalJson).join(",")}]`;
43
+ return `{${Object.entries(value).toSorted(([left], [right]) => compareText(left, right)).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
44
+ }
45
+ function invalid(message) {
46
+ throw new GraphAuthorityError("invalid-input", message);
47
+ }
48
+ function budget(message) {
49
+ throw new GraphAuthorityError("budget", message);
50
+ }
51
+ function textAtom(value, label) {
52
+ if (typeof value !== "string")
53
+ invalid(`${label} must be a string.`);
54
+ if (Buffer.byteLength(value, "utf8") > GRAPH_LIMITS.atomBytes) {
55
+ budget(`${label} exceeds the ${GRAPH_LIMITS.atomBytes} byte graph atom limit.`);
56
+ }
57
+ return value;
58
+ }
59
+ function markdownPath(value, label) {
60
+ const path = textAtom(value, label);
61
+ if (!path.toLowerCase().endsWith(".md") || !isCanonicalNoteId(path.slice(0, -3))) {
62
+ invalid(`${label} must be a canonical vault-relative Markdown path.`);
63
+ }
64
+ return path;
65
+ }
66
+ function createGraphSnapshot(snapshot) {
67
+ if (snapshot === null || typeof snapshot !== "object")
68
+ invalid("A vault snapshot is required.");
69
+ const rootInput = textAtom(snapshot.root, "Vault root");
70
+ if (!isAbsolute(rootInput) || rootInput.includes("\x00"))
71
+ invalid("Vault root must be absolute.");
72
+ const root = resolve(rootInput);
73
+ const indexPath = textAtom(snapshot.indexPath, "Catalog path");
74
+ if (!isAbsolute(indexPath) || indexPath.includes("\x00"))
75
+ invalid("Catalog path must be absolute.");
76
+ const catalogPath = markdownPath(relative(root, resolve(indexPath)).split(sep).join("/"), "Catalog path");
77
+ const catalogNoteId = catalogPath.slice(0, -3);
78
+ if (!Array.isArray(snapshot.notes))
79
+ invalid("Vault notes must be an array.");
80
+ if (snapshot.notes.length > GRAPH_LIMITS.notes) {
81
+ budget(`Graph exceeds the ${GRAPH_LIMITS.notes} note limit.`);
82
+ }
83
+ let sourceBytes = 0;
84
+ const paths = new Set;
85
+ const sources = snapshot.notes.map((value) => {
86
+ if (value === null || typeof value !== "object")
87
+ invalid("Every vault note must be an object.");
88
+ const candidate = value;
89
+ const path = markdownPath(candidate.path, "Note path");
90
+ if (candidate.id !== path.slice(0, -3))
91
+ invalid(`Note ID must match its canonical path: ${path}.`);
92
+ if (paths.has(path))
93
+ invalid(`Duplicate note path: ${path}.`);
94
+ paths.add(path);
95
+ if (typeof candidate.content !== "string")
96
+ invalid(`Note content must be a string: ${path}.`);
97
+ sourceBytes += Buffer.byteLength(candidate.content, "utf8");
98
+ if (sourceBytes > GRAPH_LIMITS.sourceBytes) {
99
+ budget(`Graph sources exceed the ${GRAPH_LIMITS.sourceBytes} byte limit.`);
100
+ }
101
+ return { path, content: candidate.content };
102
+ }).toSorted((left, right) => compareText(left.path, right.path));
103
+ const documentIds = new Set;
104
+ const noteIds = new Set;
105
+ let connectionObservations = 0;
106
+ const checked = sources.map(({ path, content }) => {
107
+ let note;
108
+ try {
109
+ note = parseNote(path, content);
110
+ } catch {
111
+ invalid(`Cannot parse authored Markdown: ${path}.`);
112
+ }
113
+ if (noteIds.has(note.id))
114
+ invalid(`Duplicate note ID: ${note.id}.`);
115
+ noteIds.add(note.id);
116
+ textAtom(note.title, `Title in ${path}`);
117
+ textAtom(note.properties.type ?? "", `Type in ${path}`);
118
+ for (const tag of note.tags)
119
+ textAtom(tag, `Tag in ${path}`);
120
+ const identity2 = documentIdState(note.metadata);
121
+ if (identity2.kind === "invalid")
122
+ invalid(`Invalid document_id in ${path}.`);
123
+ const documentId = identity2.kind === "valid" ? identity2.documentId : null;
124
+ if (documentId !== null) {
125
+ if (documentIds.has(documentId))
126
+ invalid(`Duplicate document_id: ${documentId}.`);
127
+ documentIds.add(documentId);
128
+ }
129
+ const scopes = analyzeAuthoredRepositoryScopes(note.metadata);
130
+ if (!scopes.valid)
131
+ invalid(`Invalid repository_scopes in ${path}.`);
132
+ for (const scope of scopes.scopes)
133
+ textAtom(scope, `Repository scope in ${path}`);
134
+ connectionObservations += note.links.length + (note.relationDeclarations?.length ?? 0) + (note.relationIssues?.length ?? 0);
135
+ if (connectionObservations > GRAPH_LIMITS.facts) {
136
+ budget(`Graph connections exceed the ${GRAPH_LIMITS.facts} observation limit.`);
137
+ }
138
+ return { note, documentId, scopes: scopes.scopes };
139
+ });
140
+ let analysis;
141
+ try {
142
+ analysis = analyzeVault(checked.map(({ note }) => note), {
143
+ catalogNoteId,
144
+ mentionScope: () => false,
145
+ maxNotes: GRAPH_LIMITS.notes,
146
+ maxConnectionObservations: GRAPH_LIMITS.facts
147
+ });
148
+ } catch (error) {
149
+ if (error instanceof VaultAnalysisBudgetError)
150
+ budget(error.message);
151
+ throw error;
152
+ }
153
+ const included = new Set(analysis.noteConnections.map(({ id }) => id));
154
+ const factGroups = new Map;
155
+ let factCount = 0;
156
+ const add = (source, relation, tuple) => {
157
+ if (!included.has(source))
158
+ invalid(`Graph fact has no authored source: ${source}.`);
159
+ for (const atom of tuple) {
160
+ if (typeof atom === "string")
161
+ textAtom(atom, relation);
162
+ else if (typeof atom === "number" && (!Number.isSafeInteger(atom) || atom < 1)) {
163
+ invalid(`Graph source lines must be positive safe integers: ${source}.`);
164
+ }
165
+ }
166
+ const key = canonicalJson({ relation, tuple });
167
+ const group = factGroups.get(source) ?? new Map;
168
+ if (group.has(key))
169
+ return;
170
+ if (factCount >= GRAPH_LIMITS.facts)
171
+ budget(`Graph exceeds the ${GRAPH_LIMITS.facts} fact limit.`);
172
+ factCount += 1;
173
+ group.set(key, Object.freeze({ relation, tuple: Object.freeze([...tuple]) }));
174
+ factGroups.set(source, group);
175
+ };
176
+ for (const { note, scopes } of checked) {
177
+ if (!included.has(note.id))
178
+ continue;
179
+ const type = note.properties.type ?? "";
180
+ add(note.id, "wordcell.note", [note.id, note.path, note.title, type]);
181
+ if (type.normalize("NFC").toLocaleLowerCase("en-US") === "concept") {
182
+ add(note.id, "wordcell.concept", [note.id]);
183
+ }
184
+ for (const tag of note.tags)
185
+ add(note.id, "wordcell.tag", [note.id, tag]);
186
+ for (const scope of scopes)
187
+ add(note.id, "wordcell.scope", [note.id, scope]);
188
+ }
189
+ for (const link of analysis.contextualLinks) {
190
+ const source = link.source.slice(0, -3);
191
+ add(source, "wordcell.link", [source, link.target.slice(0, -3), link.line]);
192
+ }
193
+ for (const relation of analysis.authoredRelations) {
194
+ add(relation.source, "wordcell.relation", [
195
+ relation.source,
196
+ relation.target,
197
+ relation.predicate,
198
+ relation.provenance.line
199
+ ]);
200
+ }
201
+ for (const relation of analysis.externalAuthoredRelations) {
202
+ add(relation.source, "wordcell.external-relation", [
203
+ relation.source,
204
+ relation.target,
205
+ relation.predicate,
206
+ relation.provenance.line
207
+ ]);
208
+ }
209
+ const sourceDigests = Object.freeze(sources.map(({ path, content }) => Object.freeze({ path, contentSha256: sha256(content) })));
210
+ const digests = new Map(sourceDigests.map(({ path, contentSha256 }) => [path, contentSha256]));
211
+ const records = Object.freeze(checked.filter(({ note }) => included.has(note.id)).map(({ note, documentId }) => Object.freeze({
212
+ key: `edition:note/${sha256(documentId === null ? `path:${note.id}` : `document:${documentId}`)}`,
213
+ id: note.id,
214
+ path: note.path,
215
+ documentId,
216
+ contentSha256: digests.get(note.path),
217
+ facts: Object.freeze([...factGroups.get(note.id)?.entries() ?? []].toSorted(([left], [right]) => compareText(left, right)).map(([, fact]) => fact))
218
+ })).toSorted((left, right) => compareText(left.key, right.key)));
219
+ const identity = Object.freeze({
220
+ schemaVersion: 1,
221
+ vaultIdentity: sha256(root),
222
+ catalogNoteId,
223
+ sourceDigests,
224
+ records
225
+ });
226
+ return Object.freeze({ ...identity, revision: sha256(canonicalJson(identity)), factCount });
227
+ }
228
+
229
+ // src/graph-cache-filesystem.ts
230
+ import { lstat, open } from "fs/promises";
231
+ var graphCacheFileSystem = { lstat, open };
232
+
233
+ // src/graph-authority.ts
234
+ var openOhGraphAdapter = async (...args) => await (await import("./authority-4sp9d552.js")).openOhGraphAdapter(...args);
235
+ var MAX_CACHE_BYTES = 67108864;
236
+ var ignoredCache = `# Disposable Wordcell graph state. Rebuild from Markdown.
237
+ *
238
+ `;
239
+ var hash = (bytes) => createHash2("sha256").update(bytes).digest("hex");
240
+ function errno(value, code) {
241
+ return value !== null && typeof value === "object" && "code" in value && value.code === code;
242
+ }
243
+ async function rootPath(root) {
244
+ if (typeof root !== "string" || root.trim() === "" || root.includes("\x00")) {
245
+ throw new GraphAuthorityError("invalid-input", "A graph operation requires a vault root.");
246
+ }
247
+ return await realpath(resolve2(root));
248
+ }
249
+ async function scan(root, options) {
250
+ return await scanVault(root, {
251
+ ...options,
252
+ mentionScope: false,
253
+ maxNotes: GRAPH_LIMITS.notes,
254
+ maxTotalBytes: GRAPH_LIMITS.sourceBytes,
255
+ maxConnectionObservations: GRAPH_LIMITS.facts
256
+ });
257
+ }
258
+ async function directoryIdentity(path) {
259
+ const info = await graphCacheFileSystem.lstat(path, { bigint: true });
260
+ if (!info.isDirectory() || info.isSymbolicLink() || await realpath(path) !== path) {
261
+ throw new GraphAuthorityError("unsafe-cache", "Wordcell graph state requires a real directory inside the vault.");
262
+ }
263
+ return { dev: info.dev, ino: info.ino };
264
+ }
265
+ async function sameDirectory(path, expected) {
266
+ const actual = await directoryIdentity(path);
267
+ if (actual.dev !== expected.dev || actual.ino !== expected.ino) {
268
+ throw new GraphAuthorityError("unsafe-cache", "The graph cache directory changed during the operation.");
269
+ }
270
+ }
271
+ async function readCache(path, maximumBytes = MAX_CACHE_BYTES) {
272
+ let observed;
273
+ try {
274
+ observed = await graphCacheFileSystem.lstat(path, { bigint: true });
275
+ } catch (error) {
276
+ if (errno(error, "ENOENT"))
277
+ return null;
278
+ throw error;
279
+ }
280
+ if (!observed.isFile() || observed.isSymbolicLink() || observed.nlink !== 1n) {
281
+ throw new GraphAuthorityError("unsafe-cache", "Graph cache files must be regular files with one link.");
282
+ }
283
+ if (observed.size > BigInt(maximumBytes))
284
+ throw new GraphAuthorityError("budget", "Graph cache exceeds its byte limit.");
285
+ const handle = await graphCacheFileSystem.open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
286
+ try {
287
+ const before = await handle.stat({ bigint: true });
288
+ if (!before.isFile() || before.nlink !== 1n || before.dev !== observed.dev || before.ino !== observed.ino) {
289
+ throw new GraphAuthorityError("unsafe-cache", "The graph cache changed before it could be read.");
290
+ }
291
+ if (before.size > BigInt(maximumBytes)) {
292
+ throw new GraphAuthorityError("budget", "Graph cache exceeds its byte limit.");
293
+ }
294
+ if (before.size !== observed.size || before.mtimeNs !== observed.mtimeNs || before.ctimeNs !== observed.ctimeNs) {
295
+ throw new GraphAuthorityError("stale", "The graph cache changed before it could be read.");
296
+ }
297
+ const bytes = Buffer.alloc(Number(before.size));
298
+ let offset = 0;
299
+ while (offset < bytes.length) {
300
+ const { bytesRead } = await handle.read(bytes, offset, bytes.length - offset, offset);
301
+ if (bytesRead === 0)
302
+ throw new GraphAuthorityError("stale", "The graph cache changed during its read.");
303
+ offset += bytesRead;
304
+ }
305
+ const after = await handle.stat({ bigint: true });
306
+ const current = await graphCacheFileSystem.lstat(path, { bigint: true });
307
+ if (before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs || current.dev !== before.dev || current.ino !== before.ino || current.nlink !== 1n) {
308
+ throw new GraphAuthorityError("stale", "The graph cache changed during its read.");
309
+ }
310
+ return bytes;
311
+ } finally {
312
+ await handle.close();
313
+ }
314
+ }
315
+ async function requireSingleFileCache(path) {
316
+ for (const suffix of ["-wal", "-shm", "-journal"]) {
317
+ try {
318
+ await graphCacheFileSystem.lstat(path + suffix);
319
+ } catch (error) {
320
+ if (errno(error, "ENOENT"))
321
+ continue;
322
+ throw error;
323
+ }
324
+ throw new GraphAuthorityError("unsafe-cache", "Graph cache has an active or foreign SQLite sidecar; close its writer before rebuilding.");
325
+ }
326
+ }
327
+ async function assertFresh(root, options, revision) {
328
+ if (createGraphSnapshot(await scan(root, options)).revision !== revision) {
329
+ throw new GraphAuthorityError("stale", "Markdown changed during the graph operation; retry from a fresh snapshot.");
330
+ }
331
+ }
332
+ async function openGraphAuthority(snapshot) {
333
+ return await openOhGraphAdapter(createGraphSnapshot(snapshot));
334
+ }
335
+ async function withGraph(rootInput, options, work) {
336
+ const root = await rootPath(rootInput);
337
+ const snapshot = createGraphSnapshot(await scan(root, options));
338
+ let authority;
339
+ if (options.persisted) {
340
+ const cache = join(root, ".wordcell");
341
+ let identity;
342
+ try {
343
+ identity = await directoryIdentity(cache);
344
+ } catch (error) {
345
+ if (errno(error, "ENOENT"))
346
+ throw new GraphAuthorityError("stale", "No graph cache exists; run wordcell graph rebuild first.");
347
+ throw error;
348
+ }
349
+ const path = join(cache, "oh.sqlite");
350
+ await requireSingleFileCache(path);
351
+ const bytes = await readCache(path);
352
+ if (bytes === null)
353
+ throw new GraphAuthorityError("stale", "No graph cache exists; run wordcell graph rebuild first.");
354
+ await sameDirectory(cache, identity);
355
+ authority = await openOhGraphAdapter(snapshot, { databaseBytes: bytes });
356
+ } else
357
+ authority = await openOhGraphAdapter(snapshot);
358
+ try {
359
+ const result = await work(authority);
360
+ await assertFresh(root, options, snapshot.revision);
361
+ return result;
362
+ } finally {
363
+ await authority.close();
364
+ }
365
+ }
366
+ async function verifyGraph(root, options = {}) {
367
+ return await withGraph(root, { ...options, persisted: true }, async (authority) => await authority.verify());
368
+ }
369
+ async function queryGraph(root, request, options = {}) {
370
+ const checked = validateGraphQueryRequest(request);
371
+ return await withGraph(root, options, async (authority) => await authority.query(checked));
372
+ }
373
+ async function rebuildGraph(rootInput, options = {}) {
374
+ const root = await rootPath(rootInput);
375
+ const rootIdentity = await directoryIdentity(root);
376
+ const snapshot = createGraphSnapshot(await scan(root, options));
377
+ const cache = join(root, ".wordcell");
378
+ try {
379
+ await mkdir(cache, { mode: 448 });
380
+ } catch (error) {
381
+ if (!errno(error, "EEXIST"))
382
+ throw error;
383
+ }
384
+ const cacheIdentity = await directoryIdentity(cache);
385
+ const lease = await acquireFileLease(join(cache, ".rebuild.lock"));
386
+ let staging;
387
+ let stagingIdentity;
388
+ try {
389
+ await sameDirectory(root, rootIdentity);
390
+ await sameDirectory(cache, cacheIdentity);
391
+ const ignore = join(cache, ".gitignore");
392
+ const oldIgnore = await readCache(ignore, 4096);
393
+ if (oldIgnore !== null && oldIgnore.toString("utf8") !== ignoredCache) {
394
+ throw new GraphAuthorityError("unsafe-cache", "The graph cache has an unrecognized .gitignore; preserve it and use a separate vault root.");
395
+ }
396
+ if (oldIgnore === null) {
397
+ const handle2 = await graphCacheFileSystem.open(ignore, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
398
+ try {
399
+ await handle2.writeFile(ignoredCache);
400
+ await handle2.sync();
401
+ } finally {
402
+ await handle2.close();
403
+ }
404
+ }
405
+ const path = join(cache, "oh.sqlite");
406
+ await requireSingleFileCache(path);
407
+ const previous = await readCache(path);
408
+ staging = await mkdtemp(join(cache, ".build-"));
409
+ stagingIdentity = await directoryIdentity(staging);
410
+ const stagedPath = join(staging, "oh.sqlite");
411
+ if (previous !== null && !options.fresh) {
412
+ const handle2 = await graphCacheFileSystem.open(stagedPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
413
+ try {
414
+ await handle2.writeFile(previous);
415
+ } finally {
416
+ await handle2.close();
417
+ }
418
+ }
419
+ const authority = await openOhGraphAdapter(snapshot, { databasePath: stagedPath, writable: true });
420
+ let result;
421
+ try {
422
+ result = await authority.verify();
423
+ } finally {
424
+ await authority.close();
425
+ }
426
+ const ready = await readCache(stagedPath);
427
+ if (ready === null)
428
+ throw new GraphAuthorityError("corrupt-cache", "The graph rebuild did not produce a database.");
429
+ await requireSingleFileCache(stagedPath);
430
+ await assertFresh(root, options, snapshot.revision);
431
+ await lease.assertOwned();
432
+ await sameDirectory(root, rootIdentity);
433
+ await sameDirectory(cache, cacheIdentity);
434
+ await requireSingleFileCache(path);
435
+ const current = await readCache(path);
436
+ if (previous === null !== (current === null) || previous !== null && current !== null && hash(previous) !== hash(current)) {
437
+ throw new GraphAuthorityError("stale", "The graph cache changed during rebuild; retry from its current state.");
438
+ }
439
+ const handle = await graphCacheFileSystem.open(stagedPath, constants.O_RDONLY | constants.O_NOFOLLOW);
440
+ try {
441
+ await handle.sync();
442
+ } finally {
443
+ await handle.close();
444
+ }
445
+ await rename(stagedPath, path);
446
+ const directory = await graphCacheFileSystem.open(cache, constants.O_RDONLY);
447
+ try {
448
+ await directory.sync();
449
+ } finally {
450
+ await directory.close();
451
+ }
452
+ return result;
453
+ } finally {
454
+ try {
455
+ if (staging !== undefined && stagingIdentity !== undefined) {
456
+ let stillOwned = false;
457
+ try {
458
+ await sameDirectory(root, rootIdentity);
459
+ await sameDirectory(cache, cacheIdentity);
460
+ await sameDirectory(staging, stagingIdentity);
461
+ stillOwned = true;
462
+ } catch {}
463
+ if (stillOwned)
464
+ await rm(staging, { recursive: true, force: true });
465
+ }
466
+ } finally {
467
+ await lease.release();
468
+ }
469
+ }
470
+ }
471
+
472
+ export { createGraphSnapshot, openGraphAuthority, verifyGraph, queryGraph, rebuildGraph };