glimmervoid 0.30.0 → 0.32.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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/glimmervoid.js +144 -57
  3. package/dist/chunks/{adapters-Dho1VSY5.js → adapters-BU74M4QZ.js} +1 -1
  4. package/dist/chunks/{adapters-zPDx1Xox.js → adapters-CoC18rdt.js} +56 -31
  5. package/dist/chunks/agent-api-cli-V_BkYhBI.js +142 -0
  6. package/dist/chunks/agent-api-core-B3YI8SYs.js +192 -0
  7. package/dist/chunks/{agent-setup-cli-B5Atxr3F.js → agent-setup-cli-D4956BfN.js} +11 -28
  8. package/dist/chunks/backoff-DjVpfHk7.js +14 -0
  9. package/dist/chunks/child-process-safe-B3zcPdFM.js +2 -0
  10. package/dist/chunks/{child-process-safe-DRGsp0TE.js → child-process-safe-DDELk9XC.js} +3 -2
  11. package/dist/chunks/coerce-NSst8oRP.js +32 -0
  12. package/dist/chunks/{contracts-DGkKNh9W.js → config-CZ4H51D0.js} +374 -1684
  13. package/dist/chunks/{config-store-mm7MgzoL.js → config-store-BVtdHkDK.js} +594 -329
  14. package/dist/chunks/config-store-Cp62jN1O.js +2 -0
  15. package/dist/chunks/contracts-DXujp-Zn.js +2131 -0
  16. package/dist/chunks/git-version-CrLW4a6r.js +58 -0
  17. package/dist/chunks/{grok-DuU8xVLZ.js → grok-C4lzPFEg.js} +24 -21
  18. package/dist/chunks/{hook-relay-core-BHfYFnYV.js → hook-relay-core-DA_bz2KA.js} +10 -5
  19. package/dist/chunks/{hook-tools-pSJia7ks.js → hook-tools-UX2fmt81.js} +3 -5
  20. package/dist/chunks/{ingest-editor-core-BuI1gMF_.js → ingest-editor-core-vR6Wv6bP.js} +24 -13
  21. package/dist/chunks/json-core-dfa_7Fak.js +23 -0
  22. package/dist/chunks/json-file-oEH9vt-0.js +352 -0
  23. package/dist/chunks/knowledge-graph-cli-Bjy9_kv5.js +1354 -0
  24. package/dist/chunks/{loopback-post-C7C22eYi.js → loopback-post-DepQfdcM.js} +40 -26
  25. package/dist/chunks/{main-CuIZQPhZ.js → main-DSbpYn2Y.js} +27880 -19139
  26. package/dist/chunks/{node-pty-preflight-CK1knGIR.js → node-pty-preflight-C8QIlrtU.js} +4 -6
  27. package/dist/chunks/{pair-cli-w-gCAF-1.js → pair-cli-CKn-wUw9.js} +4 -3
  28. package/dist/chunks/paths-CeJl2Vcg.js +55 -0
  29. package/dist/chunks/{remote-config-C2ytQwrs.js → remote-config-DP44Ybt9.js} +37 -41
  30. package/dist/chunks/{rtk-hook-core-Bl6gYHPN.js → rtk-hook-core-BmEz2aq9.js} +3 -5
  31. package/dist/chunks/rtk-resolver-CIVLE_xk.js +2 -0
  32. package/dist/chunks/{rtk-resolver-CLo-gZzw.js → rtk-resolver-jJxy_Mrt.js} +2 -2
  33. package/dist/chunks/{runtime-paths-Ci_ksSkj.js → runtime-paths-BPoxENmK.js} +2 -6
  34. package/dist/chunks/runtime-paths-DU5jBEEO.js +2 -0
  35. package/dist/chunks/{sandbox-deps-Dd5-kZnf.js → sandbox-deps-DuaNFq7n.js} +16 -13
  36. package/dist/chunks/sandbox-proxy-core-hnQgKR0T.js +27 -0
  37. package/dist/chunks/{session-Cez8aIWg.js → session-B8fJb_Or.js} +25 -2
  38. package/dist/chunks/text-DoirglWV.js +21 -0
  39. package/dist/chunks/timer-deps-u2DAQLok.js +15 -0
  40. package/dist/chunks/{visions-cli-BXQFIBCX.js → visions-cli-BhFdym5n.js} +1 -1
  41. package/dist/chunks/{visions-setup-CvVnOe1r.js → visions-setup-CfgNHiG3.js} +20 -357
  42. package/dist/client/assets/index-C_XKsDFx.css +1 -0
  43. package/dist/client/assets/index-CoqWjYxP.js +254 -0
  44. package/dist/client/fonts/CommitMono-OFL.txt +92 -0
  45. package/dist/client/fonts/commit-mono-400-italic.woff2 +0 -0
  46. package/dist/client/fonts/commit-mono-400-regular.woff2 +0 -0
  47. package/dist/client/fonts/commit-mono-700-italic.woff2 +0 -0
  48. package/dist/client/fonts/commit-mono-700-regular.woff2 +0 -0
  49. package/dist/client/index.html +7 -24
  50. package/dist/client/octicons-LICENSE.txt +21 -0
  51. package/dist/scripts/postinstall-path-check.js +1 -1
  52. package/dist/server/index.js +3 -3
  53. package/dist/session/command-hook-relay.js +1 -1
  54. package/dist/session/hook-relay.js +3 -3
  55. package/dist/session/hook-tool-relay.js +2 -2
  56. package/dist/session/sandbox-open-launcher.js +79 -0
  57. package/dist/session/statusline-relay.js +8 -18
  58. package/dist/session/visions-relay.js +20 -11
  59. package/package.json +4 -1
  60. package/dist/chunks/agent-api-cli-D0MmtEdz.js +0 -71
  61. package/dist/chunks/agent-api-core-Dz0r21y9.js +0 -103
  62. package/dist/chunks/child-process-safe-DvQjXsgz.js +0 -2
  63. package/dist/chunks/config-store-CC8T3okC.js +0 -2
  64. package/dist/chunks/ingest-core-DWI1Mbu4.js +0 -342
  65. package/dist/chunks/rtk-resolver-CMQAidlI.js +0 -2
  66. package/dist/chunks/runtime-paths-JLrZ3wyi.js +0 -2
  67. package/dist/client/assets/index-B1MFijjb.css +0 -1
  68. package/dist/client/assets/index-CU1iPPdn.js +0 -249
@@ -0,0 +1,1354 @@
1
+ import { r as errorMessage } from "./text-DoirglWV.js";
2
+ import { s as resolvePackageBin } from "./runtime-paths-BPoxENmK.js";
3
+ import { r as execFileSync } from "./child-process-safe-DDELk9XC.js";
4
+ import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
5
+ import { r as BrowserConfig } from "./config-CZ4H51D0.js";
6
+ import fs, { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync } from "node:fs";
7
+ import path, { dirname, join, posix, resolve, win32 } from "node:path";
8
+ import { parseArgs } from "node:util";
9
+ import os from "node:os";
10
+ import { z } from "zod";
11
+ import { DatabaseSync } from "node:sqlite";
12
+ //#region knowledge-graph/coherence-delta.ts
13
+ var journalRecordKinds = [
14
+ "decision",
15
+ "blocked",
16
+ "conjecture"
17
+ ];
18
+ var openWorkStates = /* @__PURE__ */ new Set([
19
+ "open",
20
+ "active",
21
+ "blocked"
22
+ ]);
23
+ var closedTaskStatuses$1 = /* @__PURE__ */ new Set(["done", "dropped"]);
24
+ var openTaskStatuses = /* @__PURE__ */ new Set([
25
+ "todo",
26
+ "doing",
27
+ "waiting"
28
+ ]);
29
+ function inferRecordKind(recordId) {
30
+ if (/^wrk-[0-9a-f]{16}$/.test(recordId)) return "work";
31
+ if (/^d-[0-9a-f]{8,}$/.test(recordId)) return "decision";
32
+ return null;
33
+ }
34
+ function findStatusDivergence(trackingStatus, workState) {
35
+ if (trackingStatus === void 0) return null;
36
+ if (closedTaskStatuses$1.has(trackingStatus) && openWorkStates.has(workState)) return `kg says ${trackingStatus}, ledger says ${workState}`;
37
+ if (openTaskStatuses.has(trackingStatus) && workState === "completed") return `ledger says completed, kg still ${trackingStatus}`;
38
+ if (trackingStatus !== "dropped" && workState === "cancelled") return `ledger says cancelled, kg still ${trackingStatus}`;
39
+ return null;
40
+ }
41
+ function isAfter(timestamp, since) {
42
+ return since !== void 0 && timestamp !== null && Date.parse(timestamp) > Date.parse(since);
43
+ }
44
+ function hasDecisionMovedSince(decision, since) {
45
+ return isAfter(decision.at, since) || isAfter(decision.retractedAt, since);
46
+ }
47
+ function reportWorkRecord(tracked, snapshot, since) {
48
+ const workOrder = snapshot.workOrders.find((candidate) => candidate.id === tracked.recordId);
49
+ if (!workOrder) return {
50
+ tracked,
51
+ label: tracked.recordId,
52
+ state: "missing",
53
+ findings: [{
54
+ kind: "missing",
55
+ detail: "not in this repo's work ledger"
56
+ }],
57
+ decisions: []
58
+ };
59
+ const findings = [];
60
+ if (snapshot.unverifiedCompletedWorkIds.includes(workOrder.id)) findings.push({
61
+ kind: "unverified",
62
+ detail: "completed with no verification on record"
63
+ });
64
+ const divergence = tracked.trackingKind === "task" ? findStatusDivergence(tracked.trackingStatus, workOrder.state) : null;
65
+ if (divergence) findings.push({
66
+ kind: "status-diverges",
67
+ detail: divergence
68
+ });
69
+ if (isAfter(workOrder.lastEventAt, since)) findings.push({
70
+ kind: "moved-since",
71
+ detail: `last event ${workOrder.lastEventAt}`
72
+ });
73
+ const decisions = snapshot.decisions.filter((decision) => decision.workId === workOrder.id && (since === void 0 || hasDecisionMovedSince(decision, since)));
74
+ return {
75
+ tracked,
76
+ label: workOrder.objective,
77
+ state: `${workOrder.state}/${workOrder.readiness}`,
78
+ findings,
79
+ decisions
80
+ };
81
+ }
82
+ function reportDecisionRecord(tracked, snapshot, since) {
83
+ const decision = snapshot.decisions.find((candidate) => candidate.id === tracked.recordId);
84
+ if (!decision) return {
85
+ tracked,
86
+ label: tracked.recordId,
87
+ state: "missing",
88
+ findings: [{
89
+ kind: "missing",
90
+ detail: "not in this repo's decision journal"
91
+ }],
92
+ decisions: []
93
+ };
94
+ const findings = decision.standing === "retracted" ? [{
95
+ kind: "retracted",
96
+ detail: "withdrawn in the journal; kg notes relying on it need a look"
97
+ }] : [];
98
+ if (isAfter(decision.retractedAt, since)) findings.push({
99
+ kind: "moved-since",
100
+ detail: `retracted ${decision.retractedAt}`
101
+ });
102
+ if (isAfter(decision.at, since)) findings.push({
103
+ kind: "moved-since",
104
+ detail: `decided ${decision.at}`
105
+ });
106
+ return {
107
+ tracked,
108
+ label: decision.chose,
109
+ state: `${decision.kind}/${decision.standing}`,
110
+ findings,
111
+ decisions: []
112
+ };
113
+ }
114
+ function buildCoherenceDelta(trackedRecords, snapshotsByRepo, since) {
115
+ return [...new Set(trackedRecords.map((tracked) => tracked.repo))].toSorted().map((repo) => {
116
+ const repoRecords = trackedRecords.filter((tracked) => tracked.repo === repo);
117
+ const snapshot = snapshotsByRepo.get(repo) ?? {
118
+ isAvailable: false,
119
+ reason: "not read"
120
+ };
121
+ if (!snapshot.isAvailable) return {
122
+ repo,
123
+ isAvailable: false,
124
+ reason: snapshot.reason,
125
+ records: repoRecords
126
+ };
127
+ const records = repoRecords.map((tracked) => tracked.record === "work" ? reportWorkRecord(tracked, snapshot, since) : reportDecisionRecord(tracked, snapshot, since));
128
+ return {
129
+ repo,
130
+ isAvailable: true,
131
+ heading: snapshot.heading,
132
+ headingReasons: snapshot.headingReasons,
133
+ records
134
+ };
135
+ });
136
+ }
137
+ //#endregion
138
+ //#region knowledge-graph/graph-schema.ts
139
+ var UNSAFE_TEXT_CHARACTERS = /[\p{Cc}\p{Zl}\p{Zp}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu;
140
+ var INVISIBLE_TEXT_CHARACTERS = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\s]/gu;
141
+ function hasVisibleText(text) {
142
+ return text.replace(INVISIBLE_TEXT_CHARACTERS, "") !== "";
143
+ }
144
+ function containsUnsafeTextCharacter(text) {
145
+ return text.match(UNSAFE_TEXT_CHARACTERS) !== null;
146
+ }
147
+ function stripUnsafeTextCharacters(text) {
148
+ return text.replace(UNSAFE_TEXT_CHARACTERS, "");
149
+ }
150
+ function splitIntoSafeLines(text) {
151
+ return text.split(/\r\n|[\r\n\u2028\u2029]/).map((line) => line.split(" ").map(stripUnsafeTextCharacters).join(" "));
152
+ }
153
+ function listKnownPropertyKeys(schema, kind) {
154
+ const properties = schema.kinds[kind]?.properties;
155
+ if (properties === void 0) return null;
156
+ if (!("shape" in properties) || typeof properties.shape !== "object" || properties.shape === null) return null;
157
+ return Object.keys(properties.shape);
158
+ }
159
+ function describeUnknownKind(schema, kind) {
160
+ return `unknown kind "${kind}"; known: ${Object.keys(schema.kinds).join(", ")}`;
161
+ }
162
+ function describeUnknownEdgeType(schema, edgeType) {
163
+ return `unknown edge type "${edgeType}"; known: ${Object.keys(schema.edges).join(", ")}`;
164
+ }
165
+ function isKindAllowed(allowedKinds, kind) {
166
+ return allowedKinds === "any" || allowedKinds.includes(kind);
167
+ }
168
+ function describeAllowedKinds(allowedKinds) {
169
+ return allowedKinds === "any" ? "any kind" : allowedKinds.join(" | ");
170
+ }
171
+ function findEdgeViolation(schema, candidate) {
172
+ const definition = schema.edges[candidate.edgeType];
173
+ if (!definition) return describeUnknownEdgeType(schema, candidate.edgeType);
174
+ if (!isKindAllowed(definition.fromKinds, candidate.fromKind)) return `${candidate.edgeType} cannot start at a ${candidate.fromKind} (${candidate.fromId}); allowed: ${describeAllowedKinds(definition.fromKinds)}`;
175
+ if (!isKindAllowed(definition.toKinds, candidate.toKind)) return `${candidate.edgeType} cannot point at a ${candidate.toKind} (${candidate.toId}); allowed: ${describeAllowedKinds(definition.toKinds)}`;
176
+ if (candidate.fromId === candidate.toId) return `${candidate.fromId} cannot ${candidate.edgeType} itself`;
177
+ if (definition.maxOutgoingPerNode !== void 0 && candidate.existingOutgoingCount >= definition.maxOutgoingPerNode) return `${candidate.fromId} already has ${candidate.existingOutgoingCount} ${candidate.edgeType} edge(s); limit is ${definition.maxOutgoingPerNode}`;
178
+ if (definition.isAcyclic && candidate.targetReachesSource) return `${candidate.fromId} ${candidate.edgeType} ${candidate.toId} would create a cycle`;
179
+ return null;
180
+ }
181
+ function parseNodeProperties(schema, kind, rawProperties) {
182
+ const kindDefinition = schema.kinds[kind];
183
+ if (!kindDefinition) return { error: describeUnknownKind(schema, kind) };
184
+ const parsed = kindDefinition.properties.safeParse(rawProperties);
185
+ if (!parsed.success) return { error: `invalid ${kind} properties:\n${z.prettifyError(parsed.error)}` };
186
+ return { properties: parsed.data };
187
+ }
188
+ function findCycleNodeIds(edges) {
189
+ const targetsBySource = /* @__PURE__ */ new Map();
190
+ for (const edge of edges) targetsBySource.set(edge.fromId, [...targetsBySource.get(edge.fromId) ?? [], edge.toId]);
191
+ const finishedIds = /* @__PURE__ */ new Set();
192
+ const onPathIds = /* @__PURE__ */ new Set();
193
+ const cycleNodeIds = /* @__PURE__ */ new Set();
194
+ const visit = (nodeId) => {
195
+ if (finishedIds.has(nodeId)) return;
196
+ if (onPathIds.has(nodeId)) {
197
+ cycleNodeIds.add(nodeId);
198
+ return;
199
+ }
200
+ onPathIds.add(nodeId);
201
+ for (const targetId of targetsBySource.get(nodeId) ?? []) visit(targetId);
202
+ onPathIds.delete(nodeId);
203
+ finishedIds.add(nodeId);
204
+ };
205
+ for (const sourceId of targetsBySource.keys()) visit(sourceId);
206
+ return [...cycleNodeIds];
207
+ }
208
+ function findIntegrityViolations(schema, nodes, edges) {
209
+ const violations = [];
210
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
211
+ for (const node of nodes) {
212
+ const parsed = parseNodeProperties(schema, node.kind, node.properties);
213
+ if ("error" in parsed) violations.push(`${node.id}: ${parsed.error}`);
214
+ }
215
+ const outgoingCountByTypeAndSource = /* @__PURE__ */ new Map();
216
+ for (const edge of edges) {
217
+ const fromNode = nodesById.get(edge.fromId);
218
+ const toNode = nodesById.get(edge.toId);
219
+ if (!fromNode || !toNode) {
220
+ violations.push(`${edge.fromId} ${edge.edgeType} ${edge.toId}: dangling edge`);
221
+ continue;
222
+ }
223
+ const countKey = `${edge.edgeType}\u0000${edge.fromId}`;
224
+ const existingOutgoingCount = outgoingCountByTypeAndSource.get(countKey) ?? 0;
225
+ outgoingCountByTypeAndSource.set(countKey, existingOutgoingCount + 1);
226
+ const violation = findEdgeViolation(schema, {
227
+ edgeType: edge.edgeType,
228
+ fromId: edge.fromId,
229
+ fromKind: fromNode.kind,
230
+ toId: edge.toId,
231
+ toKind: toNode.kind,
232
+ existingOutgoingCount,
233
+ targetReachesSource: false
234
+ });
235
+ if (violation) violations.push(violation);
236
+ }
237
+ for (const [edgeType, definition] of Object.entries(schema.edges)) {
238
+ if (!definition.isAcyclic) continue;
239
+ const cycleNodeIds = findCycleNodeIds(edges.filter((edge) => edge.edgeType === edgeType));
240
+ if (cycleNodeIds.length > 0) violations.push(`${edgeType} cycle through ${cycleNodeIds.join(", ")}`);
241
+ }
242
+ return violations;
243
+ }
244
+ //#endregion
245
+ //#region knowledge-graph/coherence-source.ts
246
+ var LedgerText = z.string().transform(stripUnsafeTextCharacters);
247
+ var CoherenceRefusal = z.looseObject({ error: LedgerText });
248
+ var WorkInspectOutput = z.looseObject({ work: z.array(z.looseObject({
249
+ work: LedgerText,
250
+ state: LedgerText,
251
+ readiness: LedgerText,
252
+ opened: z.looseObject({ objective: LedgerText }),
253
+ last: z.looseObject({ at: LedgerText }).nullable()
254
+ })) });
255
+ var OrientHeading = z.looseObject({
256
+ action: LedgerText,
257
+ reasons: z.array(LedgerText)
258
+ });
259
+ var OrientOutput = OrientHeading.extend({ consequences: z.looseObject({ unverifiedCompletedWork: z.array(LedgerText) }) });
260
+ var TrackableJournalKind = z.enum(journalRecordKinds);
261
+ var JournalRow = z.looseObject({
262
+ id: LedgerText,
263
+ kind: LedgerText,
264
+ at: LedgerText,
265
+ chose: LedgerText.optional(),
266
+ because: LedgerText.optional(),
267
+ work: LedgerText.optional(),
268
+ supersedes: LedgerText.optional()
269
+ });
270
+ var JOURNAL_OPEN_FLAGS = constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0);
271
+ function readRegularFile(filePath, displayName) {
272
+ if (!lstatSync(filePath).isFile()) throw new Error(`${displayName} is not a regular file`);
273
+ const fileDescriptor = openSync(filePath, JOURNAL_OPEN_FLAGS);
274
+ try {
275
+ if (!fstatSync(fileDescriptor).isFile()) throw new Error(`${displayName} is not a regular file`);
276
+ return readFileSync(fileDescriptor, "utf8");
277
+ } finally {
278
+ closeSync(fileDescriptor);
279
+ }
280
+ }
281
+ function readCompleteJournalLines(filePath, displayName) {
282
+ return readRegularFile(filePath, displayName).split("\n").slice(0, -1).filter((line) => line.trim() !== "");
283
+ }
284
+ function findEarliestRetractionAtById(rows) {
285
+ const retractedAtById = /* @__PURE__ */ new Map();
286
+ for (const row of rows) {
287
+ if (row.kind !== "retraction" || !row.supersedes) continue;
288
+ const knownAt = retractedAtById.get(row.supersedes);
289
+ if (knownAt !== void 0 && Date.parse(knownAt) <= Date.parse(row.at)) continue;
290
+ retractedAtById.set(row.supersedes, row.at);
291
+ }
292
+ return retractedAtById;
293
+ }
294
+ function findSettlementById(rows, retractedAtById) {
295
+ const settlementById = /* @__PURE__ */ new Map();
296
+ for (const row of rows) {
297
+ if (!row.supersedes || retractedAtById.has(row.id)) continue;
298
+ if (row.kind === "resolution") settlementById.set(row.supersedes, "resolved");
299
+ if (row.kind === "dismissal" && settlementById.get(row.supersedes) !== "resolved") settlementById.set(row.supersedes, "dismissed");
300
+ }
301
+ return settlementById;
302
+ }
303
+ function listJournalFiles(journalDirectory, relativeDirectory) {
304
+ return readdirSync(join(journalDirectory, relativeDirectory), { withFileTypes: true }).flatMap((entry) => {
305
+ const relativePath = join(relativeDirectory, entry.name);
306
+ if (entry.name.endsWith(".jsonl")) return [relativePath];
307
+ if (entry.isSymbolicLink()) throw new Error(`${relativePath} is a symbolic link`);
308
+ if (entry.isDirectory()) return listJournalFiles(journalDirectory, relativePath);
309
+ return [];
310
+ });
311
+ }
312
+ function readDecisionJournal(repo) {
313
+ const journalDirectory = join(repo, ".coherence", "decisions");
314
+ const journalDirectoryStats = lstatSync(journalDirectory, { throwIfNoEntry: false });
315
+ if (journalDirectoryStats === void 0) return [];
316
+ if (!journalDirectoryStats.isDirectory()) throw new Error(".coherence/decisions is not a directory");
317
+ const rows = listJournalFiles(journalDirectory, "").flatMap((fileName) => readCompleteJournalLines(join(journalDirectory, fileName), fileName).map((line, index) => {
318
+ const parsed = JournalRow.safeParse(JSON.parse(line));
319
+ if (!parsed.success) throw new Error(`unreadable journal row ${fileName}:${index + 1}`);
320
+ return parsed.data;
321
+ }));
322
+ const retractedAtById = findEarliestRetractionAtById(rows);
323
+ const settlementById = findSettlementById(rows, retractedAtById);
324
+ const standingOf = (row, isConjecture) => {
325
+ if (retractedAtById.has(row.id)) return "retracted";
326
+ if (!isConjecture) return "standing";
327
+ return settlementById.get(row.id) ?? "open";
328
+ };
329
+ return rows.flatMap((row) => {
330
+ const kind = TrackableJournalKind.safeParse(row.kind);
331
+ if (!kind.success) return [];
332
+ return [{
333
+ id: row.id,
334
+ kind: kind.data,
335
+ standing: standingOf(row, kind.data === "conjecture"),
336
+ chose: row.chose ?? "",
337
+ because: row.because ?? "",
338
+ at: row.at,
339
+ workId: row.work ?? null,
340
+ retractedAt: retractedAtById.get(row.id) ?? null
341
+ }];
342
+ }).toSorted((left, right) => left.at.localeCompare(right.at));
343
+ }
344
+ function describeShapeDrift(commandName, error) {
345
+ const firstIssue = error.issues[0];
346
+ if (firstIssue === void 0) return `unexpected ${commandName} output`;
347
+ return stripUnsafeTextCharacters(`unexpected ${commandName} output at ${firstIssue.path.length === 0 ? "top level" : firstIssue.path.join(".")}: ${firstIssue.message}`);
348
+ }
349
+ function describeOrientFailure(orientJson, error) {
350
+ const heading = OrientHeading.safeParse(orientJson);
351
+ if (heading.success && heading.data.action === "refuse") return `orient refused: ${heading.data.reasons.join("; ")}`;
352
+ return describeShapeDrift("coherence orient", error);
353
+ }
354
+ function describeReadFailure(error) {
355
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return "coherence CLI not found";
356
+ return stripUnsafeTextCharacters(error instanceof Error ? error.message.split("\n")[0] ?? "unknown error" : String(error));
357
+ }
358
+ function readRepoSnapshot(repo, runCoherence) {
359
+ const runCoherenceJson = (commandArguments) => JSON.parse(runCoherence(repo, commandArguments));
360
+ if (!existsSync(join(repo, ".coherence"))) return {
361
+ isAvailable: false,
362
+ reason: "no .coherence ledger in this repo"
363
+ };
364
+ try {
365
+ const workInspectJson = runCoherenceJson([
366
+ "work",
367
+ "inspect",
368
+ "--json"
369
+ ]);
370
+ const workRefusal = CoherenceRefusal.safeParse(workInspectJson);
371
+ if (workRefusal.success) return {
372
+ isAvailable: false,
373
+ reason: `work ledger refused: ${workRefusal.data.error}`
374
+ };
375
+ const workInspect = WorkInspectOutput.safeParse(workInspectJson);
376
+ if (!workInspect.success) return {
377
+ isAvailable: false,
378
+ reason: describeShapeDrift("coherence work inspect", workInspect.error)
379
+ };
380
+ const orientJson = runCoherenceJson(["orient", "--json"]);
381
+ const orient = OrientOutput.safeParse(orientJson);
382
+ if (!orient.success) return {
383
+ isAvailable: false,
384
+ reason: describeOrientFailure(orientJson, orient.error)
385
+ };
386
+ const workOrders = workInspect.data.work.map((entry) => ({
387
+ id: entry.work,
388
+ objective: entry.opened.objective,
389
+ state: entry.state,
390
+ readiness: entry.readiness,
391
+ lastEventAt: entry.last?.at ?? null
392
+ }));
393
+ return {
394
+ isAvailable: true,
395
+ heading: orient.data.action,
396
+ headingReasons: orient.data.reasons,
397
+ workOrders,
398
+ unverifiedCompletedWorkIds: orient.data.consequences.unverifiedCompletedWork,
399
+ decisions: readDecisionJournal(repo)
400
+ };
401
+ } catch (error) {
402
+ return {
403
+ isAvailable: false,
404
+ reason: describeReadFailure(error)
405
+ };
406
+ }
407
+ }
408
+ //#endregion
409
+ //#region knowledge-graph/graph-store.ts
410
+ var NodeRow = z.object({
411
+ id: z.string(),
412
+ kind: z.string(),
413
+ title: z.string(),
414
+ body: z.string(),
415
+ properties: z.string(),
416
+ created_at: z.string(),
417
+ updated_at: z.string()
418
+ });
419
+ var EdgeRow = z.object({
420
+ edge_type: z.string(),
421
+ from_id: z.string(),
422
+ to_id: z.string(),
423
+ created_at: z.string()
424
+ });
425
+ var MetaRow = z.object({ value: z.string() });
426
+ var ColumnNameRow = z.object({ name: z.string() });
427
+ var CountRow = z.object({ edge_count: z.number() });
428
+ var NumberRow = z.object({ last_number: z.number() });
429
+ var NeighbourRow = z.object({ id: z.string() });
430
+ var SqliteBusyError = z.looseObject({ errcode: z.literal(5) });
431
+ var tableDefinitions = `
432
+ CREATE TABLE IF NOT EXISTS graph_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
433
+ CREATE TABLE IF NOT EXISTS nodes (
434
+ id TEXT PRIMARY KEY,
435
+ kind TEXT NOT NULL,
436
+ title TEXT NOT NULL CHECK (length(trim(title)) > 0),
437
+ body TEXT NOT NULL DEFAULT '',
438
+ properties TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(properties)),
439
+ created_at TEXT NOT NULL,
440
+ updated_at TEXT NOT NULL
441
+ );
442
+ CREATE INDEX IF NOT EXISTS nodes_by_kind ON nodes (kind);
443
+ CREATE TABLE IF NOT EXISTS edges (
444
+ edge_type TEXT NOT NULL,
445
+ from_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,
446
+ to_id TEXT NOT NULL REFERENCES nodes (id) ON DELETE CASCADE,
447
+ created_at TEXT NOT NULL,
448
+ PRIMARY KEY (edge_type, from_id, to_id)
449
+ );
450
+ CREATE INDEX IF NOT EXISTS edges_by_target ON edges (to_id, edge_type);
451
+ CREATE TABLE IF NOT EXISTS id_counters (prefix TEXT PRIMARY KEY, last_number INTEGER NOT NULL);
452
+ CREATE VIRTUAL TABLE IF NOT EXISTS node_search USING fts5 (id UNINDEXED, title, body);
453
+ `;
454
+ function toGraphNode(row) {
455
+ const parsedRow = NodeRow.parse(row);
456
+ return {
457
+ id: parsedRow.id,
458
+ kind: parsedRow.kind,
459
+ title: parsedRow.title,
460
+ body: parsedRow.body,
461
+ properties: z.record(z.string(), z.unknown()).parse(JSON.parse(parsedRow.properties)),
462
+ createdAt: parsedRow.created_at,
463
+ updatedAt: parsedRow.updated_at
464
+ };
465
+ }
466
+ function toGraphEdge(row) {
467
+ const parsedRow = EdgeRow.parse(row);
468
+ return {
469
+ edgeType: parsedRow.edge_type,
470
+ fromId: parsedRow.from_id,
471
+ toId: parsedRow.to_id,
472
+ createdAt: parsedRow.created_at
473
+ };
474
+ }
475
+ function toSearchExpression(text) {
476
+ return text.split(/\s+/).filter((token) => token.length > 0).map((token) => `"${token.replaceAll("\"", "\"\"")}"*`).join(" ");
477
+ }
478
+ function withoutEmptyValues(properties) {
479
+ return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== void 0 && value !== ""));
480
+ }
481
+ var BUSY_TIMEOUT_MS = 5e3;
482
+ var JOURNAL_SWITCH_RETRY_MS = 10;
483
+ function normalizeTitle(title) {
484
+ const trimmedTitle = title.trim();
485
+ if (!hasVisibleText(trimmedTitle)) throw new Error("title must not be empty");
486
+ if (containsUnsafeTextCharacter(trimmedTitle)) throw new Error("title must be one line without control or bidi characters");
487
+ return trimmedTitle;
488
+ }
489
+ function readOwnership(database) {
490
+ if (!(database.prepare("SELECT 1 AS found FROM sqlite_master WHERE substr(name, 1, 7) <> 'sqlite_' LIMIT 1").get() !== void 0)) return { kind: "fresh" };
491
+ const metaColumnNames = database.prepare("SELECT name FROM pragma_table_info('graph_meta')").all().map((row) => ColumnNameRow.parse(row).name);
492
+ if (!metaColumnNames.includes("key") || !metaColumnNames.includes("value")) return { kind: "foreign" };
493
+ const storedName = MetaRow.safeParse(database.prepare("SELECT value FROM graph_meta WHERE key = 'schema_name'").get());
494
+ return storedName.success ? {
495
+ kind: "owned",
496
+ schemaName: storedName.data.value
497
+ } : { kind: "foreign" };
498
+ }
499
+ function refuseUnlessClaimable(ownership, databasePath, schema) {
500
+ if (ownership.kind === "foreign") throw new Error(`${databasePath} is not a knowledge graph database; refusing to claim it`);
501
+ if (ownership.kind === "owned" && ownership.schemaName !== schema.name) throw new Error(`${databasePath} belongs to the "${ownership.schemaName}" graph, not "${schema.name}"`);
502
+ }
503
+ function runInTransaction(database, work, beginStatement = "BEGIN IMMEDIATE") {
504
+ if (database.isTransaction) return work();
505
+ database.exec(beginStatement);
506
+ try {
507
+ const value = work();
508
+ database.exec("COMMIT");
509
+ return value;
510
+ } catch (error) {
511
+ database.exec("ROLLBACK");
512
+ throw error;
513
+ }
514
+ }
515
+ function tryEnableWriteAheadLog(database, deadlineMs) {
516
+ try {
517
+ database.exec("PRAGMA journal_mode = WAL");
518
+ return true;
519
+ } catch (error) {
520
+ if (SqliteBusyError.safeParse(error).success && Date.now() < deadlineMs) return false;
521
+ throw error;
522
+ }
523
+ }
524
+ function enableWriteAheadLog(database) {
525
+ const deadlineMs = Date.now() + BUSY_TIMEOUT_MS;
526
+ const retryPause = new Int32Array(new SharedArrayBuffer(4));
527
+ while (!tryEnableWriteAheadLog(database, deadlineMs)) Atomics.wait(retryPause, 0, 0, JOURNAL_SWITCH_RETRY_MS);
528
+ }
529
+ function claimDatabase(database, databasePath, schema) {
530
+ refuseUnlessClaimable(readOwnership(database), databasePath, schema);
531
+ database.exec("PRAGMA foreign_keys = ON");
532
+ runInTransaction(database, () => {
533
+ const ownership = readOwnership(database);
534
+ refuseUnlessClaimable(ownership, databasePath, schema);
535
+ database.exec(tableDefinitions);
536
+ if (ownership.kind !== "fresh") return;
537
+ database.prepare("INSERT INTO graph_meta (key, value) VALUES ('schema_name', ?), ('schema_version', ?)").run(schema.name, String(schema.version));
538
+ });
539
+ enableWriteAheadLog(database);
540
+ }
541
+ function openGraphStore(databasePath, schema, currentTimestamp = () => (/* @__PURE__ */ new Date()).toISOString()) {
542
+ const database = new DatabaseSync(databasePath, { timeout: BUSY_TIMEOUT_MS });
543
+ try {
544
+ claimDatabase(database, databasePath, schema);
545
+ } catch (error) {
546
+ database.close();
547
+ throw error;
548
+ }
549
+ const transaction = (work) => runInTransaction(database, work);
550
+ const getNode = (id) => {
551
+ const row = database.prepare("SELECT * FROM nodes WHERE id = ?").get(id);
552
+ return row === void 0 ? null : toGraphNode(row);
553
+ };
554
+ const requireNode = (id) => {
555
+ const node = getNode(id);
556
+ if (!node) throw new Error(`no node with id ${id}`);
557
+ return node;
558
+ };
559
+ const writeSearchEntry = (node) => {
560
+ database.prepare("DELETE FROM node_search WHERE id = ?").run(node.id);
561
+ database.prepare("INSERT INTO node_search (id, title, body) VALUES (?, ?, ?)").run(node.id, node.title, node.body);
562
+ };
563
+ const claimNextId = (idPrefix) => {
564
+ const row = database.prepare("INSERT INTO id_counters (prefix, last_number) VALUES (?, 1) ON CONFLICT (prefix) DO UPDATE SET last_number = last_number + 1 RETURNING last_number").get(idPrefix);
565
+ return `${idPrefix}-${NumberRow.parse(row).last_number}`;
566
+ };
567
+ const parsePropertiesOrThrow = (kind, rawProperties) => {
568
+ const parsed = parseNodeProperties(schema, kind, rawProperties);
569
+ if ("error" in parsed) throw new Error(parsed.error);
570
+ return parsed.properties;
571
+ };
572
+ const addNode = (kind, title, rawProperties = {}, body = "") => {
573
+ const properties = parsePropertiesOrThrow(kind, withoutEmptyValues(rawProperties));
574
+ const normalizedTitle = normalizeTitle(title);
575
+ const idPrefix = schema.kinds[kind]?.idPrefix ?? kind;
576
+ return transaction(() => {
577
+ const timestamp = currentTimestamp();
578
+ const id = claimNextId(idPrefix);
579
+ database.prepare("INSERT INTO nodes (id, kind, title, body, properties, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, kind, normalizedTitle, body, JSON.stringify(properties), timestamp, timestamp);
580
+ const node = requireNode(id);
581
+ writeSearchEntry(node);
582
+ return node;
583
+ });
584
+ };
585
+ const updateNode = (id, changes) => {
586
+ const title = changes.title === void 0 ? void 0 : normalizeTitle(changes.title);
587
+ return transaction(() => {
588
+ const existing = requireNode(id);
589
+ const mergedProperties = withoutEmptyValues({
590
+ ...existing.properties,
591
+ ...changes.properties
592
+ });
593
+ const properties = parsePropertiesOrThrow(existing.kind, mergedProperties);
594
+ database.prepare("UPDATE nodes SET title = ?, body = ?, properties = ?, updated_at = ? WHERE id = ?").run(title ?? existing.title, changes.body ?? existing.body, JSON.stringify(properties), currentTimestamp(), id);
595
+ const node = requireNode(id);
596
+ writeSearchEntry(node);
597
+ return node;
598
+ });
599
+ };
600
+ const removeNode = (id) => {
601
+ requireNode(id);
602
+ transaction(() => {
603
+ database.prepare("DELETE FROM nodes WHERE id = ?").run(id);
604
+ database.prepare("DELETE FROM node_search WHERE id = ?").run(id);
605
+ });
606
+ };
607
+ const isReachable = (edgeType, startId, targetId) => {
608
+ return database.prepare(`
609
+ WITH RECURSIVE reachable (id) AS (
610
+ SELECT ?
611
+ UNION
612
+ SELECT edges.to_id FROM edges JOIN reachable ON edges.from_id = reachable.id WHERE edges.edge_type = ?
613
+ )
614
+ SELECT 1 AS found FROM reachable WHERE id = ? LIMIT 1`).get(startId, edgeType, targetId) !== void 0;
615
+ };
616
+ const findEdge = (fromId, edgeType, toId) => {
617
+ const row = database.prepare("SELECT * FROM edges WHERE edge_type = ? AND from_id = ? AND to_id = ?").get(edgeType, fromId, toId);
618
+ return row === void 0 ? null : toGraphEdge(row);
619
+ };
620
+ const link = (fromId, edgeType, toId) => {
621
+ const fromNode = requireNode(fromId);
622
+ const toNode = requireNode(toId);
623
+ return transaction(() => {
624
+ const existingEdge = findEdge(fromId, edgeType, toId);
625
+ if (existingEdge) return existingEdge;
626
+ const countRow = database.prepare("SELECT count(*) AS edge_count FROM edges WHERE edge_type = ? AND from_id = ?").get(edgeType, fromId);
627
+ const violation = findEdgeViolation(schema, {
628
+ edgeType,
629
+ fromId,
630
+ fromKind: fromNode.kind,
631
+ toId,
632
+ toKind: toNode.kind,
633
+ existingOutgoingCount: CountRow.parse(countRow).edge_count,
634
+ targetReachesSource: isReachable(edgeType, toId, fromId)
635
+ });
636
+ if (violation) throw new Error(violation);
637
+ const createdAt = currentTimestamp();
638
+ database.prepare("INSERT INTO edges (edge_type, from_id, to_id, created_at) VALUES (?, ?, ?, ?)").run(edgeType, fromId, toId, createdAt);
639
+ return {
640
+ edgeType,
641
+ fromId,
642
+ toId,
643
+ createdAt
644
+ };
645
+ });
646
+ };
647
+ const unlink = (fromId, edgeType, toId) => Number(database.prepare("DELETE FROM edges WHERE edge_type = ? AND from_id = ? AND to_id = ?").run(edgeType, fromId, toId).changes) > 0;
648
+ const refuseUnknownFilters = (kind, propertyFilters) => {
649
+ if (kind !== void 0 && !schema.kinds[kind]) throw new Error(describeUnknownKind(schema, kind));
650
+ const filterKeys = Object.keys(propertyFilters);
651
+ if (filterKeys.length === 0) return;
652
+ const knownKeysPerKind = (kind === void 0 ? Object.keys(schema.kinds) : [kind]).map((kindInScope) => listKnownPropertyKeys(schema, kindInScope));
653
+ if (knownKeysPerKind.includes(null)) return;
654
+ const knownKeys = new Set(knownKeysPerKind.flatMap((keys) => keys ?? []));
655
+ const unknownKey = filterKeys.find((key) => !knownKeys.has(key));
656
+ if (unknownKey !== void 0) throw new Error(`unknown field "${unknownKey}"${kind === void 0 ? "" : ` for ${kind}`}; known: ${[...knownKeys].join(", ")}`);
657
+ };
658
+ const listNodes = (kind, propertyFilters = {}) => {
659
+ refuseUnknownFilters(kind, propertyFilters);
660
+ const rows = kind === void 0 ? database.prepare("SELECT * FROM nodes ORDER BY created_at, id").all() : database.prepare("SELECT * FROM nodes WHERE kind = ? ORDER BY created_at, id").all(kind);
661
+ const filterEntries = Object.entries(propertyFilters);
662
+ return rows.map(toGraphNode).filter((node) => filterEntries.every(([key, expected]) => String(node.properties[key] ?? "") === expected));
663
+ };
664
+ const listEdges = (edgeType) => {
665
+ return (edgeType === void 0 ? database.prepare("SELECT * FROM edges ORDER BY created_at").all() : database.prepare("SELECT * FROM edges WHERE edge_type = ? ORDER BY created_at").all(edgeType)).map(toGraphEdge);
666
+ };
667
+ const outgoingEdges = (id) => database.prepare("SELECT * FROM edges WHERE from_id = ? ORDER BY edge_type, to_id").all(id).map(toGraphEdge);
668
+ const incomingEdges = (id) => database.prepare("SELECT * FROM edges WHERE to_id = ? ORDER BY edge_type, from_id").all(id).map(toGraphEdge);
669
+ const walkWithinSnapshot = (startId, edgeType, direction) => {
670
+ requireNode(startId);
671
+ if (!schema.edges[edgeType]) throw new Error(describeUnknownEdgeType(schema, edgeType));
672
+ const neighbourQuery = direction === "outgoing" ? database.prepare("SELECT to_id AS id FROM edges WHERE edge_type = ? AND from_id = ?") : database.prepare("SELECT from_id AS id FROM edges WHERE edge_type = ? AND to_id = ?");
673
+ const visitedIds = /* @__PURE__ */ new Set([startId]);
674
+ const steps = [];
675
+ let frontierIds = [startId];
676
+ for (let depth = 1; frontierIds.length > 0; depth += 1) {
677
+ const nextFrontierIds = [];
678
+ for (const nodeId of frontierIds) for (const row of neighbourQuery.all(edgeType, nodeId)) {
679
+ const neighbourId = NeighbourRow.parse(row).id;
680
+ if (visitedIds.has(neighbourId)) continue;
681
+ visitedIds.add(neighbourId);
682
+ nextFrontierIds.push(neighbourId);
683
+ }
684
+ nextFrontierIds.sort();
685
+ for (const nodeId of nextFrontierIds) steps.push({
686
+ node: requireNode(nodeId),
687
+ depth
688
+ });
689
+ frontierIds = nextFrontierIds;
690
+ }
691
+ return steps;
692
+ };
693
+ const walk = (startId, edgeType, direction) => runInTransaction(database, () => walkWithinSnapshot(startId, edgeType, direction), "BEGIN DEFERRED");
694
+ const search = (text, limit = 20) => {
695
+ const expression = toSearchExpression(text);
696
+ if (expression === "") return [];
697
+ return database.prepare("SELECT nodes.* FROM node_search JOIN nodes ON nodes.id = node_search.id WHERE node_search MATCH ? ORDER BY rank LIMIT ?").all(expression, limit).map(toGraphNode);
698
+ };
699
+ const checkIntegrity = () => findIntegrityViolations(schema, listNodes(), listEdges());
700
+ return {
701
+ schema,
702
+ addNode,
703
+ updateNode,
704
+ removeNode,
705
+ getNode,
706
+ requireNode,
707
+ link,
708
+ unlink,
709
+ listNodes,
710
+ listEdges,
711
+ outgoingEdges,
712
+ incomingEdges,
713
+ walk,
714
+ search,
715
+ checkIntegrity,
716
+ transaction,
717
+ close: () => database.close()
718
+ };
719
+ }
720
+ //#endregion
721
+ //#region knowledge-graph/personal-schema.ts
722
+ var TaskStatus = z.enum([
723
+ "todo",
724
+ "doing",
725
+ "waiting",
726
+ "done",
727
+ "dropped"
728
+ ]);
729
+ var ProjectStatus = z.enum([
730
+ "active",
731
+ "paused",
732
+ "done",
733
+ "archived"
734
+ ]);
735
+ var TaskPriority = z.enum([
736
+ "p0",
737
+ "p1",
738
+ "p2",
739
+ "p3"
740
+ ]);
741
+ var AbsoluteRepoPath = z.string().refine((repoPath) => posix.isAbsolute(repoPath) || win32.isAbsolute(repoPath), { message: "repo must be an absolute path" });
742
+ var personalSchema = {
743
+ name: "personal",
744
+ version: 1,
745
+ kinds: {
746
+ project: {
747
+ idPrefix: "P",
748
+ description: "A body of work with an outcome (a work project, a side project)",
749
+ properties: z.strictObject({
750
+ status: ProjectStatus.default("active"),
751
+ area: z.enum(["work", "personal"]).default("work")
752
+ })
753
+ },
754
+ task: {
755
+ idPrefix: "T",
756
+ description: "A concrete next action",
757
+ properties: z.strictObject({
758
+ status: TaskStatus.default("todo"),
759
+ priority: TaskPriority.default("p2"),
760
+ due: z.iso.date().optional()
761
+ })
762
+ },
763
+ note: {
764
+ idPrefix: "N",
765
+ description: "Knowledge you want to keep: a fact, a how-to, an insight, a log entry",
766
+ properties: z.strictObject({ noteType: z.enum([
767
+ "fact",
768
+ "howto",
769
+ "insight",
770
+ "log"
771
+ ]).default("insight") })
772
+ },
773
+ reference: {
774
+ idPrefix: "R",
775
+ description: "An external source: article, paper, book, repo, doc, video, talk",
776
+ properties: z.strictObject({
777
+ medium: z.enum([
778
+ "article",
779
+ "paper",
780
+ "book",
781
+ "repo",
782
+ "doc",
783
+ "video",
784
+ "talk"
785
+ ]).default("article"),
786
+ url: z.url().optional(),
787
+ author: z.string().min(1).optional()
788
+ })
789
+ },
790
+ question: {
791
+ idPrefix: "Q",
792
+ description: "An open research question",
793
+ properties: z.strictObject({ status: z.enum([
794
+ "open",
795
+ "answered",
796
+ "parked"
797
+ ]).default("open") })
798
+ },
799
+ coherence_record: {
800
+ idPrefix: "C",
801
+ description: "A pointer to a ticket or decision in a repo's Coherence ledger; the ledger stays the source of truth",
802
+ properties: z.strictObject({
803
+ repo: AbsoluteRepoPath,
804
+ record: z.enum(["work", "decision"]),
805
+ recordId: z.string()
806
+ }).refine((pointer) => inferRecordKind(pointer.recordId) === pointer.record, {
807
+ message: "recordId does not match record kind",
808
+ path: ["recordId"]
809
+ })
810
+ },
811
+ topic: {
812
+ idPrefix: "TP",
813
+ description: "A subject area that cuts across projects",
814
+ properties: z.strictObject({})
815
+ }
816
+ },
817
+ edges: {
818
+ part_of: {
819
+ description: "Belongs to a project",
820
+ fromKinds: [
821
+ "task",
822
+ "note",
823
+ "reference",
824
+ "question"
825
+ ],
826
+ toKinds: ["project"],
827
+ maxOutgoingPerNode: 1
828
+ },
829
+ subtask_of: {
830
+ description: "Is a step of a larger task",
831
+ fromKinds: ["task"],
832
+ toKinds: ["task"],
833
+ maxOutgoingPerNode: 1,
834
+ isAcyclic: true
835
+ },
836
+ blocks: {
837
+ description: "Must finish before the target task can start",
838
+ fromKinds: ["task"],
839
+ toKinds: ["task"],
840
+ isAcyclic: true
841
+ },
842
+ cites: {
843
+ description: "Draws on an external source",
844
+ fromKinds: [
845
+ "note",
846
+ "question",
847
+ "task",
848
+ "project"
849
+ ],
850
+ toKinds: ["reference"]
851
+ },
852
+ answers: {
853
+ description: "Resolves a research question",
854
+ fromKinds: ["note"],
855
+ toKinds: ["question"]
856
+ },
857
+ supersedes: {
858
+ description: "Replaces an older note",
859
+ fromKinds: ["note"],
860
+ toKinds: ["note"],
861
+ maxOutgoingPerNode: 1,
862
+ isAcyclic: true
863
+ },
864
+ tracked_by: {
865
+ description: "Is carried out or settled by a record in a Coherence ledger",
866
+ fromKinds: [
867
+ "project",
868
+ "task",
869
+ "note",
870
+ "question"
871
+ ],
872
+ toKinds: ["coherence_record"]
873
+ },
874
+ about: {
875
+ description: "Is about a topic",
876
+ fromKinds: "any",
877
+ toKinds: ["topic"]
878
+ },
879
+ relates_to: {
880
+ description: "Loosely related; prefer a specific edge when one fits",
881
+ fromKinds: "any",
882
+ toKinds: "any"
883
+ }
884
+ }
885
+ };
886
+ var closedTaskStatuses = /* @__PURE__ */ new Set(["done", "dropped"]);
887
+ var actionableTaskStatuses = /* @__PURE__ */ new Set(["todo", "doing"]);
888
+ var priorityRank = {
889
+ p0: 0,
890
+ p1: 1,
891
+ p2: 2,
892
+ p3: 3
893
+ };
894
+ function isTaskClosed(status) {
895
+ return typeof status === "string" && closedTaskStatuses.has(status);
896
+ }
897
+ function rankNextActions(candidates) {
898
+ const isActionable = (candidate) => actionableTaskStatuses.has(candidate.status) && candidate.openBlockerIds.length === 0 && (candidate.projectStatus === void 0 || candidate.projectStatus === "active");
899
+ const compareCandidates = (left, right) => {
900
+ const statusOrder = Number(right.status === "doing") - Number(left.status === "doing");
901
+ if (statusOrder !== 0) return statusOrder;
902
+ const priorityOrder = (priorityRank[left.priority] ?? 9) - (priorityRank[right.priority] ?? 9);
903
+ if (priorityOrder !== 0) return priorityOrder;
904
+ return (left.due ?? "9999-12-31").localeCompare(right.due ?? "9999-12-31");
905
+ };
906
+ return candidates.filter(isActionable).toSorted(compareCandidates);
907
+ }
908
+ //#endregion
909
+ //#region knowledge-graph/graph-views.ts
910
+ function readTaskHierarchy(store) {
911
+ const projectIdByNode = new Map(store.listEdges("part_of").map((edge) => [edge.fromId, edge.toId]));
912
+ const parentIdByTask = new Map(store.listEdges("subtask_of").map((edge) => [edge.fromId, edge.toId]));
913
+ const lineageOf = (nodeId) => {
914
+ const lineage = /* @__PURE__ */ new Set();
915
+ let currentId = nodeId;
916
+ while (currentId !== void 0 && !lineage.has(currentId)) {
917
+ lineage.add(currentId);
918
+ currentId = parentIdByTask.get(currentId);
919
+ }
920
+ return [...lineage];
921
+ };
922
+ const projectIdOf = (nodeId) => lineageOf(nodeId).map((lineageId) => projectIdByNode.get(lineageId)).find((projectId) => projectId !== void 0);
923
+ return {
924
+ lineageOf,
925
+ projectIdOf
926
+ };
927
+ }
928
+ function collectNextActions(store) {
929
+ const tasks = store.listNodes("task");
930
+ const nodesById = new Map(store.listNodes().map((node) => [node.id, node]));
931
+ const openBlockerIdsByTask = /* @__PURE__ */ new Map();
932
+ for (const edge of store.listEdges("blocks")) {
933
+ if (isTaskClosed(nodesById.get(edge.fromId)?.properties.status)) continue;
934
+ openBlockerIdsByTask.set(edge.toId, [...openBlockerIdsByTask.get(edge.toId) ?? [], edge.fromId]);
935
+ }
936
+ const hierarchy = readTaskHierarchy(store);
937
+ return rankNextActions(tasks.map((task) => {
938
+ const projectId = hierarchy.projectIdOf(task.id);
939
+ const projectStatus = projectId === void 0 ? void 0 : nodesById.get(projectId)?.properties.status;
940
+ return {
941
+ id: task.id,
942
+ title: task.title,
943
+ status: String(task.properties.status),
944
+ priority: String(task.properties.priority),
945
+ due: typeof task.properties.due === "string" ? task.properties.due : void 0,
946
+ projectStatus: typeof projectStatus === "string" ? projectStatus : void 0,
947
+ openBlockerIds: openBlockerIdsByTask.get(task.id) ?? []
948
+ };
949
+ }));
950
+ }
951
+ function formatPropertyList(properties) {
952
+ return Object.entries(properties).map(([key, value]) => `${key}=${stripUnsafeTextCharacters(String(value))}`).join(" ");
953
+ }
954
+ function formatNodeLine(node) {
955
+ const propertyList = formatPropertyList(node.properties);
956
+ return `${node.id.padEnd(6)} ${node.kind.padEnd(9)} ${node.title}${propertyList === "" ? "" : ` [${propertyList}]`}`;
957
+ }
958
+ function quoteBody(body) {
959
+ if (body === "") return "";
960
+ return splitIntoSafeLines(body).map((line) => line === "" ? ">" : `> ${line}`).join("\n");
961
+ }
962
+ function renderMarkdown(schema, nodes, edges) {
963
+ const titleById = new Map(nodes.map((node) => [node.id, node.title]));
964
+ const describeEdge = (edge, perspectiveId) => {
965
+ const isOutgoing = edge.fromId === perspectiveId;
966
+ const otherId = isOutgoing ? edge.toId : edge.fromId;
967
+ return `- ${isOutgoing ? edge.edgeType : `${edge.edgeType} (from)`}: ${otherId} ${titleById.get(otherId) ?? ""}`.trimEnd();
968
+ };
969
+ const sections = Object.keys(schema.kinds).map((kind) => {
970
+ const kindNodes = nodes.filter((node) => node.kind === kind);
971
+ if (kindNodes.length === 0) return "";
972
+ const nodeBlocks = kindNodes.map((node) => {
973
+ const relatedEdges = edges.filter((edge) => edge.fromId === node.id || edge.toId === node.id);
974
+ return [
975
+ `### ${node.id} ${node.title}`,
976
+ formatPropertyList(node.properties),
977
+ ...relatedEdges.map((edge) => describeEdge(edge, node.id)),
978
+ quoteBody(node.body)
979
+ ].filter((line) => line !== "").join("\n");
980
+ });
981
+ return [`## ${kind}`, ...nodeBlocks].join("\n\n");
982
+ });
983
+ return `${[`# ${schema.name} graph`, ...sections.filter((section) => section !== "")].join("\n\n")}\n`;
984
+ }
985
+ //#endregion
986
+ //#region knowledge-graph/cli.ts
987
+ var schemasByName = { personal: personalSchema };
988
+ var writeCommandNames = /* @__PURE__ */ new Set([
989
+ "add",
990
+ "set",
991
+ "rm",
992
+ "link",
993
+ "unlink",
994
+ "track"
995
+ ]);
996
+ var SinceTimestamp = z.iso.datetime({ offset: true });
997
+ var usage = `glimmervoid kg: a typed, local-first knowledge and task graph (experimental)
998
+
999
+ glimmervoid kg schema kinds, their fields, and allowed edges
1000
+ glimmervoid kg add <kind> <title> [key=value...] create a node (--body text)
1001
+ glimmervoid kg set <id> [key=value...] change fields; title= and body= also work; key= clears
1002
+ glimmervoid kg rm <id> delete a node and its edges
1003
+ glimmervoid kg link <from> <edge> <to> add a typed edge, e.g. kg link T-1 blocks T-2
1004
+ glimmervoid kg unlink <from> <edge> <to>
1005
+ glimmervoid kg show <id> a node with every edge in and out
1006
+ glimmervoid kg ls [kind] [key=value...] list nodes, filtered by fields
1007
+ glimmervoid kg find <text> full-text search over titles and bodies
1008
+ glimmervoid kg next unblocked todo/doing tasks in active projects, ranked
1009
+ glimmervoid kg chain <id> [edge] [--incoming] walk an edge transitively; bare "kg chain T-3" lists everything blocking T-3
1010
+ glimmervoid kg check re-validate every node and edge against the schema
1011
+ glimmervoid kg export the whole graph as markdown
1012
+ glimmervoid kg track <id> <repo> <recordId> point a kg node at a Coherence ticket (wrk-...) or journal record (d-...)
1013
+ glimmervoid kg delta [id] [--since <iso time>] what the tracked Coherence records did, and where kg and the ledger disagree
1014
+
1015
+ --graph <name> which schema (default personal) --db <path> database file (default <glimmervoid home>/knowledge-graph/<graph>.sqlite)
1016
+ --json machine-readable output`;
1017
+ function splitAssignments(tokens) {
1018
+ const assignments = {};
1019
+ for (const token of tokens) {
1020
+ const separatorIndex = token.indexOf("=");
1021
+ if (separatorIndex <= 0) throw new Error(`expected key=value, got "${token}"`);
1022
+ assignments[token.slice(0, separatorIndex)] = token.slice(separatorIndex + 1);
1023
+ }
1024
+ return assignments;
1025
+ }
1026
+ function requirePositional(positionals, index, name) {
1027
+ const value = positionals[index];
1028
+ if (value === void 0) throw new Error(`missing <${name}>\n\n${usage}`);
1029
+ return value;
1030
+ }
1031
+ function readEdgeArguments(positionals) {
1032
+ return {
1033
+ fromId: requirePositional(positionals, 1, "from"),
1034
+ edgeType: requirePositional(positionals, 2, "edge"),
1035
+ toId: requirePositional(positionals, 3, "to")
1036
+ };
1037
+ }
1038
+ function collectTrackedRecords(store, scopeNodeId) {
1039
+ const nodesById = new Map(store.listNodes().map((node) => [node.id, node]));
1040
+ const hierarchy = readTaskHierarchy(store);
1041
+ const isInScope = (nodeId) => scopeNodeId === void 0 || hierarchy.lineageOf(nodeId).includes(scopeNodeId) || hierarchy.projectIdOf(nodeId) === scopeNodeId;
1042
+ return store.listEdges("tracked_by").filter((edge) => isInScope(edge.fromId)).flatMap((edge) => {
1043
+ const trackingNode = nodesById.get(edge.fromId);
1044
+ const pointer = nodesById.get(edge.toId);
1045
+ if (!trackingNode || !pointer) return [];
1046
+ const recordId = String(pointer.properties.recordId);
1047
+ const record = inferRecordKind(recordId);
1048
+ if (record === null) return [];
1049
+ const trackingStatus = trackingNode.properties.status;
1050
+ return [{
1051
+ trackingNodeId: trackingNode.id,
1052
+ trackingKind: trackingNode.kind,
1053
+ trackingStatus: typeof trackingStatus === "string" ? trackingStatus : void 0,
1054
+ recordNodeId: pointer.id,
1055
+ repo: String(pointer.properties.repo),
1056
+ record,
1057
+ recordId
1058
+ }];
1059
+ });
1060
+ }
1061
+ function renderRepoReport(report) {
1062
+ const repoLabel = stripUnsafeTextCharacters(report.repo);
1063
+ if (!report.isAvailable) {
1064
+ const recordLines = report.records.map((tracked) => ` ${tracked.trackingNodeId} -> ${tracked.recordNodeId} ${tracked.recordId} (not checked)`);
1065
+ return [`${repoLabel} unavailable: ${report.reason}`, ...recordLines].join("\n");
1066
+ }
1067
+ const recordBlocks = report.records.map((recordReport) => [
1068
+ ` ${recordReport.tracked.trackingNodeId} -> ${recordReport.tracked.recordNodeId} ${recordReport.tracked.record.padEnd(8)} ${recordReport.state.padEnd(16)} ${recordReport.label}`,
1069
+ ...recordReport.findings.map((finding) => ` ! ${finding.kind}: ${finding.detail}`),
1070
+ ...recordReport.decisions.map((decision) => ` ${decision.kind} ${decision.id} (${decision.standing}) ${decision.chose}`)
1071
+ ].join("\n"));
1072
+ return [`${repoLabel} heading: ${report.heading}${report.headingReasons.length === 0 ? "" : ` (${report.headingReasons.join("; ")})`}`, ...recordBlocks].join("\n");
1073
+ }
1074
+ function print(context, jsonValue, text) {
1075
+ context.writeOutput(`${context.isJson ? JSON.stringify(jsonValue, null, 2) : text}\n`);
1076
+ return 0;
1077
+ }
1078
+ function describeSchema(schema) {
1079
+ const kindLines = Object.entries(schema.kinds).map(([kind, definition]) => {
1080
+ const shape = (listKnownPropertyKeys(schema, kind) ?? []).join(", ");
1081
+ return ` ${kind} (${definition.idPrefix}-n): ${definition.description}${shape === "" ? "" : `\n fields: ${shape}`}`;
1082
+ });
1083
+ const edgeLines = Object.entries(schema.edges).map(([edgeType, definition]) => {
1084
+ const limits = [definition.maxOutgoingPerNode === void 0 ? "" : `max ${definition.maxOutgoingPerNode}`, definition.isAcyclic ? "acyclic" : ""].filter((limit) => limit !== "").join(", ");
1085
+ return ` ${edgeType}: ${describeAllowedKinds(definition.fromKinds)} -> ${describeAllowedKinds(definition.toKinds)}${limits === "" ? "" : ` (${limits})`} ${definition.description}`;
1086
+ });
1087
+ return [
1088
+ `graph "${schema.name}" v${schema.version}`,
1089
+ "kinds:",
1090
+ ...kindLines,
1091
+ "edges:",
1092
+ ...edgeLines
1093
+ ].join("\n");
1094
+ }
1095
+ var commands = {
1096
+ schema: (context) => print(context, context.store.schema, describeSchema(context.store.schema)),
1097
+ add: (context) => {
1098
+ const kind = requirePositional(context.positionals, 1, "kind");
1099
+ const title = requirePositional(context.positionals, 2, "title");
1100
+ const node = context.store.addNode(kind, title, splitAssignments(context.positionals.slice(3)), context.body ?? "");
1101
+ print(context, node, formatNodeLine(node));
1102
+ },
1103
+ set: (context) => {
1104
+ const id = requirePositional(context.positionals, 1, "id");
1105
+ const { title, body, ...properties } = splitAssignments(context.positionals.slice(2));
1106
+ const node = context.store.updateNode(id, {
1107
+ title,
1108
+ body: context.body ?? body,
1109
+ properties
1110
+ });
1111
+ print(context, node, formatNodeLine(node));
1112
+ },
1113
+ rm: (context) => {
1114
+ const id = requirePositional(context.positionals, 1, "id");
1115
+ context.store.removeNode(id);
1116
+ print(context, { removed: id }, `removed ${id}`);
1117
+ },
1118
+ link: (context) => {
1119
+ const { fromId, edgeType, toId } = readEdgeArguments(context.positionals);
1120
+ const edge = context.store.link(fromId, edgeType, toId);
1121
+ print(context, edge, `${edge.fromId} ${edge.edgeType} ${edge.toId}`);
1122
+ },
1123
+ unlink: (context) => {
1124
+ const { fromId, edgeType, toId } = readEdgeArguments(context.positionals);
1125
+ const wasRemoved = context.store.unlink(fromId, edgeType, toId);
1126
+ print(context, { removed: wasRemoved }, wasRemoved ? "unlinked" : "no such edge");
1127
+ },
1128
+ show: (context) => {
1129
+ const node = context.store.requireNode(requirePositional(context.positionals, 1, "id"));
1130
+ const outgoing = context.store.outgoingEdges(node.id);
1131
+ const incoming = context.store.incomingEdges(node.id);
1132
+ const titleOf = (id) => context.store.getNode(id)?.title ?? "?";
1133
+ const lines = [
1134
+ formatNodeLine(node),
1135
+ ...outgoing.map((edge) => ` -> ${edge.edgeType} ${edge.toId} ${titleOf(edge.toId)}`),
1136
+ ...incoming.map((edge) => ` <- ${edge.edgeType} ${edge.fromId} ${titleOf(edge.fromId)}`),
1137
+ node.body === "" ? "" : `\n${splitIntoSafeLines(node.body).join("\n")}`
1138
+ ];
1139
+ print(context, {
1140
+ node,
1141
+ outgoing,
1142
+ incoming
1143
+ }, lines.filter((line) => line !== "").join("\n"));
1144
+ },
1145
+ ls: (context) => {
1146
+ const kind = context.positionals[1]?.includes("=") ? void 0 : context.positionals[1];
1147
+ const filterTokens = context.positionals.slice(kind === void 0 ? 1 : 2);
1148
+ const nodes = context.store.listNodes(kind, splitAssignments(filterTokens));
1149
+ print(context, nodes, nodes.map(formatNodeLine).join("\n") || "(none)");
1150
+ },
1151
+ find: (context) => {
1152
+ const nodes = context.store.search(context.positionals.slice(1).join(" "));
1153
+ print(context, nodes, nodes.map(formatNodeLine).join("\n") || "(no matches)");
1154
+ },
1155
+ next: (context) => {
1156
+ const nextActions = collectNextActions(context.store);
1157
+ print(context, nextActions, nextActions.map((action) => `${action.id.padEnd(6)} ${action.priority} ${action.status.padEnd(5)} ${action.title}${action.due === void 0 ? "" : ` due ${action.due}`}`).join("\n") || "(nothing actionable)");
1158
+ },
1159
+ chain: (context) => {
1160
+ const id = requirePositional(context.positionals, 1, "id");
1161
+ const edgeType = context.positionals[2] ?? "blocks";
1162
+ const steps = context.store.walk(id, edgeType, context.isIncoming || context.positionals[2] === void 0 ? "incoming" : "outgoing");
1163
+ print(context, steps, steps.map((step) => `${" ".repeat(step.depth - 1)}${formatNodeLine(step.node)}`).join("\n") || "(empty)");
1164
+ },
1165
+ check: (context) => {
1166
+ const violations = context.store.checkIntegrity();
1167
+ print(context, violations, violations.join("\n") || "ok: every node and edge matches the schema");
1168
+ return violations.length > 0 ? 1 : 0;
1169
+ },
1170
+ track: (context) => {
1171
+ const trackingNode = context.store.requireNode(requirePositional(context.positionals, 1, "id"));
1172
+ const repo = realpathSync(resolve(requirePositional(context.positionals, 2, "repo")));
1173
+ const recordId = requirePositional(context.positionals, 3, "recordId");
1174
+ const record = inferRecordKind(recordId);
1175
+ if (record === null) throw new Error(`"${recordId}" is neither a ticket id (wrk-...) nor a decision id (d-...)`);
1176
+ const snapshot = readRepoSnapshot(repo, context.runCoherence);
1177
+ if (!snapshot.isAvailable) throw new Error(`cannot verify ${recordId}: ${snapshot.reason}`);
1178
+ const label = record === "work" ? snapshot.workOrders.find((workOrder) => workOrder.id === recordId)?.objective : snapshot.decisions.find((decision) => decision.id === recordId)?.chose;
1179
+ if (label === void 0) throw new Error(`${recordId} is not in the Coherence ledger at ${repo}`);
1180
+ const { pointer, edge } = context.store.transaction(() => {
1181
+ const trackedPointer = context.store.listNodes("coherence_record", {
1182
+ repo,
1183
+ recordId
1184
+ })[0] ?? context.store.addNode("coherence_record", label, {
1185
+ repo,
1186
+ record,
1187
+ recordId
1188
+ });
1189
+ return {
1190
+ pointer: trackedPointer,
1191
+ edge: context.store.link(trackingNode.id, "tracked_by", trackedPointer.id)
1192
+ };
1193
+ });
1194
+ print(context, {
1195
+ pointer,
1196
+ edge
1197
+ }, `${trackingNode.id} tracked_by ${pointer.id} (${record} ${recordId}: ${label})`);
1198
+ },
1199
+ delta: (context) => {
1200
+ const scopeNodeId = context.positionals[1];
1201
+ if (scopeNodeId !== void 0) context.store.requireNode(scopeNodeId);
1202
+ const trackedRecords = collectTrackedRecords(context.store, scopeNodeId);
1203
+ const repos = [...new Set(trackedRecords.map((tracked) => tracked.repo))];
1204
+ const reports = buildCoherenceDelta(trackedRecords, new Map(repos.map((repo) => [repo, readRepoSnapshot(repo, context.runCoherence)])), context.since);
1205
+ print(context, reports, reports.map(renderRepoReport).join("\n\n") || "(nothing tracked; use glimmervoid kg track)");
1206
+ },
1207
+ export: (context) => {
1208
+ context.writeOutput(renderMarkdown(context.store.schema, context.store.listNodes(), context.store.listEdges()));
1209
+ }
1210
+ };
1211
+ function resolveDatabasePath(requestedPath, commandName, defaultPath) {
1212
+ if (requestedPath === void 0) return defaultPath;
1213
+ if (requestedPath.trim() === "") throw new Error("--db needs a database file path");
1214
+ if (writeCommandNames.has(commandName) || existsSync(requestedPath)) return requestedPath;
1215
+ throw new Error(`no knowledge graph database at ${requestedPath}; check --db, or create it with a write such as kg add`);
1216
+ }
1217
+ function runCommandLine(commandArguments, dependencies) {
1218
+ const { values, positionals } = parseArgs({
1219
+ args: commandArguments,
1220
+ allowPositionals: true,
1221
+ options: {
1222
+ graph: {
1223
+ type: "string",
1224
+ default: "personal"
1225
+ },
1226
+ db: { type: "string" },
1227
+ body: { type: "string" },
1228
+ since: { type: "string" },
1229
+ incoming: {
1230
+ type: "boolean",
1231
+ default: false
1232
+ },
1233
+ json: {
1234
+ type: "boolean",
1235
+ default: false
1236
+ },
1237
+ help: {
1238
+ type: "boolean",
1239
+ short: "h",
1240
+ default: false
1241
+ }
1242
+ }
1243
+ });
1244
+ const commandName = positionals[0];
1245
+ const command = commandName === void 0 ? void 0 : commands[commandName];
1246
+ if (values.help || commandName === void 0 || command === void 0) {
1247
+ dependencies.writeOutput(`${usage}\n`);
1248
+ return values.help ? 0 : 1;
1249
+ }
1250
+ const schema = schemasByName[values.graph];
1251
+ if (!schema) throw new Error(`unknown graph "${values.graph}"; known: ${Object.keys(schemasByName).join(", ")}`);
1252
+ if (values.since !== void 0 && !SinceTimestamp.safeParse(values.since).success) throw new Error(`--since needs an ISO 8601 timestamp with Z or an offset, e.g. 2026-10-09T09:00:00Z; got "${values.since}"`);
1253
+ const databasePath = resolveDatabasePath(values.db, commandName, join(dependencies.defaultDatabaseDirectory, `${schema.name}.sqlite`));
1254
+ mkdirSync(dirname(databasePath), { recursive: true });
1255
+ const store = openGraphStore(databasePath, schema);
1256
+ try {
1257
+ return command({
1258
+ store,
1259
+ positionals,
1260
+ body: values.body,
1261
+ since: values.since,
1262
+ isIncoming: values.incoming,
1263
+ isJson: values.json,
1264
+ runCoherence: dependencies.runCoherence,
1265
+ writeOutput: dependencies.writeOutput
1266
+ }) ?? 0;
1267
+ } finally {
1268
+ store.close();
1269
+ }
1270
+ }
1271
+ function runKnowledgeGraphCli(commandArguments, dependencies) {
1272
+ try {
1273
+ return runCommandLine(commandArguments, dependencies);
1274
+ } catch (error) {
1275
+ dependencies.writeError(`kg: ${error instanceof Error ? error.message : String(error)}\n`);
1276
+ return 1;
1277
+ }
1278
+ }
1279
+ //#endregion
1280
+ //#region server/knowledge-graph-cli.ts
1281
+ var COHERENCE_TIMEOUT_MS = 3e4;
1282
+ var COHERENCE_MAX_BUFFER_BYTES = 16777216;
1283
+ var CoherenceRefusalExit = z.looseObject({
1284
+ status: z.literal(2),
1285
+ stdout: z.string().min(1)
1286
+ });
1287
+ var KnowledgeGraphGate = z.looseObject({ knowledgeGraph: BrowserConfig.shape.knowledgeGraph });
1288
+ var KNOWLEDGE_GRAPH_DISABLED_MESSAGE = "glimmervoid kg is experimental and off. Turn on Settings > Lanes > Knowledge graph, or set knowledgeGraph.enabled in config.json.";
1289
+ function decideKnowledgeGraphGate(configPath) {
1290
+ if (!configPath) return {
1291
+ isEnabled: false,
1292
+ loadError: null
1293
+ };
1294
+ try {
1295
+ const parsed = KnowledgeGraphGate.safeParse(JSON.parse(fs.readFileSync(configPath, "utf8")));
1296
+ return {
1297
+ isEnabled: parsed.success && parsed.data.knowledgeGraph?.enabled === true,
1298
+ loadError: null
1299
+ };
1300
+ } catch (error) {
1301
+ return {
1302
+ isEnabled: false,
1303
+ loadError: `Could not load ${configPath}: ${errorMessage(error)}`
1304
+ };
1305
+ }
1306
+ }
1307
+ function runBundledCoherence(repo, commandArguments) {
1308
+ const coherenceCliPath = resolvePackageBin("@danilocampos/coherence", "coherence");
1309
+ if (!coherenceCliPath) throw new Error("Could not resolve the coherence CLI");
1310
+ try {
1311
+ return execFileSync(process.execPath, [coherenceCliPath, ...commandArguments], {
1312
+ cwd: repo,
1313
+ env: {
1314
+ ...process.env,
1315
+ GIT_OPTIONAL_LOCKS: "0"
1316
+ },
1317
+ encoding: "utf8",
1318
+ timeout: COHERENCE_TIMEOUT_MS,
1319
+ maxBuffer: COHERENCE_MAX_BUFFER_BYTES,
1320
+ stdio: [
1321
+ "ignore",
1322
+ "pipe",
1323
+ "pipe"
1324
+ ]
1325
+ });
1326
+ } catch (error) {
1327
+ const refusalExit = CoherenceRefusalExit.safeParse(error);
1328
+ if (!refusalExit.success) throw error;
1329
+ return refusalExit.data.stdout;
1330
+ }
1331
+ }
1332
+ function runKnowledgeGraphCommand(commandArguments) {
1333
+ const homeDirectory = glimmervoidHomeDir(os.homedir(), process.env);
1334
+ const gate = decideKnowledgeGraphGate(decideConfigPath({
1335
+ env: process.env,
1336
+ homeDir: homeDirectory
1337
+ }, (candidate) => fs.existsSync(candidate)).path);
1338
+ if (gate.loadError) {
1339
+ console.error(`kg: ${gate.loadError}`);
1340
+ return 1;
1341
+ }
1342
+ if (!gate.isEnabled) {
1343
+ console.error(KNOWLEDGE_GRAPH_DISABLED_MESSAGE);
1344
+ return 1;
1345
+ }
1346
+ return runKnowledgeGraphCli(commandArguments, {
1347
+ defaultDatabaseDirectory: path.join(homeDirectory, "knowledge-graph"),
1348
+ runCoherence: runBundledCoherence,
1349
+ writeOutput: (text) => process.stdout.write(text),
1350
+ writeError: (text) => process.stderr.write(text)
1351
+ });
1352
+ }
1353
+ //#endregion
1354
+ export { runKnowledgeGraphCommand };