paperlint 7.2.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (474) hide show
  1. package/CLAUDE.md +9 -25
  2. package/CONTRIBUTING.md +5 -4
  3. package/README.md +4 -3
  4. package/dist/adapters/bibtex/index.d.ts +3 -0
  5. package/dist/adapters/bibtex/index.d.ts.map +1 -0
  6. package/dist/adapters/bibtex/index.js +103 -0
  7. package/dist/adapters/bibtex/index.js.map +1 -0
  8. package/dist/adapters/latex/filecontents.d.ts +33 -0
  9. package/dist/adapters/latex/filecontents.d.ts.map +1 -0
  10. package/dist/adapters/latex/filecontents.js +65 -0
  11. package/dist/adapters/latex/filecontents.js.map +1 -0
  12. package/dist/adapters/latex/includes.d.ts +3 -3
  13. package/dist/adapters/latex/includes.d.ts.map +1 -1
  14. package/dist/adapters/latex/includes.js +18 -10
  15. package/dist/adapters/latex/includes.js.map +1 -1
  16. package/dist/adapters/latex/index.d.ts.map +1 -1
  17. package/dist/adapters/latex/index.js +2 -0
  18. package/dist/adapters/latex/index.js.map +1 -1
  19. package/dist/adapters/latex/nodes.d.ts +12 -0
  20. package/dist/adapters/latex/nodes.d.ts.map +1 -1
  21. package/dist/adapters/latex/nodes.js +39 -0
  22. package/dist/adapters/latex/nodes.js.map +1 -1
  23. package/dist/adapters/latex/parse.d.ts +7 -1
  24. package/dist/adapters/latex/parse.d.ts.map +1 -1
  25. package/dist/adapters/latex/parse.js +55 -6
  26. package/dist/adapters/latex/parse.js.map +1 -1
  27. package/dist/adapters/memory/index.d.ts +3 -0
  28. package/dist/adapters/memory/index.d.ts.map +1 -1
  29. package/dist/adapters/memory/index.js +9 -0
  30. package/dist/adapters/memory/index.js.map +1 -1
  31. package/dist/adapters/references/index.d.ts.map +1 -1
  32. package/dist/adapters/references/index.js +27 -24
  33. package/dist/adapters/references/index.js.map +1 -1
  34. package/dist/adapters/sources-record/index.d.ts +9 -0
  35. package/dist/adapters/sources-record/index.d.ts.map +1 -0
  36. package/dist/adapters/sources-record/index.js +68 -0
  37. package/dist/adapters/sources-record/index.js.map +1 -0
  38. package/dist/adapters/tex-output/blg.d.ts +9 -0
  39. package/dist/adapters/tex-output/blg.d.ts.map +1 -0
  40. package/dist/adapters/tex-output/blg.js +20 -0
  41. package/dist/adapters/tex-output/blg.js.map +1 -0
  42. package/dist/adapters/tex-output/fls.d.ts +8 -0
  43. package/dist/adapters/tex-output/fls.d.ts.map +1 -0
  44. package/dist/adapters/tex-output/fls.js +16 -0
  45. package/dist/adapters/tex-output/fls.js.map +1 -0
  46. package/dist/adapters/tex-output/index.d.ts +7 -0
  47. package/dist/adapters/tex-output/index.d.ts.map +1 -0
  48. package/dist/adapters/tex-output/index.js +7 -0
  49. package/dist/adapters/tex-output/index.js.map +1 -0
  50. package/dist/bib-rules.d.ts +47 -0
  51. package/dist/bib-rules.d.ts.map +1 -0
  52. package/dist/bib-rules.js +127 -0
  53. package/dist/bib-rules.js.map +1 -0
  54. package/dist/build.d.ts +29 -1
  55. package/dist/build.d.ts.map +1 -1
  56. package/dist/build.js +41 -30
  57. package/dist/build.js.map +1 -1
  58. package/dist/claim-provenance.d.ts +2 -2
  59. package/dist/cli.d.ts +9 -5
  60. package/dist/cli.d.ts.map +1 -1
  61. package/dist/cli.js +43 -68
  62. package/dist/cli.js.map +1 -1
  63. package/dist/domain/paper-source.d.ts +1 -10
  64. package/dist/domain/paper-source.d.ts.map +1 -1
  65. package/dist/domain/paper-source.js +34 -24
  66. package/dist/domain/paper-source.js.map +1 -1
  67. package/dist/domain/paper-sources.d.ts +72 -0
  68. package/dist/domain/paper-sources.d.ts.map +1 -0
  69. package/dist/domain/paper-sources.js +18 -0
  70. package/dist/domain/paper-sources.js.map +1 -0
  71. package/dist/domain/sources-record.d.ts +102 -0
  72. package/dist/domain/sources-record.d.ts.map +1 -0
  73. package/dist/domain/sources-record.js +38 -0
  74. package/dist/domain/sources-record.js.map +1 -0
  75. package/dist/domain/tex-run.d.ts +25 -0
  76. package/dist/domain/tex-run.d.ts.map +1 -0
  77. package/dist/domain/tex-run.js +79 -0
  78. package/dist/domain/tex-run.js.map +1 -0
  79. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  80. package/dist/eslint-rules/latex-language.js +10 -13
  81. package/dist/eslint-rules/latex-language.js.map +1 -1
  82. package/dist/eslint-rules/paper-context.d.ts.map +1 -1
  83. package/dist/eslint-rules/paper-context.js +6 -5
  84. package/dist/eslint-rules/paper-context.js.map +1 -1
  85. package/dist/eslint-rules/paper-typography.d.ts +0 -9
  86. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  87. package/dist/eslint-rules/paper-typography.js +34 -96
  88. package/dist/eslint-rules/paper-typography.js.map +1 -1
  89. package/dist/eslint-rules/papers.d.ts +1 -3
  90. package/dist/eslint-rules/papers.d.ts.map +1 -1
  91. package/dist/eslint-rules/papers.js +2 -4
  92. package/dist/eslint-rules/papers.js.map +1 -1
  93. package/dist/eslint-rules/tex-build.d.ts.map +1 -1
  94. package/dist/eslint-rules/tex-build.js +8 -25
  95. package/dist/eslint-rules/tex-build.js.map +1 -1
  96. package/dist/init.d.ts +1 -4
  97. package/dist/init.d.ts.map +1 -1
  98. package/dist/init.js +3 -9
  99. package/dist/init.js.map +1 -1
  100. package/dist/latex-log.d.ts +10 -0
  101. package/dist/latex-log.d.ts.map +1 -1
  102. package/dist/latex-log.js +10 -0
  103. package/dist/latex-log.js.map +1 -1
  104. package/dist/latex-loop.d.ts +2 -0
  105. package/dist/latex-loop.d.ts.map +1 -1
  106. package/dist/latex-loop.js.map +1 -1
  107. package/dist/lib/markdown.d.ts +5 -81
  108. package/dist/lib/markdown.d.ts.map +1 -1
  109. package/dist/lib/markdown.js +5 -116
  110. package/dist/lib/markdown.js.map +1 -1
  111. package/dist/lib/paper-config.d.ts.map +1 -1
  112. package/dist/lib/paper-config.js +0 -2
  113. package/dist/lib/paper-config.js.map +1 -1
  114. package/dist/lib/skill-eval-fixture.d.ts +6 -4
  115. package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
  116. package/dist/lib/skill-eval-fixture.js +25 -18
  117. package/dist/lib/skill-eval-fixture.js.map +1 -1
  118. package/dist/new-paper.d.ts +5 -11
  119. package/dist/new-paper.d.ts.map +1 -1
  120. package/dist/new-paper.js +12 -23
  121. package/dist/new-paper.js.map +1 -1
  122. package/dist/package-dirs.d.ts.map +1 -1
  123. package/dist/package-dirs.js +3 -2
  124. package/dist/package-dirs.js.map +1 -1
  125. package/dist/paper-files.d.ts +4 -4
  126. package/dist/paper-includes.d.ts +4 -7
  127. package/dist/paper-includes.d.ts.map +1 -1
  128. package/dist/paper-includes.js +29 -21
  129. package/dist/paper-includes.js.map +1 -1
  130. package/dist/paper-prose.d.ts +17 -0
  131. package/dist/paper-prose.d.ts.map +1 -0
  132. package/dist/paper-prose.js +46 -0
  133. package/dist/paper-prose.js.map +1 -0
  134. package/dist/paper-record.d.ts +25 -0
  135. package/dist/paper-record.d.ts.map +1 -0
  136. package/dist/paper-record.js +39 -0
  137. package/dist/paper-record.js.map +1 -0
  138. package/dist/paper-sources.d.ts +66 -0
  139. package/dist/paper-sources.d.ts.map +1 -0
  140. package/dist/paper-sources.js +92 -0
  141. package/dist/paper-sources.js.map +1 -0
  142. package/dist/ports/bib-reader.d.ts +18 -0
  143. package/dist/ports/bib-reader.d.ts.map +1 -0
  144. package/dist/ports/bib-reader.js +2 -0
  145. package/dist/ports/bib-reader.js.map +1 -0
  146. package/dist/ports/check-references.d.ts +9 -4
  147. package/dist/ports/check-references.d.ts.map +1 -1
  148. package/dist/ports/latex.d.ts +15 -0
  149. package/dist/ports/latex.d.ts.map +1 -1
  150. package/dist/ports/sources-codec.d.ts +11 -0
  151. package/dist/ports/sources-codec.d.ts.map +1 -0
  152. package/dist/ports/sources-codec.js +2 -0
  153. package/dist/ports/sources-codec.js.map +1 -0
  154. package/dist/ports/tex-output.d.ts +17 -0
  155. package/dist/ports/tex-output.d.ts.map +1 -0
  156. package/dist/ports/tex-output.js +2 -0
  157. package/dist/ports/tex-output.js.map +1 -0
  158. package/dist/recorded-bibliography.d.ts +89 -0
  159. package/dist/recorded-bibliography.d.ts.map +1 -0
  160. package/dist/recorded-bibliography.js +136 -0
  161. package/dist/recorded-bibliography.js.map +1 -0
  162. package/dist/reference-rules.d.ts +5 -5
  163. package/dist/reference-rules.d.ts.map +1 -1
  164. package/dist/reference-rules.js +150 -77
  165. package/dist/reference-rules.js.map +1 -1
  166. package/dist/references.d.ts +71 -16
  167. package/dist/references.d.ts.map +1 -1
  168. package/dist/references.js +110 -26
  169. package/dist/references.js.map +1 -1
  170. package/dist/register-bands.d.ts +3 -3
  171. package/dist/register-bands.d.ts.map +1 -1
  172. package/dist/register-bands.js.map +1 -1
  173. package/dist/register.d.ts +2 -2
  174. package/dist/sources-record.d.ts +38 -0
  175. package/dist/sources-record.d.ts.map +1 -0
  176. package/dist/sources-record.js +123 -0
  177. package/dist/sources-record.js.map +1 -0
  178. package/dist/sources-rules.d.ts +44 -0
  179. package/dist/sources-rules.d.ts.map +1 -0
  180. package/dist/sources-rules.js +76 -0
  181. package/dist/sources-rules.js.map +1 -0
  182. package/dist/structure.d.ts.map +1 -1
  183. package/dist/structure.js +4 -3
  184. package/dist/structure.js.map +1 -1
  185. package/dist/tex-paper.d.ts +10 -15
  186. package/dist/tex-paper.d.ts.map +1 -1
  187. package/dist/tex-paper.js +24 -40
  188. package/dist/tex-paper.js.map +1 -1
  189. package/dist/tex-venue-rules.d.ts +3 -3
  190. package/dist/tex-venue-rules.d.ts.map +1 -1
  191. package/dist/tex-venue-rules.js.map +1 -1
  192. package/dist/types.d.ts +0 -2
  193. package/dist/types.d.ts.map +1 -1
  194. package/docs/configuration.md +18 -12
  195. package/docs/design/paper-sources.md +197 -0
  196. package/docs/e2e.md +4 -6
  197. package/docs/package-shape-options.md +1 -1
  198. package/docs/prior-art/paper-folder-scaffolding.md +24 -29
  199. package/docs/references.md +34 -8
  200. package/docs/rules/bib/reachable-entry.md +64 -0
  201. package/docs/rules/paper/sources-fresh.md +81 -0
  202. package/docs/rules/tex/claim-provenance.md +0 -1
  203. package/docs/rules.md +62 -61
  204. package/eslint-rules/latex-language.harness.mjs +3 -3
  205. package/eslint-rules/latex-language.ts +10 -13
  206. package/eslint-rules/paper-context.ts +6 -5
  207. package/eslint-rules/paper-typography.test.mjs +29 -157
  208. package/eslint-rules/paper-typography.ts +35 -104
  209. package/eslint-rules/papers.harness.mjs +1 -1
  210. package/eslint-rules/papers.ts +2 -4
  211. package/eslint-rules/tex-build.harness.mjs +2 -2
  212. package/eslint-rules/tex-build.ts +8 -25
  213. package/eslint.config.mjs +22 -1
  214. package/fixtures/build-e2e/aidc/paper.tex +1 -1
  215. package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
  216. package/fixtures/build-e2e/anon-author/paper.tex +1 -1
  217. package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
  218. package/fixtures/build-e2e/anon-href/paper.tex +1 -1
  219. package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
  220. package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
  221. package/fixtures/paper-sources/p1/paper.tex +30 -0
  222. package/fixtures/paper-sources/p1/refs.bib +6 -0
  223. package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
  224. package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
  225. package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
  226. package/fixtures/paper-sources/p1/tex-truth.json +37 -0
  227. package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
  228. package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
  229. package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
  230. package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
  231. package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
  232. package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
  233. package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
  234. package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
  235. package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
  236. package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
  237. package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
  238. package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
  239. package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
  240. package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
  241. package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
  242. package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
  243. package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
  244. package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
  245. package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
  246. package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
  247. package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
  248. package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
  249. package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
  250. package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
  251. package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
  252. package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
  253. package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
  254. package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
  255. package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
  256. package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
  257. package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
  258. package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
  259. package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
  260. package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
  261. package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
  262. package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
  263. package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
  264. package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
  265. package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
  266. package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
  267. package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
  268. package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
  269. package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
  270. package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
  271. package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
  272. package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
  273. package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
  274. package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
  275. package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
  276. package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
  277. package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
  278. package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
  279. package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
  280. package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
  281. package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
  282. package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
  283. package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
  284. package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
  285. package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
  286. package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
  287. package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
  288. package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
  289. package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
  290. package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
  291. package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
  292. package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
  293. package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
  294. package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
  295. package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
  296. package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
  297. package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
  298. package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
  299. package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
  300. package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
  301. package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
  302. package/hooks/hooks.harness.ts +15 -15
  303. package/hooks/paper-edit-guard.hook.mjs +5 -8
  304. package/hooks/paper-edit-guard.test.ts +12 -3
  305. package/hooks/paper-skills-nudge.hook.mjs +2 -3
  306. package/hooks/paper-skills-nudge.test.ts +13 -3
  307. package/hooks/paper-status-gates.hook.mjs +2 -2
  308. package/hooks/paper-status-gates.test.ts +14 -2
  309. package/lib/markdown.test.mjs +5 -60
  310. package/lib/markdown.ts +9 -136
  311. package/lib/paper-config.ts +0 -2
  312. package/lib/skill-checks.test.mjs +4 -1
  313. package/lib/skill-eval-fixture.ts +25 -18
  314. package/package.json +2 -12
  315. package/presets/realm.jsonc +1 -1
  316. package/scripts/check.test.ts +23 -1
  317. package/scripts/check.ts +7 -3
  318. package/scripts/coverage-config.test.ts +19 -10
  319. package/scripts/harness-api.frozen.json +0 -5
  320. package/scripts/layer-legacy-frozen.test.ts +41 -2
  321. package/scripts/layer-legacy-frozen.ts +31 -2
  322. package/skills/cold-read-diff/SKILL.md +3 -3
  323. package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
  324. package/skills/grade-paper-writing/SKILL.md +15 -22
  325. package/skills/grade-paper-writing/SKILL.md.spec.ts +14 -21
  326. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
  327. package/skills/grade-paper-writing/prose-lint.mjs +117 -278
  328. package/skills/grade-paper-writing/prose-lint.test.ts +230 -79
  329. package/skills/paper-adversarial-review/SKILL.md +2 -2
  330. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  331. package/skills/paper-pipeline/SKILL.md +9 -12
  332. package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
  333. package/skills/paper-pipeline/description-language.eval.mjs +25 -15
  334. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
  335. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
  336. package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
  337. package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
  338. package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
  339. package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
  340. package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
  341. package/skills/paper-pipeline/scripts/README.md +0 -4
  342. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +91 -105
  343. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +85 -241
  344. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +166 -153
  345. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
  346. package/skills/paper-status/SKILL.md +2 -2
  347. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  348. package/skills/render-paper/SKILL.md +2 -2
  349. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  350. package/skills/render-paper/extract-pdf-facts.mjs +2 -2
  351. package/skills/tighten-paper/SKILL.md +29 -42
  352. package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
  353. package/skills/verify-citations/SKILL.md +7 -4
  354. package/skills/verify-citations/SKILL.md.spec.ts +6 -3
  355. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -32
  356. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +138 -43
  357. package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
  358. package/skills/verify-citations/scripts/bib-authors.mjs +54 -193
  359. package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
  360. package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
  361. package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
  362. package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
  363. package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
  364. package/src/adapters/bibtex/index.test.ts +163 -0
  365. package/src/adapters/bibtex/index.ts +155 -0
  366. package/src/adapters/latex/filecontents.ts +114 -0
  367. package/src/adapters/latex/includes.ts +18 -10
  368. package/src/adapters/latex/index.test.ts +73 -0
  369. package/src/adapters/latex/index.ts +2 -0
  370. package/src/adapters/latex/nodes.ts +64 -0
  371. package/src/adapters/latex/parse.test.ts +38 -0
  372. package/src/adapters/latex/parse.ts +67 -6
  373. package/src/adapters/memory/index.ts +17 -0
  374. package/src/adapters/references/index.test.ts +75 -21
  375. package/src/adapters/references/index.ts +37 -32
  376. package/src/adapters/sources-record/index.test.ts +87 -0
  377. package/src/adapters/sources-record/index.ts +82 -0
  378. package/src/adapters/tex-output/blg.ts +27 -0
  379. package/src/adapters/tex-output/fls.ts +24 -0
  380. package/src/adapters/tex-output/index.test.ts +171 -0
  381. package/src/adapters/tex-output/index.ts +12 -0
  382. package/src/bib-rules.test.ts +356 -0
  383. package/src/bib-rules.ts +228 -0
  384. package/src/bibliography-at.test.ts +286 -0
  385. package/src/build.harness.mjs +2 -2
  386. package/src/build.test.ts +211 -1
  387. package/src/build.ts +101 -41
  388. package/src/claim-provenance.test.ts +33 -7
  389. package/src/claim-provenance.ts +2 -2
  390. package/src/cli.harness.mjs +46 -56
  391. package/src/cli.test.ts +44 -48
  392. package/src/cli.ts +51 -82
  393. package/src/domain/paper-source.test.ts +58 -20
  394. package/src/domain/paper-source.ts +38 -42
  395. package/src/domain/paper-sources.ts +94 -0
  396. package/src/domain/sources-record.test.ts +71 -0
  397. package/src/domain/sources-record.ts +136 -0
  398. package/src/domain/tex-run.test.ts +228 -0
  399. package/src/domain/tex-run.ts +139 -0
  400. package/src/folder-venue-rule.test.ts +1 -1
  401. package/src/heading-case.test.ts +24 -10
  402. package/src/init.test.ts +6 -7
  403. package/src/init.ts +3 -22
  404. package/src/latex-log.ts +20 -0
  405. package/src/latex-loop.harness.ts +1 -0
  406. package/src/latex-loop.ts +2 -0
  407. package/src/lint-fix.test.ts +10 -4
  408. package/src/linted-set.test.ts +9 -1
  409. package/src/new-paper.harness.mjs +12 -30
  410. package/src/new-paper.test.ts +15 -11
  411. package/src/new-paper.ts +11 -30
  412. package/src/package-dirs.ts +3 -2
  413. package/src/paper-files.test.ts +5 -4
  414. package/src/paper-files.ts +4 -4
  415. package/src/paper-includes.test.ts +157 -38
  416. package/src/paper-includes.ts +39 -35
  417. package/src/paper-prose.test.ts +78 -0
  418. package/src/paper-prose.ts +67 -0
  419. package/src/paper-record.test.ts +173 -0
  420. package/src/paper-record.ts +66 -0
  421. package/src/paper-settings-commands.test.ts +5 -2
  422. package/src/paper-sources.ts +151 -0
  423. package/src/parse-args.test.ts +0 -1
  424. package/src/ports/bib-reader.ts +27 -0
  425. package/src/ports/check-references.ts +7 -3
  426. package/src/ports/latex.ts +19 -0
  427. package/src/ports/sources-codec.ts +11 -0
  428. package/src/ports/tex-output.ts +17 -0
  429. package/src/recorded-bibliography.ts +297 -0
  430. package/src/reference-rules.ts +217 -104
  431. package/src/references.test.ts +520 -177
  432. package/src/references.ts +185 -37
  433. package/src/register-bands.test.ts +5 -1
  434. package/src/register-bands.ts +3 -5
  435. package/src/register.test.ts +6 -1
  436. package/src/register.ts +2 -2
  437. package/src/rule-cases.test.ts +68 -12
  438. package/src/rule-docs.test.ts +0 -1
  439. package/src/sources-record.test.ts +308 -0
  440. package/src/sources-record.ts +231 -0
  441. package/src/sources-rules.test.ts +152 -0
  442. package/src/sources-rules.ts +117 -0
  443. package/src/structure.harness.ts +5 -11
  444. package/src/structure.ts +4 -3
  445. package/src/tex-paper.test.ts +148 -78
  446. package/src/tex-paper.ts +31 -61
  447. package/src/tex-venue-rules.test.ts +36 -8
  448. package/src/tex-venue-rules.ts +3 -5
  449. package/src/types.ts +0 -2
  450. package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
  451. package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
  452. package/dist/eslint-rules/bib-reachable-entry.js +0 -75
  453. package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
  454. package/eslint-rules/bib-reachable-entry.ts +0 -76
  455. package/fixtures/real-markdown-paper/baseline.json +0 -22
  456. package/fixtures/real-markdown-paper/baseline.ts +0 -70
  457. package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
  458. package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
  459. package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
  460. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
  461. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
  462. package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
  463. package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
  464. package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
  465. package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
  466. package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
  467. package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
  468. package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
  469. package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
  470. package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
  471. package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
  472. package/skills/tighten-paper/structure.mjs +0 -364
  473. package/skills/tighten-paper/structure.test.ts +0 -117
  474. package/templates/paper/paper.md +0 -4
@@ -206,7 +206,7 @@ try {
206
206
  // ═══════════════════════════════════════════════════════════════════════════
207
207
  {
208
208
  const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
209
- const P = "docs/papers/alpha/paper.md";
209
+ const P = "docs/papers/alpha/paper.tex";
210
210
  const T = "docs/papers/alpha/paper.tex";
211
211
  const deny = (label: string, cmd: string) => {
212
212
  const r = at(dir, "paper-edit-guard", onBash(cmd));
@@ -228,7 +228,7 @@ try {
228
228
  };
229
229
 
230
230
  // The two cases `touches` alone MISSES: a redirection target is not an argv token.
231
- deny("a redirect write to paper.md", `echo x ${GT} ${P}`);
231
+ deny("a redirect write to paper.tex", `echo x ${GT} ${P}`);
232
232
  deny("an append write to a .tex", `cat /tmp/a ${GT}${GT} ${T}`);
233
233
  deny("sed -i on a paper", `sed -i s/a/b/ ${P}`);
234
234
  deny("cp onto a paper", `cp /tmp/a ${P}`);
@@ -358,8 +358,8 @@ try {
358
358
  // for a gate that had never read the declaration. Caught by its own negative control,
359
359
  // 2026-09-12. A declared root only demonstrates anything when the default would MISS it.
360
360
  const DECLARED = "writing/drafts";
361
- const declaredWrite = onBash(`echo x ${GT} ${DECLARED}/alpha/paper.md`);
362
- const defaultWrite = onBash(`echo x ${GT} papers/alpha/paper.md`);
361
+ const declaredWrite = onBash(`echo x ${GT} ${DECLARED}/alpha/paper.tex`);
362
+ const defaultWrite = onBash(`echo x ${GT} papers/alpha/paper.tex`);
363
363
 
364
364
  // (a) a declared root is used AS DECLARED — and the default is NOT.
365
365
  {
@@ -479,11 +479,11 @@ try {
479
479
  r.stdout.length === 0 && r.stderr.length === 0 && r.exitCode === 0,
480
480
  );
481
481
  };
482
- lands("a relative paper.md", "docs/papers/alpha/paper.md");
482
+ lands("a relative paper.tex", "docs/papers/alpha/paper.tex");
483
483
  lands("a .tex", "docs/papers/alpha/paper.tex");
484
484
  // 🔴 THE ABSOLUTE SPELLING. The live harness sends absolute paths; a helper that only builds
485
485
  // relative ones let an anchored predecessor pass every test while being dead in production.
486
- lands("an ABSOLUTE paper.md", join(dir, "docs/papers/alpha/paper.md"));
486
+ lands("an ABSOLUTE paper.tex", join(dir, "docs/papers/alpha/paper.tex"));
487
487
  silent("a README under the papers root", "docs/papers/README.md");
488
488
  silent("a file outside the papers root", "README.md");
489
489
  // The advisory's own asymmetry: it goes quiet where the gate refuses.
@@ -495,7 +495,7 @@ try {
495
495
  const r = at(
496
496
  broken,
497
497
  "paper-skills-nudge",
498
- onEdit("docs/papers/alpha/paper.md"),
498
+ onEdit("docs/papers/alpha/paper.tex"),
499
499
  );
500
500
  check(
501
501
  "nudge is SILENT (not blocking) on an unusable declaration",
@@ -508,7 +508,7 @@ try {
508
508
  const d = at(
509
509
  broken,
510
510
  "paper-skills-nudge",
511
- onEdit("papers/alpha/paper.md"),
511
+ onEdit("papers/alpha/paper.tex"),
512
512
  );
513
513
  check(
514
514
  "nudge: an explicit null is NOT read as an absence (no fallback to the default root)",
@@ -537,15 +537,15 @@ try {
537
537
  r.stdout.length === 0 && r.stderr.length === 0 && r.exitCode === 0,
538
538
  );
539
539
  };
540
- fires("a relative paper.md", "docs/papers/alpha/paper.md");
541
- fires("an ABSOLUTE paper.md", join(dir, "docs/papers/alpha/paper.md"));
540
+ fires("a relative paper.tex", "docs/papers/alpha/paper.tex");
541
+ fires("an ABSOLUTE paper.tex", join(dir, "docs/papers/alpha/paper.tex"));
542
542
  silent("a README under the papers root", "docs/papers/README.md");
543
543
  silent("a file outside the papers root", "README.md");
544
544
  // 🔴 THE BOUNDARY IN `(?:^|/)`: a directory merely ENDING in the root's last segment must
545
545
  // not smuggle a match. Without the boundary this is a hit.
546
546
  silent(
547
547
  "a look-alike root (`notdocs/papers/…`)",
548
- "notdocs/papers/alpha/paper.md",
548
+ "notdocs/papers/alpha/paper.tex",
549
549
  );
550
550
  // A `.tex` whose directory name carries a shell metacharacter cannot reach a command.
551
551
  silent(
@@ -560,8 +560,8 @@ try {
560
560
  );
561
561
  check(
562
562
  "gates: an explicit null is NOT read as an absence (no fallback to the default root)",
563
- at(broken, "paper-status-gates", onEdit("papers/alpha/paper.md")).stderr
564
- .length === 0,
563
+ at(broken, "paper-status-gates", onEdit("papers/alpha/paper.tex"))
564
+ .stderr.length === 0,
565
565
  );
566
566
  }
567
567
  // The declared root is regex-ESCAPED: `.` in a root must not act as a wildcard.
@@ -576,7 +576,7 @@ try {
576
576
  at(
577
577
  dotted,
578
578
  "paper-status-gates",
579
- onEdit("docs.v2/papers/alpha/paper.md"),
579
+ onEdit("docs.v2/papers/alpha/paper.tex"),
580
580
  ).stderr,
581
581
  ),
582
582
  );
@@ -585,7 +585,7 @@ try {
585
585
  at(
586
586
  dotted,
587
587
  "paper-status-gates",
588
- onEdit("docsXv2/papers/alpha/paper.md"),
588
+ onEdit("docsXv2/papers/alpha/paper.tex"),
589
589
  ).stderr.length === 0,
590
590
  );
591
591
  }
@@ -186,7 +186,7 @@ export const papersRoot = (rawConfig) => {
186
186
  /**
187
187
  * Redirection targets (`> f`, `>> f`, `1> f`, `&> f`), which `touches` cannot see.
188
188
  *
189
- * MEASURED, not assumed: `leafCommandsNormalized("echo x > p/x/paper.md")` returns
189
+ * MEASURED, not assumed: `leafCommandsNormalized("echo x > p/x/paper.tex")` returns
190
190
  * `[{head:"echo", argv:["echo","x"], args:["x"], …}]` — the redirection and its target are
191
191
  * DROPPED by the parser, so they appear in no field of any leaf. The closed vocabulary
192
192
  * (`runs` / `touches` / `pipesToShell` / `isSideEffecting`) has no way to ask what a command
@@ -196,9 +196,9 @@ export const papersRoot = (rawConfig) => {
196
196
  * 🔴 THE SCAN IS QUOTE-AWARE, and the version before it was not. It stripped quoted spans
197
197
  * wholesale, so a quoted redirection target vanished with them. MEASURED on this guard:
198
198
  *
199
- * echo x > <root>/x/paper.md → blocked
200
- * echo x > "<root>/x/paper.md" → ALLOWED
201
- * echo x > '<root>/x/paper.md' → ALLOWED
199
+ * echo x > <root>/x/paper.tex → blocked
200
+ * echo x > "<root>/x/paper.tex" → ALLOWED
201
+ * echo x > '<root>/x/paper.tex' → ALLOWED
202
202
  *
203
203
  * Stripping was not merely careless: a `>` INSIDE quotes is not a redirection at all, so the
204
204
  * guard has to know about quoting or it fires on any command that merely QUOTES a paper write —
@@ -376,10 +376,7 @@ const runsMutator = (cmd) => MUTATORS.some((m) => cmd.runs(m));
376
376
  */
377
377
  const isPaperSource = (p) => !isFrozenSnapshot(p) && hasSourceExtension(p);
378
378
 
379
- const hasSourceExtension = (p) =>
380
- p.endsWith(".tex") ||
381
- /\/(paper|draft)\.md$/.test(p) ||
382
- /^(paper|draft)\.md$/.test(p);
379
+ const hasSourceExtension = (p) => p.endsWith(".tex");
383
380
 
384
381
  /**
385
382
  * A FROZEN SNAPSHOT — a file under a paper's `versions/`, which is the same carve-out as the
@@ -15,7 +15,7 @@ const dir = hookConsumer(useTempDir("paper-edit-guard-"), {
15
15
  });
16
16
  /** `>` built rather than typed: a literal one here would be a redirection to the guard's own scan. */
17
17
  const GT = String.fromCharCode(62);
18
- const P = "papers/alpha/paper.md";
18
+ const P = "papers/alpha/paper.tex";
19
19
 
20
20
  /** The guard's exit code for one Bash command: 2 denies, 0 lets it through. */
21
21
  const guard = (command: string): number =>
@@ -35,8 +35,8 @@ test("a `>` inside quotes, after an escaped quote, or escaped itself is not a re
35
35
  test("a redirection target is read through backslashes, quoted or not, and ends at a delimiter", () => {
36
36
  assert.deepEqual(
37
37
  [
38
- guard(`echo x ${GT} "papers/alpha/pa\\per.md"`),
39
- guard(`echo x ${GT} papers/alpha/pa\\per.md`),
38
+ guard(`echo x ${GT} "papers/alpha/pa\\per.tex"`),
39
+ guard(`echo x ${GT} papers/alpha/pa\\per.tex`),
40
40
  guard(`echo x ${GT}${P}; true`),
41
41
  ],
42
42
  [2, 2, 2],
@@ -55,3 +55,12 @@ test("a command cut off right after a backslash still names its target, quoted o
55
55
  [2, 2],
56
56
  );
57
57
  });
58
+
59
+ test("only paper.tex is a paper source: copying over another file of the paper is let through", () => {
60
+ // Guards: the extension filter of the argv leg. (A redirection is refused for ANY file under the
61
+ // papers root, so the argv leg is the one that says which files are sources.)
62
+ assert.deepEqual(
63
+ [guard("cp /tmp/x papers/alpha/notes.txt"), guard(`cp /tmp/x ${P}`)],
64
+ [0, 2],
65
+ );
66
+ });
@@ -96,9 +96,8 @@ const papersRoot = (rawConfig) => {
96
96
  return root;
97
97
  };
98
98
 
99
- /** A paper SOURCE — a `.tex`, or a markdown-drafted `paper.md` / `draft.md`. Not every note or README. */
100
- const isPaperSource = (p) =>
101
- p.endsWith(".tex") || /\/(paper|draft)\.md$/.test(p);
99
+ /** A paper SOURCE — a `.tex`. Not every note or README. */
100
+ const isPaperSource = (p) => p.endsWith(".tex");
102
101
 
103
102
  const CHECKLIST = `📄 Editing a paper — drive it with the \`paper-pipeline\` skill, don't wing the review.
104
103
  → Update <paper-dir>/PIPELINE-STATUS.md: mark the row for any stage you (re)ran, and read its
@@ -10,11 +10,11 @@ import { useTempDir } from "../test/support.ts";
10
10
 
11
11
  const root = useTempDir("paper-skills-nudge-");
12
12
 
13
- test("no paperlint.json: an edit to papers/<p>/paper.md gets the checklist", () => {
13
+ test("no paperlint.json: an edit to papers/<p>/paper.tex gets the checklist", () => {
14
14
  const dir = hookConsumer(join(root, "default"));
15
15
  const r = runShippedHook(
16
16
  "paper-skills-nudge",
17
- onEdit("papers/alpha/paper.md"),
17
+ onEdit("papers/alpha/paper.tex"),
18
18
  dir,
19
19
  );
20
20
  assert.equal(r.exitCode, 0);
@@ -27,7 +27,17 @@ test("a paperlint.json that does not parse: silent, exit 0", () => {
27
27
  });
28
28
  const r = runShippedHook(
29
29
  "paper-skills-nudge",
30
- onEdit("papers/alpha/paper.md"),
30
+ onEdit("papers/alpha/paper.tex"),
31
+ dir,
32
+ );
33
+ assert.deepEqual([r.exitCode, r.stdout, r.stderr], [0, "", ""]);
34
+ });
35
+
36
+ test("an edit to a file of the paper other than its source: silent", () => {
37
+ const dir = hookConsumer(join(root, "not-a-source"));
38
+ const r = runShippedHook(
39
+ "paper-skills-nudge",
40
+ onEdit("papers/alpha/notes.txt"),
31
41
  dir,
32
42
  );
33
43
  assert.deepEqual([r.exitCode, r.stdout, r.stderr], [0, "", ""]);
@@ -31,7 +31,7 @@
31
31
  * harness's own edit helper builds a RELATIVE path. The live harness sends an ABSOLUTE one.
32
32
  * Measured against the real runtime:
33
33
  *
34
- * file_path "<root>/<paper>/paper.md" → fires
34
+ * file_path "<root>/<paper>/paper.tex" → fires
35
35
  * file_path "/abs/path/to/repo/<root>/<paper>/…" → SILENT
36
36
  *
37
37
  * So the hook would have been dead in production and green in the tests: precisely the
@@ -120,7 +120,7 @@ const papersRoot = (rawConfig) => {
120
120
  */
121
121
  const paperSourceRe = (root) =>
122
122
  new RegExp(
123
- `(?:^|/)${root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/([A-Za-z0-9._-]+)/(?:[^/]*\\.tex|(?:.*/)?(?:paper|draft)\\.md)$`,
123
+ `(?:^|/)${root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/([A-Za-z0-9._-]+)/[^/]*\\.tex$`,
124
124
  );
125
125
 
126
126
  /**
@@ -17,7 +17,7 @@ test("no paperlint.json: an edit under papers/ runs the gates for that paper", (
17
17
  });
18
18
  const r = runShippedHook(
19
19
  "paper-status-gates",
20
- onEdit("papers/alpha/paper.md"),
20
+ onEdit("papers/alpha/paper.tex"),
21
21
  dir,
22
22
  );
23
23
  assert.equal(r.exitCode, 0);
@@ -31,7 +31,19 @@ test("a paperlint.json that does not parse: silent, exit 0", () => {
31
31
  });
32
32
  const r = runShippedHook(
33
33
  "paper-status-gates",
34
- onEdit("papers/alpha/paper.md"),
34
+ onEdit("papers/alpha/paper.tex"),
35
+ dir,
36
+ );
37
+ assert.deepEqual([r.exitCode, r.stdout, r.stderr], [0, "", ""]);
38
+ });
39
+
40
+ test("an edit to a file of the paper other than its source: no gates run", () => {
41
+ const dir = hookConsumer(join(root, "not-a-source"), {
42
+ "papers/alpha/PIPELINE-STATUS.md": STATUS,
43
+ });
44
+ const r = runShippedHook(
45
+ "paper-status-gates",
46
+ onEdit("papers/alpha/notes.txt"),
35
47
  dir,
36
48
  );
37
49
  assert.deepEqual([r.exitCode, r.stdout, r.stderr], [0, "", ""]);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * markdown.mjs — the markup readers every script shares, on the shapes their callers rely on:
3
3
  * frontmatter blanked (offsets kept) or cut, headings from the parser (not from a `#` inside a
4
- * fence), sections by level, fences, paragraphs and tables. And the one thing a test run never
4
+ * fence), and fences. And the one thing a test run never
5
5
  * sees by accident: markdown-it failing to resolve, where every reader must degrade to its
6
6
  * documented empty value and `requireMarkdown` must refuse with exit 2.
7
7
  */
@@ -17,11 +17,8 @@ import {
17
17
  frontmatterBlock,
18
18
  headings,
19
19
  requireMarkdown,
20
- paragraphs,
21
- splitSections,
22
20
  stripFences,
23
21
  stripFrontmatter,
24
- tables,
25
22
  } from "./markdown.ts";
26
23
 
27
24
  const DOC = [
@@ -93,27 +90,6 @@ test("an empty ATX heading has empty text", () => {
93
90
  );
94
91
  });
95
92
 
96
- test("splitSections: a preamble always first, then each heading of the level range", () => {
97
- assert.deepEqual(
98
- splitSections(DOC, { min: 2, max: 2 }).map((s) => [
99
- s.heading?.text ?? null,
100
- s.body.split("\n")[0],
101
- ]),
102
- [
103
- [null, "---"],
104
- ["One", "Body one."],
105
- ["Two", "last"],
106
- ],
107
- );
108
- assert.deepEqual(
109
- splitSections("## A").map((s) => [s.heading?.text ?? null, s.body]),
110
- [
111
- [null, ""],
112
- ["A", ""],
113
- ],
114
- );
115
- });
116
-
117
93
  test("fences and indented code, as content; stripped as lines, or blanked in place", () => {
118
94
  const src = "a\n```\nx\n```\n\n y\nb";
119
95
  assert.deepEqual(
@@ -126,30 +102,6 @@ test("fences and indented code, as content; stripped as lines, or blanked in pla
126
102
  );
127
103
  });
128
104
 
129
- test("paragraphs are line ranges; tables are rows of cells, the alignment row skipped", () => {
130
- assert.deepEqual(
131
- { paragraphs: paragraphs(DOC), tables: tables(DOC) },
132
- {
133
- paragraphs: [
134
- { line: 6, endLine: 7 },
135
- { line: 9, endLine: 10 },
136
- { line: 13, endLine: 14 },
137
- { line: 27, endLine: 28 },
138
- ],
139
- tables: [
140
- {
141
- line: 22,
142
- endLine: 25,
143
- rows: [
144
- ["a", "b"],
145
- ["1", "2"],
146
- ],
147
- },
148
- ],
149
- },
150
- );
151
- });
152
-
153
105
  const root = useTempDir("markdown-no-parser-");
154
106
  writeTree(root, {
155
107
  // Registered with --import: resolving `markdown-it` fails, as it does with no node_modules.
@@ -159,7 +111,7 @@ writeTree(root, {
159
111
  "probe.mjs":
160
112
  `const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"))});\n` +
161
113
  "const t = '# H\\n\\n```\\nx\\n```\\n';\n" +
162
- "console.log(JSON.stringify([m.MD_AVAILABLE(), m.headings(t), m.fences(t), m.stripFences(t), m.paragraphs(t), m.tables(t)]));\n" +
114
+ "console.log(JSON.stringify([m.MD_AVAILABLE(), m.headings(t), m.fences(t), m.stripFences(t)]));\n" +
163
115
  "m.requireMarkdown();\n" +
164
116
  "console.log('not reached');\n",
165
117
  "probe-plugin.mjs":
@@ -188,22 +140,15 @@ test("with no markdown-it, every reader degrades to its empty value and requireM
188
140
  });
189
141
  assert.deepEqual(r, {
190
142
  status: 2,
191
- stdout:
192
- JSON.stringify([false, [], [], "# H\n\n```\nx\n```\n", [], []]) + "\n",
143
+ stdout: JSON.stringify([false, [], [], "# H\n\n```\nx\n```\n"]) + "\n",
193
144
  stderr:
194
145
  "markdown-it does not resolve — this script parses markup with a parser and without it would produce a confident zero instead of an error.\n" +
195
146
  " Cured by `npm i` at the repo root.\n",
196
147
  });
197
148
  });
198
149
 
199
- test("splitSections of a document with no heading is the preamble alone; requireMarkdown is silent with a parser", () => {
200
- assert.deepEqual(
201
- [
202
- splitSections("just text").map((s) => [s.heading, s.body]),
203
- requireMarkdown(),
204
- ],
205
- [[[null, "just text"]], undefined],
206
- );
150
+ test("requireMarkdown is silent with a parser", () => {
151
+ assert.equal(requireMarkdown(), undefined);
207
152
  });
208
153
 
209
154
  test("headings read a token stream that breaks markdown-it's shape as empty text and offset 0", () => {
package/lib/markdown.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * markdown.mjs — markup parsing for EVERYTHING in this repo that reads markdown.
3
3
  *
4
- * Three kinds of consumer: two advisory linters (`kb-lint`, `paper-lint` — through the
5
- * re-export from `lint-core.mjs`) and skill scripts (`tighten-paper/structure.mjs`,
6
- * `grade-paper-writing/prose-lint.mjs`, `paper-pipeline/scripts/*`). Hence the home is
7
- * here and not in the linter core: importing "the linter core" from a skill script
8
- * would be a lie about the direction of the dependency.
4
+ * Its consumers read the package's own markdown — skills' `SKILL.md` frontmatter
5
+ * (`skill-corpus.mjs`), fenced commands — and a consumer's linters of its own files.
6
+ * Hence the home is here and not in a linter core: importing "the linter core" from a
7
+ * skill script would be a lie about the direction of the dependency.
9
8
  *
10
9
  * CHECKS NOTHING AND PRINTS NOTHING: text in, data out. What to do with a missing
11
10
  * parser is the caller's decision, and the decisions DIFFER:
@@ -43,13 +42,6 @@ export interface Heading {
43
42
  readonly offset: number;
44
43
  }
45
44
 
46
- /** A chunk of the document: the heading it starts with (`null` for the preamble), with and without it. */
47
- export interface Section {
48
- readonly heading: Heading | null;
49
- readonly raw: string;
50
- readonly body: string;
51
- }
52
-
53
45
  /** A block tokenizer with markdown-it's shape — the default, or one a test passes. */
54
46
  export type Parse = (text: string) => readonly Token[];
55
47
 
@@ -169,56 +161,6 @@ export function headings(
169
161
  return out;
170
162
  }
171
163
 
172
- /**
173
- * Slicing the document into chunks "a heading + everything up to the next heading FROM
174
- * THE SAME RANGE of levels". → `[{ heading, raw, body }]`.
175
- *
176
- * This is exactly what four scripts did as `split(/^(?=## )/m)` and
177
- * `split(/^(?=#{2,3} )/m)`. The shape of the expression lied about the intent: "two or
178
- * three hashes" is about CHARACTERS, while what is needed is the LEVEL, and `#{2,3}`
179
- * also caught a hash inside a ```-block, that is, it cut a quotation from someone
180
- * else's paper as a section of its own.
181
- *
182
- * `heading` — `null` on the zeroth element (the preamble before the first heading). It
183
- * is ALWAYS there, including empty: the former `split` also always returned the chunk
184
- * before the first separator as its zeroth element, and callers rely on that
185
- * (`sub === chunk` in `artifact-coverage` meant "there are no subsections").
186
- *
187
- * `raw` — WITH the heading line (it is used to count `§` references and bold numbers,
188
- * which occur in the heading itself too), `body` — without it (it is used to count words).
189
- *
190
- * 2026-08-11: moved here rather than copied into every script, because the chunk
191
- * boundary is the one place where an off-by-one is easy, and such a mistake has already
192
- * happened (`recordBlock` in `skill-corpus.mjs` returned a single "#" character for all
193
- * 21 skills).
194
- */
195
- export function splitSections(
196
- text: string,
197
- { min = 2, max = 6 }: { min?: number; max?: number } = {},
198
- ): Section[] {
199
- const hs = headings(text).filter((h) => h.depth >= min && h.depth <= max);
200
- const out: Section[] = [];
201
- const [first] = hs;
202
- const preEnd = first ? first.offset : text.length;
203
- out.push({
204
- heading: null,
205
- raw: text.slice(0, preEnd),
206
- body: text.slice(0, preEnd),
207
- });
208
- for (const [i, h] of hs.entries()) {
209
- const next = hs[i + 1];
210
- const end = next ? next.offset : text.length;
211
- const nl = text.indexOf("\n", h.offset);
212
- const bodyStart = nl === -1 || nl + 1 > end ? end : nl + 1;
213
- out.push({
214
- heading: h,
215
- raw: text.slice(h.offset, end),
216
- body: text.slice(bodyStart, end),
217
- });
218
- }
219
- return out;
220
- }
221
-
222
164
  /**
223
165
  * The text without the contents of ```-blocks (the fence lines themselves go too).
224
166
  *
@@ -229,14 +171,11 @@ export function splitSections(
229
171
  * `blank: true` — the block's lines are NOT deleted but REPLACED with empty ones. The
230
172
  * difference is not cosmetic: deleting the lines GLUES the paragraph before the block to
231
173
  * the paragraph after it, and a code block is a block boundary — on the other side a
232
- * different paragraph starts. Measured 2026-08-11 on `<papers-root>/<paper>/README.md`:
233
- * `prose-lint` counted 82 sentences there, and after the lines were deleted it counted
234
- * 81 — the lead paragraph "**Reproduction commands:**" merged with the text AFTER the
235
- * block into one sentence. That is exactly the failure against which `prose-lint` itself
236
- * says "Paragraph and heading boundaries END a sentence", and it inflates the
237
- * claims-per-sentence counter — the threshold at which the file emits findings. The
238
- * former regex preserved the boundary (it left one newline on each side), so `blank` is
239
- * also a preservation of the former behaviour where that behaviour was right.
174
+ * different paragraph starts. Measured 2026-08-11 on a README: a sentence counter counted
175
+ * 82 sentences there, and after the lines were deleted it counted 81 — the lead paragraph
176
+ * "**Reproduction commands:**" merged with the text AFTER the block into one sentence.
177
+ * The former regex preserved the boundary (it left one newline on each side), so `blank`
178
+ * is also a preservation of the former behaviour where that behaviour was right.
240
179
  */
241
180
  /**
242
181
  * The CONTENTS of fenced blocks — the other side of `stripFences`.
@@ -304,69 +243,3 @@ export function requireMarkdown({
304
243
  );
305
244
  process.exit(2);
306
245
  }
307
-
308
- /**
309
- * GFM tables — rows, cells and the line range, WITH THE PARSER.
310
- *
311
- * The only consumer as of 2026-08-20 is `crosspost-article`, which needs to re-lay a
312
- * table into a monospace block for a platform without tables (Medium). It lives here
313
- * and not in the skill, by the rule "markup is parsed with a parser, and in one place":
314
- * with a regex a table is told apart from a line of text with pipes only by its
315
- * neighbours, and that is exactly the class that has already cost this repo 27 places.
316
- *
317
- * Returns `{ line, endLine, rows }`, where `rows[0]` is the header; the alignment row
318
- * (`| --- |`) is thrown away, because in monospace output it has nothing to express.
319
- */
320
- /**
321
- * The document's paragraphs — `paragraph_open` line ranges, WITH THE PARSER.
322
- * `[{ line, endLine }]`.
323
- *
324
- * The only consumer as of 2026-08-21 is `crosspost-article`: the blog's sources are
325
- * hard-wrapped at a column (an editorial habit, not markdown), and the platforms render
326
- * a single `\n` inside a paragraph DIFFERENTLY — dev.to (Redcarpet) keeps it as a
327
- * literal line break, and the paragraph arrives torn into short lines exactly at the
328
- * source file's boundaries. Medium has no such defect not because the platform is more
329
- * honest, but because the paste goes through "Import a story" — the importer takes the
330
- * ALREADY RENDERED page of the site (where the breaks were collapsed by the browser long
331
- * ago), not the raw markdown.
332
- *
333
- * The range is EXACTLY the one `t.map` gives on `paragraph_open` — that is, a code
334
- * fence, a heading, a table, a list (in a TIGHT list the item is not wrapped in a
335
- * `paragraph_open` at all, it has no paragraph of its own) fall outside this function
336
- * automatically, by the construction of the parser, and not by a separate line-type
337
- * check.
338
- */
339
- export function paragraphs(text: string): { line: number; endLine: number }[] {
340
- if (md === null) return [];
341
- return md
342
- .parse(blankFrontmatter(text), {})
343
- .flatMap((t) =>
344
- t.type === "paragraph_open" && t.map
345
- ? [{ line: t.map[0], endLine: t.map[1] }]
346
- : [],
347
- );
348
- }
349
-
350
- export function tables(
351
- text: string,
352
- ): { line: number; endLine: number; rows: string[][] }[] {
353
- if (md === null) return [];
354
- const lines = blankFrontmatter(text).split("\n");
355
- const out: { line: number; endLine: number; rows: string[][] }[] = [];
356
- for (const t of md.parse(blankFrontmatter(text), {})) {
357
- if (t.type !== "table_open" || !t.map) continue;
358
- const rows: string[][] = [];
359
- for (const raw of lines.slice(t.map[0], t.map[1])) {
360
- if (/^\s*\|?[\s:|-]+\|?\s*$/.test(raw) && raw.includes("-")) continue; // alignment
361
- rows.push(
362
- raw
363
- .replace(/^\s*\|/, "")
364
- .replace(/\|\s*$/, "")
365
- .split("|")
366
- .map((c) => c.trim()),
367
- );
368
- }
369
- out.push({ line: t.map[0], endLine: t.map[1], rows });
370
- }
371
- return out;
372
- }
@@ -49,8 +49,6 @@ export const CONFIG_FILE: string = "paperlint.json";
49
49
  */
50
50
  export const PAPER_MARKERS: readonly string[] = Object.freeze([
51
51
  "paper.tex",
52
- "paper.md",
53
- "draft.md",
54
52
  "PIPELINE-STATUS.md",
55
53
  ]);
56
54
 
@@ -236,7 +236,10 @@ test("a Bash entry narrowed to the instructed script permits it, and one narrowe
236
236
  await brokenBy("verify-citations", narrowTo("extract-ref-facts.mjs")),
237
237
  null,
238
238
  );
239
- const lines = await brokenBy("verify-citations", narrowTo("round-diff.mjs"));
239
+ const lines = await brokenBy(
240
+ "verify-citations",
241
+ narrowTo("generated-code.mjs"),
242
+ );
240
243
  assert.equal(
241
244
  lines?.[0],
242
245
  '2 assertion(s) failed for skill "verify-citations":',