@hawkeyexl/manni 4.11.0 → 4.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{batch-UAULUMGH.js → batch-KMGZMIAQ.js} +20 -19
- package/dist/build-NO4AWCYG.js +36 -0
- package/dist/calibrate-XQUG5XAK.js +41 -0
- package/dist/capture-5XE3ZMXK.js +38 -0
- package/dist/{check-YEOYBFST.js → check-HTNPLWOV.js} +19 -18
- package/dist/check-RJ45IBA6.js +34 -0
- package/dist/{check-VELUQAUW.js → check-W24BQNCT.js} +35 -34
- package/dist/{check-VELUQAUW.js.map → check-W24BQNCT.js.map} +1 -1
- package/dist/check-WFXSXICD.js +30 -0
- package/dist/{chunk-IGSHYP74.js → chunk-23P7KMJM.js} +4 -4
- package/dist/{chunk-HA6MQMOY.js → chunk-24J7TYPU.js} +6 -6
- package/dist/{chunk-PCS4VJBV.js → chunk-37QJLVS3.js} +9 -9
- package/dist/{chunk-4CK6ZSOK.js → chunk-3P6KCU36.js} +3 -3
- package/dist/{chunk-54F74XR3.js → chunk-5JBVFWVP.js} +19 -19
- package/dist/{chunk-B7XYNRJQ.js → chunk-6KIYVVKL.js} +2 -2
- package/dist/{chunk-J6IDQGJL.js → chunk-6TVH4FI7.js} +469 -85
- package/dist/chunk-6TVH4FI7.js.map +1 -0
- package/dist/{chunk-GDVZ5VII.js → chunk-7A452BPZ.js} +4 -4
- package/dist/{chunk-M42DZCOU.js → chunk-7FCLXEAQ.js} +2 -2
- package/dist/{chunk-2MJ62AYQ.js → chunk-7MCQHTQ5.js} +4 -3
- package/dist/chunk-7MCQHTQ5.js.map +1 -0
- package/dist/{chunk-LBSCW232.js → chunk-7NXPQKSB.js} +7 -7
- package/dist/{chunk-OPXWPJDA.js → chunk-A3TEDWBZ.js} +14 -14
- package/dist/{chunk-BBT6AVUG.js → chunk-BHHXU2D2.js} +2 -2
- package/dist/{chunk-D4N2V43Z.js → chunk-CEI2J42X.js} +13 -7
- package/dist/chunk-CEI2J42X.js.map +1 -0
- package/dist/{chunk-FQXWVS6J.js → chunk-CMMBWHT2.js} +9 -9
- package/dist/{chunk-EIFCP6CZ.js → chunk-CO47QJKZ.js} +2 -2
- package/dist/{chunk-2DRHT73U.js → chunk-CVZMKWZ5.js} +913 -1398
- package/dist/chunk-CVZMKWZ5.js.map +1 -0
- package/dist/{chunk-ADMEPUBT.js → chunk-CXWTNT2O.js} +3 -3
- package/dist/{chunk-LKP7QDFN.js → chunk-DAQTRAP5.js} +4 -4
- package/dist/chunk-DSWKBZWW.js +16 -0
- package/dist/chunk-DSWKBZWW.js.map +1 -0
- package/dist/{chunk-PWBH7DCQ.js → chunk-EESH6COU.js} +4 -4
- package/dist/{chunk-JPQDNYXH.js → chunk-EM67O2ET.js} +8 -2
- package/dist/chunk-EM67O2ET.js.map +1 -0
- package/dist/{chunk-HQR2ZBHW.js → chunk-I2XQRRP7.js} +36 -7
- package/dist/chunk-I2XQRRP7.js.map +1 -0
- package/dist/{chunk-NCPWXHDO.js → chunk-JEUFYSFQ.js} +4 -4
- package/dist/{chunk-2IR4T5PO.js → chunk-JJ4RSDSY.js} +2 -2
- package/dist/chunk-KCB5JCK4.js +2272 -0
- package/dist/chunk-KCB5JCK4.js.map +1 -0
- package/dist/{chunk-VSEBFHAY.js → chunk-KFKL4VTL.js} +11369 -10218
- package/dist/chunk-KFKL4VTL.js.map +1 -0
- package/dist/{chunk-PCQ7ELJH.js → chunk-L2TD7DZB.js} +109 -5
- package/dist/chunk-L2TD7DZB.js.map +1 -0
- package/dist/{chunk-WDWXAYVH.js → chunk-L3RLGGRK.js} +4 -4
- package/dist/{chunk-T7JAOYE5.js → chunk-L75CAZG4.js} +6 -6
- package/dist/{chunk-U7CTFN2J.js → chunk-MYRPMSOX.js} +7 -7
- package/dist/{chunk-Y6DOMTQD.js → chunk-NCF5NQV5.js} +27 -27
- package/dist/chunk-NCF5NQV5.js.map +1 -0
- package/dist/{chunk-WQWKYAAK.js → chunk-NUQF5F5J.js} +2 -2
- package/dist/{chunk-FUCPZVRY.js → chunk-OEN6FBUW.js} +2 -2
- package/dist/{chunk-FBMZJEB3.js → chunk-OKMTUAWO.js} +4 -4
- package/dist/{chunk-QLWC7MKD.js → chunk-OTJOBZJ5.js} +135 -135
- package/dist/chunk-OTJOBZJ5.js.map +1 -0
- package/dist/{chunk-R4WXQIOZ.js → chunk-PCEM5FOZ.js} +3 -3
- package/dist/{chunk-4D5IZSXM.js → chunk-PXNCRGUL.js} +3 -3
- package/dist/chunk-Q2QC5AZB.js +1868 -0
- package/dist/chunk-Q2QC5AZB.js.map +1 -0
- package/dist/{chunk-ODRPL3WB.js → chunk-Q3ODX5PF.js} +2 -2
- package/dist/{chunk-KGEQJ5CC.js → chunk-RRFFUWDL.js} +8 -8
- package/dist/{chunk-NSBXVPZL.js → chunk-S3XUYW5K.js} +17 -17
- package/dist/{chunk-LPIF5LIE.js → chunk-SAU2WQFC.js} +2 -2
- package/dist/{chunk-MSRU4D4P.js → chunk-SFLICIQV.js} +2 -2
- package/dist/{chunk-NGTVREIH.js → chunk-U6IHLEWT.js} +2 -2
- package/dist/{chunk-QCHVR4ZM.js → chunk-VM7VAFJW.js} +33 -7
- package/dist/chunk-VM7VAFJW.js.map +1 -0
- package/dist/{chunk-6HWBRZIN.js → chunk-VQYGLU4C.js} +252 -193
- package/dist/chunk-VQYGLU4C.js.map +1 -0
- package/dist/{chunk-2IAZEB45.js → chunk-WGMFDG6U.js} +229 -821
- package/dist/chunk-WGMFDG6U.js.map +1 -0
- package/dist/{chunk-GV56WGL2.js → chunk-WO4JYWZK.js} +1036 -883
- package/dist/chunk-WO4JYWZK.js.map +1 -0
- package/dist/chunk-XYRUDFKS.js +685 -0
- package/dist/chunk-XYRUDFKS.js.map +1 -0
- package/dist/{chunk-VQK4VHLF.js → chunk-YQV75FIL.js} +2 -2
- package/dist/chunk-YUTVAMWW.js +2 -0
- package/dist/{chunk-RLT5SEML.js → chunk-Z5MZCCTT.js} +6 -6
- package/dist/{chunk-BKVHX6FU.js → chunk-ZIRBFXDW.js} +5 -10
- package/dist/chunk-ZIRBFXDW.js.map +1 -0
- package/dist/{cli-O4ABTN4Z.js → cli-236TGVJG.js} +31 -30
- package/dist/{cli-O4ABTN4Z.js.map → cli-236TGVJG.js.map} +1 -1
- package/dist/{cli-3BTCK4AB.js → cli-3FAPJBEY.js} +27 -26
- package/dist/{cli-3BTCK4AB.js.map → cli-3FAPJBEY.js.map} +1 -1
- package/dist/{cli-JJMOVIKS.js → cli-BS43EVCD.js} +14 -14
- package/dist/{cli-RORDPAPE.js → cli-CMR4FYF6.js} +59 -38
- package/dist/cli-CMR4FYF6.js.map +1 -0
- package/dist/{cli-ONTC5PKT.js → cli-FX53PBQ3.js} +9 -9
- package/dist/{cli-6KZTHWGP.js → cli-IQNJLKSN.js} +37 -36
- package/dist/{cli-6KZTHWGP.js.map → cli-IQNJLKSN.js.map} +1 -1
- package/dist/{cli-4MF37HAQ.js → cli-KOMXVPXX.js} +26 -25
- package/dist/{cli-4MF37HAQ.js.map → cli-KOMXVPXX.js.map} +1 -1
- package/dist/{cli-I62SRH3R.js → cli-NGL63E53.js} +14 -13
- package/dist/{cli-DRMZBZLB.js → cli-QPGS2MR5.js} +43 -42
- package/dist/{cli-DRMZBZLB.js.map → cli-QPGS2MR5.js.map} +1 -1
- package/dist/{cli-VID7PJS3.js → cli-RKQX4TI3.js} +23 -22
- package/dist/{cli-VID7PJS3.js.map → cli-RKQX4TI3.js.map} +1 -1
- package/dist/{cli-VUKMWSSF.js → cli-WNEI5L7P.js} +4 -4
- package/dist/cli.js +19 -13
- package/dist/cli.js.map +1 -1
- package/dist/{config-XDA42RJL.js → config-JJW75SBX.js} +5 -5
- package/dist/docmeta.js +13 -12
- package/dist/docmeta.js.map +1 -1
- package/dist/github-4UR67MEN.js +35 -0
- package/dist/github-UPGKMVVF.js +30 -0
- package/dist/graph/runtime.d.ts +4 -1
- package/dist/graph/runtime.js +20 -9
- package/dist/graph/runtime.js.map +1 -1
- package/dist/{host-6GURH5HE.js → host-YSTYO5DX.js} +3 -3
- package/dist/index.d.ts +192 -26
- package/dist/index.js +129 -124
- package/dist/index.js.map +1 -1
- package/dist/{json-BXY44ED7.js → json-DQIELR36.js} +18 -17
- package/dist/labels-XSTJJMSG.js +73 -0
- package/dist/labels-XSTJJMSG.js.map +1 -0
- package/dist/lint-BLBGGOYM.js +34 -0
- package/dist/list-B3RRRWFQ.js +32 -0
- package/dist/list-ZFRT3JQH.js +35 -0
- package/dist/{page-EV7OUBAU.js → page-5ISEVJR5.js} +18 -16
- package/dist/{prepare-JHJDJB6I.js → prepare-KFXXG2CJ.js} +22 -21
- package/dist/{prepare-JHJDJB6I.js.map → prepare-KFXXG2CJ.js.map} +1 -1
- package/dist/{pretty-Q5Y2PA64.js → pretty-Y7CGQLAF.js} +18 -17
- package/dist/{pretty-SCRK66M5.js → pretty-ZISH3Q4F.js} +18 -17
- package/dist/{provider-527CO4DM.js → provider-LBX4IXGX.js} +3 -3
- package/dist/{release-3D5PK6M7.js → release-VV3UTKE5.js} +3 -3
- package/dist/{reporters-M6LJ2IYU.js → reporters-26H3N2KW.js} +3 -3
- package/dist/{reporters-A5ADKDYR.js → reporters-4STXJJE2.js} +16 -15
- package/dist/{reporters-RBYTASMI.js → reporters-74B4U2GZ.js} +7 -7
- package/dist/run-DVGTKM52.js +37 -0
- package/dist/{term-CQAGNG3Q.js → term-PLJEYJQV.js} +981 -141
- package/dist/term-PLJEYJQV.js.map +1 -0
- package/dist/validate-CJNFQHMD.js +19 -0
- package/package.json +3 -2
- package/plugin/manni/.claude-plugin/plugin.json +1 -1
- package/shapes/graph/shapes-1.1.0.ttl +785 -0
- package/dist/build-QSPNK5AL.js +0 -35
- package/dist/calibrate-KXN4URPM.js +0 -40
- package/dist/capture-JHTSUPUC.js +0 -37
- package/dist/check-MDGXEUWL.js +0 -20
- package/dist/check-WOQRKNZK.js +0 -29
- package/dist/chunk-2DRHT73U.js.map +0 -1
- package/dist/chunk-2IAZEB45.js.map +0 -1
- package/dist/chunk-2MJ62AYQ.js.map +0 -1
- package/dist/chunk-4VBSTXN6.js +0 -392
- package/dist/chunk-4VBSTXN6.js.map +0 -1
- package/dist/chunk-6HWBRZIN.js.map +0 -1
- package/dist/chunk-BKVHX6FU.js.map +0 -1
- package/dist/chunk-D4N2V43Z.js.map +0 -1
- package/dist/chunk-EFW7CAG4.js +0 -387
- package/dist/chunk-EFW7CAG4.js.map +0 -1
- package/dist/chunk-GV56WGL2.js.map +0 -1
- package/dist/chunk-HQR2ZBHW.js.map +0 -1
- package/dist/chunk-J6IDQGJL.js.map +0 -1
- package/dist/chunk-JPQDNYXH.js.map +0 -1
- package/dist/chunk-JROBZ7JA.js +0 -818
- package/dist/chunk-JROBZ7JA.js.map +0 -1
- package/dist/chunk-PCQ7ELJH.js.map +0 -1
- package/dist/chunk-QCHVR4ZM.js.map +0 -1
- package/dist/chunk-QLWC7MKD.js.map +0 -1
- package/dist/chunk-TU2WUBVZ.js +0 -2
- package/dist/chunk-VSEBFHAY.js.map +0 -1
- package/dist/chunk-Y6DOMTQD.js.map +0 -1
- package/dist/cli-RORDPAPE.js.map +0 -1
- package/dist/github-RY3FRA6P.js +0 -29
- package/dist/github-YKQSH7FS.js +0 -34
- package/dist/lint-YJ2L22BW.js +0 -33
- package/dist/list-NSG24VVX.js +0 -31
- package/dist/list-V6EGCSHX.js +0 -34
- package/dist/run-BUE2DDXN.js +0 -36
- package/dist/term-CQAGNG3Q.js.map +0 -1
- package/dist/validate-ZBW44RHT.js +0 -18
- /package/dist/{batch-UAULUMGH.js.map → batch-KMGZMIAQ.js.map} +0 -0
- /package/dist/{build-QSPNK5AL.js.map → build-NO4AWCYG.js.map} +0 -0
- /package/dist/{calibrate-KXN4URPM.js.map → calibrate-XQUG5XAK.js.map} +0 -0
- /package/dist/{capture-JHTSUPUC.js.map → capture-5XE3ZMXK.js.map} +0 -0
- /package/dist/{check-MDGXEUWL.js.map → check-HTNPLWOV.js.map} +0 -0
- /package/dist/{check-WOQRKNZK.js.map → check-RJ45IBA6.js.map} +0 -0
- /package/dist/{check-YEOYBFST.js.map → check-WFXSXICD.js.map} +0 -0
- /package/dist/{chunk-IGSHYP74.js.map → chunk-23P7KMJM.js.map} +0 -0
- /package/dist/{chunk-HA6MQMOY.js.map → chunk-24J7TYPU.js.map} +0 -0
- /package/dist/{chunk-PCS4VJBV.js.map → chunk-37QJLVS3.js.map} +0 -0
- /package/dist/{chunk-4CK6ZSOK.js.map → chunk-3P6KCU36.js.map} +0 -0
- /package/dist/{chunk-54F74XR3.js.map → chunk-5JBVFWVP.js.map} +0 -0
- /package/dist/{chunk-B7XYNRJQ.js.map → chunk-6KIYVVKL.js.map} +0 -0
- /package/dist/{chunk-GDVZ5VII.js.map → chunk-7A452BPZ.js.map} +0 -0
- /package/dist/{chunk-M42DZCOU.js.map → chunk-7FCLXEAQ.js.map} +0 -0
- /package/dist/{chunk-LBSCW232.js.map → chunk-7NXPQKSB.js.map} +0 -0
- /package/dist/{chunk-OPXWPJDA.js.map → chunk-A3TEDWBZ.js.map} +0 -0
- /package/dist/{chunk-BBT6AVUG.js.map → chunk-BHHXU2D2.js.map} +0 -0
- /package/dist/{chunk-FQXWVS6J.js.map → chunk-CMMBWHT2.js.map} +0 -0
- /package/dist/{chunk-EIFCP6CZ.js.map → chunk-CO47QJKZ.js.map} +0 -0
- /package/dist/{chunk-ADMEPUBT.js.map → chunk-CXWTNT2O.js.map} +0 -0
- /package/dist/{chunk-LKP7QDFN.js.map → chunk-DAQTRAP5.js.map} +0 -0
- /package/dist/{chunk-PWBH7DCQ.js.map → chunk-EESH6COU.js.map} +0 -0
- /package/dist/{chunk-NCPWXHDO.js.map → chunk-JEUFYSFQ.js.map} +0 -0
- /package/dist/{chunk-2IR4T5PO.js.map → chunk-JJ4RSDSY.js.map} +0 -0
- /package/dist/{chunk-WDWXAYVH.js.map → chunk-L3RLGGRK.js.map} +0 -0
- /package/dist/{chunk-T7JAOYE5.js.map → chunk-L75CAZG4.js.map} +0 -0
- /package/dist/{chunk-U7CTFN2J.js.map → chunk-MYRPMSOX.js.map} +0 -0
- /package/dist/{chunk-WQWKYAAK.js.map → chunk-NUQF5F5J.js.map} +0 -0
- /package/dist/{chunk-FUCPZVRY.js.map → chunk-OEN6FBUW.js.map} +0 -0
- /package/dist/{chunk-FBMZJEB3.js.map → chunk-OKMTUAWO.js.map} +0 -0
- /package/dist/{chunk-R4WXQIOZ.js.map → chunk-PCEM5FOZ.js.map} +0 -0
- /package/dist/{chunk-4D5IZSXM.js.map → chunk-PXNCRGUL.js.map} +0 -0
- /package/dist/{chunk-ODRPL3WB.js.map → chunk-Q3ODX5PF.js.map} +0 -0
- /package/dist/{chunk-KGEQJ5CC.js.map → chunk-RRFFUWDL.js.map} +0 -0
- /package/dist/{chunk-NSBXVPZL.js.map → chunk-S3XUYW5K.js.map} +0 -0
- /package/dist/{chunk-LPIF5LIE.js.map → chunk-SAU2WQFC.js.map} +0 -0
- /package/dist/{chunk-MSRU4D4P.js.map → chunk-SFLICIQV.js.map} +0 -0
- /package/dist/{chunk-NGTVREIH.js.map → chunk-U6IHLEWT.js.map} +0 -0
- /package/dist/{chunk-VQK4VHLF.js.map → chunk-YQV75FIL.js.map} +0 -0
- /package/dist/{chunk-TU2WUBVZ.js.map → chunk-YUTVAMWW.js.map} +0 -0
- /package/dist/{chunk-RLT5SEML.js.map → chunk-Z5MZCCTT.js.map} +0 -0
- /package/dist/{cli-JJMOVIKS.js.map → cli-BS43EVCD.js.map} +0 -0
- /package/dist/{cli-ONTC5PKT.js.map → cli-FX53PBQ3.js.map} +0 -0
- /package/dist/{cli-I62SRH3R.js.map → cli-NGL63E53.js.map} +0 -0
- /package/dist/{cli-VUKMWSSF.js.map → cli-WNEI5L7P.js.map} +0 -0
- /package/dist/{config-XDA42RJL.js.map → config-JJW75SBX.js.map} +0 -0
- /package/dist/{github-RY3FRA6P.js.map → github-4UR67MEN.js.map} +0 -0
- /package/dist/{github-YKQSH7FS.js.map → github-UPGKMVVF.js.map} +0 -0
- /package/dist/{host-6GURH5HE.js.map → host-YSTYO5DX.js.map} +0 -0
- /package/dist/{json-BXY44ED7.js.map → json-DQIELR36.js.map} +0 -0
- /package/dist/{lint-YJ2L22BW.js.map → lint-BLBGGOYM.js.map} +0 -0
- /package/dist/{list-NSG24VVX.js.map → list-B3RRRWFQ.js.map} +0 -0
- /package/dist/{list-V6EGCSHX.js.map → list-ZFRT3JQH.js.map} +0 -0
- /package/dist/{page-EV7OUBAU.js.map → page-5ISEVJR5.js.map} +0 -0
- /package/dist/{pretty-Q5Y2PA64.js.map → pretty-Y7CGQLAF.js.map} +0 -0
- /package/dist/{pretty-SCRK66M5.js.map → pretty-ZISH3Q4F.js.map} +0 -0
- /package/dist/{provider-527CO4DM.js.map → provider-LBX4IXGX.js.map} +0 -0
- /package/dist/{release-3D5PK6M7.js.map → release-VV3UTKE5.js.map} +0 -0
- /package/dist/{reporters-A5ADKDYR.js.map → reporters-26H3N2KW.js.map} +0 -0
- /package/dist/{reporters-M6LJ2IYU.js.map → reporters-4STXJJE2.js.map} +0 -0
- /package/dist/{reporters-RBYTASMI.js.map → reporters-74B4U2GZ.js.map} +0 -0
- /package/dist/{run-BUE2DDXN.js.map → run-DVGTKM52.js.map} +0 -0
- /package/dist/{validate-ZBW44RHT.js.map → validate-CJNFQHMD.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cite/core/page.ts","../src/cite/errors.ts","../src/cite/core/hash.ts","../src/cite/core/range.ts","../src/cite/types.ts","../src/cite/core/severity.ts","../src/cite/core/statements.ts"],"sourcesContent":["/**\n * Read a page's citations and its markers.\n *\n * Entries come from the frontmatter (via meta's extractor for the format), or\n * from `CheckPageOptions.citations` when a manifest owns them; either way each\n * one is validated against the registered `manni:citations:1.0.0` schema and\n * carries where it sits.\n * Markers are `cite <id> [<id>…]` comments in the body, resolved to the\n * entries they name, so each citation knows what anchors it. Every id one\n * marker names anchors the same text, so every claim under it pins the same\n * bytes and holds the same hash (proposal 0056).\n *\n * The findings made here are the ones that need no source: `entry-invalid`,\n * `marker-invalid`, `marker-orphan`, `marker-repeated` and `anchor-invalid`.\n */\nimport { extname } from \"node:path\";\nimport * as Ajv2020Ns from \"ajv/dist/2020.js\";\nimport type { ValidateFunction } from \"ajv/dist/2020.js\";\nimport citationsSchema from \"../../meta/schemas/citations/1.0.0.json\" with { type: \"json\" };\nimport {\n extractorForExtension,\n locateFrontmatter,\n supportedExtensions,\n type ExtractedMetadata,\n type MetadataExtractor,\n} from \"../../meta/index.js\";\nimport { extractorByName, hasFrontmatterFence } from \"../../meta/internal.js\";\nimport { isEncryptedValue } from \"../../shared/encryption.js\";\nimport { CiteError } from \"../errors.js\";\nimport type {\n Citation,\n CitationFinding,\n CitationInput,\n CitationOrigin,\n CiteRule,\n PageCitation,\n PageCitations,\n} from \"../types.js\";\nimport { isKeyedPin } from \"./hash.js\";\nimport { parseLines, spellSource, tooWide } from \"./range.js\";\nimport { DEFAULT_SEVERITY, ruleId } from \"./severity.js\";\nimport { lineAt, parseStatements } from \"./statements.js\";\n\nexport { MAX_IDS_PER_MARKER } from \"./statements.js\";\n\nexport const MAX_MARKERS_PER_PAGE = 500;\n\n/** Formats whose leading fenced block is frontmatter meta would read. */\nconst ELEMENT_BACKED = new Set([\"html\", \"xml\"]);\n/** Formats whose only metadata channel is a fenced block. */\nconst FENCE_ONLY = new Set([\"markdown\", \"mdx\"]);\n\n/** What a marker with a JSON payload is told, since an entry never lives in the body. */\nexport const MARKER_JSON =\n \"A marker names an entry by id. Write the entry in frontmatter or the sidecar.\";\n\nexport interface ReadPageOptions {\n /** Extractor name; derived from the file's extension when absent. */\n format?: string;\n /**\n * The page's citations as a manifest owns them, already merged. Given, the\n * page's own frontmatter `citations` is not read.\n */\n citations?: readonly CitationInput[];\n /**\n * The manifest that owns this page's `citations`, when one does. A page\n * that also carries its own is `entry-invalid`: neither channel wins, and\n * the discarded value would be exactly the one nobody checked.\n */\n owned?: { file: string; collection: string };\n /**\n * `false` reads the entries alone, for a caller that only counts them: no\n * marker is parsed, so no marker finding is raised and none is anchored.\n */\n markers?: boolean;\n}\n\n/** Meta's `external:owned` sentence, said by `cite check` under `entry-invalid`. */\nexport function ownedMessage(owner: { file: string; collection: string }): string {\n return `\"citations\" is owned by manifest ${owner.file} (collection ${owner.collection}); remove it from the document`;\n}\n\n// ajv is CommonJS with a default export; under NodeNext the constructor lives\n// on `.default`, as src/meta/core/validator.ts notes.\ntype AjvCtor = typeof import(\"ajv/dist/2020.js\").default;\nconst Ajv2020 = Ajv2020Ns.default as unknown as AjvCtor;\n\nlet entryValidator: ValidateFunction | undefined;\n\nfunction compileEntryValidator(): ValidateFunction {\n if (entryValidator) return entryValidator;\n const ajv = new Ajv2020({ allErrors: false, strict: false });\n ajv.addSchema(citationsSchema);\n entryValidator = ajv.compile({\n $ref: `${citationsSchema.$id}#/$defs/citationEntry`,\n });\n return entryValidator;\n}\n\n/** Ajv validation of one entry object against `$defs.citationEntry`; the first error text, or undefined. */\nexport function validateEntry(entry: unknown): string | undefined {\n const validate = compileEntryValidator();\n if (validate(entry)) return undefined;\n const first = validate.errors?.[0];\n if (!first) return \"invalid entry\";\n return `${first.instancePath} ${first.message ?? \"is invalid\"}`.trim();\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Copy a validated entry into the `Citation` shape, field by field. */\nfunction toCitation(entry: Record<string, unknown>): Citation {\n const source = isRecord(entry.source) ? entry.source : {};\n const citation: Citation = {\n source: {\n file: String(source.file),\n integrity: String(source.integrity),\n },\n };\n if (typeof source.lines === \"number\" || typeof source.lines === \"string\") {\n citation.source.lines = source.lines;\n }\n if (typeof source[\"commit-sha\"] === \"string\") {\n citation.source[\"commit-sha\"] = source[\"commit-sha\"];\n }\n if (typeof entry.id === \"string\") citation.id = entry.id;\n if (isRecord(entry.claim)) {\n const claim = entry.claim;\n citation.claim = { integrity: String(claim.integrity) };\n if (typeof claim.lines === \"number\" || typeof claim.lines === \"string\") {\n citation.claim.lines = claim.lines;\n }\n }\n if (typeof entry.quote === \"boolean\") citation.quote = entry.quote;\n return citation;\n}\n\ntype FindingExtra = Pick<CitationFinding, \"line\" | \"id\" | \"src\" | \"index\" | \"file\">;\n\nfunction finding(rule: CiteRule, message: string, extra: FindingExtra): CitationFinding {\n const level = DEFAULT_SEVERITY[rule];\n const out: CitationFinding = {\n rule,\n ruleId: ruleId(rule),\n // The caller re-applies the configured severity table; no page-side rule\n // defaults to `off`, so there is nothing to collapse.\n severity: level === \"off\" ? \"error\" : level,\n message,\n };\n if (extra.line !== undefined) out.line = extra.line;\n if (extra.file !== undefined) out.file = extra.file;\n if (extra.id !== undefined) out.id = extra.id;\n if (extra.src !== undefined) out.src = extra.src;\n if (extra.index !== undefined) out.index = extra.index;\n return out;\n}\n\n/** `<id>: <text>`, or the text alone for an entry with no id. */\nfunction named(id: string | undefined, text: string): string {\n return id === undefined ? text : `${id}: ${text}`;\n}\n\n/** The `id`/`src` a finding carries for an entry that may not have validated. */\nfunction subjectOf(entry: unknown): Pick<FindingExtra, \"id\" | \"src\"> {\n const out: Pick<FindingExtra, \"id\" | \"src\"> = {};\n if (!isRecord(entry)) return out;\n if (typeof entry.id === \"string\") out.id = entry.id;\n if (isRecord(entry.source) && typeof entry.source.file === \"string\") out.src = entry.source.file;\n return out;\n}\n\nexport function pickExtractor(file: string, format: string | undefined): MetadataExtractor {\n if (format !== undefined) {\n const forced = extractorByName(format);\n if (forced === undefined) {\n throw new CiteError(\n `Unknown format \"${format}\". Supported extensions: ${supportedExtensions().join(\", \")}.`,\n );\n }\n return forced;\n }\n const extension = extname(file);\n const byExt = extractorForExtension(extension);\n if (!byExt) {\n throw new CiteError(\n `Unsupported file type \"${extension}\" for \"${file}\". Supported: ${supportedExtensions().join(\", \")}. Use --as to override.`,\n );\n }\n return byExt;\n}\n\nfunction byLine(a: CitationFinding, b: CitationFinding): number {\n return (a.line ?? Number.MAX_SAFE_INTEGER) - (b.line ?? Number.MAX_SAFE_INTEGER);\n}\n\n/** File line of body line 1: the first line after the frontmatter, or 1. */\nexport function bodyLineOf(content: string, bodyOffset: number): number {\n if (bodyOffset <= 0) return 1;\n const line = lineAt(content, bodyOffset);\n // A page that ends at the closing fence with no newline leaves the body\n // starting on the line after the one the offset sits at the end of.\n return content.charCodeAt(bodyOffset - 1) === 10 ? line : line + 1;\n}\n\n/**\n * A page's citation entries as they are written, each with where it sits: the\n * manifest's when one owns them, else the page's own frontmatter. The entries\n * are raw, so one the schema refuses is still in the list at its own index,\n * which is how `remove` reaches an entry `check` can only report.\n */\nexport function citationInputs(\n file: string,\n extracted: ExtractedMetadata,\n injected?: readonly CitationInput[],\n): CitationInput[] {\n if (injected !== undefined) return [...injected];\n const raw: unknown = extracted.data.citations;\n const list: unknown[] = Array.isArray(raw) ? (raw as unknown[]) : [];\n return list.map((entry, index) => {\n const origin: CitationInput[\"origin\"] = { kind: \"frontmatter\", file };\n const line = extracted.lineFor(`/citations/${String(index)}`);\n if (line !== undefined) origin.line = line;\n return { entry, origin };\n });\n}\n\nexport function readPage(\n file: string,\n content: string,\n opts?: ReadPageOptions,\n): PageCitations {\n const extractor = pickExtractor(file, opts?.format);\n const format = extractor.name;\n // An opening fence with no close is not a block, so the extractor reads no\n // metadata and every entry the page carries would silently vanish. rst and\n // asciidoc read a stray fence as body on purpose, ahead of their native\n // headers; the fence-only formats have no such reading.\n if (FENCE_ONLY.has(format) && hasFrontmatterFence(content) && locateFrontmatter(content) === null) {\n throw new CiteError(\n `${file}: Unterminated front matter fence: the opening fence has no matching close, so the page's citations cannot be read. Add a closing fence.`,\n );\n }\n const extracted = extractor.extract(content, file);\n const data = extracted.data;\n const bodyOffset = ELEMENT_BACKED.has(format)\n ? 0\n : (locateFrontmatter(content)?.closeEnd ?? 0);\n const bodyLine = bodyLineOf(content, bodyOffset);\n\n const findings: CitationFinding[] = [];\n const citations: PageCitation[] = [];\n /** Entries by id, for marker resolution. */\n const byId = new Map<string, PageCitation>();\n /** Every id seen, for uniqueness. */\n const ids = new Set<string>();\n /** The first marker naming an entry, by entry. */\n const markedAt = new Map<PageCitation, number>();\n\n // The page root reserves the whole `citation-` prefix, so a typo fails\n // loudly rather than passing as a key of somebody else's.\n for (const key of Object.keys(data)) {\n if (!key.startsWith(\"citation-\")) continue;\n findings.push(\n finding(\n \"entry-invalid\",\n `\"${key}\" is not a citations key; the page root reserves the citation- prefix`,\n { line: extracted.lineFor(`/${key}`) },\n ),\n );\n }\n\n const injected = opts?.citations;\n const rawCitations = data.citations;\n // The page carries a key a manifest owns (0020 across files). The page's\n // own entries are still read and checked, so the report shows what the\n // page would publish; the finding says the manifest is the one authority.\n if (opts?.owned !== undefined && rawCitations !== undefined) {\n findings.push(\n finding(\"entry-invalid\", ownedMessage(opts.owned), {\n line: extracted.lineFor(\"/citations\"),\n }),\n );\n }\n if (injected === undefined && rawCitations !== undefined && !Array.isArray(rawCitations)) {\n findings.push(\n finding(\"entry-invalid\", \"citations must be an array of entries\", {\n line: extracted.lineFor(\"/citations\"),\n }),\n );\n }\n const inputs = citationInputs(file, extracted, injected);\n\n inputs.forEach((input, index) => {\n const origin: CitationOrigin = { ...input.origin, index };\n /** Where a finding about the entry itself sits: the entry's own line, in its own file. */\n const where: FindingExtra = { index };\n if (origin.line !== undefined) where.line = origin.line;\n if (origin.kind === \"manifest\") where.file = origin.file;\n\n const entry = input.entry;\n const error = validateEntry(entry);\n if (error !== undefined || !isRecord(entry)) {\n findings.push(finding(\"entry-invalid\", error ?? \"must be object\", { ...where, ...subjectOf(entry) }));\n return;\n }\n const citation = toCitation(entry);\n const subject: FindingExtra = { ...where, src: spellSource(citation.source) };\n if (citation.id !== undefined) subject.id = citation.id;\n\n // A range that ends before it starts: the schema cannot compare two\n // numbers, so the rule is kept here.\n const bad = ([\"claim\", \"source\"] as const).find((end) => {\n const lines = end === \"claim\" ? citation.claim?.lines : citation.source.lines;\n return lines !== undefined && parseLines(lines) === undefined;\n });\n if (bad !== undefined) {\n const lines = bad === \"claim\" ? citation.claim?.lines : citation.source.lines;\n findings.push(\n finding(\n \"entry-invalid\",\n named(citation.id, `${bad}.lines \"${String(lines)}\" ends before it starts`),\n subject,\n ),\n );\n return;\n }\n\n // A range wider than the cap is refused at either end (proposal 0044).\n const wide = ([\"claim\", \"source\"] as const)\n .map((end) => {\n const lines = end === \"claim\" ? citation.claim?.lines : citation.source.lines;\n const parsed = lines === undefined ? undefined : parseLines(lines);\n const reason = parsed === undefined ? undefined : tooWide(parsed);\n return reason === undefined ? undefined : `${end}.lines \"${String(lines)}\" ${reason}`;\n })\n .find((message) => message !== undefined);\n if (wide !== undefined) {\n findings.push(finding(\"entry-invalid\", named(citation.id, wide), subject));\n return;\n }\n\n // The pin's prefix says how it was taken, and only an encrypted source\n // carries a keyed one.\n const encrypted = isEncryptedValue(citation.source.file);\n if (encrypted !== isKeyedPin(citation.source.integrity)) {\n findings.push(\n finding(\n \"entry-invalid\",\n named(\n citation.id,\n encrypted\n ? \"an encrypted source is pinned with hmac-sha256-, not sha256-.\"\n : \"a plain source is pinned with sha256-, not hmac-sha256-.\",\n ),\n subject,\n ),\n );\n return;\n }\n\n const page: PageCitation = { citation, origin };\n citations.push(page);\n if (citation.id !== undefined) {\n if (ids.has(citation.id)) {\n findings.push(finding(\"entry-invalid\", `duplicate id \"${citation.id}\"`, subject));\n } else {\n ids.add(citation.id);\n byId.set(citation.id, page);\n }\n }\n });\n\n // Markers.\n const body = content.slice(bodyOffset);\n const statements =\n opts?.markers === false ? [] : parseStatements(body, format, { offset: bodyOffset, line: bodyLine });\n if (statements.length > MAX_MARKERS_PER_PAGE) {\n findings.push(\n finding(\n \"marker-invalid\",\n `more than ${String(MAX_MARKERS_PER_PAGE)} markers on one page (${String(statements.length)}); the rest are not read`,\n { line: statements[MAX_MARKERS_PER_PAGE]?.line },\n ),\n );\n }\n\n for (const statement of statements.slice(0, MAX_MARKERS_PER_PAGE)) {\n const { payload } = statement;\n if (payload.kind === \"bad\") {\n findings.push(\n finding(\n \"marker-invalid\",\n payload.json === true ? MARKER_JSON : `invalid marker: ${payload.reason}`,\n { line: statement.line },\n ),\n );\n continue;\n }\n // Each named id reports on its own: an orphan is a well-formed id with a\n // missing entry, so each one is its own subject and its own fix. A\n // malformed marker is one line and one fix, and was handled above.\n // The entries this marker actually anchors. An id the marker repeats is\n // reported and left to the marker that won it, so it never lands here.\n const anchored: PageCitation[] = [];\n for (const id of payload.ids) {\n const target = byId.get(id);\n if (target === undefined) {\n findings.push(\n finding(\"marker-orphan\", `no entry has id \"${id}\"`, {\n line: statement.line,\n id,\n }),\n );\n continue;\n }\n const first = markedAt.get(target);\n if (first !== undefined) {\n findings.push(\n finding(\n \"marker-repeated\",\n `${id} is named by markers at lines ${String(first)} and ${String(statement.line)}; the first anchors it.`,\n {\n line: statement.line,\n id,\n index: target.origin.index,\n src: spellSource(target.citation.source),\n },\n ),\n );\n continue;\n }\n markedAt.set(target, statement.line);\n target.marker = statement;\n anchored.push(target);\n }\n // A quote entry anchors the next fenced block and a plain one anchors the\n // paragraph, so a list that mixes them would mean two spans on one line,\n // and the page would not say which id got which.\n const quoted = anchored.find((entry) => entry.citation.quote === true);\n const plain = anchored.find((entry) => entry.citation.quote !== true);\n if (quoted !== undefined && plain !== undefined) {\n const mixed: FindingExtra = {\n line: statement.line,\n index: quoted.origin.index,\n src: spellSource(quoted.citation.source),\n };\n if (quoted.citation.id !== undefined) mixed.id = quoted.citation.id;\n findings.push(\n finding(\n \"anchor-invalid\",\n named(\n quoted.citation.id,\n \"a quote entry shares a marker with a non-quote entry. Give the quote its own marker.\",\n ),\n mixed,\n ),\n );\n }\n }\n\n // Anchors: exactly one way in, and a quote needs one of them.\n for (const entry of citations) {\n const { citation, origin, marker } = entry;\n const subject: FindingExtra = { index: origin.index, src: spellSource(citation.source) };\n if (citation.id !== undefined) subject.id = citation.id;\n if (origin.line !== undefined) subject.line = origin.line;\n if (origin.kind === \"manifest\") subject.file = origin.file;\n\n if (citation.claim?.lines !== undefined && marker !== undefined) {\n // A marker names an id, so an entry with both always has one. The\n // finding sits on the marker, which is the half a reader can see.\n const onPage: FindingExtra = { index: origin.index, line: marker.line, src: subject.src };\n if (citation.id !== undefined) onPage.id = citation.id;\n findings.push(\n finding(\n \"anchor-invalid\",\n `${citation.id ?? \"the entry\"} has claim lines and a marker. Keep one.`,\n onPage,\n ),\n );\n continue;\n }\n if (citation.quote === true && citation.claim?.lines === undefined && marker === undefined) {\n findings.push(\n finding(\"anchor-invalid\", named(citation.id, \"quote needs a claim or a marker.\"), subject),\n );\n }\n }\n\n return {\n file,\n format,\n content,\n bodyOffset,\n bodyLine,\n citations,\n statements,\n findings: findings.sort(byLine),\n };\n}\n","import { ToolError } from \"../shared/errors.js\";\nimport { LineRangeError } from \"../shared/pin.js\";\n\n/** An operational or usage error in the citation tool: exit 2. */\nexport class CiteError extends ToolError {\n constructor(message: string) {\n super(message);\n this.name = \"CiteError\";\n }\n}\n\n/**\n * Run a shared pin-engine call under cite's error contract: a\n * `LineRangeError` comes back as a `CiteError` with the same message, which is\n * what cite's callers (and library users of the `cite` namespace) match on.\n */\nexport function asCiteError<T>(call: () => T): T {\n try {\n return call();\n } catch (error) {\n if (error instanceof LineRangeError) throw new CiteError(error.message);\n throw error;\n }\n}\n","/**\n * The hashing rule, as cite uses it. The engine is `src/shared/pin.ts`, shared\n * with meta; this module keeps cite's import path and its error contract: a\n * range past the end of the text throws `CiteError`.\n */\nimport {\n hashRange as sharedHashRange,\n sliceLines as sharedSliceLines,\n type LineRange,\n} from \"../../shared/pin.js\";\nimport { asCiteError } from \"../errors.js\";\n\nexport { hashLines, isKeyedPin, normalizeText, splitLines } from \"../../shared/pin.js\";\n\n/**\n * The cited lines joined by LF. Throws `CiteError` when the range runs past\n * the end of the text (`<path> has N lines; line M is out of range`).\n */\nexport function sliceLines(lines: readonly string[], range?: LineRange, label?: string): string {\n return asCiteError(() => sharedSliceLines(lines, range, label));\n}\n\n/** The pin of the cited lines: plain, or the keyed pin under `key` when one is given. Throws `CiteError` past the end. */\nexport function hashRange(text: string, range?: LineRange, key?: string): string {\n return asCiteError(() => sharedHashRange(text, range, key));\n}\n","/**\n * Source and line grammar.\n *\n * An entry's `source.file` is a path or an encrypted source (`~` and at least\n * 82 base64url characters, proposal 0045), and its `lines` an integer or\n * `\"L1-L2\"`. `FILE_PATTERN` is the schema's `$defs.fileRef.pattern`, spelled\n * once here and once in the registered `manni:citations:1.0.0` schema\n * (`src/meta/schemas/citations/1.0.0.json`); `test/cite/range.test.ts` pins\n * the two equal.\n *\n * The command line spells a source as one string, `path`, `path:L` or\n * `path:L1-L2` (`SRC_PATTERN`), and reports spell it that way too.\n */\nimport { isEncryptedValue } from \"../../shared/encryption.js\";\nimport { MAX_RANGE_LINES, lineSpec, parseLines } from \"../../shared/pin.js\";\nimport { CiteError } from \"../errors.js\";\nimport type { CitationSource, LineSpec, PageLines, SourceRange } from \"../types.js\";\n\n// The line grammar is the shared pin engine's; this path keeps cite's imports working.\nexport { lineSpec, parseLines, spellLines } from \"../../shared/pin.js\";\n\n/** The schema's `$defs.fileRef.pattern`, as a RegExp. */\nexport const FILE_PATTERN =\n /^(?:~[A-Za-z0-9_-]{82,}|(?!~)(?:(?!\\.\\.?(?:\\/|$))[^/\\\\:\\r\\n\\t]+)(?:\\/(?:(?!\\.\\.?(?:\\/|$))[^/\\\\:\\r\\n\\t]+))*)$/;\n\n/** A source on the command line: a `file`, then an optional `:L` or `:L1-L2`. */\nexport const SRC_PATTERN =\n /^(?:~[A-Za-z0-9_-]{82,}|(?!~)(?:(?!\\.\\.?(?:\\/|:|$))[^/\\\\:\\r\\n\\t]+)(?:\\/(?:(?!\\.\\.?(?:\\/|:|$))[^/\\\\:\\r\\n\\t]+))*)(?::[1-9][0-9]*(?:-[1-9][0-9]*)?)?$/;\n\n/**\n * Parse a command-line source. Throws `CiteError` on bad grammar or an end\n * line before the start. A one-line `path:L` comes back with `end` equal to\n * `start`, so a range's width is always `end - start + 1`.\n */\nexport function parseSrc(src: string): SourceRange {\n if (!SRC_PATTERN.test(src)) {\n throw new CiteError(\n `Invalid src \"${src}\": expected path, path:L, path:L1-L2, or an encrypted source (~ and at least 82 base64url characters) with the same line forms; ` +\n \"the path is repo-root-relative, posix, and free of . and .. segments.\",\n );\n }\n // The grammar keeps `:` out of every path segment and out of the base64url\n // alphabet, so the first one starts the line suffix.\n const colon = src.indexOf(\":\");\n const path = colon === -1 ? src : src.slice(0, colon);\n const range: SourceRange = { path, encrypted: isEncryptedValue(path) };\n if (colon === -1) return range;\n const [startText, endText] = src.slice(colon + 1).split(\"-\");\n const start = Number(startText);\n const end = endText === undefined ? start : Number(endText);\n if (end < start) {\n throw new CiteError(`Invalid range \"${src}\": end line ${end} is before start line ${start}.`);\n }\n range.start = start;\n range.end = end;\n return range;\n}\n\n/**\n * How a range too wide to cite is refused: `spans N lines, more than 5000`.\n * Undefined for a range of at most `MAX_RANGE_LINES` lines.\n */\nexport function tooWide(lines: PageLines): string | undefined {\n const width = lines.end - lines.start + 1;\n return width > MAX_RANGE_LINES\n ? `spans ${String(width)} lines, more than ${String(MAX_RANGE_LINES)}`\n : undefined;\n}\n\n/** The canonical spelling: `path:L` for one line, `path:L1-L2` otherwise. */\nexport function formatSrc(range: SourceRange): string {\n const { path, start, end } = range;\n if (start === undefined) return path;\n if (end === undefined || end === start) return `${path}:${start}`;\n return `${path}:${start}-${end}`;\n}\n\n/** An entry's source as a range. Its lines must already be valid (see `parseLines`). */\nexport function sourceRange(source: CitationSource): SourceRange {\n const range: SourceRange = { path: source.file, encrypted: isEncryptedValue(source.file) };\n if (source.lines === undefined) return range;\n const lines = parseLines(source.lines);\n if (lines !== undefined) {\n range.start = lines.start;\n range.end = lines.end;\n }\n return range;\n}\n\n/** An entry's source spelled as one string, `path:L1-L2`, the way the command line takes it. */\nexport function spellSource(source: CitationSource): string {\n return formatSrc(sourceRange(source));\n}\n\n/** A range's lines as an entry writes them; undefined for a whole file. */\nexport function rangeLines(range: SourceRange): LineSpec | undefined {\n if (range.start === undefined) return undefined;\n return lineSpec({ start: range.start, end: range.end ?? range.start });\n}\n","/**\n * The citation tool's types. Proposal 0044 is the record, and stress test 25\n * the shape: an entry has two ends, `claim` and `source`, each a line range\n * and a hash, and a check classifies both with one set of rules.\n *\n * Vocabulary: a page's `citations` live in its frontmatter, or in a manifest\n * a collection declares (handed in through `CheckPageOptions.citations`). A\n * body marker, `cite <id>` in the format's comment syntax, is the other way\n * to anchor an entry's claim.\n */\nimport type { ValidationResult } from \"../meta/index.js\";\nimport type { CollectionConfig } from \"../shared/collections.js\";\nimport type { ConfigFile } from \"../shared/config-file.js\";\nimport type { KeySource } from \"../shared/encryption-key.js\";\nimport type { LineSpec, PageLines } from \"../shared/pin.js\";\nimport type { Confirm } from \"../shared/prompt.js\";\nimport type { Severity } from \"../shared/severity.js\";\n\n/** A parsed `<src>` or `source`: a path or `~` ciphertext, and an optional line range. */\nexport interface SourceRange {\n /** Repo-root-relative posix path, or the `~` ciphertext when `encrypted`. */\n path: string;\n /** `true` when `path` is an encrypted source (`~` and 82+ base64url characters) rather than a path. */\n encrypted: boolean;\n /** 1-based, inclusive. Both absent for a whole-file citation. */\n start?: number;\n end?: number;\n}\n\n// The line shapes are the shared pin engine's, re-exported so cite's imports and its namespace keep them.\nexport type { LineSpec, PageLines };\n\n/** The page text a citation supports. */\nexport interface CitationClaim {\n /** Body lines: counted from the first line after the frontmatter. Absent when a marker anchors the entry. */\n lines?: LineSpec;\n /** `sha256-<64 hex>` over the claimed lines, always plain. */\n integrity: string;\n}\n\n/** The lines a claim rests on. */\nexport interface CitationSource {\n /** Root-relative posix path, or `~` and the path encrypted with the family key. */\n file: string;\n /** File lines. Absent pins the whole file. */\n lines?: LineSpec;\n /** `sha256-<64 hex>`, or `hmac-sha256-<64 hex>` exactly when `file` is encrypted. */\n integrity: string;\n /** The git commit hash the pin was taken at: 7 to 64 lowercase hex. */\n \"commit-sha\"?: string;\n}\n\n/** One entry of `citations`, as the schema (`manni:citations:1.0.0`) spells it. */\nexport interface Citation {\n id?: string;\n claim?: CitationClaim;\n source: CitationSource;\n quote?: boolean;\n}\n\n/** Where an entry is kept: the page's frontmatter, or a manifest a collection declares. */\nexport type OriginKind = \"frontmatter\" | \"manifest\";\n\n/** Where an entry sits. */\nexport interface CitationOrigin {\n kind: OriginKind;\n /** The file the entry sits in: the page for frontmatter, the manifest otherwise. */\n file: string;\n /** Line of the entry in that file. */\n line?: number;\n /** Index in the page's `citations` list. */\n index: number;\n}\n\n/**\n * One entry handed to `checkCitations` from outside the page, already merged\n * from a manifest. `entry` is the raw value and is validated as a frontmatter\n * entry is; its index is its position in the list.\n */\nexport interface CitationInput {\n entry: unknown;\n origin: { kind: OriginKind; file: string; line?: number };\n}\n\n/** How a citation's claim is anchored: by its lines, by a body marker, or not at all. */\nexport type CitationAnchor = \"claim\" | \"marker\" | null;\n\nexport type SourceStatus =\n | \"current\"\n | \"moved\"\n | \"moved-ambiguous\"\n | \"changed\"\n | \"missing\"\n | \"never-true\"\n | \"skipped\";\n\nexport type ClaimStatus =\n | \"current\"\n | \"moved\"\n | \"moved-ambiguous\"\n | \"reanchored\"\n | \"changed\"\n | \"skipped\";\n\n/** Where a misplaced marker is, and where it belongs (proposal 0054). */\nexport interface MisplacedMarkerReport {\n /** The paragraph the marker's run splits, in file lines. */\n unit: string;\n /** The file line the marker takes once its run has moved. */\n to: number;\n}\n\n/** The marker that anchors a citation: its line, and its misplacement when it has one. */\nexport interface MarkerEnd {\n /** File line of the marker. */\n line: number;\n /** Present only when the marker line sits inside a paragraph. */\n misplaced?: MisplacedMarkerReport;\n}\n\n/**\n * Why a source could not be read: not a tracked file under the root, an\n * encrypted source with no key to decrypt it, one that does not decrypt under\n * the key there is, or a tracked file that could not be read.\n */\nexport type MissingReason = \"untracked\" | \"no-key\" | \"undecryptable\" | \"unreadable\";\n\n/**\n * Every rule a finding can carry, stated once. The union and the default\n * severity table are derived from this array so the three cannot disagree.\n */\nexport const CITE_RULES = [\n \"source-moved\",\n \"source-moved-ambiguous\",\n \"source-changed\",\n \"source-never-true\",\n \"source-missing\",\n \"claim-moved\",\n \"claim-moved-ambiguous\",\n \"claim-reanchored\",\n \"claim-changed\",\n \"marker-orphan\",\n \"marker-invalid\",\n \"marker-repeated\",\n \"marker-misplaced\",\n \"anchor-invalid\",\n \"entry-invalid\",\n \"quote-drift\",\n] as const;\nexport type CiteRule = (typeof CITE_RULES)[number];\n\n/**\n * A rule's level: the family scale (`src/shared/severity.ts`) plus `off`,\n * which drops the rule's findings altogether.\n */\nexport type CiteSeverity = Severity | \"off\";\n\n/** The claim end of a classified citation. Line specs are `\"L\"` or `\"L1-L2\"`. */\nexport interface ClaimEnd {\n /** The entry's body lines. Absent when a marker anchors the claim. */\n lines?: string;\n /** Where the claim is judged, in file lines: the recorded lines, or what the marker anchors. */\n fileLines?: string;\n status: ClaimStatus;\n /** For `moved`: the new body lines. */\n newLines?: string;\n /** For `moved-ambiguous`: every candidate, in body lines. */\n candidates?: string[];\n /** For `moved`: the new file lines. Pretty-only. */\n newFileLines?: string;\n /** For `moved-ambiguous`: every candidate, in file lines. Pretty-only. */\n candidateFileLines?: string[];\n /** For `changed`: the page lines at the claim now, for `--show-diff`. Pretty-only. */\n text?: string[];\n /**\n * For `changed` and `reanchored`: the baseline, the newest commit whose\n * page held the pin. Derived from the page's history, never stored (0053).\n */\n commitSha?: string;\n /** For `changed`: `false` when the walk hit a shallow boundary or its cap. */\n historyAvailable?: boolean;\n /** Subjects of the commits that touched the page since the baseline. Pretty-only. */\n commitsSince?: string[];\n /** Unified diff of the claim's lines since the baseline. Pretty-only, `--show-diff`. */\n diff?: string;\n /**\n * The claim's lines as they were at the baseline. Never serialized: it is\n * what `update --accept` reads to tell an edit from a replacement.\n */\n baselineText?: string[];\n}\n\n/** The source end of a classified citation. */\nexport interface SourceEnd {\n /** `file` and `lines` as the entry spells them: `path`, `path:L`, `path:L1-L2`, the ciphertext for `path`. */\n src: string;\n status: SourceStatus;\n /**\n * The path an encrypted source decrypted to. Never printed except by the\n * pretty reporter under `--reveal`; never serialized to a machine format.\n */\n resolvedPath?: string;\n /**\n * The path a moved encrypted source's `newSrc` decrypts to. Printed only by\n * the pretty reporter under `--reveal`; never serialized to a machine format.\n */\n resolvedNewPath?: string;\n /** For `missing`: why the source could not be read. Composes the message, which never names a path. */\n missingReason?: MissingReason;\n /**\n * For `moved`: the new file lines. For `changed`: the span the pinned\n * range's old first and last lines now cover, when each sits exactly once\n * and in order (proposal 0055).\n */\n newLines?: string;\n /**\n * For `moved`: the new `src`, spelled as the entry spells sources. It names\n * another file when the pin followed its text across one, and carries the\n * new path encrypted for an encrypted source.\n */\n newSrc?: string;\n /** For `moved-ambiguous`: every candidate `src`. */\n candidates?: string[];\n /** The commit the pin was taken at. */\n commitSha?: string;\n /** For `changed` with a commit: whether git could show that commit. */\n historyAvailable?: boolean;\n /** Subjects of commits touching the path since the commit. Pretty-only. */\n commitsSince?: string[];\n /** Unified diff of the path since the commit. Pretty-only, `--show-diff`. */\n diff?: string;\n /** The move search hit its byte budget before covering the file. */\n truncatedSearch?: boolean;\n /**\n * The truncation happened across the files a commit touched rather than\n * inside one file, which is the other half of the notice a run raises. Never\n * serialized: `truncatedSearch` stays the one boolean the output carries.\n */\n truncatedAcross?: boolean;\n}\n\n/** One classified citation: both ends, and how it is anchored. */\nexport interface CitationResult {\n citation: Citation;\n origin: CitationOrigin;\n anchor: CitationAnchor;\n /** File line of the marker that names the entry, when one does. */\n markerLine?: number;\n /**\n * File line the citation anchors to now: the claim's first line, or the\n * first line of the text a marker anchors. Where its findings sit.\n */\n anchorLine?: number;\n /** The marker that names the entry, when one does, and its misplacement. */\n marker?: MarkerEnd;\n /** `null` for an entry with no `claim`: a bare pin, or a marker with no drift check. */\n claim: ClaimEnd | null;\n source: SourceEnd;\n}\n\nexport interface CitationFinding {\n rule: CiteRule;\n /** `manni:cite/<rule>`. */\n ruleId: string;\n /** On the family scale: an `error` fails the file; a `warning` or a `notice` is reported and never does. */\n severity: Severity;\n message: string;\n /** Line the finding anchors to: in the page, or in `file` when that is set. */\n line?: number;\n /** The manifest, when the finding sits there rather than on the page. */\n file?: string;\n id?: string;\n /** The source, spelled as the entry spells it. */\n src?: string;\n newSrc?: string;\n /** Index in the page's `citations` for a finding about one entry. */\n index?: number;\n}\n\nexport interface PageCitationReport {\n file: string;\n format: string;\n citations: CitationResult[];\n findings: CitationFinding[];\n /** Run-level advice, said once per run by the caller: shallow clone, root fallback. */\n notices: string[];\n}\n\n/** What `showFile` answers: the bytes, or why it could not. */\nexport type ShownFile = { text: string } | { missing: \"commit\" | \"path\" };\n\n/** One commit that touched a path. */\nexport interface PageCommit {\n /** The full hash. */\n sha: string;\n subject: string;\n}\n\n/**\n * What `pageCommits` answers: the commits that touched a path, newest first.\n * `shallow` is a repository whose oldest commit may have an unfetched parent,\n * and `truncated` a list the cap cut short. Either means a walk that exhausts\n * the list cannot say the pin never held.\n */\nexport interface PageHistory {\n commits: PageCommit[];\n shallow: boolean;\n truncated: boolean;\n}\n\n/**\n * A thin git client over `execFile(\"git\", …)`, memoized per call shape so a\n * page with a thousand citations against one path runs one `git show`.\n */\nexport interface GitClient {\n available(): Promise<boolean>;\n head(): Promise<string | null>;\n /** Tracked files, posix, relative to the root. */\n lsFiles(): Promise<string[]>;\n showFile(commit: string, path: string): Promise<ShownFile>;\n subjectsSince(commit: string, path: string): Promise<string[]>;\n diffSince(commit: string, path: string): Promise<string>;\n /**\n * Paths whose content differs between `commit` and `HEAD`: committed\n * changes only. The candidate list a pin is followed across (proposal\n * 0055). A client without it follows no source out of its own file.\n */\n changedSince?(commit: string): Promise<string[]>;\n /**\n * The commits that touched `path`, newest first, at most `cap` of them. A\n * client without it reads no claim history, so a claim that no longer holds\n * is `changed` with no baseline.\n */\n pageCommits?(path: string, cap: number): Promise<PageHistory>;\n /**\n * Whether the clone is shallow, so a commit it lacks may still be in the\n * history. A client without it, or without `isAncestor`, never re-records\n * a commit for being outside the history.\n */\n shallow?(): Promise<boolean>;\n /**\n * Whether `commit` is `of` or one of its ancestors. A commit the clone does\n * not have is not.\n */\n isAncestor?(commit: string, of: string): Promise<boolean>;\n}\n\n/**\n * The files a source may resolve to. Built from `git ls-files` when git is\n * available, else from a walk that follows no symlinks. Every entry is\n * realpath-contained in the root.\n */\nexport interface SourceIndex {\n files(): readonly string[];\n /** Whether `path` is a tracked file under the root. An encrypted source is decrypted first, then asked here. */\n has(path: string): boolean;\n}\n\n/**\n * What one run has read so far: each tracked file once, with its lines split\n * once. A run makes one with `createSourceCache` and passes it to every\n * `readSource` it makes, so a source cited a hundred times is read once. It\n * holds whatever the files said when first read, so a run that rewrites files\n * it may later read as sources makes none.\n */\nexport type SourceCache = Map<string, Promise<{ text: string; lines: readonly string[] } | undefined>>;\n\nexport interface CheckPageOptions {\n /** Absolute directory source paths resolve from. */\n root: string;\n /** Default true. False: page-side rules only; every source status is `skipped`. Claim ends are still judged. */\n checkSources?: boolean;\n /**\n * The encryption key: decrypts encrypted sources and keys their pins.\n * Absent, an encrypted citation is `missing` (no key to decrypt it).\n */\n key?: string;\n severity?: Partial<Record<CiteRule, CiteSeverity>>;\n /**\n * The git client the source index and history are read through. Defaults\n * to one over the root, which is used whenever git is on PATH and the root\n * is inside a work tree. A caller that wants no git at all passes `noGit()`.\n */\n gitClient?: GitClient;\n /**\n * Directory the page's own path resolves from, and so the repository its\n * history is read in. Defaults to `root`, the same directory unless\n * `--root` sent the sources to another checkout.\n */\n pageRoot?: string;\n /** The client the page's history is read through. Defaults to one over `pageRoot`. */\n pageGitClient?: GitClient;\n /** How many commits the claim history walk reads. Defaults to `MAX_PAGE_COMMITS`. */\n pageCommitCap?: number;\n sourceIndex?: SourceIndex;\n /** Source files already read this run (`createSourceCache`); absent, each citation reads its own. */\n sourceCache?: SourceCache;\n /**\n * The page's citations as merged from a manifest, each with where it sits.\n * Given, the page's own frontmatter `citations` is not read. Without it, a\n * grader calling `checkCitations` on a sidecar page would find no citations.\n */\n citations?: readonly CitationInput[];\n /**\n * The manifest that owns this page's `citations`, when one does. A page\n * that carries its own anyway is `entry-invalid`, in meta's `external:owned`\n * words.\n */\n owned?: { file: string; collection: string };\n}\n\nexport interface MintOptions {\n root: string;\n /** `path`, `path:L`, `path:L1-L2`, or an encrypted `~file` with the same line forms. */\n src: string;\n id?: string;\n /** The claim pin, already taken over the page lines. */\n claim?: CitationClaim;\n quote?: boolean;\n /** A commit to record; `false` records none; absent records HEAD when git has one. */\n commitSha?: string | false;\n /**\n * Write `file` encrypted and pin the lines with the keyed pin; needs `key`.\n * A source that is already encrypted stays encrypted either way.\n */\n encrypt?: boolean;\n /** The encryption key: encrypts, decrypts an encrypted source, and keys its pin. */\n key?: string;\n gitClient?: GitClient;\n sourceIndex?: SourceIndex;\n}\n\n/**\n * One replacement against a fixed text: the bytes `[start, end)` become\n * `text`. A writer that has a position from a parse hands back an edit rather\n * than a rewritten page, so several of them can be collected against that one\n * parse and applied together (`applyEdits`). Nothing is then read out of a\n * text that has already been written to.\n */\nexport interface TextEdit {\n start: number;\n end: number;\n text: string;\n}\n\n/** One marker found in a page body: `cite <id>` in the format's comment syntax. */\nexport interface InlineStatement {\n /** File line of the marker. */\n line: number;\n /** File line of the anchored text: the rest of the marker's line, else the paragraph or block that follows. */\n anchorLine?: number;\n /**\n * The ids the marker names, in the order written, or why the marker is not\n * read. A payload of one id is what proposal 0044 shipped; 0056 widened it\n * to a space-separated list, and every id in the list anchors the same text.\n */\n payload:\n | { kind: \"ref\"; ids: [string, ...string[]] }\n | { kind: \"bad\"; reason: string; json?: boolean };\n /** The marker text between the delimiters, trimmed. */\n raw: string;\n /** Character offsets of the whole marker in the file, for rewriting. */\n start: number;\n end: number;\n}\n\n/** A validated citation with where it sits and the marker that names it, before classification. */\nexport interface PageCitation {\n citation: Citation;\n origin: CitationOrigin;\n /** The first marker naming the entry's id. */\n marker?: InlineStatement;\n}\n\n/** What `readPage` returns: the entries, the markers, and page-side findings. */\nexport interface PageCitations {\n file: string;\n format: string;\n content: string;\n /** Offset where the body starts (just past the closing fence, or 0). */\n bodyOffset: number;\n /** File line of body line 1: the first line after the frontmatter, or 1. */\n bodyLine: number;\n citations: PageCitation[];\n statements: InlineStatement[];\n findings: CitationFinding[];\n}\n\nexport interface CheckOptions {\n /** Positional inputs; empty means the configured collections' `paths:` (0041). */\n inputs: string[];\n /**\n * `--collection <name>`, repeatable: the collections this run covers. Empty\n * or absent means every declared one; see `selectCollections`.\n */\n collection?: string[];\n exts?: string[];\n /** `--exclude`, appended to the selected collections' `exclude:` when the run reads from them. */\n exclude?: string[];\n as?: string;\n configPath?: string;\n noConfig?: boolean;\n cwd?: string;\n stdinContent?: string;\n allowEmpty?: boolean;\n respectGitignore?: boolean;\n onNotice?: (message: string) => void;\n onConfigLoaded?: (info: { path: string; dir: string }) => void;\n baseline?: string | boolean;\n writeBaseline?: string | boolean;\n /** `--no-check-sources` (false): page-side rules only. Absent leaves `checkSources:` in charge. */\n checkSources?: boolean;\n /**\n * `--show-diff`: the report will want diffs and commit subjects. The check\n * is the same either way; the run only says, once, when git is not there\n * to give them.\n */\n showDiff?: boolean;\n root?: string;\n /**\n * The git client every page is checked through. Defaults to one over the\n * root, used whenever git is on PATH and the root is inside a work tree; a\n * test hands in a fake.\n */\n gitClient?: GitClient;\n /**\n * The client every page's own history is read through, over the directory\n * the page paths resolve from. Defaults to one there; a test hands in a fake.\n */\n pageGitClient?: GitClient;\n /** Defaults to `process.env`; read for `MANNI_ENCRYPTION_KEY`. A test hands in its own. */\n env?: NodeJS.ProcessEnv;\n}\n\nexport interface CheckRun {\n results: ValidationResult[];\n summary: import(\"../meta/index.js\").RunSummary;\n frame: import(\"../meta/index.js\").FingerprintContext;\n pages: PageCitationReport[];\n warnings: number;\n notices: number;\n}\n\n\nexport interface AddOptions {\n page: string;\n /** The claim's lines as an editor numbers them (`page:L`, `page:L1-L2`). Absent: a bare pin. */\n pageLines?: PageLines;\n src: string;\n id?: string;\n /** Write a marker above the page lines instead of claim lines, and pin the text it anchors. Needs `pageLines` and `id`. */\n marker?: boolean;\n /** The page lines are a fenced block that reproduces the source. Needs `pageLines`. */\n quote?: boolean;\n /**\n * `--encrypt`: write `file` encrypted, with a keyed pin, even when no key is\n * available yet, in which case `confirm` is asked whether to generate one.\n * An available key encrypts every add without it, so `false` and absent\n * are the same.\n */\n encrypt?: boolean;\n /** `false` records no `commit-sha`. */\n commitSha?: boolean;\n dryRun?: boolean;\n as?: string;\n configPath?: string;\n noConfig?: boolean;\n cwd?: string;\n stdinContent?: string;\n root?: string;\n /**\n * The git client HEAD and the source index are read through. Defaults to\n * one over the root; a test hands in a fake.\n */\n gitClient?: GitClient;\n /** Defaults to `process.env`; read for `MANNI_ENCRYPTION_KEY`. A test hands in its own. */\n env?: NodeJS.ProcessEnv;\n onNotice?: (message: string) => void;\n onConfigLoaded?: (info: { path: string; dir: string }) => void;\n /**\n * Asked when `encrypt` needs a key and there is none; the CLI passes\n * `terminalConfirm()`. Absent, the add refuses instead of asking.\n */\n confirm?: Confirm;\n}\n\n/** The manifest half of a write: what it says now, and whether it was saved. */\nexport interface ManifestWrite {\n /** The manifest as the run reports it. */\n file: string;\n /** 1-based line the written entry now sits on. */\n line: number;\n /** The whole manifest, rewritten. */\n content: string;\n diff: string;\n written: boolean;\n}\n\nexport interface AddResult {\n file: string;\n citation: Citation;\n /**\n * Where the entry was written: the page's frontmatter, or the manifest the\n * page's collection declares. The config decides, not a flag.\n */\n placed: \"frontmatter\" | \"manifest\";\n /** The manifest the entry went to, under `placed: \"manifest\"`. */\n manifest?: ManifestWrite;\n /** File lines of the claim after the write: the claim lines, or the text the marker anchors. */\n claimLines?: PageLines;\n /** File line of the marker that names the entry, under `marker`. */\n markerLine?: number;\n /**\n * Whether the id joined the list of a marker already there, rather than\n * getting a marker line of its own. A join moves no line on the page.\n */\n markerJoined?: boolean;\n /**\n * The first pinned source line, as the file holds it, so a mis-typed range\n * is visible at write time. Absent for a whole file, which has no first\n * line worth naming, and for an encrypted source, which no output reveals.\n */\n sourceLine?: string;\n /** The rewritten page. */\n content: string;\n diff: string;\n written: boolean;\n}\n\nexport interface UpdateOptions extends Omit<CheckOptions, \"baseline\" | \"writeBaseline\"> {\n accept?: boolean;\n only?: string[];\n dryRun?: boolean;\n /**\n * Re-record `commit-sha` where the recorded commit does not contain the\n * pinned lines. Rewrites that one field and nothing else.\n */\n recommit?: boolean;\n}\n\n/**\n * One end `update` rewrote.\n *\n * Read the typed pairs: `fromLines`/`toLines` for lines, `fromPin`/`toPin`\n * for pins. `from` and `to` carry the same values, but which of the two a row\n * holds depends on the row, so they are easy to misuse.\n */\n/** Why `--recommit` re-recorded a commit. */\nexport type RecommitReason = \"not-contained\" | \"not-in-history\";\n\nexport interface UpdateRewrite {\n id?: string;\n /** Index in the page's `citations`. */\n index: number;\n /** File line of the entry. */\n line?: number;\n end: \"claim\" | \"source\" | \"marker\";\n /**\n * `re-anchored` covers both re-pins: the pin held over another span (0054),\n * and the words held while the anchor moved (0053). `status` is what tells\n * those apart. `replaced` is a claim `--accept` refused to re-pin.\n */\n reason: \"moved\" | \"accepted\" | \"re-anchored\" | \"shifted\" | \"replaced\" | \"recommitted\";\n /**\n * The status that was repaired. `replaced` is a claim whose line held\n * wholly other text, accepted because `--only` named it.\n */\n status: \"moved\" | \"changed\" | \"reanchored\" | \"replaced\" | \"never-true\" | \"misplaced\" | \"current\";\n /**\n * Before and after. A moved or shifted claim, and a moved marker: file\n * lines. A moved source: its `src`. An accepted or re-anchored end: its pin.\n * The typed pairs below say which, so prefer them.\n */\n from: string;\n to: string;\n /** A moved marker, or a moved or shifted claim: its file lines, before and after. */\n fromLines?: string;\n toLines?: string;\n /** An accepted or re-anchored end: its pin, before and after. */\n fromPin?: string;\n toPin?: string;\n /** A re-committed source: the commit it recorded, and the one it records now. */\n fromCommit?: string;\n toCommit?: string;\n /**\n * A re-committed source: why the recorded commit could not stand.\n * `not-contained`: it does not hold the pinned lines. `not-in-history`: it\n * holds them, but is not in the history of HEAD.\n */\n because?: RecommitReason;\n /** A re-anchored claim: the span that held, in file lines. */\n lines?: string;\n /** A re-anchored claim: the span now pinned, in file lines. */\n newLines?: string;\n /** An accepted, re-anchored or refused claim: the file line of its first body line. */\n at?: number;\n /** An accepted claim a marker anchors: the marker's file line. Pretty-only. */\n markerLine?: number;\n /** An accepted claim: the text now pinned, whitespace collapsed. */\n text?: string;\n /** An accepted source: its `src`. */\n src?: string;\n /**\n * An accepted source: the commit recorded in the entry, when it records\n * one. A re-anchored or refused claim: the baseline its words were read\n * against.\n */\n commitSha?: string;\n /**\n * A refused claim the word-overlap test refused: the share of the claim's\n * words, from 0 to 1, that its line still holds. Absent on a claim the\n * sentence test refused, which is how a report tells the two apart.\n */\n wordShare?: number;\n}\n\nexport interface UpdatePage {\n file: string;\n rewritten: UpdateRewrite[];\n /**\n * Claims `--accept` refused to re-pin, because the stored first line now\n * holds text that shares no sentence with the claim at its baseline, or too\n * few of its words. Each carries `reason: \"replaced\"`, and each is work left\n * undone: exit 1.\n */\n refused: UpdateRewrite[];\n skipped: CitationFinding[];\n diff: string;\n written: boolean;\n /** The rewritten page, for the stdin input only: it has no file to be written to. */\n content?: string;\n}\n\n/** One manifest a run rewrote: what changed in it, and whether it was saved. */\nexport interface ManifestChange {\n /** The manifest as the run reports it. */\n file: string;\n diff: string;\n written: boolean;\n}\n\nexport interface UpdateRun {\n pages: UpdatePage[];\n rewritten: number;\n skipped: number;\n /** The manifests this run rewrote, each written once however many pages it holds. */\n manifests?: ManifestChange[];\n exitCode: 0 | 1;\n}\n\nexport interface RemoveOptions\n extends Omit<CheckOptions, \"baseline\" | \"writeBaseline\" | \"checkSources\" | \"showDiff\"> {\n /**\n * `--only <id>`, repeatable: the entries to remove, each named by its id or\n * by its pointer (`/citations/N`). Required, and an empty list is a\n * refusal: `remove` never takes every citation off a page.\n */\n only: string[];\n dryRun?: boolean;\n}\n\n/** One entry `remove` took out, and the markers that went with it. */\nexport interface Removal {\n id?: string;\n /** Index in the page's `citations`. Absent for a marker that named no entry. */\n index?: number;\n /** Where the entry was kept. Absent for a marker that named no entry. */\n origin?: { kind: OriginKind; file: string; line?: number };\n /** File lines of the markers removed with it, in the page as it was. */\n markerLines: number[];\n}\n\nexport interface RemovePage {\n file: string;\n removed: Removal[];\n diff: string;\n written: boolean;\n /** The rewritten page, for the stdin input only: it has no file to be written to. */\n content?: string;\n}\n\nexport interface RemoveRun {\n pages: RemovePage[];\n /** Entries removed, counting a marker that named no entry as one. */\n removed: number;\n /** The manifests this run rewrote, each written once however many pages it holds. */\n manifests?: ManifestChange[];\n}\n\n/** One citation `reencryptCitations` rewrote: where it sits, and its source before and after. */\nexport interface ReencryptedCitation {\n id?: string;\n /** Index in `citations`. */\n index?: number;\n /** File line of the entry. */\n line?: number;\n /** `file` and its line suffix, under the old key and under the new. */\n from: string;\n to: string;\n}\n\n/** What `reencryptCitations` did to one page. */\nexport interface ReencryptCitationsResult {\n /** The page with every re-encrypted citation rewritten; the input itself when none was. */\n content: string;\n rewritten: ReencryptedCitation[];\n /** Citations that could not be re-encrypted, and why. A message never names a path. */\n skipped: { id?: string; index?: number; line?: number; message: string }[];\n}\n\n/**\n * Config under `cite:` in manni.config.yaml, camelCase as `meta:` is. The\n * document set is not here: proposal 0041 moved `paths` and `exclude` to the\n * family-level `collections:` list, which every tool reads. Neither is the\n * encryption key, the top-level `encryptionKey:` every tool reads (0045).\n */\nexport interface CiteConfig {\n allowEmpty?: boolean;\n respectGitignore?: boolean;\n root?: string;\n baseline?: string;\n checkSources?: boolean;\n severity?: Partial<Record<CiteRule, CiteSeverity>>;\n}\n\nexport interface LoadedCiteConfig {\n config: CiteConfig;\n path: string;\n dir: string;\n /** The file as the user would name it, for messages. */\n source: string;\n /** The family file's top-level `collections:`, parsed by the shared layer (0041). */\n collections: CollectionConfig[];\n}\n\n/** What every command core resolves before touching a file. */\nexport interface CiteRun {\n config: CiteConfig | null;\n /** The positional inputs, or the selected collections' `paths:` in declaration order. */\n inputs: string[];\n /** Directory the inputs resolve from: cwd for positional inputs, the config directory for collections. */\n base: string;\n /**\n * The collections this run covers: every declared one, or the ones\n * `--collection` named. `[]` when no config governs the run.\n */\n collections: CollectionConfig[];\n /** Whether `inputs` came from the collections rather than the command line. */\n fromCollections: boolean;\n configDir?: string;\n configPath?: string;\n /** The config file as the user would name it, for messages. */\n configSource?: string;\n /** Absolute root source paths resolve from. */\n root: string;\n /**\n * The family encryption key (proposal 0045): `MANNI_ENCRYPTION_KEY`, else\n * the config's top-level `encryptionKey:`. Absent when neither is set.\n */\n key?: string;\n /** Where `key` came from. The environment wins over the config. */\n keySource: KeySource;\n /**\n * The config file that governs the run, as discovery found it. A write\n * that needs a key (`add --encrypt` with none) puts one there.\n */\n configFile?: ConfigFile;\n}\n","/**\n * Which rules fail a run, which only warn, and which are notices.\n *\n * Warnings and notices never affect the exit code (docevals' rule).\n * `source-moved` is a warning because `cite update` fixes it mechanically,\n * and a PR job that failed on it would make the ratchet noisy. `claim-moved`\n * is a notice: the text is found verbatim, so nothing drifted.\n * `claim-reanchored` is a notice for the same reason (proposal 0053): the\n * words the pin covered are found unchanged since the baseline, so only the\n * layout moved, and plain `cite update` re-pins it.\n * `claim-changed` is a warning, since it fires on any edit to a pinned\n * paragraph, a typo fix beside the cited sentence included. A repository that\n * wants either hard sets `severity: { claim-changed: error }`.\n * `marker-misplaced` is a warning too: it annotates the pull request, and one\n * `cite update` clears it, so a job that failed on it would block on a page\n * that was fine yesterday (proposal 0054, stress test 15).\n */\nimport { CITE_RULES, type CiteRule, type CiteSeverity } from \"../types.js\";\n\nexport const DEFAULT_SEVERITY: Readonly<Record<CiteRule, CiteSeverity>> = {\n \"source-moved\": \"warning\",\n \"source-moved-ambiguous\": \"error\",\n \"source-changed\": \"error\",\n \"source-never-true\": \"error\",\n \"source-missing\": \"error\",\n \"claim-moved\": \"notice\",\n \"claim-moved-ambiguous\": \"warning\",\n \"claim-reanchored\": \"notice\",\n \"claim-changed\": \"warning\",\n \"marker-orphan\": \"error\",\n \"marker-invalid\": \"error\",\n \"marker-repeated\": \"warning\",\n \"marker-misplaced\": \"warning\",\n \"anchor-invalid\": \"error\",\n \"entry-invalid\": \"error\",\n \"quote-drift\": \"error\",\n};\n\nexport const RULE_ID_PREFIX = \"manni:cite\";\n\n/** `manni:cite/<rule>`: the SARIF rule id and the JUnit failure type. */\nexport function ruleId(rule: CiteRule): string {\n return `${RULE_ID_PREFIX}/${rule}`;\n}\n\nexport function isCiteRule(value: string): value is CiteRule {\n return (CITE_RULES as readonly string[]).includes(value);\n}\n\n/** The effective severity table: defaults with the config's overrides. */\nexport function resolveSeverity(\n overrides?: Partial<Record<CiteRule, CiteSeverity>>,\n): Record<CiteRule, CiteSeverity> {\n return { ...DEFAULT_SEVERITY, ...(overrides ?? {}) };\n}\n","/**\n * Markers: `cite <id> [<id>…]` inside the format's comment syntax.\n *\n * | Format | Forms |\n * |---------------|------------------------------------------------------------------------------------|\n * | markdown, mdx | `<!-- cite a b -->`, `{/* cite a b *\\/}`, `[comment]: # (cite a b)` |\n * | html, xml | `<!-- cite a b -->` |\n * | asciidoc | `// (cite a b)` |\n * | rst | `.. (cite a b)` |\n *\n * A marker carries one or more ids, separated by spaces, and nothing else\n * (`cite true` names id `true`). Every id in the list anchors the text the\n * marker anchors, so a paragraph resting on four sources carries one comment\n * rather than four (proposal 0056). An id cannot hold `(`, `)`, `{`, `}`, `<`,\n * `>` or a leading `-`, so no list can close a parenthesised comment early.\n *\n * A word starting with `{` was the inline entry of proposal 0044's first\n * draft; it is `marker-invalid` now, because an entry lives in frontmatter or\n * a manifest and a source is never written into the body. The scanner uses\n * `indexOf` for the delimiters, never a regex over the whole page, and is\n * handed the body only, so a `cite` inside frontmatter is never matched.\n *\n * It does not read code. An opener inside a fenced block or a backtick span,\n * as `src/shared/code-regions.ts` finds them, is skipped, so a page that\n * documents the syntax carries no statements.\n *\n * Every form parses in both markdown and mdx. The first form is what\n * `formatStatement` writes: the html comment for markdown, the jsx comment\n * for mdx, which rejects an html comment.\n */\nimport type { InlineStatement, TextEdit } from \"../types.js\";\nimport { codeEndAt, codeRegions } from \"../../shared/code-regions.js\";\nimport { CiteError } from \"../errors.js\";\n\nexport interface StatementForm {\n open: string;\n close: string;\n}\n\nconst HTML_COMMENT: StatementForm = { open: \"<!--\", close: \"-->\" };\nconst JSX_COMMENT: StatementForm = { open: \"{/*\", close: \"*/}\" };\nconst LINK_REFERENCE: StatementForm = { open: \"[comment]: # (\", close: \")\" };\nconst MARKDOWN_FORMS: readonly StatementForm[] = [HTML_COMMENT, JSX_COMMENT, LINK_REFERENCE];\nconst MDX_FORMS: readonly StatementForm[] = [JSX_COMMENT, HTML_COMMENT, LINK_REFERENCE];\nconst COMMENT_FORMS: readonly StatementForm[] = [{ open: \"<!--\", close: \"-->\" }];\nconst ASCIIDOC_FORMS: readonly StatementForm[] = [{ open: \"// (\", close: \")\" }];\nconst RST_FORMS: readonly StatementForm[] = [{ open: \".. (\", close: \")\" }];\n\nconst ID = /^[a-z0-9][a-z0-9-]*$/;\n\n/**\n * A fence opener in any format. `paragraphAfter` reads it whether or not it\n * was given a format, and so does the claim search (`claims.ts`), so every\n * family's opener counts; the per-format locators below are what `quote`\n * anchoring uses.\n * A markdown fence keeps its meaning when indented, as inside a list item, so\n * it is read there too, matching the code skip in `src/shared/code-regions.ts`; the\n * asciidoc `----` stays at column 0.\n */\nexport const ANY_FENCE = /^(?:[ \\t]*(?:`{3,}|~{3,})|-{4,})/;\nconst MARKDOWN_FENCE = /^(`{3,}|~{3,})/;\nconst ASCIIDOC_FENCE = /^(-{4,})/;\n/** The forms per extractor name. Unknown formats have none. */\nexport function statementForms(format: string): readonly StatementForm[] {\n switch (format) {\n case \"markdown\":\n return MARKDOWN_FORMS;\n case \"mdx\":\n return MDX_FORMS;\n case \"html\":\n case \"xml\":\n return COMMENT_FORMS;\n case \"asciidoc\":\n return ASCIIDOC_FORMS;\n case \"rst\":\n return RST_FORMS;\n default:\n return [];\n }\n}\n\n/** The fence-opener pattern for a format, or undefined when it has no locator. */\nfunction fenceFor(format: string): RegExp | undefined {\n switch (format) {\n case \"markdown\":\n case \"mdx\":\n return MARKDOWN_FENCE;\n case \"asciidoc\":\n return ASCIIDOC_FENCE;\n default:\n return undefined;\n }\n}\n\n/** Offset of the terminator of the line containing `pos` (or the text length). */\nfunction lineEnd(content: string, pos: number): number {\n const nl = content.indexOf(\"\\n\", pos);\n return nl === -1 ? content.length : nl;\n}\n\n/** The text of the line `[pos, end)`, without a trailing CR. */\nfunction lineText(content: string, pos: number, end: number): string {\n const text = content.slice(pos, end);\n return text.endsWith(\"\\r\") ? text.slice(0, -1) : text;\n}\n\n/** `end` pulled back over a CR so a CRLF terminator is excluded whole. */\nfunction beforeCr(content: string, end: number): number {\n return end > 0 && content.charCodeAt(end - 1) === 13 ? end - 1 : end;\n}\n\n/**\n * Where a forward scan of whole lines starts from `offset`: the next line when\n * `offset` sits inside one (the rest of a statement's own line), the line\n * itself when `offset` is already at its start.\n */\nfunction nextLineStart(content: string, offset: number): number {\n if (offset === 0 || content.charCodeAt(offset - 1) === 10) return offset;\n return lineEnd(content, offset) + 1;\n}\n\n/**\n * A marker holds at most 25 ids. Past that it is `marker-invalid`, on the\n * same grounds as the 500 markers a page may hold: an unbounded list is an\n * unbounded allocation from a file the tool did not write. The cap counts\n * ids rather than characters, because a character cap moves with how long\n * the ids happen to be. Proposal 0056 is the record.\n */\nexport const MAX_IDS_PER_MARKER = 25;\n\n/**\n * The payload, as one or more ids separated by spaces. The checks run in a\n * stated order and the first defect is the finding, so one marker raises one\n * `marker-invalid` however many of its words are wrong.\n *\n * A run of spaces separates as one, which is what a soft-wrapped editor and a\n * shell both do. A comma is never a separator: `cite a,b` is one token, that\n * token is not an id, and the message quotes it back (proposal 0034's one\n * separator per list).\n */\nfunction payloadOf(payload: string): InlineStatement[\"payload\"] {\n if (payload === \"\") return { kind: \"bad\", reason: \"empty payload\" };\n // A marker is one line: `isMarkerLine` tests one line, so half a wrapped\n // marker would land inside the pin of the text below it.\n if (/[\\r\\n]/.test(payload)) {\n return { kind: \"bad\", reason: \"a marker is one line; write two markers\" };\n }\n // Destructured rather than cast: a `ref` carries at least one id, and the\n // head is what proves it to the compiler.\n const [head, ...rest] = payload.split(\" \").filter((word) => word !== \"\");\n if (head === undefined) return { kind: \"bad\", reason: \"empty payload\" };\n const words: [string, ...string[]] = [head, ...rest];\n // An entry lives in frontmatter or a manifest; the body carries names. This\n // runs before the id test so the more actionable message fires.\n if (words.some((word) => word.startsWith(\"{\"))) {\n return { kind: \"bad\", reason: \"a JSON payload\", json: true };\n }\n if (words.length > MAX_IDS_PER_MARKER) {\n return {\n kind: \"bad\",\n reason: `more than ${String(MAX_IDS_PER_MARKER)} ids in one marker (${String(words.length)}); write a second marker`,\n };\n }\n const bad = words.find((word) => !ID.test(word));\n if (bad !== undefined) return { kind: \"bad\", reason: `\"${bad}\" is not an id` };\n const seen = new Set<string>();\n for (const word of words) {\n // One id named twice inside one marker has no second anchor and no winner\n // to pick, so it is a typo in one line rather than `marker-repeated`.\n if (seen.has(word)) {\n return { kind: \"bad\", reason: `\"${word}\" is named twice in one marker` };\n }\n seen.add(word);\n }\n return { kind: \"ref\", ids: words };\n}\n\n/** Whether the text between a form's delimiters is a cite statement. */\nfunction isCite(inner: string): boolean {\n return inner === \"cite\" || (inner.startsWith(\"cite\") && /\\s/.test(inner.charAt(4)));\n}\n\n/**\n * Whether a line holds one marker and nothing else, in any of the format's\n * forms. Such a line is never text a marker anchors: markers stacked above a\n * paragraph all anchor the paragraph, and none of them is part of its pin.\n */\nexport function isMarkerLine(line: string, format: string): boolean {\n const text = line.trim();\n return statementForms(format).some((form) => {\n if (text.length < form.open.length + form.close.length) return false;\n if (!text.startsWith(form.open)) return false;\n // The first close is the last thing on the line, so there is one marker.\n const closeAt = text.indexOf(form.close, form.open.length);\n if (closeAt !== text.length - form.close.length) return false;\n return isCite(text.slice(form.open.length, closeAt).trim());\n });\n}\n\n/** An ATX heading: one to six `#`, then a space or the end of the line. */\nconst ATX_HEADING = /^[ \\t]*#{1,6}([ \\t]|$)/;\n/** An asciidoc section title: one to six `=`, then a space. */\nconst ASCIIDOC_TITLE = /^={1,6}[ \\t]/;\n/**\n * A line of one punctuation character repeated three or more times, spaces\n * allowed: a setext underline, a thematic break, an rst adornment.\n */\nconst ADORNMENT = /^[ \\t]*([-=~^\"'*+#_:<>])(?:[ \\t]*\\1){2,}[ \\t]*$/;\n/** A line that is nothing but one JSX or HTML tag, opening or closing. */\nconst TAG_LINE = /^[ \\t]*<\\/?[A-Za-z][^<>]*>[ \\t]*$/;\n/** A table row: a line whose first non-blank character is a pipe. */\nconst TABLE_ROW = /^[ \\t]*\\|/;\n\n/**\n * Whether a line is a table row. The header row, the `|---|---|` delimiter row\n * and every body row all open with a pipe, so all three read as rows. Leading\n * whitespace is allowed, because a table inside a list item is indented and is\n * still a table.\n *\n * A row is a statement of its own. Nothing else separates one row from the\n * next, so a paragraph walk reads a whole table as one paragraph, and a claim\n * pinned to one row would re-pin every row below it. `unitAt` in\n * `src/cite/core/claims.ts` reads this instead, so a re-pin covers the row and\n * no more.\n */\nexport function isTableRow(line: string): boolean {\n return TABLE_ROW.test(line);\n}\n\n/**\n * A table rule: the `|---|---|` line under a header row. The outer pipes are\n * optional, a cell may carry `:` alignment markers, and the whole line may be\n * indented, because a table inside a list item is indented and is still a\n * table.\n *\n * A rule has to carry at least one `|`, which is what the leading lookahead\n * asks. Every pipe in the pattern that follows is optional on its own, so\n * without that test a bare `---` reads as a table rule, and `---` is a\n * thematic break or the underline of a setext heading instead.\n *\n * `isTableRow` reads a rule as a row, deliberately: a claim over a header, its\n * rule and a body row covers three rows and keeps all three. This asks the\n * narrower question `unitAt` needs, which is whether a span holds nothing but\n * rules. Such a span carries no words, so `update --accept` refuses to pin one.\n */\nconst TABLE_RULE = /^(?=[^|]*\\|)[ \\t]*\\|?(?:[ \\t]*:?-+:?[ \\t]*\\|)*[ \\t]*:?-+:?[ \\t]*\\|?[ \\t]*$/;\n\nexport function isTableSeparator(line: string): boolean {\n return TABLE_RULE.test(line);\n}\n\n/**\n * Whether a line is a **bound line**: one the format makes a block on its own,\n * so no paragraph continues across it, and no pin covers it beside text.\n *\n * | Format | Bound lines |\n * |---|---|\n * | markdown, mdx | An ATX heading, and a line that is nothing but one tag. |\n * | html, xml | A line that is nothing but one tag. |\n * | asciidoc | A section title. |\n * | every format | An adornment: one of `-=~^\"'*+#_:<>` repeated three or more times. |\n *\n * Proposal 0054 is the record, and its stress tests 8 and 9 say why a heading\n * and a tag line are read this way rather than joined to the text below them.\n * A tag line is bound in every format that reads one, so a marker under\n * `<body>` anchors the element below it and never the whole document.\n */\nexport function isBoundLine(line: string, format: string): boolean {\n if (ADORNMENT.test(line)) return true;\n switch (format) {\n case \"markdown\":\n case \"mdx\":\n return ATX_HEADING.test(line) || TAG_LINE.test(line);\n case \"html\":\n case \"xml\":\n return TAG_LINE.test(line);\n case \"asciidoc\":\n return ASCIIDOC_TITLE.test(line);\n default:\n return false;\n }\n}\n\n/** Scan a body for statements. `from` maps body offsets/lines to file offsets/lines. */\nexport function parseStatements(\n body: string,\n format: string,\n from: { offset: number; line: number } = { offset: 0, line: 1 },\n): InlineStatement[] {\n const out: InlineStatement[] = [];\n const fileLine = (bodyOffset: number): number =>\n from.line + lineAt(body, bodyOffset) - 1;\n const code = codeRegions(body, format);\n\n for (const form of statementForms(format)) {\n let cursor = 0;\n for (;;) {\n const at = body.indexOf(form.open, cursor);\n if (at === -1) break;\n const codeEnd = codeEndAt(code, at);\n if (codeEnd !== undefined) {\n cursor = codeEnd;\n continue;\n }\n const closeAt = body.indexOf(form.close, at + form.open.length);\n if (closeAt === -1) break;\n const end = closeAt + form.close.length;\n cursor = end;\n const inner = body.slice(at + form.open.length, closeAt).trim();\n if (!isCite(inner)) continue;\n const payload = inner.slice(4).trim();\n\n // Anchor: the rest of the close delimiter's line when it carries text,\n // else the paragraph or fenced block that follows.\n const unit = anchoredLines(body, end, format);\n const anchorLine = unit === undefined ? undefined : from.line + unit.start - 1;\n\n const statement: InlineStatement = {\n line: fileLine(at),\n payload: payloadOf(payload),\n raw: inner,\n start: from.offset + at,\n end: from.offset + end,\n };\n if (anchorLine !== undefined) statement.anchorLine = anchorLine;\n out.push(statement);\n }\n }\n return out.sort((a, b) => a.start - b.start);\n}\n\n/** `\"\\r\\n\"` when the text's first line break is CRLF, else `\"\\n\"`. */\nexport function detectEol(text: string): \"\\n\" | \"\\r\\n\" {\n const nl = text.indexOf(\"\\n\");\n return nl > 0 && text.charCodeAt(nl - 1) === 13 ? \"\\r\\n\" : \"\\n\";\n}\n\n/**\n * The paragraph starting at or after `offset` (skipping blank lines), as\n * `{ start, end, line }` file offsets and the file line it starts on. A fence\n * opener ends the search with no paragraph. Given a `format`, a marker-only\n * line is skipped like a blank one before the paragraph, and ends it after,\n * and a bound line is a unit of one line: it ends a paragraph above it, and\n * stands alone when it is the first line found.\n */\nexport function paragraphAfter(\n content: string,\n offset: number,\n format?: string,\n): { start: number; end: number; line: number } | undefined {\n const marker = (text: string): boolean => format !== undefined && isMarkerLine(text, format);\n const bound = (text: string): boolean => format !== undefined && isBoundLine(text, format);\n let pos = offset;\n // The rest of the line `offset` sits in counts when it carries text; when\n // it is blank the paragraph can only start on a later line.\n const firstEnd = lineEnd(content, pos);\n if (lineText(content, pos, firstEnd).trim() === \"\") pos = firstEnd + 1;\n\n while (pos < content.length) {\n const end = lineEnd(content, pos);\n const text = lineText(content, pos, end);\n if (text.trim() === \"\" || marker(text)) {\n pos = end + 1;\n continue;\n }\n if (ANY_FENCE.test(text)) return undefined;\n const start = pos;\n let last = end;\n // A bound line is its own unit, so it never gathers the lines under it.\n let cursor = bound(text) ? content.length : end + 1;\n while (cursor < content.length) {\n const e = lineEnd(content, cursor);\n const t = lineText(content, cursor, e);\n if (t.trim() === \"\" || ANY_FENCE.test(t) || marker(t) || bound(t)) break;\n last = e;\n cursor = e + 1;\n }\n return { start, end: beforeCr(content, last), line: lineAt(content, start) };\n }\n return undefined;\n}\n\ninterface FencedBlock {\n start: number;\n end: number;\n line: number;\n text: string;\n}\n\n/**\n * The next fenced block whose opener matches `fence`, scanning whole lines\n * from `offset`, plus the offset just past its closing fence line so a caller\n * can continue.\n */\nfunction scanFence(\n content: string,\n offset: number,\n fence: RegExp,\n): (FencedBlock & { next: number }) | undefined {\n let pos = nextLineStart(content, offset);\n while (pos < content.length) {\n const end = lineEnd(content, pos);\n const text = lineText(content, pos, end);\n const opener = fence.exec(text)?.[1];\n if (opener === undefined) {\n pos = end + 1;\n continue;\n }\n const line = lineAt(content, pos);\n const start = Math.min(end + 1, content.length);\n let cursor = start;\n while (cursor < content.length) {\n const e = lineEnd(content, cursor);\n if (lineText(content, cursor, e).startsWith(opener)) {\n return {\n start,\n end: cursor,\n line,\n text: content.slice(start, cursor),\n next: e + 1,\n };\n }\n cursor = e + 1;\n }\n return undefined;\n }\n return undefined;\n}\n\n/**\n * The fenced code block starting at or after `offset` (``` or ~~~; asciidoc\n * `----`), as file offsets of its content lines and the file line of the\n * opening fence. `undefined` for formats with no locator (html, xml, rst).\n */\nexport function fencedBlockAfter(\n content: string,\n offset: number,\n format: string,\n): { start: number; end: number; line: number; text: string } | undefined {\n const fence = fenceFor(format);\n if (!fence) return undefined;\n const block = scanFence(content, offset, fence);\n if (!block) return undefined;\n const { start, end, line, text } = block;\n return { start, end, line, text };\n}\n\n/**\n * The fenced block at `offset` in any fence family, for callers that have no\n * format at hand (the claim search). Undefined when the line at `offset` is\n * not an opener.\n */\nexport function fencedBlockAt(\n content: string,\n offset: number,\n): { start: number; end: number; line: number; text: string } | undefined {\n const pos = nextLineStart(content, offset);\n if (!ANY_FENCE.test(lineText(content, pos, lineEnd(content, pos)))) {\n return undefined;\n }\n const block =\n scanFence(content, pos, MARKDOWN_FENCE) ?? scanFence(content, pos, ASCIIDOC_FENCE);\n if (!block || block.line !== lineAt(content, pos)) return undefined;\n const { start, end, line, text } = block;\n return { start, end, line, text };\n}\n\n/** Every fenced block in the body, for `--quote` without `--claim`. */\nexport function fencedBlocks(\n content: string,\n bodyOffset: number,\n format: string,\n): { start: number; end: number; line: number; text: string }[] {\n const fence = fenceFor(format);\n if (!fence) return [];\n const out: FencedBlock[] = [];\n let pos = bodyOffset;\n for (;;) {\n const block = scanFence(content, pos, fence);\n if (!block) return out;\n const { start, end, line, text } = block;\n out.push({ start, end, line, text });\n pos = block.next;\n }\n}\n\n/** The form `formatStatement` writes for a format, or a refusal naming the format. */\nfunction writtenForm(format: string): StatementForm {\n const form = statementForms(format)[0];\n if (!form) {\n throw new CiteError(`No marker syntax for format \"${format}\".`);\n }\n return form;\n}\n\n/** Render a marker in the format's first form, e.g. `<!-- cite fetch-timeout -->`. */\nexport function formatStatement(\n format: string,\n payload: { kind: \"ref\"; ids: readonly [string, ...string[]] },\n): string {\n const form = writtenForm(format);\n // `[comment]: # (`, `// (` and `.. (` hug their parentheses; the comment\n // forms take a space inside each delimiter.\n const pad = form.open.endsWith(\"(\") ? \"\" : \" \";\n return `${form.open}${pad}cite ${payload.ids.join(\" \")}${pad}${form.close}`;\n}\n\n/**\n * The one edit that replaces a marker's payload with `ids`, as a span of\n * `content` and the text to put in it. The marker keeps its own form rather\n * than being rendered afresh, so a page that spells its markers in a form\n * `formatStatement` would not choose keeps that form, along with its\n * indentation and inner spacing.\n *\n * Returned as an edit rather than applied, so a caller with several of them\n * can collect them all against one parse and apply them together. That is how\n * `remove` rewrites a page: nothing it reads a position from has been written\n * to yet.\n *\n * `content` must be the text the statement was parsed from; the payload is\n * found inside the span the statement recorded, and the answer is undefined\n * when it is not there. The marker must also open and close on one line, since\n * a payload spanning lines would be folded onto one here, changing the line\n * count and moving every claim below it. Both callers hold to that: `remove`\n * refuses a marker line it cannot read whole, and `add` joins only a marker\n * with no line break in it.\n */\nexport function respellEdit(\n content: string,\n statement: InlineStatement,\n ids: readonly [string, ...string[]],\n): TextEdit | undefined {\n const text = content.slice(statement.start, statement.end);\n // No open delimiter carries `cite`, so the first occurrence of the trimmed\n // inner text is the payload itself.\n const at = text.indexOf(statement.raw);\n if (at === -1) return undefined;\n return {\n start: statement.start + at,\n end: statement.start + at + statement.raw.length,\n text: `cite ${ids.join(\" \")}`,\n };\n}\n\n/**\n * `content` with the marker's payload replaced by `ids`. The one-edit form of\n * `respellEdit`, for a caller rewriting a single marker; `content` unchanged\n * when the payload is not where the statement says.\n */\nexport function respellStatement(\n content: string,\n statement: InlineStatement,\n ids: readonly [string, ...string[]],\n): string {\n const edit = respellEdit(content, statement, ids);\n if (edit === undefined) return content;\n return content.slice(0, edit.start) + edit.text + content.slice(edit.end);\n}\n\n/**\n * The first line at or after `offset` that is neither blank nor a marker on\n * its own, as a 1-based line of `text`.\n */\nfunction nextTextLine(text: string, offset: number, format: string): number | undefined {\n let pos = nextLineStart(text, offset);\n while (pos < text.length) {\n const end = lineEnd(text, pos);\n const line = lineText(text, pos, end);\n if (line.trim() !== \"\" && !isMarkerLine(line, format)) return lineAt(text, pos);\n pos = end + 1;\n }\n return undefined;\n}\n\n/**\n * The fenced block opening on `line`, as the lines it spans, fences included.\n * Undefined when that line is not an opener the format knows, or the block is\n * never closed.\n */\nexport function fenceSpanAt(\n text: string,\n line: number,\n format: string,\n): { start: number; end: number } | undefined {\n const fence = fenceFor(format);\n if (!fence) return undefined;\n const block = scanFence(text, offsetOfLine(text, line), fence);\n if (!block || block.line !== line) return undefined;\n return { start: line, end: lineAt(text, block.end) };\n}\n\n/**\n * The fenced block `line` sits inside rather than opens, as the lines it\n * spans, fences included. Undefined when the line opens a block, sits in no\n * block, or the format has no fence. The closing fence counts as inside, so a\n * caller that wants a content line asks whether its span ends before the\n * block does.\n */\nexport function fenceAround(\n text: string,\n bodyOffset: number,\n line: number,\n format: string,\n): { start: number; end: number } | undefined {\n const block = fencedBlocks(text, bodyOffset, format).find(\n (b) => line > b.line && line <= lineAt(text, b.end),\n );\n return block === undefined ? undefined : { start: block.line, end: lineAt(text, block.end) };\n}\n\n/** Whether `line` sits inside a fenced block rather than opening one. */\nexport function insideFence(\n text: string,\n bodyOffset: number,\n line: number,\n format: string,\n): boolean {\n return fenceAround(text, bodyOffset, line, format) !== undefined;\n}\n\n/**\n * The paragraph or fenced block that holds `line`, as the lines it spans,\n * fences included: what a marker written above it anchors. A paragraph runs\n * up to a blank line, a fence, a marker-only line, a bound line or the body's\n * start, and down as `paragraphAfter` reads it. Undefined for a blank line, a\n * marker-only line, or a line nothing can anchor.\n */\nexport function unitHolding(\n text: string,\n line: number,\n format: string,\n body: { offset: number; line: number },\n): { start: number; end: number; kind: \"paragraph\" | \"block\" } | undefined {\n const textOf = (n: number): string => lineText(text, offsetOfLine(text, n), lineEnd(text, offsetOfLine(text, n)));\n const own = textOf(line);\n if (own.trim() === \"\" || isMarkerLine(own, format)) return undefined;\n const block = fencedBlocks(text, body.offset, format).find(\n (b) => line >= b.line && line <= lineAt(text, b.end),\n );\n if (block !== undefined) {\n return { start: block.line, end: lineAt(text, block.end), kind: \"block\" };\n }\n if (ANY_FENCE.test(own)) return undefined;\n let start = line;\n // A bound line is its own unit, so the walk up never leaves it.\n while (start - 1 >= body.line && !isBoundLine(own, format)) {\n const above = textOf(start - 1);\n if (\n above.trim() === \"\" ||\n ANY_FENCE.test(above) ||\n isMarkerLine(above, format) ||\n isBoundLine(above, format)\n ) {\n break;\n }\n start--;\n }\n const paragraph = paragraphAfter(text, offsetOfLine(text, start), format);\n if (paragraph?.line !== start) return undefined;\n return { start, end: lineAt(text, paragraph.end), kind: \"paragraph\" };\n}\n\n/** A markdown list item's opening line: its indentation, its bullet or number, and the gap after. */\nconst LIST_ITEM = /^([ \\t]*)([-*+]|\\d{1,9}[.)])([ \\t]+|$)/;\n/** A thematic break, which a list item's pattern would otherwise read as `* * *`. */\nconst THEMATIC_BREAK = /^[ \\t]*([-*_])(?:[ \\t]*\\1){2,}[ \\t]*$/;\n\n/** The text of 1-based line `n` of `text`. */\nfunction textOfLine(text: string, n: number): string {\n const pos = offsetOfLine(text, n);\n return lineText(text, pos, lineEnd(text, pos));\n}\n\n/** The leading spaces and tabs of a line. */\nfunction indentOf(line: string): string {\n return /^[ \\t]*/.exec(line)?.[0] ?? \"\";\n}\n\n/** The column a run of spaces and tabs reaches, with tab stops every 4 columns. */\nfunction columnOf(indent: string): number {\n let column = 0;\n for (const ch of indent) column = ch === \"\\t\" ? column + 4 - (column % 4) : column + 1;\n return column;\n}\n\n/** The list item a markdown line opens, or undefined. */\nfunction listItemOf(line: string): { lead: string; bullet: string; gap: string } | undefined {\n if (THEMATIC_BREAK.test(line)) return undefined;\n const match = LIST_ITEM.exec(line);\n if (match === null) return undefined;\n const [, lead = \"\", bullet = \"\", gap = \"\"] = match;\n return { lead, bullet, gap };\n}\n\n/**\n * Whether the list item on `line` follows an earlier item of its list: the\n * nearest text above it, past blank and marker-only lines, is indented deeper\n * than the item, or sits in a paragraph holding an item at the same column.\n */\nfunction continuesList(\n text: string,\n line: number,\n format: string,\n bodyLine: number,\n column: number,\n): boolean {\n let n = line - 1;\n while (n >= bodyLine) {\n const above = textOfLine(text, n);\n if (above.trim() !== \"\" && !isMarkerLine(above, format)) break;\n n--;\n }\n if (n < bodyLine) return false;\n if (columnOf(indentOf(textOfLine(text, n))) > column) return true;\n for (; n >= bodyLine; n--) {\n const above = textOfLine(text, n);\n if (\n above.trim() === \"\" ||\n ANY_FENCE.test(above) ||\n isMarkerLine(above, format) ||\n isBoundLine(above, format)\n ) {\n break;\n }\n const item = listItemOf(above);\n if (item !== undefined && columnOf(item.lead) === column) return true;\n }\n return false;\n}\n\n/**\n * The indentation a marker written above `line` carries, so it stays in the\n * container the line is in. A paragraph's own leading spaces and tabs, copied\n * as they are. Above a markdown list item that follows an earlier item, the\n * column of the item's text, which keeps the marker inside the list: at the\n * item's own column it would end the list. asciidoc never indents one, since\n * a comment there starts at column 0.\n */\nexport function markerIndent(\n text: string,\n line: number,\n format: string,\n bodyLine: number,\n): string {\n if (format === \"asciidoc\") return \"\";\n const own = textOfLine(text, line);\n const indent = indentOf(own);\n if (format !== \"markdown\" && format !== \"mdx\") return indent;\n const item = listItemOf(own);\n if (item === undefined || !continuesList(text, line, format, bodyLine, columnOf(item.lead))) {\n return indent;\n }\n // A gap of five spaces or more is one space and indented code, so the text starts after one.\n const gap = item.gap === \"\" || columnOf(item.gap) > 4 ? \" \" : item.gap;\n return item.lead + \" \".repeat(item.bullet.length) + gap;\n}\n\n/**\n * The lines a marker anchors, as 1-based lines of `text`: the rest of its own\n * line when that carries text, else the paragraph or fenced block that\n * follows. Marker-only lines below it are skipped, so markers stacked above\n * one paragraph all anchor it, and none is part of the pin. With `quote`, the\n * next fenced block after it, wherever that is. Undefined when nothing\n * follows to anchor.\n */\nexport function anchoredLines(\n text: string,\n after: number,\n format: string,\n quote = false,\n): { start: number; end: number } | undefined {\n if (quote) {\n const block = fencedBlockAfter(text, after, format);\n return block === undefined ? undefined : { start: block.line, end: lineAt(text, block.end) };\n }\n const restEnd = lineEnd(text, after);\n if (text.slice(after, restEnd).trim() !== \"\") {\n const line = lineAt(text, after);\n return { start: line, end: line };\n }\n const paragraph = paragraphAfter(text, after, format);\n if (paragraph !== undefined) {\n return { start: paragraph.line, end: lineAt(text, paragraph.end) };\n }\n // A fence where a paragraph would be: the block is what the marker anchors.\n const line = nextTextLine(text, after, format);\n return line === undefined ? undefined : fenceSpanAt(text, line, format);\n}\n\n/**\n * The offset of every LF in a text, so a line number is a lookup rather than\n * a scan from the start. A page asks `lineAt` and `offsetOfLine` once per\n * citation, and a scan per question made a page of hundreds of citations\n * quadratic. Only the last few texts are kept, and the index is a pure\n * function of the text, so nothing carries from one run to the next.\n */\nconst LINE_INDEX_SLOTS = 4;\nconst lineIndexes: { text: string; newlines: number[] }[] = [];\n\nfunction newlinesOf(content: string): readonly number[] {\n const hit = lineIndexes.findIndex((entry) => entry.text === content);\n const found = hit === -1 ? undefined : lineIndexes[hit];\n if (found !== undefined) return found.newlines;\n const newlines: number[] = [];\n for (let at = content.indexOf(\"\\n\"); at !== -1; at = content.indexOf(\"\\n\", at + 1)) newlines.push(at);\n lineIndexes.unshift({ text: content, newlines });\n lineIndexes.length = Math.min(lineIndexes.length, LINE_INDEX_SLOTS);\n return newlines;\n}\n\n/** 1-based file line of a file offset, counting CRLF once. */\nexport function lineAt(content: string, offset: number): number {\n const stop = Math.min(offset, content.length);\n const newlines = newlinesOf(content);\n // The number of LFs before `stop`: a binary search for the first one at or past it.\n let low = 0;\n let high = newlines.length;\n while (low < high) {\n const mid = (low + high) >>> 1;\n if ((newlines[mid] ?? Infinity) < stop) low = mid + 1;\n else high = mid;\n }\n return low + 1;\n}\n\n/** File offset of the first character of a 1-based line (the length when past the end). */\nexport function offsetOfLine(content: string, line: number): number {\n if (line <= 1) return 0;\n const before = newlinesOf(content)[line - 2];\n return before === undefined ? content.length : before + 1;\n}\n"],"mappings":"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","names":["sliceLines","hashRange","line"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/meta/reporters/rule-id.ts","../src/meta/reporters/sarif.ts","../src/shared/xml.ts","../src/meta/reporters/junit.ts","../src/meta/reporters/index.ts"],"sourcesContent":["/**\n * A violation's **rule identity**, shared by the SARIF `ruleId` and the JUnit\n * `<failure type>`.\n *\n * Built from `schema` + `keyword` and nothing else. `message` is Ajv-generated\n * prose, so a rule id derived from it would rename itself on any Ajv upgrade\n * and every historical alert in the consumer would close and reopen as new.\n * That is the same reasoning `src/core/baseline.ts` applies to fingerprints,\n * and for the same reason: these are the durable half of a violation.\n */\nimport type { FieldError } from \"../types.js\";\nimport { canonicalSchemaRef, type FingerprintContext } from \"../core/baseline.js\";\n\n/** The `schema` label the command layer stamps on its own failures. */\nconst SYNTHETIC_SCHEMA = \"(parse)\";\n\n/** A document whose metadata block could not be parsed. */\nexport const PARSE_ERROR_RULE = \"manni/parse-error\";\n\n/** A document no schema set could be resolved for. */\nexport const SCHEMA_ERROR_RULE = \"manni/schema-error\";\n\n/**\n * A document carrying a key a manifest owns (proposal 0037). Not a\n * synthetic-schema special case: `external:owned` is a builtin-shaped ref\n * like `check:<name>`, so the ordinary join produces this id.\n */\nexport const EXTERNAL_OWNED_RULE = \"external:owned/external\";\n\n/**\n * Two documents carry the same value of a manifest's join field (proposal\n * 0039), so one manifest entry matched both.\n */\nexport const EXTERNAL_DUPLICATE_RULE = \"external:duplicate/external\";\n\n/**\n * A property its schema marks `x-manni-encrypt` holds a plain value\n * (proposal 0045). Builtin-shaped like `external:owned`.\n */\nexport const ENCRYPTED_PLAIN_RULE = \"encrypted:plain/encrypted\";\n\n/**\n * A marked property holds a ciphertext that does not decrypt under the\n * current key (proposal 0045).\n */\nexport const ENCRYPTED_UNREADABLE_RULE = \"encrypted:unreadable/encrypted\";\n\n/**\n * A managed field whose asserted value differs from the derived one, or is\n * missing while a source has one. `derived:stale` is builtin-shaped like the\n * external refs, so the ordinary join produces this id too.\n */\nexport const DERIVED_STALE_RULE = \"derived:stale/derived\";\n\n/**\n * A key the page holds that its schemas mark `x-manni-location: external`\n * (proposal 0047). A warning, builtin-shaped like `external:owned`.\n */\nexport const LOCATION_EXTERNAL_RULE = \"location:external/location\";\n\n/**\n * A key a manifest supplies that its schemas mark `x-manni-location: page`\n * (proposal 0047). A warning, filed at the manifest line.\n */\nexport const LOCATION_PAGE_RULE = \"location:page/location\";\n\n/** What each reserved rule means, for the consumer's rule listing. */\nexport const RESERVED_RULES: Record<string, string> = {\n [LOCATION_EXTERNAL_RULE]:\n \"The page stores a key its schema prefers external metadata for; run `manni meta relocate` to move it to the collection's manifest.\",\n [LOCATION_PAGE_RULE]:\n \"A manifest stores a key its schema prefers the page for; run `manni meta relocate` to move it into the page.\",\n [ENCRYPTED_PLAIN_RULE]:\n \"A property the schema marks x-manni-encrypt holds a plain value; the page must hold it encrypted.\",\n [ENCRYPTED_UNREADABLE_RULE]:\n \"A property the schema marks x-manni-encrypt holds a value that does not decrypt under the current key.\",\n [PARSE_ERROR_RULE]: \"The document's metadata block could not be parsed.\",\n [SCHEMA_ERROR_RULE]: \"No schema set could be resolved for the document.\",\n [EXTERNAL_OWNED_RULE]:\n \"The document carries a key a manifest owns; the manifest is the only place that key may be set.\",\n [EXTERNAL_DUPLICATE_RULE]:\n \"Two documents carry the same value of a manifest's join field, so one manifest entry matched both.\",\n [DERIVED_STALE_RULE]:\n \"A managed field whose value differs from what git, CODEOWNERS or the GitHub or GitLab review record says; run `manni meta derive` to refresh it.\",\n};\n\n/**\n * The rule id for one violation.\n *\n * docmeta's own failures carry `schema: \"(parse)\"` and no real keyword, so the\n * naive join yields `(parse)/parse` — a rule id that names an implementation\n * detail and reads as garbage in a security tab. They get reserved ids instead.\n *\n * With a `frame`, the schema reference is canonicalized exactly as a\n * fingerprint's is, so a local-file schema does not produce one rule id from\n * the repository root and a different, machine-specific one from `docs/`.\n */\nexport function ruleIdFor(e: FieldError, frame?: FingerprintContext): string {\n if (e.schema === SYNTHETIC_SCHEMA) {\n if (e.keyword === \"parse\") return PARSE_ERROR_RULE;\n if (e.keyword === \"schema\") return SCHEMA_ERROR_RULE;\n }\n return `${canonicalSchemaRef(e.schema, frame)}/${e.keyword}`;\n}\n\n/** `\"\"` is the whole document; every reporter spells that the same way. */\nexport function fieldLabel(instancePath: string): string {\n return instancePath === \"\" ? \"(root)\" : instancePath;\n}\n","/**\n * SARIF 2.1.0 — the interchange format GitHub code scanning, GitLab, and Azure\n * DevOps ingest directly.\n *\n * What `--format github` cannot do is persist: `::error` workflow commands\n * render on the pull request and then vanish with the job log. SARIF findings\n * become tracked alerts with state across commits, which is what makes \"when\n * did this regress\" and \"is metadata debt trending down\" answerable at all.\n *\n * Three properties carry that tracking, and each has a trap:\n *\n * - **`artifactLocation.uri` must be repository-root-relative.** GitHub\n * resolves it against the repository root and *silently drops* results that\n * do not resolve — the upload succeeds, with zero alerts. `ValidationResult`\n * labels are relative to whatever the run resolved against, which is only\n * coincidentally the repository root, so they are rebased here.\n * - **`ruleId` must not be built from prose.** See `./rule-id.ts`.\n * - **`partialFingerprints` must match the baseline's fingerprint.** One\n * identity function, two consumers.\n */\nimport { isAbsolute } from \"node:path\";\nimport pkg from \"../../../package.json\" with { type: \"json\" };\nimport {\n isErrorSeverity,\n type FieldError,\n type ValidationResult,\n} from \"../types.js\";\nimport {\n canonicalFilePath,\n canonicalSchemaRef,\n fingerprint,\n type FingerprintContext,\n} from \"../core/baseline.js\";\nimport { findGitRoot } from \"../core/config.js\";\n// One definition, in core: commands set this label and reporters test for it,\n// so two copies would drift and a `<stdin>` result would acquire an\n// `artifactLocation.uri` naming a file that does not exist.\nimport { STDIN_LABEL } from \"../core/load-files.js\";\nimport { RESERVED_RULES, fieldLabel, ruleIdFor } from \"./rule-id.js\";\n\n/** Where a consumer is sent to read about docmeta itself. */\nconst INFORMATION_URI = \"https://hawkeyexl.github.io/manni/meta/\";\n\n/** Where a consumer is sent to read about one finding. */\nconst HELP_URI = \"https://hawkeyexl.github.io/manni/meta/fix/\";\n\n/**\n * The property-bag key GitHub keys alert identity on. Versioned deliberately,\n * and **not renamed with the tool**: the value is what lets an alert opened by\n * a docmeta run stay the same alert under manni. A new key would re-open every\n * finding once; that is what `/v2` is for, should the fingerprint ever change.\n */\nconst FINGERPRINT_KEY = \"docmetaViolation/v1\";\n\n/** Not a path anyone can resolve, so it can never be an artifact location. */\n// Imported rather than redeclared: two independent copies of this literal\n// would drift silently, and the failure mode is a `<stdin>` result acquiring an\n// `artifactLocation.uri` that names no file.\n\n/**\n * Said once, on stderr, when SARIF paths could not be made\n * repository-root-relative — the `GITIGNORE_UNAVAILABLE` precedent. Degrading\n * in silence is the wrong failure here: a uri that does not resolve produces a\n * *successful* upload with no alerts, so nothing downstream ever complains.\n */\nexport const SARIF_NO_GIT_ROOT =\n \"SARIF paths could not be made repository-root-relative: no git repository was found here. GitHub code scanning resolves artifactLocation.uri against the repository root and drops results that do not resolve, so an upload may report no findings at all.\";\n\nexport interface SarifOptions {\n /** The run's path frame. Without one, `file` labels are emitted as they are. */\n frame?: FingerprintContext;\n /** Diagnostics for the user; the CLI writes these to stderr. */\n onNotice?: (message: string) => void;\n}\n\ntype SarifLevel = \"error\" | \"warning\" | \"note\";\n\n/**\n * SARIF's word for a finding's weight. The family's `error` and `warning`\n * are SARIF's own; its `notice` is SARIF's `note`. An absent severity is\n * `error`, as everywhere (`isErrorSeverity`).\n */\nfunction sarifLevel(e: Parameters<typeof isErrorSeverity>[0]): SarifLevel {\n if (isErrorSeverity(e)) return \"error\";\n return e.severity === \"notice\" ? \"note\" : \"warning\";\n}\n\ninterface SarifRule {\n id: string;\n shortDescription: { text: string };\n helpUri: string;\n}\n\ninterface SarifResult {\n ruleId: string;\n level: SarifLevel;\n message: { text: string };\n locations: {\n physicalLocation: {\n artifactLocation: { uri: string };\n region?: { startLine: number };\n };\n }[];\n partialFingerprints: Record<string, string>;\n}\n\n/**\n * What a rule *is*, in one line.\n *\n * Only the reserved rules get bespoke prose; everything else is named by the\n * schema and keyword that produced it, which is all docmeta actually knows. A\n * per-keyword phrasebook would be prose docmeta would then have to keep in step\n * with Ajv's own vocabulary for no consumer benefit.\n */\nfunction describeRule(id: string, e: FieldError, schemaRef: string): string {\n return (\n RESERVED_RULES[id] ??\n `Schema \"${schemaRef}\" rejected the document on its \"${e.keyword}\" keyword.`\n );\n}\n\n/**\n * Said when a result had to be dropped because its path lies outside the\n * repository.\n *\n * Dropping is the only truthful option — GitHub would drop it anyway, having\n * failed to resolve it — but doing so in silence reproduces the exact failure\n * this reporter exists to prevent: an upload that succeeds while reporting\n * fewer findings than the run actually made.\n */\nexport const sarifDroppedNotice = (n: number): string =>\n `${n} SARIF ${n === 1 ? \"finding lies\" : \"findings lie\"} outside the repository and ${n === 1 ? \"was\" : \"were\"} omitted: an artifactLocation.uri that leaves the repository root cannot be resolved by a code-scanning consumer.`;\n\n/**\n * The frame `artifactLocation.uri` is measured against.\n *\n * Same rebase `canonicalFilePath` already performs for baseline keys, with\n * `base` swapped for the repository root. Returns null when there is nothing to\n * rebase against, which the caller reports rather than papers over.\n */\nfunction uriFrame(frame: FingerprintContext): FingerprintContext | null {\n const root = findGitRoot(frame.cwd);\n return root === null ? null : { ...frame, base: root };\n}\n\n/**\n * A uri SARIF accepts: relative, posix, and never climbing out of the\n * repository.\n *\n * A file above the repository root rebases to `../…`, which SARIF permits but\n * GitHub cannot resolve. There is nothing truthful to emit for it, so the\n * result is dropped rather than pointed at a path that does not exist — the\n * same reasoning that skips `<stdin>`.\n */\nfunction artifactUri(\n file: string,\n frame: FingerprintContext | undefined,\n): string | null {\n const uri = canonicalFilePath(file, frame);\n // An absolute path is not a repository-relative uri either. `relative()`\n // returns one when the two paths share no root, which on Windows is any\n // checkout and temp directory on different drives.\n if (uri === \"\" || uri.startsWith(\"../\") || isAbsolute(uri)) return null;\n return uri;\n}\n\nexport function renderSarif(\n results: ValidationResult[],\n opts: SarifOptions = {},\n): string {\n const { frame, onNotice } = opts;\n\n // Two frames, deliberately: fingerprints stay measured against the config's\n // directory (so a baseline recorded from anywhere matches), while uris are\n // measured against the repository root (so GitHub can resolve them).\n const rebased = frame ? uriFrame(frame) : undefined;\n if (frame && rebased === null) onNotice?.(SARIF_NO_GIT_ROOT);\n // With no repository there is no root to measure from, and falling back to\n // `frame` would measure against the *config's* directory — which for a config\n // outside the tree being validated yields `../…` for every file, and\n // `artifactUri` drops those. That turns \"no git repo\" into \"no findings at\n // all\". Measure from where the run actually resolved its inputs instead: the\n // paths are no longer repository-relative, which the notice above already\n // says, but they are at least the paths the user typed.\n const pathFrame =\n rebased ?? (frame ? { ...frame, base: frame.runBase ?? frame.cwd } : undefined);\n\n const rules = new Map<string, SarifRule>();\n const sarifResults: SarifResult[] = [];\n let dropped = 0;\n\n for (const r of results) {\n if (r.errors.length === 0) continue;\n // `<stdin>` is not a path; nothing is lost by leaving it out, so it is not\n // counted as a drop.\n if (r.file === STDIN_LABEL) continue;\n const docUri = artifactUri(r.file, pathFrame);\n\n for (const e of r.errors) {\n // A value a manifest supplied is located in the manifest (proposal\n // 0037), which may be the one file of the run that *is* inside the\n // repository when the documents are not, or the reverse — so each\n // finding resolves its own uri, and is dropped on its own.\n const uri = e.file != null ? artifactUri(e.file, pathFrame) : docUri;\n if (uri === null) {\n dropped += 1;\n continue;\n }\n const ruleId = ruleIdFor(e, frame);\n if (!rules.has(ruleId)) {\n rules.set(ruleId, {\n id: ruleId,\n shortDescription: {\n text: describeRule(ruleId, e, canonicalSchemaRef(e.schema, frame)),\n },\n helpUri: HELP_URI,\n });\n }\n sarifResults.push({\n ruleId,\n // `FieldError.severity` maps onto SARIF's triage axis directly. Meta's\n // own validation never sets it, so its findings are all `error`; the\n // `warning` and `note` levels are for a sibling tool's advisory\n // findings (#78).\n level: sarifLevel(e),\n message: { text: `${fieldLabel(e.instancePath)} ${e.message}` },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri },\n // SARIF requires `startLine >= 1`. `0` is schema-invalid and `1`\n // silently mislocates the finding at the top of the file, so an\n // unknown line omits the region and renders file-level, which is\n // true. `startColumn` is deliberately still absent: html and xml\n // now populate `col`, but the rest of the region work (endLine,\n // endColumn, snippet) belongs to 0003, and a lone startColumn\n // would be the half of it that misleads.\n ...(e.line != null ? { region: { startLine: e.line } } : {}),\n },\n },\n ],\n partialFingerprints: { [FINGERPRINT_KEY]: fingerprint(e, frame) },\n });\n }\n }\n\n if (dropped > 0) onNotice?.(sarifDroppedNotice(dropped));\n\n return `${JSON.stringify(\n {\n $schema: \"https://json.schemastore.org/sarif-2.1.0.json\",\n version: \"2.1.0\",\n runs: [\n {\n tool: {\n driver: {\n name: \"manni\",\n version: pkg.version,\n informationUri: INFORMATION_URI,\n // Only rules this run actually hit. Enumerating every keyword of\n // every schema would mean compiling and walking each one, for no\n // consumer benefit.\n rules: [...rules.values()],\n },\n },\n results: sarifResults,\n },\n ],\n },\n null,\n 2,\n )}`;\n}\n","/**\n * XML text escaping, shared by every reporter that writes XML.\n *\n * Moved here from meta's JUnit writer when tracevals needed it too, so the\n * family keeps one escaping rule rather than one per tool.\n */\n\nconst XML_ENTITIES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/**\n * Escape every XML metacharacter, and drop the control characters XML 1.0\n * forbids outright.\n *\n * `>` and `'` do not strictly need escaping in every position, but escaping all\n * five unconditionally means no caller has to know which position it is in —\n * which is precisely the reasoning a partial escaper gets wrong. The control\n * character strip is separate: those cannot be represented in XML 1.0 at all,\n * escaped or not, so a stray one makes the whole document unparseable rather\n * than merely mis-rendered. Tab, newline, and carriage return are legal, and\n * kept.\n */\n/**\n * XML 1.0's `Char` production, in full:\n *\n * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]\n *\n * C0 controls are the familiar exclusion, but they are not the only one: a lone\n * surrogate half and the noncharacters U+FFFE/U+FFFF are equally illegal, and\n * both sit in a JavaScript string quite happily — a schema-authored `pattern`\n * or a filename can carry either. Iterating with `for…of` yields whole code\n * points, so a *well-formed* surrogate pair arrives as one astral character\n * (>= U+10000) and is kept; only an unpaired half falls in the D800-DFFF gap\n * and is dropped.\n */\nfunction isXmlChar(code: number): boolean {\n return (\n code === 0x9 ||\n code === 0xa ||\n code === 0xd ||\n (code >= 0x20 && code <= 0xd7ff) ||\n (code >= 0xe000 && code <= 0xfffd) ||\n (code >= 0x10000 && code <= 0x10ffff)\n );\n}\n\nexport function xmlEscape(value: string): string {\n let out = \"\";\n for (const ch of value) {\n const code = ch.codePointAt(0) ?? 0;\n if (!isXmlChar(code)) continue;\n out += XML_ENTITIES[ch] ?? ch;\n }\n return out;\n}\n","/**\n * JUnit XML — what CI systems parse for the \"Tests\" tab.\n *\n * There is no authoritative JUnit schema; Jenkins, GitLab, CircleCI, and Azure\n * each accept a different superset. This writer sticks to the attributes all of\n * them honor (`name`, `tests`, `failures`, `errors`, `classname`, `type`,\n * `message`) and avoids the contested ones — `time`, which would be meaningless\n * here, `system-out`, and nested suites. That is a compatibility judgement, not\n * a specification.\n *\n * **One `<testcase>` per file, one `<failure>` per violation.** So the tab reads\n * \"2 tests, 1 failed\" and matches `2 files checked, 1 failed`.\n * Violation-as-testcase would make the test count rise and fall with document\n * quality, which reads as a suite someone broke.\n *\n * **A warning is not a `<failure>`.** JUnit has no level below failure, and a\n * `<failure>` that did not fail the run would make the tab disagree with the\n * exit code. A warning-only file is a passing testcase; the warning is still\n * in the JSON and SARIF output, which have somewhere to put it.\n *\n * Escaping is the one thing a hand-rolled writer gets wrong. Messages carry\n * schema-authored text — a `pattern` regex may hold `<`, `&`, and quotes — and\n * paths can hold `&`. Every attribute value goes through `xmlEscape`; nothing\n * is interpolated raw.\n */\nimport type { FingerprintContext } from \"../core/baseline.js\";\nimport { isErrorSeverity, type ValidationResult } from \"../types.js\";\nimport { fieldLabel, ruleIdFor } from \"./rule-id.js\";\nimport { xmlEscape } from \"../../shared/xml.js\";\n\nexport { xmlEscape };\n\n/** Suite and classname. One suite per run; nested suites are not portable. */\nconst SUITE_NAME = \"manni\";\nconst CLASS_NAME = \"manni.validate\";\n\nconst attr = (name: string, value: string): string =>\n ` ${name}=\"${xmlEscape(value)}\"`;\n\nexport interface JunitOptions {\n /**\n * The `classname` each `<testcase>` carries: which docmeta command produced\n * these findings. Defaults to `docmeta.validate`, the only producer before\n * proposal 0026 made `query --check` a second one — whose findings must not\n * ship under validate's name.\n */\n classname?: string;\n /**\n * The run's path frame, used only to canonicalize a local-file schema ref in\n * `<failure type>`.\n *\n * Without it the attribute carries the ref exactly as the run received it —\n * `./my.schema.json` from the repo root, `../my.schema.json` from a\n * subdirectory, or a machine-absolute path once config discovery has rebased\n * it. SARIF's `ruleId` is already canonical, so a consumer correlating the two\n * for one run would find them disagreeing on the same violation.\n */\n frame?: FingerprintContext;\n}\n\nexport function renderJunit(\n results: ValidationResult[],\n opts: JunitOptions = {},\n): string {\n const tests = results.length;\n const failures = results.filter((r) => r.errors.some(isErrorSeverity)).length;\n\n const counts =\n attr(\"name\", SUITE_NAME) +\n attr(\"tests\", String(tests)) +\n attr(\"failures\", String(failures)) +\n // Every violation is a `<failure>`; `errors` is reserved for a test that\n // could not run, which has no analogue here. Emitted as 0 rather than\n // omitted, because consumers read the attribute and show a blank column\n // without it.\n attr(\"errors\", \"0\");\n\n const lines: string[] = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n `<testsuites${counts}>`,\n ` <testsuite${counts}>`,\n ];\n\n const classname = opts.classname ?? CLASS_NAME;\n for (const r of results) {\n const open = ` <testcase${attr(\"name\", r.file)}${attr(\"classname\", classname)}`;\n const failing = r.errors.filter(isErrorSeverity);\n if (failing.length === 0) {\n // Self-closing: a passing test has nothing to carry.\n lines.push(`${open}/>`);\n continue;\n }\n lines.push(`${open}>`);\n for (const e of failing) {\n const where =\n e.file != null\n ? ` (${e.file}${e.line != null ? `:${e.line}` : \"\"})`\n : e.line != null\n ? ` (line ${e.line})`\n : \"\";\n lines.push(\n ` <failure${attr(\"type\", ruleIdFor(e, opts.frame))}${attr(\n \"message\",\n `${fieldLabel(e.instancePath)} ${e.message}${where}`,\n )}/>`,\n );\n }\n lines.push(\" </testcase>\");\n }\n\n lines.push(\" </testsuite>\", \"</testsuites>\");\n return lines.join(\"\\n\");\n}\n","/**\n * Reporters render validation results to a string. The command layer writes\n * the result to stdout; diagnostics go to stderr separately.\n */\nimport {\n DocmetaError,\n isErrorSeverity,\n type BaselineSummary,\n type RunSummary,\n type ValidationResult,\n} from \"../types.js\";\nimport type { FingerprintContext } from \"../core/baseline.js\";\nimport { palette } from \"../../shared/color.js\";\nimport { fieldLabel } from \"./rule-id.js\";\nimport { renderSarif, type SarifOptions } from \"./sarif.js\";\nimport { renderJunit, type JunitOptions } from \"./junit.js\";\nimport {\n escapeWorkflowCommandMessage,\n escapeWorkflowCommandProperty,\n} from \"./github.js\";\n\nexport { renderSarif, SARIF_NO_GIT_ROOT } from \"./sarif.js\";\nexport type { SarifOptions } from \"./sarif.js\";\nexport { renderJunit, xmlEscape } from \"./junit.js\";\nexport type { JunitOptions } from \"./junit.js\";\nexport {\n escapeWorkflowCommandMessage,\n escapeWorkflowCommandProperty,\n} from \"./github.js\";\nexport {\n PARSE_ERROR_RULE,\n SCHEMA_ERROR_RULE,\n ruleIdFor,\n} from \"./rule-id.js\";\n\n/**\n * Every value `--format` accepts, in the order help and error messages list\n * them.\n *\n * The list is stated **once**. It used to live in five unlinked places — this\n * union, a separate `Set<string>` in the CLI, the error message, the option\n * description, and the docs — and the `Set` was not tied to the union at all,\n * so widening the union alone type-checked and still rejected the new value at\n * runtime. Deriving the union from the array makes that drift impossible.\n */\nexport const REPORT_FORMATS = [\n \"pretty\",\n \"json\",\n \"github\",\n \"sarif\",\n \"junit\",\n] as const;\n\nexport type ReportFormat = (typeof REPORT_FORMATS)[number];\n\n/**\n * A format list as a sentence fragment: `\"pretty or json\"`,\n * `\"pretty, json, or github\"`.\n *\n * The two-value case is why this is a function rather than one `join`. A pair\n * takes no comma before \"or\", and `get` and `schemas` have said \"Use pretty or\n * json.\" since long before the list was shared — a mechanical\n * `map(...).join(\", \")` would have quietly reworded every one of those messages\n * to \"pretty, or json\".\n */\nexport function formatList(formats: readonly string[]): string {\n // Destructured and guarded rather than indexed. `noUncheckedIndexedAccess`\n // types `formats[0]` as `string | undefined` however the length was checked,\n // so the interpolation below would happily print \"undefined\" — these two\n // guards say what `length < 2` said, in a form the compiler can carry.\n const [first, second] = formats;\n if (first === undefined) return \"\";\n if (second === undefined) return first;\n // Still needed, and not redundant however it reads: a defined `second` proves\n // there are *at least* two, not exactly two. Three or more falls through to\n // the comma-joined form below.\n if (formats.length === 2) return `${first} or ${second}`;\n return formats\n .map((format, i) => (i === formats.length - 1 ? `or ${format}` : format))\n .join(\", \");\n}\n\n/** `\"pretty, json, github, sarif, or junit\"`, for messages and help text. */\nexport const REPORT_FORMAT_LIST = formatList(REPORT_FORMATS);\n\nexport function isReportFormat(value: string): value is ReportFormat {\n return (REPORT_FORMATS as readonly string[]).includes(value);\n}\n\n/**\n * The formats **every** command produces.\n *\n * `get` and `schemas` accept exactly these two, and each used to spell the pair\n * out inline in `src/cli.ts` as `format !== \"pretty\" && format !== \"json\"` with\n * a hand-written \"Use pretty or json.\" beside it — the same stringly-typed\n * drift this module's `REPORT_FORMATS` comment describes, reproduced three more\n * times. Stated once, the guard and the message cannot disagree.\n */\nexport const COMMON_FORMATS = [\"pretty\", \"json\"] as const;\n\nexport type CommonFormat = (typeof COMMON_FORMATS)[number];\n\n/** `\"pretty or json\"`, for messages and help text. */\nexport const COMMON_FORMAT_LIST = formatList(COMMON_FORMATS);\n\nexport function isCommonFormat(value: string): value is CommonFormat {\n return (COMMON_FORMATS as readonly string[]).includes(value);\n}\n\n/**\n * The formats `query` accepts, one six-value list with per-value gates:\n * `pretty | json | csv` render result rows unconditionally (csv is proposal\n * 0029), and the findings three — `github | sarif | junit` — are legal only\n * under `--check` (proposal 0026).\n *\n * A list of its own rather than `REPORT_FORMATS`: `validate`'s list and\n * `query`'s grow independently — `csv` lives here and not there — and one\n * shared const would make every addition to either a change to both.\n */\nexport const QUERY_FORMATS = [\n \"pretty\",\n \"json\",\n \"csv\",\n \"github\",\n \"sarif\",\n \"junit\",\n] as const;\n\nexport type QueryFormat = (typeof QUERY_FORMATS)[number];\n\n/** `\"pretty, json, csv, github, sarif, or junit\"`, for messages and help. */\nexport const QUERY_FORMAT_LIST = formatList(QUERY_FORMATS);\n\nexport function isQueryFormat(value: string): value is QueryFormat {\n return (QUERY_FORMATS as readonly string[]).includes(value);\n}\n\n/**\n * The `query` formats that render **findings** rather than rows — legal only\n * with `--check`, and only when the result carries the `path` column the\n * finding convention is built from. Typed as its own union (not just a set\n * membership test) so the CLI's dispatch can hand a narrowed format straight\n * to `render`, which takes `ReportFormat` and has no `csv` case.\n */\nexport const QUERY_FINDINGS_FORMATS = [\n \"github\",\n \"sarif\",\n \"junit\",\n] as const satisfies readonly QueryFormat[];\n\nexport type QueryFindingsFormat = (typeof QUERY_FINDINGS_FORMATS)[number];\n\n/**\n * A *narrowing* guard on purpose: the CLI's query switch handles the findings\n * formats in an early return, and this is what lets its remaining cases stay\n * compile-time exhaustive over `pretty | json | csv` — a bare `Set.has` would\n * leave the union unnarrowed and force the `never` guard out.\n */\nexport function isQueryFindingsFormat(\n value: QueryFormat,\n): value is QueryFindingsFormat {\n return (QUERY_FINDINGS_FORMATS as readonly string[]).includes(value);\n}\n\n/**\n * Formats something other than a person reads.\n *\n * These own stdout completely: a diagnostic printed alongside them (which\n * config governed the run, say) would corrupt the parse. Colored escape codes\n * would too, which is why the same predicate answers both questions.\n */\nexport const MACHINE_FORMATS: ReadonlySet<ReportFormat> = new Set<ReportFormat>(\n [\"json\", \"github\", \"sarif\", \"junit\"],\n);\n\nexport const isMachineFormat = (format: ReportFormat): boolean =>\n MACHINE_FORMATS.has(format);\n\n/**\n * Formats whose output may legitimately be empty.\n *\n * `github` prints one annotation per finding, so a clean run has nothing to\n * say. Every other format has an **envelope** that must arrive even when it\n * holds nothing: a zero-byte SARIF file makes `upload-sarif` fail outright, and\n * a zero-byte JUnit file reads as a lost report rather than a passing one. So\n * emptiness is a property of the format, not of the text — which is what the\n * CLI's old bare `if (text.length > 0)` guard got wrong.\n */\nexport const OMITTED_WHEN_CLEAN: ReadonlySet<ReportFormat> =\n new Set<ReportFormat>([\"github\"]);\n\nexport interface ReportOptions extends SarifOptions, JunitOptions {\n color?: boolean;\n /** In pretty output, omit passing files. */\n quiet?: boolean;\n}\n\nexport type { FingerprintContext };\n\nconst plural = (n: number, one: string, many: string): string =>\n `${n} ${n === 1 ? one : many}`;\n\n/**\n * The baseline's line in the summary.\n *\n * A baseline is amnesty, and amnesty that goes quiet becomes permanent — so the\n * count is printed on **every** run, including the one where nothing was\n * forgiven, rather than only when something changes. The stale clause is what\n * makes a rename diagnosable: without it the user sees a wall of \"new\" findings\n * with no hint that the fix is a re-record.\n */\nfunction baselineLines(b: BaselineSummary): string[] {\n if (b.written) {\n const added = b.added ?? 0;\n const removed = b.removed ?? 0;\n return [\n `Baseline written to ${b.path}`,\n ` ${plural(b.recorded, \"finding\", \"findings\")} recorded (+${added} new, -${removed} ${\n removed === 1 ? \"no longer occurs\" : \"no longer occur\"\n })`,\n ];\n }\n const head = plural(b.recorded, \"baselined finding\", \"baselined findings\");\n if (b.stale === 0) return [head];\n const stale = b.stale === 1 ? \"1 no longer occurs\" : `${b.stale} no longer occur`;\n return [`${head}, ${stale} — run --write-baseline to prune`];\n}\n\nexport function renderPretty(\n results: ValidationResult[],\n summary: RunSummary,\n opts: ReportOptions = {},\n): string {\n const c = palette(opts.color ?? false);\n const lines: string[] = [];\n\n for (const r of results) {\n // What the baseline forgave here, so a passing file still shows its debt.\n const forgiven =\n r.baselined != null && r.baselined > 0\n ? c.dim(` (${r.baselined} baselined)`)\n : \"\";\n if (r.ok && r.errors.length === 0) {\n if (!opts.quiet) lines.push(`${c.green(\"✓\")} ${r.file}${forgiven}`);\n continue;\n }\n // A file that is `ok` with findings holds only warnings: something to\n // say, so `--quiet` keeps it, but not a failure, so it is not a `✗`.\n const mark = r.ok ? c.yellow(\"⚠\") : c.red(\"✗\");\n lines.push(`${mark} ${r.file}${forgiven}`);\n for (const e of r.errors) {\n // A value a manifest supplied is located in the manifest, not the\n // document, and the location says so (proposal 0037).\n const loc =\n e.file != null\n ? c.dim(` (${e.file}${e.line != null ? `:${e.line}` : \"\"})`)\n : e.line != null\n ? c.dim(` (line ${e.line})`)\n : \"\";\n const level = isErrorSeverity(e)\n ? \"\"\n : `${e.severity === \"notice\" ? c.dim(\"notice\") : c.yellow(\"warning\")} `;\n lines.push(\n ` ${c.cyan(fieldLabel(e.instancePath))} ${level}${e.message}${loc} ${c.dim(\n `[${e.schema}]`,\n )}`,\n );\n }\n }\n\n // Files `.gitignore` took away are named on the summary line, not left to be\n // inferred from a count that quietly shrank. A gate getting quieter without\n // being asked is the dangerous direction of change; this is what makes it\n // auditable. Omitted at zero, which is every run in a clean repo.\n const skipped =\n summary.gitignoreSkipped != null && summary.gitignoreSkipped > 0\n ? `, ${summary.gitignoreSkipped} skipped by .gitignore`\n : \"\";\n // Warnings are named only when there are any, for the same reason as the\n // skip count: every run of meta's own validation has none.\n const warnings =\n summary.warnings != null && summary.warnings > 0\n ? `, ${plural(summary.warnings, \"warning\", \"warnings\")}`\n : \"\";\n const notices =\n summary.notices != null && summary.notices > 0\n ? `, ${plural(summary.notices, \"notice\", \"notices\")}`\n : \"\";\n const summaryText = `${summary.files} file${summary.files === 1 ? \"\" : \"s\"} checked, ${summary.passed} passed, ${summary.failed} failed, ${summary.errors} error${summary.errors === 1 ? \"\" : \"s\"}${warnings}${notices}${skipped}`;\n if (lines.length > 0) lines.push(\"\");\n lines.push(summary.failed > 0 ? c.red(summaryText) : c.green(summaryText));\n if (summary.baseline) {\n for (const l of baselineLines(summary.baseline)) lines.push(c.dim(l));\n }\n return lines.join(\"\\n\");\n}\n\nexport function renderJson(\n results: ValidationResult[],\n summary: RunSummary,\n): string {\n return JSON.stringify({ summary, results }, null, 2);\n}\n\nexport function renderGithub(results: ValidationResult[]): string {\n const lines: string[] = [];\n for (const r of results) {\n for (const e of r.errors) {\n const params = [`file=${escapeWorkflowCommandProperty(e.file ?? r.file)}`];\n if (e.line != null) params.push(`line=${e.line}`);\n if (e.col != null) params.push(`col=${e.col}`);\n // Escaped as one string, after assembly: the schema id and the field\n // label are as capable of carrying a `%` as the message is.\n const msg = escapeWorkflowCommandMessage(\n `[${e.schema}] ${fieldLabel(e.instancePath)} ${e.message}`,\n );\n // The family scale was chosen to be GitHub's, so the level is the\n // severity, and an absent one is `error` (`isErrorSeverity`).\n // `::warning` and `::notice` render inline like `::error` but do not\n // fail the check, which is the severity invariant in GitHub's terms.\n const level = e.severity ?? \"error\";\n lines.push(`::${level} ${params.join(\",\")}::${msg}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport function render(\n format: ReportFormat,\n results: ValidationResult[],\n summary: RunSummary,\n opts: ReportOptions = {},\n): string {\n switch (format) {\n case \"json\":\n return renderJson(results, summary);\n case \"github\":\n return renderGithub(results);\n case \"sarif\":\n return renderSarif(results, opts);\n case \"junit\":\n return renderJunit(results, opts);\n case \"pretty\":\n return renderPretty(results, summary, opts);\n default: {\n // Exhaustive: adding a format to `REPORT_FORMATS` without a case here is\n // a compile error. The throw is for the runtime half — `render` is public\n // API, and before this existed a caller passing \"sarif\" fell through to\n // `pretty` and got a human report where it asked for a machine one.\n const unreachable: never = format;\n throw new DocmetaError(\n `Unknown report format ${JSON.stringify(unreachable)}. Use ${REPORT_FORMAT_LIST}.`,\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
|