@hawkeyexl/manni 4.10.0 → 4.11.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 (192) hide show
  1. package/README.md +1 -1
  2. package/dist/{batch-6NICDKRE.js → batch-UAULUMGH.js} +18 -14
  3. package/dist/{build-3KP2K3XN.js → build-QSPNK5AL.js} +14 -11
  4. package/dist/calibrate-KXN4URPM.js +40 -0
  5. package/dist/{capture-NETOHCB4.js → capture-JHTSUPUC.js} +16 -13
  6. package/dist/{check-E4QCB7LS.js → check-MDGXEUWL.js} +5 -5
  7. package/dist/{check-NLL4NRYA.js → check-VELUQAUW.js} +17 -14
  8. package/dist/{check-NLL4NRYA.js.map → check-VELUQAUW.js.map} +1 -1
  9. package/dist/check-WOQRKNZK.js +29 -0
  10. package/dist/{check-7B77ZD2N.js → check-YEOYBFST.js} +14 -11
  11. package/dist/{chunk-2JV7C4YV.js → chunk-2DRHT73U.js} +7 -7
  12. package/dist/{chunk-CCWMACJL.js → chunk-2IAZEB45.js} +8 -8
  13. package/dist/{chunk-ZHRPH52H.js → chunk-2IR4T5PO.js} +2 -2
  14. package/dist/{chunk-2BV4FBC7.js → chunk-2MJ62AYQ.js} +2 -2
  15. package/dist/{chunk-2BV4FBC7.js.map → chunk-2MJ62AYQ.js.map} +1 -1
  16. package/dist/{chunk-RODZ7MB7.js → chunk-4CK6ZSOK.js} +6 -6
  17. package/dist/{chunk-7Y3N4Y72.js → chunk-4D5IZSXM.js} +3 -3
  18. package/dist/{chunk-4DLF5SA4.js → chunk-4VBSTXN6.js} +4 -4
  19. package/dist/{chunk-2YJ5H5E6.js → chunk-54F74XR3.js} +15 -13
  20. package/dist/{chunk-2YJ5H5E6.js.map → chunk-54F74XR3.js.map} +1 -1
  21. package/dist/{chunk-2W34HX2U.js → chunk-6HWBRZIN.js} +47 -191
  22. package/dist/chunk-6HWBRZIN.js.map +1 -0
  23. package/dist/{chunk-SUTMSDQ2.js → chunk-ADMEPUBT.js} +3 -3
  24. package/dist/{chunk-VZVEP2OZ.js → chunk-B7XYNRJQ.js} +2 -2
  25. package/dist/{chunk-GB2XPITA.js → chunk-BBT6AVUG.js} +2 -2
  26. package/dist/{chunk-PK7EKJ5T.js → chunk-BKVHX6FU.js} +4 -4
  27. package/dist/{chunk-QBVP3SQP.js → chunk-D4N2V43Z.js} +4 -4
  28. package/dist/{chunk-6AGESHHM.js → chunk-EFW7CAG4.js} +2 -2
  29. package/dist/{chunk-H26VDFDS.js → chunk-EIFCP6CZ.js} +2 -2
  30. package/dist/{chunk-GCKHSBEN.js → chunk-FBMZJEB3.js} +2 -2
  31. package/dist/{chunk-2UCC26MO.js → chunk-FQXWVS6J.js} +8 -8
  32. package/dist/{chunk-5REEVKGH.js → chunk-FUCPZVRY.js} +2 -2
  33. package/dist/{chunk-WN47MNSK.js → chunk-GDVZ5VII.js} +4 -4
  34. package/dist/{chunk-KBG463UK.js → chunk-GV56WGL2.js} +321 -3308
  35. package/dist/chunk-GV56WGL2.js.map +1 -0
  36. package/dist/{chunk-IJ62BB2T.js → chunk-HA6MQMOY.js} +6 -6
  37. package/dist/{chunk-DNZUBCGZ.js → chunk-HQR2ZBHW.js} +3 -3
  38. package/dist/{chunk-DFCQ6BMI.js → chunk-IGSHYP74.js} +4 -4
  39. package/dist/{chunk-ABHG6I3G.js → chunk-J6IDQGJL.js} +4 -4
  40. package/dist/{chunk-GDLHTI5R.js → chunk-JPQDNYXH.js} +2 -2
  41. package/dist/{chunk-VVVKBUBO.js → chunk-JROBZ7JA.js} +2 -2
  42. package/dist/{chunk-BD7IXBWE.js → chunk-KGEQJ5CC.js} +5 -5
  43. package/dist/{chunk-LQZQ5KGG.js → chunk-LBSCW232.js} +4 -4
  44. package/dist/{chunk-G6FNWRAI.js → chunk-LKP7QDFN.js} +4 -4
  45. package/dist/{chunk-55GMOVKW.js → chunk-LPIF5LIE.js} +2 -2
  46. package/dist/chunk-M42DZCOU.js +164 -0
  47. package/dist/chunk-M42DZCOU.js.map +1 -0
  48. package/dist/{chunk-ZXXFP2LA.js → chunk-MSRU4D4P.js} +2 -2
  49. package/dist/{chunk-QJRQ4BRC.js → chunk-NSBXVPZL.js} +11 -11
  50. package/dist/{chunk-Z5YHMRLF.js → chunk-ODRPL3WB.js} +2 -2
  51. package/dist/{chunk-XU73V5XT.js → chunk-OPXWPJDA.js} +10 -10
  52. package/dist/chunk-PCQ7ELJH.js +3081 -0
  53. package/dist/chunk-PCQ7ELJH.js.map +1 -0
  54. package/dist/{chunk-3E2X2RBU.js → chunk-PCS4VJBV.js} +6 -6
  55. package/dist/{chunk-GXN7RRHR.js → chunk-PWBH7DCQ.js} +3 -3
  56. package/dist/{chunk-6M5VJOC2.js → chunk-QLWC7MKD.js} +33 -32
  57. package/dist/chunk-QLWC7MKD.js.map +1 -0
  58. package/dist/{chunk-EAUIQADY.js → chunk-R4WXQIOZ.js} +6 -6
  59. package/dist/{chunk-AO5TSDKT.js → chunk-RLT5SEML.js} +5 -5
  60. package/dist/{chunk-XG6PVB5S.js → chunk-SSQZB5SQ.js} +1 -11
  61. package/dist/chunk-SSQZB5SQ.js.map +1 -0
  62. package/dist/chunk-T7JAOYE5.js +393 -0
  63. package/dist/chunk-T7JAOYE5.js.map +1 -0
  64. package/dist/chunk-TU2WUBVZ.js +2 -0
  65. package/dist/{chunk-D2UW3ZCK.js → chunk-U7CTFN2J.js} +12 -10
  66. package/dist/{chunk-D2UW3ZCK.js.map → chunk-U7CTFN2J.js.map} +1 -1
  67. package/dist/{chunk-G7XD4QH5.js → chunk-VSEBFHAY.js} +10 -7
  68. package/dist/{chunk-G7XD4QH5.js.map → chunk-VSEBFHAY.js.map} +1 -1
  69. package/dist/{chunk-RZVCWX5R.js → chunk-WDWXAYVH.js} +4 -4
  70. package/dist/{chunk-6LR73NMO.js → chunk-Y6DOMTQD.js} +10 -8
  71. package/dist/{chunk-6LR73NMO.js.map → chunk-Y6DOMTQD.js.map} +1 -1
  72. package/dist/chunk-ZPLVANY2.js +15 -0
  73. package/dist/chunk-ZPLVANY2.js.map +1 -0
  74. package/dist/{cli-4YYC6LCY.js → cli-3BTCK4AB.js} +18 -15
  75. package/dist/{cli-4YYC6LCY.js.map → cli-3BTCK4AB.js.map} +1 -1
  76. package/dist/{cli-BIIUKPTE.js → cli-4MF37HAQ.js} +15 -12
  77. package/dist/{cli-BIIUKPTE.js.map → cli-4MF37HAQ.js.map} +1 -1
  78. package/dist/{cli-V2DI3K2R.js → cli-6KZTHWGP.js} +17 -14
  79. package/dist/{cli-V2DI3K2R.js.map → cli-6KZTHWGP.js.map} +1 -1
  80. package/dist/{cli-EH3I4IUX.js → cli-DRMZBZLB.js} +38 -33
  81. package/dist/cli-DRMZBZLB.js.map +1 -0
  82. package/dist/{cli-HO62TOM2.js → cli-I62SRH3R.js} +10 -8
  83. package/dist/cli-JJMOVIKS.js +83 -0
  84. package/dist/cli-JJMOVIKS.js.map +1 -0
  85. package/dist/{cli-SGVFZOAW.js → cli-O4ABTN4Z.js} +25 -21
  86. package/dist/{cli-SGVFZOAW.js.map → cli-O4ABTN4Z.js.map} +1 -1
  87. package/dist/{cli-UETKXHJN.js → cli-ONTC5PKT.js} +3 -3
  88. package/dist/{cli-TELPAMJO.js → cli-RORDPAPE.js} +17 -14
  89. package/dist/{cli-TELPAMJO.js.map → cli-RORDPAPE.js.map} +1 -1
  90. package/dist/{cli-P2X7PNY6.js → cli-VID7PJS3.js} +17 -13
  91. package/dist/{cli-P2X7PNY6.js.map → cli-VID7PJS3.js.map} +1 -1
  92. package/dist/{cli-LWST5FQH.js → cli-VUKMWSSF.js} +3 -3
  93. package/dist/cli.js +18 -12
  94. package/dist/cli.js.map +1 -1
  95. package/dist/{config-TYXYQK3I.js → config-XDA42RJL.js} +3 -3
  96. package/dist/docmeta.js +9 -7
  97. package/dist/docmeta.js.map +1 -1
  98. package/dist/github-RY3FRA6P.js +29 -0
  99. package/dist/github-YKQSH7FS.js +34 -0
  100. package/dist/index.d.ts +1023 -915
  101. package/dist/index.js +106 -81
  102. package/dist/index.js.map +1 -1
  103. package/dist/{json-QR4G35KA.js → json-BXY44ED7.js} +13 -10
  104. package/dist/{lint-QTI7XK2A.js → lint-YJ2L22BW.js} +13 -10
  105. package/dist/list-NSG24VVX.js +31 -0
  106. package/dist/list-V6EGCSHX.js +34 -0
  107. package/dist/{page-FSXDXCDD.js → page-EV7OUBAU.js} +12 -9
  108. package/dist/{prepare-UPIBTFNS.js → prepare-JHJDJB6I.js} +17 -14
  109. package/dist/{prepare-UPIBTFNS.js.map → prepare-JHJDJB6I.js.map} +1 -1
  110. package/dist/{pretty-R4BQD2VH.js → pretty-Q5Y2PA64.js} +14 -11
  111. package/dist/{pretty-4IONWSEP.js → pretty-SCRK66M5.js} +13 -10
  112. package/dist/{reporters-FPP6JNDP.js → reporters-A5ADKDYR.js} +12 -9
  113. package/dist/{reporters-DHO2BXAD.js → reporters-RBYTASMI.js} +5 -5
  114. package/dist/run-BUE2DDXN.js +36 -0
  115. package/dist/{term-ASM4LVYC.js → term-CQAGNG3Q.js} +17 -14
  116. package/dist/{term-ASM4LVYC.js.map → term-CQAGNG3Q.js.map} +1 -1
  117. package/dist/{validate-7JI3H57P.js → validate-ZBW44RHT.js} +6 -5
  118. package/dist/validate-ZBW44RHT.js.map +1 -0
  119. package/package.json +1 -1
  120. package/plugin/manni/.claude-plugin/plugin.json +1 -1
  121. package/dist/calibrate-LUA3DWKD.js +0 -36
  122. package/dist/check-PTECPIIB.js +0 -26
  123. package/dist/chunk-2W34HX2U.js.map +0 -1
  124. package/dist/chunk-6M5VJOC2.js.map +0 -1
  125. package/dist/chunk-KBG463UK.js.map +0 -1
  126. package/dist/chunk-XG6PVB5S.js.map +0 -1
  127. package/dist/cli-EH3I4IUX.js.map +0 -1
  128. package/dist/github-ESK45CEZ.js +0 -26
  129. package/dist/github-WER7JUTN.js +0 -31
  130. package/dist/list-JO4G3TS3.js +0 -30
  131. package/dist/list-O2MJUP5V.js +0 -28
  132. package/dist/run-YH5Z7RJ7.js +0 -32
  133. /package/dist/{batch-6NICDKRE.js.map → batch-UAULUMGH.js.map} +0 -0
  134. /package/dist/{build-3KP2K3XN.js.map → build-QSPNK5AL.js.map} +0 -0
  135. /package/dist/{calibrate-LUA3DWKD.js.map → calibrate-KXN4URPM.js.map} +0 -0
  136. /package/dist/{capture-NETOHCB4.js.map → capture-JHTSUPUC.js.map} +0 -0
  137. /package/dist/{check-7B77ZD2N.js.map → check-MDGXEUWL.js.map} +0 -0
  138. /package/dist/{check-E4QCB7LS.js.map → check-WOQRKNZK.js.map} +0 -0
  139. /package/dist/{check-PTECPIIB.js.map → check-YEOYBFST.js.map} +0 -0
  140. /package/dist/{chunk-2JV7C4YV.js.map → chunk-2DRHT73U.js.map} +0 -0
  141. /package/dist/{chunk-CCWMACJL.js.map → chunk-2IAZEB45.js.map} +0 -0
  142. /package/dist/{chunk-ZHRPH52H.js.map → chunk-2IR4T5PO.js.map} +0 -0
  143. /package/dist/{chunk-RODZ7MB7.js.map → chunk-4CK6ZSOK.js.map} +0 -0
  144. /package/dist/{chunk-7Y3N4Y72.js.map → chunk-4D5IZSXM.js.map} +0 -0
  145. /package/dist/{chunk-4DLF5SA4.js.map → chunk-4VBSTXN6.js.map} +0 -0
  146. /package/dist/{chunk-SUTMSDQ2.js.map → chunk-ADMEPUBT.js.map} +0 -0
  147. /package/dist/{chunk-VZVEP2OZ.js.map → chunk-B7XYNRJQ.js.map} +0 -0
  148. /package/dist/{chunk-GB2XPITA.js.map → chunk-BBT6AVUG.js.map} +0 -0
  149. /package/dist/{chunk-PK7EKJ5T.js.map → chunk-BKVHX6FU.js.map} +0 -0
  150. /package/dist/{chunk-QBVP3SQP.js.map → chunk-D4N2V43Z.js.map} +0 -0
  151. /package/dist/{chunk-6AGESHHM.js.map → chunk-EFW7CAG4.js.map} +0 -0
  152. /package/dist/{chunk-H26VDFDS.js.map → chunk-EIFCP6CZ.js.map} +0 -0
  153. /package/dist/{chunk-GCKHSBEN.js.map → chunk-FBMZJEB3.js.map} +0 -0
  154. /package/dist/{chunk-2UCC26MO.js.map → chunk-FQXWVS6J.js.map} +0 -0
  155. /package/dist/{chunk-5REEVKGH.js.map → chunk-FUCPZVRY.js.map} +0 -0
  156. /package/dist/{chunk-WN47MNSK.js.map → chunk-GDVZ5VII.js.map} +0 -0
  157. /package/dist/{chunk-IJ62BB2T.js.map → chunk-HA6MQMOY.js.map} +0 -0
  158. /package/dist/{chunk-DNZUBCGZ.js.map → chunk-HQR2ZBHW.js.map} +0 -0
  159. /package/dist/{chunk-DFCQ6BMI.js.map → chunk-IGSHYP74.js.map} +0 -0
  160. /package/dist/{chunk-ABHG6I3G.js.map → chunk-J6IDQGJL.js.map} +0 -0
  161. /package/dist/{chunk-GDLHTI5R.js.map → chunk-JPQDNYXH.js.map} +0 -0
  162. /package/dist/{chunk-VVVKBUBO.js.map → chunk-JROBZ7JA.js.map} +0 -0
  163. /package/dist/{chunk-BD7IXBWE.js.map → chunk-KGEQJ5CC.js.map} +0 -0
  164. /package/dist/{chunk-LQZQ5KGG.js.map → chunk-LBSCW232.js.map} +0 -0
  165. /package/dist/{chunk-G6FNWRAI.js.map → chunk-LKP7QDFN.js.map} +0 -0
  166. /package/dist/{chunk-55GMOVKW.js.map → chunk-LPIF5LIE.js.map} +0 -0
  167. /package/dist/{chunk-ZXXFP2LA.js.map → chunk-MSRU4D4P.js.map} +0 -0
  168. /package/dist/{chunk-QJRQ4BRC.js.map → chunk-NSBXVPZL.js.map} +0 -0
  169. /package/dist/{chunk-Z5YHMRLF.js.map → chunk-ODRPL3WB.js.map} +0 -0
  170. /package/dist/{chunk-XU73V5XT.js.map → chunk-OPXWPJDA.js.map} +0 -0
  171. /package/dist/{chunk-3E2X2RBU.js.map → chunk-PCS4VJBV.js.map} +0 -0
  172. /package/dist/{chunk-GXN7RRHR.js.map → chunk-PWBH7DCQ.js.map} +0 -0
  173. /package/dist/{chunk-EAUIQADY.js.map → chunk-R4WXQIOZ.js.map} +0 -0
  174. /package/dist/{chunk-AO5TSDKT.js.map → chunk-RLT5SEML.js.map} +0 -0
  175. /package/dist/{cli-HO62TOM2.js.map → chunk-TU2WUBVZ.js.map} +0 -0
  176. /package/dist/{chunk-RZVCWX5R.js.map → chunk-WDWXAYVH.js.map} +0 -0
  177. /package/dist/{config-TYXYQK3I.js.map → cli-I62SRH3R.js.map} +0 -0
  178. /package/dist/{cli-UETKXHJN.js.map → cli-ONTC5PKT.js.map} +0 -0
  179. /package/dist/{cli-LWST5FQH.js.map → cli-VUKMWSSF.js.map} +0 -0
  180. /package/dist/{github-ESK45CEZ.js.map → config-XDA42RJL.js.map} +0 -0
  181. /package/dist/{github-WER7JUTN.js.map → github-RY3FRA6P.js.map} +0 -0
  182. /package/dist/{json-QR4G35KA.js.map → github-YKQSH7FS.js.map} +0 -0
  183. /package/dist/{lint-QTI7XK2A.js.map → json-BXY44ED7.js.map} +0 -0
  184. /package/dist/{list-JO4G3TS3.js.map → lint-YJ2L22BW.js.map} +0 -0
  185. /package/dist/{list-O2MJUP5V.js.map → list-NSG24VVX.js.map} +0 -0
  186. /package/dist/{page-FSXDXCDD.js.map → list-V6EGCSHX.js.map} +0 -0
  187. /package/dist/{pretty-4IONWSEP.js.map → page-EV7OUBAU.js.map} +0 -0
  188. /package/dist/{pretty-R4BQD2VH.js.map → pretty-Q5Y2PA64.js.map} +0 -0
  189. /package/dist/{reporters-DHO2BXAD.js.map → pretty-SCRK66M5.js.map} +0 -0
  190. /package/dist/{reporters-FPP6JNDP.js.map → reporters-A5ADKDYR.js.map} +0 -0
  191. /package/dist/{run-YH5Z7RJ7.js.map → reporters-RBYTASMI.js.map} +0 -0
  192. /package/dist/{validate-7JI3H57P.js.map → run-BUE2DDXN.js.map} +0 -0
@@ -1,30 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- CHECK_NAME,
4
- DEFAULT_BASELINE_PATH,
5
3
  DERIVED_KEYWORD,
6
4
  DERIVED_STALE_SCHEMA,
7
- DERIVE_SOURCES,
8
5
  DocmetaError,
9
6
  FILE_SCHEMA_KEY,
10
- LEGACY_BASELINE_PATH,
11
7
  ManifestCache,
12
- PROVENANCE_FIELD,
13
- STAMPED_DATE_FIELDS,
14
8
  STDIN_LABEL,
15
9
  STDIN_TOKEN,
16
- assertNonEmpty,
17
10
  blockLines,
18
- canonicalSchemaRef,
19
11
  classifyRef,
20
12
  colOf,
21
13
  collectSchemaPins,
22
14
  colonEnd,
23
- compareDerived,
24
15
  contentEnd,
25
16
  deepEqual,
26
- derivableFields,
27
- deriveCovers,
28
17
  detectEol,
29
18
  detectStyle,
30
19
  emptySource,
@@ -35,34 +24,25 @@ import {
35
24
  gitignoreOptions,
36
25
  insertKey,
37
26
  integrityOf,
38
- isBuiltinField,
39
- isErrorSeverity,
40
27
  keyLines,
41
28
  keyString,
42
29
  lineEnd,
43
30
  loadConfig,
44
31
  loadSchema,
45
32
  locateFrontmatter,
46
- managedFields,
47
33
  positionForFactory,
48
34
  publishedBuiltins,
49
35
  rangeOf,
50
36
  rebaseConfigSchemaRefs,
51
37
  replaceBlockValue,
52
38
  replaceFlowValue,
53
- resolveBaselineRequest,
54
39
  resolveElements,
55
- resolveRunConfig,
56
40
  resolveSchemaSetWithSource,
57
41
  resolveTargetSet,
58
42
  schemaLoadOptions,
59
43
  schemaTrustRoot,
60
- settleBaseline,
61
- staleFindings,
62
- supportedExtensions,
63
- toJsonText,
64
44
  writeFileAtomic
65
- } from "./chunk-G7XD4QH5.js";
45
+ } from "./chunk-VSEBFHAY.js";
66
46
  import {
67
47
  STDIN_LINES_MARKER
68
48
  } from "./chunk-YEUYKR62.js";
@@ -87,104 +67,40 @@ import {
87
67
  pageManifestPath,
88
68
  parseCollections,
89
69
  sameFile
90
- } from "./chunk-6M5VJOC2.js";
70
+ } from "./chunk-QLWC7MKD.js";
91
71
  import {
92
72
  ToolError,
93
73
  errorMessage
94
74
  } from "./chunk-G3T47CFV.js";
95
75
 
96
- // src/meta/commands/validate.ts
97
- import { readFile as readFile6 } from "node:fs/promises";
98
-
99
- // src/meta/core/external-metadata.ts
100
- import { readFile as readFile3 } from "node:fs/promises";
101
- import { isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
102
- import {
103
- LineCounter,
104
- isMap as isMap2,
105
- isNode,
106
- isScalar,
107
- isSeq,
108
- parseDocument as parseDocument2
109
- } from "yaml";
110
-
111
- // src/shared/manifest-cas.ts
112
- import { readFile } from "node:fs/promises";
113
- import { posix } from "node:path";
114
- var COMMIT_ATTEMPTS = 5;
115
- function conflictRefusal(file) {
116
- return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
117
- }
118
- function heldManifest(path, file, before, absent = false) {
119
- return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map(), absent };
120
- }
121
- function encodeValue(value) {
122
- return value === void 0 ? "absent" : JSON.stringify(value);
123
- }
124
- function baseKey(at2) {
125
- const entry = at2.join === "path" ? posix.normalize(at2.entry) : at2.entry;
126
- return `${entry}\0${at2.key}`;
127
- }
128
- function rebaseable(held, op, codec) {
129
- const at2 = baseKey(op);
130
- if (held.bases.has(at2)) return;
131
- held.bases.set(at2, {
132
- entry: op.entry,
133
- key: op.key,
134
- join: op.join,
135
- value: encodeValue(codec.read(held.before, op))
136
- });
76
+ // src/shared/cli-options.ts
77
+ import { basename, relative } from "node:path";
78
+ function collect(value, prev) {
79
+ return prev.concat([value]);
137
80
  }
138
- function rebase(held, current, codec) {
139
- for (const base of held.bases.values()) {
140
- if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
141
- }
142
- let text2 = current;
143
- for (const op of held.ops) text2 = codec.apply(text2, op);
144
- return text2;
81
+ function splitList(value) {
82
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
145
83
  }
146
- function rebaseline(held) {
147
- held.before = held.text;
148
- held.ops = [];
149
- held.bases.clear();
150
- held.absent = false;
84
+ var STDIN_REQUIRES_AS = "Reading from stdin (`-`) requires --as <format> to choose an extractor.";
85
+ async function readStdin() {
86
+ const chunks = [];
87
+ for await (const chunk of process.stdin) chunks.push(chunk);
88
+ return Buffer.concat(chunks).toString("utf8");
151
89
  }
152
- async function settle(held, io) {
153
- for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
154
- let current;
155
- try {
156
- current = await io.read(held.path);
157
- } catch (error) {
158
- if (held.absent && isMissing(error)) {
159
- await io.write(held.path, held.text);
160
- held.absent = false;
161
- return;
162
- }
163
- const reason = error instanceof Error ? error.message : String(error);
164
- throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
165
- }
166
- if (!held.absent && current === held.before) {
167
- await io.write(held.path, held.text);
168
- return;
169
- }
170
- const replayed = rebase(held, current, io.codec);
171
- if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
172
- held.before = current;
173
- held.text = replayed;
174
- held.absent = false;
175
- }
176
- throw io.toError(conflictRefusal(held.file));
90
+ function configOption(value) {
91
+ if (value === false) return { noConfig: true };
92
+ return typeof value === "string" ? { configPath: value } : {};
177
93
  }
178
- function isMissing(error) {
179
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
94
+ function explicitFalse(value) {
95
+ return value === false ? false : void 0;
180
96
  }
181
- async function readManifestOrEmpty(absPath, perPage) {
182
- try {
183
- return await readFile(absPath, "utf8");
184
- } catch (e) {
185
- if (perPage && isMissing(e)) return "";
186
- throw e;
187
- }
97
+ function reportConfig(toStdout, cwd) {
98
+ return (info) => {
99
+ const where = (relative(cwd, info.dir) || ".").replace(/\\/g, "/");
100
+ const line = `Using ${basename(info.path)} (${where})
101
+ `;
102
+ (toStdout ? process.stdout : process.stderr).write(line);
103
+ };
188
104
  }
189
105
 
190
106
  // src/meta/core/external-metadata-fetch.ts
@@ -280,29 +196,29 @@ async function readCapped(url, res, maxBytes) {
280
196
 
281
197
  // src/shared/encryption-key.ts
282
198
  import { spawnSync } from "node:child_process";
283
- import { readFile as readFile2 } from "node:fs/promises";
284
- import { basename as basename2, dirname as dirname2, join as join2, relative, resolve } from "node:path";
199
+ import { readFile } from "node:fs/promises";
200
+ import { basename as basename3, dirname as dirname2, join as join2, relative as relative2, resolve } from "node:path";
285
201
  import { isDeepStrictEqual } from "node:util";
286
202
  import { isMap, parseDocument, parse as parseYaml, stringify } from "yaml";
287
203
 
288
204
  // src/shared/write-file.ts
289
205
  import { chmod, rename, rm, stat, writeFile } from "node:fs/promises";
290
- import { basename, dirname, join } from "node:path";
206
+ import { basename as basename2, dirname, join } from "node:path";
291
207
  var LOCKED = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
292
208
  var RENAME_ATTEMPTS = 3;
293
209
  function errorCode(err) {
294
210
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
295
211
  }
296
- var wait = (ms) => new Promise((resolve8) => {
297
- setTimeout(resolve8, ms);
212
+ var wait = (ms) => new Promise((resolve4) => {
213
+ setTimeout(resolve4, ms);
298
214
  });
299
- async function writeTextAtomic(path, text2) {
215
+ async function writeTextAtomic(path, text) {
300
216
  const tmp = join(
301
217
  dirname(path),
302
- `.${basename(path)}.manni-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`
218
+ `.${basename2(path)}.manni-${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`
303
219
  );
304
220
  try {
305
- await writeFile(tmp, text2, "utf8");
221
+ await writeFile(tmp, text, "utf8");
306
222
  try {
307
223
  const { mode } = await stat(path);
308
224
  await chmod(tmp, mode);
@@ -343,14 +259,14 @@ function resolveEncryptionKey(opts) {
343
259
  return { key: void 0, source: "none" };
344
260
  }
345
261
  function relativeSource(cwd, path) {
346
- return relative(resolve(cwd), path).replace(/\\/g, "/");
262
+ return relative2(resolve(cwd), path).replace(/\\/g, "/");
347
263
  }
348
264
  function errorCode2(err) {
349
265
  return typeof err === "object" && err !== null && "code" in err ? String(err.code) : void 0;
350
266
  }
351
267
  async function readIfPresent(path) {
352
268
  try {
353
- return await readFile2(path, "utf8");
269
+ return await readFile(path, "utf8");
354
270
  } catch (err) {
355
271
  if (errorCode2(err) === "ENOENT") return null;
356
272
  throw err;
@@ -362,21 +278,21 @@ async function fileTarget(file, toError) {
362
278
  `${file.source} is a single-tool config file, which cannot hold the family ${ENCRYPTION_KEY_FIELD}. Move its keys under the tool's section in ${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}, or set ${ENCRYPTION_KEY_ENV}.`
363
279
  );
364
280
  }
365
- const text2 = await readIfPresent(file.path);
366
- return { path: file.path, source: file.source, text: text2 ?? file.text };
281
+ const text = await readIfPresent(file.path);
282
+ return { path: file.path, source: file.source, text: text ?? file.text };
367
283
  }
368
284
  async function defaultTarget(cwd) {
369
285
  const root = findGitRoot(cwd) ?? resolve(cwd);
370
286
  for (const name of FAMILY_CONFIG_NAMES) {
371
287
  const path2 = join2(root, name);
372
- const text2 = await readIfPresent(path2);
373
- if (text2 !== null) return { path: path2, source: relativeSource(cwd, path2), text: text2 };
288
+ const text = await readIfPresent(path2);
289
+ if (text !== null) return { path: path2, source: relativeSource(cwd, path2), text };
374
290
  }
375
291
  const path = join2(root, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml");
376
292
  return { path, source: relativeSource(cwd, path), text: null };
377
293
  }
378
- function withKey(text2, key, previous, source, toError) {
379
- const doc = parseDocument(text2);
294
+ function withKey(text, key, previous, source, toError) {
295
+ const doc = parseDocument(text);
380
296
  const [first] = doc.errors;
381
297
  if (first !== void 0) {
382
298
  throw toError(`${source}: invalid YAML: ${first.message}`);
@@ -421,16 +337,16 @@ async function writeEncryptionKey(opts) {
421
337
  );
422
338
  }
423
339
  const target = file === null ? await defaultTarget(opts.cwd) : await fileTarget(file, toError);
424
- const text2 = target.text === null ? stringify({
340
+ const text = target.text === null ? stringify({
425
341
  [ENCRYPTION_KEY_FIELD]: key,
426
342
  ...typeof previous === "string" ? { [ENCRYPTION_KEY_PREVIOUS_FIELD]: previous } : {}
427
343
  }) : withKey(target.text, key, previous, target.source, toError);
428
- verify(target.text, text2, key, previous, target.source, toError);
429
- if (opts.dryRun !== true) await writeTextAtomic(target.path, text2);
344
+ verify(target.text, text, key, previous, target.source, toError);
345
+ if (opts.dryRun !== true) await writeTextAtomic(target.path, text);
430
346
  return { path: target.path, source: target.source, created: target.text === null };
431
347
  }
432
348
  function isIgnoredByGit(path) {
433
- const result = spawnSync("git", ["check-ignore", "-q", "--", basename2(path)], {
349
+ const result = spawnSync("git", ["check-ignore", "-q", "--", basename3(path)], {
434
350
  cwd: dirname2(path),
435
351
  stdio: "ignore",
436
352
  windowsHide: true
@@ -491,8 +407,8 @@ function valueAt(data, pointer) {
491
407
  let node = data;
492
408
  for (const seg of segments(pointer)) {
493
409
  if (Array.isArray(node)) {
494
- const items2 = node;
495
- node = /^\d+$/.test(seg) ? items2[Number(seg)] : void 0;
410
+ const items = node;
411
+ node = /^\d+$/.test(seg) ? items[Number(seg)] : void 0;
496
412
  } else if (isRecord(node)) {
497
413
  node = Object.hasOwn(node, seg) ? node[seg] : void 0;
498
414
  } else {
@@ -507,10 +423,10 @@ function withValueAt(root, pointer, value) {
507
423
  const seg = parts[i];
508
424
  if (seg === void 0) return value;
509
425
  if (Array.isArray(node)) {
510
- const items2 = [...node];
511
- if (!/^\d+$/.test(seg) || Number(seg) >= items2.length) return node;
512
- items2[Number(seg)] = set(items2[Number(seg)], i + 1);
513
- return items2;
426
+ const items = [...node];
427
+ if (!/^\d+$/.test(seg) || Number(seg) >= items.length) return node;
428
+ items[Number(seg)] = set(items[Number(seg)], i + 1);
429
+ return items;
514
430
  }
515
431
  if (isRecord(node) && Object.hasOwn(node, seg)) {
516
432
  return { ...node, [seg]: set(node[seg], i + 1) };
@@ -647,6 +563,97 @@ function redactedDerived(f) {
647
563
  };
648
564
  }
649
565
 
566
+ // src/meta/core/external-metadata.ts
567
+ import { readFile as readFile3 } from "node:fs/promises";
568
+ import { isAbsolute, relative as relative3, resolve as resolve2, sep } from "node:path";
569
+ import {
570
+ LineCounter,
571
+ isMap as isMap2,
572
+ isNode,
573
+ isScalar,
574
+ isSeq,
575
+ parseDocument as parseDocument2
576
+ } from "yaml";
577
+
578
+ // src/shared/manifest-cas.ts
579
+ import { readFile as readFile2 } from "node:fs/promises";
580
+ import { posix } from "node:path";
581
+ var COMMIT_ATTEMPTS = 5;
582
+ function conflictRefusal(file) {
583
+ return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
584
+ }
585
+ function heldManifest(path, file, before, absent = false) {
586
+ return { path, file, before, text: before, ops: [], bases: /* @__PURE__ */ new Map(), absent };
587
+ }
588
+ function encodeValue(value) {
589
+ return value === void 0 ? "absent" : JSON.stringify(value);
590
+ }
591
+ function baseKey(at2) {
592
+ const entry = at2.join === "path" ? posix.normalize(at2.entry) : at2.entry;
593
+ return `${entry}\0${at2.key}`;
594
+ }
595
+ function rebaseable(held, op, codec) {
596
+ const at2 = baseKey(op);
597
+ if (held.bases.has(at2)) return;
598
+ held.bases.set(at2, {
599
+ entry: op.entry,
600
+ key: op.key,
601
+ join: op.join,
602
+ value: encodeValue(codec.read(held.before, op))
603
+ });
604
+ }
605
+ function rebase(held, current, codec) {
606
+ for (const base of held.bases.values()) {
607
+ if (encodeValue(codec.read(current, base)) !== base.value) return void 0;
608
+ }
609
+ let text = current;
610
+ for (const op of held.ops) text = codec.apply(text, op);
611
+ return text;
612
+ }
613
+ function rebaseline(held) {
614
+ held.before = held.text;
615
+ held.ops = [];
616
+ held.bases.clear();
617
+ held.absent = false;
618
+ }
619
+ async function settle(held, io) {
620
+ for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
621
+ let current;
622
+ try {
623
+ current = await io.read(held.path);
624
+ } catch (error) {
625
+ if (held.absent && isMissing(error)) {
626
+ await io.write(held.path, held.text);
627
+ held.absent = false;
628
+ return;
629
+ }
630
+ const reason = error instanceof Error ? error.message : String(error);
631
+ throw io.toError(`Manifest ${held.file} could not be read: ${reason}`);
632
+ }
633
+ if (!held.absent && current === held.before) {
634
+ await io.write(held.path, held.text);
635
+ return;
636
+ }
637
+ const replayed = rebase(held, current, io.codec);
638
+ if (replayed === void 0) throw io.toError(conflictRefusal(held.file));
639
+ held.before = current;
640
+ held.text = replayed;
641
+ held.absent = false;
642
+ }
643
+ throw io.toError(conflictRefusal(held.file));
644
+ }
645
+ function isMissing(error) {
646
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
647
+ }
648
+ async function readManifestOrEmpty(absPath, perPage) {
649
+ try {
650
+ return await readFile2(absPath, "utf8");
651
+ } catch (e) {
652
+ if (perPage && isMissing(e)) return "";
653
+ throw e;
654
+ }
655
+ }
656
+
650
657
  // src/meta/core/external-metadata.ts
651
658
  var EXTERNAL_OWNED_SCHEMA = "external:owned";
652
659
  var EXTERNAL_DUPLICATE_SCHEMA = "external:duplicate";
@@ -658,7 +665,7 @@ var perPageRules = /* @__PURE__ */ new WeakMap();
658
665
  var strayEntries = /* @__PURE__ */ new WeakSet();
659
666
  var posix2 = (p) => p.split(sep).join("/");
660
667
  function reportedPath(abs, base) {
661
- const rel = relative2(base, abs);
668
+ const rel = relative3(base, abs);
662
669
  return rel === "" ? "." : posix2(rel);
663
670
  }
664
671
  function reported(of, file) {
@@ -689,18 +696,18 @@ async function loadExternalMetadata(collections, opts) {
689
696
  rules.push({ collection: declared, manifest, configDir: opts.configDir });
690
697
  continue;
691
698
  }
692
- const join5 = externalMetadataJoin(manifest);
699
+ const join4 = externalMetadataJoin(manifest);
693
700
  let parsed;
694
701
  let file;
695
702
  if (classifyRef(manifest.file).kind === "url") {
696
703
  file = manifest.file;
697
- const text2 = await fetchExternalMetadata(manifest.file, {
704
+ const text = await fetchExternalMetadata(manifest.file, {
698
705
  ...manifest.tokenEnv !== void 0 ? { tokenEnv: manifest.tokenEnv } : {},
699
706
  ...opts.offline !== void 0 ? { offline: opts.offline } : {},
700
707
  ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
701
708
  ...opts.env !== void 0 ? { env: opts.env } : {}
702
709
  });
703
- parsed = parseManifest(manifest, collection, text2, file, opts.configDir, join5, ownership(declared, manifest));
710
+ parsed = parseManifest(manifest, collection, text, file, opts.configDir, join4, ownership(declared, manifest));
704
711
  } else {
705
712
  const abs = isAbsolute(manifest.file) ? manifest.file : resolve2(opts.configDir, manifest.file);
706
713
  const label = reportedPath(abs, opts.base);
@@ -716,7 +723,7 @@ async function loadExternalMetadata(collections, opts) {
716
723
  // this run's spelling onto every value and entry instead.
717
724
  JSON.stringify([
718
725
  collection,
719
- join5,
726
+ join4,
720
727
  opts.configDir,
721
728
  variantOf(declared, manifest)
722
729
  ]),
@@ -726,7 +733,7 @@ async function loadExternalMetadata(collections, opts) {
726
733
  await readManifest(abs, label),
727
734
  label,
728
735
  opts.configDir,
729
- join5,
736
+ join4,
730
737
  ownership(declared, manifest)
731
738
  )
732
739
  );
@@ -740,14 +747,14 @@ async function loadExternalMetadata(collections, opts) {
740
747
  else owners.set(key, [{ collection, file }]);
741
748
  }
742
749
  let target;
743
- if (join5 === PATH_JOIN) {
750
+ if (join4 === PATH_JOIN) {
744
751
  target = byPath;
745
752
  } else {
746
- const existing = byField.get(join5);
753
+ const existing = byField.get(join4);
747
754
  if (existing) target = existing;
748
755
  else {
749
756
  target = /* @__PURE__ */ new Map();
750
- byField.set(join5, target);
757
+ byField.set(join4, target);
751
758
  }
752
759
  }
753
760
  for (const [indexKey, values] of parsed.values) {
@@ -782,14 +789,14 @@ function variantOf(declared, manifest) {
782
789
  }
783
790
  async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
784
791
  const collection = declared.name;
785
- const join5 = externalMetadataJoin(manifest);
792
+ const join4 = externalMetadataJoin(manifest);
786
793
  const implied = manifest.keys === void 0 ? { collection: declared, manifest, file: manifest.file, files: /* @__PURE__ */ new Set() } : void 0;
787
794
  for (const key of manifest.keys ?? []) {
788
795
  const list2 = into.owners.get(key);
789
796
  if (list2) list2.push({ collection, file: manifest.file });
790
797
  else into.owners.set(key, [{ collection, file: manifest.file }]);
791
798
  }
792
- const target = indexFor(join5, into);
799
+ const target = indexFor(join4, into);
793
800
  for (const pageAbs of opts.pages ?? []) {
794
801
  const resolved = pageManifestPath(manifest.file, opts.configDir, pageAbs);
795
802
  if ("outside" in resolved) {
@@ -798,7 +805,7 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
798
805
  }
799
806
  continue;
800
807
  }
801
- const rel = posix2(relative2(opts.configDir, pageAbs));
808
+ const rel = posix2(relative3(opts.configDir, pageAbs));
802
809
  if (!isMember(declared, rel)) continue;
803
810
  const earlier = resolvedBy.get(resolved.abs);
804
811
  if (earlier !== void 0 && earlier !== pageAbs) {
@@ -811,14 +818,14 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
811
818
  const label = reportedPath(resolved.abs, opts.base);
812
819
  const parsed = await parsedManifests.parse(
813
820
  resolved.abs,
814
- JSON.stringify([collection, join5, opts.configDir, variantOf(declared, manifest)]),
821
+ JSON.stringify([collection, join4, opts.configDir, variantOf(declared, manifest)]),
815
822
  async () => parseManifest(
816
823
  manifest,
817
824
  collection,
818
825
  await readPageManifest(resolved.abs, label),
819
826
  label,
820
827
  opts.configDir,
821
- join5,
828
+ join4,
822
829
  ownership(declared, manifest)
823
830
  )
824
831
  );
@@ -832,7 +839,7 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
832
839
  );
833
840
  }
834
841
  for (const [indexKey, values] of parsed.values) {
835
- const key = join5 === PATH_JOIN ? pageAbs : indexKey;
842
+ const key = join4 === PATH_JOIN ? pageAbs : indexKey;
836
843
  const merged = target.get(key) ?? /* @__PURE__ */ new Map();
837
844
  for (const [owned, value] of values) merged.set(owned, reported(value, label));
838
845
  target.set(key, merged);
@@ -846,12 +853,12 @@ async function loadPerPage(declared, manifest, opts, resolvedBy, into) {
846
853
  }
847
854
  return implied;
848
855
  }
849
- function indexFor(join5, into) {
850
- if (join5 === PATH_JOIN) return into.byPath;
851
- const existing = into.byField.get(join5);
856
+ function indexFor(join4, into) {
857
+ if (join4 === PATH_JOIN) return into.byPath;
858
+ const existing = into.byField.get(join4);
852
859
  if (existing) return existing;
853
860
  const created = /* @__PURE__ */ new Map();
854
- into.byField.set(join5, created);
861
+ into.byField.set(join4, created);
855
862
  return created;
856
863
  }
857
864
  function outsideRefusal(manifest, collection, pageRel) {
@@ -881,11 +888,11 @@ function unreadable(file, err) {
881
888
  const reason = err instanceof Error ? err.message : String(err);
882
889
  return new DocmetaError(`Manifest ${file} could not be read: ${reason}`);
883
890
  }
884
- function parseManifest(manifest, collection, text2, file, configDir, join5, owns) {
891
+ function parseManifest(manifest, collection, text, file, configDir, join4, owns) {
885
892
  const target = /* @__PURE__ */ new Map();
886
893
  const entries = [];
887
894
  const lc = new LineCounter();
888
- const doc = parseDocument2(text2, { lineCounter: lc, uniqueKeys: false });
895
+ const doc = parseDocument2(text, { lineCounter: lc, uniqueKeys: false });
889
896
  const problem = doc.errors[0];
890
897
  if (problem) {
891
898
  throw new DocmetaError(
@@ -897,7 +904,7 @@ function parseManifest(manifest, collection, text2, file, configDir, join5, owns
897
904
  return { values: target, entries };
898
905
  if (!isMap2(root)) {
899
906
  throw new DocmetaError(
900
- `Manifest ${file}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
907
+ `Manifest ${file}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
901
908
  );
902
909
  }
903
910
  const lineAt = (node) => {
@@ -921,7 +928,7 @@ function parseManifest(manifest, collection, text2, file, configDir, join5, owns
921
928
  `Manifest ${where}: "${spelled}" must be a mapping of owned keys to values.`
922
929
  );
923
930
  }
924
- const indexKey = join5 === PATH_JOIN ? resolve2(configDir, spelled) : spelled;
931
+ const indexKey = join4 === PATH_JOIN ? resolve2(configDir, spelled) : spelled;
925
932
  const values = target.get(indexKey) ?? /* @__PURE__ */ new Map();
926
933
  for (const kv of pair.value.items) {
927
934
  const key = isScalar(kv.key) ? String(kv.key.value) : String(kv.key);
@@ -947,8 +954,8 @@ function parseManifest(manifest, collection, text2, file, configDir, join5, owns
947
954
  target.set(indexKey, values);
948
955
  entries.push({
949
956
  collection,
950
- join: join5,
951
- ...join5 === PATH_JOIN ? { abs: indexKey } : {},
957
+ join: join4,
958
+ ...join4 === PATH_JOIN ? { abs: indexKey } : {},
952
959
  spelled,
953
960
  file,
954
961
  ...entryLine === void 0 ? {} : { line: entryLine }
@@ -1209,6 +1216,9 @@ function isFieldLocation(value) {
1209
1216
  return value === "page" || value === "external";
1210
1217
  }
1211
1218
 
1219
+ // src/meta/core/graph-output.ts
1220
+ var GRAPH_OUTPUT_KEYWORD = "x-manni-graph-output";
1221
+
1212
1222
  // src/meta/core/validator.ts
1213
1223
  import * as AjvDraft07Ns from "ajv";
1214
1224
  import * as Ajv2019Ns from "ajv/dist/2019.js";
@@ -1216,11 +1226,6 @@ import * as Ajv2020Ns from "ajv/dist/2020.js";
1216
1226
  import * as AjvDraft04Ns from "ajv-draft-04";
1217
1227
  import * as addFormatsNs from "ajv-formats";
1218
1228
  import { createRequire } from "node:module";
1219
-
1220
- // src/meta/core/graph-output.ts
1221
- var GRAPH_OUTPUT_KEYWORD = "x-manni-graph-output";
1222
-
1223
- // src/meta/core/validator.ts
1224
1229
  var draft06MetaSchema = createRequire(import.meta.url)(
1225
1230
  "ajv/dist/refs/json-schema-draft-06.json"
1226
1231
  );
@@ -1835,23 +1840,6 @@ function familyMarks(opts) {
1835
1840
  })();
1836
1841
  }
1837
1842
 
1838
- // src/meta/core/relocation.ts
1839
- import { access, readFile as readFile4, stat as stat2, unlink } from "node:fs/promises";
1840
- import { basename as basename3, extname, isAbsolute as isAbsolute3, join as join3, relative as relative4, resolve as resolve3, sep as sep3 } from "node:path";
1841
- import picomatch from "picomatch";
1842
- import {
1843
- LineCounter as LineCounter3,
1844
- isMap as isMap4,
1845
- isNode as isNode3,
1846
- isScalar as isScalar3,
1847
- isSeq as isSeq2,
1848
- parse as parseYaml2,
1849
- parseDocument as parseDocument4
1850
- } from "yaml";
1851
-
1852
- // src/meta/core/collections.ts
1853
- import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolvePath, sep as sep2 } from "node:path";
1854
-
1855
1843
  // src/meta/core/projection.ts
1856
1844
  var SYSTEM_COLUMNS = ["_path", "_format", "_present", "_data"];
1857
1845
  var RESERVED = new Set(SYSTEM_COLUMNS);
@@ -1982,7 +1970,7 @@ function isParamName(name) {
1982
1970
  return PARAM_NAME_HEAD.exec(name)?.[0] === name;
1983
1971
  }
1984
1972
  function collectNamedParameters(sql) {
1985
- const tokens2 = [];
1973
+ const tokens = [];
1986
1974
  const seen = /* @__PURE__ */ new Set();
1987
1975
  const prefixOf = /* @__PURE__ */ new Map();
1988
1976
  let i = 0;
@@ -2012,14 +2000,14 @@ function collectNamedParameters(sql) {
2012
2000
  prefixOf.set(m[0], ch);
2013
2001
  if (!seen.has(token)) {
2014
2002
  seen.add(token);
2015
- tokens2.push(token);
2003
+ tokens.push(token);
2016
2004
  }
2017
2005
  i += 1 + m[0].length;
2018
2006
  } else {
2019
2007
  i++;
2020
2008
  }
2021
2009
  }
2022
- return tokens2;
2010
+ return tokens;
2023
2011
  }
2024
2012
  function stripLeadingTrivia(sql) {
2025
2013
  let i = 0;
@@ -2032,16 +2020,16 @@ function stripLeadingTrivia(sql) {
2032
2020
  return sql.slice(i);
2033
2021
  }
2034
2022
  }
2035
- function isTrivia(text2) {
2023
+ function isTrivia(text) {
2036
2024
  let i = 0;
2037
- while (i < text2.length) {
2038
- const ch = text2[i];
2025
+ while (i < text.length) {
2026
+ const ch = text[i];
2039
2027
  if (ch === void 0 || /\s/.test(ch) || ch === ";") {
2040
2028
  i++;
2041
2029
  continue;
2042
2030
  }
2043
- if (startsComment(text2, i)) {
2044
- i = skipComment(text2, i);
2031
+ if (startsComment(text, i)) {
2032
+ i = skipComment(text, i);
2045
2033
  continue;
2046
2034
  }
2047
2035
  return false;
@@ -2053,8 +2041,8 @@ function loadSqlite() {
2053
2041
  sqliteModule ??= (async () => {
2054
2042
  const original = process.emitWarning.bind(process);
2055
2043
  const filtered = (warning, ...rest) => {
2056
- const text2 = typeof warning === "string" ? warning : warning.message;
2057
- if (text2.includes("SQLite is an experimental feature")) return;
2044
+ const text = typeof warning === "string" ? warning : warning.message;
2045
+ if (text.includes("SQLite is an experimental feature")) return;
2058
2046
  Reflect.apply(original, process, [warning, ...rest]);
2059
2047
  };
2060
2048
  process.emitWarning = filtered;
@@ -2068,10 +2056,11 @@ function loadSqlite() {
2068
2056
  }
2069
2057
 
2070
2058
  // src/meta/core/collections.ts
2059
+ import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolvePath, sep as sep2 } from "node:path";
2071
2060
  function memberOf(collections, configDir, base, label) {
2072
2061
  if (collections.length === 0) return [];
2073
2062
  if (label === STDIN_LABEL) return [];
2074
- const rel = relative3(configDir, resolvePath(base, label)).split(sep2).join("/");
2063
+ const rel = relative4(configDir, resolvePath(base, label)).split(sep2).join("/");
2075
2064
  if (isAbsolute2(rel)) return [];
2076
2065
  return collections.filter((c) => isMember(c, rel)).map((c) => c.name);
2077
2066
  }
@@ -2108,6 +2097,20 @@ function createCollectionViews(db, collections) {
2108
2097
  }
2109
2098
  }
2110
2099
 
2100
+ // src/meta/core/relocation.ts
2101
+ import { access, readFile as readFile4, stat as stat2, unlink } from "node:fs/promises";
2102
+ import { basename as basename4, extname, isAbsolute as isAbsolute3, join as join3, relative as relative5, resolve as resolve3, sep as sep3 } from "node:path";
2103
+ import picomatch from "picomatch";
2104
+ import {
2105
+ LineCounter as LineCounter3,
2106
+ isMap as isMap4,
2107
+ isNode as isNode3,
2108
+ isScalar as isScalar3,
2109
+ isSeq as isSeq2,
2110
+ parse as parseYaml2,
2111
+ parseDocument as parseDocument4
2112
+ } from "yaml";
2113
+
2111
2114
  // src/meta/core/external-metadata-write.ts
2112
2115
  import { posix as posix3 } from "node:path";
2113
2116
  import {
@@ -2118,9 +2121,9 @@ import {
2118
2121
  parseDocument as parseDocument3,
2119
2122
  stringify as stringify2
2120
2123
  } from "yaml";
2121
- function spliceManifestValue(text2, options) {
2124
+ function spliceManifestValue(text, options) {
2122
2125
  const { entry, key, value, file } = options;
2123
- const join5 = options.join ?? PATH_JOIN;
2126
+ const join4 = options.join ?? PATH_JOIN;
2124
2127
  const at2 = (line2) => {
2125
2128
  const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
2126
2129
  if (line2 === void 0) return name;
@@ -2134,9 +2137,9 @@ function spliceManifestValue(text2, options) {
2134
2137
  if (value === void 0) {
2135
2138
  throw new DocmetaError(`${at2()}: refusing to write "${key}" for "${entry}" with no value.`);
2136
2139
  }
2137
- const eol = detectEol(text2);
2140
+ const eol = detectEol(text);
2138
2141
  const lc = new LineCounter2();
2139
- const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
2142
+ const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
2140
2143
  const problem = doc.errors[0];
2141
2144
  if (problem) {
2142
2145
  throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
@@ -2145,18 +2148,18 @@ function spliceManifestValue(text2, options) {
2145
2148
  const r = rangeOf(node);
2146
2149
  return r ? lc.linePos(r[0]).line : void 0;
2147
2150
  };
2148
- const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2151
+ const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2149
2152
  const root = doc.contents;
2150
2153
  let spelled = entry;
2151
2154
  let edit;
2152
- if (root === null || isScalar2(root) && root.value === null && emptySource(text2, root)) {
2153
- edit = startManifest(text2, entry, key, value, eol);
2155
+ if (root === null || isScalar2(root) && root.value === null && emptySource(text, root)) {
2156
+ edit = startManifest(text, entry, key, value, eol);
2154
2157
  } else if (!isMap3(root)) {
2155
2158
  throw new DocmetaError(
2156
- `${at2()}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
2159
+ `${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
2157
2160
  );
2158
2161
  } else {
2159
- const style = detectStyle(text2, root, eol);
2162
+ const style = detectStyle(text, root, eol);
2160
2163
  const matches = root.items.filter((p) => same(keyString(p.key), entry));
2161
2164
  const [first, second] = matches;
2162
2165
  if (first && second) {
@@ -2170,7 +2173,7 @@ function spliceManifestValue(text2, options) {
2170
2173
  `${at2()}: the manifest is a flow mapping ({ \u2026 }), which this writer does not add entries to. Rewrite it in block style.`
2171
2174
  );
2172
2175
  }
2173
- edit = appendEntry(text2, root, entry, key, value, style);
2176
+ edit = appendEntry(text, root, entry, key, value, style);
2174
2177
  } else {
2175
2178
  spelled = keyString(first.key);
2176
2179
  const where = at2(keyLine(first.key));
@@ -2193,18 +2196,18 @@ function spliceManifestValue(text2, options) {
2193
2196
  );
2194
2197
  let made;
2195
2198
  if (v.flow === true) {
2196
- if (hit) made = replaceFlowValue(text2, hit, value);
2197
- else if (v.items.length === 0) made = fillEmptyEntry(text2, first, key, value, style);
2199
+ if (hit) made = replaceFlowValue(text, hit, value);
2200
+ else if (v.items.length === 0) made = fillEmptyEntry(text, first, key, value, style);
2198
2201
  else throw flowRefusal();
2199
2202
  if (!made) throw hit ? flowRefusal() : formRefusal();
2200
2203
  } else {
2201
- made = hit ? replaceBlockValue(text2, hit, value, style) : insertKey(text2, v, key, value, style);
2204
+ made = hit ? replaceBlockValue(text, hit, value, style) : insertKey(text, v, key, value, style);
2202
2205
  if (!made) throw formRefusal();
2203
2206
  }
2204
2207
  edit = made;
2205
2208
  }
2206
2209
  }
2207
- const out = text2.slice(0, edit.start) + edit.insert + text2.slice(edit.end);
2210
+ const out = text.slice(0, edit.start) + edit.insert + text.slice(edit.end);
2208
2211
  const line = readBack(
2209
2212
  doc,
2210
2213
  out,
@@ -2217,13 +2220,13 @@ function spliceManifestValue(text2, options) {
2217
2220
  );
2218
2221
  return { text: out, line };
2219
2222
  }
2220
- function readManifestValue(text2, options) {
2221
- const join5 = options.join ?? PATH_JOIN;
2222
- const doc = parseDocument3(text2, { uniqueKeys: false });
2223
+ function readManifestValue(text, options) {
2224
+ const join4 = options.join ?? PATH_JOIN;
2225
+ const doc = parseDocument3(text, { uniqueKeys: false });
2223
2226
  if (doc.errors.length > 0) return void 0;
2224
2227
  const root = doc.contents;
2225
2228
  if (!isMap3(root)) return void 0;
2226
- const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2229
+ const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2227
2230
  const pair = root.items.find((p) => same(keyString(p.key), options.entry));
2228
2231
  const held = pair?.value;
2229
2232
  if (!isMap3(held)) return void 0;
@@ -2231,17 +2234,17 @@ function readManifestValue(text2, options) {
2231
2234
  const node = kv?.value;
2232
2235
  return isNode2(node) ? node.toJSON() : void 0;
2233
2236
  }
2234
- function removeManifestKey(text2, options) {
2237
+ function removeManifestKey(text, options) {
2235
2238
  const { entry, key, file } = options;
2236
- const join5 = options.join ?? PATH_JOIN;
2239
+ const join4 = options.join ?? PATH_JOIN;
2237
2240
  const at2 = (line) => {
2238
2241
  const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
2239
2242
  if (line === void 0) return name;
2240
2243
  return file === void 0 ? `${name} line ${String(line)}` : `${name}:${String(line)}`;
2241
2244
  };
2242
- const eol = detectEol(text2);
2245
+ const eol = detectEol(text);
2243
2246
  const lc = new LineCounter2();
2244
- const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
2247
+ const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
2245
2248
  const problem = doc.errors[0];
2246
2249
  if (problem) {
2247
2250
  throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
@@ -2251,13 +2254,13 @@ function removeManifestKey(text2, options) {
2251
2254
  return r ? lc.linePos(r[0]).line : void 0;
2252
2255
  };
2253
2256
  const root = doc.contents;
2254
- if (root === null || isScalar2(root) && root.value === null) return { text: text2 };
2257
+ if (root === null || isScalar2(root) && root.value === null) return { text };
2255
2258
  if (!isMap3(root)) {
2256
2259
  throw new DocmetaError(
2257
- `${at2()}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
2260
+ `${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
2258
2261
  );
2259
2262
  }
2260
- const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2263
+ const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2261
2264
  const matches = root.items.filter((p) => same(keyString(p.key), entry));
2262
2265
  const [first, second] = matches;
2263
2266
  if (first && second) {
@@ -2265,7 +2268,7 @@ function removeManifestKey(text2, options) {
2265
2268
  `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
2266
2269
  );
2267
2270
  }
2268
- if (first === void 0) return { text: text2 };
2271
+ if (first === void 0) return { text };
2269
2272
  const spelled = keyString(first.key);
2270
2273
  const where = at2(keyLine(first.key));
2271
2274
  const v = first.value;
@@ -2279,7 +2282,7 @@ function removeManifestKey(text2, options) {
2279
2282
  `${at2(keyLine(again.key))}: "${spelled}" sets "${key}" twice (first at line ${String(keyLine(hit.key) ?? "?")}). Remove one.`
2280
2283
  );
2281
2284
  }
2282
- if (hit === void 0) return { text: text2 };
2285
+ if (hit === void 0) return { text };
2283
2286
  const whole = v.items.length === 1;
2284
2287
  if (!whole && v.flow === true) {
2285
2288
  throw new DocmetaError(
@@ -2287,18 +2290,18 @@ function removeManifestKey(text2, options) {
2287
2290
  );
2288
2291
  }
2289
2292
  const from = rangeOf(whole ? first.key : hit.key);
2290
- const to = whole ? contentEnd(text2, v) : contentEnd(text2, hit.value) ?? contentEnd(text2, hit.key);
2293
+ const to = whole ? contentEnd(text, v) : contentEnd(text, hit.value) ?? contentEnd(text, hit.key);
2291
2294
  if (!from || to === void 0) {
2292
2295
  throw new DocmetaError(
2293
2296
  `${where}: "${spelled}" writes a key in a form this writer does not edit. Write it as "${key}: \u2026".`
2294
2297
  );
2295
2298
  }
2296
- let { start, end } = keyLines(text2, from[0], to, eol);
2299
+ let { start, end } = keyLines(text, from[0], to, eol);
2297
2300
  if (whole) {
2298
- if (text2.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
2299
- else if (start === 0 && text2.startsWith(eol, end)) end += eol.length;
2301
+ if (text.slice(start - 2 * eol.length, start) === eol + eol) start -= eol.length;
2302
+ else if (start === 0 && text.startsWith(eol, end)) end += eol.length;
2300
2303
  }
2301
- const out = text2.slice(0, start) + text2.slice(end);
2304
+ const out = text.slice(0, start) + text.slice(end);
2302
2305
  const after = parseDocument3(out, { uniqueKeys: false });
2303
2306
  const fail = (why) => new DocmetaError(
2304
2307
  `${at2()}: removing "${key}" for "${entry}" did not read back as removed, so nothing was written. ${why}`
@@ -2317,16 +2320,16 @@ function removeManifestKey(text2, options) {
2317
2320
  if (!deepEqual(now ?? null, expected)) throw fail("Another value in the manifest would have changed.");
2318
2321
  return { text: out };
2319
2322
  }
2320
- function renameManifestEntry(text2, options) {
2323
+ function renameManifestEntry(text, options) {
2321
2324
  const { entry, to, file } = options;
2322
- const join5 = options.join ?? PATH_JOIN;
2325
+ const join4 = options.join ?? PATH_JOIN;
2323
2326
  const at2 = (line) => {
2324
2327
  const name = file === void 0 ? "Manifest" : `Manifest ${file}`;
2325
2328
  if (line === void 0) return name;
2326
2329
  return file === void 0 ? `${name} line ${String(line)}` : `${name}:${String(line)}`;
2327
2330
  };
2328
2331
  const lc = new LineCounter2();
2329
- const doc = parseDocument3(text2, { lineCounter: lc, uniqueKeys: false });
2332
+ const doc = parseDocument3(text, { lineCounter: lc, uniqueKeys: false });
2330
2333
  const problem = doc.errors[0];
2331
2334
  if (problem) {
2332
2335
  throw new DocmetaError(`${at2()} is not valid YAML: ${problem.message}`);
@@ -2336,20 +2339,20 @@ function renameManifestEntry(text2, options) {
2336
2339
  return r2 ? lc.linePos(r2[0]).line : void 0;
2337
2340
  };
2338
2341
  const root = doc.contents;
2339
- if (root === null || isScalar2(root) && root.value === null) return { text: text2 };
2342
+ if (root === null || isScalar2(root) && root.value === null) return { text };
2340
2343
  if (!isMap3(root)) {
2341
2344
  throw new DocmetaError(
2342
- `${at2()}: the manifest must be a mapping from document ${join5 === PATH_JOIN ? "path" : `"${join5}"`} to owned keys.`
2345
+ `${at2()}: the manifest must be a mapping from document ${join4 === PATH_JOIN ? "path" : `"${join4}"`} to owned keys.`
2343
2346
  );
2344
2347
  }
2345
- const same = join5 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2348
+ const same = join4 === PATH_JOIN ? (a, b) => posix3.normalize(a) === posix3.normalize(b) : (a, b) => a === b;
2346
2349
  const [first, second] = root.items.filter((p) => same(keyString(p.key), entry));
2347
2350
  if (first && second) {
2348
2351
  throw new DocmetaError(
2349
2352
  `${at2(keyLine(second.key))}: "${keyString(second.key)}" is named twice (first at line ${String(keyLine(first.key) ?? "?")}). Merge the two entries into one.`
2350
2353
  );
2351
2354
  }
2352
- if (first === void 0) return { text: text2 };
2355
+ if (first === void 0) return { text };
2353
2356
  const taken = root.items.find((p) => same(keyString(p.key), to));
2354
2357
  if (taken !== void 0) {
2355
2358
  throw new DocmetaError(
@@ -2358,8 +2361,8 @@ function renameManifestEntry(text2, options) {
2358
2361
  }
2359
2362
  const spelled = keyString(first.key);
2360
2363
  const r = rangeOf(first.key);
2361
- const end = contentEnd(text2, first.key);
2362
- if (!r || end === void 0 || colonEnd(text2, first) === void 0) {
2364
+ const end = contentEnd(text, first.key);
2365
+ if (!r || end === void 0 || colonEnd(text, first) === void 0) {
2363
2366
  throw new DocmetaError(
2364
2367
  `${at2(keyLine(first.key))}: "${spelled}" is written in a form this writer does not edit. Write it as "${spelled}: \u2026".`
2365
2368
  );
@@ -2367,7 +2370,7 @@ function renameManifestEntry(text2, options) {
2367
2370
  const quoted = isScalar2(first.key) && (first.key.type === "QUOTE_DOUBLE" || first.key.type === "QUOTE_SINGLE");
2368
2371
  const plain = stringify2(to, { lineWidth: 0 }).replace(/\n$/, "");
2369
2372
  const insert = quoted || plain.includes("\n") ? JSON.stringify(to) : plain;
2370
- const out = text2.slice(0, r[0]) + insert + text2.slice(end);
2373
+ const out = text.slice(0, r[0]) + insert + text.slice(end);
2371
2374
  const after = parseDocument3(out, { uniqueKeys: false });
2372
2375
  const fail = (why) => new DocmetaError(
2373
2376
  `${at2()}: renaming "${spelled}" to "${to}" did not read back as renamed, so nothing was written. ${why}`
@@ -2382,25 +2385,25 @@ function renameManifestEntry(text2, options) {
2382
2385
  if (!deepEqual(now, expected)) throw fail("Another value in the manifest would have changed.");
2383
2386
  return { text: out };
2384
2387
  }
2385
- function startManifest(text2, entry, key, value, eol) {
2388
+ function startManifest(text, entry, key, value, eol) {
2386
2389
  const style = { step: 2, indentSeq: true, eol };
2387
2390
  const block = blockLines({ [entry]: { [key]: value } }, 0, style);
2388
2391
  let insert;
2389
- if (text2 === "" || text2.endsWith("\n")) insert = block + eol;
2392
+ if (text === "" || text.endsWith("\n")) insert = block + eol;
2390
2393
  else insert = eol + block;
2391
- return { start: text2.length, end: text2.length, insert };
2394
+ return { start: text.length, end: text.length, insert };
2392
2395
  }
2393
- function appendEntry(text2, root, entry, key, value, style) {
2396
+ function appendEntry(text, root, entry, key, value, style) {
2394
2397
  const body = { [entry]: { [key]: value } };
2395
2398
  if (root.flow === true) {
2396
2399
  const r = rangeOf(root);
2397
- const start = r ? r[0] : text2.length;
2398
- const end = contentEnd(text2, root) ?? start;
2399
- return { start, end, insert: blockLines(body, colOf(text2, start), style) };
2400
+ const start = r ? r[0] : text.length;
2401
+ const end = contentEnd(text, root) ?? start;
2402
+ return { start, end, insert: blockLines(body, colOf(text, start), style) };
2400
2403
  }
2401
2404
  const firstKey = rangeOf(root.items[0]?.key);
2402
- const col = firstKey ? colOf(text2, firstKey[0]) : 0;
2403
- const pos = lineEnd(text2, contentEnd(text2, root) ?? text2.length);
2405
+ const col = firstKey ? colOf(text, firstKey[0]) : 0;
2406
+ const pos = lineEnd(text, contentEnd(text, root) ?? text.length);
2404
2407
  const last = root.items.at(-1);
2405
2408
  const spaced = root.items.length > 1 && isNode2(last?.key) && last.key.spaceBefore === true;
2406
2409
  return {
@@ -2409,12 +2412,12 @@ function appendEntry(text2, root, entry, key, value, style) {
2409
2412
  insert: style.eol + (spaced ? style.eol : "") + blockLines(body, col, style)
2410
2413
  };
2411
2414
  }
2412
- function fillEmptyEntry(text2, entryPair, key, value, style) {
2413
- const start = colonEnd(text2, entryPair);
2415
+ function fillEmptyEntry(text, entryPair, key, value, style) {
2416
+ const start = colonEnd(text, entryPair);
2414
2417
  const keyRange = rangeOf(entryPair.key);
2415
- const end = contentEnd(text2, entryPair.value);
2418
+ const end = contentEnd(text, entryPair.value);
2416
2419
  if (start === void 0 || !keyRange || end === void 0) return void 0;
2417
- const col = colOf(text2, keyRange[0]) + style.step;
2420
+ const col = colOf(text, keyRange[0]) + style.step;
2418
2421
  return { start, end, insert: style.eol + blockLines({ [key]: value }, col, style) };
2419
2422
  }
2420
2423
  function readBack(before, out, spelled, key, value, fail) {
@@ -2455,17 +2458,17 @@ function isRecord3(x) {
2455
2458
  function relocateFailed(result) {
2456
2459
  return result.summary.stayed > 0;
2457
2460
  }
2458
- function relocationContext(run2, extras) {
2461
+ function relocationContext(run, extras) {
2459
2462
  return {
2460
2463
  cwd: extras.cwd,
2461
- base: run2.base,
2464
+ base: run.base,
2462
2465
  noConfig: extras.noConfig ?? false,
2463
- config: run2.config,
2464
- ...run2.configDir !== void 0 ? { configDir: run2.configDir } : {},
2465
- ...run2.configPath !== void 0 ? { configPath: run2.configPath } : {},
2466
- ...run2.configFile !== void 0 ? { configFile: run2.configFile } : {},
2467
- collections: run2.collections,
2468
- declaredCollections: run2.declaredCollections,
2466
+ config: run.config,
2467
+ ...run.configDir !== void 0 ? { configDir: run.configDir } : {},
2468
+ ...run.configPath !== void 0 ? { configPath: run.configPath } : {},
2469
+ ...run.configFile !== void 0 ? { configFile: run.configFile } : {},
2470
+ collections: run.collections,
2471
+ declaredCollections: run.declaredCollections,
2469
2472
  targets: extras.targets.filter((t) => t !== STDIN_TOKEN),
2470
2473
  ...extras.cliSchemas !== void 0 ? { cliSchemas: extras.cliSchemas } : {},
2471
2474
  validator: extras.validator,
@@ -2477,11 +2480,11 @@ function relocationContext(run2, extras) {
2477
2480
  }
2478
2481
  var toPosix = (p) => p.split(sep3).join("/");
2479
2482
  function reported2(abs, base) {
2480
- const rel = relative4(base, abs);
2483
+ const rel = relative5(base, abs);
2481
2484
  return rel === "" ? "." : toPosix(rel);
2482
2485
  }
2483
2486
  function configSource(path, cwd) {
2484
- return toPosix(relative4(cwd, path)) || basename3(path);
2487
+ return toPosix(relative5(cwd, path)) || basename4(path);
2485
2488
  }
2486
2489
  function cloneCollection(c) {
2487
2490
  return {
@@ -2692,7 +2695,7 @@ var Model = class {
2692
2695
  /** A label relative to the config directory, posix; undefined outside it. */
2693
2696
  relative(label) {
2694
2697
  if (label === "<stdin>") return void 0;
2695
- const rel = toPosix(relative4(this.configDir, resolve3(this.ctx.base, label)));
2698
+ const rel = toPosix(relative5(this.configDir, resolve3(this.ctx.base, label)));
2696
2699
  return isAbsolute3(rel) || rel.startsWith("../") ? void 0 : rel;
2697
2700
  }
2698
2701
  };
@@ -2704,17 +2707,17 @@ function bare(path) {
2704
2707
  }
2705
2708
  async function collectionPath(target, cwd, configDir) {
2706
2709
  if (target === STDIN_TOKEN) return void 0;
2707
- const posix5 = target.replace(/\\/g, "/");
2708
- const scanned = picomatch.scan(posix5);
2710
+ const posix4 = target.replace(/\\/g, "/");
2711
+ const scanned = picomatch.scan(posix4);
2709
2712
  if (scanned.isGlob) {
2710
- const rel2 = toPosix(relative4(configDir, resolve3(cwd, scanned.base === "" ? "." : scanned.base)));
2713
+ const rel2 = toPosix(relative5(configDir, resolve3(cwd, scanned.base === "" ? "." : scanned.base)));
2711
2714
  if (isAbsolute3(rel2) || rel2 === ".." || rel2.startsWith("../")) return void 0;
2712
2715
  return rel2 === "" ? scanned.glob : `${rel2}/${scanned.glob}`;
2713
2716
  }
2714
- const abs = resolve3(cwd, posix5);
2715
- const rel = toPosix(relative4(configDir, abs));
2717
+ const abs = resolve3(cwd, posix4);
2718
+ const rel = toPosix(relative5(configDir, abs));
2716
2719
  if (isAbsolute3(rel) || rel.startsWith("..")) return void 0;
2717
- let directory = posix5.endsWith("/");
2720
+ let directory = posix4.endsWith("/");
2718
2721
  if (!directory) {
2719
2722
  try {
2720
2723
  directory = (await stat2(abs)).isDirectory();
@@ -2773,7 +2776,7 @@ function marksOf(ctx) {
2773
2776
  });
2774
2777
  }
2775
2778
  function entryFor(m, label, data, configDir, base, key) {
2776
- if (m.join === PATH_JOIN) return toPosix(relative4(configDir, resolve3(base, label)));
2779
+ if (m.join === PATH_JOIN) return toPosix(relative5(configDir, resolve3(base, label)));
2777
2780
  let raw = data[m.join];
2778
2781
  if (isEncryptedValue(raw)) {
2779
2782
  const k = key();
@@ -3089,9 +3092,9 @@ async function planRelocation(ctx, request) {
3089
3092
  }
3090
3093
  const orderedPages = () => {
3091
3094
  const all = [...pages.values()];
3092
- const run2 = all.filter((p) => !p.beyond).sort((a, b) => compare(a.label, b.label));
3095
+ const run = all.filter((p) => !p.beyond).sort((a, b) => compare(a.label, b.label));
3093
3096
  const beyond = all.filter((p) => p.beyond).sort((a, b) => compare(a.label, b.label));
3094
- return [...run2, ...beyond];
3097
+ return [...run, ...beyond];
3095
3098
  };
3096
3099
  let dirty = new Set(pages.values());
3097
3100
  while (dirty.size > 0) {
@@ -3153,20 +3156,20 @@ async function planRelocation(ctx, request) {
3153
3156
  for (const [path, { file, outs, ins }] of files) {
3154
3157
  if (!m.created && outs.length === 0 && ins.length === 0) continue;
3155
3158
  const disk = m.created ? null : await readManifestText(path, file, m.perPage);
3156
- let text2 = disk ?? "";
3157
- const before = text2;
3159
+ let text = disk ?? "";
3160
+ const before = text;
3158
3161
  const where = { join: m.join, file };
3159
3162
  for (const o of outs) {
3160
- if (!o.drop) text2 = spliceManifestValue(text2, { ...where, entry: o.entry, key: o.key, value: o.value }).text;
3163
+ if (!o.drop) text = spliceManifestValue(text, { ...where, entry: o.entry, key: o.key, value: o.value }).text;
3161
3164
  }
3162
- for (const i of ins) text2 = removeManifestKey(text2, { ...where, entry: i.entry, key: i.key }).text;
3163
- const found = keyLines2(text2, m.join);
3165
+ for (const i of ins) text = removeManifestKey(text, { ...where, entry: i.entry, key: i.key }).text;
3166
+ const found = keyLines2(text, m.join);
3164
3167
  for (const o of outs) {
3165
3168
  const line = found.get(entryId(o.entry, m.join))?.get(o.key);
3166
3169
  if (line !== void 0) lines.set(lineId(o.page, file, o.key), line);
3167
3170
  }
3168
- if (m.created || text2 !== before) {
3169
- writes.push({ path, text: text2, kind: "manifest", before: disk });
3171
+ if (m.created || text !== before) {
3172
+ writes.push({ path, text, kind: "manifest", before: disk });
3170
3173
  }
3171
3174
  }
3172
3175
  }
@@ -3180,9 +3183,9 @@ async function planRelocation(ctx, request) {
3180
3183
  `${ctx.configFile.source} is a single-tool config file, which cannot hold collections:. Move its keys under meta: in ${FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"}.`
3181
3184
  );
3182
3185
  }
3183
- const text2 = editConfig(target.text, model, configSource(target.path, ctx.cwd));
3184
- verifyConfig(target.text, text2, finalCollections, configSource(target.path, ctx.cwd));
3185
- writes.push({ path: target.path, text: text2, kind: "config", before: target.disk });
3186
+ const text = editConfig(target.text, model, configSource(target.path, ctx.cwd));
3187
+ verifyConfig(target.text, text, finalCollections, configSource(target.path, ctx.cwd));
3188
+ writes.push({ path: target.path, text, kind: "config", before: target.disk });
3186
3189
  configCreated = target.text === null;
3187
3190
  }
3188
3191
  for (const page of orderedPages()) {
@@ -3277,7 +3280,7 @@ function assertNoStrandedEntries(m, index, pages, configDir, fileOf) {
3277
3280
  const stranded = m.join === PATH_JOIN ? [...index.byPath].filter(([abs, values]) => {
3278
3281
  const sv = values.get(key);
3279
3282
  return sv !== void 0 && sv.collection === m.collection && sv.file === fileOf(abs);
3280
- }).map(([abs]) => toPosix(relative4(configDir, abs))) : [...index.byField.get(m.join) ?? /* @__PURE__ */ new Map()].filter(([, values]) => {
3283
+ }).map(([abs]) => toPosix(relative5(configDir, abs))) : [...index.byField.get(m.join) ?? /* @__PURE__ */ new Map()].filter(([, values]) => {
3281
3284
  const sv = values.get(key);
3282
3285
  return sv !== void 0 && sv.file === m.file && sv.collection === m.collection;
3283
3286
  }).map(([value]) => value);
@@ -3297,12 +3300,12 @@ async function readManifestText(path, file, perPage) {
3297
3300
  throw new DocmetaError(`Manifest ${file} could not be read: ${errorMessage(err)}`);
3298
3301
  }
3299
3302
  }
3300
- var entryId = (entry, join5) => join5 === PATH_JOIN ? toPosix(entry).replace(/^\.\//, "").replace(/\/+/g, "/") : entry;
3303
+ var entryId = (entry, join4) => join4 === PATH_JOIN ? toPosix(entry).replace(/^\.\//, "").replace(/\/+/g, "/") : entry;
3301
3304
  var lineId = (page, manifest, key) => `${page}\0${manifest}\0${key}`;
3302
- function keyLines2(text2, join5) {
3305
+ function keyLines2(text, join4) {
3303
3306
  const out = /* @__PURE__ */ new Map();
3304
3307
  const lc = new LineCounter3();
3305
- const doc = parseDocument4(text2, { lineCounter: lc, uniqueKeys: false });
3308
+ const doc = parseDocument4(text, { lineCounter: lc, uniqueKeys: false });
3306
3309
  const root = doc.contents;
3307
3310
  if (!isMap4(root)) return out;
3308
3311
  for (const pair of root.items) {
@@ -3314,15 +3317,15 @@ function keyLines2(text2, join5) {
3314
3317
  const range = isNode3(kv.key) ? kv.key.range : void 0;
3315
3318
  if (range) keys.set(name, lc.linePos(range[0]).line);
3316
3319
  }
3317
- out.set(entryId(spelled, join5), keys);
3320
+ out.set(entryId(spelled, join4), keys);
3318
3321
  }
3319
3322
  return out;
3320
3323
  }
3321
3324
  async function configTarget(ctx, configDir) {
3322
3325
  if (ctx.configPath !== void 0) {
3323
3326
  try {
3324
- const text2 = await readFile4(ctx.configPath, "utf8");
3325
- return { path: ctx.configPath, text: text2, disk: text2 };
3327
+ const text = await readFile4(ctx.configPath, "utf8");
3328
+ return { path: ctx.configPath, text, disk: text };
3326
3329
  } catch (err) {
3327
3330
  if (err.code !== "ENOENT") {
3328
3331
  throw new DocmetaError(
@@ -3335,15 +3338,15 @@ async function configTarget(ctx, configDir) {
3335
3338
  for (const name of FAMILY_CONFIG_NAMES) {
3336
3339
  const path = join3(configDir, name);
3337
3340
  try {
3338
- const text2 = await readFile4(path, "utf8");
3339
- return { path, text: text2, disk: text2 };
3341
+ const text = await readFile4(path, "utf8");
3342
+ return { path, text, disk: text };
3340
3343
  } catch {
3341
3344
  }
3342
3345
  }
3343
3346
  return { path: join3(configDir, FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml"), text: null, disk: null };
3344
3347
  }
3345
- function editConfig(text2, model, source) {
3346
- const doc = parseDocument4(text2 ?? "");
3348
+ function editConfig(text, model, source) {
3349
+ const doc = parseDocument4(text ?? "");
3347
3350
  const [problem] = doc.errors;
3348
3351
  if (problem !== void 0) throw new DocmetaError(`${source}: invalid YAML: ${problem.message}`);
3349
3352
  if (doc.contents !== null && !isMap4(doc.contents)) {
@@ -3515,7 +3518,7 @@ function buildResult(input) {
3515
3518
  };
3516
3519
  }
3517
3520
  async function applyRelocation(ctx, plan, io = {}) {
3518
- const write = io.write ?? ((path, text2) => writeFileAtomic(path, text2, { createParents: true }));
3521
+ const write = io.write ?? ((path, text) => writeFileAtomic(path, text, { createParents: true }));
3519
3522
  const remove = io.remove ?? ((path) => unlink(path));
3520
3523
  const label = (w) => w.kind === "config" ? configSource(w.path, ctx.cwd) : reported2(w.path, ctx.base);
3521
3524
  for (const w of plan.writes) {
@@ -3687,12 +3690,6 @@ function relocationOffers(plan) {
3687
3690
  return plan.offers;
3688
3691
  }
3689
3692
 
3690
- // src/meta/commands/validate.ts
3691
- import { resolve as resolve7, extname as extname3 } from "node:path";
3692
-
3693
- // src/meta/core/derive/provenance.ts
3694
- import picomatch2 from "picomatch";
3695
-
3696
3693
  // src/shared/pin.ts
3697
3694
  var LineRangeError = class extends ToolError {
3698
3695
  constructor(message) {
@@ -3700,12 +3697,12 @@ var LineRangeError = class extends ToolError {
3700
3697
  this.name = "LineRangeError";
3701
3698
  }
3702
3699
  };
3703
- function normalizeText(text2) {
3704
- const body = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
3700
+ function normalizeText(text) {
3701
+ const body = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
3705
3702
  return body.replace(/\r\n/g, "\n");
3706
3703
  }
3707
- function splitLines(text2) {
3708
- const parts = normalizeText(text2).split("\n");
3704
+ function splitLines(text) {
3705
+ const parts = normalizeText(text).split("\n");
3709
3706
  if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
3710
3707
  return parts;
3711
3708
  }
@@ -3722,8 +3719,8 @@ function sliceLines(lines, range, label) {
3722
3719
  }
3723
3720
  return lines.slice(start - 1, end).join("\n");
3724
3721
  }
3725
- function hashRange(text2, range, key) {
3726
- return hashLines(sliceLines(splitLines(text2), range), key);
3722
+ function hashRange(text, range, key) {
3723
+ return hashLines(sliceLines(splitLines(text), range), key);
3727
3724
  }
3728
3725
  function hashLines(joined, key) {
3729
3726
  return key === void 0 ? integrityOf(Buffer.from(joined, "utf8")) : keyedPin(joined, key);
@@ -3770,14 +3767,14 @@ function findWindows(lines, length, pin, key, opts) {
3770
3767
  for (let start = bandEnd + 1; start <= lastStart; start++) order.push(start);
3771
3768
  let spent = 0;
3772
3769
  for (const start of order) {
3773
- const text2 = joined(start);
3774
- const bytes = Buffer.byteLength(text2, "utf8");
3770
+ const text = joined(start);
3771
+ const bytes = Buffer.byteLength(text, "utf8");
3775
3772
  if (spent + bytes > budget) {
3776
3773
  if (counter !== void 0) counter.left = budget - spent;
3777
3774
  return { starts, truncated: true };
3778
3775
  }
3779
3776
  spent += bytes;
3780
- if (hashLines(text2, key) === pin) starts.push(start);
3777
+ if (hashLines(text, key) === pin) starts.push(start);
3781
3778
  }
3782
3779
  if (counter !== void 0) counter.left = budget - spent;
3783
3780
  return { starts, truncated: false };
@@ -3808,9 +3805,9 @@ function toBodyLines(lines, bodyLine) {
3808
3805
  return { start: lines.start - bodyLine + 1, end: lines.end - bodyLine + 1 };
3809
3806
  }
3810
3807
  function splitPageArgument(arg) {
3811
- const text2 = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
3812
- const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text2);
3813
- const head = m === null ? text2 : text2.slice(0, m.index);
3808
+ const text = arg.startsWith(STDIN_LINES_MARKER) ? `-${arg.slice(STDIN_LINES_MARKER.length)}` : arg;
3809
+ const m = /:([1-9][0-9]*)(?:-([1-9][0-9]*))?$/.exec(text);
3810
+ const head = m === null ? text : text.slice(0, m.index);
3814
3811
  if (m === null || head === "") return { page: arg };
3815
3812
  const start = Number(m[1]);
3816
3813
  const end = m[2] === void 0 ? start : Number(m[2]);
@@ -3821,6 +3818,7 @@ function splitPageArgument(arg) {
3821
3818
  }
3822
3819
 
3823
3820
  // src/meta/core/derive/provenance.ts
3821
+ import picomatch2 from "picomatch";
3824
3822
  var ZERO_SHA = "0000000000000000000000000000000000000000";
3825
3823
  var DEFAULT_MACHINES = ["*[bot]"];
3826
3824
  var PROVENANCE_POINTER = "/provenance";
@@ -3875,8 +3873,8 @@ function readProvenancePage(content, opts) {
3875
3873
  }
3876
3874
  var HEADER = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) ([1-9][0-9]*) ([1-9][0-9]*)(?: ([1-9][0-9]*))?$/;
3877
3875
  var ALL_ZERO = /^0+$/;
3878
- function parseLinePorcelain(text2) {
3879
- const rows = text2.split("\n");
3876
+ function parseLinePorcelain(text) {
3877
+ const rows = text.split("\n");
3880
3878
  if (rows.length > 0 && rows[rows.length - 1] === "") rows.pop();
3881
3879
  const out = [];
3882
3880
  let i = 0;
@@ -4299,2959 +4297,6 @@ function attributeRange(input) {
4299
4297
  return { "generated-by": input.generatedBy, lines: lineSpec(span), integrity: pinOf(page.body, span) };
4300
4298
  }
4301
4299
 
4302
- // src/meta/core/derive/config.ts
4303
- var DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
4304
- function commandsOf(config) {
4305
- if (config?.commands === void 0) return void 0;
4306
- const out = {};
4307
- for (const [field, c] of Object.entries(config.commands)) {
4308
- out[field] = {
4309
- run: c.run,
4310
- timeoutMs: c.timeout === void 0 ? DEFAULT_COMMAND_TIMEOUT_MS : Math.round(c.timeout * 1e3)
4311
- };
4312
- }
4313
- return out;
4314
- }
4315
- function machinesOf(config) {
4316
- return config?.machines ?? DEFAULT_MACHINES;
4317
- }
4318
-
4319
- // src/meta/core/derive/index.ts
4320
- import { dirname as dirname5, resolve as resolve5 } from "node:path";
4321
-
4322
- // src/meta/core/derive/cache.ts
4323
- import { createHash } from "node:crypto";
4324
- import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
4325
- import { join as join4 } from "node:path";
4326
- var REVIEW_CACHE_DIR = ".manni/meta/review-cache";
4327
- var REVIEW_CACHE_VERSION = 1;
4328
- var ReviewCache = class {
4329
- constructor(dir, enabled) {
4330
- this.dir = dir;
4331
- this.enabled = enabled;
4332
- }
4333
- dir;
4334
- enabled;
4335
- entryPath(key) {
4336
- const digest = createHash("sha256").update(key).digest("hex");
4337
- return join4(this.dir, `${digest}.json`);
4338
- }
4339
- /** The cached change, or `undefined` for a miss. Never `null`: nulls are not stored. */
4340
- get(key) {
4341
- if (!this.enabled) return void 0;
4342
- let raw;
4343
- try {
4344
- raw = JSON.parse(readFileSync(this.entryPath(key), "utf8"));
4345
- } catch {
4346
- return void 0;
4347
- }
4348
- if (!isRecord4(raw)) return void 0;
4349
- if (raw.version !== REVIEW_CACHE_VERSION || raw.key !== key) return void 0;
4350
- const change = readChange(raw.change);
4351
- return change ?? void 0;
4352
- }
4353
- set(key, value) {
4354
- if (!this.enabled) return;
4355
- const entry = { version: REVIEW_CACHE_VERSION, key, change: value };
4356
- const path = this.entryPath(key);
4357
- const tmp = `${path}.${process.pid}-${Math.random().toString(36).slice(2, 8)}.tmp`;
4358
- try {
4359
- mkdirSync(this.dir, { recursive: true });
4360
- writeFileSync(tmp, `${JSON.stringify(entry, null, 2)}
4361
- `, "utf8");
4362
- renameSync(tmp, path);
4363
- } catch {
4364
- try {
4365
- rmSync(tmp, { force: true });
4366
- } catch {
4367
- }
4368
- }
4369
- }
4370
- };
4371
- function cachedClient(client, cache, identity) {
4372
- return {
4373
- detect: () => client.detect(),
4374
- status: () => client.status(),
4375
- async mergedChangeFor(sha) {
4376
- if (identity === null) return client.mergedChangeFor(sha);
4377
- const key = `${identity.host}/${identity.project}/${sha}`;
4378
- const hit = cache.get(key);
4379
- if (hit !== void 0) return hit;
4380
- const change = await client.mergedChangeFor(sha);
4381
- if (change !== null) cache.set(key, change);
4382
- return change;
4383
- }
4384
- };
4385
- }
4386
- function isRecord4(v) {
4387
- return typeof v === "object" && v !== null && !Array.isArray(v);
4388
- }
4389
- function readChange(v) {
4390
- if (!isRecord4(v)) return null;
4391
- const { id, mergedAt, approvals } = v;
4392
- if (typeof id !== "number" || typeof mergedAt !== "string") return null;
4393
- if (!Array.isArray(approvals)) return null;
4394
- const read = [];
4395
- for (const a of approvals) {
4396
- if (!isRecord4(a)) return null;
4397
- if (typeof a.login !== "string" || typeof a.submittedAt !== "string") return null;
4398
- read.push({ login: a.login, submittedAt: a.submittedAt });
4399
- }
4400
- return { id, mergedAt, approvals: read };
4401
- }
4402
-
4403
- // src/meta/core/derive/codeowners.ts
4404
- import { existsSync } from "node:fs";
4405
- import { readFile as readFile5 } from "node:fs/promises";
4406
- import { dirname as dirname3, relative as relative5, resolve as resolve4, sep as sep4 } from "node:path";
4407
- import picomatch3 from "picomatch";
4408
- var CODEOWNERS_LOCATIONS = [
4409
- ".github/CODEOWNERS",
4410
- "CODEOWNERS",
4411
- "docs/CODEOWNERS",
4412
- ".gitlab/CODEOWNERS"
4413
- ];
4414
- function findCodeowners(root, explicit) {
4415
- if (explicit !== void 0) {
4416
- const path = resolve4(root, explicit);
4417
- return existsSync(path) ? path : null;
4418
- }
4419
- for (const loc of CODEOWNERS_LOCATIONS) {
4420
- const path = resolve4(root, ...loc.split("/"));
4421
- if (existsSync(path)) return path;
4422
- }
4423
- return null;
4424
- }
4425
- var SECTION_HEADER = /^(\^?)\[([^\]]+)\](?:\[(\d+)\])?(.*)$/;
4426
- var TOKEN = /(?:\\.|[^\s\\])+/g;
4427
- function parseCodeowners(text2, path, label) {
4428
- const rules = [];
4429
- const sections = [];
4430
- let current;
4431
- const lines = text2.split(/\r?\n/);
4432
- for (let i = 0; i < lines.length; i += 1) {
4433
- const raw = lines[i] ?? "";
4434
- const line = raw.trim();
4435
- if (line === "" || line.startsWith("#")) continue;
4436
- if (line.startsWith("[") || line.startsWith("^[")) {
4437
- const m = SECTION_HEADER.exec(line);
4438
- if (!m) continue;
4439
- const [, caret, rawName, approvals, rest] = m;
4440
- const name = (rawName ?? "").trim();
4441
- if (name === "") continue;
4442
- const owners2 = tokens(rest ?? "");
4443
- const existing = sections.find(
4444
- (s) => s.name.toLowerCase() === name.toLowerCase()
4445
- );
4446
- if (existing) {
4447
- if (owners2.length > 0) existing.defaultOwners = owners2;
4448
- current = existing;
4449
- continue;
4450
- }
4451
- const section = {
4452
- name,
4453
- optional: caret === "^",
4454
- ...approvals !== void 0 ? { approvals: Number(approvals) } : {},
4455
- defaultOwners: owners2
4456
- };
4457
- sections.push(section);
4458
- current = section;
4459
- continue;
4460
- }
4461
- const [first, ...owners] = tokens(line);
4462
- if (first === void 0) continue;
4463
- const pattern = unescapePattern(first);
4464
- if (pattern === "" || pattern === "/") continue;
4465
- rules.push({
4466
- pattern,
4467
- owners,
4468
- line: i + 1,
4469
- ...current ? { section: current.name } : {}
4470
- });
4471
- }
4472
- return { path, label, rules, sections };
4473
- }
4474
- function tokens(s) {
4475
- return s.match(TOKEN) ?? [];
4476
- }
4477
- function unescapePattern(token) {
4478
- return token.replace(/\\ /g, " ").replace(/^\\#/, "#");
4479
- }
4480
- function ownersFor(file, relPath) {
4481
- const matchers = matchersFor(file);
4482
- const defaults = new Map(file.sections.map((s) => [s.name, s.defaultOwners]));
4483
- const order = [void 0, ...file.sections.map((s) => s.name)];
4484
- const winners = /* @__PURE__ */ new Map();
4485
- file.rules.forEach((rule, i) => {
4486
- if (matchers[i]?.(relPath)) winners.set(rule.section, rule);
4487
- });
4488
- if (winners.size === 0) return null;
4489
- const owners = [];
4490
- let line = 0;
4491
- let lineFromOwner = false;
4492
- for (const section of order) {
4493
- const rule = winners.get(section);
4494
- if (!rule) continue;
4495
- const chosen = rule.owners.length > 0 ? rule.owners : (section === void 0 ? void 0 : defaults.get(section)) ?? [];
4496
- if (chosen.length > 0) {
4497
- line = rule.line;
4498
- lineFromOwner = true;
4499
- } else if (!lineFromOwner) {
4500
- line = rule.line;
4501
- }
4502
- for (const o of chosen) if (!owners.includes(o)) owners.push(o);
4503
- }
4504
- return { owners, line };
4505
- }
4506
- var matcherCache = /* @__PURE__ */ new WeakMap();
4507
- function matchersFor(file) {
4508
- let matchers = matcherCache.get(file);
4509
- if (!matchers) {
4510
- matchers = file.rules.map((r) => picomatch3(toGlobs(r.pattern), PICOMATCH));
4511
- matcherCache.set(file, matchers);
4512
- }
4513
- return matchers;
4514
- }
4515
- var PICOMATCH = { dot: true, windows: false };
4516
- function toGlobs(pattern) {
4517
- let p = pattern;
4518
- const dirOnly = p.endsWith("/");
4519
- if (dirOnly) p = p.slice(0, -1);
4520
- let anchored = false;
4521
- if (p.startsWith("/")) {
4522
- anchored = true;
4523
- p = p.slice(1);
4524
- } else if (p.includes("/") && !p.startsWith("**/")) {
4525
- anchored = true;
4526
- }
4527
- const base = anchored || p.startsWith("**/") ? p : `**/${p}`;
4528
- if (dirOnly) return [`${base}/**`];
4529
- const last = p.slice(p.lastIndexOf("/") + 1);
4530
- return /[*?[]/.test(last) ? [base] : [base, `${base}/**`];
4531
- }
4532
- var NOT_FOUND_REASON = `no CODEOWNERS file found (looked for ${CODEOWNERS_LOCATIONS.join(", ")})`;
4533
- async function deriveFromCodeowners(inputs, opts) {
4534
- const records = /* @__PURE__ */ new Map();
4535
- const explicitPath = opts.explicit === void 0 ? void 0 : resolve4(opts.configDir ?? process.cwd(), opts.explicit);
4536
- if (explicitPath !== void 0 && !existsSync(explicitPath)) {
4537
- return {
4538
- status: { available: false, reason: `CODEOWNERS not found at ${explicitPath}` },
4539
- records
4540
- };
4541
- }
4542
- const byRoot = /* @__PURE__ */ new Map();
4543
- const rootCache = /* @__PURE__ */ new Map();
4544
- for (const input of inputs) {
4545
- const dir = dirname3(input.absPath);
4546
- let root = rootCache.get(dir);
4547
- if (root === void 0) {
4548
- root = opts.rootOf(input.absPath);
4549
- rootCache.set(dir, root);
4550
- }
4551
- if (root === null) continue;
4552
- const bucket = byRoot.get(root);
4553
- if (bucket) bucket.push(input);
4554
- else byRoot.set(root, [input]);
4555
- }
4556
- let found = 0;
4557
- const parsed = /* @__PURE__ */ new Map();
4558
- for (const [root, bucket] of byRoot) {
4559
- const path = explicitPath ?? findCodeowners(root);
4560
- if (path === null) {
4561
- for (const input of bucket) records.set(input.label, null);
4562
- continue;
4563
- }
4564
- found += 1;
4565
- const label = toPosix2(relative5(opts.configDir ?? root, path));
4566
- const key = `${path}\0${label}`;
4567
- let file = parsed.get(key);
4568
- if (!file) {
4569
- file = readFile5(path, "utf8").then(
4570
- (text2) => parseCodeowners(text2, path, label)
4571
- );
4572
- parsed.set(key, file);
4573
- }
4574
- let owners;
4575
- try {
4576
- owners = await file;
4577
- } catch (err) {
4578
- return {
4579
- status: {
4580
- available: false,
4581
- reason: `CODEOWNERS at ${label} could not be read: ${errorMessage(err)}`
4582
- },
4583
- records
4584
- };
4585
- }
4586
- for (const input of bucket) {
4587
- const rel = toPosix2(relative5(root, input.absPath));
4588
- const hit = ownersFor(owners, rel);
4589
- records.set(
4590
- input.label,
4591
- hit === null ? null : {
4592
- value: hit.owners,
4593
- source: "codeowners",
4594
- evidence: `${label}:${hit.line}`
4595
- }
4596
- );
4597
- }
4598
- }
4599
- if (byRoot.size === 0) {
4600
- return {
4601
- status: { available: false, reason: "no git repository contains the documents" },
4602
- records
4603
- };
4604
- }
4605
- if (found === 0 && explicitPath === void 0) {
4606
- return { status: { available: true, reason: NOT_FOUND_REASON }, records };
4607
- }
4608
- return { status: { available: true }, records };
4609
- }
4610
- function toPosix2(p) {
4611
- return p.split(sep4).join("/");
4612
- }
4613
-
4614
- // src/meta/core/derive/spawn.ts
4615
- import { spawn } from "node:child_process";
4616
- var DEFAULT_TIMEOUT_MS = 3e4;
4617
- var MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
4618
- var BinMissing = class extends Error {
4619
- };
4620
- function run(bin, args, opts) {
4621
- return new Promise((settle2, reject) => {
4622
- let done = false;
4623
- const finish = (r) => {
4624
- if (done) return;
4625
- done = true;
4626
- settle2(r);
4627
- };
4628
- const fail = (err) => {
4629
- if (done) return;
4630
- done = true;
4631
- reject(err);
4632
- };
4633
- let child;
4634
- try {
4635
- child = spawn(bin, [...opts.prefixArgs ?? [], ...args], {
4636
- cwd: opts.cwd,
4637
- windowsHide: true
4638
- });
4639
- } catch (err) {
4640
- fail(err instanceof Error ? err : new Error(String(err)));
4641
- return;
4642
- }
4643
- let stdout = "";
4644
- let stderr = "";
4645
- let timedOut = false;
4646
- let tooLarge = false;
4647
- const stop = (r) => {
4648
- clearTimeout(timer);
4649
- child.kill();
4650
- setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
4651
- finish({ code: null, ...r });
4652
- };
4653
- child.stdout.setEncoding("utf8");
4654
- child.stdout.on("data", (chunk) => {
4655
- stdout += chunk;
4656
- if (stdout.length > MAX_OUTPUT_BYTES) {
4657
- tooLarge = true;
4658
- stop({ stdout, stderr, timedOut, tooLarge });
4659
- }
4660
- });
4661
- child.stderr.setEncoding("utf8");
4662
- child.stderr.on("data", (chunk) => {
4663
- stderr += chunk;
4664
- if (stderr.length > MAX_OUTPUT_BYTES) {
4665
- tooLarge = true;
4666
- stop({ stdout, stderr, timedOut, tooLarge });
4667
- }
4668
- });
4669
- child.stdin.end();
4670
- const timer = setTimeout(() => {
4671
- timedOut = true;
4672
- stop({ stdout, stderr, timedOut, tooLarge });
4673
- }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
4674
- child.on("error", (err) => {
4675
- clearTimeout(timer);
4676
- fail(err.code === "ENOENT" ? new BinMissing(err.message) : err);
4677
- });
4678
- child.on("close", (code) => {
4679
- clearTimeout(timer);
4680
- finish({ code, stdout, stderr, timedOut, tooLarge });
4681
- });
4682
- });
4683
- }
4684
- function commandLine(bin, args) {
4685
- return [bin, ...args].join(" ");
4686
- }
4687
- function lastLine(text2) {
4688
- const lines = text2.split(/\r?\n/).filter((l) => l.trim() !== "");
4689
- return lines.at(-1) ?? "";
4690
- }
4691
-
4692
- // src/meta/core/derive/command.ts
4693
- var PATH_PLACEHOLDER = "{path}";
4694
- function isPerFile(command) {
4695
- return command.run.some((arg) => arg.includes(PATH_PLACEHOLDER));
4696
- }
4697
- function argvFor(command, label) {
4698
- return command.run.map((arg) => arg.split(PATH_PLACEHOLDER).join(label));
4699
- }
4700
- function valueOf(stdout) {
4701
- const text2 = stdout.trim();
4702
- if (text2 === "") return null;
4703
- if (!/^[{["]/.test(text2)) return text2;
4704
- try {
4705
- return JSON.parse(text2);
4706
- } catch {
4707
- return text2;
4708
- }
4709
- }
4710
- async function deriveFromCommands(inputs, commands, opts) {
4711
- const records = /* @__PURE__ */ new Map();
4712
- const fields = Object.keys(commands);
4713
- if (fields.length === 0 || inputs.length === 0) {
4714
- return { status: { available: true }, records };
4715
- }
4716
- for (const input of inputs) records.set(input.label, {});
4717
- for (const field of fields) {
4718
- const command = commands[field];
4719
- if (command === void 0) continue;
4720
- if (isPerFile(command)) {
4721
- for (const [label, record] of records) {
4722
- const outcome = await runOne(field, argvFor(command, label), command.timeoutMs, opts);
4723
- if ("reason" in outcome) return unavailable(outcome.reason);
4724
- record[field] = outcome.value;
4725
- }
4726
- } else {
4727
- const outcome = await runOne(field, command.run, command.timeoutMs, opts);
4728
- if ("reason" in outcome) return unavailable(outcome.reason);
4729
- for (const record of records.values()) record[field] = outcome.value;
4730
- }
4731
- }
4732
- return { status: { available: true }, records };
4733
- }
4734
- function unavailable(reason) {
4735
- return { status: { available: false, reason }, records: /* @__PURE__ */ new Map() };
4736
- }
4737
- async function runOne(field, argv, timeoutMs, opts) {
4738
- const [program, ...args] = argv;
4739
- const where = `(derive.commands.${field})`;
4740
- if (program === void 0) {
4741
- return { reason: `no program to run ${where}` };
4742
- }
4743
- const line = argv.join(" ");
4744
- let result;
4745
- try {
4746
- result = await run(opts.spawn?.bin ?? program, args, {
4747
- ...opts.spawn,
4748
- cwd: opts.spawn?.cwd ?? opts.cwd,
4749
- timeoutMs
4750
- });
4751
- } catch (err) {
4752
- if (err instanceof BinMissing) {
4753
- return { reason: `\`${program}\` is not on PATH ${where}` };
4754
- }
4755
- return { reason: `\`${line}\` could not be run: ${errorMessage(err)} ${where}` };
4756
- }
4757
- if (result.timedOut) {
4758
- return { reason: `\`${line}\` timed out after ${String(timeoutMs / 1e3)}s ${where}` };
4759
- }
4760
- if (result.tooLarge) {
4761
- return {
4762
- reason: `\`${line}\` wrote more than ${String(MAX_OUTPUT_BYTES / (1024 * 1024))} MiB; a command reports one field's value ${where}`
4763
- };
4764
- }
4765
- if (result.code !== 0) {
4766
- const last = lastLine(result.stderr);
4767
- const detail = last === "" ? "no output on stderr" : last;
4768
- const exit = result.code === null ? "killed by a signal" : `exit ${String(result.code)}`;
4769
- return { reason: `\`${line}\` failed (${exit}): ${detail} ${where}` };
4770
- }
4771
- const value = valueOf(result.stdout);
4772
- return { value: value === null ? null : { value, source: "command", evidence: line } };
4773
- }
4774
-
4775
- // src/meta/core/derive/reviews.ts
4776
- import { resolve as resolvePath2 } from "node:path";
4777
- function parseOriginUrl(url) {
4778
- const trimmed = url.trim();
4779
- if (trimmed === "") return null;
4780
- if (isLocalPath(trimmed)) return null;
4781
- const scp = /^(?:[^@/:]+@)?([^/:]+):(?!\/\/)(.+)$/.exec(trimmed);
4782
- if (scp) {
4783
- const host = scp[1];
4784
- const path = scp[2];
4785
- if (host === void 0 || path === void 0) return null;
4786
- if (/^[A-Za-z]$/.test(host)) return null;
4787
- return project(host, path);
4788
- }
4789
- let parsed;
4790
- try {
4791
- parsed = new URL(trimmed);
4792
- } catch {
4793
- return null;
4794
- }
4795
- if (!/^(https?|ssh|git):$/.test(parsed.protocol)) return null;
4796
- if (parsed.hostname === "") return null;
4797
- return project(parsed.hostname, parsed.pathname);
4798
- }
4799
- function isLocalPath(s) {
4800
- return s.startsWith("/") || s.startsWith("./") || s.startsWith("../") || s.startsWith("\\\\");
4801
- }
4802
- function project(host, path) {
4803
- const segments2 = path.replace(/\.git$/, "").split("/").filter((s) => s !== "");
4804
- if (segments2.length < 2) return null;
4805
- return { host, project: segments2.join("/") };
4806
- }
4807
- function identityFromOrigin(host, project2, requested) {
4808
- const name = host.toLowerCase();
4809
- if (name.includes("github")) return { kind: "github", host, project: project2 };
4810
- if (name.includes("gitlab")) return { kind: "gitlab", host, project: project2 };
4811
- const only = requested !== void 0 && requested.length === 1 ? requested[0] : void 0;
4812
- return only === void 0 ? null : { kind: only, host, project: project2 };
4813
- }
4814
- var UnknownHostClient = class {
4815
- constructor(host) {
4816
- this.host = host;
4817
- }
4818
- host;
4819
- detect() {
4820
- return Promise.resolve(null);
4821
- }
4822
- status() {
4823
- return Promise.resolve({
4824
- available: false,
4825
- reason: `the origin remote is ${this.host}, which names neither GitHub nor GitLab; list exactly one of github, gitlab in derive.sources to say which it is`
4826
- });
4827
- }
4828
- mergedChangeFor() {
4829
- return Promise.resolve(null);
4830
- }
4831
- };
4832
- function httpStatus(stderr) {
4833
- const m = /\b(?:HTTP\s+(\d{3})|(\d{3})\s+Not\s+Found)\b/i.exec(stderr);
4834
- const code = m?.[1] ?? m?.[2];
4835
- return code === void 0 ? null : Number(code);
4836
- }
4837
- function isNotFound(r) {
4838
- const status = httpStatus(r.stderr);
4839
- return status === 404 || status === 422;
4840
- }
4841
- var CliClient = class {
4842
- constructor(identity, spawnOpts) {
4843
- this.identity = identity;
4844
- this.spawnOpts = spawnOpts;
4845
- }
4846
- identity;
4847
- spawnOpts;
4848
- detect() {
4849
- return Promise.resolve(this.identity);
4850
- }
4851
- async status() {
4852
- if (this.identity === null) {
4853
- return { available: false, reason: "no origin remote to tell GitHub from GitLab" };
4854
- }
4855
- const { host } = this.identity;
4856
- let r;
4857
- try {
4858
- r = await this.run(["auth", "status", "--hostname", host]);
4859
- } catch (err) {
4860
- if (err instanceof BinMissing) {
4861
- return {
4862
- available: false,
4863
- reason: `${this.tool} is not on PATH (origin is ${host}); install ${this.tool} and run ${this.tool} auth login`
4864
- };
4865
- }
4866
- throw err;
4867
- }
4868
- if (r.timedOut) throw this.timeout(["auth", "status", "--hostname", host]);
4869
- if (r.code !== 0) {
4870
- return {
4871
- available: false,
4872
- reason: `${this.tool} is not authenticated for ${host}; run ${this.tool} auth login`
4873
- };
4874
- }
4875
- return { available: true };
4876
- }
4877
- async mergedChangeFor(sha) {
4878
- if (this.identity === null) return null;
4879
- return this.lookup(this.identity, sha);
4880
- }
4881
- get bin() {
4882
- return this.spawnOpts.bin ?? this.tool;
4883
- }
4884
- run(args) {
4885
- return run(this.bin, args, this.spawnOpts);
4886
- }
4887
- timeout(args) {
4888
- const ms = this.spawnOpts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
4889
- return new DocmetaError(
4890
- `${commandLine(this.tool, args)} timed out after ${ms}ms`
4891
- );
4892
- }
4893
- /**
4894
- * Run a call whose stdout is JSON. A not-found answer is `null`; any other
4895
- * failure throws, naming the command the user can re-run by hand. The
4896
- * message quotes the tool's own name rather than `bin`, which under test
4897
- * is `node`.
4898
- */
4899
- async json(args) {
4900
- let r;
4901
- try {
4902
- r = await this.run(args);
4903
- } catch (err) {
4904
- if (err instanceof BinMissing) {
4905
- throw new DocmetaError(
4906
- `${commandLine(this.tool, args)} failed: ${this.tool} is not on PATH`
4907
- );
4908
- }
4909
- throw err;
4910
- }
4911
- const cmd = commandLine(this.tool, args);
4912
- if (r.timedOut) throw this.timeout(args);
4913
- if (r.tooLarge) {
4914
- throw new DocmetaError(`${cmd} wrote more output than manni will read`);
4915
- }
4916
- if (r.code !== 0) {
4917
- if (isNotFound(r)) return null;
4918
- const detail = lastLine(r.stderr) || lastLine(r.stdout);
4919
- const exit = r.code === null ? "signal" : `exit ${r.code}`;
4920
- throw new DocmetaError(
4921
- `${cmd} failed (${exit})${detail === "" ? "" : `: ${detail}`}`
4922
- );
4923
- }
4924
- try {
4925
- return JSON.parse(r.stdout);
4926
- } catch {
4927
- throw new DocmetaError(`${cmd} did not return JSON`);
4928
- }
4929
- }
4930
- };
4931
- function isRecord5(v) {
4932
- return typeof v === "object" && v !== null && !Array.isArray(v);
4933
- }
4934
- function items(v) {
4935
- return Array.isArray(v) ? v.filter(isRecord5) : [];
4936
- }
4937
- function numberOf(v) {
4938
- return typeof v === "number" && Number.isInteger(v) ? v : null;
4939
- }
4940
- function stringOf(v) {
4941
- return typeof v === "string" && v !== "" ? v : null;
4942
- }
4943
- function timestampOf(v) {
4944
- if (v === void 0) return void 0;
4945
- return stringOf(v);
4946
- }
4947
- function normalizeLogin(login) {
4948
- return login.replace(/\[bot\]$/, "");
4949
- }
4950
- function latestApprovals(reviews) {
4951
- const latest = /* @__PURE__ */ new Map();
4952
- for (const r of reviews) {
4953
- if (r.state !== "APPROVED") continue;
4954
- const key = normalizeLogin(r.login);
4955
- const seen = latest.get(key);
4956
- if (seen === void 0 || r.submittedAt > seen.submittedAt) {
4957
- latest.set(key, { login: r.login, submittedAt: r.submittedAt });
4958
- }
4959
- }
4960
- return byTime([...latest.values()]);
4961
- }
4962
- function byTime(approvals) {
4963
- return [...approvals].sort(
4964
- (a, b) => a.submittedAt < b.submittedAt ? -1 : a.submittedAt > b.submittedAt ? 1 : 0
4965
- );
4966
- }
4967
- var GitHubClient = class extends CliClient {
4968
- tool = "gh";
4969
- async lookup(identity, sha) {
4970
- const { host, project: project2 } = identity;
4971
- const api = (endpoint) => this.json(["api", "--hostname", host, endpoint]);
4972
- let candidates = items(await api(`repos/${project2}/commits/${sha}/pulls`)).map(
4973
- (p) => ({ number: numberOf(p.number), mergedAt: timestampOf(p.merged_at) })
4974
- );
4975
- if (candidates.length === 0) {
4976
- const found = await this.json([
4977
- "pr",
4978
- "list",
4979
- "--search",
4980
- sha,
4981
- "--state",
4982
- "merged",
4983
- "--json",
4984
- "number,mergedAt",
4985
- "-R",
4986
- `${host}/${project2}`
4987
- ]);
4988
- candidates = items(found).map((p) => ({
4989
- number: numberOf(p.number),
4990
- mergedAt: timestampOf(p.mergedAt)
4991
- }));
4992
- }
4993
- for (const c of candidates) {
4994
- if (c.number === null) continue;
4995
- let mergedAt = c.mergedAt;
4996
- if (mergedAt === void 0) {
4997
- const pr = await api(`repos/${project2}/pulls/${c.number}`);
4998
- mergedAt = isRecord5(pr) ? stringOf(pr.merged_at) ?? null : null;
4999
- }
5000
- if (mergedAt === null) continue;
5001
- const raw = [];
5002
- for (let page = 1; ; page += 1) {
5003
- const batch = items(
5004
- await api(`repos/${project2}/pulls/${c.number}/reviews?per_page=100&page=${page}`)
5005
- );
5006
- raw.push(...batch);
5007
- if (batch.length < 100) break;
5008
- }
5009
- const reviews = raw.map((r) => ({
5010
- login: isRecord5(r.user) ? stringOf(r.user.login) : null,
5011
- state: stringOf(r.state),
5012
- submittedAt: stringOf(r.submitted_at)
5013
- })).filter(
5014
- (r) => r.login !== null && r.state !== null && r.submittedAt !== null
5015
- );
5016
- return { id: c.number, mergedAt, approvals: latestApprovals(reviews) };
5017
- }
5018
- return null;
5019
- }
5020
- };
5021
- var GitLabClient = class extends CliClient {
5022
- tool = "glab";
5023
- async lookup(identity, sha) {
5024
- const { host } = identity;
5025
- const api = (endpoint) => this.json(["api", "--hostname", host, endpoint]);
5026
- const merged = items(
5027
- await api(`projects/:fullpath/repository/commits/${sha}/merge_requests`)
5028
- ).map((mr) => ({ iid: numberOf(mr.iid), mergedAt: stringOf(mr.merged_at) })).find(
5029
- (mr) => mr.iid !== null && mr.mergedAt !== null
5030
- );
5031
- if (merged === void 0) return null;
5032
- const answer = await api(`projects/:fullpath/merge_requests/${merged.iid}/approvals`);
5033
- const approvedBy = isRecord5(answer) ? items(answer.approved_by) : [];
5034
- const approvals = [];
5035
- const seen = /* @__PURE__ */ new Set();
5036
- for (const entry of approvedBy) {
5037
- const login = isRecord5(entry.user) ? stringOf(entry.user.username) : null;
5038
- if (login === null) continue;
5039
- const key = normalizeLogin(login);
5040
- if (seen.has(key)) continue;
5041
- seen.add(key);
5042
- approvals.push({ login, submittedAt: merged.mergedAt });
5043
- }
5044
- return { id: merged.iid, mergedAt: merged.mergedAt, approvals };
5045
- }
5046
- };
5047
- async function createReviewClient(root, opts) {
5048
- const spawnOpts = { ...opts.spawn, cwd: opts.spawn?.cwd ?? root };
5049
- const origin = await originUrl(root);
5050
- const parsed = origin === null ? null : parseOriginUrl(origin);
5051
- const identity = parsed === null ? null : identityFromOrigin(parsed.host, parsed.project, opts.requested);
5052
- if (parsed !== null && identity === null) return new UnknownHostClient(parsed.host);
5053
- const client = identity?.kind === "gitlab" ? new GitLabClient(identity, spawnOpts) : new GitHubClient(identity, spawnOpts);
5054
- const cache = new ReviewCache(resolvePath2(root, opts.cacheDir), opts.cache);
5055
- return cachedClient(client, cache, identity);
5056
- }
5057
- async function originUrl(root) {
5058
- try {
5059
- const r = await run("git", ["remote", "get-url", "origin"], { cwd: root });
5060
- if (r.code !== 0 || r.timedOut || r.tooLarge) return null;
5061
- return stringOf(r.stdout.trim());
5062
- } catch {
5063
- return null;
5064
- }
5065
- }
5066
- var NOTHING = { "reviewed-by": null, "last-reviewed": null };
5067
- async function deriveFromReviews(inputs, client) {
5068
- const records = /* @__PURE__ */ new Map();
5069
- const status = await client.status();
5070
- if (!status.available) return { status, records };
5071
- const identity = await client.detect();
5072
- const kind = identity?.kind ?? "github";
5073
- const bySha = /* @__PURE__ */ new Map();
5074
- const changeFor = (sha) => {
5075
- let pending = bySha.get(sha);
5076
- if (pending === void 0) {
5077
- pending = client.mergedChangeFor(sha);
5078
- bySha.set(sha, pending);
5079
- }
5080
- return pending;
5081
- };
5082
- for (const input of inputs) {
5083
- if (input.sha === null) {
5084
- records.set(input.label, NOTHING);
5085
- continue;
5086
- }
5087
- const change = await changeFor(input.sha);
5088
- records.set(input.label, factsFrom(change, kind));
5089
- }
5090
- return { status, records };
5091
- }
5092
- function factsFrom(change, kind) {
5093
- if (change === null || change.approvals.length === 0) return NOTHING;
5094
- const ordered = byTime(change.approvals);
5095
- const latest = ordered.at(-1);
5096
- if (latest === void 0) return NOTHING;
5097
- const evidence = kind === "gitlab" ? `gitlab MR !${change.id}` : `github PR #${change.id}`;
5098
- const dateEvidence = kind === "gitlab" ? `${evidence} (merge date; GitLab records no approval time)` : evidence;
5099
- return {
5100
- "reviewed-by": {
5101
- value: ordered.map((a) => a.login),
5102
- source: kind,
5103
- evidence
5104
- },
5105
- "last-reviewed": {
5106
- value: utcDate(latest.submittedAt),
5107
- source: kind,
5108
- evidence: dateEvidence
5109
- }
5110
- };
5111
- }
5112
- function utcDate(iso) {
5113
- const ms = Date.parse(iso);
5114
- if (Number.isNaN(ms)) return iso;
5115
- return new Date(ms).toISOString().slice(0, 10);
5116
- }
5117
-
5118
- // src/meta/core/derive/git.ts
5119
- import { constants as bufferConstants } from "node:buffer";
5120
- import { spawn as spawn2 } from "node:child_process";
5121
- import { availableParallelism } from "node:os";
5122
- import { dirname as dirname4, extname as extname2, isAbsolute as isAbsolute4, posix as posix4, relative as relative6, sep as sep5 } from "node:path";
5123
- import { parse as parseYaml3 } from "yaml";
5124
-
5125
- // src/shared/concurrency.ts
5126
- async function mapConcurrent(items2, limit, fn) {
5127
- const out = new Array(items2.length);
5128
- let next = 0;
5129
- const workers = Array.from(
5130
- { length: Math.max(1, Math.min(limit, items2.length)) },
5131
- async () => {
5132
- for (; ; ) {
5133
- const i = next++;
5134
- const item = items2[i];
5135
- if (i >= items2.length || item === void 0) return;
5136
- out[i] = await fn(item);
5137
- }
5138
- }
5139
- );
5140
- await Promise.all(workers);
5141
- return out;
5142
- }
5143
-
5144
- // src/meta/core/derive/git.ts
5145
- var DEFAULT_BULK_THRESHOLD = 32;
5146
- var NULL_SHA = "0".repeat(40);
5147
- var MAX_GIT_OUTPUT_BYTES = Math.min(
5148
- 512 * 1024 * 1024,
5149
- bufferConstants.MAX_STRING_LENGTH
5150
- );
5151
- var HistoryTooLarge = class extends Error {
5152
- };
5153
- var BlobsUnreadable = class extends Error {
5154
- constructor(detail) {
5155
- super(detail);
5156
- this.detail = detail;
5157
- }
5158
- detail;
5159
- };
5160
- var BlameUnreadable = class extends Error {
5161
- constructor(rel, detail) {
5162
- super(rel);
5163
- this.rel = rel;
5164
- this.detail = detail;
5165
- }
5166
- rel;
5167
- detail;
5168
- };
5169
- function bodyOf(content, fenced) {
5170
- if (fenced !== true) return content;
5171
- const loc = locateFrontmatter(content);
5172
- if (!loc) return content;
5173
- return content.slice(0, loc.openStart) + content.slice(loc.closeEnd);
5174
- }
5175
- async function deriveFromGit(inputs, opts) {
5176
- const records = /* @__PURE__ */ new Map();
5177
- const byRoot = groupByRoot(inputs);
5178
- if (byRoot.size === 0) {
5179
- return {
5180
- status: {
5181
- available: false,
5182
- reason: "no git repository contains the documents"
5183
- },
5184
- records
5185
- };
5186
- }
5187
- const threshold = opts.bulkThreshold ?? DEFAULT_BULK_THRESHOLD;
5188
- const machines = opts.machines ?? DEFAULT_MACHINES;
5189
- const wantsProvenance = opts.fields?.includes(PROVENANCE_FIELD) ?? false;
5190
- const maxBytes = opts.maxOutputBytes ?? MAX_GIT_OUTPUT_BYTES;
5191
- const parse = yamlOnce();
5192
- const reasons = [];
5193
- for (const [root, bucket] of byRoot) {
5194
- const run2 = (args, stdin) => runGit(args, root, stdin, maxBytes);
5195
- const shallow = await run2(["rev-parse", "--is-shallow-repository"]);
5196
- if (shallow.missing) {
5197
- return {
5198
- status: { available: false, reason: "git is not on PATH" },
5199
- records: /* @__PURE__ */ new Map()
5200
- };
5201
- }
5202
- if (shallow.code !== 0) {
5203
- reasons.push(`git could not read the repository at ${root}`);
5204
- continue;
5205
- }
5206
- if (text(shallow).trim() === "true") {
5207
- reasons.push(
5208
- `this checkout is shallow; use actions/checkout with fetch-depth: 0 (${root})`
5209
- );
5210
- continue;
5211
- }
5212
- try {
5213
- const found = await histories(
5214
- run2,
5215
- bucket.map((e) => e.rel),
5216
- threshold
5217
- );
5218
- if (found === null) {
5219
- reasons.push(`git could not read the history at ${root}`);
5220
- continue;
5221
- }
5222
- await readOtherParentBlobs(run2, found);
5223
- const blobs = await fetchBlobs(run2, neededBlobs(found));
5224
- if (!await followTakenBodies(run2, bucket, found, blobs, threshold)) {
5225
- reasons.push(`git could not read the history at ${root}`);
5226
- continue;
5227
- }
5228
- const manifestStamps = await stampsInManifests(run2, root, bucket, found, blobs, parse);
5229
- const facts = bucket.map(
5230
- (entry) => judge(
5231
- entry.input,
5232
- root,
5233
- found.get(entry.rel) ?? [],
5234
- blobs,
5235
- opts.now,
5236
- machines,
5237
- manifestStamps.get(entry.input.label)
5238
- )
5239
- );
5240
- if (wantsProvenance) {
5241
- const pages = await mapConcurrent(bucket, GIT_CONCURRENCY, async (entry) => ({
5242
- entry,
5243
- blame: await blameOf(run2, entry.rel, entry.input.content),
5244
- history: found.get(entry.rel) ?? []
5245
- }));
5246
- const evidence = await commitEvidence(run2, root, pages, blobs, parse);
5247
- const provenanceRun = {
5248
- machines,
5249
- ...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
5250
- };
5251
- pages.forEach((page, i) => {
5252
- const judged = facts[i];
5253
- const commits = evidence[i];
5254
- if (judged === void 0 || commits === void 0) return;
5255
- const derived = provenanceFor(page.entry, page.blame, commits, provenanceRun);
5256
- judged.provenance = derived.value;
5257
- judged.provenanceDerivation = derived.derivation;
5258
- });
5259
- }
5260
- bucket.forEach((entry, i) => {
5261
- const judged = facts[i];
5262
- if (judged !== void 0) records.set(entry.input.label, judged);
5263
- });
5264
- } catch (err) {
5265
- if (err instanceof BlobsUnreadable) {
5266
- reasons.push(`git cat-file could not read the history (${root}): ${err.detail}`);
5267
- continue;
5268
- }
5269
- if (err instanceof BlameUnreadable) {
5270
- const why = err.detail === "" ? "" : `: ${err.detail}`;
5271
- reasons.push(`git blame could not read ${err.rel} (${root})${why}`);
5272
- continue;
5273
- }
5274
- if (!(err instanceof HistoryTooLarge)) throw err;
5275
- reasons.push(`git history is too large to read in one pass (${root})`);
5276
- continue;
5277
- }
5278
- }
5279
- if (reasons.length > 0) {
5280
- return {
5281
- status: { available: false, reason: reasons.join("; ") },
5282
- records: /* @__PURE__ */ new Map()
5283
- };
5284
- }
5285
- return { status: { available: true }, records };
5286
- }
5287
- function groupByRoot(inputs) {
5288
- const rootOf = /* @__PURE__ */ new Map();
5289
- const byRoot = /* @__PURE__ */ new Map();
5290
- for (const input of inputs) {
5291
- const dir = dirname4(input.absPath);
5292
- let root = rootOf.get(dir);
5293
- if (root === void 0) {
5294
- root = findGitRoot(dir);
5295
- rootOf.set(dir, root);
5296
- }
5297
- if (root === null) continue;
5298
- const rel = relative6(root, input.absPath).split(sep5).join("/");
5299
- const bucket = byRoot.get(root);
5300
- if (bucket) bucket.push({ input, rel });
5301
- else byRoot.set(root, [{ input, rel }]);
5302
- }
5303
- return byRoot;
5304
- }
5305
- var RECORD_FORMAT = "--format=%x1e%H%x00%aI%x00%an%x00%ae%x00%(trailers:key=Co-authored-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Reviewed-by,valueonly,unfold,separator=%x1f)%x00%(trailers:key=Generated-by,valueonly,unfold,separator=%x1f)%x00%P";
5306
- var LOG_ARGS = [
5307
- "-c",
5308
- "core.quotePath=false",
5309
- "log",
5310
- "--raw",
5311
- "--diff-merges=first-parent",
5312
- "-M",
5313
- "--no-abbrev",
5314
- RECORD_FORMAT
5315
- ];
5316
- function histories(run2, rels, threshold, rev = "HEAD") {
5317
- return rels.length <= threshold ? perFileHistories(run2, rels, rev) : bulkHistories(run2, rels, rev);
5318
- }
5319
- async function perFileHistories(run2, rels, rev) {
5320
- const found = /* @__PURE__ */ new Map();
5321
- for (const rel of rels) {
5322
- const records = await lineage(run2, rel, rev);
5323
- if (records === null) {
5324
- if (await isUnbornHead(run2)) return found;
5325
- return null;
5326
- }
5327
- found.set(rel, attribute(records, [rel]).get(rel) ?? []);
5328
- }
5329
- return found;
5330
- }
5331
- async function lineage(run2, rel, rev) {
5332
- const records = [];
5333
- let at2 = rel;
5334
- let from = rev;
5335
- for (; ; ) {
5336
- const out = await run2([...LOG_ARGS, from, "--", pathspec(at2)]);
5337
- if (out.tooLarge) throw new HistoryTooLarge();
5338
- if (out.code !== 0) return null;
5339
- let renamedFrom;
5340
- for (const record of parseLog(text(out))) {
5341
- const status = record.raw.find((e) => e.newPath === at2)?.status;
5342
- if (status === "A" || status === "D") {
5343
- const whole = await run2([...LOG_ARGS, "-1", record.sha]);
5344
- if (whole.tooLarge) throw new HistoryTooLarge();
5345
- if (whole.code !== 0) return null;
5346
- record.raw = parseLog(text(whole))[0]?.raw ?? [];
5347
- const entry = record.raw.find((e) => e.newPath === at2);
5348
- if (entry !== void 0 && entry.oldPath !== at2) renamedFrom = entry.oldPath;
5349
- }
5350
- records.push(record);
5351
- if (renamedFrom !== void 0) {
5352
- from = `${record.sha}^`;
5353
- break;
5354
- }
5355
- }
5356
- if (renamedFrom === void 0) return records;
5357
- at2 = renamedFrom;
5358
- }
5359
- }
5360
- async function bulkHistories(run2, rels, rev) {
5361
- const out = await run2([...LOG_ARGS, rev]);
5362
- if (out.tooLarge) throw new HistoryTooLarge();
5363
- if (out.code !== 0) {
5364
- if (await isUnbornHead(run2)) return /* @__PURE__ */ new Map();
5365
- return null;
5366
- }
5367
- return attribute(parseLog(text(out)), [...rels]);
5368
- }
5369
- async function followTakenBodies(run2, bucket, found, blobs, threshold) {
5370
- for (; ; ) {
5371
- const byParent = /* @__PURE__ */ new Map();
5372
- for (const entry of bucket) {
5373
- const history = found.get(entry.rel) ?? [];
5374
- const taken = takenMerge(entry.input.extracted.fenced, history, blobs);
5375
- if (taken === void 0) continue;
5376
- const group = byParent.get(taken.parent) ?? [];
5377
- group.push({ rel: entry.rel, keep: history.slice(0, taken.index), path: taken.path });
5378
- byParent.set(taken.parent, group);
5379
- }
5380
- if (byParent.size === 0) return true;
5381
- const walked = /* @__PURE__ */ new Map();
5382
- for (const [parent, group] of byParent) {
5383
- const paths = [...new Set(group.map((g) => g.path))];
5384
- const side = await histories(run2, paths, threshold, parent);
5385
- if (side === null) return false;
5386
- for (const g of group) {
5387
- const tail = side.get(g.path) ?? [];
5388
- walked.set(g.rel, tail);
5389
- found.set(g.rel, [...g.keep, ...tail]);
5390
- }
5391
- }
5392
- await readOtherParentBlobs(run2, walked);
5393
- const missing = new Set([...neededBlobs(walked)].filter((sha) => !blobs.has(sha)));
5394
- for (const [sha, content] of await fetchBlobs(run2, missing)) blobs.set(sha, content);
5395
- }
5396
- }
5397
- function takenMerge(fenced, history, blobs) {
5398
- const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
5399
- const index = history.findIndex((h) => !sameBody(blobBody(h.newBlob), blobBody(h.oldBlob)));
5400
- const newest = history[index];
5401
- if (newest === void 0) return void 0;
5402
- const body = blobBody(newest.newBlob);
5403
- const k = newest.otherParentBlobs.findIndex((b) => b !== null && sameBody(body, blobBody(b)));
5404
- const parent = newest.otherParents[k];
5405
- if (k === -1 || parent === void 0) return void 0;
5406
- return { index, parent, path: newest.pathAtCommit };
5407
- }
5408
- function pathspec(rel) {
5409
- return /[*?[\]]/.test(rel) ? `:(literal)${rel}` : rel;
5410
- }
5411
- async function isUnbornHead(run2) {
5412
- const out = await run2(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
5413
- return !out.missing && out.code !== 0;
5414
- }
5415
- function parseLog(text2) {
5416
- const records = [];
5417
- for (const chunk of text2.split("")) {
5418
- if (chunk === "") continue;
5419
- const nl = chunk.indexOf("\n");
5420
- const header = nl === -1 ? chunk : chunk.slice(0, nl);
5421
- const fields = header.split("\0");
5422
- const sha = fields[0];
5423
- if (sha === void 0 || sha === "") continue;
5424
- const raw = [];
5425
- if (nl !== -1) {
5426
- for (const line of chunk.slice(nl + 1).split("\n")) {
5427
- const entry = parseRawLine(line);
5428
- if (entry) raw.push(entry);
5429
- }
5430
- }
5431
- records.push({
5432
- sha,
5433
- authorDate: fields[1] ?? "",
5434
- authorName: fields[2] ?? "",
5435
- authorEmail: fields[3] ?? "",
5436
- coAuthors: splitTrailers(fields[4]),
5437
- reviewedBy: splitTrailers(fields[5]),
5438
- generatedBy: splitTrailers(fields[6]),
5439
- parents: (fields[7] ?? "").trim().split(" ").filter((p) => p !== ""),
5440
- raw
5441
- });
5442
- }
5443
- return records;
5444
- }
5445
- function splitTrailers(field) {
5446
- if (field === void 0 || field === "") return [];
5447
- return field.split("").map((s) => s.trim()).filter((s) => s !== "");
5448
- }
5449
- function parseRawLine(line) {
5450
- if (!line.startsWith(":")) return null;
5451
- const tab = line.indexOf(" ");
5452
- if (tab === -1) return null;
5453
- const meta = line.slice(1, tab).split(" ");
5454
- const oldBlob = meta[2];
5455
- const newBlob = meta[3];
5456
- const status = meta[4];
5457
- if (oldBlob === void 0 || newBlob === void 0 || status === void 0) {
5458
- return null;
5459
- }
5460
- const paths = line.slice(tab + 1).split(" ").map(unquotePath);
5461
- const first = paths[0];
5462
- if (first === void 0) return null;
5463
- const second = paths[1];
5464
- const renamed = status.startsWith("R") || status.startsWith("C");
5465
- return {
5466
- status,
5467
- oldBlob,
5468
- newBlob,
5469
- oldPath: first,
5470
- newPath: renamed && second !== void 0 ? second : first
5471
- };
5472
- }
5473
- function unquotePath(p) {
5474
- if (p.length < 2 || !p.startsWith('"') || !p.endsWith('"')) return p;
5475
- const inner = p.slice(1, -1);
5476
- const bytes = [];
5477
- for (let i = 0; i < inner.length; i += 1) {
5478
- const ch = inner[i];
5479
- if (ch !== "\\") {
5480
- bytes.push(...Buffer.from(ch ?? "", "utf8"));
5481
- continue;
5482
- }
5483
- const next = inner[i + 1];
5484
- if (next === void 0) break;
5485
- const octal = /^[0-7]{3}/.exec(inner.slice(i + 1));
5486
- if (octal) {
5487
- bytes.push(parseInt(octal[0], 8));
5488
- i += 3;
5489
- continue;
5490
- }
5491
- const simple = {
5492
- a: 7,
5493
- b: 8,
5494
- t: 9,
5495
- n: 10,
5496
- v: 11,
5497
- f: 12,
5498
- r: 13,
5499
- '"': 34,
5500
- "\\": 92
5501
- };
5502
- const code = simple[next];
5503
- if (code === void 0) bytes.push(...Buffer.from(next, "utf8"));
5504
- else bytes.push(code);
5505
- i += 1;
5506
- }
5507
- return Buffer.from(bytes).toString("utf8");
5508
- }
5509
- function attribute(records, wanted) {
5510
- const histories2 = /* @__PURE__ */ new Map();
5511
- const tracked = /* @__PURE__ */ new Map();
5512
- for (const rel of wanted) {
5513
- histories2.set(rel, []);
5514
- tracked.set(rel, rel);
5515
- }
5516
- for (const record of records) {
5517
- if (record.raw.length === 0) continue;
5518
- const byNewPath = /* @__PURE__ */ new Map();
5519
- for (const entry of record.raw) byNewPath.set(entry.newPath, entry);
5520
- for (const [rel, current] of tracked) {
5521
- const entry = byNewPath.get(current);
5522
- if (!entry) continue;
5523
- histories2.get(rel)?.push({
5524
- sha: record.sha,
5525
- authorDate: record.authorDate,
5526
- authorName: record.authorName,
5527
- authorEmail: record.authorEmail,
5528
- coAuthors: record.coAuthors,
5529
- reviewedBy: record.reviewedBy,
5530
- generatedBy: record.generatedBy,
5531
- oldBlob: entry.oldBlob === NULL_SHA ? null : entry.oldBlob,
5532
- newBlob: entry.newBlob === NULL_SHA ? null : entry.newBlob,
5533
- pathAtCommit: entry.newPath,
5534
- otherParents: record.parents.slice(1),
5535
- otherParentBlobs: []
5536
- });
5537
- if (entry.oldPath !== entry.newPath) tracked.set(rel, entry.oldPath);
5538
- }
5539
- }
5540
- return histories2;
5541
- }
5542
- function neededBlobs(histories2) {
5543
- const shas = /* @__PURE__ */ new Set();
5544
- for (const history of histories2.values()) {
5545
- for (const h of history) {
5546
- if (h.oldBlob !== null) shas.add(h.oldBlob);
5547
- if (h.newBlob !== null) shas.add(h.newBlob);
5548
- for (const b of h.otherParentBlobs) if (b !== null) shas.add(b);
5549
- }
5550
- }
5551
- return shas;
5552
- }
5553
- async function readOtherParentBlobs(run2, histories2) {
5554
- const merges = [];
5555
- const specs = [];
5556
- for (const history of histories2.values()) {
5557
- for (const h of history) {
5558
- if (h.otherParents.length === 0) continue;
5559
- merges.push(h);
5560
- for (const parent of h.otherParents) specs.push(`${parent}:${h.pathAtCommit}`);
5561
- }
5562
- }
5563
- if (specs.length === 0) return;
5564
- const out = await run2(["cat-file", "--batch-check"], `${specs.join("\n")}
5565
- `);
5566
- if (out.tooLarge) throw new HistoryTooLarge();
5567
- if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
5568
- const lines = text(out).split("\n");
5569
- let i = 0;
5570
- for (const h of merges) {
5571
- h.otherParentBlobs = h.otherParents.map(() => blobOfCheckLine(lines[i++]));
5572
- }
5573
- }
5574
- function blobOfCheckLine(line) {
5575
- const [sha, kind] = (line ?? "").split(" ");
5576
- if (sha === void 0 || kind !== "blob" || !/^[0-9a-f]{40,64}$/.test(sha)) return null;
5577
- return sha;
5578
- }
5579
- async function fetchBlobs(run2, shas) {
5580
- const blobs = /* @__PURE__ */ new Map();
5581
- if (shas.size === 0) return blobs;
5582
- const out = await run2(["cat-file", "--batch"], `${[...shas].join("\n")}
5583
- `);
5584
- if (out.tooLarge) throw new HistoryTooLarge();
5585
- if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
5586
- const buf = out.raw;
5587
- let cursor = 0;
5588
- while (cursor < buf.length) {
5589
- const nl = buf.indexOf(10, cursor);
5590
- if (nl === -1) break;
5591
- const header = buf.toString("utf8", cursor, nl).split(" ");
5592
- cursor = nl + 1;
5593
- const sha = header[0];
5594
- const kind = header[1];
5595
- const size = Number(header[2]);
5596
- if (sha === void 0 || kind !== "blob" || !Number.isFinite(size)) {
5597
- continue;
5598
- }
5599
- blobs.set(sha, buf.toString("utf8", cursor, cursor + size));
5600
- cursor += size + 1;
5601
- }
5602
- return blobs;
5603
- }
5604
- var git = (value, evidence) => ({
5605
- value,
5606
- source: "git",
5607
- evidence
5608
- });
5609
- function factCommits(input, history, blobs) {
5610
- const fenced = input.extracted.fenced;
5611
- const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
5612
- const bodyChanging = [...history].reverse().filter((h) => {
5613
- const body = blobBody(h.newBlob);
5614
- if (sameBody(body, blobBody(h.oldBlob))) return false;
5615
- return !h.otherParentBlobs.some((b) => b !== null && sameBody(body, blobBody(b)));
5616
- });
5617
- return {
5618
- bodyChanging,
5619
- oldest: history[history.length - 1],
5620
- newestBody: bodyChanging[bodyChanging.length - 1]
5621
- };
5622
- }
5623
- async function stampsInManifests(run2, root, bucket, histories2, blobs, parse) {
5624
- const reads = [];
5625
- for (const entry of bucket) {
5626
- const refs = entry.input.stampManifests;
5627
- if (refs === void 0) continue;
5628
- const { oldest, newestBody } = factCommits(entry.input, histories2.get(entry.rel) ?? [], blobs);
5629
- for (const field of STAMPED_DATE_FIELDS) {
5630
- const ref = refs[field];
5631
- const at2 = field === "created" ? oldest : newestBody;
5632
- if (ref === void 0 || at2 === void 0) continue;
5633
- const rel = insideRoot(root, ref.absPath);
5634
- if (rel === void 0) continue;
5635
- const key = ref.join === "path" ? entryAt(ref.entry, entry.rel, at2.pathAtCommit) : ref.entry;
5636
- const revs = field === "created" ? [at2.sha] : [at2.sha, `${at2.sha}^`];
5637
- for (const rev of revs) {
5638
- reads.push({ label: entry.input.label, field, rev, spec: `${rev}:${rel}`, key, join: ref.join });
5639
- }
5640
- }
5641
- }
5642
- const out = /* @__PURE__ */ new Map();
5643
- if (reads.length === 0) return out;
5644
- const fetched = await fetchSpecs(run2, [...new Set(reads.map((r) => r.spec))]);
5645
- const values = /* @__PURE__ */ new Map();
5646
- const keyOf = (label, field, rev) => `${label}\0${field}\0${rev}`;
5647
- for (const r of reads) {
5648
- values.set(keyOf(r.label, r.field, r.rev), manifestValue(fetched.get(r.spec), r.key, r.join, r.field, parse));
5649
- }
5650
- for (const label of new Set(reads.map((r) => r.label))) {
5651
- out.set(label, (field, rev) => values.get(keyOf(label, field, rev)));
5652
- }
5653
- return out;
5654
- }
5655
- function judge(input, root, history, blobs, now, machines, manifestStamp2) {
5656
- const fenced = input.extracted.fenced;
5657
- const extractor = extractorFor(input);
5658
- const blobBody = (sha) => sha === null ? null : bodyOf(blobs.get(sha) ?? "", fenced);
5659
- const stampsOf = (sha) => {
5660
- if (sha === null) return {};
5661
- const content = blobs.get(sha);
5662
- return content === void 0 ? {} : extractStamps(extractor, content, input.absPath);
5663
- };
5664
- const stampAt = (field, h, parent) => {
5665
- const kept = manifestStamp2?.(field, parent ? `${h.sha}^` : h.sha);
5666
- if (kept !== void 0 && kept !== null) return kept;
5667
- return stampsOf(parent ? h.oldBlob : h.newBlob)[field];
5668
- };
5669
- const { bodyChanging, oldest, newestBody } = factCommits(input, history, blobs);
5670
- const head = history[0];
5671
- const headBlob = head === void 0 ? null : head.newBlob;
5672
- const created = oldest === void 0 ? null : createdOf(oldest, stampAt("created", oldest, false));
5673
- const workingChanged = !sameBody(
5674
- bodyOf(input.content, fenced),
5675
- headBlob === null ? null : blobBody(headBlob)
5676
- );
5677
- let lastUpdated;
5678
- let lastBodyCommit;
5679
- if (workingChanged) {
5680
- lastBodyCommit = null;
5681
- const working = input.extracted.data["last-updated"];
5682
- const committed = stampsOf(headBlob)["last-updated"];
5683
- lastUpdated = working !== void 0 && working !== null && !sameValue(working, committed) ? git(working, "stamped in working tree") : git(utcDate2(now()), "uncommitted body change");
5684
- } else if (newestBody === void 0) {
5685
- lastBodyCommit = null;
5686
- lastUpdated = null;
5687
- } else {
5688
- lastBodyCommit = newestBody.sha;
5689
- lastUpdated = lastUpdatedOf(
5690
- newestBody,
5691
- stampAt("last-updated", newestBody, false),
5692
- stampAt("last-updated", newestBody, true)
5693
- );
5694
- }
5695
- const authors = bodyChanging.length === 0 ? null : authorsOf(bodyChanging, machines);
5696
- let reviewedBy = null;
5697
- let lastReviewed = null;
5698
- if (!workingChanged && newestBody !== void 0 && newestBody.reviewedBy.length > 0) {
5699
- const evidence = `Reviewed-by trailer in ${short2(newestBody.sha)}`;
5700
- reviewedBy = git(newestBody.reviewedBy.map(nameOf), evidence);
5701
- lastReviewed = git(datePart(newestBody.authorDate), evidence);
5702
- }
5703
- return {
5704
- created,
5705
- "last-updated": lastUpdated,
5706
- authors,
5707
- "reviewed-by": reviewedBy,
5708
- "last-reviewed": lastReviewed,
5709
- provenance: null,
5710
- lastBodyCommit,
5711
- root
5712
- };
5713
- }
5714
- function createdOf(oldest, stamp) {
5715
- if (stamp !== void 0 && stamp !== null) {
5716
- return git(stamp, `stamped in ${short2(oldest.sha)}`);
5717
- }
5718
- const date = datePart(oldest.authorDate);
5719
- return git(date, `added in ${short2(oldest.sha)} (${date})`);
5720
- }
5721
- function lastUpdatedOf(commit, stamp, before) {
5722
- if (stamp !== void 0 && stamp !== null && !sameValue(stamp, before)) {
5723
- return git(stamp, `stamped in ${short2(commit.sha)}`);
5724
- }
5725
- const date = datePart(commit.authorDate);
5726
- return git(date, `body changed in ${short2(commit.sha)} (${date})`);
5727
- }
5728
- function authorsOf(bodyChanging, machines) {
5729
- const seen = /* @__PURE__ */ new Set();
5730
- const names = [];
5731
- const add = (name, email) => {
5732
- if (name === "") return;
5733
- const identity = email === null || email === "" ? name : `${name} <${email}>`;
5734
- if (machineIdentity(identity, machines) !== void 0) return;
5735
- const key = email === null || email === "" ? `name:${name.toLowerCase()}` : email.toLowerCase();
5736
- if (seen.has(key)) return;
5737
- seen.add(key);
5738
- names.push(name);
5739
- };
5740
- for (const h of bodyChanging) {
5741
- add(h.authorName, h.authorEmail);
5742
- for (const co of h.coAuthors) add(nameOf(co), emailOf(co));
5743
- }
5744
- const n = bodyChanging.length;
5745
- return git(names, `${n} body-changing ${n === 1 ? "commit" : "commits"}`);
5746
- }
5747
- function provenanceFenced(extracted) {
5748
- return extracted.fenced === true || extracted.format === "markdown" || extracted.format === "mdx";
5749
- }
5750
- var OUTSIDE_RANGE = "outside-the-attributed-range";
5751
- var ATTRIBUTED = ":attributed";
5752
- function provenanceFor(entry, blame, commits, opts) {
5753
- const { input } = entry;
5754
- const fenced = provenanceFenced(input.extracted);
5755
- const base = { content: input.content, commits, machines: opts.machines, fenced };
5756
- let derivation;
5757
- const attribution = input.attribution;
5758
- if (attribution !== void 0) {
5759
- const page = readProvenancePage(input.content, { fenced });
5760
- const { bodyLine } = page;
5761
- const ranges = attribution.targets.flatMap((target) => {
5762
- const entry2 = attributeRange({ ...base, blame, target, generatedBy: attribution.generatedBy, page });
5763
- const span = entry2 === void 0 ? void 0 : parseLines(entry2.lines);
5764
- return span === void 0 ? [] : [toFileLines(span, bodyLine)];
5765
- });
5766
- const inRange = (line) => ranges.some((range) => line.finalLine >= range.start && line.finalLine <= range.end);
5767
- const attributed = new Map(commits);
5768
- const trailers = { generatedBy: [attribution.generatedBy], coAuthoredBy: [] };
5769
- attributed.set(OUTSIDE_RANGE, { sha: OUTSIDE_RANGE, trailers: { generatedBy: [], coAuthoredBy: [] } });
5770
- const rekeyed = blame.map((line) => {
5771
- if (inRange(line)) {
5772
- if (line.uncommitted) return line;
5773
- const sha = `${line.sha}${ATTRIBUTED}`;
5774
- attributed.set(sha, { sha, trailers });
5775
- return { ...line, sha };
5776
- }
5777
- return line.uncommitted ? { ...line, sha: OUTSIDE_RANGE, uncommitted: false } : line;
5778
- });
5779
- derivation = deriveProvenance({
5780
- ...base,
5781
- commits: attributed,
5782
- generatedBy: attribution.generatedBy,
5783
- blame: rekeyed
5784
- });
5785
- } else {
5786
- derivation = deriveProvenance({
5787
- ...base,
5788
- blame,
5789
- ...opts.generatedBy !== void 0 ? { generatedBy: opts.generatedBy } : {}
5790
- });
5791
- }
5792
- const entries = derivation.derived.map((d) => d.entry);
5793
- return {
5794
- value: entries.length === 0 ? null : git(entries, provenanceEvidence(derivation)),
5795
- derivation
5796
- };
5797
- }
5798
- function provenanceEvidence(derivation) {
5799
- const shas = [];
5800
- let uncommitted = false;
5801
- for (const range of derivation.derived) {
5802
- for (let n = range.span.start; n <= range.span.end; n++) {
5803
- const evidence = derivation.evidenceByLine.get(n);
5804
- if (evidence?.machine === void 0) continue;
5805
- if (evidence.rule === 1) uncommitted = true;
5806
- else if (!shas.includes(evidence.sha)) shas.push(evidence.sha);
5807
- }
5808
- }
5809
- const first = shas[0];
5810
- if (first === void 0) return "uncommitted";
5811
- const parts = [`blame ${short2(first)}`];
5812
- if (shas.length > 1) parts.push(`${String(shas.length)} commits`);
5813
- if (uncommitted) parts.push("uncommitted");
5814
- return parts.join(", ");
5815
- }
5816
- async function blameOf(run2, rel, content) {
5817
- const out = await run2([
5818
- "-c",
5819
- "core.quotePath=false",
5820
- "blame",
5821
- "--no-ignore-revs-file",
5822
- "--line-porcelain",
5823
- "--",
5824
- rel
5825
- ]);
5826
- if (out.tooLarge) throw new HistoryTooLarge();
5827
- if (out.code !== 0) {
5828
- const inHead = await run2(["cat-file", "-e", `HEAD:${rel}`]);
5829
- if (inHead.code !== 0) {
5830
- return splitLines(content).map((line, i) => ({
5831
- sha: ZERO_SHA,
5832
- origLine: i + 1,
5833
- finalLine: i + 1,
5834
- author: "Not Committed Yet",
5835
- authorMail: "not.committed.yet",
5836
- filename: rel,
5837
- boundary: false,
5838
- uncommitted: true,
5839
- content: line
5840
- }));
5841
- }
5842
- throw new BlameUnreadable(rel, out.stderr.trim());
5843
- }
5844
- return parseLinePorcelain(text(out));
5845
- }
5846
- async function commitEvidence(run2, root, pages, blobs, parse) {
5847
- const plans = pages.map(({ entry, blame, history }) => {
5848
- const pathAt = /* @__PURE__ */ new Map();
5849
- for (const line of blame) {
5850
- if (!line.uncommitted && !pathAt.has(line.sha)) pathAt.set(line.sha, unquotePath(line.filename));
5851
- }
5852
- const trailers = /* @__PURE__ */ new Map();
5853
- const pageBlob = /* @__PURE__ */ new Map();
5854
- for (const h of history) {
5855
- trailers.set(h.sha, { generatedBy: h.generatedBy, coAuthoredBy: h.coAuthors });
5856
- const blob = h.newBlob === null ? void 0 : blobs.get(h.newBlob);
5857
- if (blob !== void 0) pageBlob.set(h.sha, blob);
5858
- }
5859
- const manifest = entry.input.provenanceManifest;
5860
- const manifestRel = manifest === void 0 ? void 0 : insideRoot(root, manifest.absPath);
5861
- return { entry, pathAt, trailers, pageBlob, manifest, manifestRel };
5862
- });
5863
- const unlogged = /* @__PURE__ */ new Set();
5864
- const specs = /* @__PURE__ */ new Set();
5865
- for (const { pathAt, trailers, pageBlob, manifestRel } of plans) {
5866
- for (const [sha, path] of pathAt) {
5867
- if (!trailers.has(sha)) unlogged.add(sha);
5868
- if (!pageBlob.has(sha)) specs.add(`${sha}:${path}`);
5869
- if (manifestRel !== void 0) specs.add(`${sha}:${manifestRel}`);
5870
- }
5871
- }
5872
- const logged = /* @__PURE__ */ new Map();
5873
- if (unlogged.size > 0) {
5874
- const out = await run2(["log", "--no-walk=unsorted", "--stdin", RECORD_FORMAT], `${[...unlogged].join("\n")}
5875
- `);
5876
- if (out.tooLarge) throw new HistoryTooLarge();
5877
- if (out.code !== 0) throw new BlobsUnreadable(`git log exit ${String(out.code)}`);
5878
- for (const record of parseLog(text(out))) {
5879
- logged.set(record.sha, { generatedBy: record.generatedBy, coAuthoredBy: record.coAuthors });
5880
- }
5881
- }
5882
- const fetched = await fetchSpecs(run2, [...specs]);
5883
- return plans.map(({ entry, pathAt, trailers, pageBlob, manifest, manifestRel }) => {
5884
- const commits = /* @__PURE__ */ new Map();
5885
- for (const [sha, path] of pathAt) {
5886
- const blob = pageBlob.get(sha) ?? fetched.get(`${sha}:${path}`);
5887
- commits.set(sha, {
5888
- sha,
5889
- trailers: trailers.get(sha) ?? logged.get(sha) ?? { generatedBy: [], coAuthoredBy: [] },
5890
- ...blob !== void 0 ? { blob } : {},
5891
- ...manifest !== void 0 ? {
5892
- stamp: manifestRel === void 0 ? [] : manifestStamp(
5893
- fetched.get(`${sha}:${manifestRel}`),
5894
- manifest.join === "path" ? entryAt(manifest.entry, entry.rel, path) : manifest.entry,
5895
- manifest.join,
5896
- parse
5897
- )
5898
- } : {}
5899
- });
5900
- }
5901
- return commits;
5902
- });
5903
- }
5904
- function insideRoot(root, abs) {
5905
- const rel = relative6(root, abs);
5906
- if (rel === "" || rel.startsWith("..") || isAbsolute4(rel)) return void 0;
5907
- return rel.split(sep5).join("/");
5908
- }
5909
- function entryAt(entry, rel, relAtCommit) {
5910
- if (relAtCommit === rel) return entry;
5911
- const fromHere = posix4.relative(posix4.dirname(rel), relAtCommit);
5912
- return posix4.join(posix4.dirname(entry), fromHere);
5913
- }
5914
- function manifestStamp(text2, key, join5, parse) {
5915
- return provenanceEntries(manifestValue(text2, key, join5, PROVENANCE_FIELD, parse));
5916
- }
5917
- function yamlOnce() {
5918
- const parsed = /* @__PURE__ */ new Map();
5919
- return (text2) => {
5920
- if (parsed.has(text2)) return parsed.get(text2);
5921
- let data;
5922
- try {
5923
- data = parseYaml3(text2);
5924
- } catch {
5925
- data = void 0;
5926
- }
5927
- parsed.set(text2, data);
5928
- return data;
5929
- };
5930
- }
5931
- function manifestValue(text2, key, join5, field, parse) {
5932
- if (text2 === void 0) return void 0;
5933
- const data = parse(text2);
5934
- if (!isRecord6(data)) return void 0;
5935
- for (const [candidate, value] of Object.entries(data)) {
5936
- const same = join5 === "path" ? posix4.normalize(candidate) === posix4.normalize(key) : candidate === key;
5937
- if (same && isRecord6(value)) return value[field];
5938
- }
5939
- return void 0;
5940
- }
5941
- function isRecord6(x) {
5942
- return typeof x === "object" && x !== null && !Array.isArray(x);
5943
- }
5944
- async function fetchSpecs(run2, specs) {
5945
- const found = /* @__PURE__ */ new Map();
5946
- if (specs.length === 0) return found;
5947
- const out = await run2(["cat-file", "--batch"], `${specs.join("\n")}
5948
- `);
5949
- if (out.tooLarge && specs.length > 1) {
5950
- const half = Math.ceil(specs.length / 2);
5951
- const first = await fetchSpecs(run2, specs.slice(0, half));
5952
- const rest = await fetchSpecs(run2, specs.slice(half));
5953
- return new Map([...first, ...rest]);
5954
- }
5955
- if (out.tooLarge) throw new HistoryTooLarge();
5956
- if (out.code !== 0) throw new BlobsUnreadable(`exit ${String(out.code)}`);
5957
- for (const [spec, content] of parseSpecBatch(out.raw, specs)) found.set(spec, content);
5958
- return found;
5959
- }
5960
- function parseSpecBatch(buf, specs) {
5961
- const found = /* @__PURE__ */ new Map();
5962
- let cursor = 0;
5963
- for (const spec of specs) {
5964
- const nl = buf.indexOf(10, cursor);
5965
- if (nl === -1) throw new BlobsUnreadable(`the answer ends before ${spec}`);
5966
- const header = buf.toString("utf8", cursor, nl);
5967
- cursor = nl + 1;
5968
- if (header.endsWith(" missing") || header.endsWith(" ambiguous")) continue;
5969
- const parts = header.split(" ");
5970
- const size = Number(parts[2]);
5971
- if (parts.length !== 3 || !Number.isInteger(size) || size < 0) {
5972
- throw new BlobsUnreadable(`unreadable header for ${spec}: ${header}`);
5973
- }
5974
- if (parts[1] === "blob") found.set(spec, buf.toString("utf8", cursor, cursor + size));
5975
- cursor += size + 1;
5976
- }
5977
- return found;
5978
- }
5979
- function extractorFor(input) {
5980
- return extractorByName(input.extracted.format) ?? extractorForExtension(extname2(input.absPath));
5981
- }
5982
- function extractStamps(extractor, content, filePath) {
5983
- if (!extractor) return {};
5984
- try {
5985
- return extractor.extract(content, filePath).data;
5986
- } catch {
5987
- return {};
5988
- }
5989
- }
5990
- function sameBody(a, b) {
5991
- if (a === null || b === null) return a === b;
5992
- return a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
5993
- }
5994
- function sameValue(a, b) {
5995
- return toJsonText(a) === toJsonText(b);
5996
- }
5997
- var short2 = (sha) => sha.slice(0, 7);
5998
- var datePart = (iso) => {
5999
- const d = new Date(iso);
6000
- return Number.isNaN(d.getTime()) ? "" : utcDate2(d);
6001
- };
6002
- function utcDate2(d) {
6003
- return d.toISOString().slice(0, 10);
6004
- }
6005
- function nameOf(trailer) {
6006
- const lt = trailer.lastIndexOf("<");
6007
- return (lt === -1 ? trailer : trailer.slice(0, lt)).trim();
6008
- }
6009
- function emailOf(trailer) {
6010
- const m = /<([^>]*)>\s*$/.exec(trailer);
6011
- return m?.[1]?.trim() ?? null;
6012
- }
6013
- var MAX_STDERR_BYTES = 16 * 1024;
6014
- var GIT_CONCURRENCY = Math.min(8, availableParallelism());
6015
- function text(out) {
6016
- try {
6017
- return out.raw.toString("utf8");
6018
- } catch {
6019
- throw new HistoryTooLarge();
6020
- }
6021
- }
6022
- function runGit(args, cwd, stdin, maxBytes) {
6023
- return new Promise((settle2, reject) => {
6024
- let done = false;
6025
- const finish = (out) => {
6026
- if (done) return;
6027
- done = true;
6028
- settle2(out);
6029
- };
6030
- const fail = (err) => {
6031
- if (done) return;
6032
- done = true;
6033
- reject(err instanceof Error ? err : new Error(String(err)));
6034
- };
6035
- const failed = (missing) => ({
6036
- code: null,
6037
- raw: Buffer.alloc(0),
6038
- missing,
6039
- tooLarge: false,
6040
- stderr: ""
6041
- });
6042
- let child;
6043
- try {
6044
- child = spawn2("git", args, { cwd, windowsHide: true });
6045
- } catch {
6046
- finish(failed(true));
6047
- return;
6048
- }
6049
- let chunks = [];
6050
- let total = 0;
6051
- let tooLarge = false;
6052
- child.stdout.on("data", (chunk) => {
6053
- if (tooLarge) return;
6054
- total += chunk.length;
6055
- if (total > maxBytes) {
6056
- tooLarge = true;
6057
- chunks = [];
6058
- child.kill();
6059
- return;
6060
- }
6061
- chunks.push(chunk);
6062
- });
6063
- const errChunks = [];
6064
- let errTotal = 0;
6065
- child.stderr.on("data", (chunk) => {
6066
- if (errTotal >= MAX_STDERR_BYTES) return;
6067
- const kept = chunk.subarray(0, MAX_STDERR_BYTES - errTotal);
6068
- errTotal += kept.length;
6069
- errChunks.push(kept);
6070
- });
6071
- child.on("error", (err) => {
6072
- finish(failed(err.code === "ENOENT"));
6073
- });
6074
- child.stdin.on("error", () => {
6075
- });
6076
- child.on("close", (code) => {
6077
- try {
6078
- const raw = tooLarge ? Buffer.alloc(0) : Buffer.concat(chunks);
6079
- const stderr = Buffer.concat(errChunks).toString("utf8");
6080
- finish({ code, raw, missing: false, tooLarge, stderr });
6081
- } catch (err) {
6082
- fail(err);
6083
- }
6084
- });
6085
- if (stdin === void 0) child.stdin.end();
6086
- else child.stdin.end(stdin);
6087
- });
6088
- }
6089
-
6090
- // src/meta/core/derive/index.ts
6091
- var FIELD_SOURCES = {
6092
- created: ["git"],
6093
- "last-updated": ["git"],
6094
- authors: ["git"],
6095
- owner: ["codeowners"],
6096
- "reviewed-by": ["github", "gitlab", "git"],
6097
- "last-reviewed": ["github", "gitlab", "git"],
6098
- provenance: ["git"]
6099
- };
6100
- var REVIEW_SOURCES = ["github", "gitlab"];
6101
- var HOST_NAME = {
6102
- github: "GitHub",
6103
- gitlab: "GitLab"
6104
- };
6105
- function sourcesFor(field, commands) {
6106
- if (isBuiltinField(field)) return FIELD_SOURCES[field];
6107
- if (commands !== void 0 && Object.hasOwn(commands, field)) return ["command"];
6108
- return [];
6109
- }
6110
- function consultedSources(fields, sources, commands) {
6111
- const needed = /* @__PURE__ */ new Set();
6112
- for (const field of fields) {
6113
- for (const s of sourcesFor(field, commands)) needed.add(s);
6114
- }
6115
- return sources.filter((s) => needed.has(s));
6116
- }
6117
- function assertSourcesAvailable(sources, hint) {
6118
- for (const name of Object.keys(sources)) {
6119
- const status = sources[name];
6120
- if (status === void 0 || status.available) continue;
6121
- const reason = status.reason ?? "it could not answer";
6122
- throw new DocmetaError(
6123
- `${name} source unavailable: ${reason}${hint === void 0 ? "" : `; ${hint}`}`
6124
- );
6125
- }
6126
- }
6127
- async function deriveMetadata(inputs, ctx) {
6128
- const consulted = new Set(consultedSources(ctx.fields, ctx.sources, ctx.commands));
6129
- const sources = {};
6130
- let git2 = /* @__PURE__ */ new Map();
6131
- if (consulted.has("git")) {
6132
- const result = await deriveFromGit(inputs, {
6133
- cwd: ctx.cwd,
6134
- now: ctx.now,
6135
- fields: ctx.fields,
6136
- ...ctx.generatedBy !== void 0 ? { generatedBy: ctx.generatedBy } : {},
6137
- ...ctx.machines !== void 0 ? { machines: ctx.machines } : {}
6138
- });
6139
- sources.git = result.status;
6140
- git2 = result.records;
6141
- }
6142
- const provenance = /* @__PURE__ */ new Map();
6143
- for (const [label, facts] of git2) {
6144
- if (facts.provenanceDerivation !== void 0) provenance.set(label, facts.provenanceDerivation);
6145
- }
6146
- const rootCache = /* @__PURE__ */ new Map();
6147
- const rootOf = (absPath) => {
6148
- const dir = dirname5(absPath);
6149
- let root = rootCache.get(dir);
6150
- if (root === void 0) {
6151
- root = findGitRoot(dir);
6152
- rootCache.set(dir, root);
6153
- }
6154
- return root;
6155
- };
6156
- const rootFor = (input) => git2.get(input.label)?.root ?? rootOf(input.absPath);
6157
- let owners = /* @__PURE__ */ new Map();
6158
- if (consulted.has("codeowners")) {
6159
- const result = await deriveFromCodeowners(inputs, {
6160
- explicit: ctx.codeowners,
6161
- configDir: ctx.configDir,
6162
- rootOf
6163
- });
6164
- sources.codeowners = result.status;
6165
- owners = result.records;
6166
- }
6167
- const requested = REVIEW_SOURCES.filter((s) => consulted.has(s));
6168
- let reviews = /* @__PURE__ */ new Map();
6169
- if (requested.length > 0) {
6170
- if (!consulted.has("git")) {
6171
- for (const s of requested) {
6172
- sources[s] = {
6173
- available: false,
6174
- reason: `the ${s} source needs the git source to find each document's commits; add git to sources`
6175
- };
6176
- }
6177
- } else if (sources.git?.available === false) {
6178
- for (const s of requested) {
6179
- sources[s] = {
6180
- available: false,
6181
- reason: `the git source could not answer, so the ${s} source cannot locate any commit`
6182
- };
6183
- }
6184
- } else {
6185
- const result = await deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested);
6186
- Object.assign(sources, result.status);
6187
- reviews = result.records;
6188
- }
6189
- }
6190
- let commanded = /* @__PURE__ */ new Map();
6191
- const doomed = Object.values(sources).some((s) => !s.available);
6192
- if (consulted.has("command") && ctx.commands !== void 0 && !doomed) {
6193
- const wanted = new Set(ctx.fields);
6194
- const commands = Object.fromEntries(
6195
- Object.entries(ctx.commands).filter(([field]) => wanted.has(field))
6196
- );
6197
- if (Object.keys(commands).length > 0) {
6198
- const result = await deriveFromCommands(inputs, commands, { cwd: ctx.configDir ?? ctx.cwd });
6199
- sources.command = result.status;
6200
- commanded = result.records;
6201
- }
6202
- }
6203
- const records = /* @__PURE__ */ new Map();
6204
- for (const input of inputs) {
6205
- const fields = {};
6206
- const g = git2.get(input.label);
6207
- const r = reviews.get(input.label);
6208
- const c = commanded.get(input.label);
6209
- for (const field of ctx.fields) {
6210
- let value = null;
6211
- for (const source of sourcesFor(field, ctx.commands)) {
6212
- if (!consulted.has(source)) continue;
6213
- const candidate = valueFrom(source, field, g, r, owners.get(input.label), c);
6214
- if (candidate !== null) {
6215
- value = candidate;
6216
- break;
6217
- }
6218
- }
6219
- fields[field] = value;
6220
- }
6221
- records.set(input.label, { file: input.label, fields });
6222
- }
6223
- return { records, sources, ...provenance.size > 0 ? { provenance } : {} };
6224
- }
6225
- function valueFrom(source, field, git2, reviews, owner, commanded) {
6226
- switch (source) {
6227
- case "git":
6228
- if (git2 === void 0 || !isBuiltinField(field) || field === "owner") return null;
6229
- return git2[field];
6230
- case "command":
6231
- return commanded?.[field] ?? null;
6232
- case "codeowners":
6233
- return field === "owner" ? owner ?? null : null;
6234
- case "github":
6235
- case "gitlab": {
6236
- if (reviews === void 0) return null;
6237
- if (field !== "reviewed-by" && field !== "last-reviewed") return null;
6238
- const fact = reviews[field];
6239
- return fact !== null && fact.source === source ? fact : null;
6240
- }
6241
- }
6242
- }
6243
- async function deriveReviewsByRoot(inputs, git2, rootFor, ctx, requested) {
6244
- const byRoot = /* @__PURE__ */ new Map();
6245
- for (const input of inputs) {
6246
- const root = rootFor(input);
6247
- if (root === null) continue;
6248
- const bucket = byRoot.get(root) ?? [];
6249
- bucket.push(input);
6250
- byRoot.set(root, bucket);
6251
- }
6252
- const status = {};
6253
- const records = /* @__PURE__ */ new Map();
6254
- const unavailable2 = (which, reason) => {
6255
- for (const s of which) status[s] = { available: false, reason };
6256
- return { status, records: /* @__PURE__ */ new Map() };
6257
- };
6258
- if (byRoot.size === 0) {
6259
- return unavailable2(requested, "no git repository contains the documents");
6260
- }
6261
- const cacheDir = resolve5(ctx.configDir ?? ctx.cwd, REVIEW_CACHE_DIR);
6262
- for (const [root, bucket] of byRoot) {
6263
- const client = ctx.reviews ?? await createReviewClient(root, { cwd: ctx.cwd, cache: ctx.cache, cacheDir, requested });
6264
- const identity = await client.detect();
6265
- if (identity === null) {
6266
- const s = await client.status();
6267
- return unavailable2(requested, s.reason ?? "no origin remote to tell GitHub from GitLab");
6268
- }
6269
- const source = identity.kind;
6270
- if (!requested.includes(source)) {
6271
- return unavailable2(
6272
- requested,
6273
- `the origin remote is ${identity.host}, which is ${HOST_NAME[source]}; add ${source} to sources, or drop reviewed-by and last-reviewed from the managed fields`
6274
- );
6275
- }
6276
- const result = await deriveFromReviews(
6277
- bucket.map((input) => ({
6278
- label: input.label,
6279
- sha: git2.get(input.label)?.lastBodyCommit ?? null
6280
- })),
6281
- client
6282
- );
6283
- if (!result.status.available) {
6284
- status[source] = result.status;
6285
- return { status, records: /* @__PURE__ */ new Map() };
6286
- }
6287
- status[source] = result.status;
6288
- for (const [label, facts] of result.records) records.set(label, facts);
6289
- }
6290
- return { status, records };
6291
- }
6292
-
6293
- // src/meta/core/derive/table.ts
6294
- var DERIVED_VIEW = "derived";
6295
- var DERIVED_ROWS = "_derived_rows";
6296
- var RESOLVED_VIEW = "resolved";
6297
- var ORIGIN_COLUMN = "_origin";
6298
- var SOURCES_COLUMN = "_sources";
6299
- function derivedColumns(commands) {
6300
- return ["_path", ...derivableFields(commands), SOURCES_COLUMN];
6301
- }
6302
- function mentionsDerived(sql) {
6303
- return /\b(?:from|join|update|into|table|view)\s+(?:"derived"|derived\b)/i.test(sql);
6304
- }
6305
- function fieldsForSql(sql, fields) {
6306
- const named = (field) => new RegExp(`(?<![\\w-])${escapeRegExp(field)}(?![\\w-])`, "i").test(sql);
6307
- if (sql.includes("*") || /\b_(?:sources|origin)\b/i.test(sql)) {
6308
- return fields.filter((field) => field !== PROVENANCE_FIELD || named(field));
6309
- }
6310
- return fields.filter(named);
6311
- }
6312
- function escapeRegExp(s) {
6313
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6314
- }
6315
- function createDerivedView(db, records, columns) {
6316
- const quoted = columns.map(quoteIdent);
6317
- db.exec(
6318
- `CREATE TABLE ${DERIVED_ROWS} (${quoted.map((c, i) => i === 0 ? `${c} TEXT PRIMARY KEY` : `${c} TEXT`).join(", ")})`
6319
- );
6320
- const insert = db.prepare(
6321
- `INSERT INTO ${DERIVED_ROWS} VALUES (${quoted.map(() => "?").join(", ")})`
6322
- );
6323
- const fields = columns.slice(1, -1);
6324
- db.exec("BEGIN");
6325
- let committed = false;
6326
- try {
6327
- for (const [label, record] of records) {
6328
- const sources = {};
6329
- const values = fields.map((field) => {
6330
- const derived = record.fields[field];
6331
- if (derived == null) return null;
6332
- sources[field] = { source: derived.source, evidence: derived.evidence };
6333
- return bindValue(derived.value);
6334
- });
6335
- insert.run(label, ...values, JSON.stringify(sources));
6336
- }
6337
- db.exec("COMMIT");
6338
- committed = true;
6339
- } finally {
6340
- if (!committed) db.exec("ROLLBACK");
6341
- }
6342
- db.exec(`CREATE VIEW ${DERIVED_VIEW} AS SELECT * FROM ${DERIVED_ROWS}`);
6343
- }
6344
- function resolvedColumns(docsColumns, commands) {
6345
- const columns = ["_path"];
6346
- const seen = /* @__PURE__ */ new Set(["_path", ORIGIN_COLUMN, SOURCES_COLUMN]);
6347
- for (const column of docsColumns) {
6348
- if (seen.has(column) || RESERVED.has(column)) continue;
6349
- seen.add(column);
6350
- columns.push(column);
6351
- }
6352
- for (const field of derivableFields(commands)) {
6353
- if (seen.has(field)) continue;
6354
- seen.add(field);
6355
- columns.push(field);
6356
- }
6357
- columns.push(ORIGIN_COLUMN, SOURCES_COLUMN);
6358
- return columns;
6359
- }
6360
- function mentionsResolved(sql) {
6361
- return /\b(?:from|join|update|into|table|view)\s+(?:"resolved"|resolved\b)/i.test(sql);
6362
- }
6363
- function jsonPath(field) {
6364
- return `$.${JSON.stringify(field)}`;
6365
- }
6366
- function sqlString(value) {
6367
- return `'${value.replaceAll("'", "''")}'`;
6368
- }
6369
- function createResolvedView(db, docsColumns, commands) {
6370
- const columns = resolvedColumns(docsColumns, commands);
6371
- const fields = columns.slice(1, -2);
6372
- const inDocs = new Set(docsColumns);
6373
- const inDerived = new Set(derivableFields(commands));
6374
- const selects = [`d.${quoteIdent("_path")} AS ${quoteIdent("_path")}`];
6375
- const origin = [];
6376
- for (const field of fields) {
6377
- const path = sqlString(jsonPath(field));
6378
- const asserted = `json_type(d.${quoteIdent("_data")}, ${path}) IS NOT NULL`;
6379
- const assertedValue = inDocs.has(field) ? `d.${quoteIdent(field)}` : `d.${quoteIdent("_data")} ->> ${path}`;
6380
- if (inDerived.has(field)) {
6381
- const derivedValue = `r.${quoteIdent(field)}`;
6382
- selects.push(
6383
- `CASE WHEN ${asserted} THEN ${assertedValue} ELSE ${derivedValue} END AS ${quoteIdent(field)}`
6384
- );
6385
- origin.push(
6386
- `${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' WHEN ${derivedValue} IS NOT NULL THEN 'derived' END`
6387
- );
6388
- } else {
6389
- selects.push(`${assertedValue} AS ${quoteIdent(field)}`);
6390
- origin.push(`${sqlString(field)}, CASE WHEN ${asserted} THEN 'asserted' END`);
6391
- }
6392
- }
6393
- selects.push(
6394
- `json_patch('{}', json_object(${origin.join(", ")})) AS ${quoteIdent(ORIGIN_COLUMN)}`
6395
- );
6396
- selects.push(`r.${quoteIdent(SOURCES_COLUMN)} AS ${quoteIdent(SOURCES_COLUMN)}`);
6397
- db.exec(
6398
- `CREATE VIEW ${RESOLVED_VIEW} AS SELECT ${selects.join(", ")} FROM docs d LEFT JOIN ${DERIVED_ROWS} r USING (${quoteIdent("_path")})`
6399
- );
6400
- }
6401
- async function deriveForTable(inputs, ctx, hint, fields) {
6402
- const derive = ctx.config?.derive;
6403
- const commands = commandsOf(derive);
6404
- const managesProvenance = derive?.fields?.includes(PROVENANCE_FIELD) === true && !fields.includes(PROVENANCE_FIELD);
6405
- const result = await deriveMetadata(inputs, {
6406
- cwd: ctx.cwd,
6407
- base: ctx.base,
6408
- ...ctx.configDir !== void 0 ? { configDir: ctx.configDir } : {},
6409
- sources: derive?.sources ?? [...DERIVE_SOURCES],
6410
- fields: managesProvenance ? [...fields, PROVENANCE_FIELD] : fields,
6411
- ...derive?.codeowners !== void 0 ? { codeowners: derive.codeowners } : {},
6412
- ...commands !== void 0 ? { commands } : {},
6413
- cache: ctx.cache,
6414
- now: ctx.now ?? (() => /* @__PURE__ */ new Date()),
6415
- machines: machinesOf(derive)
6416
- });
6417
- assertSourcesAvailable(result.sources, hint);
6418
- return result.records;
6419
- }
6420
-
6421
- // src/meta/core/checks.ts
6422
- var CHECK_KEYWORD = "check";
6423
- function checkSchemaRef(name) {
6424
- if (!CHECK_NAME.test(name) || name.toLowerCase().endsWith(".json")) {
6425
- throw new DocmetaError(
6426
- `check name "${name}" must match [a-z0-9][a-z0-9._-]* and not end in ".json" \u2014 the name is part of each finding's identity (check:<name>).`
6427
- );
6428
- }
6429
- return `check:${name}`;
6430
- }
6431
- var CONVENTION_COLUMNS = /* @__PURE__ */ new Set(["path", "line", "key", "message"]);
6432
- function rowsToFindings(name, columns, rows, loaded) {
6433
- if (!columns.includes("path")) {
6434
- throw new DocmetaError(
6435
- `check "${name}": the result has no \`path\` column. A check's SELECT names its findings through the column convention: \`path\` (required), and optionally \`line\`, \`key\`, and \`message\`; any other column is folded into the message.`
6436
- );
6437
- }
6438
- const findings = /* @__PURE__ */ new Map();
6439
- const extras = columns.filter((c) => !CONVENTION_COLUMNS.has(c));
6440
- for (const row of rows) {
6441
- const path = row.path;
6442
- if (typeof path !== "string" || loaded !== void 0 && !loaded.has(path)) {
6443
- throw new DocmetaError(
6444
- `check "${name}": a result row names ${JSON.stringify(path)}, which this run did not load \u2014 a finding must attach to a file of the corpus. Fix the check's \`path\` column.`
6445
- );
6446
- }
6447
- const key = typeof row.key === "string" && row.key !== "" ? row.key : void 0;
6448
- const line = asLine(row.line);
6449
- const message = typeof row.message === "string" && row.message !== "" ? row.message : synthesizeMessage(extras, row);
6450
- const err = {
6451
- schema: checkSchemaRef(name),
6452
- keyword: CHECK_KEYWORD,
6453
- // RFC 6901 escaping, or a key containing `/` or `~` would build a
6454
- // pointer that parses as a different (nested) location.
6455
- instancePath: key === void 0 ? "" : `/${escapePointerSegment(key)}`,
6456
- message,
6457
- ...line === void 0 ? {} : { line }
6458
- };
6459
- const bucket = findings.get(path);
6460
- if (bucket) bucket.push(err);
6461
- else findings.set(path, [err]);
6462
- }
6463
- return findings;
6464
- }
6465
- function asLine(value) {
6466
- const n = typeof value === "bigint" ? Number(value) : typeof value === "number" ? value : void 0;
6467
- return n !== void 0 && Number.isInteger(n) && n > 0 ? n : void 0;
6468
- }
6469
- function synthesizeMessage(extras, row) {
6470
- const pairs = extras.flatMap((c) => {
6471
- const value = row[c];
6472
- if (value === null || value === void 0) return [];
6473
- const text2 = typeof value === "string" ? value : typeof value === "number" || typeof value === "bigint" ? String(value) : JSON.stringify(value);
6474
- return [`${c}=${text2}`];
6475
- });
6476
- return pairs.length > 0 ? pairs.join(", ") : "check matched";
6477
- }
6478
- async function runChecks(checks, entries, ctx = {}) {
6479
- const merged = /* @__PURE__ */ new Map();
6480
- if (checks.length === 0) return merged;
6481
- const { DatabaseSync } = await loadSqlite();
6482
- const db = new DatabaseSync(":memory:");
6483
- try {
6484
- const dataColumns = corpusDataColumns(entries);
6485
- createDocsTable(db, entries, dataColumns);
6486
- registerLineFor(db, entries);
6487
- createCollectionViews(db, collectCollections(entries, ctx));
6488
- const wantsDerived = checks.filter(
6489
- (c) => mentionsDerived(c.query) || mentionsResolved(c.query)
6490
- );
6491
- if (wantsDerived.length > 0) {
6492
- if (ctx.derive === void 0) {
6493
- throw new DocmetaError(
6494
- `check "${wantsDerived[0]?.name ?? ""}": the SQL names the derived or resolved table, but this run cannot derive. Run it through \`manni meta validate\`, or drop the reference.`
6495
- );
6496
- }
6497
- const commands = ctx.commands;
6498
- const readable = derivableFields(commands);
6499
- const fields = /* @__PURE__ */ new Set();
6500
- for (const c of wantsDerived) for (const f of fieldsForSql(c.query, readable)) fields.add(f);
6501
- createDerivedView(db, await ctx.derive([...fields]), derivedColumns(commands));
6502
- createResolvedView(db, dataColumns, commands);
6503
- }
6504
- db.exec("PRAGMA query_only = 1");
6505
- const loaded = new Set(entries.map((e) => e.label));
6506
- for (const check of checks) {
6507
- const head = stripLeadingTrivia(check.query);
6508
- if (/^(attach|vacuum)\b/i.test(head)) {
6509
- throw new DocmetaError(
6510
- `check "${check.name}": ${head.split(/\s/, 1)[0]?.toUpperCase() ?? "that statement"} is refused: it can write outside the docs table.`
6511
- );
6512
- }
6513
- let tokens2;
6514
- try {
6515
- tokens2 = collectNamedParameters(check.query);
6516
- } catch (err) {
6517
- throw new DocmetaError(
6518
- `check "${check.name}": ${errorMessage(err)}`
6519
- );
6520
- }
6521
- if (tokens2.length > 0) {
6522
- throw new DocmetaError(
6523
- `check "${check.name}": the SQL references ${tokens2.join(", ")}, but checks bind no parameters \u2014 an unbound name binds NULL and matches nothing, so the gate would pass on a typo. Inline the value in the SQL.`
6524
- );
6525
- }
6526
- let columns;
6527
- let rows;
6528
- try {
6529
- assertSingleStatement(check.query);
6530
- const stmt = db.prepare(check.query);
6531
- columns = stmt.columns().map((c) => c.name);
6532
- rows = stmt.all();
6533
- } catch (err) {
6534
- const message = errorMessage(err);
6535
- throw new DocmetaError(
6536
- message.includes("attempt to write a readonly database") ? `check "${check.name}": ${message} \u2014 checks are read-only by design; a check is a SELECT over the projection. Edit the corpus with \`manni meta query\` instead.` : `check "${check.name}": ${message}`
6537
- );
6538
- }
6539
- for (const [path, errs] of rowsToFindings(
6540
- check.name,
6541
- columns,
6542
- rows,
6543
- loaded
6544
- )) {
6545
- const bucket = merged.get(path);
6546
- if (bucket) bucket.push(...errs);
6547
- else merged.set(path, errs);
6548
- }
6549
- }
6550
- } finally {
6551
- db.close();
6552
- }
6553
- return merged;
6554
- }
6555
-
6556
- // src/meta/core/derive/provenance-place.ts
6557
- import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve6, sep as sep6 } from "node:path";
6558
- var toPosix3 = (p) => p.split(sep6).join("/");
6559
- function provenanceManifests(collections, configDir, base, field = PROVENANCE_FIELD) {
6560
- const out = [];
6561
- for (const collection of collections) {
6562
- for (const manifest of collection.externalMetadata) {
6563
- const implied = manifest.keys === void 0;
6564
- if (!implied && manifest.keys?.includes(field) !== true) continue;
6565
- const url = classifyRef(manifest.file).kind === "url";
6566
- if (url && !implied) throw new DocmetaError(urlRefusal(collection.name, field));
6567
- if (url) {
6568
- out.push({
6569
- collection: collection.name,
6570
- absPath: manifest.file,
6571
- file: manifest.file,
6572
- join: externalMetadataJoin(manifest),
6573
- perPage: false,
6574
- written: manifest.file,
6575
- implied: { collection, manifest, url }
6576
- });
6577
- continue;
6578
- }
6579
- const absPath = isAbsolute5(manifest.file) ? manifest.file : resolve6(configDir, manifest.file);
6580
- const perPage = hasPagePlaceholder(manifest.file);
6581
- out.push({
6582
- collection: collection.name,
6583
- absPath,
6584
- file: perPage ? manifest.file : reportedPath(absPath, base),
6585
- join: externalMetadataJoin(manifest),
6586
- perPage,
6587
- written: manifest.file,
6588
- ...implied ? { implied: { collection, manifest, url } } : {}
6589
- });
6590
- }
6591
- }
6592
- return out;
6593
- }
6594
- function urlRefusal(collection, field) {
6595
- return `collection ${collection}: ${field} cannot come from a URL manifest, because manni meta derive writes it.`;
6596
- }
6597
- function provenancePlace(label, data, manifests, collections, configDir, base, marked, field = PROVENANCE_FIELD) {
6598
- if (manifests.length === 0) return void 0;
6599
- const members = new Set(memberOf(collections, configDir, base, label));
6600
- const owning = manifests.filter(
6601
- (m) => members.has(m.collection) && (m.implied === void 0 || ownsKey(m.implied.collection, m.implied.manifest, field, marked))
6602
- );
6603
- const [declared, second] = owning;
6604
- if (declared === void 0) return void 0;
6605
- if (second !== void 0) {
6606
- throw new DocmetaError(
6607
- `${label} is in collections ${declared.collection} and ${second.collection}, and both keep ${field} in a manifest.`
6608
- );
6609
- }
6610
- if (declared.implied?.url === true) throw new DocmetaError(urlRefusal(declared.collection, field));
6611
- const manifest = declared.perPage ? ownManifest(declared, label, collections, configDir, base) : declared;
6612
- if (manifest.join === PATH_JOIN) {
6613
- const entry = toPosix3(relative7(configDir, resolve6(base, label)));
6614
- return { manifest, absPath: manifest.absPath, entry, join: manifest.join };
6615
- }
6616
- const value = data[manifest.join];
6617
- if (typeof value !== "string" && typeof value !== "number") {
6618
- throw new DocmetaError(
6619
- `${label} carries no ${manifest.join}, which ${manifest.file} joins on, so its ${field} has no entry there.`
6620
- );
6621
- }
6622
- return { manifest, absPath: manifest.absPath, entry: String(value), join: manifest.join };
6623
- }
6624
- function ownManifest(manifest, label, collections, configDir, base) {
6625
- const resolved = pageManifestPath(manifest.written, configDir, resolve6(base, label));
6626
- if ("outside" in resolved) {
6627
- const declared = collections.find((c) => c.name === manifest.collection)?.externalMetadata.find((m) => m.file === manifest.written);
6628
- throw new DocmetaError(
6629
- outsideRefusal(declared ?? { file: manifest.written }, manifest.collection, resolved.pageRel)
6630
- );
6631
- }
6632
- return { ...manifest, absPath: resolved.abs, file: reportedPath(resolved.abs, base) };
6633
- }
6634
- function readerManifests(collections, configDir, base, field = PROVENANCE_FIELD) {
6635
- const local = collections.map((c) => ({
6636
- ...c,
6637
- externalMetadata: c.externalMetadata.filter((m) => classifyRef(m.file).kind !== "url")
6638
- }));
6639
- return provenanceManifests(local, configDir, base, field);
6640
- }
6641
- function readerPlace(label, data, manifests, collections, configDir, base, marked, field = PROVENANCE_FIELD) {
6642
- try {
6643
- const place = provenancePlace(label, data, manifests, collections, configDir, base, marked, field);
6644
- return place === void 0 ? void 0 : { absPath: place.absPath, entry: place.entry, join: place.join };
6645
- } catch (err) {
6646
- if (err instanceof DocmetaError) return void 0;
6647
- throw err;
6648
- }
6649
- }
6650
- function stampManifests(label, data, collections, configDir, base, marked, fields) {
6651
- const out = {};
6652
- for (const field of STAMPED_DATE_FIELDS) {
6653
- if (!fields.includes(field)) continue;
6654
- const manifests = readerManifests(collections, configDir, base, field);
6655
- const place = readerPlace(label, data, manifests, collections, configDir, base, marked, field);
6656
- if (place !== void 0) out[field] = place;
6657
- }
6658
- return out;
6659
- }
6660
-
6661
- // src/shared/cli-options.ts
6662
- import { basename as basename4, relative as relative8 } from "node:path";
6663
- function collect(value, prev) {
6664
- return prev.concat([value]);
6665
- }
6666
- function splitList(value) {
6667
- return value.split(",").map((s) => s.trim()).filter(Boolean);
6668
- }
6669
- var STDIN_REQUIRES_AS = "Reading from stdin (`-`) requires --as <format> to choose an extractor.";
6670
- async function readStdin() {
6671
- const chunks = [];
6672
- for await (const chunk of process.stdin) chunks.push(chunk);
6673
- return Buffer.concat(chunks).toString("utf8");
6674
- }
6675
- function configOption(value) {
6676
- if (value === false) return { noConfig: true };
6677
- return typeof value === "string" ? { configPath: value } : {};
6678
- }
6679
- function explicitFalse(value) {
6680
- return value === false ? false : void 0;
6681
- }
6682
- function reportConfig(toStdout, cwd) {
6683
- return (info) => {
6684
- const where = (relative8(cwd, info.dir) || ".").replace(/\\/g, "/");
6685
- const line = `Using ${basename4(info.path)} (${where})
6686
- `;
6687
- (toStdout ? process.stdout : process.stderr).write(line);
6688
- };
6689
- }
6690
-
6691
- // src/meta/commands/validate.ts
6692
- function parseErrorResult(file, format, message, keyword) {
6693
- const err = {
6694
- schema: "(parse)",
6695
- instancePath: "",
6696
- message,
6697
- keyword
6698
- };
6699
- return { file, format, ok: false, schemas: [], errors: [err] };
6700
- }
6701
- function once(fn) {
6702
- let pending;
6703
- return () => {
6704
- pending ??= fn();
6705
- return pending;
6706
- };
6707
- }
6708
- async function runValidate(opts) {
6709
- const cwd = opts.cwd ?? process.cwd();
6710
- const run2 = await resolveRunConfig({
6711
- cwd,
6712
- configPath: opts.configPath,
6713
- noConfig: opts.noConfig,
6714
- inputs: opts.inputs,
6715
- ...opts.collections !== void 0 ? { collections: opts.collections } : {},
6716
- onConfigLoaded: opts.onConfigLoaded
6717
- });
6718
- const {
6719
- config,
6720
- inputs,
6721
- base,
6722
- configDir,
6723
- collections,
6724
- declaredCollections,
6725
- fromCollections,
6726
- configFile
6727
- } = run2;
6728
- const membersFor = (label) => memberOf(collections, configDir ?? cwd, base, label);
6729
- const usingStdin = inputs.includes(STDIN_TOKEN);
6730
- if (inputs.length === 0) {
6731
- throw new DocmetaError(
6732
- "No files to validate. Pass paths/globs, or declare a collection under `collections:` in manni.config.yaml."
6733
- );
6734
- }
6735
- const forcedExtractor = opts.as ? extractorByName(opts.as) : void 0;
6736
- if (opts.as && !forcedExtractor) {
6737
- throw new DocmetaError(
6738
- `Unknown format "${opts.as}". Supported extensions: ${supportedExtensions().join(", ")}.`
6739
- );
6740
- }
6741
- const exts = opts.exts ?? (forcedExtractor ? forcedExtractor.extensions : void 0);
6742
- const fileInputs = inputs.filter((i) => i !== STDIN_TOKEN);
6743
- const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
6744
- const exclude = opts.exclude ?? [];
6745
- const { files: walked, gitignoreSkipped } = await resolveTargetSet({
6746
- inputs: fileInputs,
6747
- exts,
6748
- exclude,
6749
- cwd: base,
6750
- allowEmpty,
6751
- ...gitignoreOptions({
6752
- flag: opts.respectGitignore,
6753
- configured: config?.respectGitignore,
6754
- onNotice: opts.onNotice
6755
- })
6756
- });
6757
- const files = fromCollections ? retainMembers(walked, membersFor) : walked;
6758
- assertNonEmpty({
6759
- files,
6760
- inputs: fileInputs,
6761
- usingStdin,
6762
- allowEmpty,
6763
- exclude,
6764
- exts,
6765
- gitignoreSkipped,
6766
- action: "validated"
6767
- });
6768
- const trustRoot = schemaTrustRoot(cwd, configDir);
6769
- const externalMetadata = await loadExternalMetadata(collections, {
6770
- configDir: configDir ?? cwd,
6771
- base,
6772
- offline: opts.offline ?? config?.offline ?? false,
6773
- // A `{page}` manifest (0058) is read for exactly the pages this run reads.
6774
- pages: files.map((file) => resolve7(base, file))
6775
- });
6776
- const validator = new Validator(
6777
- schemaLoadOptions({
6778
- // The config's directory when a config governs the run, so one project
6779
- // keeps one cache no matter which directory the command was run from.
6780
- root: configDir ?? cwd,
6781
- // A relative file ref belongs to the run's directory, not the cache root.
6782
- fileBase: cwd,
6783
- ttlHours: config?.schemaCache?.ttlHours,
6784
- offline: opts.offline ?? config?.offline,
6785
- // Built from the rebased config, so a pinned local ref is keyed by the
6786
- // same absolute spelling `resolveSchemaSet` will hand to `loadSchema`.
6787
- pins: collectSchemaPins(config),
6788
- registered: config?.registered
6789
- })
6790
- );
6791
- const marks = pageMarks({
6792
- validator,
6793
- config,
6794
- cwd,
6795
- trustRoot,
6796
- ...opts.cliSchemas !== void 0 ? { cliSchemas: opts.cliSchemas } : {}
6797
- });
6798
- let relocation;
6799
- const relocationCtx = () => relocation ??= relocationContext(run2, {
6800
- cwd,
6801
- targets: fromCollections ? [] : opts.inputs,
6802
- validator,
6803
- ...opts.noConfig !== void 0 ? { noConfig: opts.noConfig } : {},
6804
- ...opts.env !== void 0 ? { env: opts.env } : {}
6805
- });
6806
- const refFrame = { cwd, base: configDir ?? cwd, runBase: base };
6807
- const locationFindings = async (label, own, data, refs, members, merged, lineFor) => {
6808
- const preferences = await validator.locationPreferences(data, refs);
6809
- if (preferences.size === 0) return [];
6810
- const owned = new Set(merged.collisions.map((c) => c.key));
6811
- const declaredMembers = declaredCollections === collections ? members : memberOf(declaredCollections, configDir ?? cwd, base, label);
6812
- const joins = new Set(
6813
- declaredCollections.filter((c) => declaredMembers.includes(c.name)).flatMap((c) => c.externalMetadata.map(externalMetadataJoin)).filter((join5) => join5 !== PATH_JOIN)
6814
- );
6815
- const findings = [];
6816
- for (const key of Object.keys(data)) {
6817
- const preference = preferences.get(key);
6818
- if (preference === void 0) continue;
6819
- const ref = canonicalSchemaRef(preference.schema, refFrame);
6820
- const pointer = externalMetadataPointer(key);
6821
- const common = {
6822
- keyword: LOCATION_FINDING_KEYWORD,
6823
- subject: key,
6824
- instancePath: pointer,
6825
- severity: "warning"
6826
- };
6827
- if (Object.hasOwn(own, key)) {
6828
- if (preference.location !== "external" || owned.has(key) || joins.has(key)) continue;
6829
- const line = lineFor(key);
6830
- const none = await homeless(label, own, key);
6831
- findings.push({
6832
- schema: LOCATION_EXTERNAL_SCHEMA,
6833
- ...common,
6834
- // relocate refuses --no-config (U2), so it is no answer there.
6835
- // Nor is it one for stdin, which relocate refuses: no advice there.
6836
- message: opts.noConfig ? `"${key}" is stored in the page; ${ref} prefers external metadata, and --no-config leaves it no manifest.` : label === STDIN_LABEL ? `"${key}" is stored in the page; ${ref} prefers external metadata.` : none > 0 ? `"${key}" is stored in the page; ${ref} prefers external metadata, and this document is in none of the ${String(none)} collections.` : `"${key}" is stored in the page; ${ref} prefers external metadata. Run manni meta relocate.`,
6837
- ...line != null ? { line } : {}
6838
- });
6839
- continue;
6840
- }
6841
- if (preference.location !== "page") continue;
6842
- const at2 = merged.locate(pointer);
6843
- if (at2 === void 0) continue;
6844
- findings.push({
6845
- schema: LOCATION_PAGE_SCHEMA,
6846
- ...common,
6847
- // A fetched manifest cannot give a value up, so relocate is no
6848
- // answer; the value moves in the repository that serves it.
6849
- message: /^https?:\/\//.test(at2.file) ? `"${key}" is stored in manifest ${at2.file}; ${ref} prefers the page, and a fetched manifest cannot be written. Move it in that repository.` : `"${key}" is stored in manifest ${at2.file}; ${ref} prefers the page. Run manni meta relocate.`,
6850
- file: at2.file,
6851
- ...at2.line != null ? { line: at2.line } : {}
6852
- });
6853
- }
6854
- return findings;
6855
- };
6856
- const homeless = async (label, own, key) => {
6857
- if (label === STDIN_LABEL) return 0;
6858
- const home = await keyHome(relocationCtx(), label, own, key);
6859
- return home.kind === "unowned" && home.home.kind === "none" && home.home.reason === "collections" && home.home.collections > 1 ? home.home.collections : 0;
6860
- };
6861
- const results = [];
6862
- const skippedDefaultOnly = /* @__PURE__ */ new Set();
6863
- const defaultOnly = (label, members) => {
6864
- try {
6865
- return resolveSchemaSetWithSource({
6866
- filePath: label,
6867
- fileSchema: void 0,
6868
- cliSchemas: opts.cliSchemas,
6869
- config,
6870
- memberOf: members,
6871
- fileBase: cwd,
6872
- trustRoot
6873
- }).source === "default";
6874
- } catch {
6875
- return false;
6876
- }
6877
- };
6878
- const joinHits = /* @__PURE__ */ new Map();
6879
- const scopedToCollections = (opts.collections?.length ?? 0) > 0;
6880
- const scopedByFlags = opts.inputs.length > 0 || // redundant with inputs.length (stdin is an input) — kept as belt-and-suspenders
6881
- usingStdin || opts.as !== void 0 || opts.exts !== void 0 || opts.exclude !== void 0 && opts.exclude.length > 0 || opts.respectGitignore !== void 0 || (opts.cliSchemas?.length ?? 0) > 0;
6882
- const scoped = scopedByFlags || scopedToCollections;
6883
- const configuredChecks = config?.checks ?? [];
6884
- if (!scopedByFlags) {
6885
- const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
6886
- const orphans = orphanEntries(externalMetadata, files, base, covered);
6887
- if (orphans.length > 0) throw orphanError(orphans);
6888
- }
6889
- const checksWillRun = configuredChecks.length > 0 && opts.checks !== false && !scoped;
6890
- const checkEntries = [];
6891
- const encryptionKey = lazyKey(configFile, opts.env);
6892
- let unverified = 0;
6893
- const deriveConfig = config?.derive;
6894
- const configuredFields = managedFields(deriveConfig);
6895
- const defaultedFields = deriveConfig !== void 0 && deriveConfig.fields === void 0;
6896
- const deriveWillRun = deriveConfig !== void 0 && configuredFields.length > 0 && opts.derive !== false;
6897
- const pageManaged = /* @__PURE__ */ new Map();
6898
- const derivedChecks = checksWillRun ? configuredChecks.filter((c) => mentionsDerived(c.query) || mentionsResolved(c.query)) : [];
6899
- const checksNeedDerived = derivedChecks.length > 0;
6900
- const deriveInputs = [];
6901
- const managedViews = /* @__PURE__ */ new Map();
6902
- const keepDeriveInputs = deriveWillRun || checksNeedDerived;
6903
- const deriveCommands = commandsOf(deriveConfig);
6904
- const deriveFields = new Set(deriveWillRun && !defaultedFields ? configuredFields : []);
6905
- const readable = derivableFields(deriveCommands);
6906
- for (const c of derivedChecks) for (const f of fieldsForSql(c.query, readable)) deriveFields.add(f);
6907
- const provenanceManifests2 = keepDeriveInputs && (deriveFields.has(PROVENANCE_FIELD) || deriveWillRun && configuredFields.includes(PROVENANCE_FIELD)) ? readerManifests(declaredCollections, configDir ?? cwd, base) : [];
6908
- const derivedOnce = once(async () => {
6909
- const derived = await deriveMetadata(deriveInputs, {
6910
- cwd,
6911
- base,
6912
- ...configDir !== void 0 ? { configDir } : {},
6913
- sources: deriveConfig?.sources ?? [...DERIVE_SOURCES],
6914
- fields: [...deriveFields],
6915
- ...deriveConfig?.codeowners !== void 0 ? { codeowners: deriveConfig.codeowners } : {},
6916
- ...deriveCommands !== void 0 ? { commands: deriveCommands } : {},
6917
- cache: opts.cache ?? true,
6918
- now: opts.now ?? (() => /* @__PURE__ */ new Date()),
6919
- machines: machinesOf(deriveConfig)
6920
- });
6921
- assertSourcesAvailable(
6922
- derived.sources,
6923
- deriveWillRun ? "pass --no-derive to skip the comparison, or narrow derive.sources" : "narrow derive.sources in manni.config.yaml"
6924
- );
6925
- for (const [name, status] of Object.entries(derived.sources)) {
6926
- if (status.available && status.reason !== void 0) {
6927
- opts.onNotice?.(`${name}: ${status.reason}`);
6928
- }
6929
- }
6930
- return derived;
6931
- });
6932
- const processOne = async (label, content, extension) => {
6933
- const extractor = forcedExtractor ?? extractorForExtension(extension);
6934
- if (!extractor) {
6935
- throw new DocmetaError(
6936
- `Unsupported file type "${extension}" for "${label}". Supported: ${supportedExtensions().join(", ")}. Use --as to override.`
6937
- );
6938
- }
6939
- const members = membersFor(label);
6940
- let extracted;
6941
- try {
6942
- extracted = extractor.extract(content, label, {
6943
- elements: resolveElements(label, config, members)
6944
- });
6945
- } catch (err) {
6946
- if (err instanceof DocmetaError) throw err;
6947
- if (opts.skipDefaultOnly === true && defaultOnly(label, members)) {
6948
- skippedDefaultOnly.add(label);
6949
- return;
6950
- }
6951
- results.push(
6952
- parseErrorResult(label, extractor.name, errorMessage(err), "parse")
6953
- );
6954
- return;
6955
- }
6956
- const merged = await mergeWithMarks(label, extracted, externalMetadata, members, base, {
6957
- encryptionKey,
6958
- marks
6959
- });
6960
- if (keepDeriveInputs && label !== STDIN_LABEL && (checksNeedDerived || deriveCovers(deriveConfig, members))) {
6961
- const place = provenanceManifests2.length === 0 ? void 0 : readerPlace(label, extracted.data, provenanceManifests2, declaredCollections, configDir ?? cwd, base, merged.marked);
6962
- const owned = declaredCollections.length === 0 ? {} : stampManifests(label, extracted.data, declaredCollections, configDir ?? cwd, base, merged.marked, STAMPED_DATE_FIELDS);
6963
- deriveInputs.push({
6964
- label,
6965
- absPath: resolve7(base, label),
6966
- content,
6967
- extracted,
6968
- ...place !== void 0 ? { provenanceManifest: place } : {},
6969
- ...Object.keys(owned).length > 0 ? { stampManifests: owned } : {}
6970
- });
6971
- }
6972
- const own = extracted.data;
6973
- extracted = merged.extracted;
6974
- if (deriveWillRun && label !== STDIN_LABEL) {
6975
- managedViews.set(label, {
6976
- data: extracted.data,
6977
- marked: [],
6978
- hidden: [],
6979
- refs: [],
6980
- locate: merged.locate
6981
- });
6982
- }
6983
- for (const j of merged.joins) {
6984
- const byValue = joinHits.get(j.field) ?? /* @__PURE__ */ new Map();
6985
- const line = extracted.lineFor(j.field);
6986
- const hits = byValue.get(j.value) ?? [];
6987
- hits.push({
6988
- label,
6989
- file: j.file,
6990
- shown: j.pageValue ?? j.value,
6991
- ...line != null ? { line } : {}
6992
- });
6993
- byValue.set(j.value, hits);
6994
- joinHits.set(j.field, byValue);
6995
- }
6996
- if (checksWillRun) checkEntries.push({ label, extracted });
6997
- let resolved;
6998
- try {
6999
- resolved = resolveSchemaSetWithSource({
7000
- filePath: label,
7001
- fileSchema: extracted.data[FILE_SCHEMA_KEY],
7002
- cliSchemas: opts.cliSchemas,
7003
- config,
7004
- memberOf: members,
7005
- // A document's own `$schema` is measured from the run's directory, the
7006
- // same base `loadSchema` will read it from, and contained to the
7007
- // repository the run is standing in.
7008
- fileBase: cwd,
7009
- trustRoot,
7010
- onNotice: opts.onNotice
7011
- });
7012
- } catch (err) {
7013
- results.push(
7014
- parseErrorResult(label, extractor.name, errorMessage(err), "schema")
7015
- );
7016
- return;
7017
- }
7018
- if (opts.skipDefaultOnly === true && resolved.source === "default") {
7019
- skippedDefaultOnly.add(label);
7020
- return;
7021
- }
7022
- const schemaSet = resolved.schemas;
7023
- if (deriveWillRun && defaultedFields && label !== STDIN_LABEL && deriveCovers(deriveConfig, members)) {
7024
- let claimed = /* @__PURE__ */ new Set();
7025
- try {
7026
- claimed = await validator.claimedProperties(schemaSet);
7027
- } catch (err) {
7028
- if (!(err instanceof DocmetaError)) throw err;
7029
- }
7030
- const own2 = configuredFields.filter((f) => claimed.has(f));
7031
- pageManaged.set(label, own2);
7032
- for (const f of own2) deriveFields.add(f);
7033
- }
7034
- let errors;
7035
- try {
7036
- const view = await encryptionView({
7037
- data: extracted.data,
7038
- refs: schemaSet,
7039
- validator,
7040
- key: encryptionKey,
7041
- locate: merged.locate
7042
- });
7043
- unverified += view.unverified.length;
7044
- if (deriveWillRun && label !== STDIN_LABEL) {
7045
- managedViews.set(label, {
7046
- data: view.data,
7047
- marked: view.marked,
7048
- hidden: [...view.unreadable, ...view.unverified],
7049
- refs: schemaSet,
7050
- locate: merged.locate
7051
- });
7052
- }
7053
- errors = [
7054
- ...settleFindings(
7055
- await validator.validate(
7056
- view.data,
7057
- schemaSet,
7058
- extracted.lineFor,
7059
- extracted.colFor,
7060
- merged.locate
7061
- ),
7062
- view,
7063
- extracted,
7064
- merged.locate
7065
- ),
7066
- ...encryptionFindings(view, extracted, merged.locate),
7067
- // Over the decrypted view, the object validation evaluated: a mark
7068
- // counts where Ajv takes a branch, and a branch may read a value.
7069
- ...await locationFindings(label, own, view.data, schemaSet, members, merged, extracted.lineFor)
7070
- ];
7071
- } catch (err) {
7072
- if (!(err instanceof DocmetaError) || resolved.source !== "document") {
7073
- throw err;
7074
- }
7075
- results.push(
7076
- parseErrorResult(label, extractor.name, err.message, "schema")
7077
- );
7078
- return;
7079
- }
7080
- for (const { key, file, collection } of merged.collisions) {
7081
- const line = extracted.lineFor(key);
7082
- errors.push({
7083
- schema: EXTERNAL_OWNED_SCHEMA,
7084
- keyword: EXTERNAL_KEYWORD,
7085
- subject: key,
7086
- instancePath: externalMetadataPointer(key),
7087
- message: `"${key}" is owned by manifest ${file} (collection ${collection}); remove it from the document`,
7088
- ...line != null ? { line } : {}
7089
- });
7090
- }
7091
- results.push({
7092
- file: label,
7093
- format: extractor.name,
7094
- ok: !errors.some(isErrorSeverity),
7095
- schemas: schemaSet,
7096
- errors
7097
- });
7098
- };
7099
- if (usingStdin) {
7100
- const content = opts.stdinContent ?? "";
7101
- if (!forcedExtractor) {
7102
- throw new DocmetaError(
7103
- STDIN_REQUIRES_AS
7104
- );
7105
- }
7106
- await processOne(STDIN_LABEL, content, forcedExtractor.extensions[0] ?? "");
7107
- }
7108
- for (const file of files) {
7109
- const content = await readFile6(resolve7(base, file), "utf8");
7110
- await processOne(file, content, extname3(file));
7111
- }
7112
- if (unverified > 0) opts.onNotice?.(unverifiedWarning(unverified));
7113
- if (externalMetadata) {
7114
- const byLabel = new Map(results.map((r) => [r.file, r]));
7115
- const matched = /* @__PURE__ */ new Map();
7116
- for (const [field, byValue] of joinHits) {
7117
- matched.set(field, new Set(byValue.keys()));
7118
- for (const hits of byValue.values()) {
7119
- if (hits.length < 2) continue;
7120
- for (const hit of hits) {
7121
- const others = hits.filter((h) => h.label !== hit.label).map((h) => h.label).join(", ");
7122
- const result = byLabel.get(hit.label);
7123
- if (!result) continue;
7124
- result.errors.push({
7125
- schema: EXTERNAL_DUPLICATE_SCHEMA,
7126
- keyword: EXTERNAL_KEYWORD,
7127
- subject: hit.shown,
7128
- instancePath: externalMetadataPointer(field),
7129
- message: `${hits.length} documents carry ${field} "${hit.shown}"; ${hit.file} cannot tell them apart (${others})`,
7130
- ...hit.line != null ? { line: hit.line } : {}
7131
- });
7132
- result.ok = false;
7133
- }
7134
- }
7135
- }
7136
- if (!scopedByFlags) {
7137
- const covered = scopedToCollections ? collections.map((c) => c.name) : void 0;
7138
- const orphans = orphanJoins(externalMetadata, matched, covered);
7139
- if (orphans.length > 0) throw orphanError(orphans);
7140
- }
7141
- }
7142
- if (configuredChecks.length > 0 && opts.checks !== false) {
7143
- if (scoped) {
7144
- opts.onNotice?.(
7145
- scopedToCollections ? `corpus checks skipped: run is scoped to collections ${collections.map((c) => c.name).join(", ")}` : "corpus checks skipped: run is scoped"
7146
- );
7147
- } else {
7148
- const findings = await runChecks(configuredChecks, checkEntries, {
7149
- // Every declared collection is a view (0041 rule 7), holding the
7150
- // members this run loaded — decided by the same path arithmetic
7151
- // `membersFor` uses, so a check and a `manni meta query` over the same
7152
- // corpus see the same collections.
7153
- collections: declaredCollections,
7154
- ...configDir !== void 0 ? { configDir } : {},
7155
- base,
7156
- // A check that names the `derived` table (0040) gets the same view
7157
- // `query` builds, from the run's one derivation — which already
7158
- // covers every field the checks can read, so the list handed back
7159
- // here is not needed. Built only when asked for, because building
7160
- // it spawns git.
7161
- derive: async () => (await derivedOnce()).records,
7162
- // Its columns include the configured commands' keys (0042), which a
7163
- // check's context cannot read from a config since 0041.
7164
- ...deriveCommands !== void 0 ? { commands: deriveCommands } : {}
7165
- });
7166
- const byFile = new Map(results.map((r) => [r.file, r]));
7167
- for (const [file, errs] of findings) {
7168
- const result = byFile.get(file);
7169
- if (!result && skippedDefaultOnly.has(file)) continue;
7170
- if (!result) {
7171
- throw new DocmetaError(
7172
- `check findings for "${file}" have no validation result to attach to.`
7173
- );
7174
- }
7175
- result.errors.push(...errs);
7176
- result.ok = result.ok && !errs.some(isErrorSeverity);
7177
- }
7178
- }
7179
- }
7180
- if (deriveWillRun && deriveInputs.length > 0) {
7181
- const derived = await derivedOnce();
7182
- const byFile = new Map(results.map((r) => [r.file, r]));
7183
- for (const input of deriveInputs) {
7184
- const record = derived.records.get(input.label);
7185
- const result = byFile.get(input.label);
7186
- if (!record || !result) continue;
7187
- const managed = managedViews.get(input.label);
7188
- const managedHere = !deriveCovers(deriveConfig, membersFor(input.label)) ? [] : defaultedFields ? pageManaged.get(input.label) ?? [] : configuredFields;
7189
- const fields = managedHere.filter(
7190
- (field) => !(managed?.hidden.some((p) => isAtOrUnder(p, pointerOf(field))) ?? false)
7191
- );
7192
- let marked = [];
7193
- if (managed !== void 0) {
7194
- const stamped = { ...managed.data };
7195
- for (const field of fields) {
7196
- const d = record.fields[field];
7197
- if (d != null) stamped[field] = d.value;
7198
- }
7199
- marked = [...managed.marked, ...await validator.markedPointers(stamped, managed.refs)];
7200
- }
7201
- const asserted = (field) => managed !== void 0 ? managed.data[field] : input.extracted.data[field];
7202
- const findings = fields.flatMap((field) => {
7203
- if (field === PROVENANCE_FIELD) {
7204
- const derivation = derived.provenance?.get(input.label);
7205
- if (derivation === void 0) return [];
7206
- return provenanceFindings(
7207
- compareProvenance(provenanceEntries(asserted(field)), derivation),
7208
- derivation.page.bodyLine
7209
- );
7210
- }
7211
- const compared = compareDerived(field, asserted(field), record.fields[field]);
7212
- const at2 = pointerOf(field);
7213
- return staleFindings(
7214
- [marked.some((p) => isAtOrUnder(p, at2)) ? redactedDerived(compared) : compared],
7215
- input.extracted.lineFor,
7216
- managed?.locate
7217
- );
7218
- });
7219
- if (findings.length === 0) continue;
7220
- result.errors.push(...findings);
7221
- result.ok = false;
7222
- }
7223
- }
7224
- const frame = { cwd, base: configDir ?? cwd, runBase: base };
7225
- const { results: reported3, baseline } = await settleBaseline(
7226
- results,
7227
- resolveBaselineRequest(opts, config?.baseline, configDir, cwd, {
7228
- current: DEFAULT_BASELINE_PATH,
7229
- legacy: LEGACY_BASELINE_PATH
7230
- }),
7231
- frame
7232
- );
7233
- const failed = reported3.filter((r) => !r.ok).length;
7234
- const count2 = (keep) => reported3.reduce((n, r) => n + r.errors.filter(keep).length, 0);
7235
- const warnings = count2((e) => e.severity === "warning");
7236
- const notices = count2((e) => e.severity === "notice");
7237
- const summary = {
7238
- files: reported3.length,
7239
- passed: reported3.length - failed,
7240
- failed,
7241
- errors: count2(isErrorSeverity),
7242
- // Omitted at zero, like `gitignoreSkipped` below: only the location
7243
- // findings (proposal 0047) are warnings, so a run with no
7244
- // `x-manni-location` marks keeps the summary as it was.
7245
- ...warnings > 0 ? { warnings } : {},
7246
- ...notices > 0 ? { notices } : {},
7247
- // Omitted when nothing was skipped: there is nothing to audit, and the
7248
- // JSON summary stays as it was for every run in a clean repo.
7249
- ...gitignoreSkipped > 0 ? { gitignoreSkipped } : {},
7250
- ...baseline ? { baseline } : {}
7251
- };
7252
- return { results: reported3, summary, frame };
7253
- }
7254
-
7255
4300
  export {
7256
4301
  collect,
7257
4302
  splitList,
@@ -7279,6 +4324,7 @@ export {
7279
4324
  META_CONTEXT,
7280
4325
  EncryptionRefusal,
7281
4326
  unreadableMessage,
4327
+ unverifiedWarning,
7282
4328
  lazyKey,
7283
4329
  pointerOf,
7284
4330
  topLevelKeyOf,
@@ -7287,14 +4333,17 @@ export {
7287
4333
  withValueAt,
7288
4334
  encryptionView,
7289
4335
  settleFindings,
4336
+ encryptionFindings,
7290
4337
  redactForModel,
7291
4338
  redactedDerived,
7292
4339
  EXTERNAL_OWNED_SCHEMA,
7293
4340
  EXTERNAL_DUPLICATE_SCHEMA,
7294
4341
  EXTERNAL_KEYWORD,
7295
4342
  PATH_JOIN,
4343
+ reportedPath,
7296
4344
  externalMetadataJoin,
7297
4345
  loadExternalMetadata,
4346
+ outsideRefusal,
7298
4347
  mergeExternalMetadata,
7299
4348
  mergeWithMarks,
7300
4349
  externalMetadataPointer,
@@ -7305,6 +4354,7 @@ export {
7305
4354
  LOCATION_KEYWORD,
7306
4355
  LOCATION_EXTERNAL_SCHEMA,
7307
4356
  LOCATION_PAGE_SCHEMA,
4357
+ LOCATION_FINDING_KEYWORD,
7308
4358
  GRAPH_OUTPUT_KEYWORD,
7309
4359
  compileWithFormats,
7310
4360
  stringFormatNames,
@@ -7319,6 +4369,7 @@ export {
7319
4369
  familyMarks,
7320
4370
  SYSTEM_COLUMNS,
7321
4371
  RESERVED,
4372
+ quoteIdent,
7322
4373
  bindValue,
7323
4374
  corpusDataColumns,
7324
4375
  createDocsTable,
@@ -7366,57 +4417,19 @@ export {
7366
4417
  toFileLines,
7367
4418
  toBodyLines,
7368
4419
  splitPageArgument,
4420
+ ZERO_SHA,
4421
+ DEFAULT_MACHINES,
7369
4422
  provenanceEntries,
7370
4423
  readProvenancePage,
4424
+ parseLinePorcelain,
4425
+ machineIdentity,
4426
+ deriveProvenance,
7371
4427
  compareProvenance,
7372
4428
  planProvenanceWrite,
7373
4429
  rangeResults,
7374
4430
  provenanceFindings,
7375
4431
  renderProvenanceValue,
7376
4432
  parseProvenanceTarget,
7377
- commandsOf,
7378
- machinesOf,
7379
- REVIEW_CACHE_DIR,
7380
- REVIEW_CACHE_VERSION,
7381
- ReviewCache,
7382
- cachedClient,
7383
- BinMissing,
7384
- run,
7385
- PATH_PLACEHOLDER,
7386
- isPerFile,
7387
- argvFor,
7388
- valueOf,
7389
- deriveFromCommands,
7390
- parseOriginUrl,
7391
- identityFromOrigin,
7392
- GitHubClient,
7393
- GitLabClient,
7394
- createReviewClient,
7395
- deriveFromReviews,
7396
- mapConcurrent,
7397
- provenanceFenced,
7398
- FIELD_SOURCES,
7399
- sourcesFor,
7400
- consultedSources,
7401
- assertSourcesAvailable,
7402
- deriveMetadata,
7403
- DERIVED_VIEW,
7404
- DERIVED_ROWS,
7405
- RESOLVED_VIEW,
7406
- derivedColumns,
7407
- mentionsDerived,
7408
- fieldsForSql,
7409
- createDerivedView,
7410
- resolvedColumns,
7411
- mentionsResolved,
7412
- createResolvedView,
7413
- deriveForTable,
7414
- rowsToFindings,
7415
- provenanceManifests,
7416
- provenancePlace,
7417
- readerManifests,
7418
- readerPlace,
7419
- stampManifests,
7420
- runValidate
4433
+ attributeRange
7421
4434
  };
7422
- //# sourceMappingURL=chunk-KBG463UK.js.map
4435
+ //# sourceMappingURL=chunk-GV56WGL2.js.map