@hawkeyexl/manni 4.4.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +1 -1
  2. package/dist/batch-OZOACN4G.js +41 -0
  3. package/dist/build-Z5VOVADH.js +32 -0
  4. package/dist/calibrate-TZYT42DR.js +36 -0
  5. package/dist/capture-SL4MNDDP.js +34 -0
  6. package/dist/check-5OMA42Y6.js +26 -0
  7. package/dist/{check-HYNGXYJV.js → check-OAJWC3WD.js} +7 -5
  8. package/dist/{check-ASPIHA6B.js → check-PKY7KTQE.js} +13 -14
  9. package/dist/check-WB2QDHJQ.js +880 -0
  10. package/dist/check-WB2QDHJQ.js.map +1 -0
  11. package/dist/{chunk-MWJLZMBL.js → chunk-2JFPBZJK.js} +11 -11
  12. package/dist/{chunk-QQUKGCAJ.js → chunk-2KRB3EG7.js} +2 -2
  13. package/dist/{chunk-DXNZS72W.js → chunk-2M6DVNI5.js} +10 -6
  14. package/dist/{chunk-DXNZS72W.js.map → chunk-2M6DVNI5.js.map} +1 -1
  15. package/dist/{chunk-QZ6LRSNV.js → chunk-3NWX34ZI.js} +19 -19
  16. package/dist/{chunk-IDV6HJCB.js → chunk-5G53XATW.js} +4 -4
  17. package/dist/{chunk-M2JCK5F6.js → chunk-6KKZXB2O.js} +19 -6
  18. package/dist/chunk-6KKZXB2O.js.map +1 -0
  19. package/dist/{chunk-PNPQTHBT.js → chunk-733Y2STG.js} +2 -2
  20. package/dist/{chunk-WB3QM6PP.js → chunk-73TC4DYH.js} +332 -2522
  21. package/dist/chunk-73TC4DYH.js.map +1 -0
  22. package/dist/{chunk-EPH54CAY.js → chunk-7WRP5FFP.js} +4 -4
  23. package/dist/{chunk-NXV7WE2D.js → chunk-APKH2T6E.js} +29 -591
  24. package/dist/chunk-APKH2T6E.js.map +1 -0
  25. package/dist/{chunk-I2AH3CJJ.js → chunk-B3TS4WRM.js} +17 -15
  26. package/dist/{chunk-I2AH3CJJ.js.map → chunk-B3TS4WRM.js.map} +1 -1
  27. package/dist/chunk-BWCWTHYS.js +146 -0
  28. package/dist/chunk-BWCWTHYS.js.map +1 -0
  29. package/dist/{chunk-MMTSSTG7.js → chunk-C3B2VJNC.js} +2 -2
  30. package/dist/{chunk-PU6DFJMY.js → chunk-C73RSUIC.js} +4 -4
  31. package/dist/chunk-CU57HT2E.js +116 -0
  32. package/dist/chunk-CU57HT2E.js.map +1 -0
  33. package/dist/chunk-CWXZ2MRT.js +222 -0
  34. package/dist/chunk-CWXZ2MRT.js.map +1 -0
  35. package/dist/{chunk-GCQRPUSB.js → chunk-DC5DDJVE.js} +6 -6
  36. package/dist/{chunk-6OYEI2VV.js → chunk-ECVQNGTH.js} +3 -3
  37. package/dist/chunk-FLAO4ZS3.js +502 -0
  38. package/dist/chunk-FLAO4ZS3.js.map +1 -0
  39. package/dist/chunk-FQGLRJGX.js +174 -0
  40. package/dist/chunk-FQGLRJGX.js.map +1 -0
  41. package/dist/{chunk-7UKO3ASZ.js → chunk-GAJIJKET.js} +3 -3
  42. package/dist/{chunk-6JSSEWBG.js → chunk-GREQKO6M.js} +202 -54
  43. package/dist/chunk-GREQKO6M.js.map +1 -0
  44. package/dist/chunk-HX44GB4Z.js +30 -0
  45. package/dist/chunk-HX44GB4Z.js.map +1 -0
  46. package/dist/{chunk-4IUC2MNL.js → chunk-IY7NYNPZ.js} +9 -11
  47. package/dist/{chunk-4IUC2MNL.js.map → chunk-IY7NYNPZ.js.map} +1 -1
  48. package/dist/{chunk-57NEQRVO.js → chunk-KCOFEWV5.js} +8 -6
  49. package/dist/{chunk-57NEQRVO.js.map → chunk-KCOFEWV5.js.map} +1 -1
  50. package/dist/chunk-M2RLHZ4J.js +849 -0
  51. package/dist/chunk-M2RLHZ4J.js.map +1 -0
  52. package/dist/{chunk-NLXTXE5Y.js → chunk-M6GSS72T.js} +185 -33
  53. package/dist/chunk-M6GSS72T.js.map +1 -0
  54. package/dist/{chunk-YTURO7SI.js → chunk-MHM4QEBV.js} +4 -4
  55. package/dist/{chunk-RL5XWQOL.js → chunk-MOWMCEOJ.js} +143 -54
  56. package/dist/chunk-MOWMCEOJ.js.map +1 -0
  57. package/dist/{chunk-C4SQM22E.js → chunk-MWBJ3KTF.js} +13 -11
  58. package/dist/chunk-MWBJ3KTF.js.map +1 -0
  59. package/dist/chunk-N7JSEDSC.js +233 -0
  60. package/dist/chunk-N7JSEDSC.js.map +1 -0
  61. package/dist/{chunk-JCDXE76J.js → chunk-NBMPHD4I.js} +2 -2
  62. package/dist/{chunk-37LP5HMZ.js → chunk-NHHOPZRA.js} +12 -10
  63. package/dist/{chunk-37LP5HMZ.js.map → chunk-NHHOPZRA.js.map} +1 -1
  64. package/dist/{chunk-YB4IBW3M.js → chunk-NTWW7J76.js} +2 -2
  65. package/dist/{chunk-UJCVAWP7.js → chunk-PCFG7E6B.js} +3 -3
  66. package/dist/{chunk-UJCVAWP7.js.map → chunk-PCFG7E6B.js.map} +1 -1
  67. package/dist/{chunk-FBEQCK47.js → chunk-PL4UAF4S.js} +73 -1304
  68. package/dist/chunk-PL4UAF4S.js.map +1 -0
  69. package/dist/chunk-QCHVR4ZM.js +286 -0
  70. package/dist/chunk-QCHVR4ZM.js.map +1 -0
  71. package/dist/{chunk-PIKOVN6W.js → chunk-QW77LCOU.js} +10 -10
  72. package/dist/{chunk-2B7ZNTPV.js → chunk-SM6NOMJS.js} +2 -2
  73. package/dist/chunk-SWSTHZBI.js +2464 -0
  74. package/dist/chunk-SWSTHZBI.js.map +1 -0
  75. package/dist/{chunk-JXLTXMJ7.js → chunk-T6SYGLWR.js} +3 -3
  76. package/dist/{chunk-CK7SKVHR.js → chunk-TBAMBLF3.js} +3 -12
  77. package/dist/chunk-TBAMBLF3.js.map +1 -0
  78. package/dist/chunk-TH4JVQPF.js +133 -0
  79. package/dist/chunk-TH4JVQPF.js.map +1 -0
  80. package/dist/{chunk-QRWGPQKO.js → chunk-TLXE7LYK.js} +4 -4
  81. package/dist/{chunk-M3W7J6MU.js → chunk-TUAAR3MM.js} +3280 -283
  82. package/dist/chunk-TUAAR3MM.js.map +1 -0
  83. package/dist/{chunk-SKSDRUUK.js → chunk-UAKNZVCD.js} +4 -4
  84. package/dist/{chunk-YJOGUXGP.js → chunk-UJSGS2Y5.js} +8 -6
  85. package/dist/{chunk-YJOGUXGP.js.map → chunk-UJSGS2Y5.js.map} +1 -1
  86. package/dist/{chunk-UQISLDAV.js → chunk-UWQ2UVHE.js} +8 -6
  87. package/dist/{chunk-UQISLDAV.js.map → chunk-UWQ2UVHE.js.map} +1 -1
  88. package/dist/chunk-WQWKYAAK.js +86 -0
  89. package/dist/chunk-WQWKYAAK.js.map +1 -0
  90. package/dist/{chunk-LJ2RDH3F.js → chunk-XG6PVB5S.js} +1 -23
  91. package/dist/chunk-XG6PVB5S.js.map +1 -0
  92. package/dist/{chunk-Q35YIDBV.js → chunk-XKBEHKWO.js} +29 -264
  93. package/dist/chunk-XKBEHKWO.js.map +1 -0
  94. package/dist/chunk-XPEWUK7J.js +532 -0
  95. package/dist/chunk-XPEWUK7J.js.map +1 -0
  96. package/dist/{chunk-MYMBCTDE.js → chunk-XXDXIIQ4.js} +2 -2
  97. package/dist/chunk-YGFIQ6Y5.js +17 -0
  98. package/dist/chunk-YGFIQ6Y5.js.map +1 -0
  99. package/dist/chunk-YLK2J5YK.js +610 -0
  100. package/dist/chunk-YLK2J5YK.js.map +1 -0
  101. package/dist/{chunk-3VK37OR2.js → chunk-Z4IZGEM3.js} +16 -12
  102. package/dist/{chunk-3VK37OR2.js.map → chunk-Z4IZGEM3.js.map} +1 -1
  103. package/dist/{cli-6HIDIFVT.js → cli-6EWVMEN6.js} +5 -3
  104. package/dist/{cli-6HIDIFVT.js.map → cli-6EWVMEN6.js.map} +1 -1
  105. package/dist/{cli-VI7VHWNZ.js → cli-6H7HVDMQ.js} +113 -20
  106. package/dist/{cli-VI7VHWNZ.js.map → cli-6H7HVDMQ.js.map} +1 -1
  107. package/dist/{cli-RPM5PELX.js → cli-EPVZHAZS.js} +24 -20
  108. package/dist/cli-EPVZHAZS.js.map +1 -0
  109. package/dist/{cli-MN7PYMBI.js → cli-LUCSGTVI.js} +17 -19
  110. package/dist/{cli-MN7PYMBI.js.map → cli-LUCSGTVI.js.map} +1 -1
  111. package/dist/{cli-LFP5ZGYS.js → cli-NIG4N3XM.js} +16 -15
  112. package/dist/{cli-LFP5ZGYS.js.map → cli-NIG4N3XM.js.map} +1 -1
  113. package/dist/{cli-PYPX6O6E.js → cli-PSKNB66T.js} +225 -58
  114. package/dist/cli-PSKNB66T.js.map +1 -0
  115. package/dist/{cli-3BIBYHFK.js → cli-UO4NHIB3.js} +17 -17
  116. package/dist/{cli-6UB5N6CU.js → cli-UQ7A3YA2.js} +17 -18
  117. package/dist/{cli-6UB5N6CU.js.map → cli-UQ7A3YA2.js.map} +1 -1
  118. package/dist/{cli-673OTRGE.js → cli-YHYDLQAF.js} +10 -10
  119. package/dist/cli.js +11 -11
  120. package/dist/config-P4HDNTYD.js +26 -0
  121. package/dist/conformance-ZFCMKM6M.js +18 -0
  122. package/dist/docmeta.js +9 -9
  123. package/dist/github-7ZCH5PSO.js +26 -0
  124. package/dist/github-POT6724K.js +31 -0
  125. package/dist/host-6GURH5HE.js +23 -0
  126. package/dist/index.d.ts +102 -12
  127. package/dist/index.js +185 -171
  128. package/dist/index.js.map +1 -1
  129. package/dist/{json-SY7KLNC7.js → json-KT2K62L5.js} +12 -13
  130. package/dist/lint-5AQ5C2BV.js +29 -0
  131. package/dist/list-5JZKX2NK.js +28 -0
  132. package/dist/list-SWPFXQMU.js +30 -0
  133. package/dist/{page-KSGV5RD4.js → page-UPSXRP75.js} +11 -12
  134. package/dist/prepare-6GNEWCNH.js +155 -0
  135. package/dist/prepare-6GNEWCNH.js.map +1 -0
  136. package/dist/{pretty-CXSDXNAS.js → pretty-Q6FQAOK4.js} +13 -14
  137. package/dist/{pretty-A5EGTN35.js → pretty-SQRPFPSQ.js} +12 -13
  138. package/dist/provider-AMGRDY6V.js +34 -0
  139. package/dist/release-IECYCLTD.js +36 -0
  140. package/dist/release-IECYCLTD.js.map +1 -0
  141. package/dist/{reporters-HUY7UXFS.js → reporters-45MMC2ID.js} +13 -13
  142. package/dist/{reporters-K2VUKSAK.js → reporters-XUV4MM64.js} +7 -5
  143. package/dist/reporters-XUV4MM64.js.map +1 -0
  144. package/dist/run-KMWY2IAN.js +32 -0
  145. package/dist/run-KMWY2IAN.js.map +1 -0
  146. package/dist/{term-BIQUPMOR.js → term-PCJUCWNF.js} +16 -17
  147. package/dist/{term-BIQUPMOR.js.map → term-PCJUCWNF.js.map} +1 -1
  148. package/dist/validate-IMNCZNWG.js +17 -0
  149. package/dist/validate-IMNCZNWG.js.map +1 -0
  150. package/package.json +2 -2
  151. package/plugin/manni/.claude-plugin/plugin.json +1 -1
  152. package/plugin/manni/hooks/hooks.json +47 -0
  153. package/dist/batch-GFNLTGOH.js +0 -36
  154. package/dist/build-COYIQ25L.js +0 -31
  155. package/dist/calibrate-MDBLJKEE.js +0 -31
  156. package/dist/capture-LRML2PKV.js +0 -32
  157. package/dist/check-EDLBYL5N.js +0 -27
  158. package/dist/chunk-6JSSEWBG.js.map +0 -1
  159. package/dist/chunk-A5ULMVHQ.js +0 -164
  160. package/dist/chunk-A5ULMVHQ.js.map +0 -1
  161. package/dist/chunk-C4SQM22E.js.map +0 -1
  162. package/dist/chunk-CK7SKVHR.js.map +0 -1
  163. package/dist/chunk-FBEQCK47.js.map +0 -1
  164. package/dist/chunk-LJ2RDH3F.js.map +0 -1
  165. package/dist/chunk-M2JCK5F6.js.map +0 -1
  166. package/dist/chunk-M3W7J6MU.js.map +0 -1
  167. package/dist/chunk-NLXTXE5Y.js.map +0 -1
  168. package/dist/chunk-NXV7WE2D.js.map +0 -1
  169. package/dist/chunk-Q35YIDBV.js.map +0 -1
  170. package/dist/chunk-RL5XWQOL.js.map +0 -1
  171. package/dist/chunk-TK3FC4RF.js +0 -3090
  172. package/dist/chunk-TK3FC4RF.js.map +0 -1
  173. package/dist/chunk-TU2WUBVZ.js +0 -2
  174. package/dist/chunk-WB3QM6PP.js.map +0 -1
  175. package/dist/cli-PYPX6O6E.js.map +0 -1
  176. package/dist/cli-RPM5PELX.js.map +0 -1
  177. package/dist/github-6QMYOHC3.js +0 -27
  178. package/dist/github-SXPGP5DB.js +0 -32
  179. package/dist/lint-ASMJLLPA.js +0 -28
  180. package/dist/list-KZNVTNVC.js +0 -31
  181. package/dist/list-Q57YTFUQ.js +0 -29
  182. package/dist/run-3DYJNRIG.js +0 -33
  183. package/dist/validate-53RKM2XG.js +0 -16
  184. /package/dist/{batch-GFNLTGOH.js.map → batch-OZOACN4G.js.map} +0 -0
  185. /package/dist/{build-COYIQ25L.js.map → build-Z5VOVADH.js.map} +0 -0
  186. /package/dist/{calibrate-MDBLJKEE.js.map → calibrate-TZYT42DR.js.map} +0 -0
  187. /package/dist/{capture-LRML2PKV.js.map → capture-SL4MNDDP.js.map} +0 -0
  188. /package/dist/{check-ASPIHA6B.js.map → check-5OMA42Y6.js.map} +0 -0
  189. /package/dist/{check-EDLBYL5N.js.map → check-OAJWC3WD.js.map} +0 -0
  190. /package/dist/{check-HYNGXYJV.js.map → check-PKY7KTQE.js.map} +0 -0
  191. /package/dist/{chunk-MWJLZMBL.js.map → chunk-2JFPBZJK.js.map} +0 -0
  192. /package/dist/{chunk-QQUKGCAJ.js.map → chunk-2KRB3EG7.js.map} +0 -0
  193. /package/dist/{chunk-QZ6LRSNV.js.map → chunk-3NWX34ZI.js.map} +0 -0
  194. /package/dist/{chunk-IDV6HJCB.js.map → chunk-5G53XATW.js.map} +0 -0
  195. /package/dist/{chunk-PNPQTHBT.js.map → chunk-733Y2STG.js.map} +0 -0
  196. /package/dist/{chunk-EPH54CAY.js.map → chunk-7WRP5FFP.js.map} +0 -0
  197. /package/dist/{chunk-MMTSSTG7.js.map → chunk-C3B2VJNC.js.map} +0 -0
  198. /package/dist/{chunk-PU6DFJMY.js.map → chunk-C73RSUIC.js.map} +0 -0
  199. /package/dist/{chunk-GCQRPUSB.js.map → chunk-DC5DDJVE.js.map} +0 -0
  200. /package/dist/{chunk-6OYEI2VV.js.map → chunk-ECVQNGTH.js.map} +0 -0
  201. /package/dist/{chunk-7UKO3ASZ.js.map → chunk-GAJIJKET.js.map} +0 -0
  202. /package/dist/{chunk-YTURO7SI.js.map → chunk-MHM4QEBV.js.map} +0 -0
  203. /package/dist/{chunk-JCDXE76J.js.map → chunk-NBMPHD4I.js.map} +0 -0
  204. /package/dist/{chunk-YB4IBW3M.js.map → chunk-NTWW7J76.js.map} +0 -0
  205. /package/dist/{chunk-PIKOVN6W.js.map → chunk-QW77LCOU.js.map} +0 -0
  206. /package/dist/{chunk-2B7ZNTPV.js.map → chunk-SM6NOMJS.js.map} +0 -0
  207. /package/dist/{chunk-JXLTXMJ7.js.map → chunk-T6SYGLWR.js.map} +0 -0
  208. /package/dist/{chunk-QRWGPQKO.js.map → chunk-TLXE7LYK.js.map} +0 -0
  209. /package/dist/{chunk-SKSDRUUK.js.map → chunk-UAKNZVCD.js.map} +0 -0
  210. /package/dist/{chunk-MYMBCTDE.js.map → chunk-XXDXIIQ4.js.map} +0 -0
  211. /package/dist/{cli-3BIBYHFK.js.map → cli-UO4NHIB3.js.map} +0 -0
  212. /package/dist/{chunk-TU2WUBVZ.js.map → cli-YHYDLQAF.js.map} +0 -0
  213. /package/dist/{cli-673OTRGE.js.map → config-P4HDNTYD.js.map} +0 -0
  214. /package/dist/{github-6QMYOHC3.js.map → conformance-ZFCMKM6M.js.map} +0 -0
  215. /package/dist/{github-SXPGP5DB.js.map → github-7ZCH5PSO.js.map} +0 -0
  216. /package/dist/{json-SY7KLNC7.js.map → github-POT6724K.js.map} +0 -0
  217. /package/dist/{lint-ASMJLLPA.js.map → host-6GURH5HE.js.map} +0 -0
  218. /package/dist/{list-KZNVTNVC.js.map → json-KT2K62L5.js.map} +0 -0
  219. /package/dist/{list-Q57YTFUQ.js.map → lint-5AQ5C2BV.js.map} +0 -0
  220. /package/dist/{page-KSGV5RD4.js.map → list-5JZKX2NK.js.map} +0 -0
  221. /package/dist/{pretty-A5EGTN35.js.map → list-SWPFXQMU.js.map} +0 -0
  222. /package/dist/{pretty-CXSDXNAS.js.map → page-UPSXRP75.js.map} +0 -0
  223. /package/dist/{reporters-HUY7UXFS.js.map → pretty-Q6FQAOK4.js.map} +0 -0
  224. /package/dist/{reporters-K2VUKSAK.js.map → pretty-SQRPFPSQ.js.map} +0 -0
  225. /package/dist/{run-3DYJNRIG.js.map → provider-AMGRDY6V.js.map} +0 -0
  226. /package/dist/{validate-53RKM2XG.js.map → reporters-45MMC2ID.js.map} +0 -0
@@ -0,0 +1,2464 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ listFormats,
4
+ parserByName,
5
+ parserForExtension,
6
+ supportedExtensions,
7
+ unsupportedFormatMessage
8
+ } from "./chunk-73TC4DYH.js";
9
+ import {
10
+ packageRoot
11
+ } from "./chunk-TIUTIOFB.js";
12
+ import {
13
+ BLOCK_KINDS,
14
+ BLOCK_KIND_NODE,
15
+ checkContains,
16
+ checkContainsIn,
17
+ checkHeading,
18
+ checkSequence,
19
+ checkSequenceIn,
20
+ isWildcard,
21
+ matchSections,
22
+ occurrenceRange
23
+ } from "./chunk-TBAMBLF3.js";
24
+ import {
25
+ LintError
26
+ } from "./chunk-YGFIQ6Y5.js";
27
+ import {
28
+ STDIN_TOKEN,
29
+ assertNonEmpty,
30
+ gitignoreOptions,
31
+ isErrorSeverity,
32
+ resolveTargetSet
33
+ } from "./chunk-KCOFEWV5.js";
34
+ import {
35
+ ditaOtHome,
36
+ findConfigFile,
37
+ readConfigFile,
38
+ selectCollections
39
+ } from "./chunk-XKBEHKWO.js";
40
+ import {
41
+ warn
42
+ } from "./chunk-HX44GB4Z.js";
43
+ import {
44
+ package_default
45
+ } from "./chunk-PCFG7E6B.js";
46
+ import {
47
+ ToolError,
48
+ errorMessage
49
+ } from "./chunk-G3T47CFV.js";
50
+
51
+ // src/lint/commands/lint.ts
52
+ import { readFile as readFile3 } from "node:fs/promises";
53
+ import { extname, relative, resolve as resolve2 } from "node:path";
54
+
55
+ // src/lint/core/template-registry.ts
56
+ import { readFile } from "node:fs/promises";
57
+ import { dirname, isAbsolute, join, resolve as resolvePath } from "node:path";
58
+ import { parse as parseYaml } from "yaml";
59
+ import { dereference } from "@apidevtools/json-schema-ref-parser";
60
+ import * as AjvNs from "ajv";
61
+
62
+ // schemas/lint/template.json
63
+ var template_default = {
64
+ $schema: "http://json-schema.org/draft-07/schema#",
65
+ $id: "manni-lint:template:2",
66
+ title: "manni lint template file",
67
+ description: "A template file: named doctype templates, plus the reusable fragments they point at with `$ref`. Validated after dereferencing, so no `$ref` survives to be seen here. A template is itself a rule: it describes the page, and it takes every rule key plus `title`, `types` and `extends`.",
68
+ type: "object",
69
+ additionalProperties: false,
70
+ required: ["templates"],
71
+ properties: {
72
+ $schema: {
73
+ description: "URL of this schema, so an editor can offer completion. Read by editors, never by the linter.",
74
+ type: "string"
75
+ },
76
+ info: {
77
+ description: "Free-form metadata about the file (title, version, author). Never read by the linter; declared so a file that carries it is not rejected.",
78
+ type: "object"
79
+ },
80
+ templates: {
81
+ description: "The doctype templates this file defines, keyed by name.",
82
+ type: "object",
83
+ additionalProperties: false,
84
+ patternProperties: {
85
+ "^[A-Za-z][A-Za-z0-9-_]*$": { $ref: "#/definitions/template" }
86
+ }
87
+ },
88
+ components: {
89
+ description: "Reusable fragments, targeted by `$ref` from elsewhere in the file. Left unconstrained: a fragment is validated where it is inlined, not where it is declared.",
90
+ type: "object"
91
+ }
92
+ },
93
+ definitions: {
94
+ min: {
95
+ description: "Fewest occurrences. Absent means one. Deliberately carries no JSON Schema `default`, and neither does any other key in this file: Ajv's `useDefaults` would write one in at load time, and a written-in value is indistinguishable from a value the author typed, so it wins the `extends` merge against the parent's real value. The code reads `undefined` instead.",
96
+ type: "integer",
97
+ minimum: 0
98
+ },
99
+ max: {
100
+ description: "Most occurrences. Absent is unbounded, so write `max: 1` for exactly once and `max: 0` to forbid. Must be at least `min` where both are written; the loader checks that, because JSON Schema cannot compare two siblings.",
101
+ type: "integer",
102
+ minimum: 0
103
+ },
104
+ id: {
105
+ description: "Handle for this rule, unique among its siblings. It appears in messages, and `extends` targets it to replace one inherited rule in place.",
106
+ type: "string",
107
+ pattern: "^[A-Za-z][A-Za-z0-9-_]*$"
108
+ },
109
+ description: {
110
+ description: "Prose description of what the rule is for. Never checked.",
111
+ type: "string"
112
+ },
113
+ heading: {
114
+ description: "The heading of the section this rule describes. Absent means any heading, or none. A string is exact text, a list is one-of, `{ pattern }` is an unanchored regular expression, and `false` means the section has no heading of its own.",
115
+ oneOf: [
116
+ { type: "string", minLength: 1 },
117
+ {
118
+ type: "array",
119
+ minItems: 1,
120
+ items: { type: "string", minLength: 1 }
121
+ },
122
+ {
123
+ type: "object",
124
+ additionalProperties: false,
125
+ required: ["pattern"],
126
+ properties: {
127
+ pattern: { type: "string", minLength: 1 }
128
+ }
129
+ },
130
+ { const: false }
131
+ ]
132
+ },
133
+ attributes: {
134
+ description: "Literal attributes the node must carry. A string is the required value; `true` requires the attribute to be present, and `false` requires it to be absent.",
135
+ type: "object",
136
+ additionalProperties: {
137
+ oneOf: [{ type: "string" }, { type: "boolean" }]
138
+ }
139
+ },
140
+ oneOfSequenceOrContains: {
141
+ description: "A rule says what it holds in order (`sequence`) or in any order (`contains`), never both.",
142
+ type: "object",
143
+ not: { required: ["sequence", "contains"] }
144
+ },
145
+ noHeadingOnRepeat: {
146
+ description: "`repeat` groups sibling rules into one repeated unit, so the group has no heading of its own. Put the heading on the first rule inside it.",
147
+ type: "object",
148
+ not: { required: ["heading", "repeat"] }
149
+ },
150
+ noInstructions: {
151
+ description: "`instructions` was the language-model hook of doc-structure-lint. manni lint is deterministic and has no model, so a template carrying it is rejected outright rather than silently ignored; the loader turns this into a migration message pointing at manni docevals.",
152
+ type: "object",
153
+ not: { required: ["instructions"] }
154
+ },
155
+ template: {
156
+ description: "One doctype template. A template is a rule describing the page itself, so it takes every rule key, plus `title`, `types` and `extends`.",
157
+ type: "object",
158
+ additionalProperties: false,
159
+ allOf: [
160
+ { $ref: "#/definitions/noInstructions" },
161
+ { $ref: "#/definitions/oneOfSequenceOrContains" },
162
+ { $ref: "#/definitions/noHeadingOnRepeat" }
163
+ ],
164
+ properties: {
165
+ title: {
166
+ description: "Human-readable name for the template, shown by `manni lint templates`.",
167
+ type: "string"
168
+ },
169
+ types: {
170
+ description: "Doctypes this template serves, matched against a page's `type` frontmatter.",
171
+ type: "array",
172
+ items: { type: "string", minLength: 1 }
173
+ },
174
+ extends: {
175
+ description: "Template reference (built-in id, path, or URL, optionally with a `#name` fragment) whose rules this template inherits.",
176
+ type: "string",
177
+ minLength: 1
178
+ },
179
+ id: { $ref: "#/definitions/id" },
180
+ description: { $ref: "#/definitions/description" },
181
+ heading: { $ref: "#/definitions/heading" },
182
+ repeat: { $ref: "#/definitions/repeat" },
183
+ min: {
184
+ description: "Refused on a template: a page is one page. Declared here only so the loader can say that in a sentence, rather than leaving Ajv to report an unexpected key.",
185
+ type: "integer",
186
+ minimum: 0
187
+ },
188
+ max: {
189
+ description: "Refused on a template, for the same reason as `min`.",
190
+ type: "integer",
191
+ minimum: 0
192
+ },
193
+ sequence: { $ref: "#/definitions/sequence" },
194
+ contains: { $ref: "#/definitions/blockRules" },
195
+ sections: { $ref: "#/definitions/rules" }
196
+ }
197
+ },
198
+ rule: {
199
+ description: "One rule. It describes a section of the page, or, with `repeat`, a run of sections matched as one unit.",
200
+ type: "object",
201
+ additionalProperties: false,
202
+ allOf: [
203
+ { $ref: "#/definitions/noInstructions" },
204
+ { $ref: "#/definitions/oneOfSequenceOrContains" },
205
+ { $ref: "#/definitions/noHeadingOnRepeat" }
206
+ ],
207
+ properties: {
208
+ id: { $ref: "#/definitions/id" },
209
+ description: { $ref: "#/definitions/description" },
210
+ heading: { $ref: "#/definitions/heading" },
211
+ repeat: { $ref: "#/definitions/repeat" },
212
+ min: { $ref: "#/definitions/min" },
213
+ max: { $ref: "#/definitions/max" },
214
+ sequence: { $ref: "#/definitions/sequence" },
215
+ contains: { $ref: "#/definitions/blockRules" },
216
+ sections: { $ref: "#/definitions/rules" }
217
+ }
218
+ },
219
+ rules: {
220
+ description: "Rules for the subsections, in document order.",
221
+ type: "array",
222
+ items: { $ref: "#/definitions/rule" }
223
+ },
224
+ repeat: {
225
+ description: "Rules matched in order as one unit, repeated by this rule's `min` and `max`.",
226
+ type: "array",
227
+ minItems: 1,
228
+ items: { $ref: "#/definitions/rule" }
229
+ },
230
+ sequence: {
231
+ description: "What the section holds, in order. Each entry is one run of one content kind.",
232
+ type: "array",
233
+ minItems: 1,
234
+ items: {
235
+ allOf: [
236
+ { $ref: "#/definitions/blockRules" },
237
+ { type: "object", maxProperties: 1 }
238
+ ]
239
+ }
240
+ },
241
+ blockRules: {
242
+ description: "Block rules, keyed by content kind. The key is plural because it carries a count: the node model says `codeBlock`, a template says `codeBlocks: { min: 1 }`.",
243
+ type: "object",
244
+ additionalProperties: false,
245
+ minProperties: 1,
246
+ properties: {
247
+ paragraphs: { $ref: "#/definitions/paragraphs" },
248
+ codeBlocks: { $ref: "#/definitions/codeBlocks" },
249
+ lists: { $ref: "#/definitions/lists" },
250
+ tables: { $ref: "#/definitions/tables" },
251
+ admonitions: { $ref: "#/definitions/admonitions" },
252
+ images: { $ref: "#/definitions/images" },
253
+ blockquotes: { $ref: "#/definitions/counted" },
254
+ definitionLists: { $ref: "#/definitions/counted" },
255
+ elements: { $ref: "#/definitions/elements" }
256
+ }
257
+ },
258
+ counted: {
259
+ description: "A block rule that only counts.",
260
+ type: "object",
261
+ additionalProperties: false,
262
+ properties: {
263
+ min: { $ref: "#/definitions/min" },
264
+ max: { $ref: "#/definitions/max" }
265
+ }
266
+ },
267
+ paragraphs: {
268
+ description: "Paragraph requirements.",
269
+ type: "object",
270
+ additionalProperties: false,
271
+ properties: {
272
+ min: { $ref: "#/definitions/min" },
273
+ max: { $ref: "#/definitions/max" },
274
+ pattern: {
275
+ description: "Unanchored regular expression every paragraph in the run must match.",
276
+ type: "string",
277
+ minLength: 1
278
+ }
279
+ }
280
+ },
281
+ codeBlocks: {
282
+ description: "Code block requirements.",
283
+ type: "object",
284
+ additionalProperties: false,
285
+ properties: {
286
+ min: { $ref: "#/definitions/min" },
287
+ max: { $ref: "#/definitions/max" },
288
+ language: {
289
+ description: "Language every code block must be tagged with, as the node model spells it. A list is one-of: the block must declare one of them.",
290
+ oneOf: [
291
+ { type: "string", minLength: 1 },
292
+ {
293
+ type: "array",
294
+ minItems: 1,
295
+ items: { type: "string", minLength: 1 }
296
+ }
297
+ ]
298
+ },
299
+ fenceInfo: {
300
+ description: "Info-string tail after the language, where the format carries one.",
301
+ type: "string"
302
+ }
303
+ }
304
+ },
305
+ lists: {
306
+ description: "List requirements.",
307
+ type: "object",
308
+ additionalProperties: false,
309
+ properties: {
310
+ min: { $ref: "#/definitions/min" },
311
+ max: { $ref: "#/definitions/max" },
312
+ ordered: {
313
+ description: "Whether the list must be ordered. Absent accepts either.",
314
+ type: "boolean"
315
+ },
316
+ items: { $ref: "#/definitions/listItems" }
317
+ }
318
+ },
319
+ listItems: {
320
+ description: "Requirements applied to every item of every list in the run. Recurses arbitrarily deep.",
321
+ type: "object",
322
+ additionalProperties: false,
323
+ allOf: [{ $ref: "#/definitions/oneOfSequenceOrContains" }],
324
+ properties: {
325
+ min: { $ref: "#/definitions/min" },
326
+ max: { $ref: "#/definitions/max" },
327
+ sequence: { $ref: "#/definitions/sequence" },
328
+ contains: { $ref: "#/definitions/blockRules" }
329
+ }
330
+ },
331
+ tables: {
332
+ description: "Table requirements.",
333
+ type: "object",
334
+ additionalProperties: false,
335
+ properties: {
336
+ min: { $ref: "#/definitions/min" },
337
+ max: { $ref: "#/definitions/max" },
338
+ columns: {
339
+ description: "The header cells, in order.",
340
+ type: "array",
341
+ items: { type: "string" }
342
+ }
343
+ }
344
+ },
345
+ admonitions: {
346
+ description: "Admonition requirements.",
347
+ type: "object",
348
+ additionalProperties: false,
349
+ properties: {
350
+ min: { $ref: "#/definitions/min" },
351
+ max: { $ref: "#/definitions/max" },
352
+ variant: {
353
+ description: "Which flavor of admonition. The node model's `variant`, not any one format's spelling.",
354
+ enum: [
355
+ "note",
356
+ "tip",
357
+ "important",
358
+ "caution",
359
+ "warning",
360
+ "danger"
361
+ ]
362
+ }
363
+ }
364
+ },
365
+ images: {
366
+ description: "Image requirements.",
367
+ type: "object",
368
+ additionalProperties: false,
369
+ properties: {
370
+ min: { $ref: "#/definitions/min" },
371
+ max: { $ref: "#/definitions/max" },
372
+ url: {
373
+ description: "Where the image must point.",
374
+ type: "string"
375
+ },
376
+ alt: {
377
+ description: "Alternative text the image must carry.",
378
+ type: "string"
379
+ },
380
+ attributes: { $ref: "#/definitions/attributes" }
381
+ }
382
+ },
383
+ elements: {
384
+ description: "Named-wrapper requirements. `sequence` and `contains` here describe what the element holds, which is never the enclosing section's content.",
385
+ type: "object",
386
+ additionalProperties: false,
387
+ allOf: [{ $ref: "#/definitions/oneOfSequenceOrContains" }],
388
+ properties: {
389
+ min: { $ref: "#/definitions/min" },
390
+ max: { $ref: "#/definitions/max" },
391
+ tag: {
392
+ description: "The element's name, as an author of the format would write it. A list is one-of: an element counts when it carries any of them.",
393
+ oneOf: [
394
+ { type: "string", minLength: 1 },
395
+ {
396
+ type: "array",
397
+ minItems: 1,
398
+ items: { type: "string", minLength: 1 }
399
+ }
400
+ ]
401
+ },
402
+ attributes: { $ref: "#/definitions/attributes" },
403
+ sequence: { $ref: "#/definitions/sequence" },
404
+ contains: { $ref: "#/definitions/blockRules" }
405
+ }
406
+ }
407
+ }
408
+ };
409
+
410
+ // templates/lint/tgdp/manifest.json
411
+ var manifest_default = {
412
+ $comment: 'Built-in doctype templates derived from The Good Docs Project templates, pinned to release v1.6.0 ("Iron"): https://gitlab.com/tgdp/templates/-/tree/v1.6.0. Each entry\'s `file` is a YAML template beside this manifest; `source` is the upstream Markdown it was derived from, vendored under test/lint/fixtures/tgdp/ and linted against the derived template so the two cannot drift. Run `npm run check:tgdp-pin` to see whether upstream has moved past the pin.',
413
+ vendor: "tgdp",
414
+ upstream: "https://gitlab.com/tgdp/templates",
415
+ pin: "v1.6.0",
416
+ templates: [
417
+ {
418
+ id: "tgdp:how-to:1.6",
419
+ file: "tgdp/how-to.yaml",
420
+ title: "How-to guide (The Good Docs Project)",
421
+ types: ["how-to"],
422
+ source: "how-to/template_how-to.md"
423
+ },
424
+ {
425
+ id: "tgdp:tutorial:1.6",
426
+ file: "tgdp/tutorial.yaml",
427
+ title: "Tutorial (The Good Docs Project)",
428
+ types: ["tutorial"],
429
+ source: "tutorial/template_tutorial.md"
430
+ },
431
+ {
432
+ id: "tgdp:reference:1.6",
433
+ file: "tgdp/reference.yaml",
434
+ title: "Reference (The Good Docs Project)",
435
+ types: ["reference"],
436
+ source: "reference/template_reference.md"
437
+ },
438
+ {
439
+ id: "tgdp:concept:1.6",
440
+ file: "tgdp/concept.yaml",
441
+ title: "Concept (The Good Docs Project)",
442
+ types: ["concept", "explanation"],
443
+ source: "concept/template_concept.md"
444
+ },
445
+ {
446
+ id: "tgdp:troubleshooting:1.6",
447
+ file: "tgdp/troubleshooting.yaml",
448
+ title: "Troubleshooting (The Good Docs Project)",
449
+ types: ["troubleshooting"],
450
+ source: "troubleshooting/template_troubleshooting.md"
451
+ },
452
+ {
453
+ id: "tgdp:release-notes:1.6",
454
+ file: "tgdp/release-notes.yaml",
455
+ title: "Release notes (The Good Docs Project)",
456
+ types: ["release-notes"],
457
+ source: "release-notes/template_release-notes.md"
458
+ },
459
+ {
460
+ id: "tgdp:readme:1.6",
461
+ file: "tgdp/readme.yaml",
462
+ title: "README (The Good Docs Project)",
463
+ types: ["readme"],
464
+ source: "readme/template_readme.md"
465
+ }
466
+ ]
467
+ };
468
+
469
+ // src/lint/core/template-registry.ts
470
+ var Ajv = AjvNs.default;
471
+ var DEREFERENCE_OPTIONS = { resolve: { external: false } };
472
+ var MANIFESTS = [manifest_default];
473
+ var BUILTINS = /* @__PURE__ */ new Map();
474
+ for (const manifest of MANIFESTS) {
475
+ for (const entry of manifest.templates) BUILTINS.set(entry.id, entry);
476
+ }
477
+ var builtinCache = /* @__PURE__ */ new Map();
478
+ function listBuiltins() {
479
+ return [...BUILTINS.values()].map((entry) => ({
480
+ id: entry.id,
481
+ title: entry.title,
482
+ types: entry.types
483
+ }));
484
+ }
485
+ async function readBuiltinFile(id, file) {
486
+ const path = join(packageRoot(import.meta.url), "templates", "lint", file);
487
+ try {
488
+ return await readFile(path, "utf8");
489
+ } catch (err) {
490
+ throw new LintError(
491
+ `Built-in template "${id}" is registered but its file could not be read (${path}): ${errorMessage(err)}`
492
+ );
493
+ }
494
+ }
495
+ async function loadBuiltin(id) {
496
+ const cached = builtinCache.get(id);
497
+ if (cached) return cached;
498
+ const entry = BUILTINS.get(id);
499
+ if (!entry) {
500
+ throw new LintError(`No built-in template is registered under "${id}".`);
501
+ }
502
+ const raw = await readBuiltinFile(id, entry.file);
503
+ const parsed = parseYaml(raw);
504
+ if (!isRecord(parsed)) {
505
+ throw new LintError(
506
+ `Built-in template "${id}" (${entry.file}) is not a template file: expected an object at the top level, got ${parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed}.`
507
+ );
508
+ }
509
+ const file = validateTemplateFile(
510
+ await dereference(parsed, DEREFERENCE_OPTIONS),
511
+ entry.file
512
+ );
513
+ const names = Object.keys(file.templates ?? {});
514
+ const template = file.templates?.[names[0] ?? ""];
515
+ if (!template || names.length !== 1) {
516
+ throw new LintError(
517
+ `Built-in template "${id}" must define exactly one template, found ${names.length}.`
518
+ );
519
+ }
520
+ const resolved = { types: entry.types, ...template };
521
+ builtinCache.set(id, resolved);
522
+ return resolved;
523
+ }
524
+ var BUILTIN_ID = /^[a-z0-9][a-z0-9._-]*(?::[a-z0-9][a-z0-9._-]*)+$/i;
525
+ var FILE_EXTENSIONS = [".yaml", ".yml", ".json"];
526
+ function classifyRef(ref) {
527
+ if (/^https?:\/\//i.test(ref)) return { kind: "url", ref };
528
+ const lower = ref.toLowerCase();
529
+ if (!ref.includes("/") && !ref.includes("\\") && !FILE_EXTENSIONS.some((ext) => lower.endsWith(ext)) && BUILTIN_ID.test(ref)) {
530
+ return { kind: "builtin", ref };
531
+ }
532
+ return { kind: "file", ref };
533
+ }
534
+ var DEFAULT_TIMEOUT_MS = 1e4;
535
+ var ajv = new Ajv();
536
+ var compiled = null;
537
+ function fileValidator() {
538
+ compiled ??= ajv.compile(template_default);
539
+ return compiled;
540
+ }
541
+ function isRecord(value) {
542
+ return typeof value === "object" && value !== null && !Array.isArray(value);
543
+ }
544
+ function stringsOf(value) {
545
+ if (typeof value === "string") return [value];
546
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
547
+ return [];
548
+ }
549
+ function suggestEvalName(segments) {
550
+ const name = segments.filter((segment) => segment !== "templates" && segment !== "sections").join("-").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
551
+ return name || "section-instructions";
552
+ }
553
+ function findInstructions(node, segments = [], seen = /* @__PURE__ */ new WeakSet()) {
554
+ if (typeof node !== "object" || node === null) return null;
555
+ if (seen.has(node)) return null;
556
+ seen.add(node);
557
+ if (Array.isArray(node)) {
558
+ for (const [index, item] of node.entries()) {
559
+ const hit = findInstructions(item, [...segments, String(index)], seen);
560
+ if (hit) return hit;
561
+ }
562
+ return null;
563
+ }
564
+ const record = node;
565
+ const insideSectionsMap = segments.at(-1) === "sections";
566
+ if (!insideSectionsMap && "instructions" in record) {
567
+ return {
568
+ path: segments.join("."),
569
+ name: suggestEvalName(segments),
570
+ values: stringsOf(record.instructions)
571
+ };
572
+ }
573
+ for (const [key, value] of Object.entries(record)) {
574
+ const hit = findInstructions(value, [...segments, key], seen);
575
+ if (hit) return hit;
576
+ }
577
+ return null;
578
+ }
579
+ function instructionsMessage(source, hit) {
580
+ const assertion = hit.values[0] ?? "";
581
+ return [
582
+ `${source}: "${hit.path}" uses \`instructions\`, which manni lint no longer evaluates \u2014 structure checking is deterministic. Move it to a manni docevals assertion eval in manni.config.yaml:`,
583
+ "",
584
+ " docevals:",
585
+ " evals:",
586
+ ` ${hit.name}:`,
587
+ ` assertion: ${assertion}`,
588
+ " grader: ai"
589
+ ].join("\n");
590
+ }
591
+ var V1_KEYS = {
592
+ required: '"required" is not a template key. A rule is optional with "min: 0".',
593
+ additionalSections: '"additionalSections" is not a template key. v2 has no equivalent, so drop it.',
594
+ code_blocks: '"code_blocks" is not a template key. The v2 spelling is "codeBlocks".'
595
+ };
596
+ var V1_SECTIONS_MAP = '"sections" is a list of rules, not a map. Name each rule with "id" and list them in order.';
597
+ var V1_REPEAT_BOOLEAN = '"repeat" is a list of rules, not a boolean. A rule repeats through "min" and "max".';
598
+ var V1_PARAGRAPH_PATTERNS = '"patterns" is not a paragraphs key. The v2 spelling is "pattern", one regular expression.';
599
+ function findV1Key(node, seen = /* @__PURE__ */ new WeakSet()) {
600
+ if (typeof node !== "object" || node === null) return null;
601
+ if (seen.has(node)) return null;
602
+ seen.add(node);
603
+ if (Array.isArray(node)) {
604
+ for (const item of node) {
605
+ const hit = findV1Key(item, seen);
606
+ if (hit) return hit;
607
+ }
608
+ return null;
609
+ }
610
+ const record = node;
611
+ for (const [key, message] of Object.entries(V1_KEYS)) {
612
+ if (key in record) return message;
613
+ }
614
+ if (isRecord(record.sections)) return V1_SECTIONS_MAP;
615
+ if (typeof record.repeat === "boolean") return V1_REPEAT_BOOLEAN;
616
+ if (isRecord(record.paragraphs) && "patterns" in record.paragraphs) {
617
+ return V1_PARAGRAPH_PATTERNS;
618
+ }
619
+ for (const value of Object.values(record)) {
620
+ const hit = findV1Key(value, seen);
621
+ if (hit) return hit;
622
+ }
623
+ return null;
624
+ }
625
+ function findV1File(data) {
626
+ if (!isRecord(data)) return null;
627
+ return findV1Key(data.templates) ?? findV1Key(data.components);
628
+ }
629
+ function ruleLabel(rule, path) {
630
+ return rule.id === void 0 ? path : `"${rule.id}"`;
631
+ }
632
+ function checkRange(range, source, label) {
633
+ const { min, max } = range;
634
+ if (min !== void 0 && max !== void 0 && max < min) {
635
+ throw new LintError(`${source}: rule ${label} sets max ${max} below min ${min}.`);
636
+ }
637
+ }
638
+ function sameOccurrenceRange(a, b) {
639
+ const rangeA = occurrenceRange(a);
640
+ const rangeB = occurrenceRange(b);
641
+ return rangeA.min === rangeB.min && rangeA.max === rangeB.max;
642
+ }
643
+ function checkPattern(pattern, source) {
644
+ if (pattern === void 0) return;
645
+ try {
646
+ new RegExp(pattern);
647
+ } catch (err) {
648
+ throw new LintError(
649
+ `Invalid pattern "${pattern}" in ${source}: ${errorMessage(err)}.`
650
+ );
651
+ }
652
+ }
653
+ function checkListItems(items, source, path) {
654
+ checkRange(items, source, path);
655
+ if (items.contains) checkBlockRule(items.contains, source, `${path}.contains`);
656
+ items.sequence?.forEach((entry, index) => {
657
+ checkBlockRule(entry, source, `${path}.sequence[${index}]`);
658
+ });
659
+ }
660
+ function checkBlockRule(block, source, path) {
661
+ for (const kind of BLOCK_KINDS) {
662
+ const counted = block[kind];
663
+ if (counted) checkRange(counted, source, `${path}.${kind}`);
664
+ }
665
+ checkPattern(block.paragraphs?.pattern, source);
666
+ if (block.lists?.items) {
667
+ checkListItems(block.lists.items, source, `${path}.lists.items`);
668
+ }
669
+ const element = block.elements;
670
+ if (element?.contains) {
671
+ checkBlockRule(element.contains, source, `${path}.elements.contains`);
672
+ }
673
+ element?.sequence?.forEach((entry, index) => {
674
+ checkBlockRule(entry, source, `${path}.elements.sequence[${index}]`);
675
+ });
676
+ }
677
+ function checkRuleBody(rule, source, path) {
678
+ const heading = rule.heading;
679
+ if (typeof heading === "object" && !Array.isArray(heading)) {
680
+ checkPattern(heading.pattern, source);
681
+ }
682
+ if (rule.contains) checkBlockRule(rule.contains, source, `${path}.contains`);
683
+ rule.sequence?.forEach((entry, index) => {
684
+ checkBlockRule(entry, source, `${path}.sequence[${index}]`);
685
+ });
686
+ checkRuleList(rule.repeat, source, `${path}.repeat`);
687
+ checkRuleList(rule.sections, source, `${path}.sections`);
688
+ }
689
+ function checkRuleList(rules, source, path) {
690
+ if (!rules) return;
691
+ const ids = /* @__PURE__ */ new Set();
692
+ for (const [index, rule] of rules.entries()) {
693
+ const here = `${path}[${index}]`;
694
+ if (rule.id !== void 0) {
695
+ if (ids.has(rule.id)) {
696
+ throw new LintError(`${source}: two sibling rules share the id "${rule.id}".`);
697
+ }
698
+ ids.add(rule.id);
699
+ }
700
+ checkRange(rule, source, ruleLabel(rule, here));
701
+ checkRuleBody(rule, source, here);
702
+ }
703
+ for (const [index, rule] of rules.entries()) {
704
+ const next = rules[index + 1];
705
+ if (!next) break;
706
+ if (isWildcard(rule) && rule.repeat === void 0 && isWildcard(next) && next.repeat === void 0 && sameOccurrenceRange(rule, next)) {
707
+ warn(
708
+ `${source}: two adjacent rules have no heading and no repeat; only rule order tells them apart.`
709
+ );
710
+ break;
711
+ }
712
+ }
713
+ }
714
+ function checkTemplateFile(file, source) {
715
+ for (const [name, template] of Object.entries(file.templates ?? {})) {
716
+ if (template.min !== void 0 || template.max !== void 0) {
717
+ throw new LintError(
718
+ `${source}: a template may not set min or max; a page is one page.`
719
+ );
720
+ }
721
+ checkRuleBody(template, source, `templates.${name}`);
722
+ }
723
+ }
724
+ var NOT_CONSTRAINTS = {
725
+ oneOfSequenceOrContains: "sets both sequence and contains; a rule says what it holds in order, or in any order, never both",
726
+ noHeadingOnRepeat: "sets both heading and repeat; a repeat group has no heading of its own, so put the heading on the first rule inside it",
727
+ noInstructions: "carries an instructions key, which this tool does not read"
728
+ };
729
+ function describeError(error) {
730
+ const where = error.instancePath || "/";
731
+ if (error.keyword === "not") {
732
+ for (const [name, why] of Object.entries(NOT_CONSTRAINTS)) {
733
+ if (error.schemaPath.includes(name)) return `${where} ${why}`;
734
+ }
735
+ }
736
+ const params = error.params;
737
+ const extra = typeof params.additionalProperty === "string" ? ` ("${params.additionalProperty}")` : "";
738
+ return `${where} ${error.message ?? "is invalid"}${extra}`;
739
+ }
740
+ function validateTemplateFile(data, source) {
741
+ const instructions = findInstructions(data);
742
+ if (instructions) throw new LintError(instructionsMessage(source, instructions));
743
+ const v1 = findV1File(data);
744
+ if (v1) throw new LintError(`${source}: ${v1}`);
745
+ const validate = fileValidator();
746
+ if (!validate(data)) {
747
+ const errors = validate.errors ?? [];
748
+ const detail = errors.map(describeError).join("; ") || "does not match the template schema";
749
+ throw new LintError(`${source} is not a valid template file: ${detail}.`);
750
+ }
751
+ const file = data;
752
+ checkTemplateFile(file, source);
753
+ return file;
754
+ }
755
+ function splitFragment(ref) {
756
+ const hash = ref.lastIndexOf("#");
757
+ if (hash === -1) return { base: ref, fragment: null };
758
+ return { base: ref.slice(0, hash), fragment: ref.slice(hash + 1) || null };
759
+ }
760
+ function extensionSource(ref, kind) {
761
+ if (kind !== "url") return ref;
762
+ try {
763
+ return new URL(ref).pathname;
764
+ } catch {
765
+ return ref;
766
+ }
767
+ }
768
+ function parseTemplateFile(raw, source, kind) {
769
+ const lower = extensionSource(source, kind).toLowerCase();
770
+ if (lower.endsWith(".json")) {
771
+ try {
772
+ return JSON.parse(raw);
773
+ } catch (err) {
774
+ throw new LintError(`${source} is not valid JSON: ${errorMessage(err)}`);
775
+ }
776
+ }
777
+ try {
778
+ return parseYaml(raw);
779
+ } catch (err) {
780
+ throw new LintError(`${source} is not valid YAML: ${errorMessage(err)}`);
781
+ }
782
+ }
783
+ async function readText(ref) {
784
+ try {
785
+ return await readFile(ref, "utf8");
786
+ } catch (err) {
787
+ const code = err?.code;
788
+ if (code === "ENOENT") {
789
+ throw new LintError(`Template file not found: "${ref}".`);
790
+ }
791
+ throw new LintError(
792
+ `Template file "${ref}" could not be read: ${errorMessage(err)}`
793
+ );
794
+ }
795
+ }
796
+ var urlCache = /* @__PURE__ */ new Map();
797
+ function clearTemplateCaches() {
798
+ builtinCache.clear();
799
+ urlCache.clear();
800
+ }
801
+ async function fetchText(ref, timeoutMs) {
802
+ let res;
803
+ try {
804
+ res = await fetch(ref, { signal: AbortSignal.timeout(timeoutMs) });
805
+ } catch (err) {
806
+ const e = err;
807
+ if (e.name === "TimeoutError" || e.name === "AbortError") {
808
+ throw new LintError(
809
+ `Failed to fetch template file "${ref}": timed out after ${timeoutMs}ms.`
810
+ );
811
+ }
812
+ throw new LintError(`Failed to fetch template file "${ref}": ${e.message}`);
813
+ }
814
+ if (!res.ok) {
815
+ throw new LintError(`Failed to fetch template file "${ref}": HTTP ${res.status}.`);
816
+ }
817
+ return await res.text();
818
+ }
819
+ async function dereferenceTemplates(data, source) {
820
+ try {
821
+ return await dereference(data, DEREFERENCE_OPTIONS);
822
+ } catch (err) {
823
+ throw new LintError(`${source}: could not resolve a "$ref": ${errorMessage(err)}`);
824
+ }
825
+ }
826
+ async function loadTemplateFile(ref, options = {}) {
827
+ const { kind } = classifyRef(ref);
828
+ if (kind === "builtin") {
829
+ throw new LintError(
830
+ `"${ref}" is a built-in template id, not a template file. Load it with loadTemplate().`
831
+ );
832
+ }
833
+ const cached = kind === "url" ? urlCache.get(ref) : void 0;
834
+ if (cached) return cached;
835
+ const raw = kind === "url" ? await fetchText(ref, options.timeoutMs ?? DEFAULT_TIMEOUT_MS) : await readText(ref);
836
+ const parsed = parseTemplateFile(raw, ref, kind);
837
+ if (!isRecord(parsed)) {
838
+ throw new LintError(
839
+ `${ref} is not a template file: expected an object at the top level, got ${parsed === null ? "null" : Array.isArray(parsed) ? "an array" : typeof parsed}.`
840
+ );
841
+ }
842
+ const file = validateTemplateFile(await dereferenceTemplates(parsed, ref), ref);
843
+ if (kind === "url") urlCache.set(ref, file);
844
+ return file;
845
+ }
846
+ async function loadTemplate(ref, options = {}) {
847
+ const { base, fragment } = splitFragment(ref);
848
+ const { kind } = classifyRef(base);
849
+ if (kind === "builtin") {
850
+ if (fragment !== null) {
851
+ throw new LintError(
852
+ `"${ref}" adds a "#${fragment}" fragment to a built-in template id. A built-in id names one template on its own - drop the fragment and use "${base}". Fragments name a template inside a file, as in "./templates.yaml#how-to".`
853
+ );
854
+ }
855
+ if (!BUILTINS.has(base)) {
856
+ const available = [...BUILTINS.keys()].join(", ");
857
+ throw new LintError(
858
+ `Unknown built-in template "${base}". Available: ${available || "(none)"}.`
859
+ );
860
+ }
861
+ return loadBuiltin(base);
862
+ }
863
+ const file = await loadTemplateFile(base, options);
864
+ const templates = file.templates ?? {};
865
+ const names = Object.keys(templates);
866
+ if (fragment !== null) {
867
+ const named = Object.hasOwn(templates, fragment) ? templates[fragment] : void 0;
868
+ if (named === void 0) {
869
+ throw new LintError(
870
+ `${base} has no template named "${fragment}". Available: ${names.join(", ") || "(none)"}.`
871
+ );
872
+ }
873
+ return named;
874
+ }
875
+ const [only] = Object.values(templates);
876
+ if (names.length === 1 && only !== void 0) return only;
877
+ if (names.length === 0) throw new LintError(`${base} defines no templates.`);
878
+ throw new LintError(
879
+ `${base} defines ${names.length} templates; name one with a "#" fragment (e.g. "${base}#${names[0] ?? ""}"). Available: ${names.join(", ")}.`
880
+ );
881
+ }
882
+ function mergeRuleLists(parent, child) {
883
+ if (!parent) return child;
884
+ if (!child) return parent;
885
+ const merged = [...parent];
886
+ for (const childRule of child) {
887
+ const id = childRule.id;
888
+ const index = id === void 0 ? -1 : merged.findIndex((rule) => rule.id === id);
889
+ const target = index === -1 ? void 0 : merged[index];
890
+ if (target === void 0) merged.push(childRule);
891
+ else merged[index] = mergeRules(target, childRule);
892
+ }
893
+ return merged;
894
+ }
895
+ function mergeRules(parent, child) {
896
+ const sections = mergeRuleLists(parent.sections, child.sections);
897
+ const { sections: _parentSections, ...parentRest } = parent;
898
+ const { sections: _childSections, ...childRest } = child;
899
+ const merged = { ...parentRest, ...childRest };
900
+ if (sections !== void 0) merged.sections = sections;
901
+ return merged;
902
+ }
903
+ function mergeTemplates(parent, child) {
904
+ const sections = mergeRuleLists(parent.sections, child.sections);
905
+ const { sections: _parentSections, ...parentRest } = parent;
906
+ const { sections: _childSections, ...childRest } = child;
907
+ const merged = { ...parentRest, ...childRest };
908
+ if (sections !== void 0) merged.sections = sections;
909
+ delete merged.extends;
910
+ return merged;
911
+ }
912
+ async function resolveExtends(template, load = loadTemplate, chain = []) {
913
+ const parentRef = template.extends;
914
+ if (parentRef === void 0) return template;
915
+ if (chain.includes(parentRef)) {
916
+ throw new LintError(
917
+ `Template "extends" cycle: ${[...chain, parentRef].join(" -> ")}.`
918
+ );
919
+ }
920
+ const parent = await resolveExtends(await load(parentRef), load, [...chain, parentRef]);
921
+ return mergeTemplates(parent, template);
922
+ }
923
+ function refRelativeTo(baseRef, ref) {
924
+ const { base, fragment } = splitFragment(ref);
925
+ if (classifyRef(base).kind !== "file" || isAbsolute(base)) return ref;
926
+ const { base: fromBase } = splitFragment(baseRef);
927
+ const from = classifyRef(fromBase).kind;
928
+ if (from === "url") {
929
+ const rebased2 = new URL(base, fromBase).href;
930
+ return fragment === null ? rebased2 : `${rebased2}#${fragment}`;
931
+ }
932
+ if (from !== "file") return ref;
933
+ const rebased = resolvePath(dirname(fromBase), base);
934
+ return fragment === null ? rebased : `${rebased}#${fragment}`;
935
+ }
936
+ async function loadResolvedTemplate(ref, options = {}, chain = []) {
937
+ const seen = chain.length === 0 ? [ref] : chain;
938
+ const template = await loadTemplate(ref, options);
939
+ const parentRef = template.extends;
940
+ if (parentRef === void 0) return template;
941
+ const absolute = refRelativeTo(ref, parentRef);
942
+ if (seen.includes(absolute)) {
943
+ throw new LintError(
944
+ `Template "extends" cycle: ${[...seen, absolute].join(" -> ")}.`
945
+ );
946
+ }
947
+ const parent = await loadResolvedTemplate(absolute, options, [
948
+ ...seen,
949
+ absolute
950
+ ]);
951
+ return mergeTemplates(parent, template);
952
+ }
953
+
954
+ // src/lint/core/resolve-template.ts
955
+ import picomatch from "picomatch";
956
+ var FILE_TEMPLATE_KEY = "$template";
957
+ var TYPE_KEY = "type";
958
+ function buildTypeIndex(params) {
959
+ const index = /* @__PURE__ */ new Map();
960
+ for (const builtin of params.builtins ?? []) {
961
+ for (const type of builtin.types) {
962
+ index.set(type, { ref: builtin.id, source: "builtin" });
963
+ }
964
+ }
965
+ for (const { ref, file } of params.userFiles ?? []) {
966
+ for (const [name, template] of Object.entries(file.templates ?? {})) {
967
+ for (const type of template.types ?? []) {
968
+ index.set(type, { ref: `${ref}#${name}`, source: "user" });
969
+ }
970
+ }
971
+ }
972
+ for (const [type, ref] of Object.entries(params.explicitTypes ?? {})) {
973
+ index.set(type, { ref, source: "config" });
974
+ }
975
+ return index;
976
+ }
977
+ var matcherCache = /* @__PURE__ */ new Map();
978
+ function clearMatcherCache() {
979
+ matcherCache.clear();
980
+ }
981
+ function matchesGlob(glob, filePath) {
982
+ let m = matcherCache.get(glob);
983
+ if (!m) {
984
+ m = picomatch(glob, { dot: true });
985
+ matcherCache.set(glob, m);
986
+ }
987
+ return m(filePath.replace(/\\/g, "/"));
988
+ }
989
+ function suggestTypes(unknown, known) {
990
+ const target = unknown.toLowerCase();
991
+ const scored = [];
992
+ for (const type of known) {
993
+ const distance = editDistance(target, type.toLowerCase());
994
+ const threshold = target.length <= 4 ? 1 : 3;
995
+ if (distance <= threshold) scored.push({ type, distance });
996
+ }
997
+ return scored.sort((a, b) => a.distance - b.distance || a.type.localeCompare(b.type)).slice(0, 3).map((s) => s.type);
998
+ }
999
+ function editDistance(a, b) {
1000
+ if (a === b) return 0;
1001
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
1002
+ let last = b.length;
1003
+ for (let i = 0; i < a.length; i++) {
1004
+ const row = [i + 1];
1005
+ let left = i + 1;
1006
+ let diagonal = i;
1007
+ for (const [j, above] of prev.slice(1).entries()) {
1008
+ const cost = a.charCodeAt(i) === b.charCodeAt(j) ? 0 : 1;
1009
+ const cell = Math.min(left + 1, above + 1, diagonal + cost);
1010
+ row.push(cell);
1011
+ diagonal = above;
1012
+ left = cell;
1013
+ }
1014
+ prev = row;
1015
+ last = left;
1016
+ }
1017
+ return last;
1018
+ }
1019
+ function refFromFrontmatter(frontmatter, key) {
1020
+ const value = frontmatter?.[key];
1021
+ return typeof value === "string" && value.length > 0 ? value : null;
1022
+ }
1023
+ function resolveTemplateRef(params) {
1024
+ const steps = [];
1025
+ const typeIndex = params.typeIndex ?? /* @__PURE__ */ new Map();
1026
+ const decide = (stage, ref) => ({
1027
+ ref,
1028
+ stage,
1029
+ steps
1030
+ });
1031
+ if (params.cliTemplate) {
1032
+ steps.push({ stage: "cli", ref: params.cliTemplate, detail: "--template" });
1033
+ return decide("cli", params.cliTemplate);
1034
+ }
1035
+ steps.push({ stage: "cli", ref: null, detail: "--template not given" });
1036
+ const fileTemplate = refFromFrontmatter(params.frontmatter, FILE_TEMPLATE_KEY);
1037
+ if (fileTemplate) {
1038
+ steps.push({
1039
+ stage: "frontmatter-template",
1040
+ ref: fileTemplate,
1041
+ detail: `${FILE_TEMPLATE_KEY} in frontmatter`
1042
+ });
1043
+ return decide("frontmatter-template", fileTemplate);
1044
+ }
1045
+ steps.push({
1046
+ stage: "frontmatter-template",
1047
+ ref: null,
1048
+ detail: `no ${FILE_TEMPLATE_KEY} in frontmatter`
1049
+ });
1050
+ for (const override of params.overrides ?? []) {
1051
+ if (matchesGlob(override.files, params.filePath) || params.absolutePath !== void 0 && matchesGlob(override.files, params.absolutePath)) {
1052
+ steps.push({
1053
+ stage: "config-override",
1054
+ ref: override.template,
1055
+ detail: `matched override "${override.files}"`
1056
+ });
1057
+ return decide("config-override", override.template);
1058
+ }
1059
+ }
1060
+ steps.push({
1061
+ stage: "config-override",
1062
+ ref: null,
1063
+ detail: (params.overrides ?? []).length ? "no override glob matched" : "no overrides configured"
1064
+ });
1065
+ const type = refFromFrontmatter(params.frontmatter, TYPE_KEY);
1066
+ if (type) {
1067
+ const entry = typeIndex.get(type);
1068
+ if (entry) {
1069
+ steps.push({
1070
+ stage: "type",
1071
+ ref: entry.ref,
1072
+ detail: `type: ${type} -> ${entry.source} template`
1073
+ });
1074
+ return decide("type", entry.ref);
1075
+ }
1076
+ steps.push({
1077
+ stage: "type",
1078
+ ref: null,
1079
+ detail: `type: ${type} matches no template`
1080
+ });
1081
+ return {
1082
+ ref: null,
1083
+ stage: null,
1084
+ cause: "unknown-type",
1085
+ unknownType: type,
1086
+ suggestions: suggestTypes(type, typeIndex.keys()),
1087
+ knownTypes: knownTypes(typeIndex),
1088
+ steps
1089
+ };
1090
+ }
1091
+ steps.push({ stage: "type", ref: null, detail: "no type in frontmatter" });
1092
+ if (params.defaultTemplate) {
1093
+ steps.push({
1094
+ stage: "config-default",
1095
+ ref: params.defaultTemplate,
1096
+ detail: "config default"
1097
+ });
1098
+ return decide("config-default", params.defaultTemplate);
1099
+ }
1100
+ steps.push({ stage: "config-default", ref: null, detail: "no default configured" });
1101
+ return { ref: null, stage: null, cause: "no-type", steps };
1102
+ }
1103
+ function knownTypes(index) {
1104
+ return [...index.keys()].sort();
1105
+ }
1106
+
1107
+ // src/lint/core/validator.ts
1108
+ function origin() {
1109
+ return {
1110
+ start: { line: 1, column: 1, offset: 0 },
1111
+ end: { line: 1, column: 1, offset: 0 }
1112
+ };
1113
+ }
1114
+ function span(sections) {
1115
+ const first = sections[0];
1116
+ const last = sections[sections.length - 1];
1117
+ if (!first || !last) return origin();
1118
+ return { start: { ...first.position.start }, end: { ...last.position.end } };
1119
+ }
1120
+ function pageSection(tree) {
1121
+ const [only] = tree.sections;
1122
+ if (tree.sections.length === 1 && only && only.level <= 1) return only;
1123
+ return {
1124
+ slug: "",
1125
+ title: "",
1126
+ level: 0,
1127
+ order: 0,
1128
+ parentSlug: null,
1129
+ titlePosition: null,
1130
+ position: span(tree.sections),
1131
+ children: [],
1132
+ sections: tree.sections
1133
+ };
1134
+ }
1135
+ var BLOCK_NOUNS = {
1136
+ paragraphs: "paragraphs",
1137
+ codeBlocks: "code blocks",
1138
+ lists: "lists",
1139
+ tables: "tables",
1140
+ admonitions: "admonitions",
1141
+ images: "images",
1142
+ blockquotes: "blockquotes",
1143
+ definitionLists: "definition lists",
1144
+ elements: "elements"
1145
+ };
1146
+ var Pruner = class {
1147
+ emitted;
1148
+ /** Keyed by message, so one rule asked twice is reported once. */
1149
+ missing = /* @__PURE__ */ new Map();
1150
+ constructor(kinds) {
1151
+ this.emitted = new Set(kinds);
1152
+ }
1153
+ get gaps() {
1154
+ return [...this.missing.values()];
1155
+ }
1156
+ emits(kind) {
1157
+ return this.emitted.has(kind);
1158
+ }
1159
+ drop(rule, noun) {
1160
+ this.missing.set(`${rule}: ${noun}`, { rule, noun });
1161
+ }
1162
+ /** A table rule without its `columns`, when the parser reports no cells. */
1163
+ tables(rule) {
1164
+ if (rule.columns === void 0 || this.emits("tableCell")) return rule;
1165
+ this.drop("columns", "table columns");
1166
+ const { columns: _columns, ...kept } = rule;
1167
+ return kept;
1168
+ }
1169
+ /**
1170
+ * A list rule with its `items` pruned.
1171
+ *
1172
+ * `items` has no capability of its own to check. A parser that reports a
1173
+ * list reports its items, since `ListNode.items` is not optional, and
1174
+ * `listItem` is not one of the `BLOCK_KINDS` a block rule can target. So
1175
+ * the `lists` gap covers the whole rule, and only what the items *hold* can
1176
+ * go unchecked.
1177
+ *
1178
+ * `listItem` is a `ContentKind` and parsers do declare it. That is a fact
1179
+ * about the node model rather than about this pruner: a template names
1180
+ * `lists`, and reaches an item only through `items`, so there is no rule
1181
+ * whose capability would be `listItem` on its own.
1182
+ */
1183
+ lists(rule) {
1184
+ const items = rule.items;
1185
+ if (items === void 0) return rule;
1186
+ const pruned = this.listItems(items);
1187
+ return pruned === items ? rule : { ...rule, items: pruned };
1188
+ }
1189
+ listItems(rule) {
1190
+ return this.holder(rule);
1191
+ }
1192
+ elements(rule) {
1193
+ return this.holder(rule);
1194
+ }
1195
+ /** The `sequence`/`contains` pair every content holder carries. */
1196
+ holder(rule) {
1197
+ const kept = { ...rule };
1198
+ let changed = false;
1199
+ if (rule.sequence) {
1200
+ const sequence = this.sequence(rule.sequence);
1201
+ if (sequence === void 0) {
1202
+ delete kept.sequence;
1203
+ changed = true;
1204
+ } else if (sequence !== rule.sequence) {
1205
+ kept.sequence = sequence;
1206
+ changed = true;
1207
+ }
1208
+ }
1209
+ if (rule.contains) {
1210
+ const contains = this.block(rule.contains);
1211
+ if (contains === void 0) {
1212
+ delete kept.contains;
1213
+ changed = true;
1214
+ } else if (contains !== rule.contains) {
1215
+ kept.contains = contains;
1216
+ changed = true;
1217
+ }
1218
+ }
1219
+ return changed ? kept : rule;
1220
+ }
1221
+ /**
1222
+ * One `sequence`, with the runs this format cannot see taken out.
1223
+ *
1224
+ * Taking the entry out rather than leaving it empty is what keeps the rest of
1225
+ * the sequence meaningful: a run of a kind the parser never emits can never
1226
+ * appear among the actual runs, so an entry left standing would report an
1227
+ * order error on every file of that format. `undefined` means the key goes.
1228
+ */
1229
+ sequence(entries) {
1230
+ const kept = [];
1231
+ let changed = false;
1232
+ for (const entry of entries) {
1233
+ const pruned = this.block(entry);
1234
+ if (pruned === void 0) {
1235
+ changed = true;
1236
+ continue;
1237
+ }
1238
+ if (pruned !== entry) changed = true;
1239
+ kept.push(pruned);
1240
+ }
1241
+ if (!changed) return entries;
1242
+ return kept.length > 0 ? kept : void 0;
1243
+ }
1244
+ /** One block rule, or `undefined` when nothing in it can be checked. */
1245
+ block(block) {
1246
+ const kept = { ...block };
1247
+ let changed = false;
1248
+ for (const key of BLOCK_KINDS) {
1249
+ if (kept[key] === void 0) continue;
1250
+ if (this.emits(BLOCK_KIND_NODE[key])) continue;
1251
+ this.drop(key, BLOCK_NOUNS[key]);
1252
+ Object.assign(kept, { [key]: void 0 });
1253
+ changed = true;
1254
+ }
1255
+ const tables = kept.tables;
1256
+ if (tables) {
1257
+ const pruned = this.tables(tables);
1258
+ if (pruned !== tables) {
1259
+ kept.tables = pruned;
1260
+ changed = true;
1261
+ }
1262
+ }
1263
+ const lists = kept.lists;
1264
+ if (lists) {
1265
+ const pruned = this.lists(lists);
1266
+ if (pruned !== lists) {
1267
+ kept.lists = pruned;
1268
+ changed = true;
1269
+ }
1270
+ }
1271
+ const elements = kept.elements;
1272
+ if (elements) {
1273
+ const pruned = this.elements(elements);
1274
+ if (pruned !== elements) {
1275
+ kept.elements = pruned;
1276
+ changed = true;
1277
+ }
1278
+ }
1279
+ if (!changed) return block;
1280
+ return BLOCK_KINDS.some((key) => kept[key] !== void 0) ? kept : void 0;
1281
+ }
1282
+ rules(rules) {
1283
+ const kept = [];
1284
+ let changed = false;
1285
+ for (const rule of rules) {
1286
+ const pruned = this.rule(rule);
1287
+ if (pruned !== rule) changed = true;
1288
+ kept.push(pruned);
1289
+ }
1290
+ return changed ? kept : rules;
1291
+ }
1292
+ rule(rule) {
1293
+ const kept = this.holder(rule);
1294
+ let changed = kept !== rule;
1295
+ const out = changed ? kept : { ...rule };
1296
+ if (rule.repeat) {
1297
+ const repeat = this.rules(rule.repeat);
1298
+ if (repeat !== rule.repeat) {
1299
+ out.repeat = repeat;
1300
+ changed = true;
1301
+ }
1302
+ }
1303
+ if (rule.sections) {
1304
+ const sections = this.rules(rule.sections);
1305
+ if (sections !== rule.sections) {
1306
+ out.sections = sections;
1307
+ changed = true;
1308
+ }
1309
+ }
1310
+ return changed ? out : rule;
1311
+ }
1312
+ };
1313
+ var pruneCache = /* @__PURE__ */ new WeakMap();
1314
+ function prune(template, kinds) {
1315
+ const key = [...kinds].sort().join(",");
1316
+ let byKinds = pruneCache.get(template);
1317
+ if (!byKinds) {
1318
+ byKinds = /* @__PURE__ */ new Map();
1319
+ pruneCache.set(template, byKinds);
1320
+ }
1321
+ const hit = byKinds.get(key);
1322
+ if (hit) return hit;
1323
+ const pruner = new Pruner(kinds);
1324
+ const pruned = { template: pruner.rule(template), gaps: pruner.gaps };
1325
+ byKinds.set(key, pruned);
1326
+ return pruned;
1327
+ }
1328
+ function uncheckedFinding(format, name, gap) {
1329
+ return {
1330
+ type: "unsupported_content_kind",
1331
+ heading: null,
1332
+ message: `The ${format} parser does not report ${gap.noun}, so the "${gap.rule}" rule in template "${name}" is not checked for this file.`,
1333
+ position: origin(),
1334
+ // A warning, because nothing about the document is wrong: the rule asks
1335
+ // about something this format cannot express. It never changes the exit
1336
+ // code - `runLint` counts a file as failed only on an error-severity
1337
+ // finding - so a docset of mixed formats stays green while still saying
1338
+ // which rules went unchecked, and where.
1339
+ severity: "warning"
1340
+ };
1341
+ }
1342
+ function checkSection(section, rule) {
1343
+ return [
1344
+ ...checkHeading(section, rule.heading),
1345
+ ...checkSequence(section, rule.sequence),
1346
+ ...checkContains(section, rule.contains)
1347
+ ];
1348
+ }
1349
+ function validateSections(sections, rules, options = {}) {
1350
+ const { matches, findings } = matchSections(sections, rules, options);
1351
+ const all = [...findings];
1352
+ for (const match of matches) {
1353
+ all.push(...checkSection(match.section, match.rule));
1354
+ all.push(
1355
+ ...validateSections(match.section.sections, match.rule.sections, {
1356
+ ...options,
1357
+ parent: match.section
1358
+ })
1359
+ );
1360
+ }
1361
+ return all;
1362
+ }
1363
+ function validateDocument(tree, template, options = {}) {
1364
+ const kinds = options.kinds ?? parserByName(tree.format)?.kinds ?? [];
1365
+ const pruned = prune(template, kinds);
1366
+ const rules = pruned.template;
1367
+ const name = options.template ?? template.title ?? "unnamed";
1368
+ const findings = pruned.gaps.map(
1369
+ (gap) => uncheckedFinding(tree.format, name, gap)
1370
+ );
1371
+ const page = pageSection(tree);
1372
+ const ctx = {
1373
+ heading: page.title || null,
1374
+ position: page.position
1375
+ };
1376
+ findings.push(...checkHeading(page, rules.heading));
1377
+ findings.push(...checkSequenceIn(page.children, rules.sequence, ctx));
1378
+ findings.push(...checkContainsIn(page.children, rules.contains, ctx));
1379
+ findings.push(
1380
+ ...validateSections(page.sections, rules.sections, {
1381
+ parent: page,
1382
+ source: options.source,
1383
+ template: options.template
1384
+ })
1385
+ );
1386
+ return findings.sort(
1387
+ (a, b) => a.position.start.offset - b.position.start.offset
1388
+ );
1389
+ }
1390
+
1391
+ // src/lint/core/config.ts
1392
+ import { dirname as dirname2, isAbsolute as isAbsolute2, resolve } from "node:path";
1393
+ import { parse as parseYaml2 } from "yaml";
1394
+ import * as AjvNs2 from "ajv";
1395
+
1396
+ // schemas/lint/config.json
1397
+ var config_default = {
1398
+ $schema: "http://json-schema.org/draft-07/schema#",
1399
+ $id: "manni-lint:config:1",
1400
+ title: "manni lint config section",
1401
+ description: "The `lint:` section of a shared `manni.config.yaml`. The file's root is a mapping of tool name to that tool's settings, so this schema describes the section only - never the file. The document set is not here: proposal 0041 declares it once for every tool, under the family-level `collections:` key, and `paths`/`exclude` under `lint:` are refused by name. Every key is optional: a repo that configures no manni lint at all is a legitimate config, and so is one that carries only a sibling tool's section. `additionalProperties: false` applies at every level below `lint:`, which is what turns a typo'd key into a loud failure instead of a silent default.",
1402
+ type: "object",
1403
+ additionalProperties: false,
1404
+ properties: {
1405
+ allowEmpty: {
1406
+ description: "Treat a run that matched zero files as a success rather than an operational error. The same key, with the same meaning, as `cite.allowEmpty`: for a shared CI template or a pre-commit hook whose file list may legitimately be empty. `--allow-empty` on the command line sets it for one run.",
1407
+ type: "boolean"
1408
+ },
1409
+ overrides: {
1410
+ description: "Repo policy: everything under a glob is linted with one template, whatever the pages say they are. First matching entry wins, and an override outranks a page's own `type`.",
1411
+ type: "array",
1412
+ items: {
1413
+ description: "One policy entry. Both keys are required: an entry naming no template, or no files, is a line that silently does nothing.",
1414
+ type: "object",
1415
+ additionalProperties: false,
1416
+ required: ["files", "template"],
1417
+ properties: {
1418
+ files: {
1419
+ description: "Glob matched against each file's path, written with forward slashes whatever the platform uses.",
1420
+ type: "string",
1421
+ minLength: 1
1422
+ },
1423
+ template: {
1424
+ description: "Template ref applied to the files this entry matches.",
1425
+ type: "string",
1426
+ minLength: 1
1427
+ }
1428
+ }
1429
+ }
1430
+ },
1431
+ structure: {
1432
+ description: "The structure job, which `manni lint structure` and `manni lint check` both run. Declaring it with no keys is how a repo says the job applies. Its one setting is the tool that performs it.",
1433
+ type: "object",
1434
+ additionalProperties: false,
1435
+ properties: {
1436
+ tool: {
1437
+ description: "Which tool performs the job. `manni` is manni's own template-structure engine; `dita-ot` is DITA Open Toolkit, which validates a DITA map and the topics it reaches and is located through the top-level `tools.dita-ot.home`. `--tool <name>` on the command line names the same thing.",
1438
+ type: "string",
1439
+ enum: ["manni", "dita-ot"]
1440
+ }
1441
+ }
1442
+ },
1443
+ template: {
1444
+ description: "Template ref applied to a page that declares no doctype and matches no override. The last stage of the resolution chain, not an override: `--template`, a page's `$template`, an `overrides` entry, and the page's own `type` all outrank it.",
1445
+ type: "string",
1446
+ minLength: 1
1447
+ },
1448
+ templates: {
1449
+ description: "Template files to load. Their templates' `types:` join the doctype routing table, outranking the built-ins, so adopting a repo's own how-to shape is one entry here and no flag.",
1450
+ type: "array",
1451
+ items: {
1452
+ type: "string",
1453
+ minLength: 1
1454
+ }
1455
+ },
1456
+ types: {
1457
+ description: "Explicit doctype -> template ref map, for a doctype no template file claims with its own `types:`. Keys are the vocabulary a page's `type` frontmatter is written in, so they are the author's names rather than this schema's - constrained only to the shape a doctype slug takes, not to a fixed set.",
1458
+ type: "object",
1459
+ additionalProperties: false,
1460
+ patternProperties: {
1461
+ "^[A-Za-z0-9._:-]+$": {
1462
+ description: "A template ref: a built-in id, a path, or a URL, optionally with a `#name` fragment.",
1463
+ type: "string",
1464
+ minLength: 1
1465
+ }
1466
+ }
1467
+ }
1468
+ }
1469
+ };
1470
+
1471
+ // src/lint/core/config.ts
1472
+ var Ajv2 = AjvNs2.default;
1473
+ var LINT_TOOLS = ["manni", "dita-ot"];
1474
+ var LINT_JOBS = ["structure"];
1475
+ var TOOLS_BY_JOB = {
1476
+ structure: ["manni", "dita-ot"]
1477
+ };
1478
+ function isLintTool(value) {
1479
+ return LINT_TOOLS.includes(value);
1480
+ }
1481
+ var SECTION_KEY = "lint";
1482
+ var CONFIG_FILE = {
1483
+ section: SECTION_KEY,
1484
+ legacyNames: [],
1485
+ toError: (message) => new LintError(message)
1486
+ };
1487
+ var SECTION_KEYS = Object.keys(config_default.properties);
1488
+ var STRUCTURE_KEYS = Object.keys(
1489
+ config_default.properties.structure.properties
1490
+ );
1491
+ var CONFIG_REF = "https://hawkeyexl.github.io/manni/meta/reference/configuration/";
1492
+ var MOVED_KEYS = ["paths", "exclude"];
1493
+ function assertNoMovedKeys(raw, source) {
1494
+ for (const key of MOVED_KEYS) {
1495
+ if (Object.hasOwn(raw, key)) {
1496
+ throw new LintError(
1497
+ `${source}: "${key}" is no longer a ${SECTION_KEY} key. Document sets are declared once for every tool, under a top-level collections: list. See ${CONFIG_REF}#collections`
1498
+ );
1499
+ }
1500
+ }
1501
+ }
1502
+ function rejectUnknownKeys(obj, allowed, where, source) {
1503
+ for (const key of Object.keys(obj)) {
1504
+ if (!allowed.includes(key)) {
1505
+ throw new LintError(
1506
+ `Unknown key "${key}" under ${where}: in ${source}. Supported keys: ${allowed.join(", ")}.`
1507
+ );
1508
+ }
1509
+ }
1510
+ }
1511
+ function checkJobs(section, source) {
1512
+ for (const job of LINT_JOBS) {
1513
+ const value = section[job];
1514
+ if (value == null || !isRecord2(value)) continue;
1515
+ rejectUnknownKeys(value, STRUCTURE_KEYS, `${SECTION_KEY}.${job}`, source);
1516
+ const tool = value.tool;
1517
+ if (tool === void 0) continue;
1518
+ if (typeof tool !== "string" || !TOOLS_BY_JOB[job].includes(tool)) {
1519
+ throw new LintError(
1520
+ `${source}: ${SECTION_KEY}.${job}.tool must be one of: ${TOOLS_BY_JOB[job].join(", ")}.`
1521
+ );
1522
+ }
1523
+ }
1524
+ }
1525
+ var ajv2 = new Ajv2({ allErrors: true });
1526
+ var compiled2 = null;
1527
+ function sectionValidator() {
1528
+ compiled2 ??= ajv2.compile(config_default);
1529
+ return compiled2;
1530
+ }
1531
+ function isRecord2(value) {
1532
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1533
+ }
1534
+ function describeError2(error) {
1535
+ const params = error.params;
1536
+ const extra = typeof params.additionalProperty === "string" ? ` ("${params.additionalProperty}")` : "";
1537
+ return `${SECTION_KEY}${error.instancePath} ${error.message ?? "is invalid"}${extra}`;
1538
+ }
1539
+ function rejectUnNested(root, source) {
1540
+ const stray = SECTION_KEYS.filter((key) => key in root);
1541
+ if (stray.length === 0) return;
1542
+ throw new LintError(
1543
+ `${source}: found ${stray.map((key) => `"${key}:"`).join(", ")} at the top level, but no "${SECTION_KEY}:" key. This file is shared across the manni family, so every manni lint setting belongs under a top-level "${SECTION_KEY}:" key. Indent the file's contents one level and add that key.`
1544
+ );
1545
+ }
1546
+ function rejectMiscasedWrapper(root, source) {
1547
+ const miscased = Object.keys(root).find(
1548
+ (key) => key !== SECTION_KEY && key.toLowerCase() === SECTION_KEY
1549
+ );
1550
+ if (miscased === void 0) return;
1551
+ throw new LintError(
1552
+ `${source}: found "${miscased}:" at the top level, but the key is case-sensitive and manni lint reads "${SECTION_KEY}:" exactly. Rename "${miscased}:" to "${SECTION_KEY}:".`
1553
+ );
1554
+ }
1555
+ function parseConfigSection(section, source) {
1556
+ if (section == null) return {};
1557
+ if (isRecord2(section)) {
1558
+ assertNoMovedKeys(section, source);
1559
+ rejectUnknownKeys(section, SECTION_KEYS, SECTION_KEY, source);
1560
+ checkJobs(section, source);
1561
+ }
1562
+ const validate = sectionValidator();
1563
+ if (!validate(section)) {
1564
+ const detail = (validate.errors ?? []).map(describeError2).join("; ") || `does not match the ${SECTION_KEY} config schema`;
1565
+ throw new LintError(
1566
+ `${source}: invalid "${SECTION_KEY}" section: ${detail}.`
1567
+ );
1568
+ }
1569
+ return section;
1570
+ }
1571
+ function parseConfig(text, source) {
1572
+ let raw;
1573
+ try {
1574
+ raw = parseYaml2(text);
1575
+ } catch (err) {
1576
+ throw new LintError(
1577
+ `${source}: invalid YAML: ${errorMessage(err)}`
1578
+ );
1579
+ }
1580
+ if (raw == null) return {};
1581
+ if (!isRecord2(raw)) {
1582
+ throw new LintError(
1583
+ `${source}: top level must be a mapping of tool name to that tool's settings, with manni lint's under "${SECTION_KEY}:".`
1584
+ );
1585
+ }
1586
+ if (!(SECTION_KEY in raw)) {
1587
+ rejectUnNested(raw, source);
1588
+ rejectMiscasedWrapper(raw, source);
1589
+ return {};
1590
+ }
1591
+ return parseConfigSection(raw[SECTION_KEY], source);
1592
+ }
1593
+ async function loadConfig(explicitPath, cwd = process.cwd()) {
1594
+ const file = await readLintConfigFile(explicitPath, cwd);
1595
+ if (file === null) return null;
1596
+ return { config: sectionOf(file), path: file.path };
1597
+ }
1598
+ function readLintConfigFile(explicitPath, cwd) {
1599
+ return explicitPath ? readConfigFile(explicitPath, cwd, CONFIG_FILE) : findConfigFile(cwd, CONFIG_FILE);
1600
+ }
1601
+ function sectionOf(file) {
1602
+ if (!file.wrapped && isRecord2(file.value)) {
1603
+ rejectMiscasedWrapper(file.value, file.source);
1604
+ }
1605
+ return parseConfigSection(file.value, file.source);
1606
+ }
1607
+ function rebaseConfig(found) {
1608
+ if (!found) return {};
1609
+ const dir = dirname2(resolve(found.path));
1610
+ const config = found.config;
1611
+ const ref = (value) => refRelativeTo(found.path, value);
1612
+ const glob = (value) => isAbsolute2(value) ? value : resolve(dir, value).replace(/\\/g, "/");
1613
+ return {
1614
+ ...config,
1615
+ ...config.templates ? { templates: config.templates.map(ref) } : {},
1616
+ ...config.template ? { template: ref(config.template) } : {},
1617
+ ...config.types ? {
1618
+ types: Object.fromEntries(
1619
+ Object.entries(config.types).map(([type, value]) => [type, ref(value)])
1620
+ )
1621
+ } : {},
1622
+ ...config.overrides ? {
1623
+ overrides: config.overrides.map((o) => ({
1624
+ files: glob(o.files),
1625
+ template: ref(o.template)
1626
+ }))
1627
+ } : {}
1628
+ };
1629
+ }
1630
+ var STDIN = "-";
1631
+ async function resolveLintRun(opts) {
1632
+ const cwd = resolve(opts.cwd);
1633
+ const wanted = opts.collection ?? [];
1634
+ if (wanted.length > 0 && opts.inputs.some((input) => input !== STDIN)) {
1635
+ throw new LintError(
1636
+ "--collection selects a configured collection; it cannot be combined with paths."
1637
+ );
1638
+ }
1639
+ const file = opts.noConfig ? null : await readLintConfigFile(opts.configPath, cwd);
1640
+ if (wanted.length > 0 && file === null) {
1641
+ throw new LintError("--collection needs a config file to select from.");
1642
+ }
1643
+ if (file !== null) opts.onConfigLoaded?.({ path: file.path, dir: file.dir });
1644
+ const config = rebaseConfig(
1645
+ file === null ? null : { config: sectionOf(file), path: file.path }
1646
+ );
1647
+ const collections = file === null ? [] : selectCollections(
1648
+ file.collections,
1649
+ wanted,
1650
+ file.source,
1651
+ (message) => new LintError(message)
1652
+ );
1653
+ const fromCollections = wanted.length > 0 || opts.inputs.length === 0;
1654
+ const collectionPaths = fromCollections ? collections.flatMap((c) => c.paths) : [];
1655
+ const inputs = fromCollections ? [...opts.inputs, ...collectionPaths] : opts.inputs;
1656
+ const base = collectionPaths.length > 0 && file ? file.dir : cwd;
1657
+ return {
1658
+ config,
1659
+ inputs,
1660
+ base,
1661
+ collections,
1662
+ fromCollections,
1663
+ tools: file?.tools ?? {},
1664
+ ...file === null ? {} : { configDir: file.dir, configPath: file.path, configSource: file.source }
1665
+ };
1666
+ }
1667
+
1668
+ // src/lint/tools/dita-ot.ts
1669
+ import { execFile } from "node:child_process";
1670
+ import { mkdtemp, readFile as readFile2, rm, stat } from "node:fs/promises";
1671
+ import { tmpdir } from "node:os";
1672
+ import { delimiter, join as join2 } from "node:path";
1673
+ import { fileURLToPath } from "node:url";
1674
+ var MAX_BUFFER = 64 * 1024 * 1024;
1675
+ var NOT_ON_PATH = "dita is not on PATH. Install DITA Open Toolkit to lint structure, or set tools.dita-ot.home.";
1676
+ var noLauncher = (home) => `no DITA Open Toolkit at "${home}". tools.dita-ot.home must be a DITA-OT installation directory.`;
1677
+ function ditaOtLauncher(home, platform = process.platform) {
1678
+ const name = platform === "win32" ? "dita.bat" : "dita";
1679
+ return home === void 0 ? name : join2(home, "bin", name);
1680
+ }
1681
+ function isBatch(launcher) {
1682
+ const lower = launcher.toLowerCase();
1683
+ return lower.endsWith(".bat") || lower.endsWith(".cmd");
1684
+ }
1685
+ function quoteForCmd(value) {
1686
+ if (value.includes('"')) {
1687
+ throw new LintError(
1688
+ `DITA Open Toolkit cannot be given an argument containing a quote: ${value}`
1689
+ );
1690
+ }
1691
+ return `"${value}"`;
1692
+ }
1693
+ function ditaOtCommandLine(launcher, args) {
1694
+ if (!isBatch(launcher)) {
1695
+ return { command: launcher, argv: args, verbatim: false };
1696
+ }
1697
+ const line = [launcher, ...args].map(quoteForCmd).join(" ");
1698
+ return {
1699
+ command: process.env["COMSPEC"] ?? "cmd.exe",
1700
+ argv: ["/d", "/s", "/c", `"${line}"`],
1701
+ verbatim: true
1702
+ };
1703
+ }
1704
+ var spawnDitaOt = (launcher, args, opts) => new Promise((settle, reject) => {
1705
+ const line = ditaOtCommandLine(launcher, args);
1706
+ execFile(
1707
+ line.command,
1708
+ line.argv,
1709
+ {
1710
+ cwd: opts.cwd,
1711
+ windowsHide: true,
1712
+ windowsVerbatimArguments: line.verbatim,
1713
+ maxBuffer: MAX_BUFFER,
1714
+ encoding: "utf8"
1715
+ },
1716
+ (error, stdout, stderr) => {
1717
+ if (!error) {
1718
+ settle({ code: 0, stdout, stderr });
1719
+ return;
1720
+ }
1721
+ if (typeof error.code === "number") {
1722
+ settle({ code: error.code, stdout, stderr });
1723
+ return;
1724
+ }
1725
+ if (error.code == null && typeof error.signal === "string") {
1726
+ settle({ code: null, signal: error.signal, stdout, stderr });
1727
+ return;
1728
+ }
1729
+ const failure = error;
1730
+ reject(failure);
1731
+ }
1732
+ );
1733
+ });
1734
+ function isEnoent(err) {
1735
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
1736
+ }
1737
+ function isMapping(value) {
1738
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1739
+ }
1740
+ async function isFile(path) {
1741
+ try {
1742
+ return (await stat(path)).isFile();
1743
+ } catch {
1744
+ return false;
1745
+ }
1746
+ }
1747
+ async function isOnPath(name) {
1748
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
1749
+ if (dir === "") continue;
1750
+ if (await isFile(join2(dir, name))) return true;
1751
+ }
1752
+ return false;
1753
+ }
1754
+ function foldDitaOtSeverity(level) {
1755
+ switch (level.toUpperCase()) {
1756
+ case "FATAL":
1757
+ case "ERROR":
1758
+ return "error";
1759
+ case "WARN":
1760
+ return "warning";
1761
+ case "INFO":
1762
+ return "notice";
1763
+ default:
1764
+ return null;
1765
+ }
1766
+ }
1767
+ function pathFromFileUri(location) {
1768
+ let url;
1769
+ try {
1770
+ url = new URL(location);
1771
+ } catch {
1772
+ return location;
1773
+ }
1774
+ if (url.protocol !== "file:") return location;
1775
+ try {
1776
+ return fileURLToPath(url);
1777
+ } catch {
1778
+ return decodeURIComponent(url.pathname);
1779
+ }
1780
+ }
1781
+ function readMessage(raw) {
1782
+ if (!isMapping(raw)) return null;
1783
+ const code = raw["code"];
1784
+ if (typeof code !== "string" || code === "") return null;
1785
+ const level = raw["level"];
1786
+ const severity = typeof level === "string" ? foldDitaOtSeverity(level) : null;
1787
+ if (severity === null) return null;
1788
+ const msg = raw["msg"];
1789
+ const location = raw["location"];
1790
+ const line = raw["line"];
1791
+ const row = raw["row"];
1792
+ return {
1793
+ code,
1794
+ severity,
1795
+ // A coded message with no text is still a finding; the code says what it is.
1796
+ message: typeof msg === "string" && msg !== "" ? msg : code,
1797
+ ...typeof location === "string" && location !== "" ? { file: pathFromFileUri(location) } : {},
1798
+ ...typeof line === "number" ? { line } : {},
1799
+ ...typeof row === "number" ? { column: row } : {}
1800
+ };
1801
+ }
1802
+ function parseDitaOtLog(text) {
1803
+ let parsed;
1804
+ try {
1805
+ parsed = JSON.parse(text);
1806
+ } catch {
1807
+ return null;
1808
+ }
1809
+ if (!Array.isArray(parsed)) return null;
1810
+ const messages = [];
1811
+ for (const raw of parsed) {
1812
+ const message = readMessage(raw);
1813
+ if (message !== null) messages.push(message);
1814
+ }
1815
+ return messages;
1816
+ }
1817
+ function parseDitaOtVersion(output) {
1818
+ const match = /\b(\d+(?:\.\d+)+(?:[-.][0-9A-Za-z]+)*)/.exec(output);
1819
+ return match?.[1] ?? null;
1820
+ }
1821
+ function failureText(result) {
1822
+ const stderr = result.stderr.trim();
1823
+ if (stderr !== "") return stderr;
1824
+ if (result.signal != null) return `killed by ${result.signal}`;
1825
+ return `exit code ${String(result.code)}`;
1826
+ }
1827
+ async function readLog(path) {
1828
+ let text;
1829
+ try {
1830
+ text = await readFile2(path, "utf8");
1831
+ } catch {
1832
+ return null;
1833
+ }
1834
+ return parseDitaOtLog(text);
1835
+ }
1836
+ async function runDitaOtValidate(opts) {
1837
+ const spawn = opts.spawn ?? spawnDitaOt;
1838
+ const launcher = ditaOtLauncher(opts.home);
1839
+ if (opts.home !== void 0 && !await isFile(launcher)) {
1840
+ throw new LintError(noLauncher(opts.home));
1841
+ }
1842
+ const scratch = await mkdtemp(join2(tmpdir(), "manni-dita-ot-"));
1843
+ try {
1844
+ const results = [];
1845
+ for (const [index, target] of opts.targets.entries()) {
1846
+ const logfile = join2(scratch, `${String(index)}.json`);
1847
+ const args = [
1848
+ `--input=${target}`,
1849
+ "--format=dita",
1850
+ `--output=${join2(scratch, `out-${String(index)}`)}`,
1851
+ `--temp=${join2(scratch, `temp-${String(index)}`)}`,
1852
+ "--logger=json",
1853
+ `--logfile=${logfile}`,
1854
+ "--verbose"
1855
+ ];
1856
+ let result;
1857
+ try {
1858
+ result = await spawn(launcher, args, { cwd: opts.cwd });
1859
+ } catch (err) {
1860
+ if (isEnoent(err)) {
1861
+ throw new LintError(
1862
+ opts.home === void 0 ? NOT_ON_PATH : noLauncher(opts.home)
1863
+ );
1864
+ }
1865
+ throw err;
1866
+ }
1867
+ const messages = await readLog(logfile);
1868
+ const explained = messages !== null && messages.some((m) => m.severity === "error");
1869
+ if (result.code !== 0 && !explained) {
1870
+ if (opts.home === void 0 && !await isOnPath(launcher)) {
1871
+ throw new LintError(NOT_ON_PATH);
1872
+ }
1873
+ throw new LintError(
1874
+ `DITA Open Toolkit failed on ${target}: ${failureText(result)}. Run it directly on that file to see why.`
1875
+ );
1876
+ }
1877
+ results.push({ target, messages: messages ?? [] });
1878
+ }
1879
+ return results;
1880
+ } finally {
1881
+ await rm(scratch, { recursive: true, force: true });
1882
+ }
1883
+ }
1884
+ async function probeDitaOt(ctx, spawn = spawnDitaOt) {
1885
+ const home = ctx.tools === void 0 ? void 0 : ditaOtHome(ctx.tools, ctx.configDir ?? ctx.cwd);
1886
+ const launcher = ditaOtLauncher(home);
1887
+ if (home !== void 0 && !await isFile(launcher)) {
1888
+ return { available: false, version: null };
1889
+ }
1890
+ let result;
1891
+ try {
1892
+ result = await spawn(launcher, ["--version"], { cwd: ctx.cwd });
1893
+ } catch {
1894
+ return { available: false, version: null };
1895
+ }
1896
+ if (result.code !== 0) return { available: false, version: null };
1897
+ return {
1898
+ available: true,
1899
+ // Both channels, because which one carries the banner is not fixed: the
1900
+ // shell launcher prints it on stdout, and the Windows `.bat` run through
1901
+ // `cmd.exe` has been seen to put it on stderr. Reading one would report a
1902
+ // toolkit that is plainly there as versionless on the other platform.
1903
+ version: parseDitaOtVersion(`${result.stdout}
1904
+ ${result.stderr}`)
1905
+ };
1906
+ }
1907
+ var DITA_FORMAT = {
1908
+ name: "dita",
1909
+ label: "DITA",
1910
+ extensions: [".ditamap", ".dita", ".xml"],
1911
+ kinds: []
1912
+ };
1913
+ var ditaOt = {
1914
+ name: "dita-ot",
1915
+ label: "DITA Open Toolkit",
1916
+ formats: () => [{ ...DITA_FORMAT }],
1917
+ // Maps only. DITA-OT takes one `--input` per invocation, so walking
1918
+ // every topic in a docset would pay a JVM start per topic. Naming a `.dita`
1919
+ // explicitly still works: a named file bypasses the extension filter.
1920
+ walkExtensions: () => [".ditamap"],
1921
+ probe: (ctx) => probeDitaOt(ctx),
1922
+ // None. Every option manni owns is refused under this tool, stdin included.
1923
+ ownedOptions: []
1924
+ };
1925
+
1926
+ // src/lint/tools/index.ts
1927
+ var manni = {
1928
+ name: "manni",
1929
+ label: "manni's own structure engine",
1930
+ formats: () => listFormats(),
1931
+ walkExtensions: () => supportedExtensions(),
1932
+ // Nothing to look for: the engine is this package, so it is available
1933
+ // wherever the CLI is. A tool that shells out answers this by looking.
1934
+ probe: () => Promise.resolve({ available: true, version: package_default.version }),
1935
+ // `-` is stdin, and it is in this list so the derivation in `lint.ts` has
1936
+ // one place to learn that manni owns it. The refusal itself is not shaped
1937
+ // like the others, because "option" is the wrong word for a positional:
1938
+ // `assertOptionsOwned` special-cases it to say "cannot read stdin" instead.
1939
+ // A tool that copies this list without reading that guard gets the right
1940
+ // ownership and the wrong sentence.
1941
+ ownedOptions: ["--template", "--templates", "--explain", "--as", "-"]
1942
+ };
1943
+ var descriptors = /* @__PURE__ */ new Map([
1944
+ [manni.name, manni],
1945
+ [ditaOt.name, ditaOt]
1946
+ ]);
1947
+ function structureTools() {
1948
+ return [...descriptors.values()];
1949
+ }
1950
+ function resolveStructureTool(name) {
1951
+ const descriptor = descriptors.get(name);
1952
+ if (descriptor) return descriptor;
1953
+ throw new LintError(
1954
+ `Unknown --tool "${name}" for structure. Use ${TOOLS_BY_JOB.structure.join(", ")}.`
1955
+ );
1956
+ }
1957
+
1958
+ // src/lint/commands/lint.ts
1959
+ var STDIN_LABEL = "<stdin>";
1960
+ var TEMPLATE_FILE_EXTENSIONS = [".yaml", ".yml", ".json"];
1961
+ async function asLintError(run) {
1962
+ try {
1963
+ return await run();
1964
+ } catch (err) {
1965
+ if (err instanceof LintError) throw err;
1966
+ if (err instanceof ToolError) throw new LintError(err.message);
1967
+ throw err;
1968
+ }
1969
+ }
1970
+ function templateFiles(value) {
1971
+ if (value == null) return [];
1972
+ return Array.isArray(value) ? value : [value];
1973
+ }
1974
+ var TOOL_OPTIONS = [
1975
+ { spelling: "--template", given: (o) => o.template !== void 0 },
1976
+ {
1977
+ spelling: "--templates",
1978
+ given: (o) => templateFiles(o.templates).length > 0
1979
+ },
1980
+ { spelling: "--explain", given: (o) => o.explain === true },
1981
+ { spelling: "--as", given: (o) => o.as !== void 0 }
1982
+ ];
1983
+ function foreignOptions(tool) {
1984
+ const foreign = /* @__PURE__ */ new Map();
1985
+ for (const other of structureTools()) {
1986
+ if (other === tool) continue;
1987
+ for (const spelling of other.ownedOptions) {
1988
+ if (tool.ownedOptions.includes(spelling)) continue;
1989
+ if (!foreign.has(spelling)) foreign.set(spelling, other);
1990
+ }
1991
+ }
1992
+ return foreign;
1993
+ }
1994
+ function assertOptionsOwned(opts, tool, usingStdin) {
1995
+ const foreign = foreignOptions(tool);
1996
+ if (foreign.size === 0) return;
1997
+ for (const { spelling, given } of TOOL_OPTIONS) {
1998
+ const owner = foreign.get(spelling);
1999
+ if (owner === void 0 || !given(opts)) continue;
2000
+ throw new LintError(
2001
+ `${spelling} is an option of the ${owner.name} tool; the structure job's tool is ${tool.name}.`
2002
+ );
2003
+ }
2004
+ if (usingStdin && foreign.has(STDIN_TOKEN)) {
2005
+ throw new LintError(`${tool.name} cannot read stdin. Name files or a map.`);
2006
+ }
2007
+ }
2008
+ function origin2() {
2009
+ return {
2010
+ start: { line: 1, column: 1, offset: 0 },
2011
+ end: { line: 1, column: 1, offset: 0 }
2012
+ };
2013
+ }
2014
+ function skip(file, reason, cause = "unsupported-format") {
2015
+ return {
2016
+ file,
2017
+ // `success` means "linted and produced no findings". A file that was never
2018
+ // linted is neither passing nor failing, and calling it a pass would hide
2019
+ // the gap from anyone reading CI output.
2020
+ success: false,
2021
+ findings: [],
2022
+ template: null,
2023
+ skipped: cause,
2024
+ reason
2025
+ };
2026
+ }
2027
+ function parseFinding(message) {
2028
+ return {
2029
+ type: "parse_error",
2030
+ heading: null,
2031
+ message,
2032
+ position: origin2(),
2033
+ severity: "error"
2034
+ };
2035
+ }
2036
+ function templateLoader() {
2037
+ const cache = /* @__PURE__ */ new Map();
2038
+ return (ref) => {
2039
+ let pending = cache.get(ref);
2040
+ if (!pending) {
2041
+ pending = loadResolvedTemplate(ref);
2042
+ cache.set(ref, pending);
2043
+ }
2044
+ return pending;
2045
+ };
2046
+ }
2047
+ function cliTemplateRef(template, templatePaths) {
2048
+ if (!template) return void 0;
2049
+ if (template.includes("#")) return template;
2050
+ if (classifyRef(template).kind !== "file") return template;
2051
+ if (template.includes("/") || template.includes("\\")) return template;
2052
+ if (TEMPLATE_FILE_EXTENSIONS.some((e) => template.toLowerCase().endsWith(e))) {
2053
+ return template;
2054
+ }
2055
+ if (templatePaths.length === 0) return template;
2056
+ return `${templatePaths[templatePaths.length - 1] ?? ""}#${template}`;
2057
+ }
2058
+ function templateIdentity(ref) {
2059
+ const hash = ref.lastIndexOf("#");
2060
+ if (hash === -1) return { source: ref, template: ref };
2061
+ return { source: ref.slice(0, hash), template: ref.slice(hash + 1) };
2062
+ }
2063
+ function unknownTypeFinding(resolution, typeIndex, position) {
2064
+ const suggestions = resolution.suggestions ?? [];
2065
+ const known = resolution.knownTypes ?? knownTypes(typeIndex);
2066
+ const hint = suggestions.length ? ` Did you mean ${suggestions.map((s) => `"${s}"`).join(", ")}?` : ` Known doctypes: ${known.join(", ") || "(none)"}.`;
2067
+ const declared = resolution.unknownType ?? "(none recorded)";
2068
+ return {
2069
+ type: "unknown_type",
2070
+ heading: null,
2071
+ message: `No template serves type "${declared}".${hint} Declare it on a template with "types:", then pass that file with --templates.`,
2072
+ position,
2073
+ severity: "error"
2074
+ };
2075
+ }
2076
+ async function lintOne(label, content, parser, ctx) {
2077
+ let tree;
2078
+ try {
2079
+ tree = parser.parse(content, label);
2080
+ } catch (err) {
2081
+ return {
2082
+ file: label,
2083
+ success: false,
2084
+ findings: [parseFinding(errorMessage(err))],
2085
+ template: null
2086
+ };
2087
+ }
2088
+ const resolution = resolveTemplateRef({
2089
+ filePath: label,
2090
+ absolutePath: ctx.absoluteOf?.(label),
2091
+ frontmatter: tree.frontmatter,
2092
+ cliTemplate: ctx.cliTemplate,
2093
+ overrides: ctx.overrides,
2094
+ typeIndex: ctx.typeIndex,
2095
+ defaultTemplate: ctx.defaultTemplate
2096
+ });
2097
+ const withResolution = (result) => ctx.explain ? { ...result, resolution } : result;
2098
+ const metaPosition = tree.frontmatterPosition ?? origin2();
2099
+ if (resolution.ref === null) {
2100
+ if (resolution.cause === "unknown-type") {
2101
+ return withResolution({
2102
+ file: label,
2103
+ success: false,
2104
+ findings: [unknownTypeFinding(resolution, ctx.typeIndex, metaPosition)],
2105
+ template: null
2106
+ });
2107
+ }
2108
+ return withResolution(
2109
+ skip(
2110
+ label,
2111
+ "no type in frontmatter and no template resolved",
2112
+ "no-template"
2113
+ )
2114
+ );
2115
+ }
2116
+ const brokenTemplate = (err) => withResolution({
2117
+ file: label,
2118
+ success: false,
2119
+ findings: [
2120
+ {
2121
+ type: "template_error",
2122
+ heading: null,
2123
+ message: errorMessage(err),
2124
+ position: metaPosition,
2125
+ severity: "error"
2126
+ }
2127
+ ],
2128
+ template: resolution.ref
2129
+ });
2130
+ if (resolution.stage === "frontmatter-template" && classifyRef(resolution.ref).kind === "url") {
2131
+ return brokenTemplate(
2132
+ new LintError(
2133
+ `${FILE_TEMPLATE_KEY} may not name a URL ("${resolution.ref}"): a document must not choose what the linter fetches. Declare it in manni.config.yaml under "templates:" or "types:", or pass it with --template.`
2134
+ )
2135
+ );
2136
+ }
2137
+ const ref = resolution.stage === "frontmatter-template" ? refRelativeTo(ctx.absoluteOf?.(label) ?? label, resolution.ref) : resolution.ref;
2138
+ let template;
2139
+ try {
2140
+ template = await ctx.getTemplate(ref);
2141
+ } catch (err) {
2142
+ return brokenTemplate(err);
2143
+ }
2144
+ if (ctx.explain) {
2145
+ return withResolution({
2146
+ file: label,
2147
+ success: true,
2148
+ findings: [],
2149
+ template: resolution.ref,
2150
+ // The tree and the loaded template, for the alignment block. Both are
2151
+ // already in hand here, and neither survives this function otherwise.
2152
+ alignment: { tree, template }
2153
+ });
2154
+ }
2155
+ let findings;
2156
+ try {
2157
+ findings = validateDocument(tree, template, {
2158
+ ...templateIdentity(ref),
2159
+ kinds: parser.kinds
2160
+ });
2161
+ } catch (err) {
2162
+ if (err instanceof LintError) return brokenTemplate(err);
2163
+ throw err;
2164
+ }
2165
+ return withResolution({
2166
+ file: label,
2167
+ // "Linted and found nothing wrong", which is not the same as "produced no
2168
+ // findings". A `warning` - today, a rule this file's format cannot answer -
2169
+ // says something about the run rather than about the document, and the
2170
+ // exit code follows `success`, so counting it as a failure would turn a
2171
+ // docset of mixed formats permanently red. The family's rule, and the one
2172
+ // meta and cite already apply: `ok` is "no error-severity finding".
2173
+ success: !findings.some(isErrorSeverity),
2174
+ findings,
2175
+ template: resolution.ref
2176
+ });
2177
+ }
2178
+ async function lintWithManni(run) {
2179
+ const { opts, config, cwd, files, forcedParser, usingStdin } = run;
2180
+ const templatePaths = templateFiles(opts.templates ?? config.templates);
2181
+ const getTemplate = templateLoader();
2182
+ const userFiles = [];
2183
+ for (const ref of templatePaths) {
2184
+ const file = await loadTemplateFile(ref);
2185
+ const templates = {};
2186
+ for (const name of Object.keys(file.templates ?? {})) {
2187
+ templates[name] = await getTemplate(`${ref}#${name}`);
2188
+ }
2189
+ userFiles.push({ ref, file: { ...file, templates } });
2190
+ }
2191
+ const typeIndex = buildTypeIndex({
2192
+ builtins: listBuiltins(),
2193
+ userFiles,
2194
+ explicitTypes: opts.types ?? config.types
2195
+ });
2196
+ const ctx = {
2197
+ getTemplate,
2198
+ absoluteOf: (file) => resolve2(cwd, file).replace(/\\/g, "/"),
2199
+ typeIndex,
2200
+ cliTemplate: cliTemplateRef(opts.template, templatePaths),
2201
+ overrides: opts.overrides ?? config.overrides,
2202
+ // `template:` in config is the default for a page that declares no type -
2203
+ // the bottom of the chain, not the top. `--template` is the top.
2204
+ defaultTemplate: opts.defaultTemplate ?? config.template,
2205
+ explain: opts.explain === true
2206
+ };
2207
+ if (ctx.cliTemplate != null) await getTemplate(ctx.cliTemplate);
2208
+ const results = [];
2209
+ if (usingStdin && forcedParser) {
2210
+ results.push(
2211
+ await lintOne(STDIN_LABEL, opts.stdinContent ?? "", forcedParser, ctx)
2212
+ );
2213
+ }
2214
+ for (const file of files) {
2215
+ const ext = extname(file);
2216
+ const parser = forcedParser ?? parserForExtension(ext);
2217
+ if (!parser) {
2218
+ results.push(skip(file, unsupportedFormatMessage(ext || file)));
2219
+ continue;
2220
+ }
2221
+ let content;
2222
+ try {
2223
+ content = await readFile3(resolve2(cwd, file), "utf8");
2224
+ } catch (err) {
2225
+ results.push(
2226
+ skip(file, `could not be read: ${errorMessage(err)}`, "unreadable")
2227
+ );
2228
+ continue;
2229
+ }
2230
+ results.push(await lintOne(file, content, parser, ctx));
2231
+ }
2232
+ return results;
2233
+ }
2234
+ function ditaOtFinding(message) {
2235
+ const point = message.line === void 0 ? null : {
2236
+ line: message.line,
2237
+ // The log calls the column `row`; see `readMessage` in the seam.
2238
+ column: message.column ?? 1,
2239
+ // DITA-OT reports no byte offset, and nothing downstream needs one.
2240
+ offset: 0
2241
+ };
2242
+ return {
2243
+ type: message.code,
2244
+ heading: null,
2245
+ message: message.message,
2246
+ position: point === null ? origin2() : { start: point, end: point },
2247
+ severity: message.severity
2248
+ };
2249
+ }
2250
+ async function lintWithDitaOt(run) {
2251
+ const { cwd, files, onNotice } = run;
2252
+ const readable = ditaOt.formats().flatMap((format) => format.extensions);
2253
+ const reads = new Set(readable.map((ext) => ext.toLowerCase()));
2254
+ const targets = [];
2255
+ const unsupported = [];
2256
+ for (const file of files) {
2257
+ if (reads.has(extname(file).toLowerCase())) targets.push(file);
2258
+ else {
2259
+ unsupported.push(
2260
+ skip(
2261
+ file,
2262
+ `dita-ot does not read "${extname(file) || file}". It reads ${readable.join(", ")}.`
2263
+ )
2264
+ );
2265
+ }
2266
+ }
2267
+ const labels = /* @__PURE__ */ new Map();
2268
+ for (const file of targets) {
2269
+ const absolute = resolve2(cwd, file);
2270
+ if (!labels.has(absolute)) labels.set(absolute, file);
2271
+ }
2272
+ const unique = [...labels.values()];
2273
+ const labelOf = (absolute) => {
2274
+ const known = labels.get(absolute);
2275
+ if (known !== void 0) return known;
2276
+ const near = relative(cwd, absolute).replace(/\\/g, "/");
2277
+ return near === "" ? absolute : near;
2278
+ };
2279
+ for (const file of unique) {
2280
+ if (extname(file).toLowerCase() !== ".ditamap") {
2281
+ onNotice?.(
2282
+ `checked ${file} without a map; references outside it are not resolved.`
2283
+ );
2284
+ }
2285
+ }
2286
+ if (unique.length === 0) return unsupported;
2287
+ const results = await run.validate({
2288
+ targets: [...labels.keys()],
2289
+ cwd,
2290
+ ...run.home === void 0 ? {} : { home: run.home }
2291
+ });
2292
+ const byFile = new Map(unique.map((file) => [file, []]));
2293
+ const findingsFor = (file) => {
2294
+ let list = byFile.get(file);
2295
+ if (list === void 0) {
2296
+ list = [];
2297
+ byFile.set(file, list);
2298
+ }
2299
+ return list;
2300
+ };
2301
+ for (const { target, messages } of results) {
2302
+ const invoked = labelOf(target);
2303
+ for (const message of messages) {
2304
+ const file = message.file === void 0 ? invoked : labelOf(resolve2(cwd, message.file));
2305
+ findingsFor(file).push(ditaOtFinding(message));
2306
+ }
2307
+ }
2308
+ return [
2309
+ ...unsupported,
2310
+ ...[...byFile].map(([file, findings]) => ({
2311
+ file,
2312
+ // The family's rule, as manni's branch applies it: `ok` is "no
2313
+ // error-severity finding", so a warning-only file stays green.
2314
+ success: !findings.some(isErrorSeverity),
2315
+ findings,
2316
+ template: null
2317
+ }))
2318
+ ];
2319
+ }
2320
+ function assertSomethingWasChecked(results, explain, tool) {
2321
+ const skipped = results.filter((r) => r.skipped != null).length;
2322
+ const checked = results.length - skipped;
2323
+ if (explain || checked > 0 || skipped === 0) return;
2324
+ const unrouted = results.filter((r) => r.skipped === "no-template").length;
2325
+ const unsupported = results.filter(
2326
+ (r) => r.skipped === "unsupported-format"
2327
+ ).length;
2328
+ const unreadable = results.filter((r) => r.skipped === "unreadable").length;
2329
+ const advice = [
2330
+ unrouted > 0 ? `${unrouted} had no template: give a page a "type:" that a template serves, pass -t/--template <ref>, or set "lint.template" as a default.` : null,
2331
+ unsupported > 0 ? (
2332
+ // `--as` belongs to manni's engine, so offering it to someone running
2333
+ // another tool is advice that answers with a usage error. The formats
2334
+ // line is true for every tool, because every tool declares one.
2335
+ `${unsupported} ${unsupported === 1 ? "is" : "are"} in a format ${tool.name} does not read: ` + (tool.ownedOptions.includes("--as") ? `pass --as <format> to force one, or target files in a format ` : `target files in a format `) + `"manni lint tools" lists.`
2336
+ ) : null,
2337
+ unreadable > 0 ? `${unreadable} could not be read: check the permissions on those paths, or drop them from the run with --exclude <glob>.` : null
2338
+ ].filter((line) => line !== null).join(" ");
2339
+ throw new LintError(
2340
+ `Nothing was checked: all ${skipped} file(s) were skipped. ${advice} Run "manni lint structure <paths> --explain" to see how each file resolved.`
2341
+ );
2342
+ }
2343
+ async function runLint(opts) {
2344
+ const run = await resolveLintRun({
2345
+ cwd: opts.cwd ?? process.cwd(),
2346
+ configPath: opts.configPath,
2347
+ noConfig: opts.noConfig,
2348
+ inputs: opts.inputs,
2349
+ collection: opts.collection,
2350
+ onConfigLoaded: opts.onConfigLoaded
2351
+ });
2352
+ const config = run.config;
2353
+ const cwd = run.base;
2354
+ if (run.inputs.length === 0) {
2355
+ throw new LintError(
2356
+ "No files to check. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
2357
+ );
2358
+ }
2359
+ const tool = resolveStructureTool(
2360
+ opts.tool ?? config.structure?.tool ?? "manni"
2361
+ );
2362
+ const usingStdin = run.inputs.includes(STDIN_TOKEN);
2363
+ assertOptionsOwned(opts, tool, usingStdin);
2364
+ const forcedParser = opts.as != null ? parserByName(opts.as) : void 0;
2365
+ if (opts.as != null && !forcedParser) {
2366
+ throw new LintError(
2367
+ `Unknown format "${opts.as}". Run "manni lint tools" to see the formats manni lint reads.`
2368
+ );
2369
+ }
2370
+ if (usingStdin && !forcedParser) {
2371
+ throw new LintError(
2372
+ "Reading from stdin (-) requires --as <format> to choose a parser."
2373
+ );
2374
+ }
2375
+ const fileInputs = run.inputs.filter((input) => input !== STDIN_TOKEN);
2376
+ const exts = opts.exts ?? forcedParser?.extensions ?? tool.walkExtensions();
2377
+ const allowEmpty = opts.allowEmpty ?? config.allowEmpty;
2378
+ const exclude = [
2379
+ .../* @__PURE__ */ new Set([
2380
+ ...opts.exclude ?? [],
2381
+ ...run.fromCollections ? run.collections.flatMap((c) => c.exclude) : []
2382
+ ])
2383
+ ];
2384
+ const { files, gitignoreSkipped } = await asLintError(
2385
+ () => resolveTargetSet({
2386
+ inputs: fileInputs,
2387
+ exts,
2388
+ exclude,
2389
+ cwd,
2390
+ allowEmpty,
2391
+ ...gitignoreOptions({
2392
+ flag: opts.respectGitignore,
2393
+ onNotice: opts.onNotice
2394
+ })
2395
+ })
2396
+ );
2397
+ await asLintError(() => {
2398
+ assertNonEmpty({
2399
+ files,
2400
+ inputs: fileInputs,
2401
+ usingStdin,
2402
+ allowEmpty,
2403
+ exclude,
2404
+ exts,
2405
+ gitignoreSkipped,
2406
+ action: "linted"
2407
+ });
2408
+ });
2409
+ const name = tool.name;
2410
+ let results;
2411
+ if (name === "manni") {
2412
+ results = await lintWithManni({
2413
+ opts,
2414
+ config,
2415
+ cwd,
2416
+ files,
2417
+ forcedParser,
2418
+ usingStdin
2419
+ });
2420
+ } else if (name === "dita-ot") {
2421
+ const home = ditaOtHome(run.tools, run.configDir ?? cwd);
2422
+ results = await lintWithDitaOt({
2423
+ cwd,
2424
+ files,
2425
+ ...home === void 0 ? {} : { home },
2426
+ ...opts.onNotice === void 0 ? {} : { onNotice: opts.onNotice },
2427
+ validate: opts.runDitaOt ?? runDitaOtValidate
2428
+ });
2429
+ } else {
2430
+ throw new LintError(`No implementation is registered for the ${name} tool.`);
2431
+ }
2432
+ assertSomethingWasChecked(results, opts.explain === true, tool);
2433
+ const skipped = results.filter((r) => r.skipped != null).length;
2434
+ const failed = results.filter((r) => r.skipped == null && !r.success).length;
2435
+ const checked = results.length - skipped;
2436
+ return {
2437
+ results,
2438
+ summary: { checked, passed: checked - failed, failed, skipped }
2439
+ };
2440
+ }
2441
+
2442
+ export {
2443
+ listBuiltins,
2444
+ classifyRef,
2445
+ clearTemplateCaches,
2446
+ loadTemplateFile,
2447
+ loadTemplate,
2448
+ resolveExtends,
2449
+ clearMatcherCache,
2450
+ validateSections,
2451
+ validateDocument,
2452
+ LINT_TOOLS,
2453
+ LINT_JOBS,
2454
+ TOOLS_BY_JOB,
2455
+ isLintTool,
2456
+ parseConfig,
2457
+ loadConfig,
2458
+ rebaseConfig,
2459
+ resolveLintRun,
2460
+ resolveStructureTool,
2461
+ STDIN_LABEL,
2462
+ runLint
2463
+ };
2464
+ //# sourceMappingURL=chunk-SWSTHZBI.js.map