@treeseed/sdk 0.1.2 → 0.3.1

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 (237) hide show
  1. package/README.md +97 -506
  2. package/dist/{src/cli-tools.d.ts → cli-tools.d.ts} +1 -1
  3. package/dist/cli-tools.js +5 -3
  4. package/dist/{src/content-store.d.ts → content-store.d.ts} +3 -2
  5. package/dist/content-store.js +52 -20
  6. package/dist/{src/d1-store.d.ts → d1-store.d.ts} +62 -1
  7. package/dist/d1-store.js +625 -65
  8. package/dist/field-aliases.d.ts +11 -0
  9. package/dist/field-aliases.js +41 -0
  10. package/dist/graph/build.d.ts +19 -0
  11. package/dist/graph/build.js +949 -0
  12. package/dist/graph/dsl.d.ts +2 -0
  13. package/dist/graph/dsl.js +243 -0
  14. package/dist/graph/query.d.ts +47 -0
  15. package/dist/graph/query.js +447 -0
  16. package/dist/graph/ranking.d.ts +3 -0
  17. package/dist/graph/ranking.js +483 -0
  18. package/dist/graph/schema.d.ts +142 -0
  19. package/dist/graph/schema.js +133 -0
  20. package/dist/graph.d.ts +52 -0
  21. package/dist/graph.js +133 -0
  22. package/dist/index.d.ts +28 -0
  23. package/dist/index.js +91 -2
  24. package/dist/model-registry.d.ts +8 -0
  25. package/dist/model-registry.js +351 -25
  26. package/dist/operations/providers/default.d.ts +10 -0
  27. package/dist/operations/providers/default.js +514 -0
  28. package/dist/operations/runtime.d.ts +7 -0
  29. package/dist/operations/runtime.js +60 -0
  30. package/dist/operations/services/config-runtime.d.ts +269 -0
  31. package/dist/operations/services/config-runtime.js +1397 -0
  32. package/dist/operations/services/d1-migration.d.ts +6 -0
  33. package/dist/operations/services/d1-migration.js +89 -0
  34. package/dist/operations/services/deploy.d.ts +371 -0
  35. package/dist/operations/services/deploy.js +981 -0
  36. package/dist/operations/services/git-workflow.d.ts +49 -0
  37. package/dist/operations/services/git-workflow.js +218 -0
  38. package/dist/operations/services/github-automation.d.ts +156 -0
  39. package/dist/operations/services/github-automation.js +256 -0
  40. package/dist/operations/services/local-dev.d.ts +9 -0
  41. package/dist/operations/services/local-dev.js +106 -0
  42. package/dist/operations/services/mailpit-runtime.d.ts +4 -0
  43. package/dist/operations/services/mailpit-runtime.js +59 -0
  44. package/dist/operations/services/railway-deploy.d.ts +53 -0
  45. package/dist/operations/services/railway-deploy.js +123 -0
  46. package/dist/operations/services/runtime-paths.d.ts +19 -0
  47. package/dist/operations/services/runtime-paths.js +54 -0
  48. package/dist/operations/services/runtime-tools.d.ts +117 -0
  49. package/dist/operations/services/runtime-tools.js +358 -0
  50. package/dist/operations/services/save-deploy-preflight.d.ts +34 -0
  51. package/dist/operations/services/save-deploy-preflight.js +76 -0
  52. package/dist/operations/services/template-registry.d.ts +88 -0
  53. package/dist/operations/services/template-registry.js +407 -0
  54. package/dist/operations/services/watch-dev.d.ts +21 -0
  55. package/dist/operations/services/watch-dev.js +284 -0
  56. package/dist/operations/services/workspace-preflight.d.ts +40 -0
  57. package/dist/operations/services/workspace-preflight.js +165 -0
  58. package/dist/operations/services/workspace-save.d.ts +42 -0
  59. package/dist/operations/services/workspace-save.js +235 -0
  60. package/dist/operations/services/workspace-tools.d.ts +16 -0
  61. package/dist/operations/services/workspace-tools.js +270 -0
  62. package/dist/operations-registry.d.ts +5 -0
  63. package/dist/operations-registry.js +68 -0
  64. package/dist/operations-types.d.ts +71 -0
  65. package/dist/operations-types.js +17 -0
  66. package/dist/operations.d.ts +6 -0
  67. package/dist/operations.js +16 -0
  68. package/dist/platform/books-data.d.ts +1 -0
  69. package/dist/platform/books-data.js +1 -0
  70. package/dist/platform/contracts.d.ts +158 -0
  71. package/dist/platform/contracts.js +0 -0
  72. package/dist/platform/deploy/config.d.ts +4 -0
  73. package/dist/platform/deploy/config.js +222 -0
  74. package/dist/platform/deploy-config.d.ts +1 -0
  75. package/dist/platform/deploy-config.js +1 -0
  76. package/dist/platform/deploy-runtime.d.ts +18 -0
  77. package/dist/platform/deploy-runtime.js +78 -0
  78. package/dist/platform/env.yaml +394 -0
  79. package/dist/platform/environment.d.ts +130 -0
  80. package/dist/platform/environment.js +331 -0
  81. package/dist/platform/plugin.d.ts +2 -0
  82. package/dist/platform/plugin.js +4 -0
  83. package/dist/platform/plugins/constants.d.ts +22 -0
  84. package/dist/platform/plugins/constants.js +29 -0
  85. package/dist/platform/plugins/plugin.d.ts +51 -0
  86. package/dist/platform/plugins/plugin.js +6 -0
  87. package/dist/platform/plugins/runtime.d.ts +35 -0
  88. package/dist/platform/plugins/runtime.js +161 -0
  89. package/dist/platform/plugins.d.ts +6 -0
  90. package/dist/platform/plugins.js +38 -0
  91. package/dist/platform/site-config-schema.js +1 -0
  92. package/dist/platform/tenant/config.d.ts +9 -0
  93. package/dist/platform/tenant/config.js +154 -0
  94. package/dist/platform/tenant/runtime-config.d.ts +4 -0
  95. package/dist/platform/tenant/runtime-config.js +20 -0
  96. package/dist/platform/tenant-config.d.ts +1 -0
  97. package/dist/platform/tenant-config.js +1 -0
  98. package/dist/platform/utils/books-data.d.ts +29 -0
  99. package/dist/platform/utils/books-data.js +82 -0
  100. package/dist/platform/utils/site-config-schema.js +321 -0
  101. package/dist/remote.d.ts +175 -0
  102. package/dist/remote.js +202 -0
  103. package/dist/runtime.js +35 -22
  104. package/dist/scripts/aggregate-book.js +121 -0
  105. package/dist/scripts/build-dist.js +54 -13
  106. package/dist/scripts/build-tenant-worker.js +36 -0
  107. package/dist/scripts/cleanup-markdown.js +373 -0
  108. package/dist/scripts/cli-test-fixtures.js +48 -0
  109. package/dist/scripts/config-treeseed.js +95 -0
  110. package/dist/scripts/ensure-mailpit.js +29 -0
  111. package/dist/scripts/local-dev.js +129 -0
  112. package/dist/scripts/logs-mailpit.js +2 -0
  113. package/dist/scripts/patch-starlight-content-path.js +172 -0
  114. package/dist/scripts/release-verify.js +34 -6
  115. package/dist/scripts/run-fixture-astro-command.js +18 -0
  116. package/dist/scripts/scaffold-site.js +65 -0
  117. package/dist/scripts/stop-mailpit.js +5 -0
  118. package/dist/scripts/sync-dev-vars.js +6 -0
  119. package/dist/scripts/sync-template.js +20 -0
  120. package/dist/scripts/template-catalog.test.js +100 -0
  121. package/dist/scripts/template-command.js +31 -0
  122. package/dist/scripts/tenant-astro-command.js +3 -0
  123. package/dist/scripts/tenant-build.js +16 -0
  124. package/dist/scripts/tenant-check.js +7 -0
  125. package/dist/scripts/tenant-d1-migrate-local.js +11 -0
  126. package/dist/scripts/tenant-deploy.js +180 -0
  127. package/dist/scripts/tenant-destroy.js +104 -0
  128. package/dist/scripts/tenant-dev.js +171 -0
  129. package/dist/scripts/tenant-lint.js +4 -0
  130. package/dist/scripts/tenant-test.js +4 -0
  131. package/dist/scripts/test-cloudflare-local.js +212 -0
  132. package/dist/scripts/test-scaffold.js +314 -0
  133. package/dist/scripts/test-smoke.js +71 -13
  134. package/dist/scripts/treeseed-assert-release-tag-version.js +21 -0
  135. package/dist/scripts/treeseed-build-dist.js +134 -0
  136. package/dist/scripts/treeseed-publish-package.js +19 -0
  137. package/dist/scripts/treeseed-release-verify.js +131 -0
  138. package/dist/scripts/treeseed-run-ts.js +45 -0
  139. package/dist/scripts/validate-templates.js +6 -0
  140. package/dist/scripts/verify-driver.js +29 -0
  141. package/dist/scripts/workflow-commands.test.js +39 -0
  142. package/dist/scripts/workspace-close.js +24 -0
  143. package/dist/scripts/workspace-command-e2e.js +718 -0
  144. package/dist/scripts/workspace-lint.js +9 -0
  145. package/dist/scripts/workspace-preflight.js +22 -0
  146. package/dist/scripts/workspace-publish-changed-packages.js +16 -0
  147. package/dist/scripts/workspace-release-verify.js +81 -0
  148. package/dist/scripts/workspace-release.js +42 -0
  149. package/dist/scripts/workspace-save.js +124 -0
  150. package/dist/scripts/workspace-start-warning.js +3 -0
  151. package/dist/scripts/workspace-start.js +71 -0
  152. package/dist/scripts/workspace-test-unit.js +4 -0
  153. package/dist/scripts/workspace-test.js +11 -0
  154. package/dist/sdk-fields.d.ts +11 -0
  155. package/dist/sdk-fields.js +169 -0
  156. package/dist/sdk-filters.d.ts +4 -0
  157. package/dist/sdk-filters.js +12 -15
  158. package/dist/sdk-types.d.ts +796 -0
  159. package/dist/sdk-types.js +7 -1
  160. package/dist/sdk-version.d.ts +2 -0
  161. package/dist/sdk-version.js +42 -0
  162. package/dist/sdk.d.ts +215 -0
  163. package/dist/sdk.js +235 -11
  164. package/dist/stores/cursor-store.js +9 -3
  165. package/dist/stores/lease-store.js +8 -2
  166. package/dist/{src/stores → stores}/message-store.d.ts +1 -1
  167. package/dist/stores/message-store.js +27 -3
  168. package/dist/stores/operational-store.d.ts +24 -0
  169. package/dist/stores/operational-store.js +279 -0
  170. package/dist/stores/run-store.js +8 -1
  171. package/dist/stores/subscription-store.js +7 -5
  172. package/dist/template-catalog.d.ts +13 -0
  173. package/dist/template-catalog.js +141 -0
  174. package/dist/treeseed/services/compose.yml +7 -0
  175. package/dist/treeseed/template-catalog/catalog.fixture.json +55 -0
  176. package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.d.ts +2 -0
  177. package/dist/treeseed/template-catalog/templates/starter-basic/template/astro.config.ts +3 -0
  178. package/dist/treeseed/template-catalog/templates/starter-basic/template/package.json +32 -0
  179. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/config.yaml +40 -0
  180. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/empty/.gitkeep +1 -0
  181. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/knowledge/handbook/index.mdx +11 -0
  182. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content/pages/welcome.mdx +11 -0
  183. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.d.ts +1 -0
  184. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/content.config.ts +3 -0
  185. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/env.yaml +1 -0
  186. package/dist/treeseed/template-catalog/templates/starter-basic/template/src/manifest.yaml +19 -0
  187. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +26 -0
  188. package/dist/treeseed/template-catalog/templates/starter-basic/template/tsconfig.json +9 -0
  189. package/dist/treeseed/template-catalog/templates/starter-basic/template.config.json +90 -0
  190. package/dist/utils/agents/contracts/messages.d.ts +88 -0
  191. package/dist/utils/agents/contracts/messages.js +138 -0
  192. package/dist/utils/agents/contracts/run.d.ts +20 -0
  193. package/dist/utils/agents/contracts/run.js +0 -0
  194. package/dist/utils/agents/runtime-types.d.ts +117 -0
  195. package/dist/utils/agents/runtime-types.js +4 -0
  196. package/dist/verification.d.ts +20 -0
  197. package/dist/verification.js +98 -0
  198. package/dist/workflow/operations.d.ts +396 -0
  199. package/dist/workflow/operations.js +841 -0
  200. package/dist/workflow-state.d.ts +56 -0
  201. package/dist/workflow-state.js +195 -0
  202. package/dist/workflow-support.d.ts +9 -0
  203. package/dist/workflow-support.js +176 -0
  204. package/dist/workflow.d.ts +111 -0
  205. package/dist/workflow.js +97 -0
  206. package/package.json +111 -5
  207. package/scripts/verify-driver.mjs +29 -0
  208. package/dist/scripts/.ts-run-1775630384291-crtqr3izsa.js +0 -22
  209. package/dist/scripts/.ts-run-1775630388025-vnjle0z75a.js +0 -129
  210. package/dist/scripts/assert-release-tag-version.d.ts +0 -1
  211. package/dist/scripts/build-dist.d.ts +0 -1
  212. package/dist/scripts/fixture-tools.d.ts +0 -5
  213. package/dist/scripts/package-tools.d.ts +0 -15
  214. package/dist/scripts/publish-package.d.ts +0 -1
  215. package/dist/scripts/release-verify.d.ts +0 -1
  216. package/dist/scripts/test-smoke.d.ts +0 -1
  217. package/dist/src/index.d.ts +0 -6
  218. package/dist/src/model-registry.d.ts +0 -4
  219. package/dist/src/sdk-filters.d.ts +0 -4
  220. package/dist/src/sdk-types.d.ts +0 -285
  221. package/dist/src/sdk.d.ts +0 -109
  222. package/dist/test/test-fixture.d.ts +0 -1
  223. package/dist/test/utils/envelopes.test.d.ts +0 -1
  224. package/dist/test/utils/sdk.test.d.ts +0 -1
  225. package/dist/vitest.config.d.ts +0 -2
  226. /package/dist/{src/frontmatter.d.ts → frontmatter.d.ts} +0 -0
  227. /package/dist/{src/git-runtime.d.ts → git-runtime.d.ts} +0 -0
  228. /package/dist/{src/runtime.d.ts → runtime.d.ts} +0 -0
  229. /package/dist/{src/stores → stores}/cursor-store.d.ts +0 -0
  230. /package/dist/{src/stores → stores}/envelopes.d.ts +0 -0
  231. /package/dist/{src/stores → stores}/helpers.d.ts +0 -0
  232. /package/dist/{src/stores → stores}/lease-store.d.ts +0 -0
  233. /package/dist/{src/stores → stores}/run-store.d.ts +0 -0
  234. /package/dist/{src/stores → stores}/subscription-store.d.ts +0 -0
  235. /package/dist/{src/types → types}/agents.d.ts +0 -0
  236. /package/dist/{src/types → types}/cloudflare.d.ts +0 -0
  237. /package/dist/{src/wrangler-d1.d.ts → wrangler-d1.d.ts} +0 -0
@@ -0,0 +1,949 @@
1
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import GithubSlugger from "github-slugger";
4
+ import { toString } from "mdast-util-to-string";
5
+ import { unified } from "unified";
6
+ import remarkMdx from "remark-mdx";
7
+ import remarkParse from "remark-parse";
8
+ import { parseFrontmatterDocument } from "../frontmatter.js";
9
+ import { resolveModelDefinition } from "../model-registry.js";
10
+ import { readCanonicalFieldValue } from "../sdk-fields.js";
11
+ import {
12
+ AUTHORED_GRAPH_EDGE_TYPES,
13
+ computeEdgeId,
14
+ computeModelSignature,
15
+ createEntityNodeId,
16
+ createFileNodeId,
17
+ emptyGraphMetrics,
18
+ emptyGraphValidation,
19
+ ensureArray,
20
+ graphSnapshotRoot,
21
+ normalizeText,
22
+ resolveGraphModelConfig,
23
+ GRAPH_SNAPSHOT_VERSION,
24
+ sha1
25
+ } from "./schema.js";
26
+ const markdownProcessor = unified().use(remarkParse).use(remarkMdx);
27
+ async function walkMarkdownFiles(root) {
28
+ try {
29
+ const entries = await readdir(root, { withFileTypes: true });
30
+ const children = await Promise.all(entries.map(async (entry) => {
31
+ const fullPath = path.join(root, entry.name);
32
+ if (entry.isDirectory()) {
33
+ return walkMarkdownFiles(fullPath);
34
+ }
35
+ if (entry.isFile() && /\.(md|mdx)$/iu.test(entry.name)) {
36
+ return [fullPath];
37
+ }
38
+ return [];
39
+ }));
40
+ return children.flat();
41
+ } catch {
42
+ return [];
43
+ }
44
+ }
45
+ function stripMarkdownExtension(value) {
46
+ return value.replace(/\.(md|mdx)$/iu, "");
47
+ }
48
+ function inferSlug(filePath, root) {
49
+ return stripMarkdownExtension(path.relative(root, filePath).replace(/\\/gu, "/"));
50
+ }
51
+ function readGraphField(definition, frontmatter, field) {
52
+ const binding = definition.fields[field];
53
+ if (binding) {
54
+ const value = readCanonicalFieldValue(definition, { frontmatter }, field);
55
+ if (value !== void 0) {
56
+ return value;
57
+ }
58
+ }
59
+ const keys = /* @__PURE__ */ new Set([field]);
60
+ if (binding) {
61
+ for (const alias of binding.aliases ?? []) keys.add(alias);
62
+ for (const key of binding.contentKeys ?? []) keys.add(key);
63
+ }
64
+ for (const key of keys) {
65
+ if (key in frontmatter) {
66
+ return frontmatter[key];
67
+ }
68
+ }
69
+ return void 0;
70
+ }
71
+ function walkTree(node, visit) {
72
+ visit(node);
73
+ for (const child of node.children ?? []) {
74
+ walkTree(child, visit);
75
+ }
76
+ }
77
+ function extractMarkdownArtifacts(body) {
78
+ const tree = markdownProcessor.parse(body);
79
+ const slugger = new GithubSlugger();
80
+ const headings = [];
81
+ const links = [];
82
+ const mdxImports = [];
83
+ walkTree(tree, (node) => {
84
+ const startOffset = node.position?.start?.offset ?? 0;
85
+ const endOffset = node.position?.end?.offset ?? startOffset;
86
+ if (node.type === "heading" && typeof node.depth === "number") {
87
+ headings.push({
88
+ text: toString(node).trim(),
89
+ slug: slugger.slug(toString(node).trim() || "section"),
90
+ level: node.depth,
91
+ startOffset,
92
+ endOffset
93
+ });
94
+ }
95
+ if (node.type === "link" && typeof node.url === "string") {
96
+ links.push({
97
+ url: node.url,
98
+ text: toString(node).trim(),
99
+ startOffset,
100
+ endOffset
101
+ });
102
+ }
103
+ if (node.type === "mdxjsEsm" && typeof node.value === "string") {
104
+ const matches = node.value.matchAll(/(?:import|export)\s+(?:[^'"]+?\s+from\s+)?['"]([^'"]+)['"]/gu);
105
+ for (const match of matches) {
106
+ if (match[1]) {
107
+ mdxImports.push(match[1]);
108
+ }
109
+ }
110
+ }
111
+ });
112
+ return { headings, links, mdxImports };
113
+ }
114
+ function buildSections(fileId, body, headings, links) {
115
+ const sections = [];
116
+ const lines = body.length;
117
+ const nonWhitespace = /\S/u.test(body);
118
+ const headingOrdinals = /* @__PURE__ */ new Map();
119
+ const headingPathStack = [];
120
+ const sectionEndForHeading = (index) => {
121
+ const current = headings[index];
122
+ for (let pointer = index + 1; pointer < headings.length; pointer += 1) {
123
+ if (headings[pointer].level <= current.level) {
124
+ return headings[pointer].startOffset;
125
+ }
126
+ }
127
+ return lines;
128
+ };
129
+ if (headings.length === 0) {
130
+ if (!nonWhitespace) {
131
+ return sections;
132
+ }
133
+ sections.push({
134
+ id: `section:${fileId}:__intro:0`,
135
+ fileId,
136
+ heading: null,
137
+ headingSlug: "__intro",
138
+ headingPath: "__intro",
139
+ level: 0,
140
+ ordinal: 0,
141
+ startOffset: 0,
142
+ endOffset: lines,
143
+ rawText: body,
144
+ normalizedText: normalizeText(body),
145
+ outboundLinks: links,
146
+ referencedEntityIds: []
147
+ });
148
+ return sections;
149
+ }
150
+ const introEnd = headings[0]?.startOffset ?? 0;
151
+ if (introEnd > 0 && /\S/u.test(body.slice(0, introEnd))) {
152
+ sections.push({
153
+ id: `section:${fileId}:__intro:0`,
154
+ fileId,
155
+ heading: null,
156
+ headingSlug: "__intro",
157
+ headingPath: "__intro",
158
+ level: 0,
159
+ ordinal: 0,
160
+ startOffset: 0,
161
+ endOffset: introEnd,
162
+ rawText: body.slice(0, introEnd),
163
+ normalizedText: normalizeText(body.slice(0, introEnd)),
164
+ outboundLinks: links.filter((link) => link.startOffset >= 0 && link.startOffset < introEnd),
165
+ referencedEntityIds: []
166
+ });
167
+ }
168
+ for (let index = 0; index < headings.length; index += 1) {
169
+ const heading = headings[index];
170
+ while ((headingPathStack.at(-1)?.level ?? 0) >= heading.level) {
171
+ headingPathStack.pop();
172
+ }
173
+ const parentPath = headingPathStack.at(-1)?.path;
174
+ const headingPath = parentPath ? `${parentPath}/${heading.slug}` : heading.slug;
175
+ headingPathStack.push({ level: heading.level, path: headingPath });
176
+ const ordinal = headingOrdinals.get(headingPath) ?? 0;
177
+ headingOrdinals.set(headingPath, ordinal + 1);
178
+ const endOffset = sectionEndForHeading(index);
179
+ const rawText = body.slice(heading.startOffset, endOffset);
180
+ sections.push({
181
+ id: `section:${fileId}:${headingPath}:${ordinal}`,
182
+ fileId,
183
+ heading: heading.text,
184
+ headingSlug: heading.slug,
185
+ headingPath,
186
+ level: heading.level,
187
+ ordinal,
188
+ startOffset: heading.startOffset,
189
+ endOffset,
190
+ rawText,
191
+ normalizedText: normalizeText(rawText),
192
+ outboundLinks: links.filter((link) => link.startOffset >= heading.startOffset && link.startOffset < endOffset),
193
+ referencedEntityIds: []
194
+ });
195
+ }
196
+ return sections;
197
+ }
198
+ function normalizeReferenceValue(value) {
199
+ return value.trim().replace(/^\.\/+/u, "").replace(/\\/gu, "/");
200
+ }
201
+ const GRAPH_FRONTMATTER_RELATION_FIELDS = [
202
+ { field: "related", edgeType: "RELATES_TO", multiple: true },
203
+ { field: "references", edgeType: "REFERENCES", multiple: true },
204
+ { field: "dependsOn", edgeType: "DEPENDS_ON", multiple: true },
205
+ { field: "implements", edgeType: "IMPLEMENTS", multiple: true },
206
+ { field: "extends", edgeType: "EXTENDS", multiple: true },
207
+ { field: "supersedes", edgeType: "SUPERSEDES", multiple: true },
208
+ { field: "belongsTo", edgeType: "BELONGS_TO", multiple: true },
209
+ { field: "about", edgeType: "ABOUT", multiple: true },
210
+ { field: "usedBy", edgeType: "USED_BY", multiple: true },
211
+ { field: "generatedFrom", edgeType: "GENERATED_FROM", multiple: true }
212
+ ];
213
+ function parseGraphDocument(definition, filePath, source) {
214
+ const parsed = parseFrontmatterDocument(source);
215
+ const graphConfig = resolveGraphModelConfig(definition);
216
+ const slug = inferSlug(filePath, definition.contentDir);
217
+ const fileId = createFileNodeId(definition.name, slug);
218
+ const entityId = createEntityNodeId(definition, slug, parsed.frontmatter);
219
+ const titleValue = readGraphField(definition, parsed.frontmatter, graphConfig.titleField);
220
+ const title = typeof titleValue === "string" && titleValue.trim() ? titleValue.trim() : slug;
221
+ const tags = graphConfig.tagField ? ensureArray(readGraphField(definition, parsed.frontmatter, graphConfig.tagField)) : [];
222
+ const seriesValue = graphConfig.seriesField ? readGraphField(definition, parsed.frontmatter, graphConfig.seriesField) : void 0;
223
+ const series = typeof seriesValue === "string" && seriesValue.trim() ? seriesValue.trim() : null;
224
+ const explicitId = typeof parsed.frontmatter.id === "string" && parsed.frontmatter.id.trim() ? parsed.frontmatter.id.trim() : null;
225
+ const status = typeof parsed.frontmatter.status === "string" && parsed.frontmatter.status.trim() ? parsed.frontmatter.status.trim() : null;
226
+ const canonical = parsed.frontmatter.canonical === true;
227
+ const canonicalRef = typeof parsed.frontmatter.canonical === "string" && parsed.frontmatter.canonical.trim() ? normalizeReferenceValue(parsed.frontmatter.canonical.trim()) : null;
228
+ const version = typeof parsed.frontmatter.version === "string" && parsed.frontmatter.version.trim() ? parsed.frontmatter.version.trim() : null;
229
+ const domain = typeof parsed.frontmatter.domain === "string" && parsed.frontmatter.domain.trim() ? parsed.frontmatter.domain.trim() : null;
230
+ const audience = ensureArray(parsed.frontmatter.audience);
231
+ const updatedAtValue = readGraphField(definition, parsed.frontmatter, "updated_at") ?? parsed.frontmatter.updatedAt ?? parsed.frontmatter.updated_at;
232
+ const updatedAt = typeof updatedAtValue === "string" && updatedAtValue.trim() ? updatedAtValue.trim() : null;
233
+ const body = parsed.body;
234
+ const { headings, links, mdxImports } = extractMarkdownArtifacts(body);
235
+ const sections = graphConfig.enableSections ? buildSections(fileId, body, headings, links) : [];
236
+ const configuredReferences = graphConfig.referenceFields.flatMap((referenceField) => {
237
+ const rawValue = readGraphField(definition, parsed.frontmatter, referenceField.field);
238
+ return ensureArray(rawValue).map((value) => ({
239
+ field: referenceField.field,
240
+ value: normalizeReferenceValue(value),
241
+ targetModels: referenceField.targetModels,
242
+ edgeType: referenceField.edgeType ?? "REFERENCES"
243
+ }));
244
+ });
245
+ const inferredRelationshipReferences = GRAPH_FRONTMATTER_RELATION_FIELDS.flatMap(
246
+ (entry) => ensureArray(parsed.frontmatter[entry.field]).map((value) => ({
247
+ field: entry.field,
248
+ value: normalizeReferenceValue(value),
249
+ edgeType: entry.edgeType
250
+ }))
251
+ );
252
+ const canonicalReference = canonicalRef ? [{ field: "canonical", value: canonicalRef, edgeType: "REFERENCES" }] : [];
253
+ const explicitReferences = [...configuredReferences, ...inferredRelationshipReferences, ...canonicalReference];
254
+ return {
255
+ fileId,
256
+ entityId,
257
+ model: definition.name,
258
+ entityType: graphConfig.entityType,
259
+ slug,
260
+ title,
261
+ path: filePath,
262
+ relativePath: path.relative(definition.contentDir, filePath).replace(/\\/gu, "/"),
263
+ dirname: path.dirname(path.relative(definition.contentDir, filePath).replace(/\\/gu, "/")),
264
+ body,
265
+ normalizedBody: normalizeText(body),
266
+ frontmatter: parsed.frontmatter,
267
+ explicitId,
268
+ tags,
269
+ series,
270
+ status,
271
+ canonical,
272
+ canonicalRef,
273
+ version,
274
+ domain,
275
+ audience,
276
+ updatedAt,
277
+ sections,
278
+ headings,
279
+ links,
280
+ mdxImports,
281
+ explicitReferences
282
+ };
283
+ }
284
+ function catalogForDocument(document, hash) {
285
+ return {
286
+ path: document.path,
287
+ relativePath: document.relativePath,
288
+ model: document.model,
289
+ slug: document.slug,
290
+ fileId: document.fileId,
291
+ hash
292
+ };
293
+ }
294
+ function buildReferenceMaps(documents, models) {
295
+ const entityById = /* @__PURE__ */ new Map();
296
+ const fileById = /* @__PURE__ */ new Map();
297
+ const fileByPath = /* @__PURE__ */ new Map();
298
+ const fileByRelativePath = /* @__PURE__ */ new Map();
299
+ const entityByModelAndSlug = /* @__PURE__ */ new Map();
300
+ const sectionById = /* @__PURE__ */ new Map();
301
+ const sectionByAnchor = /* @__PURE__ */ new Map();
302
+ for (const document of documents) {
303
+ entityById.set(document.entityId, document);
304
+ fileById.set(document.fileId, document);
305
+ fileByPath.set(path.resolve(document.path), document);
306
+ fileByRelativePath.set(stripMarkdownExtension(document.relativePath), document);
307
+ entityByModelAndSlug.set(`${document.model}:${document.slug}`, document);
308
+ for (const section of document.sections) {
309
+ sectionById.set(section.id, section);
310
+ sectionByAnchor.set(`${document.fileId}#${section.headingSlug}`, section);
311
+ sectionByAnchor.set(`${document.fileId}#${section.headingPath}`, section);
312
+ }
313
+ }
314
+ const resolveReferenceString = (reference, sourceDocument) => {
315
+ const trimmed = reference.trim();
316
+ if (!trimmed) {
317
+ return { kind: "unresolved" };
318
+ }
319
+ const [pathPart, hashPart = ""] = trimmed.split("#");
320
+ if (entityById.has(trimmed)) {
321
+ const entity = entityById.get(trimmed);
322
+ return { kind: "entity", targetId: entity.entityId, fileId: entity.fileId };
323
+ }
324
+ if (pathPart.includes("/")) {
325
+ const [modelPrefix, ...slugParts] = pathPart.split("/");
326
+ if (slugParts.length > 0) {
327
+ try {
328
+ const model = resolveModelDefinition(modelPrefix, models).name;
329
+ const slug = slugParts.join("/");
330
+ const target = entityByModelAndSlug.get(`${model}:${slug}`);
331
+ if (target) {
332
+ if (hashPart) {
333
+ const section = sectionByAnchor.get(`${target.fileId}#${hashPart}`);
334
+ if (section) {
335
+ return { kind: "section", targetId: section.id, fileId: target.fileId };
336
+ }
337
+ }
338
+ return { kind: "entity", targetId: target.entityId, fileId: target.fileId };
339
+ }
340
+ } catch {
341
+ }
342
+ }
343
+ }
344
+ if (sourceDocument && !pathPart.includes("/")) {
345
+ const sameModel = entityByModelAndSlug.get(`${sourceDocument.model}:${pathPart}`);
346
+ if (sameModel) {
347
+ return { kind: "entity", targetId: sameModel.entityId, fileId: sameModel.fileId };
348
+ }
349
+ }
350
+ if (sourceDocument && pathPart) {
351
+ const absolute = path.resolve(path.dirname(sourceDocument.path), pathPart);
352
+ const normalized = stripMarkdownExtension(absolute);
353
+ for (const [filePath, document] of fileByPath.entries()) {
354
+ if (stripMarkdownExtension(filePath) === normalized) {
355
+ if (hashPart) {
356
+ const section = sectionByAnchor.get(`${document.fileId}#${hashPart}`);
357
+ if (section) {
358
+ return { kind: "section", targetId: section.id, fileId: document.fileId };
359
+ }
360
+ }
361
+ return { kind: "file", targetId: document.fileId, fileId: document.fileId };
362
+ }
363
+ }
364
+ }
365
+ if (pathPart.startsWith("/")) {
366
+ const normalizedRelative = stripMarkdownExtension(pathPart.replace(/^\/+/u, ""));
367
+ const direct = fileByRelativePath.get(normalizedRelative);
368
+ if (direct) {
369
+ if (hashPart) {
370
+ const section = sectionByAnchor.get(`${direct.fileId}#${hashPart}`);
371
+ if (section) {
372
+ return { kind: "section", targetId: section.id, fileId: direct.fileId };
373
+ }
374
+ }
375
+ return { kind: "file", targetId: direct.fileId, fileId: direct.fileId };
376
+ }
377
+ for (const document of documents) {
378
+ const routeLike = `${document.model}/${document.slug}`.replace(/^knowledge\//u, "knowledge/");
379
+ if (routeLike === normalizedRelative || document.slug === normalizedRelative) {
380
+ return { kind: "file", targetId: document.fileId, fileId: document.fileId };
381
+ }
382
+ }
383
+ }
384
+ if (hashPart && sourceDocument) {
385
+ const section = sectionByAnchor.get(`${sourceDocument.fileId}#${hashPart}`);
386
+ if (section) {
387
+ return { kind: "section", targetId: section.id, fileId: sourceDocument.fileId };
388
+ }
389
+ }
390
+ return { kind: "unresolved" };
391
+ };
392
+ return {
393
+ entityById,
394
+ fileById,
395
+ fileByPath,
396
+ fileByRelativePath,
397
+ entityByModelAndSlug,
398
+ sectionById,
399
+ sectionByAnchor,
400
+ resolveReferenceString
401
+ };
402
+ }
403
+ function buildGraphFromDocuments(documents, models, priorMetrics, delta) {
404
+ const nodes = /* @__PURE__ */ new Map();
405
+ const edges = /* @__PURE__ */ new Map();
406
+ const referenceNodes = /* @__PURE__ */ new Map();
407
+ const fileTargets = /* @__PURE__ */ new Map();
408
+ const maps = buildReferenceMaps(documents, models);
409
+ const validation = emptyGraphValidation();
410
+ const seenExplicitIds = /* @__PURE__ */ new Set();
411
+ for (const document of documents) {
412
+ if (!document.explicitId) {
413
+ validation.missingIds.push(document.fileId);
414
+ } else if (seenExplicitIds.has(document.explicitId)) {
415
+ validation.duplicateIds.push(document.explicitId);
416
+ } else {
417
+ seenExplicitIds.add(document.explicitId);
418
+ }
419
+ }
420
+ const addNode = (node) => {
421
+ nodes.set(node.id, node);
422
+ };
423
+ const addEdge = (edge) => {
424
+ edges.set(edge.id, edge);
425
+ if (["LINKS_TO", "REFERENCES"].includes(edge.type)) {
426
+ const targets = fileTargets.get(edge.ownerFileId ?? "") ?? /* @__PURE__ */ new Set();
427
+ targets.add(edge.targetId);
428
+ fileTargets.set(edge.ownerFileId ?? "", targets);
429
+ }
430
+ };
431
+ const unresolvedNodeFor = (ownerFileId, value) => {
432
+ const id = `reference:${sha1(`${ownerFileId}|${value}`)}`;
433
+ const node = referenceNodes.get(id) ?? {
434
+ id,
435
+ nodeType: "Reference",
436
+ ownerFileId,
437
+ title: value,
438
+ data: { reference: value, resolved: false }
439
+ };
440
+ referenceNodes.set(id, node);
441
+ addNode(node);
442
+ return node;
443
+ };
444
+ for (const document of documents) {
445
+ addNode({
446
+ id: document.fileId,
447
+ nodeType: "File",
448
+ sourceModel: document.model,
449
+ ownerFileId: document.fileId,
450
+ path: document.path,
451
+ slug: document.slug,
452
+ title: document.title,
453
+ tags: document.tags,
454
+ series: document.series,
455
+ status: document.status,
456
+ canonical: document.canonical,
457
+ canonicalId: document.canonicalRef,
458
+ version: document.version,
459
+ domain: document.domain,
460
+ audience: document.audience,
461
+ updatedAt: document.updatedAt,
462
+ text: document.body,
463
+ data: { relativePath: document.relativePath, explicitId: document.explicitId, frontmatter: document.frontmatter }
464
+ });
465
+ addNode({
466
+ id: document.entityId,
467
+ nodeType: document.entityType,
468
+ entityType: document.entityType,
469
+ sourceModel: document.model,
470
+ ownerFileId: document.fileId,
471
+ path: document.path,
472
+ slug: document.slug,
473
+ title: document.title,
474
+ tags: document.tags,
475
+ series: document.series,
476
+ fileId: document.fileId,
477
+ status: document.status,
478
+ canonical: document.canonical,
479
+ canonicalId: document.canonicalRef,
480
+ version: document.version,
481
+ domain: document.domain,
482
+ audience: document.audience,
483
+ updatedAt: document.updatedAt,
484
+ text: document.body,
485
+ data: { frontmatter: document.frontmatter }
486
+ });
487
+ addEdge({
488
+ id: computeEdgeId(document.fileId, "DEFINES", document.entityId, document.fileId),
489
+ type: "DEFINES",
490
+ sourceId: document.fileId,
491
+ targetId: document.entityId,
492
+ ownerFileId: document.fileId
493
+ });
494
+ addEdge({
495
+ id: computeEdgeId(document.entityId, "DEFINED_BY", document.fileId, document.fileId),
496
+ type: "DEFINED_BY",
497
+ sourceId: document.entityId,
498
+ targetId: document.fileId,
499
+ ownerFileId: document.fileId
500
+ });
501
+ for (const [index, section] of document.sections.entries()) {
502
+ addNode({
503
+ id: section.id,
504
+ nodeType: "Section",
505
+ sourceModel: document.model,
506
+ ownerFileId: document.fileId,
507
+ path: document.path,
508
+ fileId: document.fileId,
509
+ entityId: document.entityId,
510
+ slug: `${document.slug}#${section.headingSlug}`,
511
+ title: section.heading ?? document.title,
512
+ heading: section.heading,
513
+ headingPath: section.headingPath,
514
+ level: section.level,
515
+ tags: document.tags,
516
+ status: document.status,
517
+ canonical: document.canonical,
518
+ canonicalId: document.canonicalRef,
519
+ version: document.version,
520
+ domain: document.domain,
521
+ audience: document.audience,
522
+ updatedAt: document.updatedAt,
523
+ text: section.rawText,
524
+ data: {
525
+ ordinal: section.ordinal,
526
+ startOffset: section.startOffset,
527
+ endOffset: section.endOffset,
528
+ textLength: section.rawText.length,
529
+ linkDensity: section.outboundLinks.length,
530
+ ownerTitle: document.title
531
+ }
532
+ });
533
+ addEdge({
534
+ id: computeEdgeId(document.fileId, "HAS_SECTION", section.id, `${document.fileId}:${section.id}`),
535
+ type: "HAS_SECTION",
536
+ sourceId: document.fileId,
537
+ targetId: section.id,
538
+ ownerFileId: document.fileId
539
+ });
540
+ addEdge({
541
+ id: computeEdgeId(section.id, "BELONGS_TO_FILE", document.fileId, `${section.id}:${document.fileId}`),
542
+ type: "BELONGS_TO_FILE",
543
+ sourceId: section.id,
544
+ targetId: document.fileId,
545
+ ownerFileId: document.fileId
546
+ });
547
+ if (index > 0) {
548
+ const previous = document.sections[index - 1];
549
+ addEdge({
550
+ id: computeEdgeId(previous.id, "NEXT_SECTION", section.id, `${previous.id}:${section.id}`),
551
+ type: "NEXT_SECTION",
552
+ sourceId: previous.id,
553
+ targetId: section.id,
554
+ ownerFileId: document.fileId
555
+ });
556
+ addEdge({
557
+ id: computeEdgeId(section.id, "PREV_SECTION", previous.id, `${section.id}:${previous.id}`),
558
+ type: "PREV_SECTION",
559
+ sourceId: section.id,
560
+ targetId: previous.id,
561
+ ownerFileId: document.fileId
562
+ });
563
+ }
564
+ }
565
+ const stack = [];
566
+ for (const section of document.sections) {
567
+ if (section.level <= 0) {
568
+ continue;
569
+ }
570
+ while ((stack.at(-1)?.level ?? 0) >= section.level) {
571
+ stack.pop();
572
+ }
573
+ const parent = stack.at(-1);
574
+ if (parent) {
575
+ addEdge({
576
+ id: computeEdgeId(section.id, "PARENT_SECTION", parent.id, `${section.id}:${parent.id}`),
577
+ type: "PARENT_SECTION",
578
+ sourceId: section.id,
579
+ targetId: parent.id,
580
+ ownerFileId: document.fileId
581
+ });
582
+ addEdge({
583
+ id: computeEdgeId(parent.id, "CHILD_SECTION", section.id, `${parent.id}:${section.id}`),
584
+ type: "CHILD_SECTION",
585
+ sourceId: parent.id,
586
+ targetId: section.id,
587
+ ownerFileId: document.fileId
588
+ });
589
+ }
590
+ stack.push(section);
591
+ }
592
+ for (const tag of document.tags) {
593
+ const tagId = `tag:${normalizeText(tag)}`;
594
+ addNode({
595
+ id: tagId,
596
+ nodeType: "Tag",
597
+ title: tag,
598
+ slug: normalizeText(tag)
599
+ });
600
+ for (const sourceId of [document.fileId, document.entityId]) {
601
+ addEdge({
602
+ id: computeEdgeId(sourceId, "HAS_TAG", tagId, `${sourceId}:${tagId}`),
603
+ type: "HAS_TAG",
604
+ sourceId,
605
+ targetId: tagId,
606
+ ownerFileId: document.fileId
607
+ });
608
+ }
609
+ }
610
+ if (document.series) {
611
+ const seriesId = `series:${normalizeText(document.series)}`;
612
+ addNode({
613
+ id: seriesId,
614
+ nodeType: "Series",
615
+ title: document.series,
616
+ slug: normalizeText(document.series)
617
+ });
618
+ for (const sourceId of [document.fileId, document.entityId]) {
619
+ addEdge({
620
+ id: computeEdgeId(sourceId, "IN_SERIES", seriesId, `${sourceId}:${seriesId}`),
621
+ type: "IN_SERIES",
622
+ sourceId,
623
+ targetId: seriesId,
624
+ ownerFileId: document.fileId
625
+ });
626
+ }
627
+ }
628
+ for (const reference of document.explicitReferences) {
629
+ if (!AUTHORED_GRAPH_EDGE_TYPES.includes(reference.edgeType)) {
630
+ validation.invalidEdgeTypes.push({ ownerFileId: document.fileId, field: reference.field, edgeType: reference.edgeType });
631
+ continue;
632
+ }
633
+ const resolved = maps.resolveReferenceString(reference.value, document);
634
+ const targetId = resolved.kind === "unresolved" ? unresolvedNodeFor(document.fileId, reference.value).id : resolved.targetId;
635
+ if (resolved.kind === "unresolved") {
636
+ validation.brokenReferences.push({ ownerFileId: document.fileId, value: reference.value, edgeType: reference.edgeType });
637
+ }
638
+ if (reference.field === "canonical" && (resolved.kind === "unresolved" || resolved.targetId === document.entityId || resolved.targetId === document.fileId)) {
639
+ validation.invalidCanonicalRefs.push({ ownerFileId: document.fileId, value: reference.value });
640
+ }
641
+ if (reference.edgeType === "SUPERSEDES" && resolved.kind === "unresolved") {
642
+ validation.invalidSupersedesRefs.push({ ownerFileId: document.fileId, value: reference.value });
643
+ }
644
+ for (const sourceId of [document.entityId, document.fileId]) {
645
+ addEdge({
646
+ id: computeEdgeId(sourceId, reference.edgeType, targetId, `${reference.field}:${reference.value}:${sourceId}`),
647
+ type: reference.edgeType,
648
+ sourceId,
649
+ targetId,
650
+ ownerFileId: document.fileId,
651
+ data: { field: reference.field, value: reference.value }
652
+ });
653
+ }
654
+ }
655
+ for (const section of document.sections) {
656
+ for (const link of [...section.outboundLinks, ...document.mdxImports.map((entry) => ({
657
+ url: entry,
658
+ text: entry,
659
+ startOffset: section.startOffset,
660
+ endOffset: section.startOffset
661
+ }))]) {
662
+ const resolved = maps.resolveReferenceString(link.url, document);
663
+ const targetId = resolved.kind === "unresolved" ? unresolvedNodeFor(document.fileId, link.url).id : resolved.targetId;
664
+ addEdge({
665
+ id: computeEdgeId(section.id, "LINKS_TO", targetId, `${section.id}:${link.url}`),
666
+ type: "LINKS_TO",
667
+ sourceId: section.id,
668
+ targetId,
669
+ ownerFileId: document.fileId,
670
+ data: { url: link.url, text: link.text }
671
+ });
672
+ if (resolved.kind === "entity" || resolved.kind === "section") {
673
+ section.referencedEntityIds.push(targetId);
674
+ }
675
+ }
676
+ }
677
+ }
678
+ for (const document of documents) {
679
+ const candidateTargetIds = [...fileTargets.get(document.fileId) ?? /* @__PURE__ */ new Set()];
680
+ for (const section of document.sections) {
681
+ const text = normalizeText(section.rawText);
682
+ for (const targetId of candidateTargetIds) {
683
+ if (edges.has(computeEdgeId(section.id, "LINKS_TO", targetId, `${section.id}:${targetId}`)) || edges.has(computeEdgeId(section.id, "REFERENCES", targetId, `${section.id}:${targetId}`))) {
684
+ continue;
685
+ }
686
+ const target = nodes.get(targetId);
687
+ if (!target?.title) {
688
+ continue;
689
+ }
690
+ const variants = [target.title, target.slug ?? ""].map((entry) => normalizeText(entry)).filter((entry) => entry.length >= 4);
691
+ if (variants.some((variant) => variant && text.includes(variant))) {
692
+ addEdge({
693
+ id: computeEdgeId(section.id, "MENTIONS", targetId, `${section.id}:${targetId}`),
694
+ type: "MENTIONS",
695
+ sourceId: section.id,
696
+ targetId,
697
+ ownerFileId: document.fileId
698
+ });
699
+ }
700
+ }
701
+ }
702
+ }
703
+ const groupBy = (items, fn) => {
704
+ const grouped = /* @__PURE__ */ new Map();
705
+ for (const document of items) {
706
+ const key = fn(document);
707
+ if (!key) continue;
708
+ const bucket = grouped.get(key) ?? [];
709
+ bucket.push(document);
710
+ grouped.set(key, bucket);
711
+ }
712
+ return grouped;
713
+ };
714
+ for (const [, group] of groupBy(documents, (document) => document.model)) {
715
+ for (let i = 0; i < group.length; i += 1) {
716
+ for (let j = i + 1; j < group.length; j += 1) {
717
+ for (const [leftId, rightId] of [
718
+ [group[i].fileId, group[j].fileId],
719
+ [group[i].entityId, group[j].entityId]
720
+ ]) {
721
+ addEdge({
722
+ id: computeEdgeId(leftId, "SAME_COLLECTION", rightId, `${leftId}:${rightId}`),
723
+ type: "SAME_COLLECTION",
724
+ sourceId: leftId,
725
+ targetId: rightId,
726
+ ownerFileId: group[i].fileId
727
+ });
728
+ addEdge({
729
+ id: computeEdgeId(rightId, "SAME_COLLECTION", leftId, `${rightId}:${leftId}`),
730
+ type: "SAME_COLLECTION",
731
+ sourceId: rightId,
732
+ targetId: leftId,
733
+ ownerFileId: group[j].fileId
734
+ });
735
+ }
736
+ }
737
+ }
738
+ }
739
+ for (const [, group] of groupBy(documents, (document) => document.dirname && document.dirname !== "." ? document.dirname : null)) {
740
+ for (let i = 0; i < group.length; i += 1) {
741
+ for (let j = i + 1; j < group.length; j += 1) {
742
+ for (const [leftId, rightId] of [
743
+ [group[i].fileId, group[j].fileId],
744
+ [group[i].entityId, group[j].entityId]
745
+ ]) {
746
+ addEdge({
747
+ id: computeEdgeId(leftId, "SAME_DIRECTORY", rightId, `${leftId}:${rightId}`),
748
+ type: "SAME_DIRECTORY",
749
+ sourceId: leftId,
750
+ targetId: rightId,
751
+ ownerFileId: group[i].fileId
752
+ });
753
+ addEdge({
754
+ id: computeEdgeId(rightId, "SAME_DIRECTORY", leftId, `${rightId}:${leftId}`),
755
+ type: "SAME_DIRECTORY",
756
+ sourceId: rightId,
757
+ targetId: leftId,
758
+ ownerFileId: group[j].fileId
759
+ });
760
+ }
761
+ }
762
+ }
763
+ }
764
+ const metrics = {
765
+ ...priorMetrics ?? emptyGraphMetrics(),
766
+ totalFiles: documents.length,
767
+ totalSections: documents.reduce((sum, document) => sum + document.sections.length, 0),
768
+ totalEntities: documents.length,
769
+ totalEdges: edges.size,
770
+ unresolvedReferences: [...nodes.values()].filter((node) => node.nodeType === "Reference").length,
771
+ validation: {
772
+ missingIds: validation.missingIds.length,
773
+ duplicateIds: validation.duplicateIds.length,
774
+ brokenReferences: validation.brokenReferences.length,
775
+ invalidEdgeTypes: validation.invalidEdgeTypes.length,
776
+ invalidCanonicalRefs: validation.invalidCanonicalRefs.length,
777
+ invalidSupersedesRefs: validation.invalidSupersedesRefs.length
778
+ },
779
+ lastRefreshAt: (/* @__PURE__ */ new Date()).toISOString()
780
+ };
781
+ return {
782
+ nodes: [...nodes.values()],
783
+ edges: [...edges.values()],
784
+ metrics,
785
+ validation,
786
+ delta: delta ?? { added: [], modified: [], removed: [] }
787
+ };
788
+ }
789
+ async function readSnapshotFile(filePath) {
790
+ try {
791
+ return JSON.parse(await readFile(filePath, "utf8"));
792
+ } catch {
793
+ return null;
794
+ }
795
+ }
796
+ async function loadGraphSnapshot(repoRoot, models) {
797
+ const snapshotRoot = graphSnapshotRoot(repoRoot);
798
+ const graph = await readSnapshotFile(
799
+ path.join(snapshotRoot, "graph.json")
800
+ );
801
+ const catalog = await readSnapshotFile(path.join(snapshotRoot, "catalog.json"));
802
+ const metrics = await readSnapshotFile(path.join(snapshotRoot, "metrics.json"));
803
+ const delta = await readSnapshotFile(path.join(snapshotRoot, "deltas.json"));
804
+ const currentSignature = computeModelSignature(models);
805
+ if (!graph || graph.version !== GRAPH_SNAPSHOT_VERSION || graph.modelSignature !== currentSignature || !catalog) {
806
+ return null;
807
+ }
808
+ return {
809
+ modelSignature: graph.modelSignature,
810
+ documents: graph.documents,
811
+ nodes: graph.nodes,
812
+ edges: graph.edges,
813
+ catalog: catalog.catalog,
814
+ metrics: metrics ?? emptyGraphMetrics(),
815
+ validation: graph.validation ?? emptyGraphValidation(),
816
+ delta: delta ?? { added: [], modified: [], removed: [] },
817
+ snapshotRoot
818
+ };
819
+ }
820
+ async function saveGraphSnapshot(state) {
821
+ await mkdir(state.snapshotRoot, { recursive: true });
822
+ const graphPayload = {
823
+ version: GRAPH_SNAPSHOT_VERSION,
824
+ modelSignature: state.modelSignature,
825
+ documents: state.documents,
826
+ nodes: state.nodes,
827
+ edges: state.edges,
828
+ validation: state.validation
829
+ };
830
+ await Promise.all([
831
+ writeFile(path.join(state.snapshotRoot, "graph.json"), `${JSON.stringify(graphPayload, null, 2)}
832
+ `, "utf8"),
833
+ writeFile(path.join(state.snapshotRoot, "catalog.json"), `${JSON.stringify({ catalog: state.catalog }, null, 2)}
834
+ `, "utf8"),
835
+ writeFile(path.join(state.snapshotRoot, "metrics.json"), `${JSON.stringify(state.metrics, null, 2)}
836
+ `, "utf8"),
837
+ writeFile(path.join(state.snapshotRoot, "deltas.json"), `${JSON.stringify(state.delta, null, 2)}
838
+ `, "utf8"),
839
+ writeFile(
840
+ path.join(state.snapshotRoot, "indexes.json"),
841
+ `${JSON.stringify({ files: [], sections: [], entities: [] }, null, 2)}
842
+ `,
843
+ "utf8"
844
+ )
845
+ ]);
846
+ }
847
+ async function hashFile(filePath) {
848
+ const source = await readFile(filePath, "utf8");
849
+ return {
850
+ source,
851
+ hash: sha1(source)
852
+ };
853
+ }
854
+ function contentDefinitions(models) {
855
+ return Object.values(models).filter((definition) => definition.storage === "content" && Boolean(definition.contentDir)).sort((left, right) => left.name.localeCompare(right.name));
856
+ }
857
+ async function refreshGraphBuildState(repoRoot, models, request, priorState) {
858
+ const snapshotRoot = graphSnapshotRoot(repoRoot);
859
+ const modelSignature = computeModelSignature(models);
860
+ const priorDocuments = new Map((priorState?.documents ?? []).map((document) => [document.fileId, document]));
861
+ const priorCatalog = new Map((priorState?.catalog ?? []).map((entry) => [path.resolve(entry.path), entry]));
862
+ const nextDocuments = new Map(priorDocuments);
863
+ const nextCatalog = new Map(priorCatalog);
864
+ const requestedPaths = request?.paths?.map((entry) => path.resolve(repoRoot, entry)).filter(Boolean);
865
+ const changed = { added: [], modified: [], removed: [] };
866
+ const trackedPaths = /* @__PURE__ */ new Set();
867
+ const definitions = contentDefinitions(models);
868
+ if (requestedPaths && requestedPaths.length > 0) {
869
+ for (const requestedPath of requestedPaths) {
870
+ const matchingDefinition = definitions.find((definition) => requestedPath.startsWith(path.resolve(definition.contentDir)));
871
+ if (!matchingDefinition) {
872
+ continue;
873
+ }
874
+ trackedPaths.add(requestedPath);
875
+ try {
876
+ const fileStats = await stat(requestedPath);
877
+ if (!fileStats.isFile()) continue;
878
+ const { source, hash } = await hashFile(requestedPath);
879
+ const parsed = parseGraphDocument(matchingDefinition, requestedPath, source);
880
+ const existing = priorCatalog.get(requestedPath);
881
+ nextDocuments.set(parsed.fileId, parsed);
882
+ nextCatalog.set(requestedPath, catalogForDocument(parsed, hash));
883
+ if (!existing) {
884
+ changed.added.push(parsed.fileId);
885
+ } else if (existing.hash !== hash) {
886
+ changed.modified.push(parsed.fileId);
887
+ }
888
+ } catch {
889
+ const existing = priorCatalog.get(requestedPath);
890
+ if (existing) {
891
+ changed.removed.push(existing.fileId);
892
+ nextCatalog.delete(requestedPath);
893
+ nextDocuments.delete(existing.fileId);
894
+ }
895
+ }
896
+ }
897
+ } else {
898
+ for (const definition of definitions) {
899
+ const files = await walkMarkdownFiles(definition.contentDir);
900
+ for (const filePath of files) {
901
+ const resolvedPath = path.resolve(filePath);
902
+ trackedPaths.add(resolvedPath);
903
+ const { source, hash } = await hashFile(resolvedPath);
904
+ const parsed = parseGraphDocument(definition, resolvedPath, source);
905
+ const existing = priorCatalog.get(resolvedPath);
906
+ nextDocuments.set(parsed.fileId, parsed);
907
+ nextCatalog.set(resolvedPath, catalogForDocument(parsed, hash));
908
+ if (!existing) {
909
+ changed.added.push(parsed.fileId);
910
+ } else if (existing.hash !== hash) {
911
+ changed.modified.push(parsed.fileId);
912
+ }
913
+ }
914
+ }
915
+ for (const [existingPath, existing] of priorCatalog.entries()) {
916
+ if (!trackedPaths.has(existingPath)) {
917
+ changed.removed.push(existing.fileId);
918
+ nextCatalog.delete(existingPath);
919
+ nextDocuments.delete(existing.fileId);
920
+ }
921
+ }
922
+ }
923
+ const built = buildGraphFromDocuments(
924
+ [...nextDocuments.values()].sort((left, right) => left.fileId.localeCompare(right.fileId)),
925
+ models,
926
+ priorState?.metrics,
927
+ changed
928
+ );
929
+ return {
930
+ modelSignature,
931
+ documents: [...nextDocuments.values()].sort((left, right) => left.fileId.localeCompare(right.fileId)),
932
+ nodes: built.nodes,
933
+ edges: built.edges,
934
+ catalog: [...nextCatalog.values()].sort((left, right) => left.fileId.localeCompare(right.fileId)),
935
+ metrics: built.metrics,
936
+ validation: built.validation,
937
+ delta: changed,
938
+ snapshotRoot
939
+ };
940
+ }
941
+ async function clearGraphSnapshot(repoRoot) {
942
+ await rm(graphSnapshotRoot(repoRoot), { recursive: true, force: true });
943
+ }
944
+ export {
945
+ clearGraphSnapshot,
946
+ loadGraphSnapshot,
947
+ refreshGraphBuildState,
948
+ saveGraphSnapshot
949
+ };