@hawkeyexl/manni 4.3.1 → 4.4.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 (224) hide show
  1. package/README.md +9 -1
  2. package/dist/batch-DFRCLY77.js +36 -0
  3. package/dist/build-Q5C6SX5J.js +31 -0
  4. package/dist/calibrate-XQILKXS3.js +31 -0
  5. package/dist/capture-JQMOC3BW.js +32 -0
  6. package/dist/check-HYNGXYJV.js +18 -0
  7. package/dist/check-HYNGXYJV.js.map +1 -0
  8. package/dist/check-NFZQLJKN.js +27 -0
  9. package/dist/check-NFZQLJKN.js.map +1 -0
  10. package/dist/check-U54XV6BP.js +40 -0
  11. package/dist/check-U54XV6BP.js.map +1 -0
  12. package/dist/chunk-252LNOEG.js +622 -0
  13. package/dist/chunk-252LNOEG.js.map +1 -0
  14. package/dist/chunk-2H7UOFLK.js +11 -0
  15. package/dist/chunk-2H7UOFLK.js.map +1 -0
  16. package/dist/chunk-2U3ORT6D.js +230 -0
  17. package/dist/chunk-2U3ORT6D.js.map +1 -0
  18. package/dist/chunk-3AFUD75K.js +23 -0
  19. package/dist/chunk-3AFUD75K.js.map +1 -0
  20. package/dist/{chunk-3R23C5DT.js → chunk-4TSWU2SU.js} +2 -14
  21. package/dist/chunk-4TSWU2SU.js.map +1 -0
  22. package/dist/chunk-5KHIFVD2.js +1192 -0
  23. package/dist/chunk-5KHIFVD2.js.map +1 -0
  24. package/dist/chunk-5YXBLLAC.js +2165 -0
  25. package/dist/chunk-5YXBLLAC.js.map +1 -0
  26. package/dist/chunk-644JVKJ4.js +3872 -0
  27. package/dist/chunk-644JVKJ4.js.map +1 -0
  28. package/dist/chunk-6OYEI2VV.js +88 -0
  29. package/dist/chunk-6OYEI2VV.js.map +1 -0
  30. package/dist/chunk-77AINBYM.js +19 -0
  31. package/dist/chunk-77AINBYM.js.map +1 -0
  32. package/dist/chunk-77HX34KD.js +1287 -0
  33. package/dist/chunk-77HX34KD.js.map +1 -0
  34. package/dist/{chunk-CFDUZRM2.js → chunk-7QMCHA4N.js} +32 -240
  35. package/dist/chunk-7QMCHA4N.js.map +1 -0
  36. package/dist/chunk-BUXDLRDT.js +455 -0
  37. package/dist/chunk-BUXDLRDT.js.map +1 -0
  38. package/dist/chunk-C6OH6CNF.js +388 -0
  39. package/dist/chunk-C6OH6CNF.js.map +1 -0
  40. package/dist/chunk-CK7SKVHR.js +1042 -0
  41. package/dist/chunk-CK7SKVHR.js.map +1 -0
  42. package/dist/chunk-CYWHFSRT.js +6737 -0
  43. package/dist/chunk-CYWHFSRT.js.map +1 -0
  44. package/dist/chunk-ERMSO34F.js +17 -0
  45. package/dist/chunk-ERMSO34F.js.map +1 -0
  46. package/dist/{chunk-IOYXPBCL.js → chunk-G3T47CFV.js} +1 -7
  47. package/dist/{chunk-IOYXPBCL.js.map → chunk-G3T47CFV.js.map} +1 -1
  48. package/dist/chunk-G7NJSREW.js +979 -0
  49. package/dist/chunk-G7NJSREW.js.map +1 -0
  50. package/dist/chunk-GLP5PSXB.js +72 -0
  51. package/dist/chunk-GLP5PSXB.js.map +1 -0
  52. package/dist/chunk-H2DLH2JI.js +198 -0
  53. package/dist/chunk-H2DLH2JI.js.map +1 -0
  54. package/dist/chunk-H6FZ66XR.js +634 -0
  55. package/dist/chunk-H6FZ66XR.js.map +1 -0
  56. package/dist/{chunk-KJR7OVDX.js → chunk-IEQHSCZG.js} +56 -137
  57. package/dist/chunk-IEQHSCZG.js.map +1 -0
  58. package/dist/chunk-IKEWBMCN.js +98 -0
  59. package/dist/chunk-IKEWBMCN.js.map +1 -0
  60. package/dist/chunk-JOHKIAVH.js +5440 -0
  61. package/dist/chunk-JOHKIAVH.js.map +1 -0
  62. package/dist/chunk-JVTNMVYU.js +10921 -0
  63. package/dist/chunk-JVTNMVYU.js.map +1 -0
  64. package/dist/chunk-K3D6FPV6.js +164 -0
  65. package/dist/chunk-K3D6FPV6.js.map +1 -0
  66. package/dist/{chunk-UBMMXS2Y.js → chunk-KCGV52AM.js} +69 -48
  67. package/dist/{chunk-UBMMXS2Y.js.map → chunk-KCGV52AM.js.map} +1 -1
  68. package/dist/chunk-LAKAWRV5.js +1651 -0
  69. package/dist/chunk-LAKAWRV5.js.map +1 -0
  70. package/dist/chunk-LJ2RDH3F.js +49 -0
  71. package/dist/chunk-LJ2RDH3F.js.map +1 -0
  72. package/dist/chunk-M2JCK5F6.js +805 -0
  73. package/dist/chunk-M2JCK5F6.js.map +1 -0
  74. package/dist/chunk-MNH7QO4Z.js +15 -0
  75. package/dist/chunk-MNH7QO4Z.js.map +1 -0
  76. package/dist/{chunk-HAPTXWU2.js → chunk-MYC3O5EJ.js} +4 -4
  77. package/dist/chunk-MYMBCTDE.js +387 -0
  78. package/dist/chunk-MYMBCTDE.js.map +1 -0
  79. package/dist/chunk-NCPWXHDO.js +683 -0
  80. package/dist/chunk-NCPWXHDO.js.map +1 -0
  81. package/dist/chunk-NWZ2SLYL.js +2957 -0
  82. package/dist/chunk-NWZ2SLYL.js.map +1 -0
  83. package/dist/chunk-OBFENF3U.js +934 -0
  84. package/dist/chunk-OBFENF3U.js.map +1 -0
  85. package/dist/chunk-OWBRDNRZ.js +16 -0
  86. package/dist/chunk-OWBRDNRZ.js.map +1 -0
  87. package/dist/{chunk-KTSJ2ZHA.js → chunk-PAU352SV.js} +5 -3
  88. package/dist/{chunk-KTSJ2ZHA.js.map → chunk-PAU352SV.js.map} +1 -1
  89. package/dist/chunk-PCE7QHXJ.js +68 -0
  90. package/dist/chunk-PCE7QHXJ.js.map +1 -0
  91. package/dist/chunk-PCHZY2I7.js +299 -0
  92. package/dist/chunk-PCHZY2I7.js.map +1 -0
  93. package/dist/chunk-PHF3D7ER.js +1042 -0
  94. package/dist/chunk-PHF3D7ER.js.map +1 -0
  95. package/dist/chunk-Q35YIDBV.js +1202 -0
  96. package/dist/chunk-Q35YIDBV.js.map +1 -0
  97. package/dist/chunk-RLR4TKUZ.js +4398 -0
  98. package/dist/chunk-RLR4TKUZ.js.map +1 -0
  99. package/dist/chunk-RNVYGWIC.js +30 -0
  100. package/dist/chunk-RNVYGWIC.js.map +1 -0
  101. package/dist/chunk-SE4JN5XN.js +19 -0
  102. package/dist/chunk-SE4JN5XN.js.map +1 -0
  103. package/dist/chunk-TIUTIOFB.js +25 -0
  104. package/dist/chunk-TIUTIOFB.js.map +1 -0
  105. package/dist/chunk-TU2WUBVZ.js +2 -0
  106. package/dist/chunk-TU2WUBVZ.js.map +1 -0
  107. package/dist/chunk-TZOYZK2E.js +98 -0
  108. package/dist/chunk-TZOYZK2E.js.map +1 -0
  109. package/dist/chunk-UKZNLVJW.js +17 -0
  110. package/dist/chunk-UKZNLVJW.js.map +1 -0
  111. package/dist/chunk-UZC7LDAQ.js +178 -0
  112. package/dist/chunk-UZC7LDAQ.js.map +1 -0
  113. package/dist/chunk-V46OILVC.js +3090 -0
  114. package/dist/chunk-V46OILVC.js.map +1 -0
  115. package/dist/chunk-WQE54PNH.js +1975 -0
  116. package/dist/chunk-WQE54PNH.js.map +1 -0
  117. package/dist/chunk-X3BPBDGQ.js +52 -0
  118. package/dist/chunk-X3BPBDGQ.js.map +1 -0
  119. package/dist/chunk-XSLV76YK.js +54 -0
  120. package/dist/chunk-XSLV76YK.js.map +1 -0
  121. package/dist/{chunk-5HNHEUD5.js → chunk-XYLUCIKQ.js} +3 -3
  122. package/dist/chunk-YEUYKR62.js +72 -0
  123. package/dist/chunk-YEUYKR62.js.map +1 -0
  124. package/dist/{chunk-G5CM2VGQ.js → chunk-YFQBFNQS.js} +8 -94
  125. package/dist/chunk-YFQBFNQS.js.map +1 -0
  126. package/dist/chunk-YIL4XLLJ.js +1661 -0
  127. package/dist/chunk-YIL4XLLJ.js.map +1 -0
  128. package/dist/chunk-YNHZHSSM.js +41 -0
  129. package/dist/chunk-YNHZHSSM.js.map +1 -0
  130. package/dist/chunk-YTCZRLTU.js +96 -0
  131. package/dist/chunk-YTCZRLTU.js.map +1 -0
  132. package/dist/chunk-YYBFZLAJ.js +374 -0
  133. package/dist/chunk-YYBFZLAJ.js.map +1 -0
  134. package/dist/chunk-ZXRAE55H.js +31 -0
  135. package/dist/chunk-ZXRAE55H.js.map +1 -0
  136. package/dist/cli-2QPR2OZG.js +232 -0
  137. package/dist/cli-2QPR2OZG.js.map +1 -0
  138. package/dist/cli-4IOPGZHR.js +1203 -0
  139. package/dist/cli-4IOPGZHR.js.map +1 -0
  140. package/dist/cli-5FL73WHA.js +346 -0
  141. package/dist/cli-5FL73WHA.js.map +1 -0
  142. package/dist/cli-C262QOWM.js +739 -0
  143. package/dist/cli-C262QOWM.js.map +1 -0
  144. package/dist/cli-LJLLIQB2.js +423 -0
  145. package/dist/cli-LJLLIQB2.js.map +1 -0
  146. package/dist/cli-OFPZ5MAS.js +626 -0
  147. package/dist/cli-OFPZ5MAS.js.map +1 -0
  148. package/dist/cli-SHSMSRQY.js +28 -0
  149. package/dist/cli-SHSMSRQY.js.map +1 -0
  150. package/dist/cli-SRHFUZHA.js +473 -0
  151. package/dist/cli-SRHFUZHA.js.map +1 -0
  152. package/dist/cli-URZQXL4K.js +552 -0
  153. package/dist/cli-URZQXL4K.js.map +1 -0
  154. package/dist/cli.d.ts +12 -2
  155. package/dist/cli.js +109 -7661
  156. package/dist/cli.js.map +1 -1
  157. package/dist/docmeta.js +15 -3
  158. package/dist/docmeta.js.map +1 -1
  159. package/dist/github-FE6KCNYJ.js +27 -0
  160. package/dist/github-FE6KCNYJ.js.map +1 -0
  161. package/dist/github-QXL3VMK2.js +32 -0
  162. package/dist/github-QXL3VMK2.js.map +1 -0
  163. package/dist/index.d.ts +65 -3
  164. package/dist/index.js +407 -337
  165. package/dist/index.js.map +1 -1
  166. package/dist/json-7WV7KL6H.js +17 -0
  167. package/dist/json-7WV7KL6H.js.map +1 -0
  168. package/dist/json-SGZ6ZRFD.js +35 -0
  169. package/dist/json-SGZ6ZRFD.js.map +1 -0
  170. package/dist/lint-DL4LJRAY.js +28 -0
  171. package/dist/lint-DL4LJRAY.js.map +1 -0
  172. package/dist/list-6BAXTZUM.js +29 -0
  173. package/dist/list-6BAXTZUM.js.map +1 -0
  174. package/dist/list-7BKNYPSB.js +31 -0
  175. package/dist/list-7BKNYPSB.js.map +1 -0
  176. package/dist/page-3U2GCFNE.js +41 -0
  177. package/dist/page-3U2GCFNE.js.map +1 -0
  178. package/dist/picker-3XF36MJF.js +14 -0
  179. package/dist/picker-3XF36MJF.js.map +1 -0
  180. package/dist/pretty-2H57QVTY.js +43 -0
  181. package/dist/pretty-2H57QVTY.js.map +1 -0
  182. package/dist/pretty-N6DO4OOL.js +37 -0
  183. package/dist/pretty-N6DO4OOL.js.map +1 -0
  184. package/dist/reporters-M6LJ2IYU.js +21 -0
  185. package/dist/reporters-M6LJ2IYU.js.map +1 -0
  186. package/dist/reporters-N6GHXSS7.js +42 -0
  187. package/dist/reporters-N6GHXSS7.js.map +1 -0
  188. package/dist/reporters-UCNEV7PH.js +71 -0
  189. package/dist/reporters-UCNEV7PH.js.map +1 -0
  190. package/dist/run-XZDAREJE.js +33 -0
  191. package/dist/run-XZDAREJE.js.map +1 -0
  192. package/dist/term-5ICGKPC3.js +1592 -0
  193. package/dist/term-5ICGKPC3.js.map +1 -0
  194. package/dist/types-Y6TXHM7M.js +10 -0
  195. package/dist/types-Y6TXHM7M.js.map +1 -0
  196. package/dist/validate-GQRM77YN.js +16 -0
  197. package/dist/validate-GQRM77YN.js.map +1 -0
  198. package/package.json +1 -1
  199. package/plugin/manni/.claude-plugin/plugin.json +9 -0
  200. package/plugin/manni/hooks/hooks.json +52 -0
  201. package/plugin/manni/skills/check/SKILL.md +20 -0
  202. package/plugin/manni/skills/evals/SKILL.md +27 -0
  203. package/plugin/manni/skills/fix/SKILL.md +56 -0
  204. package/plugin/manni/skills/setup/SKILL.md +41 -0
  205. package/dist/batch-A5HZKYIW.js +0 -18
  206. package/dist/calibrate-S5K6QHUY.js +0 -13
  207. package/dist/capture-OZEZDABQ.js +0 -15
  208. package/dist/chunk-3R23C5DT.js.map +0 -1
  209. package/dist/chunk-CFDUZRM2.js.map +0 -1
  210. package/dist/chunk-G5CM2VGQ.js.map +0 -1
  211. package/dist/chunk-KJR7OVDX.js.map +0 -1
  212. package/dist/chunk-V4KUVE47.js +0 -27170
  213. package/dist/chunk-V4KUVE47.js.map +0 -1
  214. package/dist/chunk-ZQBTK3DO.js +0 -26518
  215. package/dist/chunk-ZQBTK3DO.js.map +0 -1
  216. package/dist/picker-6HRMETE7.js +0 -12
  217. package/plugin/tracevals/.claude-plugin/plugin.json +0 -9
  218. package/plugin/tracevals/hooks/hooks.json +0 -17
  219. /package/dist/{batch-A5HZKYIW.js.map → batch-DFRCLY77.js.map} +0 -0
  220. /package/dist/{calibrate-S5K6QHUY.js.map → build-Q5C6SX5J.js.map} +0 -0
  221. /package/dist/{capture-OZEZDABQ.js.map → calibrate-XQILKXS3.js.map} +0 -0
  222. /package/dist/{picker-6HRMETE7.js.map → capture-JQMOC3BW.js.map} +0 -0
  223. /package/dist/{chunk-HAPTXWU2.js.map → chunk-MYC3O5EJ.js.map} +0 -0
  224. /package/dist/{chunk-5HNHEUD5.js.map → chunk-XYLUCIKQ.js.map} +0 -0
@@ -0,0 +1,1651 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ blockMatches,
4
+ claimEnd,
5
+ claimLineNow,
6
+ claimLinesNow,
7
+ findingsFor,
8
+ misplacedMarkerAt,
9
+ misplacedMarkers,
10
+ misplacedMessageFor,
11
+ movedUnit,
12
+ shortCommit,
13
+ toValidationResult,
14
+ unitText
15
+ } from "./chunk-252LNOEG.js";
16
+ import {
17
+ SEVERITIES
18
+ } from "./chunk-77AINBYM.js";
19
+ import {
20
+ ANY_FENCE,
21
+ CITE_RULES,
22
+ CiteError,
23
+ SRC_PATTERN,
24
+ anchoredLines,
25
+ fenceSpanAt,
26
+ formatSrc,
27
+ isCiteRule,
28
+ isMarkerLine,
29
+ parseSrc,
30
+ pickExtractor,
31
+ readPage,
32
+ resolveSeverity,
33
+ ruleId,
34
+ sliceLines,
35
+ sourceRange,
36
+ spellSource
37
+ } from "./chunk-G7NJSREW.js";
38
+ import {
39
+ STDIN_REQUIRES_AS
40
+ } from "./chunk-V46OILVC.js";
41
+ import {
42
+ MAX_RANGE_LINES,
43
+ MOVE_BUDGET_BYTES,
44
+ PATH_JOIN,
45
+ configMarks,
46
+ externalMetadataJoin,
47
+ findWindows,
48
+ hashLines,
49
+ lineSpec,
50
+ loadExternalMetadata,
51
+ memberOf,
52
+ mergeWithMarks,
53
+ orphanEntries,
54
+ orphanError,
55
+ orphanJoins,
56
+ parseLines,
57
+ resolveEncryptionKey,
58
+ spellLines,
59
+ splitLines,
60
+ toBodyLines
61
+ } from "./chunk-RLR4TKUZ.js";
62
+ import {
63
+ STDIN_LABEL,
64
+ STDIN_TOKEN,
65
+ assertNonEmpty,
66
+ classifyRef,
67
+ extractorByName,
68
+ gitIgnored,
69
+ gitignoreOptions,
70
+ isErrorSeverity,
71
+ resolveBaselineRequest,
72
+ resolveTargetSet,
73
+ settleBaseline,
74
+ supportedExtensions
75
+ } from "./chunk-JVTNMVYU.js";
76
+ import {
77
+ decryptValue,
78
+ encryptValue,
79
+ findConfigFile,
80
+ findGitRoot,
81
+ hasPagePlaceholder,
82
+ ownsKey,
83
+ pageManifestPath,
84
+ readConfigFile,
85
+ selectCollections,
86
+ warn
87
+ } from "./chunk-Q35YIDBV.js";
88
+
89
+ // src/cite/commands/check.ts
90
+ import { existsSync as existsSync2 } from "node:fs";
91
+ import { readFile as readFile2 } from "node:fs/promises";
92
+ import { resolve as resolve3 } from "node:path";
93
+
94
+ // src/cite/core/sources.ts
95
+ import fg from "fast-glob";
96
+ import { lstat, readFile, realpath } from "node:fs/promises";
97
+ import { join, sep } from "node:path";
98
+ var CONTEXT = "cite-src";
99
+ function encryptSourcePath(path, key) {
100
+ return encryptValue(path, key, CONTEXT);
101
+ }
102
+ function decryptSourcePath(token, key) {
103
+ const opened = decryptValue(token, key, CONTEXT);
104
+ if (!opened.ok || typeof opened.value !== "string") return void 0;
105
+ const path = opened.value;
106
+ if (path.includes(":") || path.startsWith("~") || !SRC_PATTERN.test(path)) return void 0;
107
+ return path;
108
+ }
109
+ function isUnder(real, realRoot) {
110
+ return real === realRoot || real.startsWith(realRoot.endsWith(sep) ? realRoot : realRoot + sep);
111
+ }
112
+ async function admit(root, realRoot, rel) {
113
+ const abs = join(root, rel);
114
+ try {
115
+ const info = await lstat(abs);
116
+ if (!info.isFile()) return void 0;
117
+ const real = await realpath(abs);
118
+ return isUnder(real, realRoot) ? rel : void 0;
119
+ } catch {
120
+ return void 0;
121
+ }
122
+ }
123
+ async function candidates(root, opts) {
124
+ const client = opts?.gitClient;
125
+ if (client !== void 0 && await client.available()) {
126
+ return client.lsFiles();
127
+ }
128
+ return fg("**/*", {
129
+ cwd: root,
130
+ onlyFiles: true,
131
+ dot: true,
132
+ followSymbolicLinks: false,
133
+ ignore: ["**/.git/**", "**/node_modules/**"]
134
+ });
135
+ }
136
+ async function buildSourceIndex(root, opts) {
137
+ const realRoot = await realpath(root);
138
+ const admitted = await Promise.all(
139
+ (await candidates(root, opts)).map((rel) => admit(root, realRoot, rel))
140
+ );
141
+ const files = Object.freeze(
142
+ [...new Set(admitted.filter((rel) => rel !== void 0))].sort()
143
+ );
144
+ const byPath = new Set(files);
145
+ return {
146
+ files: () => files,
147
+ has: (path) => byPath.has(path)
148
+ };
149
+ }
150
+ function isEnoent(error) {
151
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
152
+ }
153
+ async function sourceIndexFor(root, client) {
154
+ try {
155
+ return await buildSourceIndex(root, { gitClient: client });
156
+ } catch (error) {
157
+ if (isEnoent(error)) throw new CiteError(`Root directory not found: ${root}.`);
158
+ throw error;
159
+ }
160
+ }
161
+ function resolveSourcePath(range, key) {
162
+ if (!range.encrypted) return { path: range.path };
163
+ if (key === void 0) return { reason: "no-key" };
164
+ const path = decryptSourcePath(range.path, key);
165
+ return path === void 0 ? { reason: "undecryptable" } : { path };
166
+ }
167
+ function createSourceCache() {
168
+ return /* @__PURE__ */ new Map();
169
+ }
170
+ async function readSource(root, index, range, key, cache) {
171
+ const resolved = resolveSourcePath(range, key);
172
+ if ("reason" in resolved) return { kind: "missing", reason: resolved.reason };
173
+ const resolvedPath = resolved.path;
174
+ if (!index.has(resolvedPath)) return { kind: "missing", reason: "untracked" };
175
+ if (cache === void 0) {
176
+ const text = await readTracked(root, resolvedPath);
177
+ return text === void 0 ? { kind: "missing", reason: "unreadable" } : { kind: "ok", resolvedPath, text };
178
+ }
179
+ const slot = JSON.stringify([root, resolvedPath]);
180
+ let read = cache.get(slot);
181
+ if (read === void 0) {
182
+ read = readTracked(root, resolvedPath).then(
183
+ (text) => text === void 0 ? void 0 : { text, lines: splitLines(text) }
184
+ );
185
+ cache.set(slot, read);
186
+ }
187
+ const file = await read;
188
+ return file === void 0 ? { kind: "missing", reason: "unreadable" } : { kind: "ok", resolvedPath, text: file.text, lines: file.lines };
189
+ }
190
+ async function readTracked(root, path) {
191
+ try {
192
+ return await readFile(join(root, path), "utf8");
193
+ } catch {
194
+ return void 0;
195
+ }
196
+ }
197
+
198
+ // src/cite/core/classify.ts
199
+ async function historyOf(git, commit, path, range, pin, key, budget, counter) {
200
+ const shown = await git.showFile(commit, path);
201
+ if ("missing" in shown) {
202
+ return shown.missing === "commit" ? { kind: "unavailable" } : { kind: "never-true" };
203
+ }
204
+ const then = splitLines(shown.text);
205
+ let joined;
206
+ try {
207
+ joined = sliceLines(then, range);
208
+ } catch {
209
+ joined = void 0;
210
+ }
211
+ if (joined !== void 0 && hashLines(joined, key) === pin) {
212
+ return { kind: "original", lines: joined.split("\n") };
213
+ }
214
+ if (range.start === void 0) return { kind: "never-true" };
215
+ const length = (range.end ?? range.start) - range.start + 1;
216
+ const search = { around: range.start };
217
+ if (budget !== void 0) search.budget = budget;
218
+ if (counter !== void 0) search.counter = counter;
219
+ const found = findWindows(then, length, pin, key, search);
220
+ const [first] = found.starts;
221
+ if (first !== void 0) {
222
+ return { kind: "original", lines: then.slice(first - 1, first - 1 + length) };
223
+ }
224
+ return found.truncated ? { kind: "unknown" } : { kind: "never-true" };
225
+ }
226
+ async function followAcross(ask, counter) {
227
+ const hits = [];
228
+ const length = ask.original.length;
229
+ if (length < 1 || ask.git.changedSince === void 0) return { hits, truncated: false };
230
+ const candidates2 = (await ask.git.changedSince(ask.commit)).filter(
231
+ (path) => path !== ask.own && ask.index.has(path)
232
+ );
233
+ for (const path of candidates2) {
234
+ const text = await readTracked(ask.root, path);
235
+ if (text === void 0) continue;
236
+ const bytes = Buffer.byteLength(text, "utf8");
237
+ if (bytes > counter.left) return { hits, truncated: true };
238
+ counter.left -= bytes;
239
+ const found = findWindows(splitLines(text), length, ask.pin, ask.key, {
240
+ original: ask.original
241
+ });
242
+ for (const start of found.starts) hits.push({ path, start });
243
+ }
244
+ return { hits, truncated: false };
245
+ }
246
+ function spanOf(lines, original) {
247
+ const first = original[0];
248
+ const last = original[original.length - 1];
249
+ if (first === void 0 || last === void 0) return void 0;
250
+ const at = onlyLine(lines, first);
251
+ if (at === void 0) return void 0;
252
+ const to = original.length === 1 ? at : onlyLine(lines, last);
253
+ if (to === void 0 || to < at || to - at + 1 > MAX_RANGE_LINES) return void 0;
254
+ return { start: at, end: to };
255
+ }
256
+ function onlyLine(lines, text) {
257
+ let at;
258
+ for (let i = 0; i < lines.length; i++) {
259
+ if (lines[i] !== text) continue;
260
+ if (at !== void 0) return void 0;
261
+ at = i + 1;
262
+ }
263
+ return at;
264
+ }
265
+ async function classifyCitation(entry, opts) {
266
+ const { citation } = entry;
267
+ const range = sourceRange(citation.source);
268
+ const commit = citation.source["commit-sha"];
269
+ const result = { src: spellSource(citation.source), status: "missing" };
270
+ if (commit !== void 0) result.commitSha = commit;
271
+ const resolved = resolveSourcePath(range, opts.key);
272
+ if ("reason" in resolved) {
273
+ result.missingReason = resolved.reason;
274
+ return result;
275
+ }
276
+ const path = resolved.path;
277
+ const key = range.encrypted ? opts.key : void 0;
278
+ const pin = citation.source.integrity;
279
+ const source = await readSource(opts.root, opts.index, range, opts.key, opts.cache);
280
+ const lines = source.kind === "ok" ? source.lines ?? splitLines(source.text) : void 0;
281
+ if (source.kind === "missing") result.missingReason = source.reason;
282
+ else result.resolvedPath = source.resolvedPath;
283
+ if (lines !== void 0) {
284
+ let here;
285
+ try {
286
+ here = hashLines(sliceLines(lines, range), key);
287
+ } catch {
288
+ here = void 0;
289
+ }
290
+ if (here === pin) {
291
+ result.status = "current";
292
+ return result;
293
+ }
294
+ }
295
+ const counter = { left: opts.budget ?? MOVE_BUDGET_BYTES };
296
+ let history = { kind: "none" };
297
+ if (commit !== void 0 && await opts.git.available()) {
298
+ history = await historyOf(opts.git, commit, path, range, pin, key, opts.budget, counter);
299
+ }
300
+ const original = history.kind === "original" ? history.lines : void 0;
301
+ const spellAt = (at, start, end) => {
302
+ const written = range.encrypted && opts.key !== void 0 ? encryptSourcePath(at, opts.key) : at;
303
+ return formatSrc({ path: written, encrypted: range.encrypted, ...start === void 0 ? {} : { start, end } });
304
+ };
305
+ if (lines !== void 0 && range.start !== void 0) {
306
+ const start = range.start;
307
+ const end = range.end ?? start;
308
+ const search = { around: start, counter };
309
+ if (original !== void 0) search.original = original;
310
+ const found = findWindows(lines, end - start + 1, pin, key, search);
311
+ if (found.truncated) result.truncatedSearch = true;
312
+ const spell = (at) => formatSrc({ ...range, start: at, end: at + (end - start) });
313
+ const [only] = found.starts;
314
+ if (found.starts.length === 1 && only !== void 0) {
315
+ result.status = "moved";
316
+ result.newSrc = spell(only);
317
+ result.newLines = String(lineSpec({ start: only, end: only + (end - start) }));
318
+ if (range.encrypted) result.resolvedNewPath = path;
319
+ return result;
320
+ }
321
+ if (found.starts.length > 1) {
322
+ result.status = "moved-ambiguous";
323
+ result.candidates = found.starts.map(spell);
324
+ return result;
325
+ }
326
+ }
327
+ if (commit !== void 0 && original !== void 0) {
328
+ const across = await followAcross(
329
+ { git: opts.git, root: opts.root, index: opts.index, commit, own: path, pin, key, original },
330
+ counter
331
+ );
332
+ if (across.truncated) {
333
+ result.truncatedSearch = true;
334
+ result.truncatedAcross = true;
335
+ }
336
+ const width = range.start === void 0 ? void 0 : original.length - 1;
337
+ const spell = (hit) => width === void 0 ? spellAt(hit.path) : spellAt(hit.path, hit.start, hit.start + width);
338
+ if (across.hits.length > 0) delete result.missingReason;
339
+ const [only] = across.hits;
340
+ if (across.hits.length === 1 && only !== void 0) {
341
+ result.status = "moved";
342
+ result.newSrc = spell(only);
343
+ if (width !== void 0) {
344
+ result.newLines = String(lineSpec({ start: only.start, end: only.start + width }));
345
+ }
346
+ if (range.encrypted) result.resolvedNewPath = only.path;
347
+ return result;
348
+ }
349
+ if (across.hits.length > 1) {
350
+ result.status = "moved-ambiguous";
351
+ result.candidates = across.hits.map(spell);
352
+ return result;
353
+ }
354
+ }
355
+ if (lines === void 0) return result;
356
+ if (history.kind === "never-true") {
357
+ result.status = "never-true";
358
+ return result;
359
+ }
360
+ result.status = "changed";
361
+ if (history.kind === "unavailable") result.historyAvailable = false;
362
+ if (history.kind === "unknown") result.truncatedSearch = true;
363
+ if (original !== void 0 && commit !== void 0) {
364
+ result.historyAvailable = true;
365
+ result.commitsSince = await opts.git.subjectsSince(commit, path);
366
+ result.diff = await opts.git.diffSince(commit, path);
367
+ const span = range.start === void 0 ? void 0 : spanOf(lines, original);
368
+ if (span !== void 0) result.newLines = String(lineSpec(span));
369
+ }
370
+ return result;
371
+ }
372
+
373
+ // src/cite/core/git.ts
374
+ import { execFile } from "node:child_process";
375
+ var MAX_BUFFER = 32 * 1024 * 1024;
376
+ var NO_COMMIT = /invalid object name|bad revision|unknown revision|bad object/i;
377
+ var NO_PATH = /does not exist in|exists on disk, but not in|path .* does not exist/i;
378
+ function run(root, args) {
379
+ return new Promise((resolve4) => {
380
+ execFile(
381
+ "git",
382
+ ["-c", "core.quotepath=false", ...args],
383
+ { cwd: root, windowsHide: true, maxBuffer: MAX_BUFFER, encoding: "utf8" },
384
+ (error, stdout, stderr) => {
385
+ if (error) {
386
+ const failure = { ok: false, stderr, message: error.message };
387
+ if (typeof error.code === "number") failure.code = error.code;
388
+ resolve4(failure);
389
+ } else resolve4({ ok: true, stdout });
390
+ }
391
+ );
392
+ });
393
+ }
394
+ function gitClient(root) {
395
+ const memo = /* @__PURE__ */ new Map();
396
+ const once = (key, compute) => {
397
+ const held = memo.get(key);
398
+ if (held !== void 0) return held;
399
+ const made = compute();
400
+ memo.set(key, made);
401
+ return made;
402
+ };
403
+ const available = () => once("available", async () => (await run(root, ["rev-parse", "--show-toplevel"])).ok);
404
+ const failed = (verb, outcome) => new CiteError(`git ${verb} failed: ${outcome.stderr.trim() || outcome.message}`);
405
+ const shallow = () => once("shallow", async () => {
406
+ const out = await run(root, ["rev-parse", "--is-shallow-repository"]);
407
+ return out.ok && out.stdout.trim() === "true";
408
+ });
409
+ const hasCommit = (commit) => once(`has\0${commit}`, async () => {
410
+ const out = await run(root, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${commit}^{commit}`]);
411
+ if (out.ok) return true;
412
+ if (out.code === 1) return false;
413
+ throw failed("rev-parse", out);
414
+ });
415
+ return {
416
+ available,
417
+ shallow,
418
+ // `merge-base --is-ancestor` exits 0 for yes and 1 for no. For a commit
419
+ // it does not have it exits 128, `Not a valid commit name`, which reads
420
+ // as git failing. So `hasCommit` answers first, and is required rather
421
+ // than an optimization: a commit git lacks is in no history it holds,
422
+ // which is a no, and exactly the pin a deleted branch leaves behind.
423
+ isAncestor: (commit, of) => once(`ancestor\0${commit}\0${of}`, async () => {
424
+ if (!await hasCommit(commit)) return false;
425
+ const out = await run(root, ["merge-base", "--is-ancestor", "--end-of-options", commit, of]);
426
+ if (out.ok) return true;
427
+ if (out.code === 1) return false;
428
+ throw failed("merge-base", out);
429
+ }),
430
+ head: () => once("head", async () => {
431
+ if (!await available()) return null;
432
+ const out = await run(root, ["rev-parse", "HEAD"]);
433
+ return out.ok ? out.stdout.trim() : null;
434
+ }),
435
+ lsFiles: () => once("ls-files", async () => {
436
+ if (!await available()) return [];
437
+ const out = await run(root, ["ls-files", "-z"]);
438
+ if (!out.ok) throw failed("ls-files", out);
439
+ return out.stdout.split("\0").filter((entry) => entry !== "");
440
+ }),
441
+ showFile: (commit, path) => once(`show\0${commit}\0${path}`, async () => {
442
+ const out = await run(root, ["show", "--end-of-options", `${commit}:./${path}`]);
443
+ if (out.ok) return { text: out.stdout };
444
+ if (NO_COMMIT.test(out.stderr)) return { missing: "commit" };
445
+ if (NO_PATH.test(out.stderr)) {
446
+ return await hasCommit(commit) ? { missing: "path" } : { missing: "commit" };
447
+ }
448
+ throw failed("show", out);
449
+ }),
450
+ subjectsSince: (commit, path) => once(`log\0${commit}\0${path}`, async () => {
451
+ const out = await run(root, ["log", "--format=%s", "--end-of-options", `${commit}..HEAD`, "--", path]);
452
+ if (!out.ok) throw failed("log", out);
453
+ return out.stdout.split(/\r?\n/).filter((line) => line !== "");
454
+ }),
455
+ // `HEAD` is written out: with no second revision git compares the commit
456
+ // to the working tree, and this list is committed changes only, so a move
457
+ // that is merely saved is never followed into a page (0055 stress test 8).
458
+ changedSince: (commit) => once(`changed\0${commit}`, async () => {
459
+ if (!await available()) return [];
460
+ const out = await run(root, ["diff", "--name-only", "--end-of-options", commit, "HEAD"]);
461
+ if (!out.ok) throw failed("diff", out);
462
+ return out.stdout.split(/\r?\n/).filter((line) => line !== "");
463
+ }),
464
+ diffSince: (commit, path) => once(`diff\0${commit}\0${path}`, async () => {
465
+ const out = await run(root, ["diff", "--end-of-options", commit, "--", path]);
466
+ if (!out.ok) throw failed("diff", out);
467
+ return out.stdout;
468
+ }),
469
+ pageCommits: (path, cap) => once(`commits\0${String(cap)}\0${path}`, async () => {
470
+ if (!await available()) return { commits: [], shallow: false, truncated: false };
471
+ const out = await run(root, [
472
+ "log",
473
+ `--format=%H${UNIT}%s`,
474
+ "--topo-order",
475
+ `-n${String(cap + 1)}`,
476
+ "HEAD",
477
+ "--",
478
+ path
479
+ ]);
480
+ if (!out.ok) throw failed("log", out);
481
+ const commits = [];
482
+ for (const line of out.stdout.split(/\r?\n/)) {
483
+ if (line === "") continue;
484
+ const at = line.indexOf(UNIT);
485
+ if (at === -1) continue;
486
+ commits.push({ sha: line.slice(0, at), subject: line.slice(at + 1) });
487
+ }
488
+ const truncated = commits.length > cap;
489
+ return { commits: commits.slice(0, cap), shallow: await shallow(), truncated };
490
+ })
491
+ };
492
+ }
493
+ var UNIT = "";
494
+ var GIT_UNAVAILABLE_HISTORY = "git is not available here, so citations are checked without history: no never-true, no reanchored claims, no diffs, no commit subjects.";
495
+ var PAGE_HISTORY_UNAVAILABLE = "the page history ends before the pin held; use fetch-depth: 0 to tell reanchored claims from changed ones";
496
+ var SHALLOW_RECOMMIT = "this clone is shallow, so --recommit re-recorded only commits that do not contain their lines. Fetch full history to check the rest.";
497
+ var GIT_UNAVAILABLE_COMMIT = "git is not available here, so the citation records no commit.";
498
+ function noGit() {
499
+ return {
500
+ available: () => Promise.resolve(false),
501
+ head: () => Promise.resolve(null),
502
+ lsFiles: () => Promise.resolve([]),
503
+ showFile: () => Promise.resolve({ missing: "commit" }),
504
+ subjectsSince: () => Promise.resolve([]),
505
+ diffSince: () => Promise.resolve(""),
506
+ changedSince: () => Promise.resolve([]),
507
+ pageCommits: () => Promise.resolve({ commits: [], shallow: false, truncated: false })
508
+ };
509
+ }
510
+
511
+ // src/cite/core/history.ts
512
+ var MAX_PAGE_COMMITS = 256;
513
+ function stripMarkers(lines, format) {
514
+ return lines.filter((line) => !isMarkerLine(line, format));
515
+ }
516
+ function claimWords(lines, format) {
517
+ return stripMarkers(lines, format).join(" ").replace(/\s+/g, " ").trim();
518
+ }
519
+ function sentencesOf(text) {
520
+ return text.split(/(?<=[.!?])\s+/).map((sentence) => sentence.replace(/\s+/g, " ").trim().toLowerCase()).filter((sentence) => sentence !== "");
521
+ }
522
+ function sharesSentence(a, b) {
523
+ const left = new Set(sentencesOf(a));
524
+ return sentencesOf(b).some((sentence) => left.has(sentence));
525
+ }
526
+ function wordSet(text) {
527
+ return new Set(
528
+ text.toLowerCase().split(/\s+/).filter((word) => word !== "")
529
+ );
530
+ }
531
+ function wordShare(was, now) {
532
+ const left = wordSet(was);
533
+ if (left.size === 0) return 1;
534
+ const right = wordSet(now);
535
+ let held = 0;
536
+ for (const word of left) if (right.has(word)) held++;
537
+ return held / left.size;
538
+ }
539
+ function holdsWords(outer, inner) {
540
+ if (inner === "") return true;
541
+ if (outer === inner) return true;
542
+ if (outer.startsWith(`${inner} `)) return true;
543
+ if (outer.endsWith(` ${inner}`)) return true;
544
+ return outer.includes(` ${inner} `);
545
+ }
546
+ function runsHolding(lines, format, bodyLine, inner) {
547
+ if (inner === "") return [];
548
+ let text = "";
549
+ const at = [];
550
+ for (let n = Math.max(bodyLine, 1); n <= lines.length; n++) {
551
+ const line = lines[n - 1] ?? "";
552
+ if (isMarkerLine(line, format)) continue;
553
+ const words = line.replace(/\s+/g, " ").trim();
554
+ if (words === "") continue;
555
+ if (text !== "") {
556
+ text += " ";
557
+ at.push(n);
558
+ }
559
+ for (let i = 0; i < words.length; i++) at.push(n);
560
+ text += words;
561
+ }
562
+ const out = [];
563
+ let from = 0;
564
+ for (; ; ) {
565
+ const found = text.indexOf(inner, from);
566
+ if (found === -1) break;
567
+ from = found + 1;
568
+ const before = found === 0 ? " " : text[found - 1];
569
+ const afterAt = found + inner.length;
570
+ const after = afterAt === text.length ? " " : text[afterAt];
571
+ if (before !== " " || after !== " ") continue;
572
+ const start = at[found];
573
+ const end = at[afterAt - 1];
574
+ if (start === void 0 || end === void 0) continue;
575
+ if (!out.some((span) => span.start === start && span.end === end)) out.push({ start, end });
576
+ }
577
+ return out.filter(
578
+ (span) => !out.some(
579
+ (other) => (other.start !== span.start || other.end !== span.end) && other.start >= span.start && other.end <= span.end
580
+ )
581
+ );
582
+ }
583
+ function bounds(line) {
584
+ return line === void 0 || line.trim() === "" || ANY_FENCE.test(line);
585
+ }
586
+ function pinOf(lines, span) {
587
+ if (span.start < 1 || span.end > lines.length || span.end < span.start) return void 0;
588
+ return hashLines(lines.slice(span.start - 1, span.end).join("\n"));
589
+ }
590
+ function readThen(input, text) {
591
+ const then = readPage(input.page.file, text, { format: input.page.format });
592
+ const lines = splitLines(then.content);
593
+ if (input.entry.citation.claim?.lines !== void 0) return { page: then, lines };
594
+ const id = input.entry.citation.id;
595
+ if (id === void 0) return void 0;
596
+ const marker = then.statements.find(
597
+ (statement) => statement.payload.kind === "ref" && statement.payload.ids.includes(id)
598
+ );
599
+ if (marker === void 0) return void 0;
600
+ return { page: then, lines, marker };
601
+ }
602
+ function anchorHeldAt(input, then) {
603
+ const pin = input.entry.citation.claim?.integrity;
604
+ if (pin === void 0) return { kind: "absent" };
605
+ const was = {
606
+ citation: input.entry.citation,
607
+ origin: input.entry.origin,
608
+ ...then.marker === void 0 ? {} : { marker: then.marker }
609
+ };
610
+ const split = then.marker === void 0 ? void 0 : misplacedMarkerAt(then.page, then.lines, then.marker.line, was);
611
+ const end = claimEnd(then.page, was, then.lines, split);
612
+ if (end === null || end.status !== "current" && end.status !== "moved") {
613
+ return { kind: "absent" };
614
+ }
615
+ for (const spec of [end.fileLines, end.newFileLines]) {
616
+ const span = spec === void 0 ? void 0 : parseLines(spec);
617
+ if (span !== void 0 && pinOf(then.lines, span) === pin) {
618
+ return { kind: "found", span, lines: then.lines.slice(span.start - 1, span.end) };
619
+ }
620
+ }
621
+ if (split !== void 0) {
622
+ const text = unitText(then.lines, split);
623
+ if (hashLines(text.join("\n")) === pin) {
624
+ return { kind: "found", span: movedUnit(split), lines: text };
625
+ }
626
+ }
627
+ return { kind: "absent" };
628
+ }
629
+ function windowHeldAt(input, then) {
630
+ const pin = input.entry.citation.claim?.integrity;
631
+ const marker = then.marker;
632
+ if (pin === void 0 || marker === void 0) return { kind: "absent" };
633
+ if (input.entry.citation.quote === true) return { kind: "absent" };
634
+ const lines = then.lines;
635
+ let stop = marker.line;
636
+ for (let n = marker.line + 1; n <= lines.length; n++) {
637
+ if (bounds(lines[n - 1])) break;
638
+ stop = n;
639
+ }
640
+ const starts = [marker.line];
641
+ for (let n = marker.line + 1; n <= stop; n++) {
642
+ const line = lines[n - 1] ?? "";
643
+ if (line.trim() === "" || isMarkerLine(line, then.page.format)) continue;
644
+ starts.push(n);
645
+ }
646
+ for (const start of starts) {
647
+ for (let end = start; end <= stop; end++) {
648
+ if (pinOf(lines, { start, end }) === pin) {
649
+ return { kind: "found", span: { start, end }, lines: lines.slice(start - 1, end) };
650
+ }
651
+ }
652
+ }
653
+ return { kind: "absent" };
654
+ }
655
+ async function stillWritten(input, commit, pageText) {
656
+ const pin = input.entry.citation.claim?.integrity;
657
+ if (pin === void 0) return { stop: true, born: true };
658
+ const manifest = input.manifest;
659
+ if (manifest !== void 0) {
660
+ const shown = await input.git.showFile(commit, manifest);
661
+ const held2 = "text" in shown && shown.text.includes(pin);
662
+ return { stop: !held2, born: !held2 };
663
+ }
664
+ const held = pageText.includes(pin);
665
+ return { stop: false, born: !held };
666
+ }
667
+ async function claimHistory(input) {
668
+ const { git, path } = input;
669
+ if (git.pageCommits === void 0) return { kind: "none" };
670
+ if (!await git.available()) return { kind: "none" };
671
+ const cap = input.cap ?? MAX_PAGE_COMMITS;
672
+ const history = await git.pageCommits(path, cap);
673
+ const { commits } = history;
674
+ if (commits.length === 0) return { kind: "none" };
675
+ const format = input.page.format;
676
+ const baselineAt = (index, then, found) => {
677
+ if (found.kind !== "found") return { kind: "none" };
678
+ const commit = commits[index];
679
+ if (commit === void 0) return { kind: "none" };
680
+ return {
681
+ kind: "baseline",
682
+ commit: commit.sha,
683
+ commitsSince: commits.slice(0, index).map((c) => c.subject),
684
+ words: claimWords(found.lines, format),
685
+ pageWords: claimWords(then.lines.slice(then.page.bodyLine - 1), format),
686
+ lines: found.lines,
687
+ at: spellLines(found.span)
688
+ };
689
+ };
690
+ const examined = [];
691
+ let ended = "cap";
692
+ let born = false;
693
+ for (let i = 0; i < commits.length; i++) {
694
+ const commit = commits[i];
695
+ if (commit === void 0) continue;
696
+ const shown = await git.showFile(commit.sha, path);
697
+ if (!("text" in shown)) {
698
+ ended = shown.missing === "commit" ? "unavailable" : "birth";
699
+ break;
700
+ }
701
+ const then = readThen(input, shown.text);
702
+ if (then === void 0) {
703
+ ended = "birth";
704
+ break;
705
+ }
706
+ examined.push(then);
707
+ const found = anchorHeldAt(input, then);
708
+ if (found.kind === "found") return baselineAt(i, then, found);
709
+ const written = await stillWritten(input, commit.sha, shown.text);
710
+ if (written.born) born = true;
711
+ if (written.stop) {
712
+ ended = "birth";
713
+ break;
714
+ }
715
+ }
716
+ for (const [i, then] of examined.entries()) {
717
+ const found = windowHeldAt(input, then);
718
+ if (found.kind === "found") return baselineAt(i, then, found);
719
+ }
720
+ if (ended === "birth" || born) return { kind: "none" };
721
+ if (ended === "unavailable") return { kind: "unavailable" };
722
+ return history.truncated || history.shallow ? { kind: "unavailable" } : { kind: "none" };
723
+ }
724
+ function claimDiff(input) {
725
+ const a = input.was.lines;
726
+ const b = input.now.lines;
727
+ let head = 0;
728
+ while (head < a.length && head < b.length && a[head] === b[head]) head++;
729
+ let tail = 0;
730
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) {
731
+ tail++;
732
+ }
733
+ const out = [
734
+ `--- ${input.file}@${shortCommit(input.commit)}:${input.was.at}`,
735
+ `+++ ${input.file}:${input.now.at}`
736
+ ];
737
+ for (const line of a.slice(0, head)) out.push(` ${line}`);
738
+ for (const line of a.slice(head, a.length - tail)) out.push(`-${line}`);
739
+ for (const line of b.slice(head, b.length - tail)) out.push(`+${line}`);
740
+ for (const line of a.slice(a.length - tail)) out.push(` ${line}`);
741
+ return out.join("\n");
742
+ }
743
+ function refineClaim(input) {
744
+ const { claim, history, entry, page, lines } = input;
745
+ if (history.kind === "unavailable") return { ...claim, historyAvailable: false };
746
+ if (history.kind === "none") return claim;
747
+ const format = page.format;
748
+ const base = {
749
+ ...claim,
750
+ commitSha: history.commit,
751
+ historyAvailable: true,
752
+ commitsSince: history.commitsSince,
753
+ baselineText: history.lines
754
+ };
755
+ const withDiff = (end, now2) => {
756
+ const diff = claimDiff({
757
+ file: page.file,
758
+ commit: history.commit,
759
+ was: { at: history.at, lines: history.lines },
760
+ now: now2
761
+ });
762
+ return { ...end, diff };
763
+ };
764
+ if (entry.citation.quote === true) {
765
+ const now2 = claim.text ?? [];
766
+ const same = stripMarkers(now2, format).join("\n") === stripMarkers(history.lines, format).join("\n");
767
+ const at = claim.fileLines ?? history.at;
768
+ return withDiff(same ? { ...base, status: "reanchored" } : base, { at, lines: now2 });
769
+ }
770
+ if (entry.citation.claim?.lines === void 0) {
771
+ const now2 = claim.text ?? [];
772
+ const w12 = claimWords(now2, format);
773
+ const at = claim.fileLines ?? history.at;
774
+ const held = holdsWords(w12, history.words) && holdsWords(history.pageWords, w12);
775
+ return withDiff(held ? { ...base, status: "reanchored" } : base, { at, lines: now2 });
776
+ }
777
+ const runs = runsHolding(lines, format, page.bodyLine, history.words);
778
+ const [only] = runs;
779
+ if (runs.length > 1) {
780
+ return {
781
+ ...claim,
782
+ status: "moved-ambiguous",
783
+ candidates: runs.map((span) => spellLines(toBodyLines(span, page.bodyLine))),
784
+ candidateFileLines: runs.map(spellLines)
785
+ };
786
+ }
787
+ if (only === void 0) {
788
+ return withDiff(base, { at: claim.fileLines ?? history.at, lines: claim.text ?? [] });
789
+ }
790
+ const now = lines.slice(only.start - 1, only.end);
791
+ const w1 = claimWords(now, format);
792
+ if (!holdsWords(history.pageWords, w1)) {
793
+ return withDiff(base, { at: claim.fileLines ?? history.at, lines: claim.text ?? [] });
794
+ }
795
+ return withDiff(
796
+ {
797
+ ...base,
798
+ status: "reanchored",
799
+ newLines: spellLines(toBodyLines(only, page.bodyLine)),
800
+ newFileLines: spellLines(only)
801
+ },
802
+ { at: spellLines(only), lines: now }
803
+ );
804
+ }
805
+
806
+ // src/cite/core/check-page.ts
807
+ function byLine(a, b) {
808
+ return (a.line ?? Number.MAX_SAFE_INTEGER) - (b.line ?? Number.MAX_SAFE_INTEGER);
809
+ }
810
+ function relevel(finding, severity) {
811
+ const level = severity[finding.rule];
812
+ if (level === "off") return void 0;
813
+ return { ...finding, severity: level };
814
+ }
815
+ async function citedNow(input, source, entry) {
816
+ const range = sourceRange(entry.citation.source);
817
+ const read = await readSource(input.root, input.index, range, input.key, input.cache);
818
+ if (read.kind === "missing") return void 0;
819
+ const at = source.status === "moved" && source.newSrc !== void 0 ? parseSrc(source.newSrc) : range;
820
+ try {
821
+ return sliceLines(read.lines ?? splitLines(read.text), at);
822
+ } catch {
823
+ return void 0;
824
+ }
825
+ }
826
+ function faithful(blockText, pin, key, now) {
827
+ return hashLines(splitLines(blockText).join("\n"), key) === pin || now !== void 0 && blockMatches(blockText, now);
828
+ }
829
+ function insideOf(lines, block) {
830
+ return lines.slice(block.start, block.end - 1).join("\n");
831
+ }
832
+ async function quoteFindings(input, entry, result) {
833
+ const { citation } = entry;
834
+ if (citation.quote !== true || result.anchor === null) return [];
835
+ const id = citation.id;
836
+ const named = (text) => id === void 0 ? text : `${id}: ${text}`;
837
+ const { page } = input;
838
+ let block;
839
+ if (result.anchor === "marker" && entry.marker !== void 0) {
840
+ block = anchoredLines(page.content, entry.marker.end, page.format, true);
841
+ if (block === void 0) {
842
+ return [
843
+ {
844
+ rule: "quote-drift",
845
+ message: "quote: true, but no fenced block follows the marker",
846
+ ...result.markerLine === void 0 ? {} : { line: result.markerLine }
847
+ }
848
+ ];
849
+ }
850
+ } else {
851
+ const at = result.claim === null ? void 0 : claimLinesNow(result.claim);
852
+ if (at === void 0) return [];
853
+ const span = fenceSpanAt(page.content, at.start, page.format);
854
+ if (span === void 0 || span.end !== at.end) {
855
+ return [
856
+ {
857
+ rule: "anchor-invalid",
858
+ message: named(
859
+ `the quote's claim lines ${at.start === at.end ? String(at.start) : `${String(at.start)}-${String(at.end)}`} are no longer a fenced block.`
860
+ ),
861
+ line: at.start
862
+ }
863
+ ];
864
+ }
865
+ block = span;
866
+ }
867
+ if (!input.checkSources || result.source.status === "missing") return [];
868
+ const key = sourceRange(citation.source).encrypted ? input.key : void 0;
869
+ const now = await citedNow(input, result.source, entry);
870
+ const inside = insideOf(input.lines, block);
871
+ if (faithful(inside, citation.source.integrity, key, now)) return [];
872
+ return [
873
+ {
874
+ rule: "quote-drift",
875
+ message: "quote: true, but the fenced block does not reproduce the cited lines",
876
+ line: block.start
877
+ }
878
+ ];
879
+ }
880
+ function markerEnd(entry, misplaced) {
881
+ const { marker } = entry;
882
+ if (marker === void 0) return {};
883
+ if (misplaced === void 0) return { marker: { line: marker.line } };
884
+ return {
885
+ marker: {
886
+ line: marker.line,
887
+ misplaced: { unit: spellLines(misplaced.unit), to: misplaced.to }
888
+ }
889
+ };
890
+ }
891
+ function anchorOf(entry, claim, page) {
892
+ const { citation, marker } = entry;
893
+ const hasLines = citation.claim?.lines !== void 0;
894
+ const out = { anchor: null };
895
+ if (marker !== void 0) out.markerLine = marker.line;
896
+ if (hasLines && marker !== void 0) return out;
897
+ if (hasLines) {
898
+ out.anchor = "claim";
899
+ const at = claim === null ? void 0 : claimLineNow(claim);
900
+ if (at !== void 0) out.anchorLine = at;
901
+ return out;
902
+ }
903
+ if (marker !== void 0) {
904
+ out.anchor = "marker";
905
+ const unit = anchoredLines(page.content, marker.end, page.format, citation.quote === true);
906
+ out.anchorLine = unit?.start ?? marker.line;
907
+ }
908
+ return out;
909
+ }
910
+ async function checkCitations(page, opts) {
911
+ const readOptions = {};
912
+ if (page.format !== void 0) readOptions.format = page.format;
913
+ if (opts.citations !== void 0) readOptions.citations = opts.citations;
914
+ if (opts.owned !== void 0) readOptions.owned = opts.owned;
915
+ const read = readPage(page.file, page.content, readOptions);
916
+ const severity = resolveSeverity(opts.severity);
917
+ const client = opts.gitClient ?? gitClient(opts.root);
918
+ const pageGit = opts.pageGitClient ?? (opts.pageRoot === void 0 || opts.pageRoot === opts.root ? client : gitClient(opts.pageRoot));
919
+ const checkSources = opts.checkSources !== false;
920
+ const lines = splitLines(read.content);
921
+ const results = [];
922
+ const findings = [];
923
+ const notices = [];
924
+ for (const finding of read.findings) {
925
+ const kept = relevel(finding, severity);
926
+ if (kept !== void 0) findings.push(kept);
927
+ }
928
+ const index = checkSources ? opts.sourceIndex ?? await buildSourceIndex(opts.root, { gitClient: client }) : void 0;
929
+ const classifyOpts = index === void 0 ? void 0 : {
930
+ root: opts.root,
931
+ index,
932
+ git: client,
933
+ ...opts.key === void 0 ? {} : { key: opts.key },
934
+ ...opts.sourceCache === void 0 ? {} : { cache: opts.sourceCache }
935
+ };
936
+ const quoteInput = {
937
+ page: read,
938
+ lines,
939
+ root: opts.root,
940
+ index: index ?? emptyIndex,
941
+ checkSources,
942
+ ...opts.key === void 0 ? {} : { key: opts.key },
943
+ ...opts.sourceCache === void 0 ? {} : { cache: opts.sourceCache }
944
+ };
945
+ const misplaced = misplacedMarkers(read, lines);
946
+ const misplacedAt = new Map(misplaced.map((found) => [found.line, found]));
947
+ for (const entry of read.citations) {
948
+ const split = entry.marker === void 0 ? void 0 : misplacedAt.get(entry.marker.line);
949
+ const claim = await withClaimHistory(
950
+ { page: read, entry, claim: claimEnd(read, entry, lines, split), lines },
951
+ { path: read.file, git: pageGit, ...opts.pageCommitCap === void 0 ? {} : { cap: opts.pageCommitCap }, ...opts.owned === void 0 ? {} : { manifest: opts.owned.file } }
952
+ );
953
+ const source = classifyOpts === void 0 ? skippedSource(entry) : await classifyCitation(entry, classifyOpts);
954
+ const result = {
955
+ citation: entry.citation,
956
+ origin: entry.origin,
957
+ ...anchorOf(entry, claim, read),
958
+ ...markerEnd(entry, split),
959
+ claim,
960
+ source
961
+ };
962
+ results.push(result);
963
+ for (const found of await quoteFindings(quoteInput, entry, result)) {
964
+ const level = severity[found.rule];
965
+ if (level === "off") continue;
966
+ const finding = {
967
+ rule: found.rule,
968
+ ruleId: ruleId(found.rule),
969
+ severity: level,
970
+ message: found.message,
971
+ src: source.src,
972
+ index: entry.origin.index
973
+ };
974
+ if (found.line !== void 0) finding.line = found.line;
975
+ if (entry.citation.id !== void 0) finding.id = entry.citation.id;
976
+ findings.push(finding);
977
+ }
978
+ }
979
+ const misplacedLevel = severity["marker-misplaced"];
980
+ if (misplacedLevel !== "off") {
981
+ for (const found of misplaced) {
982
+ const entry = found.entry;
983
+ const finding = {
984
+ rule: "marker-misplaced",
985
+ ruleId: ruleId("marker-misplaced"),
986
+ severity: misplacedLevel,
987
+ message: misplacedMessageFor(entry?.citation.id, {
988
+ line: found.line,
989
+ unit: spellLines(found.unit),
990
+ place: found.place,
991
+ block: found.block
992
+ }),
993
+ line: found.line
994
+ };
995
+ if (entry !== void 0) {
996
+ finding.index = entry.origin.index;
997
+ finding.src = spellSource(entry.citation.source);
998
+ if (entry.citation.id !== void 0) finding.id = entry.citation.id;
999
+ }
1000
+ findings.push(finding);
1001
+ }
1002
+ }
1003
+ findings.push(...findingsFor(results, severity));
1004
+ const wantsSourceHistory = checkSources && read.citations.some(({ citation }) => citation.source["commit-sha"] !== void 0);
1005
+ const wantsClaimHistory = results.some((r) => r.claim?.status === "changed");
1006
+ if (wantsSourceHistory && !await client.available()) notices.push(GIT_UNAVAILABLE_HISTORY);
1007
+ else if (wantsClaimHistory && !await pageGit.available()) {
1008
+ notices.push(GIT_UNAVAILABLE_HISTORY);
1009
+ }
1010
+ if (results.some((r) => r.claim?.historyAvailable === false)) {
1011
+ notices.push(PAGE_HISTORY_UNAVAILABLE);
1012
+ }
1013
+ const unavailable = results.find((r) => r.source.historyAvailable === false);
1014
+ if (unavailable?.source.commitSha !== void 0) {
1015
+ notices.push(
1016
+ `commit ${unavailable.source.commitSha.slice(0, 7)} not found in history; use fetch-depth: 0 to enable never-true and diffs`
1017
+ );
1018
+ }
1019
+ const budget = String(MOVE_BUDGET_BYTES / (1024 * 1024));
1020
+ if (results.some((r) => r.source.truncatedSearch === true && r.source.truncatedAcross !== true)) {
1021
+ notices.push(
1022
+ `a move search hit its ${budget} MiB budget before covering the file; a citation may read changed rather than moved`
1023
+ );
1024
+ }
1025
+ if (results.some((r) => r.source.truncatedAcross === true)) {
1026
+ notices.push(
1027
+ `a move search hit its ${budget} MiB budget before covering the files changed since the pin's commit; a citation may read changed or missing rather than moved`
1028
+ );
1029
+ }
1030
+ return {
1031
+ file: read.file,
1032
+ format: read.format,
1033
+ citations: results,
1034
+ findings: findings.sort(byLine),
1035
+ notices
1036
+ };
1037
+ }
1038
+ async function withClaimHistory(now, read) {
1039
+ const { claim } = now;
1040
+ if (claim === null || claim.status !== "changed") return claim;
1041
+ if (read.path === STDIN_LABEL) return claim;
1042
+ const input = { page: now.page, entry: now.entry, claim, ...read };
1043
+ const history = await claimHistory(input);
1044
+ return refineClaim({ ...input, lines: now.lines, history });
1045
+ }
1046
+ function skippedSource(entry) {
1047
+ const { source } = entry.citation;
1048
+ const out = { src: spellSource(source), status: "skipped" };
1049
+ if (source["commit-sha"] !== void 0) out.commitSha = source["commit-sha"];
1050
+ return out;
1051
+ }
1052
+ var emptyIndex = { files: () => [], has: () => false };
1053
+
1054
+ // src/cite/core/config.ts
1055
+ import { resolve } from "node:path";
1056
+ var CITE_SECTION = "cite";
1057
+ var DEFAULT_CITE_BASELINE_PATH = ".manni-cite-baseline.json";
1058
+ var CONFIG_KEYS = [
1059
+ "allowEmpty",
1060
+ "respectGitignore",
1061
+ "root",
1062
+ "baseline",
1063
+ "checkSources",
1064
+ "severity"
1065
+ ];
1066
+ var SALT_KEY = "salt";
1067
+ var saltRefusal = (source) => `${source}: "salt" is no longer a cite key. Values are encrypted with a family key: a top-level encryptionKey:, or MANNI_ENCRYPTION_KEY. Run \`manni key set\`.`;
1068
+ var SEVERITY_LEVELS = [...[...SEVERITIES].reverse(), "off"];
1069
+ function isLevel(value) {
1070
+ return typeof value === "string" && SEVERITY_LEVELS.includes(value);
1071
+ }
1072
+ var CONFIG_REF = "https://hawkeyexl.github.io/manni/meta/reference/configuration/";
1073
+ var MOVED_KEYS = ["paths", "exclude"];
1074
+ function assertNoMovedKeys(raw, source) {
1075
+ for (const key of MOVED_KEYS) {
1076
+ if (Object.hasOwn(raw, key)) {
1077
+ throw new CiteError(
1078
+ `${source}: "${key}" is no longer a cite key. Document sets are declared once for every tool, under a top-level collections: list. See ${CONFIG_REF}#collections`
1079
+ );
1080
+ }
1081
+ }
1082
+ }
1083
+ var CONFIG_FILE = {
1084
+ section: CITE_SECTION,
1085
+ // The citation tool never had a file of its own before the family file.
1086
+ legacyNames: [],
1087
+ toError: (message) => new CiteError(message)
1088
+ };
1089
+ function rejectUnknownKeys(obj, allowed, where, source) {
1090
+ for (const key of Object.keys(obj)) {
1091
+ if (!allowed.includes(key)) {
1092
+ throw new CiteError(
1093
+ `Unknown key "${key}" under ${where}: in ${source}. Supported keys: ${allowed.join(", ")}.`
1094
+ );
1095
+ }
1096
+ }
1097
+ }
1098
+ function wrongType(key, expected, source) {
1099
+ return new CiteError(`cite.${key} in ${source} must be ${expected}.`);
1100
+ }
1101
+ function asBoolean(value, key, source) {
1102
+ if (typeof value !== "boolean") throw wrongType(key, "a boolean", source);
1103
+ return value;
1104
+ }
1105
+ function asString(value, key, source) {
1106
+ if (typeof value !== "string") throw wrongType(key, "a string", source);
1107
+ return value;
1108
+ }
1109
+ function isMapping(value) {
1110
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1111
+ }
1112
+ function parseSeverity(value, source) {
1113
+ if (!isMapping(value)) throw wrongType("severity", "a mapping", source);
1114
+ rejectUnknownKeys(value, CITE_RULES, "cite.severity", source);
1115
+ const severity = {};
1116
+ for (const [rule, level] of Object.entries(value)) {
1117
+ if (!isCiteRule(rule)) continue;
1118
+ if (!isLevel(level)) {
1119
+ const got = typeof level === "string" ? `, not "${level}"` : "";
1120
+ throw new CiteError(
1121
+ `cite.severity.${rule} in ${source} must be one of ${SEVERITY_LEVELS.join(", ")}${got}.`
1122
+ );
1123
+ }
1124
+ severity[rule] = level;
1125
+ }
1126
+ return severity;
1127
+ }
1128
+ function parseCiteConfig(raw, source) {
1129
+ if (raw == null) return {};
1130
+ if (!isMapping(raw)) {
1131
+ throw new CiteError(`cite: in ${source} must be a mapping.`);
1132
+ }
1133
+ assertNoMovedKeys(raw, source);
1134
+ if (Object.hasOwn(raw, SALT_KEY)) throw new CiteError(saltRefusal(source));
1135
+ rejectUnknownKeys(raw, CONFIG_KEYS, "cite", source);
1136
+ const config = {};
1137
+ if (raw.allowEmpty !== void 0)
1138
+ config.allowEmpty = asBoolean(raw.allowEmpty, "allowEmpty", source);
1139
+ if (raw.respectGitignore !== void 0)
1140
+ config.respectGitignore = asBoolean(raw.respectGitignore, "respectGitignore", source);
1141
+ if (raw.root !== void 0) config.root = asString(raw.root, "root", source);
1142
+ if (raw.baseline !== void 0)
1143
+ config.baseline = asString(raw.baseline, "baseline", source);
1144
+ if (raw.checkSources !== void 0)
1145
+ config.checkSources = asBoolean(raw.checkSources, "checkSources", source);
1146
+ if (raw.severity !== void 0) config.severity = parseSeverity(raw.severity, source);
1147
+ return config;
1148
+ }
1149
+ function readCiteConfigFile(explicitPath, cwd) {
1150
+ return explicitPath ? readConfigFile(explicitPath, cwd, CONFIG_FILE) : findConfigFile(cwd, CONFIG_FILE);
1151
+ }
1152
+ async function loadCiteConfig(explicitPath, cwd = process.cwd()) {
1153
+ const file = await readCiteConfigFile(explicitPath, cwd);
1154
+ return file === null ? null : toLoaded(file);
1155
+ }
1156
+ function toLoaded(file) {
1157
+ return {
1158
+ config: parseCiteConfig(file.value, file.source),
1159
+ path: file.path,
1160
+ dir: file.dir,
1161
+ source: file.source,
1162
+ collections: file.collections
1163
+ };
1164
+ }
1165
+ var STDIN = "-";
1166
+ async function resolveCiteRun(opts) {
1167
+ const cwd = resolve(opts.cwd);
1168
+ const env = opts.env ?? process.env;
1169
+ const wanted = opts.collection ?? [];
1170
+ const typedInputs = opts.inputs.filter((input) => input !== STDIN);
1171
+ if (wanted.length > 0 && typedInputs.length > 0) {
1172
+ throw new CiteError(
1173
+ "--collection selects a configured collection; it cannot be combined with paths."
1174
+ );
1175
+ }
1176
+ const file = opts.noConfig ? null : await readCiteConfigFile(opts.configPath, cwd);
1177
+ const loaded = file === null ? null : toLoaded(file);
1178
+ if (wanted.length > 0 && loaded === null) {
1179
+ throw new CiteError("--collection needs a config file to select from.");
1180
+ }
1181
+ if (loaded) opts.onConfigLoaded?.({ path: loaded.path, dir: loaded.dir });
1182
+ const config = loaded ? loaded.config : null;
1183
+ const collections = loaded ? selectCollections(loaded.collections, wanted, loaded.source, (message) => new CiteError(message)) : [];
1184
+ const fromCollections = wanted.length > 0 || opts.inputs.length === 0;
1185
+ const collectionPaths = fromCollections ? collections.flatMap((c) => c.paths) : [];
1186
+ const inputs = fromCollections ? [...opts.inputs, ...collectionPaths] : opts.inputs;
1187
+ const base = collectionPaths.length > 0 && loaded ? loaded.dir : cwd;
1188
+ let root;
1189
+ if (opts.root !== void 0) {
1190
+ root = resolve(cwd, opts.root);
1191
+ } else if (loaded && config?.root !== void 0) {
1192
+ root = resolve(loaded.dir, config.root);
1193
+ } else {
1194
+ const gitRoot = findGitRoot(cwd);
1195
+ if (gitRoot !== null) {
1196
+ root = gitRoot;
1197
+ } else {
1198
+ root = cwd;
1199
+ if (opts.resolvesSources !== false) {
1200
+ opts.onNotice?.(`No git root found; resolving src: paths from ${cwd}`);
1201
+ }
1202
+ }
1203
+ }
1204
+ const { key, source: keySource } = resolveEncryptionKey({
1205
+ env,
1206
+ file,
1207
+ toError: (message) => new CiteError(message)
1208
+ });
1209
+ return {
1210
+ config,
1211
+ inputs,
1212
+ base,
1213
+ collections,
1214
+ fromCollections,
1215
+ ...loaded ? { configDir: loaded.dir, configPath: loaded.path, configSource: loaded.source } : {},
1216
+ root,
1217
+ key,
1218
+ keySource,
1219
+ ...file === null ? {} : { configFile: file }
1220
+ };
1221
+ }
1222
+
1223
+ // src/cite/core/sidecar.ts
1224
+ import { existsSync } from "node:fs";
1225
+ import { isAbsolute, relative, resolve as resolve2, sep as sep2 } from "node:path";
1226
+ var CITATIONS_KEY = "citations";
1227
+ var toPosix = (path) => path.split(sep2).join("/");
1228
+ function reportedPath(abs, base) {
1229
+ const rel = relative(base, abs);
1230
+ return rel === "" ? "." : toPosix(rel);
1231
+ }
1232
+ function urlManifestRefusal(source, collection) {
1233
+ return `${source}: collection ${collection}: ${CITATIONS_KEY} cannot come from a URL manifest, because cite writes them.`;
1234
+ }
1235
+ function twoManifestsRefusal(label, a, b) {
1236
+ return `${label} is in collections ${a} and ${b}, and both keep citations in a manifest.`;
1237
+ }
1238
+ function ownsCitations(manifest) {
1239
+ return manifest.keys === void 0 || manifest.keys.includes(CITATIONS_KEY);
1240
+ }
1241
+ function emptyIndex2() {
1242
+ return { owners: /* @__PURE__ */ new Map(), byPath: /* @__PURE__ */ new Map(), byField: /* @__PURE__ */ new Map(), entries: [] };
1243
+ }
1244
+ async function loadCitationSidecars(opts) {
1245
+ const toError = opts.toError ?? ((message) => new CiteError(message));
1246
+ const manifests = [];
1247
+ const urls = [];
1248
+ const scoped = [];
1249
+ for (const collection of opts.collections) {
1250
+ const owning = collection.externalMetadata.filter(ownsCitations);
1251
+ if (owning.length === 0) continue;
1252
+ const loaded = [];
1253
+ for (const manifest of owning) {
1254
+ const implied = manifest.keys === void 0 ? { implied: { collection, manifest } } : {};
1255
+ if (classifyRef(manifest.file).kind === "url") {
1256
+ if (manifest.keys !== void 0) {
1257
+ throw toError(urlManifestRefusal(opts.configSource, collection.name));
1258
+ }
1259
+ urls.push({ collection: collection.name, path: manifest.file, file: manifest.file, join: externalMetadataJoin(manifest), ...implied });
1260
+ continue;
1261
+ }
1262
+ loaded.push(manifest);
1263
+ const path = isAbsolute(manifest.file) ? manifest.file : resolve2(opts.configDir, manifest.file);
1264
+ manifests.push({
1265
+ collection: collection.name,
1266
+ path,
1267
+ file: reportedPath(path, opts.base),
1268
+ join: externalMetadataJoin(manifest),
1269
+ ...hasPagePlaceholder(manifest.file) ? { pattern: manifest.file } : {},
1270
+ ...implied
1271
+ });
1272
+ }
1273
+ if (loaded.length > 0) scoped.push({ ...collection, externalMetadata: loaded });
1274
+ }
1275
+ if (manifests.length === 0 && urls.length === 0) return null;
1276
+ const index = await loadExternalMetadata(scoped, {
1277
+ configDir: opts.configDir,
1278
+ base: opts.base,
1279
+ ...opts.pages === void 0 ? {} : { pages: opts.pages }
1280
+ }) ?? (urls.length > 0 ? emptyIndex2() : null);
1281
+ if (index === null) return null;
1282
+ return sidecars(index, manifests, urls, scoped, opts);
1283
+ }
1284
+ function sidecars(index, manifests, urls, collections, opts) {
1285
+ const toError = opts.toError ?? ((message) => new CiteError(message));
1286
+ const { configDir, base } = opts;
1287
+ let marks;
1288
+ const marksOnce = () => marks ??= opts.marks?.() ?? Promise.resolve(void 0);
1289
+ const pageOwner = (owner, label) => {
1290
+ if (owner.pattern === void 0) return owner;
1291
+ const resolved = pageManifestPath(owner.pattern, configDir, resolve2(base, label));
1292
+ if ("outside" in resolved) return void 0;
1293
+ return { ...owner, path: resolved.abs, file: reportedPath(resolved.abs, base) };
1294
+ };
1295
+ const forPage = async (label, content, format) => {
1296
+ const declaredMembers = memberOf(opts.collections, configDir, base, label);
1297
+ if (declaredMembers.length === 0) return { joins: [] };
1298
+ const members = memberOf(collections, configDir, base, label);
1299
+ const memberOfCollection = (m) => declaredMembers.includes(m.collection);
1300
+ let extracted;
1301
+ const extract = () => extracted ??= pickExtractor(label, format).extract(content, label);
1302
+ const candidates2 = [...manifests, ...urls].filter((m) => m.implied !== void 0 && memberOfCollection(m));
1303
+ let marked;
1304
+ if (candidates2.length > 0) {
1305
+ const read = await marksOnce();
1306
+ const data = extract().data;
1307
+ marked = read === void 0 ? void 0 : await read(label, Object.hasOwn(data, CITATIONS_KEY) ? data : { ...data, [CITATIONS_KEY]: null }, declaredMembers);
1308
+ }
1309
+ const keeps = (m) => m.implied === void 0 || ownsKey(m.implied.collection, m.implied.manifest, CITATIONS_KEY, marked);
1310
+ const url = urls.find((m) => memberOfCollection(m) && keeps(m));
1311
+ if (url !== void 0) throw toError(urlManifestRefusal(opts.configSource, url.collection));
1312
+ const mine = manifests.filter((m) => members.includes(m.collection) && keeps(m));
1313
+ const [declared, second] = mine;
1314
+ if (declared === void 0) return { joins: [] };
1315
+ if (second !== void 0) {
1316
+ throw toError(twoManifestsRefusal(label, declared.collection, second.collection));
1317
+ }
1318
+ const owner = pageOwner(declared, label);
1319
+ if (owner === void 0) return { joins: [] };
1320
+ const readMarks = await marksOnce();
1321
+ const merged = await mergeWithMarks(label, extract(), index, members, base, {
1322
+ encryptionKey: () => opts.key,
1323
+ ...readMarks === void 0 ? {} : { marks: readMarks }
1324
+ });
1325
+ const out = { owner, joins: merged.joins };
1326
+ if (owner.join === PATH_JOIN) {
1327
+ out.entry = toPosix(relative(configDir, resolve2(base, label)));
1328
+ } else {
1329
+ const value = merged.joins.find((j) => j.field === owner.join)?.value;
1330
+ const raw = value ?? extract().data[owner.join];
1331
+ if (typeof raw === "string" && raw !== "") out.entry = raw;
1332
+ else if (typeof raw === "number" || typeof raw === "boolean") out.entry = String(raw);
1333
+ }
1334
+ const where = merged.locate(`/${CITATIONS_KEY}`);
1335
+ if (where !== void 0) {
1336
+ const supplied = merged.extracted.data[CITATIONS_KEY];
1337
+ const list = Array.isArray(supplied) ? supplied : [supplied];
1338
+ out.citations = list.map((entry, i) => {
1339
+ const at = merged.locate(`/${CITATIONS_KEY}/${String(i)}`);
1340
+ const line = (Array.isArray(supplied) ? at?.line : void 0) ?? where.line;
1341
+ return {
1342
+ entry,
1343
+ origin: {
1344
+ kind: "manifest",
1345
+ file: where.file,
1346
+ ...line === void 0 ? {} : { line }
1347
+ }
1348
+ };
1349
+ });
1350
+ }
1351
+ return out;
1352
+ };
1353
+ const ignoredManifests = async (loaded) => {
1354
+ const existing = /* @__PURE__ */ new Set();
1355
+ for (const label of loaded) {
1356
+ const members = memberOf(collections, configDir, base, label);
1357
+ for (const declared of manifests) {
1358
+ if (declared.pattern === void 0 || !members.includes(declared.collection)) continue;
1359
+ const owner = pageOwner(declared, label);
1360
+ if (owner !== void 0 && existsSync(owner.path)) existing.add(owner.file);
1361
+ }
1362
+ }
1363
+ if (existing.size === 0) return [];
1364
+ const { ignored } = await gitIgnored([...existing], base);
1365
+ return [...existing].filter((file) => ignored.has(file));
1366
+ };
1367
+ return {
1368
+ manifests,
1369
+ configDir,
1370
+ base,
1371
+ forPage,
1372
+ orphans: (loaded, covered) => orphanEntries(index, loaded, base, covered),
1373
+ orphanJoins: (matched, covered) => orphanJoins(index, matched, covered),
1374
+ ignoredManifests
1375
+ };
1376
+ }
1377
+ function ignoredManifestWarning(file) {
1378
+ return `${file} is covered by .gitignore, so CI checks out a page with no citations.`;
1379
+ }
1380
+ function orphanRefusal(orphans, toError = (m) => new CiteError(m)) {
1381
+ return toError(orphanError(orphans).message);
1382
+ }
1383
+ function duplicateJoinRefusal(field, value, file, labels) {
1384
+ return `${String(labels.length)} pages carry ${field} "${value}"; ${file} cannot tell them apart (${labels.join(", ")}).`;
1385
+ }
1386
+ function sidecarsFor(run2, over = {}) {
1387
+ const declared = run2.configFile?.collections ?? [];
1388
+ if (run2.configDir === void 0 || declared.length === 0) return Promise.resolve(null);
1389
+ const { configPath } = run2;
1390
+ return loadCitationSidecars({
1391
+ // Every declared collection, whatever `--collection` or the paths chose.
1392
+ collections: declared,
1393
+ configDir: run2.configDir,
1394
+ base: over.base ?? run2.base,
1395
+ configSource: run2.configSource ?? run2.configPath ?? "the config",
1396
+ ...run2.key === void 0 ? {} : { key: run2.key },
1397
+ ...over.toError === void 0 ? {} : { toError: over.toError },
1398
+ ...over.pages === void 0 ? {} : { pages: over.pages },
1399
+ // A keyless manifest (proposal 0068) keeps what a page's schemas mark,
1400
+ // read from meta's section of the same file.
1401
+ ...configPath === void 0 ? {} : { marks: () => configMarks(configPath, run2.base) }
1402
+ });
1403
+ }
1404
+
1405
+ // src/cite/commands/check.ts
1406
+ function joinHits() {
1407
+ const matched = /* @__PURE__ */ new Map();
1408
+ const seen = /* @__PURE__ */ new Map();
1409
+ return {
1410
+ matched,
1411
+ record(page, label) {
1412
+ for (const join2 of page.joins) {
1413
+ const values = matched.get(join2.field) ?? /* @__PURE__ */ new Set();
1414
+ values.add(join2.value);
1415
+ matched.set(join2.field, values);
1416
+ const id = `${join2.field}\0${join2.value}`;
1417
+ const hit = seen.get(id) ?? { file: join2.file, labels: [] };
1418
+ if (!hit.labels.includes(label)) hit.labels.push(label);
1419
+ seen.set(id, hit);
1420
+ if (hit.labels.length > 1) {
1421
+ throw new CiteError(
1422
+ duplicateJoinRefusal(join2.field, join2.value, join2.file, hit.labels)
1423
+ );
1424
+ }
1425
+ }
1426
+ }
1427
+ };
1428
+ }
1429
+ async function prepareRun(opts, action, verb, sources = {}) {
1430
+ const requireSources = sources.require === true;
1431
+ const cwd = resolve3(opts.cwd ?? process.cwd());
1432
+ const run2 = await resolveCiteRun({
1433
+ cwd,
1434
+ configPath: opts.configPath,
1435
+ noConfig: opts.noConfig,
1436
+ inputs: opts.inputs,
1437
+ collection: opts.collection,
1438
+ root: opts.root,
1439
+ onConfigLoaded: opts.onConfigLoaded,
1440
+ onNotice: opts.onNotice,
1441
+ env: opts.env,
1442
+ ...sources.resolve === false ? { resolvesSources: false } : {}
1443
+ });
1444
+ const { config, inputs, base } = run2;
1445
+ const checkSources = opts.checkSources ?? config?.checkSources ?? true;
1446
+ if (requireSources && !checkSources) {
1447
+ throw new CiteError(`${verb} needs the sources: drop --no-check-sources (or \`checkSources: false\`).`);
1448
+ }
1449
+ if (inputs.length === 0) {
1450
+ throw new CiteError(
1451
+ `No files to ${verb}. Pass paths/globs, or declare a collection under \`collections:\` in manni.config.yaml.`
1452
+ );
1453
+ }
1454
+ const usingStdin = inputs.includes(STDIN_TOKEN);
1455
+ const forced = opts.as === void 0 ? void 0 : extractorByName(opts.as);
1456
+ if (opts.as !== void 0 && forced === void 0) {
1457
+ throw new CiteError(
1458
+ `Unknown format "${opts.as}". Supported extensions: ${supportedExtensions().join(", ")}.`
1459
+ );
1460
+ }
1461
+ if (usingStdin && forced === void 0) {
1462
+ throw new CiteError(STDIN_REQUIRES_AS);
1463
+ }
1464
+ const exts = opts.exts ?? forced?.extensions;
1465
+ const fileInputs = inputs.filter((input) => input !== STDIN_TOKEN);
1466
+ const allowEmpty = opts.allowEmpty ?? config?.allowEmpty;
1467
+ const exclude = [
1468
+ .../* @__PURE__ */ new Set([
1469
+ ...opts.exclude ?? [],
1470
+ ...run2.fromCollections ? run2.collections.flatMap((c) => c.exclude) : []
1471
+ ])
1472
+ ];
1473
+ const { files, gitignoreSkipped } = await resolveTargetSet({
1474
+ inputs: fileInputs,
1475
+ exts,
1476
+ exclude,
1477
+ cwd: base,
1478
+ allowEmpty,
1479
+ ...gitignoreOptions({
1480
+ flag: opts.respectGitignore,
1481
+ configured: config?.respectGitignore,
1482
+ onNotice: opts.onNotice
1483
+ })
1484
+ });
1485
+ assertNonEmpty({ files, inputs: fileInputs, usingStdin, allowEmpty, exclude, exts, gitignoreSkipped, action });
1486
+ const git = opts.gitClient ?? gitClient(run2.root);
1487
+ const pageGit = opts.pageGitClient ?? (run2.base === run2.root ? git : gitClient(run2.base));
1488
+ const pageOptions = {
1489
+ root: run2.root,
1490
+ checkSources,
1491
+ key: run2.key,
1492
+ severity: config?.severity,
1493
+ gitClient: git,
1494
+ pageRoot: run2.base,
1495
+ pageGitClient: pageGit
1496
+ };
1497
+ if (checkSources) pageOptions.sourceIndex = await sourceIndexFor(run2.root, git);
1498
+ const sidecars2 = await sidecarsFor(run2, { pages: files.map((file) => resolve3(base, file)) });
1499
+ const setupFor = async (label, content) => {
1500
+ const page = await sidecars2?.forPage(label, content, forced?.name);
1501
+ if (page?.owner === void 0) return { options: pageOptions };
1502
+ return {
1503
+ sidecar: page,
1504
+ options: {
1505
+ ...pageOptions,
1506
+ owned: { file: page.owner.file, collection: page.owner.collection },
1507
+ ...page.citations === void 0 ? {} : { citations: page.citations }
1508
+ }
1509
+ };
1510
+ };
1511
+ const scopedByFlags = (
1512
+ // The inputs the operator typed, not the collection globs they resolved
1513
+ // to: a corpus run has none of the first and all of the second.
1514
+ opts.inputs.length > 0 || usingStdin || opts.as !== void 0 || opts.exts !== void 0 || opts.exclude !== void 0 && opts.exclude.length > 0 || opts.respectGitignore !== void 0
1515
+ );
1516
+ const covered = (opts.collection?.length ?? 0) > 0 ? run2.collections.map((c) => c.name) : void 0;
1517
+ return {
1518
+ cwd,
1519
+ run: run2,
1520
+ files,
1521
+ gitignoreSkipped,
1522
+ usingStdin,
1523
+ forced,
1524
+ pageOptions,
1525
+ git,
1526
+ sidecars: sidecars2,
1527
+ setupFor,
1528
+ scopedByFlags,
1529
+ ...covered === void 0 ? {} : { covered }
1530
+ };
1531
+ }
1532
+ async function assertNoOrphans(prepared) {
1533
+ const { sidecars: sidecars2 } = prepared;
1534
+ if (sidecars2 === null || prepared.scopedByFlags) return;
1535
+ const orphans = sidecars2.orphans(prepared.files, prepared.covered);
1536
+ if (orphans.length > 0) throw orphanRefusal(orphans);
1537
+ if (prepared.covered !== void 0) return;
1538
+ for (const file of await sidecars2.ignoredManifests(prepared.files)) warn(ignoredManifestWarning(file));
1539
+ }
1540
+ function assertNoOrphanJoins(prepared, hits) {
1541
+ const { sidecars: sidecars2 } = prepared;
1542
+ if (sidecars2 === null || prepared.scopedByFlags) return;
1543
+ const orphans = sidecars2.orphanJoins(hits.matched, prepared.covered);
1544
+ if (orphans.length > 0) throw orphanRefusal(orphans);
1545
+ }
1546
+ function readTarget(run2, file) {
1547
+ return readFile2(resolve3(run2.base, file), "utf8");
1548
+ }
1549
+ function sayNotices(pages, onNotice, also = []) {
1550
+ const said = /* @__PURE__ */ new Set();
1551
+ for (const notice of [...pages.flatMap((page) => page.notices), ...also]) {
1552
+ if (said.has(notice)) continue;
1553
+ said.add(notice);
1554
+ onNotice?.(notice);
1555
+ }
1556
+ }
1557
+ async function runCheck(opts) {
1558
+ const prepared = await prepareRun(opts, "checked", "check");
1559
+ const { cwd, run: run2, files, gitignoreSkipped, usingStdin, forced, pageOptions, git } = prepared;
1560
+ await assertNoOrphans(prepared);
1561
+ const hits = joinHits();
1562
+ pageOptions.sourceCache = createSourceCache();
1563
+ const pages = [];
1564
+ const checkOne = async (label, content) => {
1565
+ const setup = await prepared.setupFor(label, content);
1566
+ if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
1567
+ pages.push(await checkCitations({ file: label, content, format: forced?.name }, setup.options));
1568
+ };
1569
+ if (usingStdin) await checkOne(STDIN_LABEL, opts.stdinContent ?? "");
1570
+ for (const file of files) await checkOne(file, await readTarget(run2, file));
1571
+ assertNoOrphanJoins(prepared, hits);
1572
+ const wantsDiff = opts.showDiff === true && pageOptions.checkSources !== false && !await git.available();
1573
+ sayNotices(pages, opts.onNotice, wantsDiff ? [GIT_UNAVAILABLE_HISTORY] : []);
1574
+ const results = pages.map(toValidationResult);
1575
+ const frame = { cwd, base: run2.configDir ?? cwd, runBase: run2.base };
1576
+ const request = resolveBaselineRequest(opts, run2.config?.baseline, run2.configDir, cwd, {
1577
+ current: DEFAULT_CITE_BASELINE_PATH
1578
+ });
1579
+ if (request !== null && !request.write && !existsSync2(request.absPath)) {
1580
+ throw new CiteError(
1581
+ `Baseline "${request.label}" not found. Record one with \`manni cite check --write-baseline\`, or drop --baseline.`
1582
+ );
1583
+ }
1584
+ const { results: reported, baseline } = await settleBaseline(results, request, frame);
1585
+ const failed = reported.filter((r) => !r.ok).length;
1586
+ const count = (keep) => reported.reduce((n, r) => n + r.errors.filter(keep).length, 0);
1587
+ const warnings = count((e) => e.severity === "warning");
1588
+ const notices = count((e) => e.severity === "notice");
1589
+ const summary = {
1590
+ files: reported.length,
1591
+ passed: reported.length - failed,
1592
+ failed,
1593
+ errors: count(isErrorSeverity),
1594
+ // Omitted at zero, as meta's summary has them, so the JSON shape of a
1595
+ // clean run is the one every consumer of `meta validate` already reads.
1596
+ ...warnings > 0 ? { warnings } : {},
1597
+ ...notices > 0 ? { notices } : {},
1598
+ ...gitignoreSkipped > 0 ? { gitignoreSkipped } : {},
1599
+ ...baseline ? { baseline } : {}
1600
+ };
1601
+ return { results: reported, summary, frame, pages, warnings, notices };
1602
+ }
1603
+
1604
+ export {
1605
+ CITE_SECTION,
1606
+ DEFAULT_CITE_BASELINE_PATH,
1607
+ parseCiteConfig,
1608
+ readCiteConfigFile,
1609
+ loadCiteConfig,
1610
+ resolveCiteRun,
1611
+ gitClient,
1612
+ GIT_UNAVAILABLE_HISTORY,
1613
+ PAGE_HISTORY_UNAVAILABLE,
1614
+ SHALLOW_RECOMMIT,
1615
+ GIT_UNAVAILABLE_COMMIT,
1616
+ noGit,
1617
+ encryptSourcePath,
1618
+ decryptSourcePath,
1619
+ buildSourceIndex,
1620
+ sourceIndexFor,
1621
+ readSource,
1622
+ historyOf,
1623
+ classifyCitation,
1624
+ CITATIONS_KEY,
1625
+ urlManifestRefusal,
1626
+ twoManifestsRefusal,
1627
+ loadCitationSidecars,
1628
+ orphanRefusal,
1629
+ duplicateJoinRefusal,
1630
+ sidecarsFor,
1631
+ MAX_PAGE_COMMITS,
1632
+ stripMarkers,
1633
+ claimWords,
1634
+ sentencesOf,
1635
+ sharesSentence,
1636
+ wordShare,
1637
+ holdsWords,
1638
+ runsHolding,
1639
+ claimHistory,
1640
+ claimDiff,
1641
+ refineClaim,
1642
+ checkCitations,
1643
+ joinHits,
1644
+ prepareRun,
1645
+ assertNoOrphans,
1646
+ assertNoOrphanJoins,
1647
+ readTarget,
1648
+ sayNotices,
1649
+ runCheck
1650
+ };
1651
+ //# sourceMappingURL=chunk-LAKAWRV5.js.map