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
@@ -7,6 +7,7 @@ import { readdirSync, readFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { describe, expect, it } from "vitest";
9
9
  import { memoryFiles } from "./adapters/memory/index.ts";
10
+ import { sourcesCodec } from "./adapters/sources-record/index.ts";
10
11
  import { presetsDir } from "./package-dirs.ts";
11
12
  import { latexReader } from "./adapters/latex/index.ts";
12
13
  import {
@@ -21,10 +22,13 @@ import {
21
22
  } from "./tex-venue-rules.ts";
22
23
  import { resolvePreset } from "./presets.ts";
23
24
  import { buildConfig, SHIPPED_RULES } from "./cli.ts";
25
+ import { builtPaper } from "../test/recorded-fixture.ts";
24
26
  import { venuePreset } from "../test/support.ts";
25
27
 
26
28
  const VENUES = presetsDir();
27
29
  const PAPER = "/work/papers/p";
30
+ /** What the rules read with besides the files: the presets, the LaTeX reader, the record's schema. */
31
+ const READERS = { venuesDir: VENUES, latex: latexReader, codec: sourcesCodec };
28
32
  const shipped = Object.fromEntries(
29
33
  readdirSync(VENUES)
30
34
  .filter((f) => f.endsWith(".jsonc") || f.endsWith(".json"))
@@ -70,7 +74,7 @@ function lint(
70
74
  ...extra,
71
75
  });
72
76
  const out: Finding[] = [];
73
- const deps = { files, venuesDir: VENUES, latex: latexReader };
77
+ const deps = { files, ...READERS };
74
78
  for (const [id, rule] of [
75
79
  ...Object.entries(texVenueRules(deps)).map(
76
80
  ([n, r]) => [`tex/${n}`, r] as const,
@@ -207,13 +211,29 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
207
211
  "\\documentclass[conference,compsoc]{IEEEtran}",
208
212
  "Text.\n\\input{sections/closing}",
209
213
  );
210
- const extra = {
211
- [`${PAPER}/sections/closing.tex`]:
212
- "\\section*{LLM Usage Statement}\nNone.\n",
213
- };
214
+ // The build recorded TeX reading both files.
215
+ const extra = builtPaper(
216
+ PAPER,
217
+ {
218
+ "paper.tex": tex,
219
+ "sections/closing.tex": "\\section*{LLM Usage Statement}\nNone.\n",
220
+ },
221
+ [
222
+ { path: "paper.tex", role: "body" },
223
+ { path: "sections/closing.tex", role: "body" },
224
+ ],
225
+ );
214
226
  expect(lint(tex, AIDC, { extra })).toEqual([]);
215
227
  // Without the file, the section is missing.
216
228
  expect(ids(lint(tex, AIDC))).toEqual(["tex/required-section:missing"]);
229
+ // 🔴 The file on disk and no record of the build: the paper is paper.tex, and the section is missing.
230
+ const unbuilt = {
231
+ [`${PAPER}/sections/closing.tex`]:
232
+ "\\section*{LLM Usage Statement}\nNone.\n",
233
+ };
234
+ expect(ids(lint(tex, AIDC, { extra: unbuilt }))).toEqual([
235
+ "tex/required-section:missing",
236
+ ]);
217
237
  });
218
238
 
219
239
  it("a leftover venue name in an included file is reported at the \\input, naming the file and line", () => {
@@ -222,9 +242,17 @@ describe("the venue rules read the whole paper: the files it \\inputs, spliced w
222
242
  "Text.\n\\input{intro}\n\\section*{LLM Usage Statement}\nNone.",
223
243
  );
224
244
  const fs = lint(tex, AIDC, {
225
- extra: {
226
- [`${PAPER}/intro.tex`]: "One line.\nFirst written for AgenticDev.\n",
227
- },
245
+ extra: builtPaper(
246
+ PAPER,
247
+ {
248
+ "paper.tex": tex,
249
+ "intro.tex": "One line.\nFirst written for AgenticDev.\n",
250
+ },
251
+ [
252
+ { path: "paper.tex", role: "body" },
253
+ { path: "intro.tex", role: "body" },
254
+ ],
255
+ ),
228
256
  });
229
257
  expect(fs.map((f) => [f.line, f.message.split(": ")[0]])).toEqual([
230
258
  [5, "intro.tex:2:19"],
@@ -49,7 +49,7 @@ import type { LatexReader } from "./ports/latex.ts";
49
49
  import { paperPreset, shippedVenueNames, type Preset } from "./presets.ts";
50
50
  import type { NamedVenue } from "./domain/venue-name.ts";
51
51
  import type { PaperSource } from "./domain/paper-source.ts";
52
- import { readPaper, reportInPaper } from "./tex-paper.ts";
52
+ import { readPaper, reportInPaper, type ProseDeps } from "./tex-paper.ts";
53
53
  import type { RequiredSection } from "./tex-requirements.ts";
54
54
  import type { Finding, VenueRuleDeps } from "./venue-rules.ts";
55
55
  import { isPorting } from "./domain/cycle.ts";
@@ -476,10 +476,8 @@ export function otherVenues(
476
476
  .map(({ label, aliases, mentions }) => ({ label, aliases, mentions }));
477
477
  }
478
478
 
479
- /** What the venue-conformance rules are built with: the preset store, and the LaTeX reader. */
480
- export interface TexVenueRuleDeps extends VenueRuleDeps {
481
- readonly latex: LatexReader;
482
- }
479
+ /** What the venue-conformance rules are built with: the preset store, the LaTeX reader, and the paper's files as the build recorded them. */
480
+ export interface TexVenueRuleDeps extends VenueRuleDeps, ProseDeps {}
483
481
 
484
482
  /**
485
483
  * What a judge reads for the paper whose `paper.tex` is `filename` — the paper with its includes
package/src/types.ts CHANGED
@@ -62,8 +62,6 @@ export interface Args {
62
62
  noHooks: boolean;
63
63
  /** `--paper <name>`: `init` creates this paper. */
64
64
  paper: string | null;
65
- /** `--format tex|md` for `new` and `init --paper`. Validated by the command, not here. */
66
- format: string | null;
67
65
  /** `--venue <preset>` for `new`: a shipped preset name, or a path to the project's own. */
68
66
  venue: string | null;
69
67
  /** `--kind <kind>` for `new`: one of the venue preset's kinds. */
@@ -1,22 +0,0 @@
1
- import type { RuleContext } from "./rule-context.ts";
2
- declare const _default: {
3
- rules: {
4
- "reachable-entry": {
5
- meta: {
6
- type: "suggestion";
7
- docs: {
8
- description: string;
9
- };
10
- schema: never[];
11
- messages: {
12
- unreachable: string;
13
- };
14
- };
15
- create(context: RuleContext): {
16
- "root:exit"(): void;
17
- };
18
- };
19
- };
20
- };
21
- export default _default;
22
- //# sourceMappingURL=bib-reachable-entry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bib-reachable-entry.d.ts","sourceRoot":"","sources":["../../eslint-rules/bib-reachable-entry.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;;;;;;;;;;;;;;4BAsB/B,WAAW;;;;;;AAfjC,wBA4CE"}
@@ -1,75 +0,0 @@
1
- /**
2
- * `bib/reachable-entry` — a bibliography entry a reader cannot follow: no doi, no url, no arXiv
3
- * id. Reported ON THE ENTRY, one finding per entry; there is no fix, because the link has to be
4
- * looked up. Default severity: warn.
5
- *
6
- * Provenance: Reviewer A on HotCRP #20 (2026-08-23), "not every entry has a DOI/link" — one
7
- * paper had 0 of 47 entries reachable. Until 3.0.0 this was one count inside `paper/typography`,
8
- * silenced per paper by a declared debt; now each entry is its own finding, and an entry that
9
- * genuinely has no link (a talk, a personal communication) carries a disable directive with its
10
- * reason, in the bibliography itself:
11
- *
12
- * % eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists
13
- * @misc{smith2024talk, …}
14
- *
15
- * ⚠️ NOT "no doi". Measured 2026-08-24: ICLR/NeurIPS/TMLR issue no DOIs at all, so a doi-only
16
- * rule would demand something that does not exist and get muted for lying. A url or an arXiv id
17
- * counts.
18
- *
19
- * The bibliography read is the one INSIDE `paper.tex` (`filecontents`), where the papers this
20
- * package targets keep it. The entry boundaries and fields are found by a line-anchored `@` and
21
- * a field name followed by `=` — BibTeX's own lexical shape, one lexeme at a time, not a parse:
22
- * the parser (`@retorquere/bibtex-parser`) is an OPTIONAL peer of this package.
23
- */
24
- import { bibRange } from "./paper-typography.js";
25
- /** Entry types that are not references. */
26
- const NOT_AN_ENTRY = /^@(comment|string|preamble)\b/i;
27
- const HAS_LINK = /\b(doi|url)\s*=/i;
28
- const HAS_ARXIV = /arxiv[:\s]*\d{4}\.\d{4,5}/i;
29
- export default {
30
- rules: {
31
- "reachable-entry": {
32
- meta: {
33
- type: "suggestion",
34
- docs: {
35
- description: "a bibliography entry carries a doi, a url or an arXiv id — something a reader can follow",
36
- },
37
- schema: [],
38
- messages: {
39
- unreachable: "`{{key}}` has no doi, url or arXiv id — a reader has nothing to follow. If none exists, keep the exception with `% eslint-disable-next-line bib/reachable-entry -- <why>` above the entry",
40
- },
41
- },
42
- create(context) {
43
- return {
44
- "root:exit"() {
45
- const sc = context.sourceCode;
46
- const raw = sc.raw ?? sc.text;
47
- const bib = typeof raw === "string" ? bibRange(raw) : null;
48
- if (!bib)
49
- return;
50
- const starts = [...bib.body.matchAll(/^@/gm)].map((m) => m.index);
51
- starts.forEach((s, i) => {
52
- const entry = bib.body.slice(s, starts[i + 1] ?? bib.body.length);
53
- if (NOT_AN_ENTRY.test(entry))
54
- return;
55
- if (HAS_LINK.test(entry) || HAS_ARXIV.test(entry))
56
- return;
57
- const [head = ""] = entry.split("\n");
58
- const key = /\{\s*([^,\s]+)/.exec(head)?.[1] ?? "?";
59
- const from = bib.bodyStart + s;
60
- context.report({
61
- loc: {
62
- start: sc.getLocFromIndex(from),
63
- end: sc.getLocFromIndex(from + head.length),
64
- },
65
- messageId: "unreachable",
66
- data: { key },
67
- });
68
- });
69
- },
70
- };
71
- },
72
- },
73
- },
74
- };
75
- //# sourceMappingURL=bib-reachable-entry.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bib-reachable-entry.js","sourceRoot":"","sources":["../../eslint-rules/bib-reachable-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGjD,2CAA2C;AAC3C,MAAM,YAAY,GAAG,gCAAgC,CAAC;AACtD,MAAM,QAAQ,GAAG,kBAAkB,CAAC;AACpC,MAAM,SAAS,GAAG,4BAA4B,CAAC;AAE/C,eAAe;IACb,KAAK,EAAE;QACL,iBAAiB,EAAE;YACjB,IAAI,EAAE;gBACJ,IAAI,EAAE,YAAqB;gBAC3B,IAAI,EAAE;oBACJ,WAAW,EACT,0FAA0F;iBAC7F;gBACD,MAAM,EAAE,EAAE;gBACV,QAAQ,EAAE;oBACR,WAAW,EACT,2LAA2L;iBAC9L;aACF;YACD,MAAM,CAAC,OAAoB;gBACzB,OAAO;oBACL,WAAW;wBACT,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC;wBAC9B,MAAM,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;wBAC9B,MAAM,GAAG,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;wBAC3D,IAAI,CAAC,GAAG;4BAAE,OAAO;wBACjB,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;wBAClE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;4BACtB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;4BAClE,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;gCAAE,OAAO;4BACrC,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC;gCAAE,OAAO;4BAC1D,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;4BACtC,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;4BACpD,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC;4BAC/B,OAAO,CAAC,MAAM,CAAC;gCACb,GAAG,EAAE;oCACH,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC;oCAC/B,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;iCAC5C;gCACD,SAAS,EAAE,aAAa;gCACxB,IAAI,EAAE,EAAE,GAAG,EAAE;6BACd,CAAC,CAAC;wBACL,CAAC,CAAC,CAAC;oBACL,CAAC;iBACF,CAAC;YACJ,CAAC;SACF;KACF;CACF,CAAC"}
@@ -1,76 +0,0 @@
1
- /**
2
- * `bib/reachable-entry` — a bibliography entry a reader cannot follow: no doi, no url, no arXiv
3
- * id. Reported ON THE ENTRY, one finding per entry; there is no fix, because the link has to be
4
- * looked up. Default severity: warn.
5
- *
6
- * Provenance: Reviewer A on HotCRP #20 (2026-08-23), "not every entry has a DOI/link" — one
7
- * paper had 0 of 47 entries reachable. Until 3.0.0 this was one count inside `paper/typography`,
8
- * silenced per paper by a declared debt; now each entry is its own finding, and an entry that
9
- * genuinely has no link (a talk, a personal communication) carries a disable directive with its
10
- * reason, in the bibliography itself:
11
- *
12
- * % eslint-disable-next-line bib/reachable-entry -- an invited talk, no recording exists
13
- * @misc{smith2024talk, …}
14
- *
15
- * ⚠️ NOT "no doi". Measured 2026-08-24: ICLR/NeurIPS/TMLR issue no DOIs at all, so a doi-only
16
- * rule would demand something that does not exist and get muted for lying. A url or an arXiv id
17
- * counts.
18
- *
19
- * The bibliography read is the one INSIDE `paper.tex` (`filecontents`), where the papers this
20
- * package targets keep it. The entry boundaries and fields are found by a line-anchored `@` and
21
- * a field name followed by `=` — BibTeX's own lexical shape, one lexeme at a time, not a parse:
22
- * the parser (`@retorquere/bibtex-parser`) is an OPTIONAL peer of this package.
23
- */
24
- import { bibRange } from "./paper-typography.ts";
25
- import type { RuleContext } from "./rule-context.ts";
26
-
27
- /** Entry types that are not references. */
28
- const NOT_AN_ENTRY = /^@(comment|string|preamble)\b/i;
29
- const HAS_LINK = /\b(doi|url)\s*=/i;
30
- const HAS_ARXIV = /arxiv[:\s]*\d{4}\.\d{4,5}/i;
31
-
32
- export default {
33
- rules: {
34
- "reachable-entry": {
35
- meta: {
36
- type: "suggestion" as const,
37
- docs: {
38
- description:
39
- "a bibliography entry carries a doi, a url or an arXiv id — something a reader can follow",
40
- },
41
- schema: [],
42
- messages: {
43
- unreachable:
44
- "`{{key}}` has no doi, url or arXiv id — a reader has nothing to follow. If none exists, keep the exception with `% eslint-disable-next-line bib/reachable-entry -- <why>` above the entry",
45
- },
46
- },
47
- create(context: RuleContext) {
48
- return {
49
- "root:exit"() {
50
- const sc = context.sourceCode;
51
- const raw = sc.raw ?? sc.text;
52
- const bib = typeof raw === "string" ? bibRange(raw) : null;
53
- if (!bib) return;
54
- const starts = [...bib.body.matchAll(/^@/gm)].map((m) => m.index);
55
- starts.forEach((s, i) => {
56
- const entry = bib.body.slice(s, starts[i + 1] ?? bib.body.length);
57
- if (NOT_AN_ENTRY.test(entry)) return;
58
- if (HAS_LINK.test(entry) || HAS_ARXIV.test(entry)) return;
59
- const [head = ""] = entry.split("\n");
60
- const key = /\{\s*([^,\s]+)/.exec(head)?.[1] ?? "?";
61
- const from = bib.bodyStart + s;
62
- context.report({
63
- loc: {
64
- start: sc.getLocFromIndex(from),
65
- end: sc.getLocFromIndex(from + head.length),
66
- },
67
- messageId: "unreachable",
68
- data: { key },
69
- });
70
- });
71
- },
72
- };
73
- },
74
- },
75
- },
76
- };
@@ -1,22 +0,0 @@
1
- {
2
- "$comment": [
3
- "What the rules say about this article TODAY, as data rather than as a sentence in a docblock.",
4
- "It is not a target and not a clean bill of health — the fixture's README says findings here",
5
- "are measurements. Its job is the SILENCE half: a rule that starts reporting something new on",
6
- "225 lines of real prose has almost certainly gained a false positive, and the harness beside",
7
- "this file names the rule. That is not expressible on an eleven-line stub, where every line was",
8
- "written by someone who already knew which rule would read it.",
9
- "",
10
- "Recorded 2026-09-19, re-recorded 2026-09-26 for 3.0.0. Two of the three rules are about the",
11
- "scorecard being deliberately thin (no pdf, no frozen source) rather than about the prose.",
12
- "paper/leading-zero reports each of the 18 p-values of paperlint#44 where it is (one finding per",
13
- "occurrence since 3.0.0; before, paper/typography reported them as one count). They are real",
14
- "findings against IEEE / ISO 80000-1 style, in a blog post written for a general audience.",
15
- "The count is expected to stay. A drop is never a failure here, only growth is."
16
- ],
17
- "findings": {
18
- "paper/stages": 2,
19
- "paper/source": 1,
20
- "paper/leading-zero": 18
21
- }
22
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * ONE reader of `baseline.json`, shared by every run that compares against one: the harness beside
3
- * this file (the repository's own `bin/paperlint.mjs`), `test/e2e/install/install.e2e.ts` (the
4
- * binary a consumer actually got), and `fixtures/accepted-papers/accepted-papers.test.ts` (one
5
- * baseline per accepted paper). Two copies of "growth fails, a drop never does" would drift the
6
- * first time one of them is tightened, and the runs would then disagree about the same rules.
7
- */
8
- import { readFileSync } from "node:fs";
9
- import { dirname, join } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import { z } from "zod";
12
-
13
- /** Findings per rule id. */
14
- export type Counts = Record<string, number>;
15
-
16
- const Baseline = z.object({ findings: z.record(z.string(), z.number()) });
17
- const LintOutput = z.array(
18
- z.object({
19
- messages: z.array(z.object({ ruleId: z.string().nullable() })).nullish(),
20
- }),
21
- );
22
-
23
- const HERE = dirname(fileURLToPath(import.meta.url));
24
-
25
- /** The counts recorded in `dir`'s `baseline.json` (this article's by default), `{ruleId: n}`. */
26
- export function recordedFindings(dir: string = HERE): Counts {
27
- return Baseline.parse(
28
- JSON.parse(readFileSync(join(dir, "baseline.json"), "utf8")),
29
- ).findings;
30
- }
31
-
32
- /**
33
- * `paperlint lint --json` stdout → `{ruleId: n}`.
34
- * 🔴 Output that does not parse THROWS. An empty set read as "clean" is how this repository's
35
- * checks have gone hollow before.
36
- */
37
- export function countByRule(stdout: string): Counts {
38
- const out: Counts = {};
39
- for (const file of LintOutput.parse(JSON.parse(stdout)))
40
- for (const m of file.messages ?? []) {
41
- const rule = String(m.ruleId);
42
- out[rule] = (out[rule] ?? 0) + 1;
43
- }
44
- return out;
45
- }
46
-
47
- /**
48
- * Compare measured counts with the recording.
49
- * grew — a rule says MORE about real prose than recorded: a false positive until shown
50
- * otherwise. Always a failure.
51
- * vanished — a recorded rule went fully quiet without the recording being updated: how a check
52
- * dies unnoticed. Also a failure.
53
- * A partial drop is neither: it is what a fix looks like (paperlint#44 is expected to take typography
54
- * to zero, and then the recording is updated in the same change).
55
- */
56
- export function compareToBaseline(
57
- found: Readonly<Counts>,
58
- recorded: Readonly<Counts> = recordedFindings(),
59
- ): {
60
- grew: { rule: string; now: number; recorded: number }[];
61
- vanished: string[];
62
- } {
63
- const grew = Object.entries(found)
64
- .filter(([rule, n]) => n > (recorded[rule] ?? 0))
65
- .map(([rule, n]) => ({ rule, now: n, recorded: recorded[rule] ?? 0 }));
66
- const vanished = Object.keys(recorded).filter(
67
- (r) => (recorded[r] ?? 0) > 0 && !(r in found),
68
- );
69
- return { grew, vanished };
70
- }