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
package/src/cli.test.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import assert from "node:assert/strict";
8
8
  import { spawn } from "node:child_process";
9
- import { existsSync } from "node:fs";
9
+ import { existsSync, readdirSync } from "node:fs";
10
10
  import { createServer } from "node:http";
11
11
  import { dirname, join } from "node:path";
12
12
  import { test } from "vitest";
@@ -33,13 +33,18 @@ import { texInputsDir } from "./package-dirs.ts";
33
33
  const root = useTempDir("cli-test-");
34
34
 
35
35
  /** `run(argv)` from `cwd`, the output as one string per stream. */
36
- async function cli(argv: string[], cwd: string) {
36
+ async function cli(
37
+ argv: string[],
38
+ cwd: string,
39
+ deps: Pick<NonNullable<Parameters<typeof run>[1]>, "loadTexLanguage"> = {},
40
+ ) {
37
41
  const out: string[] = [];
38
42
  const err: string[] = [];
39
43
  const code = await run(argv, {
40
44
  log: (...a: unknown[]) => out.push(a.join(" ")),
41
45
  err: (...a: unknown[]) => err.push(a.join(" ")),
42
46
  cwd,
47
+ ...deps,
43
48
  });
44
49
  return { code, out: out.join("\n"), err: err.join("\n") };
45
50
  }
@@ -50,14 +55,14 @@ test("parseArgs: the boolean flags of build and toolchain, and a value flag with
50
55
  [
51
56
  parseArgs(["build", "--all", "--dry-run", "papers/p"]),
52
57
  parseArgs(["toolchain", "--check"]),
53
- parseArgs(["new", "x", "--format"]),
58
+ parseArgs(["new", "x", "--paper"]),
54
59
  parseArgs(["new", "x", "--venue="]),
55
60
  parseArgs(["new", "x", "--kind", "short", "--max-warnings=3"]),
56
61
  ],
57
62
  [
58
63
  { ...base, cmd: "build", all: true, dryRun: true, paths: ["papers/p"] },
59
64
  { ...base, cmd: "toolchain", check: true },
60
- { ...base, cmd: "new", paths: ["x"], missingValue: "--format" },
65
+ { ...base, cmd: "new", paths: ["x"], missingValue: "--paper" },
61
66
  { ...base, cmd: "new", paths: ["x"], missingValue: "--venue" },
62
67
  { ...base, cmd: "new", paths: ["x"], kind: "short", maxWarnings: 3 },
63
68
  ],
@@ -142,20 +147,18 @@ test("build refuses with no target, and --all over no papers", async () => {
142
147
  );
143
148
  });
144
149
 
145
- test("new refuses an unknown format, a broken config, and a config that names no papers directory", async () => {
150
+ test("new refuses a broken config, and a config that names no papers directory", async () => {
146
151
  const broken = join(root, "new-broken");
147
152
  writeTree(broken, { "paperlint.json": "{ not json" });
148
153
  const none = join(root, "new-none");
149
154
  writeTree(none, { "paperlint.json": JSON.stringify({ papersDir: [] }) });
150
155
  const results = [
151
- await cli(["new", "p", "--format", "docx"], root),
152
- await cli(["new", "p", "--format", "md"], broken),
153
- await cli(["new", "p", "--format", "md"], none),
156
+ await cli(["new", "p"], broken),
157
+ await cli(["new", "p"], none),
154
158
  ];
155
159
  assert.deepEqual(
156
160
  results.map((r) => ({ code: r.code, err: r.err.split(":")[0] })),
157
161
  [
158
- { code: 2, err: "--format must be one of tex, md — got `docx`" },
159
162
  { code: 2, err: join(broken, "paperlint.json is not valid JSON") },
160
163
  {
161
164
  code: 2,
@@ -170,7 +173,7 @@ test("new with several papers directories says which one it used", async () => {
170
173
  writeTree(dir, {
171
174
  "paperlint.json": JSON.stringify({ papersDir: ["a", "b"] }),
172
175
  });
173
- const r = await cli(["new", "p", "--format", "md", "--yes"], dir);
176
+ const r = await cli(["new", "p", "--yes"], dir);
174
177
  assert.equal(
175
178
  r.out.split("\n")[0],
176
179
  "several papers directories are declared — using the first: a",
@@ -274,20 +277,20 @@ async function withoutTex<T>(dir: string, fn: () => Promise<T>): Promise<T> {
274
277
  const noTexDir = join(root, "build-notex");
275
278
  writeTree(noTexDir, {
276
279
  "package.json": "{}",
277
- "papers/md/paper.md": "# P\n\nx\n",
280
+ "papers/nosource/notes.txt": "x\n",
278
281
  "papers/tex/paper.tex":
279
282
  "\\documentclass{article}\\begin{document}x\\end{document}\n",
280
283
  });
281
284
  const venues = texInputsDir();
282
285
 
283
- test("build: a markdown-only paper is refused", async () => {
284
- const md = await withoutTex(noTexDir, () =>
285
- cli(["build", "papers/md"], noTexDir),
286
+ test("build: a paper with no paper.tex is refused, naming paper.tex", async () => {
287
+ const none = await withoutTex(noTexDir, () =>
288
+ cli(["build", "papers/nosource"], noTexDir),
286
289
  );
287
- assert.deepEqual(md, {
290
+ assert.deepEqual(none, {
288
291
  code: 1,
289
292
  out: [
290
- "papers/md",
293
+ "papers/nosource",
291
294
  ` inputs: TEXINPUTS += the paper's directory, then ${venues}`,
292
295
  " compile: refused — no paper.tex; paperlint compiles LaTeX, and this paper has none",
293
296
  " measure: skipped — nothing is compiled",
@@ -295,7 +298,7 @@ test("build: a markdown-only paper is refused", async () => {
295
298
  " ✗ nothing to compile: no paper.tex",
296
299
  ].join("\n"),
297
300
  err:
298
- "\nNo paper.tex in: papers/md.\n" +
301
+ "\nNo paper.tex in: papers/nosource.\n" +
299
302
  'This is NOT "nothing to build" — paperlint compiles LaTeX, and these papers have no LaTeX source.\n' +
300
303
  "Write the paper in paper.tex; `paperlint new <name>` creates one.",
301
304
  });
@@ -399,7 +402,7 @@ test("build --dry-run on a paper with a venue plans for the preset's packages",
399
402
 
400
403
  test("a paper's paperlint.json that does not parse stops the lint with its path", () => {
401
404
  const dir = writeTree(join(root, "rules-broken"), {
402
- "p/paper.md": "# P\n",
405
+ "p/paper.tex": "% P\n",
403
406
  "p/paperlint.json": "{ nope",
404
407
  });
405
408
  const r = paperRuleBlocks([join(dir, "p")]);
@@ -444,15 +447,12 @@ test("silentOptionalRules: a rule no block names for any paper is silent — a g
444
447
  );
445
448
  });
446
449
 
447
- test("init with no path and --format md sets up the current directory", async () => {
450
+ test("init with no path sets up the current directory, and its first paper is paper.tex", async () => {
448
451
  const dir = writeTree(join(root, "init-here"), { "package.json": "{}" });
449
- const r = await cli(
450
- ["init", "--yes", "--no-hooks", "--paper", "first", "--format", "md"],
451
- dir,
452
- );
453
- assert.equal(
454
- existsSync(join(dir, "papers", "first", "paper.md")),
455
- true,
452
+ const r = await cli(["init", "--yes", "--no-hooks", "--paper", "first"], dir);
453
+ assert.deepEqual(
454
+ readdirSync(join(dir, "papers", "first")).sort(),
455
+ ["PIPELINE-STATUS.md", "paper.tex", "paperlint.json"],
456
456
  r.out + r.err,
457
457
  );
458
458
  });
@@ -488,12 +488,9 @@ test("lint: a named directory with nothing paperlint lints is named in full when
488
488
  test("new from inside the paper it names adds the missing files and names the paper in full", async () => {
489
489
  const dir = writeTree(join(root, "new-inside"), {
490
490
  "paperlint.json": JSON.stringify({ papersDir: "papers" }),
491
- "papers/p/paper.md": "# P\n",
491
+ "papers/p/paper.tex": "% P\n",
492
492
  });
493
- const r = await cli(
494
- ["new", "p", "--format", "md", "--yes"],
495
- join(dir, "papers", "p"),
496
- );
493
+ const r = await cli(["new", "p", "--yes"], join(dir, "papers", "p"));
497
494
  assert.match(
498
495
  r.out,
499
496
  new RegExp(
@@ -507,7 +504,7 @@ test("new with several directories, run from the first, names it in full", async
507
504
  "paperlint.json": JSON.stringify({ papersDir: ["a", "b"] }),
508
505
  "a/.keep": "",
509
506
  });
510
- const r = await cli(["new", "p", "--format", "md", "--yes"], join(dir, "a"));
507
+ const r = await cli(["new", "p", "--yes"], join(dir, "a"));
511
508
  assert.equal(
512
509
  r.out.split("\n")[0],
513
510
  `several papers directories are declared — using the first: ${join(dir, "a")}`,
@@ -519,7 +516,7 @@ test("lint: an ESLint failure that is not an empty set is not swallowed", async
519
516
  "paperlint.json": JSON.stringify({
520
517
  rules: { "paper/stages": ["error", { x: 1 }] },
521
518
  }),
522
- "papers/p/paper.md": "# P\n",
519
+ "papers/p/paper.tex": "% P\n",
523
520
  "papers/p/PIPELINE-STATUS.md": "---\nstages: []\n---\n# S\n",
524
521
  });
525
522
  await assert.rejects(cli(["lint"], dir), {
@@ -582,11 +579,11 @@ function newAtTerminal(cwd: string, answers: Record<string, string>) {
582
579
  });
583
580
  }
584
581
 
585
- test("new at a terminal asks for the format and the venue; the answers pick them", async () => {
586
- const md = writeTree(join(root, "new-tty-md"), { "package.json": "{}" });
587
- const r = await newAtTerminal(md, { "format:": "md\n", "venue:": "none\n" });
582
+ test("new at a terminal asks for the venue; the answer picks it", async () => {
583
+ const dir = writeTree(join(root, "new-tty"), { "package.json": "{}" });
584
+ const r = await newAtTerminal(dir, { "venue:": "none\n" });
588
585
  assert.equal(r.status, 0, r.out);
589
- assert.equal(existsSync(join(md, "papers", "first", "paper.md")), true);
586
+ assert.equal(existsSync(join(dir, "papers", "first", "paper.tex")), true);
590
587
  });
591
588
 
592
589
  test("chooseVenue: a question that fails (the stream ended) takes the default, no venue", async () => {
@@ -625,21 +622,20 @@ test("init's TeX Live install runs the toolchain over the project's papers, neve
625
622
  assert.equal(typeof here.installed(), "boolean");
626
623
  });
627
624
 
628
- test("lint with its LaTeX language missing from the install still lints the markdown papers", () => {
625
+ test("lint with its LaTeX language missing from the install still lints the scorecards", async () => {
629
626
  const dir = writeTree(join(root, "lint-no-tex-language"), {
630
- "papers/p/paper.md": "# P\n\nSome prose.\n",
631
- "no-latex.mjs":
632
- "import { register } from 'node:module';\n" +
633
- "register('data:text/javascript,' + encodeURIComponent(\"export async function resolve(s, c, next) { if (s === '#eslint-rules/latex-language') throw Object.assign(new Error('gone'), { code: 'ERR_MODULE_NOT_FOUND' }); return next(s, c); }\"));\n",
627
+ "papers/p/PIPELINE-STATUS.md": "---\nstages: []\n---\n# S\n",
628
+ });
629
+ const gone = Object.assign(new Error("gone"), {
630
+ code: "ERR_MODULE_NOT_FOUND",
634
631
  });
635
- const without = runNode(BIN, ["lint", "papers"], {
636
- cwd: dir,
637
- nodeArgs: ["--import", join(dir, "no-latex.mjs")],
632
+ const without = await cli(["lint", "papers"], dir, {
633
+ loadTexLanguage: () => Promise.reject(gone),
638
634
  });
639
- const whole = runNode(BIN, ["lint", "papers"], { cwd: dir });
640
- // A markdown paper needs no LaTeX language: the same report, the same code.
635
+ const whole = await cli(["lint", "papers"], dir);
636
+ // A scorecard needs no LaTeX language: the same report, the same code.
641
637
  assert.deepEqual(without, whole);
642
- assert.match(whole.stdout, /missing `PIPELINE-STATUS\.md`/);
638
+ assert.match(whole.out, /missing all of `paper\.tex`/);
643
639
  });
644
640
 
645
641
  test("`paperlint hook` with no name: exit 2, and the usage names an example", async () => {
package/src/cli.ts CHANGED
@@ -46,11 +46,11 @@ import {
46
46
  FRAGMENT_FILES,
47
47
  includeBlocks,
48
48
  paperBodies,
49
- unreadLines,
50
49
  } from "./paper-includes.ts";
51
50
  import { prepareEngine } from "./build-engine.ts";
52
51
  import { cacheRoot, cachedTree, runToolchain } from "./toolchain.ts";
53
52
  import { banalInstaller, parseBanalSettings } from "./adapters/banal/index.ts";
53
+ import { bibReader } from "./adapters/bibtex/index.ts";
54
54
  import { curlDownload } from "./adapters/curl/index.ts";
55
55
  import { latexReader } from "./adapters/latex/index.ts";
56
56
  import {
@@ -93,6 +93,12 @@ import {
93
93
  type PaperSettings,
94
94
  } from "./paper-settings.ts";
95
95
  import { referenceRules, REFERENCE_RULE_LEVELS } from "./reference-rules.ts";
96
+ import { SOURCES_RULE_LEVELS, sourcesRules } from "./sources-rules.ts";
97
+ import { sourcesRecorder } from "./sources-record.ts";
98
+ import { sourcesCodec } from "./adapters/sources-record/index.ts";
99
+ import { texOutput } from "./adapters/tex-output/index.ts";
100
+ import { BIB_RULE_LEVELS, bibRules } from "./bib-rules.ts";
101
+ import { bibliographyReader } from "./references.ts";
96
102
  import { onlineReferences } from "./adapters/references/index.ts";
97
103
  import { hotcrpPortal } from "./adapters/hotcrp/index.ts";
98
104
  import { runSubmission } from "./submission.ts";
@@ -132,15 +138,7 @@ import {
132
138
  init,
133
139
  processInteractivity,
134
140
  } from "./init.ts";
135
- import {
136
- DEFAULT_FORMAT,
137
- FORMATS,
138
- isFormat,
139
- newPaper,
140
- reportNewPaper,
141
- type PaperFormat,
142
- type VenueSetting,
143
- } from "./new-paper.ts";
141
+ import { newPaper, reportNewPaper, type VenueSetting } from "./new-paper.ts";
144
142
  // The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
145
143
  // import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
146
144
  // and from dist/ — see CONTRIBUTING.md.
@@ -174,7 +172,6 @@ import {
174
172
  } from "./folder-venue-rule.ts";
175
173
  import typography from "#eslint-rules/paper-typography";
176
174
  import texBuild from "#eslint-rules/tex-build";
177
- import bibReachable from "#eslint-rules/bib-reachable-entry";
178
175
  import reviewFrontmatter from "#eslint-rules/review-frontmatter";
179
176
  import siblingFrontmatter from "#eslint-rules/sibling-frontmatter";
180
177
  import pdfRules from "#eslint-rules/pdf-last-page-balance";
@@ -189,7 +186,7 @@ const USAGE = `paperlint — machine-checkable gates for a paper kept in git
189
186
  npx paperlint init [dir] set the project up: detect the papers directory, declare it
190
187
  in package.json, link the skills, wire the hooks into
191
188
  .claude/settings.json, offer the CI step, report what is missing
192
- npx paperlint new <name> [--venue <preset>] [--kind <kind>] [--format tex|md]
189
+ npx paperlint new <name> [--venue <preset>] [--kind <kind>]
193
190
  create <papers>/<name>/ from the template; never overwrites,
194
191
  on an existing folder adds only the missing files, then lints it.
195
192
  A <name> that names a venue (aisec-2026) is refused: name the
@@ -225,7 +222,6 @@ init:
225
222
  or with CI set, nothing is asked either
226
223
  --no-hooks do not wire the hooks (the default without a human is to wire them)
227
224
  --paper <name> create this paper too (without a human, the only way init creates one)
228
- --format tex|md the new paper's source format; default tex
229
225
 
230
226
  new:
231
227
  --venue <preset> the venue preset, written as "extends" into the paper's paperlint.json:
@@ -234,7 +230,6 @@ new:
234
230
  On a terminal without --venue, new asks; "none" leaves it unset
235
231
  --kind <kind> the paper's kind at that venue (its page limit), e.g. short — one of the
236
232
  preset's kinds; needs --venue
237
- --format tex|md the paper's source format; default tex
238
233
  --allow-venue-name create the folder even though its name names a venue (a whole word such as
239
234
  "realm" can be the work's own). Without it such a name is refused: a rejected
240
235
  paper moves to another venue and keeps its folder
@@ -312,11 +307,17 @@ settings — paperlint.json, at two levels, one schema. Both are optional.
312
307
  unknown key, in either file, is an error.
313
308
  `;
314
309
 
315
- /** What the rules over a paper.tex read with: the disk, the shipped presets, the LaTeX reader. */
310
+ /** What reads the build's record of a paper: the disk, and the schema its text is parsed with. */
311
+ const RECORD_DEPS = { files: nodeFiles, codec: sourcesCodec };
312
+
313
+ /** What reads the bibliography the last build recorded: the record, and the readers of its databases and blocks. */
314
+ const RECORDED_DEPS = { ...RECORD_DEPS, latex: latexReader, bib: bibReader };
315
+
316
+ /** What the rules over a paper.tex read with: the paper's disk and LaTeX reader, the build's record, and the shipped presets. */
316
317
  const TEX_RULE_DEPS = {
317
- files: nodeFiles,
318
- venuesDir: presetsDir(),
318
+ ...RECORD_DEPS,
319
319
  latex: latexReader,
320
+ venuesDir: presetsDir(),
320
321
  };
321
322
 
322
323
  /** The register band rules and `tex/heading-case`: the rules of the `tex` plugin that read a paper's words. */
@@ -364,7 +365,8 @@ export function buildConfig(
364
365
  // The reference rules judge `_build/references.json`, and only on `paper.tex`.
365
366
  const texPaperRules = {
366
367
  ...paperRules,
367
- ...referenceRules({ files: nodeFiles }),
368
+ ...referenceRules(RECORDED_DEPS),
369
+ ...sourcesRules(RECORD_DEPS),
368
370
  };
369
371
  // Each typography rule reports every occurrence where it is, and fixes it (`--fix`).
370
372
  const prose = {
@@ -399,8 +401,8 @@ export function buildConfig(
399
401
  },
400
402
  {
401
403
  files: ["**/PIPELINE-STATUS.md"],
402
- // Every paper folder has one, whatever its source format: the rules about the folder itself
403
- // (its stages, its frozen sources, its name) are judged here, once per paper.
404
+ // Every paper folder has one: the rules about the folder itself (its stages, its frozen
405
+ // sources, its name) are judged here, once per paper.
404
406
  plugins: {
405
407
  markdown,
406
408
  paper: {
@@ -419,12 +421,6 @@ export function buildConfig(
419
421
  ...CYCLE_RULE_LEVELS,
420
422
  },
421
423
  },
422
- {
423
- files: ["**/paper.md", "**/draft.md"],
424
- plugins: { markdown, paper: { rules: paperRules } },
425
- ...md,
426
- rules: prose,
427
- },
428
424
  {
429
425
  files: ["**/reviews/*.md"],
430
426
  plugins: { markdown, review: reviewFrontmatter },
@@ -461,7 +457,7 @@ export function buildConfig(
461
457
  },
462
458
  },
463
459
  paper: { rules: texPaperRules },
464
- bib: bibReachable,
460
+ bib: { rules: bibRules(RECORDED_DEPS) },
465
461
  talk: { rules: talkRules(TALK_RULE_DEPS) },
466
462
  format: {
467
463
  rules: {
@@ -475,8 +471,9 @@ export function buildConfig(
475
471
  rules: {
476
472
  ...prose,
477
473
  ...REFERENCE_RULE_LEVELS,
474
+ ...SOURCES_RULE_LEVELS,
478
475
  "paper/figure-ref-style": "warn",
479
- "bib/reachable-entry": "warn",
476
+ ...BIB_RULE_LEVELS,
480
477
  "tex/future-promise": "warn",
481
478
  // A number with no owner: a prose rule, on beside the others above.
482
479
  "tex/claim-provenance": "warn",
@@ -585,8 +582,7 @@ export interface RulePlugin {
585
582
  * file knows every rule id. `@eslint/markdown` is a dependency's plugin and is left out. The LaTeX
586
583
  * language rides on `tex` when `texLanguage` is given, and that is how a consumer registers it: a
587
584
  * second `tex` plugin of its own is refused by ESLint ("Cannot redefine plugin "tex"", measured
588
- * with and without the language here). Without it, `tex` carries rules only — for a config that
589
- * lints markdown papers alone.
585
+ * with and without the language here). Without it, `tex` carries rules only.
590
586
  */
591
587
  export function rulePlugins(texLanguage?: unknown): Record<string, RulePlugin> {
592
588
  const out: Record<string, { rules: Record<string, unknown> }> = {};
@@ -888,7 +884,6 @@ export function parseArgs(argv: readonly string[]): Args {
888
884
  noHooks: false,
889
885
  allowVenueName: false,
890
886
  paper: null,
891
- format: null,
892
887
  venue: null,
893
888
  kind: null,
894
889
  pdf: null,
@@ -987,10 +982,9 @@ const SWITCHES: ReadonlyMap<
987
982
  /** The flags whose value is one string field of `Args`. */
988
983
  const STRING_FLAGS: ReadonlyMap<
989
984
  string,
990
- "paper" | "format" | "venue" | "kind" | "pdf" | "abstract"
985
+ "paper" | "venue" | "kind" | "pdf" | "abstract"
991
986
  > = new Map([
992
987
  ["--paper", "paper"],
993
- ["--format", "format"],
994
988
  ["--venue", "venue"],
995
989
  ["--kind", "kind"],
996
990
  ["--pdf", "pdf"],
@@ -1184,7 +1178,6 @@ export function runHook(
1184
1178
  export async function createPaperAt(
1185
1179
  papersRoot: string,
1186
1180
  name: string,
1187
- format: PaperFormat,
1188
1181
  {
1189
1182
  log,
1190
1183
  err,
@@ -1201,7 +1194,7 @@ export async function createPaperAt(
1201
1194
  allowVenueName?: boolean;
1202
1195
  },
1203
1196
  ): Promise<number> {
1204
- const result = newPaper(papersRoot, name, format, {
1197
+ const result = newPaper(papersRoot, name, {
1205
1198
  venue,
1206
1199
  venues: allowVenueName ? [] : shippedVenueNames(PRESET_DEPS),
1207
1200
  });
@@ -1366,25 +1359,10 @@ async function runNew(
1366
1359
  const [name, ...extra] = a.paths;
1367
1360
  if (!name || extra.length > 0) {
1368
1361
  err(
1369
- `\`new\` takes exactly one paper name: \`paperlint new my-paper [--venue <preset>] [--kind <kind>] [--format tex|md]\``,
1362
+ `\`new\` takes exactly one paper name: \`paperlint new my-paper [--venue <preset>] [--kind <kind>]\``,
1370
1363
  );
1371
1364
  return 2;
1372
1365
  }
1373
- let format: PaperFormat = DEFAULT_FORMAT;
1374
- if (a.format !== null) {
1375
- if (!isFormat(a.format)) {
1376
- err(
1377
- `--format must be one of ${FORMATS.join(", ")} — got \`${a.format}\``,
1378
- );
1379
- return 2;
1380
- }
1381
- format = a.format;
1382
- } else if (processInteractivity(a.yes).interactive) {
1383
- const f = (
1384
- await askOrDefault(ask, `format: tex / md [${DEFAULT_FORMAT}] `)
1385
- )?.trim();
1386
- if (isFormat(f)) format = f;
1387
- }
1388
1366
  const cfg = readConfig({ ...a, json: false }, { log: () => {}, err, cwd });
1389
1367
  if (cfg.code !== undefined) return cfg.code;
1390
1368
  const roots = papersRoots(cfg);
@@ -1417,7 +1395,7 @@ async function runNew(
1417
1395
  ask,
1418
1396
  });
1419
1397
  if (!venue.ok) return (err(venue.error), 2);
1420
- return createPaperAt(papersRoot, name, format, {
1398
+ return createPaperAt(papersRoot, name, {
1421
1399
  log,
1422
1400
  err,
1423
1401
  cwd,
@@ -1478,6 +1456,12 @@ async function runBuild(
1478
1456
  dryRun: a.dryRun,
1479
1457
  log,
1480
1458
  checkReferences,
1459
+ readBibliography: bibliographyReader(RECORDED_DEPS),
1460
+ recordSources: sourcesRecorder({
1461
+ files: nodeFiles,
1462
+ listDir: nodeListDir,
1463
+ texOutput,
1464
+ }),
1481
1465
  engine: () => engineEnv(targets, a, { log, err }),
1482
1466
  });
1483
1467
  if (out.kind === "no-engine") return 1;
@@ -1668,10 +1652,6 @@ async function runInit(
1668
1652
  );
1669
1653
  return 2;
1670
1654
  }
1671
- if (a.format !== null && !isFormat(a.format)) {
1672
- err(`--format must be one of ${FORMATS.join(", ")} — got \`${a.format}\``);
1673
- return 2;
1674
- }
1675
1655
  return await init(a.paths[0] ?? ".", {
1676
1656
  log,
1677
1657
  err,
@@ -1679,9 +1659,8 @@ async function runInit(
1679
1659
  yes: a.yes,
1680
1660
  hooks: !a.noHooks,
1681
1661
  paper: a.paper,
1682
- format: isFormat(a.format) ? a.format : null,
1683
- createPaper: (papersRoot, name, format) =>
1684
- createPaperAt(papersRoot, name, format, { log, err, cwd }),
1662
+ createPaper: (papersRoot, name) =>
1663
+ createPaperAt(papersRoot, name, { log, err, cwd }),
1685
1664
  venues: shippedVenueNames(PRESET_DEPS),
1686
1665
  tex: initTexLive(a, { log, err, cwd }),
1687
1666
  resolveCliPapers: (root: string): string | null =>
@@ -1696,10 +1675,17 @@ export async function run(
1696
1675
  err = console.error,
1697
1676
  cwd = process.cwd(),
1698
1677
  checkReferences = onlineReferences,
1678
+ loadTexLanguage = async () =>
1679
+ (await import("#eslint-rules/latex-language")).texLanguage,
1699
1680
  }: {
1700
1681
  log?: typeof console.log;
1701
1682
  err?: typeof console.error;
1702
1683
  cwd?: string;
1684
+ /**
1685
+ * `lint`'s LaTeX language, loaded lazily because only `lint` needs it. A test hands one that
1686
+ * fails, as a broken install would.
1687
+ */
1688
+ loadTexLanguage?: () => Promise<unknown>;
1703
1689
  /**
1704
1690
  * `build`'s online reference check — the real services by default. The e2e build passes a
1705
1691
  * fake: no test depends on Crossref, Semantic Scholar or DBLP answering.
@@ -1803,33 +1789,16 @@ export async function run(
1803
1789
  };
1804
1790
 
1805
1791
  // Loaded lazily because only `lint` needs it. If the module cannot be loaded (a broken install:
1806
- // the file missing from the package), lint still runs over the markdown papers rather than
1807
- // failing outright — the LaTeX rules simply have no language to run in.
1808
- let texLanguage: unknown = null;
1809
- try {
1810
- ({ texLanguage } = await import("#eslint-rules/latex-language"));
1811
- } catch {
1812
- texLanguage = null;
1813
- }
1792
+ // the file missing from the package), lint still runs over the scorecards and the review notes
1793
+ // rather than failing outright — the LaTeX rules simply have no language to run in.
1794
+ const texLanguage: unknown = await loadTexLanguage().catch(() => null);
1814
1795
 
1815
- // Each paper's body files, resolved the way the build resolves them — only when the LaTeX language
1796
+ // Each paper's body files, as the last build recorded TeX reading them — only when the LaTeX language
1816
1797
  // loaded, since only its rules read them. The project's papers too: `paperlint lint .` reaches
1817
1798
  // papers two levels down, which a directory's immediate papers do not.
1818
1799
  const bodies = texLanguage
1819
- ? paperBodies(paperDirs([...paths, ...papersRoots(cfg)]), {
1820
- files: nodeFiles,
1821
- latex: latexReader,
1822
- })
1800
+ ? paperBodies(paperDirs([...paths, ...papersRoots(cfg)]), RECORD_DEPS)
1823
1801
  : [];
1824
- // Named only for the papers this run lints: a paper elsewhere in the project is not this run's.
1825
- const inScope = (dir: string) =>
1826
- paths.some((p) => dir === p || dir.startsWith(`${p}${sep}`));
1827
- unreadLines(
1828
- bodies.filter((b) => inScope(b.dir)),
1829
- (p) => shown(cwd, p),
1830
- ).forEach((l) => {
1831
- err(l);
1832
- });
1833
1802
 
1834
1803
  const eslint = new ESLint({
1835
1804
  cwd: lintRoot(root, paths),
@@ -1870,7 +1839,7 @@ export async function run(
1870
1839
  // is not invoked — and, not being invoked, it physically cannot report that.
1871
1840
  if (results.length === 0) {
1872
1841
  err(
1873
- `nothing was linted under ${paths.map((x) => shown(cwd, x)).join(", ")} — no PIPELINE-STATUS.md, paper.md/tex or reviews/ found there. A clean report over zero files is not a clean report.`,
1842
+ `nothing was linted under ${paths.map((x) => shown(cwd, x)).join(", ")} — no PIPELINE-STATUS.md, paper.tex or reviews/ found there. A clean report over zero files is not a clean report.`,
1874
1843
  );
1875
1844
  return 1;
1876
1845
  }