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
@@ -0,0 +1,197 @@
1
+ # Paper sources — TeX answers "which files, which bibliography"
2
+
3
+ As built in PR #174. Everything that asks which files make up a paper, or which bibliography TeX reads,
4
+ reads the record the last build wrote from TeX's own files; nothing in paperlint reads TeX source to
5
+ decide either, and nothing reads a `.bib` but one adapter. TeX's own answer for each planted paper is
6
+ recorded beside it (§8), and the consumers are tested against that, not against prose.
7
+
8
+ ## 1. TeX is the one source of truth
9
+
10
+ Which files make up the paper, and which bibliography it uses, are answered by TeX, from the last
11
+ build — never by reading TeX by hand. There is no static decision: no declaration scan, no
12
+ `filecontents` runs, no include ordering. Lint never runs TeX; it reads what the build recorded.
13
+
14
+ **What the build records — `_build/sources.json`, schema 1.** Written by the compile step, after
15
+ the last pdflatex pass and bibtex, from TeX's own files in the paper directory:
16
+
17
+ | field | from | holds |
18
+ | --------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `inputs` | `paper.fls` (`-recorder`) | every file in the paper directory TeX read, in first-read order, each `role: preamble` or `body` — before or after TeX opens `paper.aux` for writing (`\begin{document}`, measured) |
20
+ | `written` | `paper.fls` `OUTPUT` | the `.bib` and `.tex` files TeX wrote (a `filecontents` block) |
21
+ | `bibdata` | `paper.aux` `\bibdata{…}` | the databases bibtex was told to read, after every switch, macro and `\input` |
22
+ | `bibtex` | `paper.blg`, `paper.bbl` | the database files bibtex opened, the keys it typeset, its exit and errors |
23
+ | `sha256` | the files themselves | every `inputs` file and every database bibtex opened (one TeX wrote too), by path and bytes |
24
+
25
+ A file TeX wrote is an input only when it is a `.tex`: a `filecontents` block that writes `body.tex` the
26
+ body `\input`s puts that text in the PDF, so the file is hashed and spliced like any other (measured:
27
+ pass 1 `OUTPUT body.tex` then `INPUT ./body.tex` after `\begin{document}`). Every other file a pass
28
+ wrote — `.aux`, `.out`, `.toc`, a block's `.bib` — is TeX's bookkeeping or a database, not an input.
29
+ The written `.tex` is not handed to ESLint as a file of its own: its words are the block's, and the
30
+ block's body is no prose to the rules, so the text is judged once, where `readPaper` splices it.
31
+
32
+ Every path is relative to the paper directory and spelled as the directory entries spell it: TeX
33
+ logs the name it opened, which on a file system that ignores case is the source's spelling, so the
34
+ record takes each segment's spelling from the disk and one paper gets one record on every platform.
35
+
36
+ A record of its own, not `references.json`: it is written by every build (the references step is
37
+ optional and may record "not checked"), it is read by rules that have nothing to do with references,
38
+ and it goes stale when the SOURCES change, not when one `.bib` does — one staleness key per file.
39
+ `references.json` keeps its verdicts, one per entry of `sources.json`'s databases, and loses its
40
+ `bibtex` field.
41
+
42
+ **Staleness.** A rule recomputes `sha256` over the files the record lists. No record, or a different
43
+ hash (a file edited, added to an `\input`, deleted), is one finding of a new rule,
44
+ `paper/sources-fresh` — "not built" / "the paper changed since the last build — run
45
+ `npx paperlint build`" — and every rule below is silent until a build records the paper again.
46
+
47
+ **The consumers.**
48
+
49
+ | consumer | reads | no record / stale |
50
+ | ---------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------ |
51
+ | `bib/reachable-entry`, `paper/author-list`, `paper/cite-exists` | the entries of the databases bibtex opened, through the `BibReader` | silent (`paper/sources-fresh` speaks) |
52
+ | `paper/refs-checked` | keys bibtex typeset that the reader did not read (`%`, `@comment`) | silent |
53
+ | `extract-ref-facts`, `bib-authors`, `verify-cites` (dir, `.tex`) | `bibliographyAt` → the record's databases | refused: "run `npx paperlint build` first" |
54
+ | `bibliographyAt` with a `.bib` | that file alone, unchanged | — |
55
+ | `paperlint lint`'s files (`paper-includes.ts`), `paper-context` | the `body` `.tex` files of `inputs` TeX did not write | `paper.tex` only; `paper/sources-fresh` says the rest is unlinted |
56
+ | the prose rules' text (`readPaper`) | `paper.tex` with the files of `inputs` spliced where they stand | `paper.tex` alone; `paper/sources-fresh` says the rest is unlinted |
57
+
58
+ An entry in a `.bib` TeX wrote is reported at the `filecontents` block whose body bibtex takes as
59
+ the same database (`sameDatabase`) — a lookup of where the text stands, not a decision of what TeX
60
+ reads; an entry in any other `.bib` at the top of `paper.tex`, `refs.bib:12:1:` first.
61
+
62
+ **What stays syntactic: where an include stands, and nothing else.** The prose rules read
63
+ `paper.tex` with the files of `inputs` spliced in (`readPaper`): a projection of the text for rules to
64
+ read in context, which decides nothing above. Which files are spliced is the record's answer — an
65
+ include of a file `inputs` does not list (behind `\iffalse`, or past `\end{document}`) contributes
66
+ nothing, as it did to TeX, and no include is looked for on disk. Where the macro stands in the text
67
+ is the one thing read from the text, because `.fls` records no position: files are spliced in the
68
+ order the text names them, which is the order TeX read them, each ended the way TeX ends the last
69
+ line of a file it reads (a newline when the file has none — measured: `a\input{f}b` with `f` holding
70
+ `foo` typesets "afoo b"). With no record, or a stale one, the text is `paper.tex` alone. A file the
71
+ record lists that no include macro in the text stands for (an `\input` inside a macro's body, #177)
72
+ is linted as a file of its own and is absent from the whole-paper text.
73
+
74
+ **Ground truth.** `test/e2e/tex/paper-sources.e2e.ts` runs the build's own record step on each
75
+ planted paper and snapshots its `sources.json` as `tex-truth.json`; the unit tests run the
76
+ consumers on the fixture with that record. TeX is the expectation and the input at once.
77
+
78
+ ## 4. One bibtex reader
79
+
80
+ The port is `BibReader` (`src/ports/bib-reader.ts`: `read(path, text, body, inherited?)` for a block,
81
+ `readFile(path, text, inherited?)` for a `.bib`). bibtex reads a paper's databases in sequence with one
82
+ table of `@string`s, so a `@string` of `abbrev.bib` expands an entry of `refs.bib` under
83
+ `\bibliography{abbrev,refs}` (measured; in the other order bibtex warns it is undefined).
84
+ `recordedBibliography` reads the databases in the order bibtex opened them, each with the `strings` the
85
+ one before it left in force (`inherited`), and reads a block with the `@string`s its database was read
86
+ with; each database is still parsed as its own text, so each entry keeps its place in its own file.
87
+
88
+ The adapter, `src/adapters/bibtex/`, is the only code that knows `@retorquere/bibtex-parser`. It reads each database twice:
89
+
90
+ - **`entries`** — LaTeX read into text (`{\"u}` → `ü`, `\url{x}` → a link), names split into parts,
91
+ `@string`s expanded where they are used. What the checks judge.
92
+ - **`written`** — `raw: true`, every field verbatim, no outer braces dropped: each field as bibtex
93
+ takes it, `@string`s expanded where used, LaTeX as written. What `sameDatabase` compares, with the
94
+ `@preamble` commands, so `{\"o}` and `ö` stay two values, as they are in the `.bbl`.
95
+
96
+ **The fence is lint, not prose.** `eslint.config.mjs` sets `n/no-restricted-import` on
97
+ `@retorquere/bibtex-parser` (and its subpaths) for every file but `src/adapters/bibtex/**`
98
+ (`test/eslint-bib-parser.test.ts` plants imports in the app, the domain, another adapter and a
99
+ skill's script).
100
+
101
+ ## 5. A path a person gave → `bibliographyAt`
102
+
103
+ `extract-ref-facts`, `bib-authors` and `verify-cites` (its `.bib` input) take a path on the command
104
+ line. All three ask `bibliographyAt(path, deps)`: a `.bib` is read alone, as named; a `.tex`, or a
105
+ directory holding `paper.tex`, is the bibliography the last build's bibtex opened for that paper
106
+ (`recordedBibliography`) — for a database TeX wrote, the text of the `filecontents` block (or included
107
+ file) that holds it, so an entry stands where the author edits it. A refusal is a `BibliographyUnread`
108
+ (`missing`, `not-bib-or-tex`, `no-paper`, `not-built`, `no-database`, `outside-paper`), and
109
+ `bibliographyUnreadWhy` is its one sentence; `outside-paper` is a paper whose bibtex also opened a
110
+ database the paper directory does not hold (BIBINPUTS) — refused rather than read in part;
111
+ `not-built` covers a missing, unreadable and stale record alike: run `npx paperlint build` first. Each script keeps its own CLI frame around it.
112
+
113
+ ## 6. The references a build checks
114
+
115
+ The build's references step (`src/references.ts`) checks entries, not keys: two databases may each
116
+ define a key with other metadata.
117
+
118
+ - **What is checked is `readBibliography`**: the databases the build's bibtex opened, in the order it
119
+ opened them, each once, read by the bibtex reader — a view over `recordedBibliography`, the one reader
120
+ of the record's databases. Without a fresh record the step checks nothing and its note names the
121
+ reason; a database bibtex opened that is not in the paper directory (bibtex found it on its search
122
+ path, `BIBINPUTS`) is named, not skipped. One removed after the build — a `.bib` TeX wrote, then a
123
+ clean — makes the record stale: every database bibtex opened is hashed.
124
+ - **One verdict per entry, in order.** The `CheckReferences` port returns verdict `i` for entry `i`;
125
+ the adapter runs verify-cites and bib-authors per entry and pairs them by position.
126
+ - **`_build/references.json`, schema 3**: `bib {sources, sha256}`, `status`, `entries`. `sources` are
127
+ the database names as bibtex names them and `sha256` covers their text. Lint lays the verdicts on the
128
+ entries of the same databases and reports each at its entry (`entryReports`); a record that is not one
129
+ verdict per entry, in order, about the bytes the databases hold now, is not about this bibliography
130
+ (`paper/refs-fresh`).
131
+
132
+ ## 7. The post-build check — where bibtex reads differently
133
+
134
+ bibtex has no comment syntax and recovers from malformed text as no parser does, so the reader does
135
+ not emulate it. Each difference has an owner:
136
+
137
+ | shape | answered by |
138
+ | -------------------------------------------------------------- | ---------------------------------------------------------------------- |
139
+ | malformed: an entry or brace never closed, `%` inside an entry | the build — bibtex exits 2, and `paperlint build` fails with its lines |
140
+ | an entry behind `%`, or inside `@comment{…}` | `paper/refs-checked`, from the keys bibtex typeset |
141
+ | `@`, `)` or `{"}` inside a quoted field | the reader, which reads it as bibtex does |
142
+
143
+ `paper/refs-checked` names every key bibtex typeset (`paper.bbl`) that no entry of the databases it
144
+ opened has (`unseenKeys`; keys compare case-insensitively, as bibtex's do), and every database bibtex
145
+ opened that is not in the paper directory.
146
+
147
+ ## 8. Ground truth — `tex-truth.json`
148
+
149
+ Each planted paper of `fixtures/paper-sources/` carries `tex-truth.json`, written by TeX:
150
+ `test/e2e/tex/paper-sources.e2e.ts` builds it with `pdflatex -recorder` and `bibtex` and snapshots the
151
+ record the build writes. `-u` re-records; a TeX that answers differently fails. The unit tests of the
152
+ consumers lay that file at `_build/sources.json` (`test/recorded-fixture.ts`). The accepted papers of
153
+ `fixtures/accepted-papers/` carry theirs the same way (`test/e2e/tex/accepted-papers-record.e2e.ts`).
154
+ The files are the numbers; this document does not copy them.
155
+
156
+ ## 9. Decisions
157
+
158
+ - **The record, not a reading** — which files and databases a paper has is what TeX did on the last
159
+ build; a static reading of the text agrees only on the cases its author thought of.
160
+ - **No record, no verdict** — a rule that needs the record is silent without a current one, and one
161
+ rule (`paper/sources-fresh`) says so once; a stale answer is worse than none.
162
+ - **Prose rules splice only the files the record lists** — an include behind `\iffalse` contributes
163
+ nothing; without a record the text is `paper.tex` alone and `paper/sources-fresh` says the rest went
164
+ unlinted.
165
+ - **A lookup of where a text stands is not a decision of what TeX reads** — `filecontents` blocks are
166
+ found in the text only to put a finding on the line that holds an entry (`sameDatabase`).
167
+ - **One verdict per entry, not per key** — a key does not identify an entry; a verdict that cannot say
168
+ which entry it is about reports at the wrong one.
169
+ - **"Same database" is what bibtex takes** — raw bytes and the final `@string` map are a proxy that
170
+ misses a `@string` redefined after use; the text between entries reaches no `.bbl`.
171
+ - **One `.bib` reader, fenced by lint** — three readers grew before there was one; the parser's name
172
+ may appear in one folder only.
173
+ - **One path → bibliography function** — three scripts each resolved it by hand, each with its own
174
+ refusals.
175
+ - **Recorded TeX truth, not hand-written expectations** — the consumers' tests compare with what TeX
176
+ did, so a wrong belief about TeX cannot be written into both the code and its test.
177
+ - **The post-build check, not emulation** — no parser recovers as bibtex does; bibtex's own output
178
+ is compared instead.
179
+
180
+ ## 10. Scope: what this PR does not decide
181
+
182
+ - A finding for a `filecontents` block without `[overwrite]` (#176).
183
+ - A listed body file that no include macro in the text stands for (an `\input` inside a macro's body,
184
+ #177): linted as a file of its own, absent from the whole-paper text.
185
+ - `eslint --cache`: `bib/reachable-entry` depends on files other than the linted one, so a cached
186
+ result can be stale after a `.bib` changes. `paperlint lint` does not cache.
187
+ - Reading past `\end{document}` in `headings.ts`, `layout.ts` and `rendered.ts` (#178).
188
+ - A file a `filecontents` block writes under another extension than `.tex` (a `.pgf` the body
189
+ `\input`s) is not an input: its text reaches the PDF, but the record neither hashes nor splices it.
190
+ - A database TeX wrote is read from the file it left on disk, which the record does not hash: edited
191
+ after a build, that file is not detected as stale.
192
+ - Two copies compared where bibtex and the parser differ in what no check reads: a `@preamble` is
193
+ compared as written, so a `@string` it uses that is redefined between the copies is not seen; and
194
+ `keywords` are compared as the parser splits, dedupes and sorts them — it does so for that field
195
+ name unconditionally, with no option to keep it as written.
196
+ - Q3 (one projection of the live text, replacing `paper-typography`'s `skippedRanges` and
197
+ `texToMdast`'s own parse), the hooks, and an enforcement rule for Q1–Q3: later PRs.
package/docs/e2e.md CHANGED
@@ -179,10 +179,6 @@ everything.
179
179
  - **macOS and Windows.** Locally these run on whatever machine you have. In CI `build-e2e` is a
180
180
  matrix over `ubuntu-latest` and `macos-latest`; Windows is not run anywhere, and `paperlint toolchain`
181
181
  refuses it.
182
- - **The rules, on a real document.** `fixtures/real-markdown-paper/` holds a published article and
183
- a recorded baseline of what the rules say about it. That is a **lint** fixture, driven by a
184
- harness — it does not go through the installed package. Wiring it into the install corpus is
185
- open work, not something already done.
186
182
  - **The skills as behaviour.** The install run proves a skill arrived and that its paths resolve.
187
183
  Whether the agent then does the right thing with it is a different measurement, and it belongs
188
184
  to the harness tier of `vigiles`, not here.
@@ -199,5 +195,7 @@ PDF, its byte counts, the cross-check between them — and copies `fixtures/buil
199
195
  it so the LaTeX rules see LaTeX rather than a placeholder.
200
196
 
201
197
  ⚠️ **Every line of that corpus was written by somebody who already knew which rule would read it.**
202
- It keeps the stage machinery honest and it cannot tell you anything about false positives. That is
203
- what the real-article fixture is for, and why the two tiers are not substitutes.
198
+ It keeps the stage machinery honest and it cannot tell you anything about false positives. So the
199
+ run then adds one accepted paper from `fixtures/accepted-papers/` (`secure-acsac24`) and holds the
200
+ installed binary to that paper's recorded `baseline.json` — the same recording
201
+ `fixtures/accepted-papers/accepted-papers.test.ts` holds the repository's own `run` to.
@@ -138,7 +138,7 @@ lockfile in that array was not.
138
138
  zero: `pipeline-check.mjs`, `extract-ref-facts.mjs` and `bib-authors.mjs`, all reaching
139
139
  `markdown-it`. Which retires a number this note carried: the closure is **two** third-party
140
140
  packages over **eight** referenced scripts, not one over seven — `extract-ref-facts.mjs` reaches
141
- `@retorquere/bibtex-parser` through a dynamic `await import()` that a static grep does not see.
141
+ `@retorquere/bibtex-parser`, now through `#src/adapters/bibtex/index`.
142
142
 
143
143
  ⏳ **Not measured, recorded as such:** the documented 60-second install timeout; the personal
144
144
  `~/.claude/skills` and `--add-dir` channels (2 of 4 locations verified); and a genuine
@@ -16,10 +16,6 @@ still left to a human copying something by hand?**
16
16
  > - `--hooks=local` is **deferred, not implemented**; `init` refuses it by name.
17
17
  >
18
18
  > Where the build differed from this text, it is listed at the end, in § 8.
19
- >
20
- > **Later, 2026-09-24:** Markdown papers are deprecated and being removed
21
- > ([#57](https://github.com/zernie/research-paper-pipeline/issues/57)). Where this record mentions
22
- > `paper.md` or `--format md`, it describes the tool as it was when written.
23
19
 
24
20
  1. A paper folder needs a `PIPELINE-STATUS.md`, and today nothing makes it — you copy a template.
25
21
  Should there be a command, and where should per-paper settings live: in a file in every paper
@@ -66,11 +62,11 @@ changes, no migration.
66
62
  (`src/init.ts:448`), offers a CI workflow, reports missing programs, and **prints** the two
67
63
  `/plugin` lines (`src/init.ts:257-258`). It never creates the papers directory and never creates
68
64
  a paper.
69
- - **A directory is a paper** if it holds any of `PIPELINE-STATUS.md`, `paper.tex`, `paper.md`,
70
- `venue.json` (`src/build.ts:41-46`, `src/structure.ts:39`). Detection is generous.
71
- - **`rpp lint` requires `PIPELINE-STATUS.md`** in every such directory, plus one of
72
- `paper.tex`/`paper.md` (`src/structure.ts:38-43`). A paper with only `paper.md` fails the very
73
- first `rpp lint` with `missing PIPELINE-STATUS.md` — that is the README's own "First run" example.
65
+ - **A directory is a paper** if it holds any of `PIPELINE-STATUS.md`, `paper.tex`, `venue.json`
66
+ (`src/build.ts`, `src/structure.ts`). Detection is generous.
67
+ - **`rpp lint` requires `PIPELINE-STATUS.md`** in every such directory, plus `paper.tex`
68
+ (`src/structure.ts`). A paper with only `paper.tex` fails the very first `rpp lint` with
69
+ `missing PIPELINE-STATUS.md` — that is the README's own "First run" example.
74
70
 
75
71
  ⚠️ **One inconsistency worth knowing.** `paper/stages` says in its header that a missing `stages`
76
72
  field is _legitimate_ — "a paper that has shipped nothing owes nothing" (`eslint-rules/paper-stages.mjs:24-27`) —
@@ -101,7 +97,7 @@ human to copy and tick them.
101
97
  ## 2. What `PIPELINE-STATUS.md` actually holds — the split
102
98
 
103
99
  Read from the template (`skills/paper-pipeline/references/pipeline-status-template.md`), the
104
- fixtures (`fixtures/paper-stages/*`, `fixtures/real-markdown-paper/`) and four real files in the
100
+ fixtures (`fixtures/paper-stages/*`) and four real files in the
105
101
  owner's corpus. Real sizes: 86, 95, 1 057 and 1 301 lines. In the two big ones the machine-read
106
102
  part is the first 10–25 lines of front matter; the rest is a dated journal of decisions. (No
107
103
  content of those files is reproduced here.)
@@ -110,7 +106,6 @@ content of those files is reproduced here.)
110
106
  | ------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
107
  | paper short name / title | **config** | human, once — `rpp new` can prefill from the folder name |
112
108
  | venue, deadline, blind model (the `Venue:` header line) | **config** | human; changes when the paper is re-targeted |
113
- | format (`.tex` vs `.md`) | **derived** | from which source file exists |
114
109
  | venue profile, paper kind, pdf path (a separate `venue.json` in one real paper) | **config** | human; already a per-paper file in practice |
115
110
  | typography allowance (`typographyDebt`) | **config** | human, but it sits in the root key keyed by path today |
116
111
  | `stages[]`: `stage`, `date`, `venue` | **state** | **tool** at the moment of freezing (`rpp freeze`, a later step) |
@@ -172,9 +167,9 @@ parsing markdown — the fix is to ship the template as its own file and have th
172
167
  (entry front matter), Cargo (member `Cargo.toml`), Eleventy (directory data). The tools that put
173
168
  per-unit settings in the root (ESLint `files`, Vale sections) are _linters of code_, where units
174
169
  have no identity of their own — a paper does.
175
- 3. **Front matter of the source itself is not available to rpp**, because half the papers are
176
- LaTeX, which has no YAML front matter. Quarto and Astro can use the document's own header only
177
- because every document is markdown. So rpp needs a per-paper **file**, and it already has one:
170
+ 3. **Front matter of the source itself is not available to rpp**, because a paper is LaTeX, which
171
+ has no YAML front matter. Quarto and Astro can use the document's own header only because their
172
+ documents carry one. So rpp needs a per-paper **file**, and it already has one:
178
173
  `PIPELINE-STATUS.md`, whose front matter is exactly where Quarto would put these fields.
179
174
  4. **Root-level keys that are really per-paper** (`typographyDebt: { "papers/my-paper": … }`,
180
175
  `src/cli.ts:104`) are the ESLint-overrides style. They work, but they break silently on a folder
@@ -209,9 +204,9 @@ written once at freeze time, then checked forever — and today a human types it
209
204
 
210
205
  ### Commands
211
206
 
212
- **`rpp new <name> [--format tex|md]`** — creates `<papers>/<name>/` with:
207
+ **`rpp new <name>`** — creates `<papers>/<name>/` with:
213
208
 
214
- - `paper.tex` or `paper.md` (a minimal stub; `--format` defaults to `tex` — ask when interactive);
209
+ - `paper.tex` (a minimal stub);
215
210
  - `PIPELINE-STATUS.md` from a **template file** shipped in the package, with the name filled in and
216
211
  **no** `stages` field (a new paper has shipped nothing — `paper-stages.mjs:24`).
217
212
 
@@ -228,7 +223,7 @@ Rules for it, each taken from a tool above:
228
223
  **Later step, NOT part of this change — `rpp freeze <paper> <stage> [--pdf <file>] [--date <iso>]`**,
229
224
  the release-please move, recorded so the design is not lost:
230
225
 
231
- - copies the built pdf and the source into `versions/<date>-<stage>.pdf|.tex|.md`;
226
+ - copies the built pdf and the source into `versions/<date>-<stage>.pdf|.tex`;
232
227
  - **appends** one record to `stages` with `stage`, `date`, `pdf`, `bytes`, `source`, `sourceBytes`;
233
228
  - refuses a stage outside `STAGES` (`paper-stages.mjs:53`) and a date/stage pair already present.
234
229
 
@@ -259,15 +254,15 @@ the skills tell agents to update them.
259
254
 
260
255
  - **Existing folders with a `PIPELINE-STATUS.md`** (the owner's four): nothing changes. Their
261
256
  front matter is already the per-paper carrier; a later `rpp freeze` would append to their lists.
262
- - **Existing folders without one** (a draft that has only `paper.md`): `rpp new <that-name>` fills
263
- the missing file and touches nothing else.
257
+ - **Existing folders without one** (a draft that has only `paper.tex`): `rpp new <that-name>`
258
+ fills the missing file and touches nothing else.
264
259
  - **No `rpp migrate` is needed** for this recommendation. One would be needed only if the carrier
265
260
  later moves to `paper.yml`.
266
261
 
267
262
  ### The smallest first step
268
263
 
269
264
  Move the template out of the code fence into its own file (say
270
- `templates/paper/PIPELINE-STATUS.md`, plus `paper.tex` and `paper.md` stubs), make
265
+ `templates/paper/PIPELINE-STATUS.md`, plus a `paper.tex` stub), make
271
266
  `pipeline-status-template.md` point at it instead of containing it, and add `rpp new` that copies
272
267
  it. Then the README sentence "Nothing generates it and no command refreshes it" becomes "`rpp new`
273
268
  creates it". No rule changes, no consumer migration. `rpp freeze` is step two; `init` offering to
@@ -435,13 +430,13 @@ unset **and** no `--yes`._
435
430
 
436
431
  ### What `init` asks, and what it does without asking
437
432
 
438
- | decision | human at a terminal | agent / CI / `--yes` | flag |
439
- | ----------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------- |
440
- | papers directory | asked only if several candidates (today) | first candidate, said so (today) | `init <dir>` then the key |
441
- | **hooks** | "Wire the three paper hooks into `.claude/settings.json` (committed, shared)? **[Y/n]**" | **yes** — the guard is what the package is for, the edit is idempotent and shows up in `git diff` | `--no-hooks` (`--hooks=local` deferred) |
442
- | CI workflow | "Add a GitHub Actions workflow? **[y/N]**" (today) | **no** (today) — it writes into `.github/` and spends Actions minutes, which are metered | `--ci` / `--no-ci` |
443
- | first paper | only when the papers dir holds no paper: "Create a first paper? name: [skip]" then "format: tex / md [tex]" | **no**, unless `--paper <name>` is given | `--paper <name> --format tex\|md` |
444
- | external programs | reported (today) | reported (today) | — |
433
+ | decision | human at a terminal | agent / CI / `--yes` | flag |
434
+ | ----------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------- |
435
+ | papers directory | asked only if several candidates (today) | first candidate, said so (today) | `init <dir>` then the key |
436
+ | **hooks** | "Wire the three paper hooks into `.claude/settings.json` (committed, shared)? **[Y/n]**" | **yes** — the guard is what the package is for, the edit is idempotent and shows up in `git diff` | `--no-hooks` (`--hooks=local` deferred) |
437
+ | CI workflow | "Add a GitHub Actions workflow? **[y/N]**" (today) | **no** (today) — it writes into `.github/` and spends Actions minutes, which are metered | `--ci` / `--no-ci` |
438
+ | first paper | only when the papers dir holds no paper: "Create a first paper? name: [skip]" | **no**, unless `--paper <name>` is given | `--paper <name>` |
439
+ | external programs | reported (today) | reported (today) | — |
445
440
 
446
441
  Why the hooks default is "yes" even without asking, while CI is "no": the rule `install.md` already
447
442
  adopted from Playwright is _"ask only about what cannot be guessed or is expensive"_. Installing a
@@ -495,8 +490,8 @@ Recorded so the design and the code do not quietly disagree.
495
490
  - **`rpp new` lints the new folder itself** (`rpp lint <papers>/<name>`), so the structure check —
496
491
  which looks at the subdirectories of the path it is given — does not run there; the folder was
497
492
  just made with the required files, so there is nothing for it to find.
498
- - **Interactive `init` asks "Create a first paper? name: [skip]" and then the format** only when the
499
- name was given and `--format` was not.
493
+ - **Interactive `init` asks "Create a first paper? name: [skip]"** only when the papers directory
494
+ holds no paper.
500
495
 
501
496
  ## Sources checked
502
497
 
@@ -7,7 +7,11 @@ what happens in CI and without a network.
7
7
 
8
8
  ## What is checked, and where
9
9
 
10
- For each entry in the bibliography (the `filecontents` block inside `paper.tex`, else `refs.bib`):
10
+ For each entry of the databases the build's bibtex **opened** — the `.bib` files its `\bibliography` /
11
+ `\addbibresource` named after every switch and macro, and the file a `filecontents` block wrote. The
12
+ build records them in `<paper>/_build/sources.json` from TeX's own files (`paper.blg`), so nothing here
13
+ guesses which `.bib` TeX reads by reading the `.tex`. Their text is read by the one bibtex reader
14
+ (`src/ports/bib-reader.ts`):
11
15
 
12
16
  | question | asked of |
13
17
  | -------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
@@ -23,18 +27,40 @@ A work that is simply not found is `unresolvable`, never a failure: plenty of re
23
27
  indexed. Only a positive disproof fails an entry, for example a DOI that resolves to a different
24
28
  paper, or one the DOI authority says does not exist.
25
29
 
26
- The verdicts land in `<paper>/_build/references.json`. `paperlint lint` reads that file and reports:
30
+ The verdicts land in `<paper>/_build/references.json`: one verdict per entry of those databases, in the
31
+ order bibtex opened them, and a hash of their text. `paperlint lint` reads that file and reports:
27
32
 
28
- | rule | when |
29
- | -------------------- | ----------------------------------------------------------------- |
30
- | `paper/cite-exists` | an entry's identifier provably does not resolve to the work cited |
31
- | `paper/author-list` | an entry's authors are the preprint's under a published venue |
32
- | `paper/refs-fresh` | the bibliography changed since the build checked it — build again |
33
- | `paper/refs-checked` | no build has checked the references, or the last one could not |
33
+ | rule | when |
34
+ | -------------------- | ----------------------------------------------------------------------------------------- |
35
+ | `paper/cite-exists` | an entry's identifier provably does not resolve to the work cited |
36
+ | `paper/author-list` | an entry's authors are the preprint's under a published venue |
37
+ | `paper/refs-fresh` | the databases changed since the build checked them — build again |
38
+ | `paper/refs-checked` | no build has checked the references, or the last one could not, or the reader missed some |
39
+
40
+ An entry of a `.bib` is reported at the first line of `paper.tex`, with the entry's file, line and
41
+ column at the front of the message (`refs.bib:12:1: …`): ESLint does not lint a `.bib`.
34
42
 
35
43
  The step never fails the build. Without a network the PDF is still built and lint warns that the
36
44
  references were not checked.
37
45
 
46
+ **No record of the build, no answer.** Which databases bibtex opened is in `sources.json`, so a paper
47
+ with no record (never built), or one whose files changed since (`paper/sources-fresh`), has none to
48
+ check: the step records nothing, and the four rules are silent while `paper/sources-fresh` says once
49
+ that the paper must be built.
50
+
51
+ **What bibtex typeset, against what paperlint read.** The record also holds the keys bibtex typeset
52
+ (its `paper.bbl`). bibtex has no comment syntax, so an entry behind `%` or inside `@comment{…}` is one
53
+ it typesets and paperlint's reader does not; `paper/refs-checked` names each such key as not checked.
54
+ (bibtex's keys are case-insensitive: `\cite{SMITH}` typesets `\bibitem{SMITH}` from an entry `smith`,
55
+ which is the same entry.) A malformed `.bib` never gets that far: bibtex fails on it, and `paperlint
56
+ build` fails with bibtex's own lines.
57
+
58
+ A database TeX wrote itself (a `filecontents` block) is read from the file it left beside the paper.
59
+ The build hashes it like any other, so when that file is edited or removed after the build the record
60
+ is stale and `paper/sources-fresh` says so: build again to write it. A database bibtex found outside
61
+ the paper directory, on its search path (`BIBINPUTS`), is not read: the step says which and
62
+ `paper/refs-checked` warns.
63
+
38
64
  ## `repro/references-cache.json` — commit it
39
65
 
40
66
  Asking five services about every entry is slow. DBLP asks clients to pause between requests, and a
@@ -0,0 +1,64 @@
1
+ # bib/reachable-entry
2
+
3
+ **Level:** warn · **Reads:** `paper.tex`, and `_build/sources.json` — the databases the last build's
4
+ bibtex opened, read through the bibtex reader
5
+
6
+ ## What it catches
7
+
8
+ A bibliography entry with no `doi`, no `url` and no arXiv id: nothing a reader can follow. The entries
9
+ judged are those of the databases bibtex opened in the last build — TeX's own answer, recorded by
10
+ `paperlint build`, not a reading of the source for `\bibliography`.
11
+
12
+ ## Why
13
+
14
+ Reviewer A on HotCRP #20 (2026-08-23): "not every entry has a DOI/link" — one paper had 0 of 47
15
+ entries reachable. Not "no doi": ICLR, NeurIPS and TMLR issue no DOIs at all (measured 2026-08-24),
16
+ so a url or an arXiv id counts.
17
+
18
+ ## Examples
19
+
20
+ Reported on the entry's own line when the `.bib` bibtex opened is one TeX wrote from a `filecontents`
21
+ block of `paper.tex`; any other entry — a `.bib` file the author keeps, or a block in an included file —
22
+ at the top of `paper.tex`, with the entry's file, line and column first:
23
+
24
+ ```bibtex
25
+ @book{lost2026, title = {A Book}, year = {2026}}
26
+ ```
27
+
28
+ > refs.bib:12:1: `lost2026` has no doi, url or arXiv id — a reader has nothing to follow. If none
29
+ > exists, keep the exception with `% eslint-disable-next-line bib/reachable-entry -- <why>` above
30
+ > the entry
31
+
32
+ Not reported:
33
+
34
+ ```bibtex
35
+ @article{a, doi = {10.1/x}}
36
+ @misc{b, url = {https://example.org}}
37
+ @misc{c, note = {arXiv:2310.05736}}
38
+ ```
39
+
40
+ ## Options / preset fields
41
+
42
+ None.
43
+
44
+ ## What it does not check
45
+
46
+ - Whether the link resolves: that is the references check of `paperlint build`.
47
+ - `@string` and `@preamble`, which are not entries.
48
+ - A paper with no record of a build, or one changed since: the rule is silent, and
49
+ [`paper/sources-fresh`](../paper/sources-fresh.md) says once that it was not built.
50
+ - A database bibtex found outside the paper directory, and an entry bibtex reads that the `.bib` reader
51
+ does not (behind `%`, inside `@comment{…}`): that is the post-build check's.
52
+ - With `eslint --cache`, a change to a `.bib` alone does not re-run the rule: its findings depend on
53
+ files other than `paper.tex`. `paperlint lint` does not cache.
54
+
55
+ ## How to fix
56
+
57
+ Add the entry's `doi`, `url` or arXiv id. An entry that has none — a talk, a personal communication
58
+ — keeps its exception where it is written, on the line above the entry, in the block or in the
59
+ `.bib`:
60
+
61
+ ```bibtex
62
+ % eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists
63
+ @misc{smith2024talk, …}
64
+ ```
@@ -0,0 +1,81 @@
1
+ # paper/sources-fresh
2
+
3
+ **Level:** warn · **Reads:** `paper.tex` and `_build/sources.json` · **Reported on:** the first line of
4
+ `paper.tex`
5
+
6
+ ## What it catches
7
+
8
+ The paper has no usable record of the last build, so paperlint does not know which files TeX reads for
9
+ it. One finding, in one of three forms:
10
+
11
+ - **not built** — there is no `_build/sources.json`;
12
+ - **unreadable** — the file is not JSON, is of another schema number, or does not have the shape of
13
+ schema 1;
14
+ - **stale** — a file the build read has other bytes now, was deleted, or appeared where the build saw
15
+ none. The message names each: `sections/intro.tex edited, refs.bib deleted`.
16
+
17
+ ## Why
18
+
19
+ `paperlint build` asks TeX itself which files it read (`pdflatex -recorder`) and which databases bibtex
20
+ opened, and writes the answer, with a SHA-256 of every file, to `_build/sources.json`. Nothing in
21
+ paperlint answers "which files make up this paper" or "which bibliography does it use" by reading the
22
+ TeX source: `\input` behind a macro, a `.bib` written by `filecontents`, a database named by a
23
+ switch are TeX's to decide, and TeX has already decided.
24
+
25
+ The rules that need that answer — the ones over the bibliography, and the prose rules over the files
26
+ `paper.tex` includes — read the record. With no record, or one about an earlier paper, they would
27
+ judge files that are not the paper's, so they stay silent, and this rule speaks once in their place.
28
+ Without it a paper that was never built would lint clean because nothing was looked at.
29
+
30
+ ## Examples
31
+
32
+ Failing — a paper that was never built:
33
+
34
+ ```text
35
+ papers/my-paper/paper.tex
36
+ 1:1 warning the paper has not been built — run `npx paperlint build`, which records the files TeX
37
+ reads. Until it has, the files `paper.tex` includes are not linted, and the rules that
38
+ read the bibliography say nothing
39
+ paper/sources-fresh
40
+ ```
41
+
42
+ Failing — `sections/intro.tex` was edited after the last build:
43
+
44
+ ```text
45
+ papers/my-paper/paper.tex
46
+ 1:1 warning the paper changed since the last build (sections/intro.tex edited) — run `npx paperlint
47
+ build`; until then the files `paper.tex` includes are not linted, and the rules that
48
+ read the bibliography say nothing
49
+ paper/sources-fresh
50
+ ```
51
+
52
+ Passing: a paper whose files are byte for byte what the last build read. Editing a file TeX did not
53
+ read (a figure source the paper does not include, a note beside it) changes nothing.
54
+
55
+ ## Options / preset fields
56
+
57
+ No rule options. The level is `warn` for every paper; set it in the paper's `paperlint.json`:
58
+
59
+ ```json
60
+ { "rules": { "paper/sources-fresh": "error" } }
61
+ ```
62
+
63
+ `error` suits a CI job that builds first; `off` suits one that only lints and would rather not hear it.
64
+
65
+ ## What it does not check
66
+
67
+ - That the build was clean. A record is written after a build whose pdflatex and bibtex runs ended;
68
+ what they reported is `paper/refs-checked`'s and the build's own.
69
+ - Files TeX never read. A file the paper does not include is not in the record, so its edits are not
70
+ changes, and it is not linted.
71
+ - A file edited in an editor and not saved: the rule compares the bytes on disk with the record.
72
+ - `.bib` files TeX wrote itself (`filecontents`): their bytes are the block's, which is in a file the
73
+ record hashes.
74
+
75
+ ## How to fix
76
+
77
+ Build the paper again; the record is rewritten from the files as they are:
78
+
79
+ ```sh
80
+ npx paperlint build papers/my-paper
81
+ ```
@@ -68,7 +68,6 @@ any other rule.
68
68
  3 of the model_) is read as a quantity, and so is the number of a name that opens the sentence
69
69
  (_Claude 3 fails …_).
70
70
  - Whether a count followed by a plural noun is really the sample (_3 times faster_ passes as one).
71
- - Markdown papers (`paper.md`): it reads the LaTeX parse tree only.
72
71
 
73
72
  ## How to fix
74
73