@hawkeyexl/manni 4.3.1 → 4.4.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 (225) hide show
  1. package/README.md +9 -1
  2. package/dist/batch-GFNLTGOH.js +36 -0
  3. package/dist/build-COYIQ25L.js +31 -0
  4. package/dist/calibrate-MDBLJKEE.js +31 -0
  5. package/dist/capture-LRML2PKV.js +32 -0
  6. package/dist/check-ASPIHA6B.js +40 -0
  7. package/dist/check-ASPIHA6B.js.map +1 -0
  8. package/dist/check-EDLBYL5N.js +27 -0
  9. package/dist/check-EDLBYL5N.js.map +1 -0
  10. package/dist/check-HYNGXYJV.js +18 -0
  11. package/dist/check-HYNGXYJV.js.map +1 -0
  12. package/dist/chunk-2B7ZNTPV.js +230 -0
  13. package/dist/chunk-2B7ZNTPV.js.map +1 -0
  14. package/dist/chunk-2H7UOFLK.js +11 -0
  15. package/dist/chunk-2H7UOFLK.js.map +1 -0
  16. package/dist/chunk-37LP5HMZ.js +1042 -0
  17. package/dist/chunk-37LP5HMZ.js.map +1 -0
  18. package/dist/chunk-3VK37OR2.js +2957 -0
  19. package/dist/chunk-3VK37OR2.js.map +1 -0
  20. package/dist/chunk-4IUC2MNL.js +2165 -0
  21. package/dist/chunk-4IUC2MNL.js.map +1 -0
  22. package/dist/{chunk-3R23C5DT.js → chunk-4TSWU2SU.js} +2 -14
  23. package/dist/chunk-4TSWU2SU.js.map +1 -0
  24. package/dist/chunk-57NEQRVO.js +10921 -0
  25. package/dist/chunk-57NEQRVO.js.map +1 -0
  26. package/dist/chunk-6JSSEWBG.js +6737 -0
  27. package/dist/chunk-6JSSEWBG.js.map +1 -0
  28. package/dist/chunk-6OYEI2VV.js +88 -0
  29. package/dist/chunk-6OYEI2VV.js.map +1 -0
  30. package/dist/chunk-77AINBYM.js +19 -0
  31. package/dist/chunk-77AINBYM.js.map +1 -0
  32. package/dist/chunk-7UKO3ASZ.js +54 -0
  33. package/dist/chunk-7UKO3ASZ.js.map +1 -0
  34. package/dist/chunk-A5ULMVHQ.js +164 -0
  35. package/dist/chunk-A5ULMVHQ.js.map +1 -0
  36. package/dist/chunk-C4SQM22E.js +299 -0
  37. package/dist/chunk-C4SQM22E.js.map +1 -0
  38. package/dist/chunk-CK7SKVHR.js +1042 -0
  39. package/dist/chunk-CK7SKVHR.js.map +1 -0
  40. package/dist/{chunk-G5CM2VGQ.js → chunk-DXNZS72W.js} +8 -94
  41. package/dist/chunk-DXNZS72W.js.map +1 -0
  42. package/dist/chunk-EPH54CAY.js +374 -0
  43. package/dist/chunk-EPH54CAY.js.map +1 -0
  44. package/dist/{chunk-KJR7OVDX.js → chunk-FBEQCK47.js} +56 -137
  45. package/dist/chunk-FBEQCK47.js.map +1 -0
  46. package/dist/{chunk-IOYXPBCL.js → chunk-G3T47CFV.js} +1 -7
  47. package/dist/{chunk-IOYXPBCL.js.map → chunk-G3T47CFV.js.map} +1 -1
  48. package/dist/chunk-GCQRPUSB.js +1287 -0
  49. package/dist/chunk-GCQRPUSB.js.map +1 -0
  50. package/dist/chunk-GLP5PSXB.js +72 -0
  51. package/dist/chunk-GLP5PSXB.js.map +1 -0
  52. package/dist/chunk-I2AH3CJJ.js +1192 -0
  53. package/dist/chunk-I2AH3CJJ.js.map +1 -0
  54. package/dist/chunk-IDV6HJCB.js +388 -0
  55. package/dist/chunk-IDV6HJCB.js.map +1 -0
  56. package/dist/chunk-IKEWBMCN.js +98 -0
  57. package/dist/chunk-IKEWBMCN.js.map +1 -0
  58. package/dist/chunk-JCDXE76J.js +52 -0
  59. package/dist/chunk-JCDXE76J.js.map +1 -0
  60. package/dist/chunk-JXLTXMJ7.js +455 -0
  61. package/dist/chunk-JXLTXMJ7.js.map +1 -0
  62. package/dist/chunk-LJ2RDH3F.js +49 -0
  63. package/dist/chunk-LJ2RDH3F.js.map +1 -0
  64. package/dist/chunk-M2JCK5F6.js +805 -0
  65. package/dist/chunk-M2JCK5F6.js.map +1 -0
  66. package/dist/chunk-M3W7J6MU.js +4398 -0
  67. package/dist/chunk-M3W7J6MU.js.map +1 -0
  68. package/dist/chunk-MMTSSTG7.js +23 -0
  69. package/dist/chunk-MMTSSTG7.js.map +1 -0
  70. package/dist/chunk-MNH7QO4Z.js +15 -0
  71. package/dist/chunk-MNH7QO4Z.js.map +1 -0
  72. package/dist/chunk-MWJLZMBL.js +934 -0
  73. package/dist/chunk-MWJLZMBL.js.map +1 -0
  74. package/dist/chunk-MYMBCTDE.js +387 -0
  75. package/dist/chunk-MYMBCTDE.js.map +1 -0
  76. package/dist/chunk-NCPWXHDO.js +683 -0
  77. package/dist/chunk-NCPWXHDO.js.map +1 -0
  78. package/dist/chunk-NLXTXE5Y.js +1661 -0
  79. package/dist/chunk-NLXTXE5Y.js.map +1 -0
  80. package/dist/{chunk-CFDUZRM2.js → chunk-NXV7WE2D.js} +32 -240
  81. package/dist/chunk-NXV7WE2D.js.map +1 -0
  82. package/dist/chunk-OWBRDNRZ.js +16 -0
  83. package/dist/chunk-OWBRDNRZ.js.map +1 -0
  84. package/dist/{chunk-KTSJ2ZHA.js → chunk-PAU352SV.js} +5 -3
  85. package/dist/{chunk-KTSJ2ZHA.js.map → chunk-PAU352SV.js.map} +1 -1
  86. package/dist/chunk-PCE7QHXJ.js +68 -0
  87. package/dist/chunk-PCE7QHXJ.js.map +1 -0
  88. package/dist/chunk-PIKOVN6W.js +1651 -0
  89. package/dist/chunk-PIKOVN6W.js.map +1 -0
  90. package/dist/chunk-PNPQTHBT.js +17 -0
  91. package/dist/chunk-PNPQTHBT.js.map +1 -0
  92. package/dist/chunk-PU6DFJMY.js +1975 -0
  93. package/dist/chunk-PU6DFJMY.js.map +1 -0
  94. package/dist/chunk-Q35YIDBV.js +1202 -0
  95. package/dist/chunk-Q35YIDBV.js.map +1 -0
  96. package/dist/{chunk-5HNHEUD5.js → chunk-QQUKGCAJ.js} +3 -3
  97. package/dist/chunk-QRWGPQKO.js +198 -0
  98. package/dist/chunk-QRWGPQKO.js.map +1 -0
  99. package/dist/{chunk-UBMMXS2Y.js → chunk-QZ6LRSNV.js} +69 -48
  100. package/dist/{chunk-UBMMXS2Y.js.map → chunk-QZ6LRSNV.js.map} +1 -1
  101. package/dist/chunk-RL5XWQOL.js +3872 -0
  102. package/dist/chunk-RL5XWQOL.js.map +1 -0
  103. package/dist/chunk-RNVYGWIC.js +30 -0
  104. package/dist/chunk-RNVYGWIC.js.map +1 -0
  105. package/dist/chunk-SE4JN5XN.js +19 -0
  106. package/dist/chunk-SE4JN5XN.js.map +1 -0
  107. package/dist/chunk-SKSDRUUK.js +622 -0
  108. package/dist/chunk-SKSDRUUK.js.map +1 -0
  109. package/dist/chunk-TIUTIOFB.js +25 -0
  110. package/dist/chunk-TIUTIOFB.js.map +1 -0
  111. package/dist/chunk-TK3FC4RF.js +3090 -0
  112. package/dist/chunk-TK3FC4RF.js.map +1 -0
  113. package/dist/chunk-TU2WUBVZ.js +2 -0
  114. package/dist/chunk-TU2WUBVZ.js.map +1 -0
  115. package/dist/chunk-TZOYZK2E.js +98 -0
  116. package/dist/chunk-TZOYZK2E.js.map +1 -0
  117. package/dist/chunk-UJCVAWP7.js +178 -0
  118. package/dist/chunk-UJCVAWP7.js.map +1 -0
  119. package/dist/chunk-UKZNLVJW.js +17 -0
  120. package/dist/chunk-UKZNLVJW.js.map +1 -0
  121. package/dist/{chunk-HAPTXWU2.js → chunk-UQISLDAV.js} +4 -4
  122. package/dist/chunk-WB3QM6PP.js +5440 -0
  123. package/dist/chunk-WB3QM6PP.js.map +1 -0
  124. package/dist/chunk-YB4IBW3M.js +96 -0
  125. package/dist/chunk-YB4IBW3M.js.map +1 -0
  126. package/dist/chunk-YEUYKR62.js +72 -0
  127. package/dist/chunk-YEUYKR62.js.map +1 -0
  128. package/dist/chunk-YJOGUXGP.js +634 -0
  129. package/dist/chunk-YJOGUXGP.js.map +1 -0
  130. package/dist/chunk-YNHZHSSM.js +41 -0
  131. package/dist/chunk-YNHZHSSM.js.map +1 -0
  132. package/dist/chunk-YTURO7SI.js +979 -0
  133. package/dist/chunk-YTURO7SI.js.map +1 -0
  134. package/dist/chunk-ZXRAE55H.js +31 -0
  135. package/dist/chunk-ZXRAE55H.js.map +1 -0
  136. package/dist/cli-3BIBYHFK.js +473 -0
  137. package/dist/cli-3BIBYHFK.js.map +1 -0
  138. package/dist/cli-673OTRGE.js +28 -0
  139. package/dist/cli-673OTRGE.js.map +1 -0
  140. package/dist/cli-6HIDIFVT.js +1203 -0
  141. package/dist/cli-6HIDIFVT.js.map +1 -0
  142. package/dist/cli-6UB5N6CU.js +232 -0
  143. package/dist/cli-6UB5N6CU.js.map +1 -0
  144. package/dist/cli-LFP5ZGYS.js +346 -0
  145. package/dist/cli-LFP5ZGYS.js.map +1 -0
  146. package/dist/cli-MN7PYMBI.js +552 -0
  147. package/dist/cli-MN7PYMBI.js.map +1 -0
  148. package/dist/cli-PYPX6O6E.js +739 -0
  149. package/dist/cli-PYPX6O6E.js.map +1 -0
  150. package/dist/cli-RPM5PELX.js +626 -0
  151. package/dist/cli-RPM5PELX.js.map +1 -0
  152. package/dist/cli-VI7VHWNZ.js +423 -0
  153. package/dist/cli-VI7VHWNZ.js.map +1 -0
  154. package/dist/cli.d.ts +12 -2
  155. package/dist/cli.js +109 -7661
  156. package/dist/cli.js.map +1 -1
  157. package/dist/docmeta.js +15 -3
  158. package/dist/docmeta.js.map +1 -1
  159. package/dist/github-6QMYOHC3.js +27 -0
  160. package/dist/github-6QMYOHC3.js.map +1 -0
  161. package/dist/github-SXPGP5DB.js +32 -0
  162. package/dist/github-SXPGP5DB.js.map +1 -0
  163. package/dist/index.d.ts +65 -3
  164. package/dist/index.js +407 -337
  165. package/dist/index.js.map +1 -1
  166. package/dist/json-7WV7KL6H.js +17 -0
  167. package/dist/json-7WV7KL6H.js.map +1 -0
  168. package/dist/json-SY7KLNC7.js +35 -0
  169. package/dist/json-SY7KLNC7.js.map +1 -0
  170. package/dist/lint-ASMJLLPA.js +28 -0
  171. package/dist/lint-ASMJLLPA.js.map +1 -0
  172. package/dist/list-KZNVTNVC.js +31 -0
  173. package/dist/list-KZNVTNVC.js.map +1 -0
  174. package/dist/list-Q57YTFUQ.js +29 -0
  175. package/dist/list-Q57YTFUQ.js.map +1 -0
  176. package/dist/page-KSGV5RD4.js +41 -0
  177. package/dist/page-KSGV5RD4.js.map +1 -0
  178. package/dist/picker-3XF36MJF.js +14 -0
  179. package/dist/picker-3XF36MJF.js.map +1 -0
  180. package/dist/pretty-A5EGTN35.js +37 -0
  181. package/dist/pretty-A5EGTN35.js.map +1 -0
  182. package/dist/pretty-CXSDXNAS.js +43 -0
  183. package/dist/pretty-CXSDXNAS.js.map +1 -0
  184. package/dist/reporters-HUY7UXFS.js +42 -0
  185. package/dist/reporters-HUY7UXFS.js.map +1 -0
  186. package/dist/reporters-K2VUKSAK.js +71 -0
  187. package/dist/reporters-K2VUKSAK.js.map +1 -0
  188. package/dist/reporters-M6LJ2IYU.js +21 -0
  189. package/dist/reporters-M6LJ2IYU.js.map +1 -0
  190. package/dist/run-3DYJNRIG.js +33 -0
  191. package/dist/run-3DYJNRIG.js.map +1 -0
  192. package/dist/term-BIQUPMOR.js +1592 -0
  193. package/dist/term-BIQUPMOR.js.map +1 -0
  194. package/dist/types-Y6TXHM7M.js +10 -0
  195. package/dist/types-Y6TXHM7M.js.map +1 -0
  196. package/dist/validate-53RKM2XG.js +16 -0
  197. package/dist/validate-53RKM2XG.js.map +1 -0
  198. package/package.json +1 -1
  199. package/plugin/manni/.claude-plugin/plugin.json +9 -0
  200. package/plugin/manni/hooks/hooks.json +69 -0
  201. package/plugin/manni/hooks/manni.mjs +157 -0
  202. package/plugin/manni/skills/check/SKILL.md +20 -0
  203. package/plugin/manni/skills/evals/SKILL.md +27 -0
  204. package/plugin/manni/skills/fix/SKILL.md +56 -0
  205. package/plugin/manni/skills/setup/SKILL.md +41 -0
  206. package/dist/batch-A5HZKYIW.js +0 -18
  207. package/dist/calibrate-S5K6QHUY.js +0 -13
  208. package/dist/capture-OZEZDABQ.js +0 -15
  209. package/dist/chunk-3R23C5DT.js.map +0 -1
  210. package/dist/chunk-CFDUZRM2.js.map +0 -1
  211. package/dist/chunk-G5CM2VGQ.js.map +0 -1
  212. package/dist/chunk-KJR7OVDX.js.map +0 -1
  213. package/dist/chunk-V4KUVE47.js +0 -27170
  214. package/dist/chunk-V4KUVE47.js.map +0 -1
  215. package/dist/chunk-ZQBTK3DO.js +0 -26518
  216. package/dist/chunk-ZQBTK3DO.js.map +0 -1
  217. package/dist/picker-6HRMETE7.js +0 -12
  218. package/plugin/tracevals/.claude-plugin/plugin.json +0 -9
  219. package/plugin/tracevals/hooks/hooks.json +0 -17
  220. /package/dist/{batch-A5HZKYIW.js.map → batch-GFNLTGOH.js.map} +0 -0
  221. /package/dist/{calibrate-S5K6QHUY.js.map → build-COYIQ25L.js.map} +0 -0
  222. /package/dist/{capture-OZEZDABQ.js.map → calibrate-MDBLJKEE.js.map} +0 -0
  223. /package/dist/{picker-6HRMETE7.js.map → capture-LRML2PKV.js.map} +0 -0
  224. /package/dist/{chunk-5HNHEUD5.js.map → chunk-QQUKGCAJ.js.map} +0 -0
  225. /package/dist/{chunk-HAPTXWU2.js.map → chunk-UQISLDAV.js.map} +0 -0
@@ -0,0 +1,1661 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_CONFIG_FILENAME,
4
+ DEFAULT_INDEX_FILES,
5
+ DEFAULT_LINK_EXTENSIONS,
6
+ GRAPH_NOT_APPLICABLE_TO_VARIANT,
7
+ GRAPH_NOT_SOFTWARE_SUBJECT,
8
+ GraphError,
9
+ IIRDS_HAS_SUBJECT,
10
+ IIRDS_HAS_TOPIC_TYPE,
11
+ IIRDS_PRODUCT_VARIANT,
12
+ IIRDS_RELATES_TO_LIFECYCLE_PHASE,
13
+ IIRDS_RELATES_TO_PRODUCT_VARIANT,
14
+ NS,
15
+ PAGE_TYPE_TO_TOPIC_TYPE,
16
+ PREFIXES,
17
+ RDF_TYPE,
18
+ ROLE,
19
+ SOFTWARE_LIFECYCLE_IRIS,
20
+ SOFTWARE_SUBJECT_IRIS,
21
+ STDIN_PATH,
22
+ TOPIC_TYPE_IRIS,
23
+ assertCollectionWithoutPaths,
24
+ byCodeUnit,
25
+ conceptSlug,
26
+ encodeSegment,
27
+ loadRunConfig,
28
+ mintAgentIri,
29
+ mintBuildActivityIri,
30
+ mintConceptIri,
31
+ mintDocIri,
32
+ mintGraphIri,
33
+ mintProductIri,
34
+ mintSchemeIri,
35
+ mintSectionIri,
36
+ normalizeDocPath
37
+ } from "./chunk-M2JCK5F6.js";
38
+ import {
39
+ metaPageView
40
+ } from "./chunk-QRWGPQKO.js";
41
+ import {
42
+ STDIN_REQUIRES_AS
43
+ } from "./chunk-TK3FC4RF.js";
44
+ import {
45
+ writeTextAtomic
46
+ } from "./chunk-M3W7J6MU.js";
47
+ import {
48
+ extractFrontmatter
49
+ } from "./chunk-57NEQRVO.js";
50
+ import {
51
+ selectCollections
52
+ } from "./chunk-Q35YIDBV.js";
53
+ import {
54
+ package_default
55
+ } from "./chunk-UJCVAWP7.js";
56
+ import {
57
+ errorMessage
58
+ } from "./chunk-G3T47CFV.js";
59
+
60
+ // src/graph/commands/build.ts
61
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
62
+ import { dirname, resolve as resolve3 } from "node:path";
63
+
64
+ // src/graph/core/analyze.ts
65
+ import { createHash } from "node:crypto";
66
+ import { unified } from "unified";
67
+ import remarkParse from "remark-parse";
68
+ import remarkGfm from "remark-gfm";
69
+ import remarkFrontmatter from "remark-frontmatter";
70
+ import remarkMdx from "remark-mdx";
71
+ import { toString as mdastToString } from "mdast-util-to-string";
72
+ import GithubSlugger from "github-slugger";
73
+ var DOC_FORMATS = ["markdown", "mdx"];
74
+ function formatOf(path, format) {
75
+ return format ?? (path.endsWith(".mdx") ? "mdx" : "markdown");
76
+ }
77
+ var processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ["yaml", "toml"]);
78
+ var mdxProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ["yaml", "toml"]).use(remarkMdx);
79
+ var IMAGE_ELEMENTS = /* @__PURE__ */ new Set(["img", "image"]);
80
+ function jsxAttributeValue(node, attributeName) {
81
+ const attributes = node.attributes;
82
+ if (!Array.isArray(attributes)) return void 0;
83
+ for (const attribute of attributes) {
84
+ if (attribute.type !== "mdxJsxAttribute") continue;
85
+ if (attribute.name !== attributeName) continue;
86
+ return typeof attribute.value === "string" ? attribute.value : void 0;
87
+ }
88
+ return void 0;
89
+ }
90
+ function hasScheme(target) {
91
+ return /^[a-z][a-z0-9+.-]*:/i.test(target);
92
+ }
93
+ function resolveRelative(docPath, target) {
94
+ const baseSegments = normalizeDocPath(docPath).split("/").slice(0, -1);
95
+ const segments = [...baseSegments];
96
+ for (const part of target.split("/")) {
97
+ if (part === "" || part === ".") continue;
98
+ if (part === "..") {
99
+ if (segments.length === 0) return null;
100
+ if (segments[segments.length - 1] === "..") segments.push("..");
101
+ else segments.pop();
102
+ } else {
103
+ segments.push(part);
104
+ }
105
+ }
106
+ return segments.join("/");
107
+ }
108
+ var HAS_EXTENSION = /\.[a-z0-9]+$/i;
109
+ function addressesDocument(target, extensions) {
110
+ if (target === "" || !HAS_EXTENSION.test(target)) return true;
111
+ if (extensions.length === 0) return true;
112
+ const ext = target.slice(target.lastIndexOf(".")).toLowerCase();
113
+ return extensions.some((e) => e.toLowerCase() === ext);
114
+ }
115
+ function slugNorm(path) {
116
+ return path.toLowerCase().replace(/[-_]/g, "");
117
+ }
118
+ var PathIndex = class {
119
+ constructor(exact) {
120
+ this.exact = exact;
121
+ for (const path of exact) {
122
+ const lower = path.toLowerCase();
123
+ this.lower.set(lower, this.lower.has(lower) ? null : path);
124
+ const slug = slugNorm(path);
125
+ this.slugged.set(slug, this.slugged.has(slug) ? null : path);
126
+ }
127
+ }
128
+ exact;
129
+ lower = /* @__PURE__ */ new Map();
130
+ slugged = /* @__PURE__ */ new Map();
131
+ resolve(candidate) {
132
+ if (this.exact.has(candidate)) return candidate;
133
+ return this.lower.get(candidate.toLowerCase()) ?? this.slugged.get(slugNorm(candidate)) ?? void 0;
134
+ }
135
+ };
136
+ function safeDecode(value) {
137
+ try {
138
+ return decodeURIComponent(value);
139
+ } catch {
140
+ return value;
141
+ }
142
+ }
143
+ function targetCandidates(target, isDirectory, extensions, indexFiles) {
144
+ if (target !== "" && HAS_EXTENSION.test(target)) return [target];
145
+ const extensionCandidates = target === "" ? [] : extensions.map((ext) => `${target}${ext}`);
146
+ const dir = target === "" ? "" : `${target}/`;
147
+ const indexCandidates = [];
148
+ for (const indexFile of indexFiles) {
149
+ for (const ext of extensions)
150
+ indexCandidates.push(`${dir}${indexFile}${ext}`);
151
+ }
152
+ return isDirectory ? [...indexCandidates, ...extensionCandidates] : [...extensionCandidates, ...indexCandidates];
153
+ }
154
+ function routeLanguageFor(path, routes) {
155
+ let best;
156
+ for (const mapping of routes) {
157
+ if (mapping.language === void 0) continue;
158
+ const root = mapping.root;
159
+ const covers = root === "" || path === root || path.startsWith(`${root}/`);
160
+ if (!covers) continue;
161
+ if (best === void 0 || root.length > best.root.length) {
162
+ best = { root, language: mapping.language };
163
+ }
164
+ }
165
+ return best?.language;
166
+ }
167
+ function resolveRoute(pathPart, routes, index) {
168
+ const isDirectory = /\/+$/.test(pathPart);
169
+ const clean = safeDecode(pathPart).replace(/\/+$/, "");
170
+ let anyMatched = false;
171
+ for (const mapping of routes) {
172
+ if (clean !== mapping.basePath && !clean.startsWith(`${mapping.basePath}/`)) {
173
+ continue;
174
+ }
175
+ const rest = clean.slice(mapping.basePath.length).replace(/^\/+/, "");
176
+ if (!addressesDocument(rest, mapping.extensions)) continue;
177
+ anyMatched = true;
178
+ const prefix = mapping.root ? `${mapping.root}/` : "";
179
+ const stem = rest === "" ? mapping.root : `${prefix}${rest}`;
180
+ const candidates = targetCandidates(
181
+ stem,
182
+ isDirectory || rest === "",
183
+ mapping.extensions,
184
+ mapping.indexFiles
185
+ );
186
+ for (const candidate of candidates) {
187
+ const resolved = index.resolve(candidate);
188
+ if (resolved) return resolved;
189
+ }
190
+ }
191
+ return anyMatched ? "broken" : null;
192
+ }
193
+ var indexCache = /* @__PURE__ */ new WeakMap();
194
+ function pathIndexFor(allPaths) {
195
+ let index = indexCache.get(allPaths);
196
+ if (!index) {
197
+ index = new PathIndex(allPaths);
198
+ indexCache.set(allPaths, index);
199
+ }
200
+ return index;
201
+ }
202
+ function classifyLink(docPath, rawTarget, allPaths, routes) {
203
+ const raw = rawTarget;
204
+ if (hasScheme(raw)) {
205
+ try {
206
+ return { raw, kind: "external", url: new URL(raw).href };
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ const hashAt = raw.indexOf("#");
212
+ const pathPart = hashAt === -1 ? raw : raw.slice(0, hashAt);
213
+ const anchor = hashAt === -1 ? void 0 : raw.slice(hashAt + 1);
214
+ if (pathPart === "") return null;
215
+ if (pathPart.startsWith("/")) {
216
+ const resolved2 = resolveRoute(pathPart, routes, pathIndexFor(allPaths));
217
+ if (resolved2 === null) return null;
218
+ if (resolved2 === "broken") return { raw, kind: "broken" };
219
+ const link = { raw, kind: "internal", resolvedPath: resolved2 };
220
+ if (anchor) link.anchor = anchor;
221
+ return link;
222
+ }
223
+ const resolved = resolveRelative(docPath, safeDecode(pathPart));
224
+ if (resolved !== null) {
225
+ const index = pathIndexFor(allPaths);
226
+ let candidates;
227
+ if (allPaths.has(resolved)) {
228
+ candidates = [resolved];
229
+ } else if (HAS_EXTENSION.test(resolved)) {
230
+ if (!addressesDocument(resolved, DEFAULT_LINK_EXTENSIONS)) return null;
231
+ candidates = [];
232
+ } else {
233
+ candidates = targetCandidates(
234
+ resolved,
235
+ pathPart.endsWith("/"),
236
+ DEFAULT_LINK_EXTENSIONS,
237
+ DEFAULT_INDEX_FILES
238
+ );
239
+ }
240
+ for (const candidate of candidates) {
241
+ const hit = index.resolve(candidate);
242
+ if (hit) {
243
+ const link = { raw, kind: "internal", resolvedPath: hit };
244
+ if (anchor) link.anchor = anchor;
245
+ return link;
246
+ }
247
+ }
248
+ }
249
+ return { raw, kind: "broken" };
250
+ }
251
+ function classifyImage(docPath, rawTarget) {
252
+ if (hasScheme(rawTarget)) {
253
+ return { raw: rawTarget, target: rawTarget, external: true };
254
+ }
255
+ const resolved = resolveRelative(docPath, rawTarget);
256
+ return { raw: rawTarget, target: resolved ?? rawTarget, external: false };
257
+ }
258
+ function analyzeDoc(content, relPath, allPaths, options = {}) {
259
+ const routes = options.routes ?? [];
260
+ const path = normalizeDocPath(relPath);
261
+ let meta;
262
+ try {
263
+ meta = extractFrontmatter(content, "markdown");
264
+ } catch (error) {
265
+ throw new GraphError(`${path}: ${errorMessage(error)}`);
266
+ }
267
+ const format = formatOf(path, options.format);
268
+ const body = (options.parse ?? parseBody)(content, path, format);
269
+ const links = [];
270
+ for (const target of body.linkTargets) {
271
+ const link = classifyLink(path, target, allPaths, routes);
272
+ if (link) links.push(link);
273
+ }
274
+ const routeLanguage = routeLanguageFor(path, routes);
275
+ return {
276
+ path,
277
+ frontmatter: meta.data,
278
+ frontmatterPresent: meta.present,
279
+ format,
280
+ firstH1: body.firstH1,
281
+ sections: body.sections,
282
+ links,
283
+ images: body.imageTargets.map((target) => classifyImage(path, target)),
284
+ codeLanguages: body.codeLanguages,
285
+ // Omitted rather than set to undefined, so a doc under no localized route
286
+ // carries no key at all — `routeLanguage` in JSON output means something.
287
+ ...routeLanguage === void 0 ? {} : { routeLanguage },
288
+ // Over the content as read — line endings included, so the digest is
289
+ // byte-faithful and equals `sha256sum <file>` for any valid-UTF-8 file
290
+ // (ADR 01036). The CRLF corpus fixture depends on this not normalizing.
291
+ contentHash: createHash("sha256").update(content, "utf8").digest("hex")
292
+ };
293
+ }
294
+ function parseBody(content, path, format) {
295
+ const isMdx = format === "mdx";
296
+ let tree;
297
+ try {
298
+ tree = (isMdx ? mdxProcessor : processor).parse(content);
299
+ } catch (error) {
300
+ if (!isMdx) throw error;
301
+ const reason = errorMessage(error);
302
+ throw new GraphError(`Could not parse MDX in ${path}: ${reason}`);
303
+ }
304
+ const sections = [];
305
+ const linkTargets = [];
306
+ const imageTargets = [];
307
+ const codeLanguages = /* @__PURE__ */ new Set();
308
+ const definitions = /* @__PURE__ */ new Map();
309
+ let firstH1;
310
+ visit(tree, (node) => {
311
+ if (node.type === "definition") {
312
+ const def = node;
313
+ definitions.set(def.identifier, def);
314
+ }
315
+ });
316
+ const slugger = new GithubSlugger();
317
+ const stack = [];
318
+ const childCount = /* @__PURE__ */ new Map();
319
+ visit(tree, (node) => {
320
+ switch (node.type) {
321
+ case "heading": {
322
+ const level = node.depth;
323
+ const title = mdastToString(node);
324
+ const slug = slugger.slug(title);
325
+ if (level === 1 && firstH1 === void 0) firstH1 = title;
326
+ for (let top = stack.at(-1); top !== void 0 && top.level >= level; top = stack.at(-1)) {
327
+ stack.pop();
328
+ }
329
+ const parentSlug = stack.at(-1)?.slug ?? null;
330
+ const parentKey = parentSlug ?? "";
331
+ const order = (childCount.get(parentKey) ?? 0) + 1;
332
+ childCount.set(parentKey, order);
333
+ sections.push({ slug, title, level, order, parentSlug });
334
+ stack.push({ level, slug });
335
+ break;
336
+ }
337
+ case "link": {
338
+ linkTargets.push(node.url);
339
+ break;
340
+ }
341
+ case "linkReference": {
342
+ const def = definitions.get(
343
+ node.identifier
344
+ );
345
+ if (def) linkTargets.push(def.url);
346
+ break;
347
+ }
348
+ case "image": {
349
+ imageTargets.push(node.url);
350
+ break;
351
+ }
352
+ case "imageReference": {
353
+ const def = definitions.get(
354
+ node.identifier
355
+ );
356
+ if (def) imageTargets.push(def.url);
357
+ break;
358
+ }
359
+ case "code": {
360
+ const lang = node.lang;
361
+ if (lang) codeLanguages.add(lang);
362
+ break;
363
+ }
364
+ // A JSX element's `href` is a link and its `src` is an image, on any
365
+ // element (ADR 01022). Those are HTML's own names for the relationships,
366
+ // so this stays structural — dockg never learns what `<LinkCard>` means.
367
+ case "mdxJsxFlowElement":
368
+ case "mdxJsxTextElement": {
369
+ const href = jsxAttributeValue(node, "href");
370
+ if (href !== void 0) linkTargets.push(href);
371
+ const name = node.name ?? "";
372
+ if (IMAGE_ELEMENTS.has(name.toLowerCase())) {
373
+ const src = jsxAttributeValue(node, "src");
374
+ if (src !== void 0) imageTargets.push(src);
375
+ }
376
+ break;
377
+ }
378
+ }
379
+ });
380
+ return {
381
+ firstH1,
382
+ sections,
383
+ linkTargets,
384
+ imageTargets,
385
+ codeLanguages: [...codeLanguages].sort()
386
+ };
387
+ }
388
+ function visit(node, fn) {
389
+ const children = node.children;
390
+ if (!children) return;
391
+ for (const child of children) {
392
+ fn(child);
393
+ visit(child, fn);
394
+ }
395
+ }
396
+
397
+ // src/graph/core/analyze-cache.ts
398
+ import { createHash as createHash2 } from "node:crypto";
399
+ import { mkdir } from "node:fs/promises";
400
+ import { readFileSync } from "node:fs";
401
+ import { join } from "node:path";
402
+ var ANALYZE_CACHE_DIR = ".manni/graph/analyze-cache";
403
+ var ENTRY_VERSION = 1;
404
+ function sha256(text) {
405
+ return createHash2("sha256").update(text, "utf8").digest("hex");
406
+ }
407
+ var isStrings = (value) => Array.isArray(value) && value.every((v) => typeof v === "string");
408
+ function isSection(value) {
409
+ if (typeof value !== "object" || value === null) return false;
410
+ const s = value;
411
+ return typeof s.slug === "string" && typeof s.title === "string" && typeof s.level === "number" && typeof s.order === "number" && (s.parentSlug === null || typeof s.parentSlug === "string");
412
+ }
413
+ function isDocBody(value) {
414
+ if (typeof value !== "object" || value === null) return false;
415
+ const b = value;
416
+ return (b.firstH1 === void 0 || typeof b.firstH1 === "string") && Array.isArray(b.sections) && b.sections.every(isSection) && isStrings(b.linkTargets) && isStrings(b.imageTargets) && isStrings(b.codeLanguages);
417
+ }
418
+ var AnalyzeCache = class {
419
+ constructor(dir, version = package_default.version) {
420
+ this.dir = dir;
421
+ this.version = version;
422
+ }
423
+ dir;
424
+ version;
425
+ /** Pages this run served from the cache. */
426
+ hits = 0;
427
+ /** Pages this run parsed. */
428
+ misses = 0;
429
+ writes = [];
430
+ /** `parseBody`, served from the entry for `path` when its key matches. */
431
+ parse = (content, path, format) => {
432
+ const key = sha256(JSON.stringify([ENTRY_VERSION, this.version, format, path, content]));
433
+ const file = join(this.dir, `${sha256(path)}.json`);
434
+ const cached = readEntry(file, key);
435
+ if (cached !== void 0) {
436
+ this.hits++;
437
+ return cached;
438
+ }
439
+ const body = parseBody(content, path, format);
440
+ this.misses++;
441
+ this.writes.push(this.write(file, JSON.stringify({ key, body })));
442
+ return body;
443
+ };
444
+ /** Wait for this run's writes. Never rejects. */
445
+ async flush() {
446
+ await Promise.all(this.writes);
447
+ }
448
+ async write(file, text) {
449
+ try {
450
+ await mkdir(this.dir, { recursive: true });
451
+ await writeTextAtomic(file, text);
452
+ } catch {
453
+ }
454
+ }
455
+ };
456
+ function readEntry(file, key) {
457
+ try {
458
+ const entry = JSON.parse(readFileSync(file, "utf8"));
459
+ if (typeof entry !== "object" || entry === null) return void 0;
460
+ const { key: stored, body } = entry;
461
+ return stored === key && isDocBody(body) ? body : void 0;
462
+ } catch {
463
+ return void 0;
464
+ }
465
+ }
466
+
467
+ // src/graph/core/localizations.ts
468
+ var LOCALIZATIONS_FILENAME = "localizations.json";
469
+ var UNDETERMINED = "und";
470
+ var LANGUAGE_TAG = /^[A-Za-z]{2,3}(-[A-Za-z]{4})?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*$/;
471
+ function isLanguageTag(value) {
472
+ return LANGUAGE_TAG.test(value);
473
+ }
474
+ function canonicalLanguageTag(tag) {
475
+ try {
476
+ return Intl.getCanonicalLocales(tag)[0] ?? tag;
477
+ } catch {
478
+ return tag;
479
+ }
480
+ }
481
+ function searchIndexFilename(language) {
482
+ return `search.${language}.json`;
483
+ }
484
+ function vectorIndexFilename(language) {
485
+ return `vectors.${language}.bin`;
486
+ }
487
+ function emitLocalizations(doc) {
488
+ const languages = [...doc.languages].sort(
489
+ (a, b) => byCodeUnit(a.language, b.language)
490
+ );
491
+ return `${JSON.stringify({ version: doc.version, languages }, null, 2)}
492
+ `;
493
+ }
494
+ function isRecord(v) {
495
+ return typeof v === "object" && v !== null && !Array.isArray(v);
496
+ }
497
+ function parseLocalizations(text) {
498
+ let parsed;
499
+ try {
500
+ parsed = JSON.parse(text);
501
+ } catch {
502
+ return void 0;
503
+ }
504
+ if (!isRecord(parsed)) return void 0;
505
+ if (parsed["version"] !== 1 || !Array.isArray(parsed["languages"])) {
506
+ return void 0;
507
+ }
508
+ for (const entry of parsed["languages"]) {
509
+ if (!isRecord(entry)) return void 0;
510
+ const language = entry["language"];
511
+ const search = entry["search"];
512
+ if (typeof language !== "string" || !isLanguageTag(language) || typeof entry["documents"] !== "number" || !isRecord(search) || typeof search["path"] !== "string" || typeof search["entries"] !== "number" || typeof search["digest"] !== "string" || !ownsFilename(search["path"], searchIndexFilename(language))) {
513
+ return void 0;
514
+ }
515
+ const v = entry["vectors"];
516
+ if (v !== void 0) {
517
+ if (!isRecord(v) || typeof v["path"] !== "string" || typeof v["model"] !== "string" || typeof v["dtype"] !== "string" || typeof v["dims"] !== "number" || typeof v["count"] !== "number" || // Same constraint `search.path` gets: the last segment must be the
518
+ // filename this language's sidecar would be given. Without it,
519
+ // `isSafeRelativePath` alone leaves `vectors.path` far more reachable
520
+ // than `search.path` — and `search --mode vector` opens it.
521
+ !ownsFilename(v["path"], vectorIndexFilename(language))) {
522
+ return void 0;
523
+ }
524
+ }
525
+ }
526
+ return parsed;
527
+ }
528
+ function isSafeRelativePath(path) {
529
+ if (path === "" || path.startsWith("/") || /^[A-Za-z]:/.test(path)) {
530
+ return false;
531
+ }
532
+ if (path.includes("\\")) return false;
533
+ const segments = path.split("/");
534
+ let leading = true;
535
+ for (const segment of segments) {
536
+ if (segment === "" || segment === ".") return false;
537
+ if (segment === "..") {
538
+ if (!leading) return false;
539
+ continue;
540
+ }
541
+ leading = false;
542
+ }
543
+ return true;
544
+ }
545
+ function ownsFilename(path, expected) {
546
+ if (!isSafeRelativePath(path)) return false;
547
+ return path.slice(path.lastIndexOf("/") + 1) === expected;
548
+ }
549
+
550
+ // src/graph/core/derive.ts
551
+ function resolveProvDocPath(docPath, raw, docByPath) {
552
+ const docRelative = resolveRelative(docPath, raw);
553
+ if (docRelative !== null && docByPath.has(docRelative)) return docRelative;
554
+ const repoRelative = normalizeDocPath(raw);
555
+ return docByPath.has(repoRelative) ? repoRelative : null;
556
+ }
557
+ var iri = (value) => ({ kind: "iri", value });
558
+ var lit = (value) => ({ kind: "literal", value });
559
+ var typedLit = (value, datatype) => ({
560
+ kind: "literal",
561
+ value,
562
+ datatype
563
+ });
564
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
565
+ var DATETIME_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/;
566
+ function dateTerm(value) {
567
+ if (DATE_RE.test(value)) return typedLit(value, `${NS.xsd}date`);
568
+ if (DATETIME_RE.test(value)) return typedLit(value, `${NS.xsd}dateTime`);
569
+ return lit(value);
570
+ }
571
+ function asString(v) {
572
+ if (typeof v === "string" && v.length > 0) return v;
573
+ if (typeof v === "number") return String(v);
574
+ if (v instanceof Date) {
575
+ return Number.isNaN(v.getTime()) ? void 0 : v.toISOString();
576
+ }
577
+ return void 0;
578
+ }
579
+ function asStringArray(v) {
580
+ if (typeof v === "string") return v.length > 0 ? [v] : [];
581
+ if (Array.isArray(v)) {
582
+ return v.flatMap((item) => {
583
+ const s = asString(item);
584
+ return s === void 0 ? [] : [s];
585
+ });
586
+ }
587
+ return [];
588
+ }
589
+ function asPersonNames(v) {
590
+ const one = (item) => {
591
+ const name = item != null && typeof item === "object" && !Array.isArray(item) ? asString(asRecord(item).name) : asString(item);
592
+ return name === void 0 ? [] : [name];
593
+ };
594
+ return Array.isArray(v) ? v.flatMap(one) : one(v);
595
+ }
596
+ function asRecord(v) {
597
+ return v != null && typeof v === "object" && !Array.isArray(v) ? v : {};
598
+ }
599
+ function fmValue(fm, keys) {
600
+ for (const key of keys) {
601
+ if (fm[key] !== void 0 && fm[key] !== null) return fm[key];
602
+ }
603
+ return void 0;
604
+ }
605
+ var AGENT_KIND = {
606
+ Person: "person",
607
+ Organization: "org",
608
+ SoftwareAgent: "software"
609
+ };
610
+ function graphBlock(fm) {
611
+ const block = fm["graph"];
612
+ return block && typeof block === "object" && !Array.isArray(block) ? block : void 0;
613
+ }
614
+ function resolveGraph(block, fm) {
615
+ const k = { ...block ?? {} };
616
+ const fallback = (key, pageKeys) => {
617
+ if (k[key] !== void 0 && k[key] !== null) return;
618
+ const pageValue = fmValue(fm, pageKeys);
619
+ if (pageValue !== void 0) k[key] = pageValue;
620
+ };
621
+ fallback("concepts", ["concepts"]);
622
+ fallback("applies-to", ["applies-to"]);
623
+ fallback("not-applicable-to", ["not-applicable-to"]);
624
+ fallback("revision-of", ["supersedes"]);
625
+ if (k["type"] === void 0 || k["type"] === null) {
626
+ const pageType = asString(fmValue(fm, ["type"]));
627
+ const derived = pageType && Object.hasOwn(PAGE_TYPE_TO_TOPIC_TYPE, pageType) ? PAGE_TYPE_TO_TOPIC_TYPE[pageType] : void 0;
628
+ if (derived) k["type"] = derived;
629
+ }
630
+ return k;
631
+ }
632
+ function recordEntries(value) {
633
+ if (!Array.isArray(value)) return [];
634
+ return value.filter(
635
+ (e) => !!e && typeof e === "object" && !Array.isArray(e)
636
+ );
637
+ }
638
+ function pointerSegments(pointer) {
639
+ return pointer.split("/").slice(1).map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
640
+ }
641
+ function deriveGraph(docs, options) {
642
+ const { baseIri } = options;
643
+ const sources = new Set(options.derive);
644
+ const quads = [];
645
+ const add = (s, p, o) => quads.push({ s, p, o });
646
+ const mintedConcepts = /* @__PURE__ */ new Set();
647
+ const concept = (label) => {
648
+ const c = mintConceptIri(baseIri, label);
649
+ add(c, RDF_TYPE, iri(`${NS.skos}Concept`));
650
+ add(c, `${NS.skos}prefLabel`, lit(label));
651
+ add(c, `${NS.skos}inScheme`, iri(mintSchemeIri(baseIri)));
652
+ mintedConcepts.add(c);
653
+ return c;
654
+ };
655
+ const variantNode = (label) => {
656
+ const variant = mintProductIri(baseIri, label);
657
+ add(variant, RDF_TYPE, iri(IIRDS_PRODUCT_VARIANT));
658
+ add(variant, `${NS.dcterms}title`, lit(label));
659
+ return variant;
660
+ };
661
+ const emitIirdsTyping = (subjectIri, k) => {
662
+ const topicType = asString(k["type"]);
663
+ const topicTypeIri = topicType && TOPIC_TYPE_IRIS[topicType];
664
+ if (topicTypeIri) add(subjectIri, IIRDS_HAS_TOPIC_TYPE, iri(topicTypeIri));
665
+ for (const label of asStringArray(k["applies-to"])) {
666
+ add(
667
+ subjectIri,
668
+ IIRDS_RELATES_TO_PRODUCT_VARIANT,
669
+ iri(variantNode(label))
670
+ );
671
+ }
672
+ for (const label of asStringArray(k["not-applicable-to"])) {
673
+ add(subjectIri, GRAPH_NOT_APPLICABLE_TO_VARIANT, iri(variantNode(label)));
674
+ }
675
+ for (const value of asStringArray(k["about-product-lifecycle"])) {
676
+ const phase = SOFTWARE_LIFECYCLE_IRIS[value];
677
+ if (phase) add(subjectIri, IIRDS_RELATES_TO_LIFECYCLE_PHASE, iri(phase));
678
+ }
679
+ for (const value of asStringArray(k["about-product-aspect"])) {
680
+ const subject = SOFTWARE_SUBJECT_IRIS[value];
681
+ if (subject) add(subjectIri, IIRDS_HAS_SUBJECT, iri(subject));
682
+ }
683
+ for (const value of asStringArray(k["not-about-product-aspect"])) {
684
+ const subject = SOFTWARE_SUBJECT_IRIS[value];
685
+ if (subject) add(subjectIri, GRAPH_NOT_SOFTWARE_SUBJECT, iri(subject));
686
+ }
687
+ };
688
+ const docByPath = new Map(docs.map((d) => [normalizeDocPath(d.path), d]));
689
+ const prov = sources.has("provenance");
690
+ const agentNode = (name, type) => {
691
+ const a = mintAgentIri(baseIri, AGENT_KIND[type], name);
692
+ add(a, RDF_TYPE, iri(`${NS.prov}${type}`));
693
+ add(a, `${NS.foaf}name`, lit(name));
694
+ return a;
695
+ };
696
+ const qualified = options.qualified === true;
697
+ const qualifyAttribution = (docIri, agentIri, name) => {
698
+ if (!qualified) return;
699
+ const node = `${docIri}#prov.attribution.${conceptSlug(name)}`;
700
+ add(docIri, `${NS.prov}qualifiedAttribution`, iri(node));
701
+ add(node, RDF_TYPE, iri(`${NS.prov}Attribution`));
702
+ add(node, `${NS.prov}agent`, iri(agentIri));
703
+ add(node, `${NS.prov}hadRole`, iri(ROLE.author));
704
+ };
705
+ const qualifyAssociation = (activityIri, agentIri, role) => {
706
+ if (!qualified) return;
707
+ const agentSlug = agentIri.slice(agentIri.lastIndexOf("/") + 1);
708
+ const node = `${activityIri}.assoc.${agentSlug}`;
709
+ add(activityIri, `${NS.prov}qualifiedAssociation`, iri(node));
710
+ add(node, RDF_TYPE, iri(`${NS.prov}Association`));
711
+ add(node, `${NS.prov}agent`, iri(agentIri));
712
+ add(node, `${NS.prov}hadRole`, iri(role));
713
+ };
714
+ const attributeAuthor = (docIri, name) => {
715
+ const a = agentNode(name, "Person");
716
+ add(docIri, `${NS.dcterms}creator`, iri(a));
717
+ add(docIri, `${NS.prov}wasAttributedTo`, iri(a));
718
+ qualifyAttribution(docIri, a, name);
719
+ };
720
+ const provTargetEdge = (doc, docIri, raw, predicate, inverse) => {
721
+ if (hasScheme(raw)) {
722
+ add(docIri, predicate, iri(raw));
723
+ return;
724
+ }
725
+ const target = resolveProvDocPath(
726
+ normalizeDocPath(doc.path),
727
+ raw,
728
+ docByPath
729
+ );
730
+ if (target) {
731
+ const targetIri = mintDocIri(baseIri, target);
732
+ add(docIri, predicate, iri(targetIri));
733
+ if (inverse) add(targetIri, inverse, iri(docIri));
734
+ } else {
735
+ add(docIri, `${NS.graph}brokenLink`, lit(raw));
736
+ }
737
+ };
738
+ for (const doc of docs) {
739
+ const docIri = mintDocIri(baseIri, doc.path);
740
+ const fm = doc.frontmatter;
741
+ const block = graphBlock(fm);
742
+ const harvested = resolveGraph(block, fm);
743
+ let createdEmitted = false;
744
+ let modifiedEmitted = false;
745
+ add(docIri, RDF_TYPE, iri(`${NS.graph}Document`));
746
+ if (prov) add(docIri, RDF_TYPE, iri(`${NS.prov}Entity`));
747
+ add(docIri, `${NS.graph}path`, lit(normalizeDocPath(doc.path)));
748
+ add(docIri, `${NS.graph}contentHash`, lit(doc.contentHash));
749
+ if (sources.has("frontmatter")) {
750
+ const title = asString(fmValue(fm, ["title"])) ?? doc.firstH1;
751
+ if (title) add(docIri, `${NS.dcterms}title`, lit(title));
752
+ const description = asString(fmValue(fm, ["description"]));
753
+ if (description)
754
+ add(docIri, `${NS.dcterms}description`, lit(description));
755
+ for (const author of asPersonNames(fmValue(fm, ["author", "authors"]))) {
756
+ if (prov) {
757
+ attributeAuthor(docIri, author);
758
+ } else {
759
+ add(docIri, `${NS.dcterms}creator`, lit(author));
760
+ }
761
+ }
762
+ const created = asString(fmValue(fm, ["created", "date"]));
763
+ if (created) {
764
+ add(docIri, `${NS.dcterms}created`, dateTerm(created));
765
+ if (prov) add(docIri, `${NS.prov}generatedAtTime`, dateTerm(created));
766
+ createdEmitted = true;
767
+ }
768
+ const modified = asString(
769
+ fmValue(fm, ["last-updated", "updated", "lastmod", "modified"])
770
+ );
771
+ if (modified) {
772
+ add(docIri, `${NS.dcterms}modified`, dateTerm(modified));
773
+ modifiedEmitted = true;
774
+ }
775
+ const language = asString(fmValue(fm, ["lang", "language"])) ?? doc.routeLanguage;
776
+ if (language) {
777
+ add(docIri, `${NS.dcterms}language`, lit(canonicalLanguageTag(language)));
778
+ }
779
+ for (const raw of asStringArray(fmValue(fm, ["translation-of"]))) {
780
+ provTargetEdge(
781
+ doc,
782
+ docIri,
783
+ raw,
784
+ `${NS.schema}translationOfWork`,
785
+ `${NS.schema}workTranslation`
786
+ );
787
+ }
788
+ if (block) {
789
+ const label = asString(block["label"]);
790
+ if (label) {
791
+ const topic = concept(label);
792
+ add(docIri, `${NS.foaf}primaryTopic`, iri(topic));
793
+ for (const alt of asStringArray(block["alt-labels"])) {
794
+ add(topic, `${NS.skos}altLabel`, lit(alt));
795
+ }
796
+ for (const [key, predicate] of [
797
+ ["broader", "broader"],
798
+ ["narrower", "narrower"],
799
+ ["related-concepts", "related"]
800
+ ]) {
801
+ for (const value of asStringArray(block[key])) {
802
+ add(topic, `${NS.skos}${predicate}`, iri(concept(value)));
803
+ }
804
+ }
805
+ }
806
+ }
807
+ emitIirdsTyping(docIri, harvested);
808
+ }
809
+ if (sources.has("tags")) {
810
+ const labels = [
811
+ ...asStringArray(fmValue(fm, ["tags", "keywords"])),
812
+ ...asStringArray(harvested["concepts"])
813
+ ];
814
+ for (const label of labels) {
815
+ add(docIri, `${NS.dcterms}subject`, iri(concept(label)));
816
+ }
817
+ }
818
+ if (sources.has("sections")) {
819
+ const sectionMeta = asRecord(block?.["sections"]);
820
+ const sectionSlugs = new Set(doc.sections.map((s) => s.slug));
821
+ for (const section of doc.sections) {
822
+ const secIri = mintSectionIri(docIri, section.slug);
823
+ const parentIri = section.parentSlug ? mintSectionIri(docIri, section.parentSlug) : docIri;
824
+ add(secIri, RDF_TYPE, iri(`${NS.graph}Section`));
825
+ add(secIri, `${NS.dcterms}title`, lit(section.title));
826
+ add(
827
+ secIri,
828
+ `${NS.graph}level`,
829
+ typedLit(String(section.level), `${NS.xsd}integer`)
830
+ );
831
+ add(
832
+ secIri,
833
+ `${NS.graph}order`,
834
+ typedLit(String(section.order), `${NS.xsd}integer`)
835
+ );
836
+ add(parentIri, `${NS.dcterms}hasPart`, iri(secIri));
837
+ const meta = asRecord(sectionMeta[section.slug]);
838
+ emitIirdsTyping(secIri, meta);
839
+ for (const label of asStringArray(meta["concepts"])) {
840
+ add(secIri, `${NS.dcterms}subject`, iri(concept(label)));
841
+ }
842
+ }
843
+ for (const slug of Object.keys(sectionMeta)) {
844
+ if (!sectionSlugs.has(slug)) {
845
+ add(docIri, `${NS.graph}brokenSectionRef`, lit(slug));
846
+ }
847
+ }
848
+ }
849
+ if (sources.has("links")) {
850
+ for (const link of doc.links) {
851
+ if (link.kind === "external" && link.url) {
852
+ add(docIri, `${NS.dcterms}references`, iri(link.url));
853
+ } else if (link.kind === "internal" && link.resolvedPath) {
854
+ const targetIri = mintDocIri(baseIri, link.resolvedPath);
855
+ const target = docByPath.get(link.resolvedPath);
856
+ const anchor = link.anchor;
857
+ const anchorResolves = anchor !== void 0 && target !== void 0 && target.sections.some((s) => s.slug === anchor);
858
+ add(
859
+ docIri,
860
+ `${NS.dcterms}references`,
861
+ iri(
862
+ anchorResolves ? mintSectionIri(targetIri, anchor) : targetIri
863
+ )
864
+ );
865
+ } else if (link.kind === "broken") {
866
+ add(docIri, `${NS.graph}brokenLink`, lit(link.raw));
867
+ }
868
+ }
869
+ }
870
+ if (sources.has("images")) {
871
+ for (const image of doc.images) {
872
+ const target = image.external ? image.target : `${baseIri}file/${normalizeDocPath(image.target).split("/").map(encodeSegment).join("/")}`;
873
+ add(docIri, `${NS.schema}image`, iri(target));
874
+ }
875
+ }
876
+ if (sources.has("code")) {
877
+ for (const language of doc.codeLanguages) {
878
+ add(docIri, `${NS.graph}codeLanguage`, lit(language));
879
+ }
880
+ }
881
+ if (prov) {
882
+ for (const raw of block ? asStringArray(block["derived-from"]) : []) {
883
+ provTargetEdge(doc, docIri, raw, `${NS.prov}wasDerivedFrom`);
884
+ }
885
+ for (const raw of asStringArray(harvested["revision-of"])) {
886
+ provTargetEdge(doc, docIri, raw, `${NS.prov}wasRevisionOf`);
887
+ }
888
+ const gitFile = options.gitHistory?.files.get(normalizeDocPath(doc.path));
889
+ if (gitFile) {
890
+ if (!createdEmitted && gitFile.created) {
891
+ add(docIri, `${NS.dcterms}created`, dateTerm(gitFile.created));
892
+ add(docIri, `${NS.prov}generatedAtTime`, dateTerm(gitFile.created));
893
+ }
894
+ if (!modifiedEmitted && gitFile.modified) {
895
+ add(docIri, `${NS.dcterms}modified`, dateTerm(gitFile.modified));
896
+ }
897
+ for (const author of gitFile.authors) {
898
+ attributeAuthor(docIri, author);
899
+ }
900
+ for (const oldPath of gitFile.renamedFrom) {
901
+ const oldIri = mintDocIri(baseIri, oldPath);
902
+ add(docIri, `${NS.prov}wasRevisionOf`, iri(oldIri));
903
+ add(oldIri, RDF_TYPE, iri(`${NS.prov}Entity`));
904
+ }
905
+ }
906
+ const machines = [
907
+ ...new Set(
908
+ recordEntries(fmValue(fm, ["provenance"])).flatMap((e) => {
909
+ const m = asString(e["generated-by"]);
910
+ return m === void 0 ? [] : [m];
911
+ })
912
+ )
913
+ ].sort(byCodeUnit);
914
+ if (machines.length > 0) {
915
+ const activity = `${docIri}#prov.generation`;
916
+ add(docIri, `${NS.prov}wasGeneratedBy`, iri(activity));
917
+ add(activity, RDF_TYPE, iri(`${NS.prov}Activity`));
918
+ for (const machine of machines) {
919
+ const model = agentNode(machine, "SoftwareAgent");
920
+ add(activity, `${NS.prov}wasAssociatedWith`, iri(model));
921
+ qualifyAssociation(activity, model, ROLE.generator);
922
+ }
923
+ }
924
+ for (const entry of recordEntries(fmValue(fm, ["meta-provenance"]))) {
925
+ const model = asString(entry["generated-by"]);
926
+ if (!model) continue;
927
+ const pointers = asStringArray(entry["fields"]);
928
+ const confidence = asRecord(entry["confidence"]);
929
+ const filled = /* @__PURE__ */ new Map();
930
+ for (const pointer of pointers) {
931
+ if (!pointer.startsWith("/graph/")) continue;
932
+ const field = pointerSegments(pointer)[1];
933
+ if (field === void 0 || field === "") continue;
934
+ if (!filled.has(field)) filled.set(field, pointer);
935
+ }
936
+ if (filled.size === 0) continue;
937
+ const activity = `${docIri}#prov.graph-fill.${conceptSlug(model)}`;
938
+ const modelAgent = agentNode(model, "SoftwareAgent");
939
+ add(activity, RDF_TYPE, iri(`${NS.prov}Activity`));
940
+ add(activity, `${NS.prov}wasAssociatedWith`, iri(modelAgent));
941
+ qualifyAssociation(activity, modelAgent, ROLE.generator);
942
+ for (const [field, pointer] of filled) {
943
+ const fieldNode = `${activity}.field.${field}`;
944
+ add(activity, `${NS.graph}filledFieldEntry`, iri(fieldNode));
945
+ add(fieldNode, `${NS.graph}filledField`, lit(field));
946
+ const c = confidence[pointer];
947
+ if (typeof c === "number") {
948
+ add(
949
+ fieldNode,
950
+ `${NS.graph}confidence`,
951
+ typedLit(String(Math.round(c * 100) / 100), `${NS.xsd}decimal`)
952
+ );
953
+ }
954
+ }
955
+ const label = block ? asString(block["label"]) : void 0;
956
+ if (label && filled.has("label")) {
957
+ add(
958
+ activity,
959
+ `${NS.prov}generated`,
960
+ iri(mintConceptIri(baseIri, label))
961
+ );
962
+ }
963
+ }
964
+ }
965
+ }
966
+ if (prov) {
967
+ const graphIri = mintGraphIri(baseIri);
968
+ const activity = mintBuildActivityIri(baseIri);
969
+ const tool = agentNode("manni", "SoftwareAgent");
970
+ add(graphIri, RDF_TYPE, iri(`${NS.prov}Entity`));
971
+ add(graphIri, `${NS.prov}wasGeneratedBy`, iri(activity));
972
+ add(activity, RDF_TYPE, iri(`${NS.prov}Activity`));
973
+ add(activity, `${NS.prov}wasAssociatedWith`, iri(tool));
974
+ qualifyAssociation(activity, tool, ROLE.tool);
975
+ if (options.toolVersion) {
976
+ add(tool, `${NS.graph}version`, lit(options.toolVersion));
977
+ }
978
+ for (const doc of docs) {
979
+ add(activity, `${NS.prov}used`, iri(mintDocIri(baseIri, doc.path)));
980
+ }
981
+ const headTime = options.gitHistory?.headTime;
982
+ if (headTime) {
983
+ add(
984
+ activity,
985
+ `${NS.prov}endedAtTime`,
986
+ typedLit(headTime, `${NS.xsd}dateTime`)
987
+ );
988
+ }
989
+ }
990
+ if (mintedConcepts.size > 0) {
991
+ const scheme = mintSchemeIri(baseIri);
992
+ add(scheme, RDF_TYPE, iri(`${NS.skos}ConceptScheme`));
993
+ add(scheme, `${NS.dcterms}title`, lit("manni graph concepts"));
994
+ }
995
+ return dedupe(quads);
996
+ }
997
+ function quadKey(q) {
998
+ const o = q.o.kind === "iri" ? `i:${q.o.value}` : `l:${q.o.value}|${q.o.datatype ?? ""}`;
999
+ return `${q.s}|${q.p}|${o}`;
1000
+ }
1001
+ function dedupe(quads) {
1002
+ const seen = /* @__PURE__ */ new Set();
1003
+ const out = [];
1004
+ for (const q of quads) {
1005
+ const key = quadKey(q);
1006
+ if (seen.has(key)) continue;
1007
+ seen.add(key);
1008
+ out.push(q);
1009
+ }
1010
+ return out;
1011
+ }
1012
+
1013
+ // src/graph/core/discover.ts
1014
+ import { statSync } from "node:fs";
1015
+ import { extname, relative, resolve } from "node:path";
1016
+ import fg from "fast-glob";
1017
+ function discoverFiles(inputs, exclude, cwd) {
1018
+ const matches = fg.sync(inputs, {
1019
+ cwd,
1020
+ ignore: exclude,
1021
+ onlyFiles: true,
1022
+ dot: false,
1023
+ unique: true
1024
+ });
1025
+ return [...new Set(matches.map((m) => m.replace(/\\/g, "/")))].sort(
1026
+ byCodeUnit
1027
+ );
1028
+ }
1029
+ var FAMILY_EXCLUDE = ["**/node_modules/**", "**/.git/**"];
1030
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".mdx"]);
1031
+ var DEFAULT_EXTENSIONS = [".md", ".mdx", ".markdown"];
1032
+ var STDIN = "-";
1033
+ function assertInputFormat(paths, as) {
1034
+ if (as !== void 0 && !DOC_FORMATS.includes(as)) {
1035
+ throw new GraphError(
1036
+ `Unknown format "${as}". Known formats: ${DOC_FORMATS.join(", ")}.`
1037
+ );
1038
+ }
1039
+ if (paths.includes(STDIN) && as === void 0) {
1040
+ throw new GraphError(STDIN_REQUIRES_AS);
1041
+ }
1042
+ return as;
1043
+ }
1044
+ function expandInputs(inputs, base, ignore) {
1045
+ const named = [];
1046
+ const patterns = [];
1047
+ for (const input of inputs) {
1048
+ if (fg.isDynamicPattern(input)) {
1049
+ patterns.push(input.replace(/\\/g, "/"));
1050
+ continue;
1051
+ }
1052
+ let isDirectory;
1053
+ try {
1054
+ isDirectory = statSync(resolve(base, input)).isDirectory();
1055
+ } catch {
1056
+ patterns.push(input.replace(/\\/g, "/"));
1057
+ continue;
1058
+ }
1059
+ const literal = fg.convertPathToPattern(input);
1060
+ if (isDirectory) patterns.push(`${literal.replace(/\/$/, "")}/**/*`);
1061
+ else named.push(...glob([literal], base, ignore));
1062
+ }
1063
+ return { named, walked: glob(patterns, base, ignore) };
1064
+ }
1065
+ function glob(patterns, base, ignore) {
1066
+ if (patterns.length === 0) return [];
1067
+ return fg.sync(patterns, {
1068
+ cwd: base,
1069
+ ignore,
1070
+ absolute: true,
1071
+ dot: false,
1072
+ onlyFiles: true
1073
+ });
1074
+ }
1075
+ function normalizeExtensions(exts) {
1076
+ return new Set(
1077
+ exts.map((e) => {
1078
+ const lower = e.toLowerCase();
1079
+ return lower.startsWith(".") ? lower : `.${lower}`;
1080
+ })
1081
+ );
1082
+ }
1083
+ function resolveDocumentSet(config, options = {}, verb = "build", cwd = process.cwd(), base = documentBase(config, options, cwd)) {
1084
+ const paths = options.paths ?? [];
1085
+ const wanted = options.collection ?? [];
1086
+ assertCollectionWithoutPaths(wanted, paths);
1087
+ const format = assertInputFormat(paths, options.as);
1088
+ if (wanted.length > 0 && config.configSource === null) {
1089
+ throw new GraphError("--collection needs a config file to select from.");
1090
+ }
1091
+ const collections = selectCollections(
1092
+ config.collections,
1093
+ wanted,
1094
+ config.configSource ?? DEFAULT_CONFIG_FILENAME,
1095
+ (message) => new GraphError(message)
1096
+ );
1097
+ const exclude = [...FAMILY_EXCLUDE, ...options.exclude ?? []];
1098
+ let named = [];
1099
+ let walked;
1100
+ if (paths.length > 0) {
1101
+ ({ named, walked } = expandInputs(
1102
+ paths.filter((p) => p !== STDIN),
1103
+ cwd,
1104
+ exclude
1105
+ ));
1106
+ } else {
1107
+ const patterns = collections.flatMap((c) => c.paths);
1108
+ if (patterns.length === 0) {
1109
+ throw new GraphError(
1110
+ `No files to ${verb}. Pass paths/globs, or declare a collection under \`collections:\` in manni.config.yaml.`
1111
+ );
1112
+ }
1113
+ walked = collections.flatMap((c) => {
1114
+ const found = expandInputs(c.paths, config.configDir, [
1115
+ ...exclude,
1116
+ ...c.exclude
1117
+ ]);
1118
+ return [...found.named, ...found.walked];
1119
+ });
1120
+ }
1121
+ const exts = normalizeExtensions(options.ext ?? DEFAULT_EXTENSIONS);
1122
+ const keepNamed = (p) => format !== void 0 || SUPPORTED_EXTENSIONS.has(extname(p).toLowerCase());
1123
+ const keepWalked = (p) => exts.has(extname(p).toLowerCase());
1124
+ return [
1125
+ ...new Set(
1126
+ [...named.filter(keepNamed), ...walked.filter(keepWalked)].map(
1127
+ (p) => relative(base, p).replace(/\\/g, "/")
1128
+ )
1129
+ )
1130
+ ].sort(byCodeUnit);
1131
+ }
1132
+ function documentBase(config, options = {}, cwd = process.cwd()) {
1133
+ const typed = (options.paths ?? []).length > 0;
1134
+ return typed || config.collections.length === 0 ? cwd : config.configDir;
1135
+ }
1136
+ function routesFrom(config, base) {
1137
+ return config.routes.map((m) => ({
1138
+ ...m,
1139
+ root: relative(base, resolve(config.configDir, m.root)).replace(/\\/g, "/")
1140
+ }));
1141
+ }
1142
+ function documentSetPatterns(config, options = {}) {
1143
+ const paths = options.paths ?? [];
1144
+ if (paths.length > 0) return paths;
1145
+ return selectCollections(
1146
+ config.collections,
1147
+ options.collection ?? [],
1148
+ config.configSource ?? DEFAULT_CONFIG_FILENAME,
1149
+ (message) => new GraphError(message)
1150
+ ).flatMap((c) => c.paths);
1151
+ }
1152
+
1153
+ // src/graph/core/emit.ts
1154
+ var SAFE_LOCAL = /^[A-Za-z_][A-Za-z0-9_-]*$/;
1155
+ var ILLEGAL_IRI_CHARS = '<>"{}|^`\\';
1156
+ function sanitizeIri(iriValue) {
1157
+ let out = "";
1158
+ for (const ch of iriValue) {
1159
+ const code = ch.codePointAt(0);
1160
+ const illegal = code <= 32 || ILLEGAL_IRI_CHARS.includes(ch);
1161
+ out += illegal ? "%" + code.toString(16).toUpperCase().padStart(2, "0") : ch;
1162
+ }
1163
+ return out;
1164
+ }
1165
+ var shortenMemo = /* @__PURE__ */ new Map();
1166
+ function shorten(iriValue) {
1167
+ const memoized = shortenMemo.get(iriValue);
1168
+ if (memoized !== void 0) return memoized;
1169
+ let result;
1170
+ for (const [prefix, ns] of PREFIXES) {
1171
+ if (iriValue.startsWith(ns)) {
1172
+ const local = iriValue.slice(ns.length);
1173
+ if (SAFE_LOCAL.test(local)) {
1174
+ result = `${prefix}:${local}`;
1175
+ break;
1176
+ }
1177
+ }
1178
+ }
1179
+ result ??= `<${sanitizeIri(iriValue)}>`;
1180
+ shortenMemo.set(iriValue, result);
1181
+ return result;
1182
+ }
1183
+ function escapeLiteral(value) {
1184
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1185
+ }
1186
+ var INTEGER = `${NS.xsd}integer`;
1187
+ var INTEGER_RE = /^-?\d+$/;
1188
+ function renderTerm(term) {
1189
+ if (term.kind === "iri") return shorten(term.value);
1190
+ if (term.datatype === INTEGER && INTEGER_RE.test(term.value))
1191
+ return term.value;
1192
+ const quoted = `"${escapeLiteral(term.value)}"`;
1193
+ return term.datatype ? `${quoted}^^${shorten(term.datatype)}` : quoted;
1194
+ }
1195
+ function compareTerms(a, b) {
1196
+ if (a.kind !== b.kind) return a.kind === "iri" ? -1 : 1;
1197
+ const byValue = byCodeUnit(a.value, b.value);
1198
+ if (byValue !== 0) return byValue;
1199
+ const da = a.kind === "literal" ? a.datatype ?? "" : "";
1200
+ const db = b.kind === "literal" ? b.datatype ?? "" : "";
1201
+ return byCodeUnit(da, db);
1202
+ }
1203
+ function emitTurtle(quads) {
1204
+ shortenMemo.clear();
1205
+ const lines = [];
1206
+ for (const [prefix, ns] of PREFIXES) {
1207
+ lines.push(`@prefix ${prefix}: <${ns}> .`);
1208
+ }
1209
+ const subjects = /* @__PURE__ */ new Map();
1210
+ for (const quad of quads) {
1211
+ let preds = subjects.get(quad.s);
1212
+ if (!preds) {
1213
+ preds = /* @__PURE__ */ new Map();
1214
+ subjects.set(quad.s, preds);
1215
+ }
1216
+ let terms = preds.get(quad.p);
1217
+ if (!terms) {
1218
+ terms = [];
1219
+ preds.set(quad.p, terms);
1220
+ }
1221
+ terms.push(quad.o);
1222
+ }
1223
+ const sortedSubjects = [...subjects.entries()].sort(
1224
+ ([a], [b]) => byCodeUnit(a, b)
1225
+ );
1226
+ for (const [subject, preds] of sortedSubjects) {
1227
+ const sortedPreds = [...preds.entries()].sort(([a], [b]) => {
1228
+ if (a === RDF_TYPE) return b === RDF_TYPE ? 0 : -1;
1229
+ if (b === RDF_TYPE) return 1;
1230
+ return byCodeUnit(a, b);
1231
+ });
1232
+ const entries = sortedPreds.map(([p, terms], i) => {
1233
+ const objects = [...terms].sort(compareTerms).map(renderTerm).join(", ");
1234
+ const pred = p === RDF_TYPE ? "a" : shorten(p);
1235
+ const terminator = i === sortedPreds.length - 1 ? " ." : " ;";
1236
+ return { pred, objects, terminator };
1237
+ });
1238
+ const [first, ...rest] = entries;
1239
+ if (first === void 0) continue;
1240
+ lines.push("");
1241
+ lines.push(
1242
+ `${shorten(subject)} ${first.pred} ${first.objects}${first.terminator}`
1243
+ );
1244
+ for (const entry of rest) {
1245
+ lines.push(` ${entry.pred} ${entry.objects}${entry.terminator}`);
1246
+ }
1247
+ }
1248
+ return `${lines.join("\n")}
1249
+ `;
1250
+ }
1251
+
1252
+ // src/graph/core/harvest.ts
1253
+ var HARVESTED_KEYS = [
1254
+ "applies-to",
1255
+ "concepts",
1256
+ "lang",
1257
+ "language",
1258
+ "not-applicable-to",
1259
+ "supersedes",
1260
+ "translation-of",
1261
+ "type"
1262
+ ];
1263
+ function distance(a, b, max) {
1264
+ if (a === b) return 0;
1265
+ if (Math.abs(a.length - b.length) > max) return max + 1;
1266
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
1267
+ for (let i = 1; i <= a.length; i++) {
1268
+ const row = [i];
1269
+ for (let j = 1; j <= b.length; j++) {
1270
+ row[j] = Math.min(
1271
+ (prev[j] ?? 0) + 1,
1272
+ (row[j - 1] ?? 0) + 1,
1273
+ (prev[j - 1] ?? 0) + (a[i - 1] === b[j - 1] ? 0 : 1)
1274
+ );
1275
+ }
1276
+ prev = row;
1277
+ }
1278
+ return prev[b.length] ?? max + 1;
1279
+ }
1280
+ function nearestKnown(candidate, known) {
1281
+ const normalize = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[_\s]+/g, "-");
1282
+ const c = normalize(candidate);
1283
+ for (const k of known) {
1284
+ if (c === normalize(k) && candidate !== k) return k;
1285
+ }
1286
+ let best;
1287
+ for (const k of known) {
1288
+ const max = k.length > 8 ? 2 : 1;
1289
+ const d = distance(c, normalize(k), max);
1290
+ if (d > max) continue;
1291
+ if (!best || d < best.d) best = { key: k, d };
1292
+ }
1293
+ return best?.key;
1294
+ }
1295
+ function harvestWarnings(docs) {
1296
+ const out = [];
1297
+ const mappedTypes = Object.keys(PAGE_TYPE_TO_TOPIC_TYPE);
1298
+ for (const doc of docs) {
1299
+ const fm = doc.frontmatter;
1300
+ const present = new Set(Object.keys(fm));
1301
+ for (const key of present) {
1302
+ if (HARVESTED_KEYS.includes(key)) continue;
1303
+ const meant = nearestKnown(key, HARVESTED_KEYS);
1304
+ if (meant === void 0 || present.has(meant)) continue;
1305
+ out.push(
1306
+ `${doc.path}: page key "${key}" is not read by manni graph and looks like "${meant}" \u2014 nothing was derived from it`
1307
+ );
1308
+ }
1309
+ const type = fm["type"];
1310
+ if (typeof type === "string" && !Object.hasOwn(PAGE_TYPE_TO_TOPIC_TYPE, type)) {
1311
+ const meant = nearestKnown(type, mappedTypes);
1312
+ if (meant !== void 0) {
1313
+ out.push(
1314
+ `${doc.path}: page type "${type}" maps to no iiRDS topic type and looks like "${meant}" \u2014 no graph.type was derived`
1315
+ );
1316
+ }
1317
+ }
1318
+ }
1319
+ return out.sort(byCodeUnit);
1320
+ }
1321
+
1322
+ // src/graph/core/git.ts
1323
+ import { realExec } from "@hawkeyexl/inference";
1324
+ import { posix } from "node:path";
1325
+ function canonicalGitTime(time) {
1326
+ return time.replace(/[+-]00:00$/, "Z");
1327
+ }
1328
+ var RECORD = "";
1329
+ var STATUS_LINE = /^([AMDRC])\d*\t(.+)$/;
1330
+ function unquoteGitPath(path) {
1331
+ if (path.length < 2 || !path.startsWith('"') || !path.endsWith('"')) {
1332
+ return path;
1333
+ }
1334
+ const inner = path.slice(1, -1);
1335
+ const bytes = [];
1336
+ for (let i = 0; i < inner.length; i++) {
1337
+ const ch = inner[i] ?? "";
1338
+ if (ch !== "\\") {
1339
+ for (const byte of Buffer.from(ch, "utf8")) bytes.push(byte);
1340
+ continue;
1341
+ }
1342
+ const next = inner[++i];
1343
+ if (next === void 0) break;
1344
+ if (next >= "0" && next <= "7") {
1345
+ let octal = next;
1346
+ while (octal.length < 3) {
1347
+ const digit = inner[i + 1];
1348
+ if (digit === void 0 || digit < "0" || digit > "7") break;
1349
+ octal += digit;
1350
+ i++;
1351
+ }
1352
+ bytes.push(Number.parseInt(octal, 8));
1353
+ } else {
1354
+ const map = { n: "\n", t: " ", r: "\r" };
1355
+ for (const byte of Buffer.from(map[next] ?? next, "utf8"))
1356
+ bytes.push(byte);
1357
+ }
1358
+ }
1359
+ return Buffer.from(bytes).toString("utf8");
1360
+ }
1361
+ function clearedGitEnv() {
1362
+ const cleared = {};
1363
+ for (const key of Object.keys(process.env)) {
1364
+ if (key.startsWith("GIT_")) cleared[key] = void 0;
1365
+ }
1366
+ return cleared;
1367
+ }
1368
+ async function collectGitHistory(cwd, exec = realExec) {
1369
+ const git = async (args) => {
1370
+ const result = await exec(["git", ...args], {
1371
+ cwd,
1372
+ timeoutMs: 6e4,
1373
+ env: clearedGitEnv()
1374
+ });
1375
+ if (result.spawnError) {
1376
+ throw new GraphError(
1377
+ `git could not be run: ${result.spawnError} (is git installed and on PATH?)`
1378
+ );
1379
+ }
1380
+ if (result.timedOut) {
1381
+ throw new GraphError(
1382
+ "`git log` timed out after 60s \u2014 the repo history may be too large for whole-history provenance"
1383
+ );
1384
+ }
1385
+ if (result.code !== 0) {
1386
+ const detail = result.stderr.trim().slice(-300);
1387
+ throw new GraphError(
1388
+ `git history could not be read (is ${cwd} a git repo with at least one commit?)${detail ? ` \u2014 git said: ${detail}` : ""}`
1389
+ );
1390
+ }
1391
+ return result.stdout;
1392
+ };
1393
+ const prefix = `/${(await git(["rev-parse", "--show-prefix"])).trim()}`;
1394
+ const label = (raw) => posix.relative(prefix, `/${normalizeDocPath(unquoteGitPath(raw))}`);
1395
+ const stdout = await git([
1396
+ "-c",
1397
+ "core.quotepath=off",
1398
+ "log",
1399
+ `--format=${RECORD}%H%x09%an%x09%cI`,
1400
+ "--name-status",
1401
+ "-M"
1402
+ ]);
1403
+ if (stdout.trim() === "") {
1404
+ throw new GraphError(
1405
+ `git history could not be read (is ${cwd} a git repo with at least one commit?)`
1406
+ );
1407
+ }
1408
+ const files = /* @__PURE__ */ new Map();
1409
+ const currentName = /* @__PURE__ */ new Map();
1410
+ let headTime;
1411
+ let commitAuthor = "";
1412
+ let commitTime = "";
1413
+ const entry = (path) => {
1414
+ let file = files.get(path);
1415
+ if (!file) {
1416
+ file = { authors: [], renamedFrom: [] };
1417
+ files.set(path, file);
1418
+ }
1419
+ return file;
1420
+ };
1421
+ const touch = (pathThen) => {
1422
+ const path = label(pathThen);
1423
+ const current = currentName.get(path) ?? path;
1424
+ const file = entry(current);
1425
+ file.modified ??= commitTime;
1426
+ file.created = commitTime;
1427
+ if (!file.authors.includes(commitAuthor)) file.authors.push(commitAuthor);
1428
+ return file;
1429
+ };
1430
+ for (const line of stdout.split("\n")) {
1431
+ if (line.startsWith(RECORD)) {
1432
+ const [, author = "", rawTime = ""] = line.slice(1).split(" ");
1433
+ const time = canonicalGitTime(rawTime);
1434
+ commitAuthor = author;
1435
+ commitTime = time;
1436
+ headTime ??= time;
1437
+ continue;
1438
+ }
1439
+ const match = STATUS_LINE.exec(line);
1440
+ const status = match?.[1];
1441
+ const rest = match?.[2];
1442
+ if (status === void 0 || rest === void 0) continue;
1443
+ if (status === "R" || status === "C") {
1444
+ const [oldPath, newPath] = rest.split(" ");
1445
+ if (!oldPath || !newPath) continue;
1446
+ if (status === "C") {
1447
+ touch(newPath);
1448
+ continue;
1449
+ }
1450
+ const file = touch(newPath);
1451
+ const normalizedOld = label(oldPath);
1452
+ file.renamedFrom.push(normalizedOld);
1453
+ const normalizedNew = label(newPath);
1454
+ currentName.set(
1455
+ normalizedOld,
1456
+ currentName.get(normalizedNew) ?? normalizedNew
1457
+ );
1458
+ } else {
1459
+ touch(rest);
1460
+ }
1461
+ }
1462
+ return { headTime, files };
1463
+ }
1464
+
1465
+ // src/graph/core/graph-output.ts
1466
+ async function suppressGraphOutput(docs, sets) {
1467
+ const out = [];
1468
+ for (const doc of docs) {
1469
+ let preferences;
1470
+ try {
1471
+ const refs = sets.refsFor(doc.path, doc.frontmatter);
1472
+ preferences = await sets.validator.graphOutputPreferences(doc.frontmatter, refs);
1473
+ } catch (e) {
1474
+ throw new GraphError(
1475
+ `${doc.path}: cannot read which fields the graph may carry: ${errorMessage(e)}`
1476
+ );
1477
+ }
1478
+ const suppressed = [...preferences].filter(([, allowed]) => !allowed).map(([key]) => key);
1479
+ if (suppressed.length === 0) {
1480
+ out.push(doc);
1481
+ continue;
1482
+ }
1483
+ const frontmatter = { ...doc.frontmatter };
1484
+ for (const key of suppressed) Reflect.deleteProperty(frontmatter, key);
1485
+ out.push({ ...doc, frontmatter });
1486
+ }
1487
+ return out;
1488
+ }
1489
+
1490
+ // src/graph/core/external.ts
1491
+ import { resolve as resolve2 } from "node:path";
1492
+ async function openMetaView(source, cwd, paths, base = cwd) {
1493
+ try {
1494
+ return await metaPageView({
1495
+ cwd: base,
1496
+ ...source.configPath === void 0 ? {} : { configPath: resolve2(cwd, source.configPath) },
1497
+ ...source.noConfig === void 0 ? {} : { noConfig: source.noConfig },
1498
+ pages: paths.filter((p) => p !== STDIN_PATH).map((p) => resolve2(base, p))
1499
+ });
1500
+ } catch (e) {
1501
+ throw new GraphError(errorMessage(e));
1502
+ }
1503
+ }
1504
+ function pointerOf(key) {
1505
+ return `/${key.replace(/~/g, "~0").replace(/\//g, "~1")}`;
1506
+ }
1507
+ async function mergePage(view, path, frontmatter, present, format) {
1508
+ let merged;
1509
+ try {
1510
+ merged = await view.merge(path, {
1511
+ data: frontmatter,
1512
+ present,
1513
+ format,
1514
+ lineFor: () => void 0
1515
+ });
1516
+ } catch (e) {
1517
+ throw new GraphError(errorMessage(e));
1518
+ }
1519
+ const data = merged.extracted.data;
1520
+ const supplied = {};
1521
+ for (const key of Object.keys(data)) {
1522
+ if (merged.locate(pointerOf(key)) !== void 0) supplied[key] = data[key];
1523
+ }
1524
+ return { data, supplied };
1525
+ }
1526
+ async function withExternalMetadata(docs, view) {
1527
+ const out = [];
1528
+ for (const doc of docs) {
1529
+ const { data } = await mergePage(
1530
+ view,
1531
+ doc.path,
1532
+ doc.frontmatter,
1533
+ doc.frontmatterPresent,
1534
+ doc.format
1535
+ );
1536
+ out.push(data === doc.frontmatter ? doc : { ...doc, frontmatter: data });
1537
+ }
1538
+ return out;
1539
+ }
1540
+ function ownMetadata(content) {
1541
+ const meta = extractFrontmatter(content, "markdown");
1542
+ return { data: meta.data, present: meta.present };
1543
+ }
1544
+
1545
+ // src/graph/commands/build.ts
1546
+ var GIT_ABSENT_WARNING = "the graph has no revision history or commit agents";
1547
+ function gitAbsentWarning(reason) {
1548
+ return `${GIT_ABSENT_WARNING}: ${reason}`;
1549
+ }
1550
+ async function runBuild(opts = {}) {
1551
+ const { turtle, ...result } = await buildGraph(opts);
1552
+ mkdirSync(dirname(result.outPath), { recursive: true });
1553
+ writeFileSync(result.outPath, turtle, "utf8");
1554
+ return result;
1555
+ }
1556
+ async function buildGraph(opts = {}) {
1557
+ const cwd = opts.cwd ?? process.cwd();
1558
+ const config = loadRunConfig(
1559
+ {
1560
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
1561
+ ...opts.noConfig === void 0 ? {} : { noConfig: opts.noConfig },
1562
+ ...opts.paths === void 0 ? {} : { paths: opts.paths },
1563
+ ...opts.collection === void 0 ? {} : { collection: opts.collection }
1564
+ },
1565
+ cwd
1566
+ );
1567
+ const format = assertInputFormat(opts.paths ?? [], opts.as);
1568
+ const base = documentBase(config, opts, cwd);
1569
+ const files = resolveDocumentSet(config, opts, "build", cwd, base);
1570
+ const usingStdin = (opts.paths ?? []).includes(STDIN);
1571
+ const outPath = resolve3(cwd, opts.out ?? config.out);
1572
+ if (files.length === 0 && !usingStdin) {
1573
+ if (opts.allowEmpty) {
1574
+ return { outPath, docs: 0, quads: 0, warnings: [], turtle: emitTurtle([]) };
1575
+ }
1576
+ throw new GraphError(
1577
+ `No input files matched: ${documentSetPatterns(config, opts).join(", ")} (cwd: ${cwd})`
1578
+ );
1579
+ }
1580
+ if (usingStdin && opts.stdinContent === void 0) {
1581
+ throw new GraphError("graph build was given `-` but no stdin content.");
1582
+ }
1583
+ const allPaths = new Set(usingStdin ? [...files, STDIN_PATH] : files);
1584
+ const cache = new AnalyzeCache(resolve3(cwd, ANALYZE_CACHE_DIR));
1585
+ const analyzeOptions = {
1586
+ routes: routesFrom(config, base),
1587
+ ...format === void 0 ? {} : { format },
1588
+ parse: cache.parse
1589
+ };
1590
+ const read = files.map(
1591
+ (path) => analyzeDoc(readFileSync2(resolve3(base, path), "utf8"), path, allPaths, analyzeOptions)
1592
+ );
1593
+ if (usingStdin && opts.stdinContent !== void 0) {
1594
+ read.push(analyzeDoc(opts.stdinContent, STDIN_PATH, allPaths, analyzeOptions));
1595
+ }
1596
+ const view = await openMetaView(
1597
+ {
1598
+ ...opts.config === void 0 ? {} : { configPath: opts.config },
1599
+ ...opts.noConfig === void 0 ? {} : { noConfig: opts.noConfig }
1600
+ },
1601
+ cwd,
1602
+ files,
1603
+ base
1604
+ );
1605
+ const merged = await withExternalMetadata(read, view);
1606
+ const docs = await suppressGraphOutput(merged, view);
1607
+ const warnings = harvestWarnings(docs);
1608
+ let gitHistory;
1609
+ if (config.build.derive.includes("provenance")) {
1610
+ try {
1611
+ gitHistory = await collectGitHistory(base);
1612
+ } catch (e) {
1613
+ warnings.push(gitAbsentWarning(errorMessage(e)));
1614
+ }
1615
+ }
1616
+ const quads = deriveGraph(docs, {
1617
+ baseIri: config.baseIri,
1618
+ derive: config.build.derive,
1619
+ // The package version, stamped on the build agent (graph:version). One
1620
+ // package, one version: the tool no longer has one of its own.
1621
+ toolVersion: package_default.version,
1622
+ gitHistory,
1623
+ qualified: config.provenance.qualified
1624
+ });
1625
+ const turtle = emitTurtle(quads);
1626
+ await cache.flush();
1627
+ return { outPath, docs: docs.length, quads: quads.length, warnings, turtle };
1628
+ }
1629
+
1630
+ export {
1631
+ formatOf,
1632
+ analyzeDoc,
1633
+ discoverFiles,
1634
+ DEFAULT_EXTENSIONS,
1635
+ STDIN,
1636
+ assertInputFormat,
1637
+ resolveDocumentSet,
1638
+ documentBase,
1639
+ routesFrom,
1640
+ documentSetPatterns,
1641
+ LOCALIZATIONS_FILENAME,
1642
+ UNDETERMINED,
1643
+ isLanguageTag,
1644
+ canonicalLanguageTag,
1645
+ searchIndexFilename,
1646
+ vectorIndexFilename,
1647
+ emitLocalizations,
1648
+ parseLocalizations,
1649
+ isSafeRelativePath,
1650
+ deriveGraph,
1651
+ emitTurtle,
1652
+ collectGitHistory,
1653
+ openMetaView,
1654
+ mergePage,
1655
+ ownMetadata,
1656
+ GIT_ABSENT_WARNING,
1657
+ gitAbsentWarning,
1658
+ runBuild,
1659
+ buildGraph
1660
+ };
1661
+ //# sourceMappingURL=chunk-NLXTXE5Y.js.map