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
@@ -40,31 +40,12 @@ counter, is not this skill; it is the damage this skill exists to undo.
40
40
  If you are invoked while the author is still adding material, say so and decline the pass. The right
41
41
  answer is "not yet", not a smaller cut.
42
42
 
43
- ## 🏗 START WITH THE MECHANICAL LEG — run \`structure.mjs\` (added 2026-08-05)
43
+ ## 🏗 START WITH THE SHAPE — weigh every section before judging it
44
44
 
45
- \`\`\`
46
- node .claude/skills/tighten-paper/structure.mjs <paper.md> # whole paper
47
- node .claude/skills/tighten-paper/structure.mjs <paper.md> --section=3 # one section
48
- \`\`\`
49
-
50
- 🔴 **THE SCRIPT NO LONGER FINDS ANYTHING — it is an INVENTORY (2026-08-26).** All sixteen of its
51
- checks moved into ESLint rules (\`eslint-rules/paper-structure.mjs\`). The findings you used to read
52
- here now come from:
53
-
54
- \`\`\`
55
- npx eslint --no-config-lookup --config eslint.config.mjs <paper.md>
56
- \`\`\`
57
-
58
- ⚠️ Do NOT pass \`--flags-only\`. It is kept only because two harnesses assert on its silence, and it
59
- prints nothing and exits 0 whatever the paper looks like. Measured 26.08 on \`the reference paper\`:
60
- \`--flags-only\` printed 0 lines while ESLint on the same bytes reported **6 structural findings**
61
- (subsection-size ×2 · section-lead · free-section-size ×2 · block-ungraded). A mode that cannot
62
- speak is not a check, and reading its silence as "clean" is the defect this pipeline is named for.
63
-
64
- What the inventory prints, and why it stays a script: the outline **in order** — every heading, its
65
- own words, its total with subsections, its share of the body, and the \`carries:\`/\`score:\`/\`verdict:\`
66
- note the section wrote about itself, printed beside its weight. That comparison is the whole point
67
- and a linter cannot make it: a finding has nowhere to put a table.
45
+ Write the outline out **in order**: every heading, its own words before its first subsection, its
46
+ total with subsections, its share of the body — and beside each, the \`carries:\`/\`score:\`/\`verdict:\`
47
+ note the section wrote about itself. That comparison, a section's weight printed next to what it
48
+ claims about itself, is the whole point.
68
49
 
69
50
  🔴 **Why this exists, and why word counts alone mislead.** Asked whether a paper short-changed its
70
51
  contribution, per-section word counts said no — the contribution had 38% more words than related
@@ -73,7 +54,7 @@ owned **0, 28 and 38** words before their first subsection. Nothing in the toolc
73
54
  question, because every metric was per-sentence or per-document and none was per-section. The author's
74
55
  word for the result was a "salad" — a jumbled mix — and he was right while the numbers said fine.
75
56
 
76
- **What the script does NOT decide, and you must:** the ORDER, the NAMES, and whether a section earns
57
+ **What the count does NOT decide, and you must:** the ORDER, the NAMES, and whether a section earns
77
58
  its place. Those are judgement, and pretending otherwise would be the failure this whole pipeline
78
59
  exists to prevent.
79
60
 
@@ -194,18 +175,24 @@ called it "fat for no reason" after a pass that cut almost nothing and left it 1
194
175
 
195
176
  A verdict that lives in a chat log is gone by the next session, and the next session then re-derives
196
177
  it or ignores it. **Every section and subsection gets a one-line verdict comment immediately above its
197
- heading**, in the paper source, stamped with the date and the skill that produced it:
198
-
199
- \`\`\`
200
- <!-- TIGHTEN 2026-08-06 · score 8/10 · verdict: KEEP · 190w
201
- carries: tools get close but none resolves a NAMED rule against a committed config
202
- because: it is the only place the tool-ecosystem gap is closed; a reviewer who skips it
203
- asks "hasn't this been done" and gets no answer -->
204
- <!-- TIGHTEN 2026-08-06 · score 5/10 · verdict: SHORTEN -89w · 210w
205
- carries: why a linter for the rules file cannot work
206
- because: the point lands in two sentences; the rest re-establishes what §2 already did -->
207
- <!-- TIGHTEN 2026-08-06 · score 2/10 · verdict: MOVE→artifact · 45w
208
- carries: nothing the reader must hold to judge the contribution -->
178
+ heading**, in \`paper.tex\`, as LaTeX \`%\` comments, stamped with the date and the skill that
179
+ produced it:
180
+
181
+ \`\`\`latex
182
+ % TIGHTEN 2026-08-06 · score 8/10 · verdict: KEEP · 190w
183
+ % carries: tools get close but none resolves a NAMED rule against a committed config
184
+ % because: it is the only place the tool-ecosystem gap is closed; a reviewer who skips it
185
+ % asks "hasn't this been done" and gets no answer
186
+ \\section{Related tools}
187
+
188
+ % TIGHTEN 2026-08-06 · score 5/10 · verdict: SHORTEN -89w · 210w
189
+ % carries: why a linter for the rules file cannot work
190
+ % because: the point lands in two sentences; the rest re-establishes what §2 already did
191
+ \\subsection{Why not a linter}
192
+
193
+ % TIGHTEN 2026-08-06 · score 2/10 · verdict: MOVE→artifact · 45w
194
+ % carries: nothing the reader must hold to judge the contribution
195
+ \\subsection{Implementation notes}
209
196
  \`\`\`
210
197
 
211
198
  🔴 **Before assigning any \`MOVE→\` verdict, read \`paper-pipeline/references/body-vs-appendix.md\`.**
@@ -230,7 +217,7 @@ written and **not one ever concluded "cut me"**. Author, 2026-08-06, reading the
230
217
  value on every page and compared nothing.
231
218
 
232
219
  The pairing is the mechanism: **a low score beside a KEEP verdict is a visible contradiction**, and
233
- \`structure.mjs\` reports it. Prose cannot contradict itself; a number can.
220
+ the outline above puts the two side by side. Prose cannot contradict itself; a number can.
234
221
 
235
222
  **Calibration, so the scale does not drift to "everything is an 8":**
236
223
  - **9–10** — remove it and a reviewer cannot assess the contribution. The claim, its evidence, its price.
@@ -285,10 +272,10 @@ Rules for these comments:
285
272
  unpriced prose accumulates, because nothing there costs a page.
286
273
  - **Stale beats absent, but not by much.** A comment whose date precedes the last substantive edit to
287
274
  its section is stale; re-grade it rather than trusting it.
288
- - **A verdict nobody executed is a note, not a decision.** \`structure.mjs\` reports blocks still
289
- carrying a non-KEEP verdict — a paper full of unexecuted CUTs is worse than one never graded,
275
+ - **A verdict nobody executed is a note, not a decision.** List the blocks still carrying a
276
+ non-KEEP verdict — a paper full of unexecuted CUTs is worse than one never graded,
290
277
  because it reads as decided.
291
- - They are working notes: stripped before typesetting, like every other \`<!-- -->\` block.
278
+ - They are working notes: a \`%\` comment never reaches the PDF.
292
279
  - The chat summary still gets written — but it is the *derivative*, and the paper is the record.
293
280
 
294
281
  ## 🧱 Enforce subsections — a section without them is a wall
@@ -4,7 +4,7 @@ description: Verify every citation is a real work with correct metadata, and tha
4
4
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:faf8d55c8ddb82ed compiled from skills/verify-citations/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:fdb023ed7c613e1b compiled from skills/verify-citations/SKILL.md.spec.ts -->
8
8
 
9
9
  # verify-citations — every cite real, the delta explicit
10
10
 
@@ -165,10 +165,13 @@ heavier than a typo, and it is exactly the class §"any regalia about third part
165
165
 
166
166
  **How to run.**
167
167
  ```
168
- node scripts/bib-authors.mjs <paper-dir> # or a .bib, or a .tex with a filecontents block
168
+ node scripts/bib-authors.mjs <paper-dir> # the databases bibtex opened in the last build; or a .bib, or a .tex
169
169
  node scripts/bib-authors.mjs <paper-dir> --json
170
170
  ```
171
- Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error.
171
+ Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error. A paper
172
+ directory or a `.tex` is read through the build's record (`_build/sources.json`): a paper that was never built,
173
+ or changed since, is refused (exit 2) and told to run `npx paperlint build` first — the databases are TeX's answer,
174
+ not a reading of the source. A `.bib` named is read as itself.
172
175
 
173
176
  **Source is DBLP, and that choice is the evidence.** DBLP indexes the proceedings record and the CoRR record
174
177
  **separately**, so a single query returns both lists side by side — the cleanest proof available that the
@@ -244,7 +247,7 @@ It used to be one script, `verify-refs.mjs`; the measurement half stayed a scrip
244
247
  became twelve ESLint rules over the facts it writes.
245
248
 
246
249
  ```
247
- # 1. measure: parse the bibliography, ask CrossRef/arXiv, write _build/refs.facts.json
250
+ # 1. measure: parse the bibliography bibtex opened (build the paper first), ask CrossRef/arXiv, write _build/refs.facts.json
248
251
  node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir>
249
252
  node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir> --offline # cached responses only
250
253
 
@@ -181,10 +181,13 @@ heavier than a typo, and it is exactly the class §"any regalia about third part
181
181
 
182
182
  **How to run.**
183
183
  \`\`\`
184
- node scripts/bib-authors.mjs <paper-dir> # or a .bib, or a .tex with a filecontents block
184
+ node scripts/bib-authors.mjs <paper-dir> # the databases bibtex opened in the last build; or a .bib, or a .tex
185
185
  node scripts/bib-authors.mjs <paper-dir> --json
186
186
  \`\`\`
187
- Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error.
187
+ Exits **1** on any author-set or author-order disagreement, **0** when clean, **2** on usage/IO error. A paper
188
+ directory or a \`.tex\` is read through the build's record (\`_build/sources.json\`): a paper that was never built,
189
+ or changed since, is refused (exit 2) and told to run \`npx paperlint build\` first — the databases are TeX's answer,
190
+ not a reading of the source. A \`.bib\` named is read as itself.
188
191
 
189
192
  **Source is DBLP, and that choice is the evidence.** DBLP indexes the proceedings record and the CoRR record
190
193
  **separately**, so a single query returns both lists side by side — the cleanest proof available that the
@@ -260,7 +263,7 @@ It used to be one script, \`verify-refs.mjs\`; the measurement half stayed a scr
260
263
  became twelve ESLint rules over the facts it writes.
261
264
 
262
265
  \`\`\`
263
- # 1. measure: parse the bibliography, ask CrossRef/arXiv, write _build/refs.facts.json
266
+ # 1. measure: parse the bibliography bibtex opened (build the paper first), ask CrossRef/arXiv, write _build/refs.facts.json
264
267
  node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir>
265
268
  node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs <paper-dir> --offline # cached responses only
266
269
 
@@ -13,9 +13,15 @@ import {
13
13
  checkAuthors,
14
14
  claimsPublished,
15
15
  dblpHits,
16
- parseBib,
16
+ authorEntries,
17
17
  surnames,
18
18
  } from "./bib-authors.mjs";
19
+ import { bibReader } from "#src/adapters/bibtex/index";
20
+ import { absolutePath } from "#src/domain/paths";
21
+
22
+ /** This check's entries of a BibTeX text, read by the one bibtex reader. */
23
+ const entriesIn = (bib) =>
24
+ authorEntries(bibReader.readFile(absolutePath("/p/refs.bib"), bib).entries);
19
25
 
20
26
  afterEach(() => vi.unstubAllGlobals());
21
27
 
@@ -50,15 +56,6 @@ test("checkAuthors sorts every kind of entry into its bucket", async () => {
50
56
  entry("nomatch", "Ada Lovelace", "Nobody Has This"),
51
57
  entry("onlycorr", "Ada Lovelace", "Only On CoRR"),
52
58
  entry("down", "Ada Lovelace", "Service Down"),
53
- {
54
- type: "mdref",
55
- key: "sw",
56
- unparsed: true,
57
- author: "",
58
- title: "",
59
- booktitle: "",
60
- journal: "",
61
- },
62
59
  ];
63
60
  const answers = {
64
61
  "Same Paper": [hit("Same Paper.", ["Ada Lovelace", "Alan Turing"])],
@@ -101,10 +98,6 @@ test("checkAuthors sorts every kind of entry into its bucket", async () => {
101
98
  },
102
99
  ],
103
100
  skipped: [
104
- {
105
- key: "sw",
106
- why: "no author/title — a reference to software or a dataset, DBLP does not apply",
107
- },
108
101
  {
109
102
  key: "etal",
110
103
  why: "author list ends in `and others` — completeness not checkable",
@@ -228,35 +221,27 @@ test("dblpHits: no hits is an empty list; 429 is retryable; other errors are not
228
221
  );
229
222
  });
230
223
 
231
- test("parseBib reads quoted and bare field values, and a field that is absent", () => {
224
+ test("authorEntries: names written family first (`and others` kept), a quoted value, a field that is absent", () => {
232
225
  assert.deepEqual(
233
- parseBib(
234
- '@article{k, author = "Ada Lovelace", title = {T}, journal = J, }',
226
+ entriesIn(
227
+ '@article{k, author = "Ada Lovelace and van der Berg, Jan and others", title = {The {BERT} Model}, journal = {J}}\n@misc{untitled, author = {Hopper, Grace}}',
235
228
  ),
236
229
  [
237
230
  {
238
231
  type: "article",
239
232
  key: "k",
240
- author: "Ada Lovelace",
241
- title: "T",
233
+ author: "Lovelace, Ada and van der Berg, Jan and others",
234
+ title: "The BERT Model",
242
235
  booktitle: "",
243
236
  journal: "J",
244
237
  },
245
- ],
246
- );
247
- });
248
-
249
- test("parseBib: braces nested inside a value, and a bare last value with no comma after it", () => {
250
- assert.deepEqual(
251
- parseBib("@article{k, title = {The {BERT} Model}, journal = J}"),
252
- [
253
238
  {
254
- type: "article",
255
- key: "k",
256
- author: "",
257
- title: "The {BERT} Model",
239
+ type: "misc",
240
+ key: "untitled",
241
+ author: "Hopper, Grace",
242
+ title: "",
258
243
  booktitle: "",
259
- journal: "J",
244
+ journal: "",
260
245
  },
261
246
  ],
262
247
  );
@@ -1,7 +1,7 @@
1
1
  /**
2
- * bib-authors.mjs as a process: which file it picks from a paper directory, the three
3
- * bibliography sources it reads (.bib, a .tex's filecontents or its sibling refs.bib, a markdown
4
- * reference list), and the report and exit code for each outcome. DBLP is a preload that answers
2
+ * bib-authors.mjs as a process: the bibliography it reads (a .bib named, or what the last build's
3
+ * bibtex opened for a paper directory or a .tex — `_build/sources.json`, through `bibliographyAt`), the
4
+ * targets it refuses, and the report and exit code for each outcome. DBLP is a preload that answers
5
5
  * by title from a table (the documented `result.hits.hit[].info` shape) — no network.
6
6
  */
7
7
  import assert from "node:assert/strict";
@@ -15,6 +15,13 @@ import {
15
15
  useTempDir,
16
16
  writeTree,
17
17
  } from "../../../test/support.ts";
18
+ import {
19
+ plantedOnDisk,
20
+ recordOnDisk,
21
+ type Shape,
22
+ } from "../../../test/recorded-fixture.ts";
23
+ import { absolutePath } from "#src/domain/paths";
24
+ import { bibliographyUnreadWhy } from "#src/paper-sources";
18
25
 
19
26
  const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "bib-authors.mjs");
20
27
  const root = useTempDir("bib-authors-cli-");
@@ -32,6 +39,10 @@ const bib = (
32
39
  ) =>
33
40
  `@inproceedings{${key}, author = {${author}}, title = {${title}}, ${venue}}\n`;
34
41
 
42
+ /** A .tex that declares the database `name`, with `preamble` before its document. */
43
+ const declaring = (name: string, preamble = "") =>
44
+ `\\documentclass{article}\n${preamble}\\begin{document}x\\bibliography{${name}}\\end{document}\n`;
45
+
35
46
  writeTree(root, {
36
47
  // The retry backoff (1.5 s, then 3 s) and the 900 ms courtesy pause are real sleeps in the
37
48
  // script. Under CPU load they made "DBLP down" take 11.7 s once and time out. The preload
@@ -46,22 +57,53 @@ writeTree(root, {
46
57
  " const hit = (answers[q] ?? []).map((authors) => ({ info: { title: q, venue: 'X', year: '2024',\n" +
47
58
  " type: 'Conference and Workshop Papers', authors: { author: authors.map((text) => ({ text })) } } }));\n" +
48
59
  " return new Response(JSON.stringify({ result: { hits: { hit } } }), { status: 200 });\n};\n",
60
+ "findings/paper.tex": declaring("paper"),
49
61
  "findings/paper.bib":
50
62
  bib("dropped", "Ada Lovelace and Grace Hopper", "Dropped Author") +
51
63
  bib("order", "Alan Turing and Ada Lovelace", "Swapped Order"),
52
64
  "findings/other.bib": bib("never", "X", "Never Read"),
53
65
  "twotex/b.tex": "\\documentclass{article}\n",
54
- "twotex/a.tex":
55
- "\\begin{filecontents*}{refs.bib}\n" +
56
- bib("pre", "Ada Lovelace", "A Preprint", "journal = {arXiv preprint}") +
57
- "\\end{filecontents*}\n",
58
- "sibling/paper.tex": "\\documentclass{article}\n",
66
+ "twotex/paper.tex": declaring(
67
+ "refs",
68
+ "\\begin{filecontents*}[overwrite]{refs.bib}\n" +
69
+ bib("pre", "Ada Lovelace", "A Preprint", "journal = {arXiv preprint}") +
70
+ "\\end{filecontents*}\n",
71
+ ),
72
+ // What TeX wrote from that block: the file bibtex opened.
73
+ "twotex/refs.bib": bib(
74
+ "pre",
75
+ "Ada Lovelace",
76
+ "A Preprint",
77
+ "journal = {arXiv preprint}",
78
+ ),
79
+ "sibling/paper.tex": declaring("refs"),
59
80
  "sibling/refs.bib": bib("good", "Ada Lovelace", "All Good"),
60
- "nobib/paper.tex": "\\documentclass{article}\n",
61
- "md/paper.md":
62
- "# P\n\n## References\n\n1. A. Lovelace. *All Good.* Proc. X, 2024.\n2. some-npm-package, https://npm.example\n",
81
+ "etal/paper.tex": declaring("refs"),
82
+ "etal/notes.txt": "not a bibliography\n",
83
+ "etal/refs.bib":
84
+ bib("good", "Ada Lovelace", "All Good") +
85
+ bib("etal", "Ada Lovelace and others", "Truncated"),
86
+ "down/paper.tex": declaring("refs"),
63
87
  "down/refs.bib": bib("down", "Ada Lovelace", "Service Down"),
64
- "empty/.keep": "",
88
+ });
89
+
90
+ /** The record of a paper whose bibliography is `db`, a file the author keeps. */
91
+ const keeps = (db: string): Shape => ({
92
+ inputs: [
93
+ ["paper.tex", "body"],
94
+ [db, "preamble"],
95
+ ],
96
+ databases: [db],
97
+ });
98
+ recordOnDisk(join(root, "findings"), keeps("paper.bib"));
99
+ recordOnDisk(join(root, "sibling"), keeps("refs.bib"));
100
+ recordOnDisk(join(root, "etal"), keeps("refs.bib"));
101
+ recordOnDisk(join(root, "down"), keeps("refs.bib"));
102
+ // A block TeX wrote into refs.bib: the file is no input, the block is.
103
+ recordOnDisk(join(root, "twotex"), {
104
+ inputs: [["paper.tex", "body"]],
105
+ written: ["refs.bib"],
106
+ databases: ["refs.bib"],
65
107
  });
66
108
 
67
109
  const run = (...args: string[]) => {
@@ -75,14 +117,16 @@ const run = (...args: string[]) => {
75
117
  };
76
118
  };
77
119
 
78
- test("refusals: no argument, no such path, a directory with nothing to read, a .tex with no bibliography", () => {
120
+ test("refusals: no argument; a path with no bibliography to read, in bibliographyAt's words", () => {
121
+ // The path → bibliography vocabulary is tested once, beside bibliographyAt (src/paper-sources.ts).
122
+ // Here: that the command speaks it, and exits 2.
123
+ const notes = join(root, "etal", "notes.txt");
79
124
  assert.deepEqual(
80
- [
81
- run(),
82
- run(join(root, "nope")),
83
- run(join(root, "empty")),
84
- run(join(root, "nobib", "paper.tex")),
85
- ].map(({ status, stdout, stderr }) => ({ status, stdout, stderr })),
125
+ [run(), run(notes)].map(({ status, stdout, stderr }) => ({
126
+ status,
127
+ stdout,
128
+ stderr,
129
+ })),
86
130
  [
87
131
  {
88
132
  status: 2,
@@ -93,24 +137,13 @@ test("refusals: no argument, no such path, a directory with nothing to read, a .
93
137
  {
94
138
  status: 2,
95
139
  stdout: "",
96
- stderr: "bib-authors: no such path: <root>/nope\n",
97
- },
98
- {
99
- status: 2,
100
- stdout: "",
101
- stderr: "bib-authors: no .bib, .tex or paper.md in <root>/empty\n",
102
- },
103
- {
104
- status: 2,
105
- stdout: "",
106
- stderr:
107
- "bib-authors: no bibliography found in <root>/nobib/paper.tex (no filecontents block, no refs.bib beside it)\n",
140
+ stderr: `bib-authors: ${bibliographyUnreadWhy({ kind: "not-bib-or-tex", path: absolutePath(notes) }).replaceAll(root, "<root>")}\n`,
108
141
  },
109
142
  ],
110
143
  );
111
144
  });
112
145
 
113
- test("a paper directory: the canonical paper.bib is read, and each difference is printed — FAIL, exit 1", () => {
146
+ test("a paper directory: the paper.bib its \\bibliography{paper} declares is read, and each difference is printed — FAIL, exit 1", () => {
114
147
  assert.deepEqual(run(join(root, "findings")), {
115
148
  status: 1,
116
149
  stderr: "",
@@ -133,16 +166,16 @@ test("a paper directory: the canonical paper.bib is read, and each difference is
133
166
  });
134
167
  });
135
168
 
136
- test("two .tex files and no paper.tex: the first by name, said out loud; its filecontents is read", () => {
137
- const r = run(join(root, "twotex"), "--json");
169
+ test("a .tex named is its folder's paper: the database bibtex opened is read from the block that wrote it", () => {
170
+ const r = run(join(root, "twotex", "paper.tex"), "--json");
138
171
  assert.deepEqual(
139
172
  { status: r.status, stderr: r.stderr, report: parseJson(r.stdout) },
140
173
  {
141
174
  status: 0,
142
- stderr:
143
- "⚠️ <root>/twotex holds several .tex files and no canonical paper.tex: a.tex, b.tex — took a.tex. If that is the wrong one, name the file explicitly.\n",
175
+ stderr: "",
144
176
  report: {
145
- file: "<root>/twotex/a.tex",
177
+ file: "<root>/twotex/paper.tex",
178
+ files: ["<root>/twotex/paper.tex"],
146
179
  entries: 1,
147
180
  findings: [],
148
181
  skipped: [],
@@ -152,7 +185,18 @@ test("two .tex files and no paper.tex: the first by name, said out loud; its fil
152
185
  );
153
186
  });
154
187
 
155
- test("a .tex with no filecontents reads the refs.bib beside it — PASS, exit 0", () => {
188
+ test("a .bib named is read as itself", () => {
189
+ const r = run(join(root, "findings", "paper.bib"), "--json");
190
+ const report: unknown = parseJson(r.stdout);
191
+ assert.deepEqual(
192
+ report !== null && typeof report === "object" && "files" in report
193
+ ? report.files
194
+ : report,
195
+ ["<root>/findings/paper.bib"],
196
+ );
197
+ });
198
+
199
+ test("a .tex that declares refs and has no block reads refs.bib — PASS, exit 0", () => {
156
200
  const r = run(join(root, "sibling", "paper.tex"));
157
201
  assert.deepEqual(r, {
158
202
  status: 0,
@@ -162,15 +206,15 @@ test("a .tex with no filecontents reads the refs.bib beside it — PASS, exit 0"
162
206
  });
163
207
  });
164
208
 
165
- test("a markdown reference list: a software entry is not applicable, not dropped", () => {
166
- const r = run(join(root, "md"));
209
+ test("an `and others` entry is printed as not applicable, not dropped", () => {
210
+ const r = run(join(root, "etal"));
167
211
  assert.deepEqual(r, {
168
212
  status: 0,
169
213
  stderr: "",
170
214
  stdout:
171
- "== bib-authors: <root>/md/paper.md ==\n" +
172
- " · n/a ref2 — no author/title — a reference to software or a dataset, DBLP does not apply\n\n" +
173
- "-- 1 entries · 0 difference(s) · 1 not applicable · 0 NOT CHECKED\nPASS: no author-list disagreement.\n",
215
+ "== bib-authors: <root>/etal/refs.bib ==\n" +
216
+ " · n/a etal — author list ends in `and others` — completeness not checkable\n\n" +
217
+ "-- 2 entries · 0 difference(s) · 1 not applicable · 0 NOT CHECKED\nPASS: no author-list disagreement.\n",
174
218
  });
175
219
  });
176
220
 
@@ -186,3 +230,54 @@ test("DBLP down: NOT CHECKED, PARTIAL, exit 2 — never a pass", () => {
186
230
  "PARTIAL: no disagreement among the entries reached, but 1 could not be checked — this is NOT a pass.\n",
187
231
  });
188
232
  });
233
+
234
+ // The planted papers of fixtures/paper-sources, laid out as a build leaves them: the file read is the
235
+ // one bibtex opened (tex-truth.json), or the block that wrote it.
236
+ test.each([
237
+ ["v1-stale", "refs.bib"],
238
+ ["v2-overwrite", "paper.tex"],
239
+ ["v3-declared", "paper.bib"],
240
+ ["v4-commented", "refs.bib"],
241
+ ])("%s: reads %s", (paper, file) => {
242
+ const dir = join(root, "planted", paper);
243
+ plantedOnDisk(dir, paper);
244
+ const r = runNode(SCRIPT, [dir, "--json"], {
245
+ nodeArgs: ["--import", join(root, "dblp.mjs")],
246
+ });
247
+ const report: unknown = parseJson(r.stdout);
248
+ assert.deepEqual(
249
+ report !== null && typeof report === "object" && "files" in report
250
+ ? report.files
251
+ : report,
252
+ [join(dir, file)],
253
+ );
254
+ });
255
+
256
+ test("🔴 a paper that was not built is refused — run `npx paperlint build` first — and so is one changed since", () => {
257
+ const unbuilt = join(root, "unbuilt");
258
+ writeTree(unbuilt, {
259
+ "paper.tex": declaring("refs"),
260
+ "refs.bib": bib("good", "Ada Lovelace", "All Good"),
261
+ });
262
+ assert.deepEqual(
263
+ [run(unbuilt), run(join(unbuilt, "paper.tex"))].map(
264
+ ({ status, stdout, stderr }) => ({ status, stdout, stderr }),
265
+ ),
266
+ Array.from({ length: 2 }, () => ({
267
+ status: 2,
268
+ stdout: "",
269
+ stderr:
270
+ "bib-authors: <root>/unbuilt has not been built — run `npx paperlint build` first, which records the databases bibtex reads\n",
271
+ })),
272
+ );
273
+ recordOnDisk(unbuilt, keeps("refs.bib"));
274
+ writeTree(unbuilt, {
275
+ "refs.bib": bib("good", "Ada Lovelace", "All Good Edited"),
276
+ });
277
+ assert.deepEqual(run(unbuilt), {
278
+ status: 2,
279
+ stdout: "",
280
+ stderr:
281
+ "bib-authors: <root>/unbuilt changed since the last build (refs.bib edited) — run `npx paperlint build` first\n",
282
+ });
283
+ });
@@ -1,7 +1,7 @@
1
1
  // Types for bib-authors.mjs, for the exports `src/` imports — the same arrangement as
2
2
  // `verify-cites.d.mts` beside it. Only what `src/` imports is declared.
3
3
 
4
- /** One entry as `parseBib` returns it; a field the entry lacks is "". */
4
+ /** One entry as `authorEntries` returns it; a field the entry lacks is "". */
5
5
  export interface BibEntry {
6
6
  readonly type: string;
7
7
  readonly key: string;
@@ -11,7 +11,27 @@ export interface BibEntry {
11
11
  readonly journal: string;
12
12
  }
13
13
 
14
- export declare function parseBib(text: string): BibEntry[];
14
+ /** A name of a name list, in its parts (`BibName`). */
15
+ export interface ReadName {
16
+ readonly lastName?: string;
17
+ readonly firstName?: string;
18
+ readonly prefix?: string;
19
+ readonly suffix?: string;
20
+ readonly name?: string;
21
+ }
22
+
23
+ /** An entry as the bibtex reader reads it (src/domain/paper-sources.ts), as far as this check uses it. */
24
+ export interface ReadEntry {
25
+ readonly type: string;
26
+ readonly key: string;
27
+ readonly fields: Readonly<Record<string, string>>;
28
+ readonly names: Readonly<Record<string, readonly ReadName[]>>;
29
+ }
30
+
31
+ /** The entries this check reads, from the bibtex reader's. */
32
+ export declare function authorEntries(
33
+ entries: readonly ReadEntry[],
34
+ ): BibEntry[];
15
35
 
16
36
  /** True when the author list ends in `and others` — there is nothing complete to compare. */
17
37
  export declare const truncated: (authorField: string) => boolean;