paperlint 3.2.0 → 4.1.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 (442) hide show
  1. package/.github/workflows/ci.yml +16 -23
  2. package/CLAUDE.md +33 -13
  3. package/CONTRIBUTING.md +23 -7
  4. package/README.md +11 -8
  5. package/dist/adapters/banal/failure.d.ts +1 -7
  6. package/dist/adapters/banal/failure.d.ts.map +1 -1
  7. package/dist/adapters/banal/failure.js +5 -7
  8. package/dist/adapters/banal/failure.js.map +1 -1
  9. package/dist/adapters/banal/index.d.ts.map +1 -1
  10. package/dist/adapters/banal/index.js +8 -11
  11. package/dist/adapters/banal/index.js.map +1 -1
  12. package/dist/adapters/banal/install.d.ts.map +1 -1
  13. package/dist/adapters/banal/install.js +4 -3
  14. package/dist/adapters/banal/install.js.map +1 -1
  15. package/dist/adapters/banal/invocation.d.ts +10 -5
  16. package/dist/adapters/banal/invocation.d.ts.map +1 -1
  17. package/dist/adapters/banal/invocation.js +13 -1
  18. package/dist/adapters/banal/invocation.js.map +1 -1
  19. package/dist/adapters/banal/locate.d.ts +12 -9
  20. package/dist/adapters/banal/locate.d.ts.map +1 -1
  21. package/dist/adapters/banal/locate.js +19 -12
  22. package/dist/adapters/banal/locate.js.map +1 -1
  23. package/dist/adapters/banal/run.d.ts +10 -1
  24. package/dist/adapters/banal/run.d.ts.map +1 -1
  25. package/dist/adapters/banal/run.js +8 -4
  26. package/dist/adapters/banal/run.js.map +1 -1
  27. package/dist/adapters/banal/settings.d.ts +1 -1
  28. package/dist/adapters/banal/settings.d.ts.map +1 -1
  29. package/dist/adapters/banal/settings.js +2 -1
  30. package/dist/adapters/banal/settings.js.map +1 -1
  31. package/dist/adapters/latex/document-class.d.ts +22 -0
  32. package/dist/adapters/latex/document-class.d.ts.map +1 -0
  33. package/dist/adapters/latex/document-class.js +102 -0
  34. package/dist/adapters/latex/document-class.js.map +1 -0
  35. package/dist/adapters/latex/hidden.d.ts +16 -0
  36. package/dist/adapters/latex/hidden.d.ts.map +1 -0
  37. package/dist/adapters/latex/hidden.js +49 -0
  38. package/dist/adapters/latex/hidden.js.map +1 -0
  39. package/dist/adapters/latex/index.d.ts +29 -0
  40. package/dist/adapters/latex/index.d.ts.map +1 -0
  41. package/dist/adapters/latex/index.js +18 -0
  42. package/dist/adapters/latex/index.js.map +1 -0
  43. package/dist/adapters/latex/nodes.d.ts +54 -0
  44. package/dist/adapters/latex/nodes.d.ts.map +1 -0
  45. package/dist/adapters/latex/nodes.js +93 -0
  46. package/dist/adapters/latex/nodes.js.map +1 -0
  47. package/dist/adapters/latex/outline.d.ts +10 -0
  48. package/dist/adapters/latex/outline.d.ts.map +1 -0
  49. package/dist/adapters/latex/outline.js +39 -0
  50. package/dist/adapters/latex/outline.js.map +1 -0
  51. package/dist/adapters/latex/parse.d.ts +42 -0
  52. package/dist/adapters/latex/parse.d.ts.map +1 -0
  53. package/dist/adapters/latex/parse.js +94 -0
  54. package/dist/adapters/latex/parse.js.map +1 -0
  55. package/dist/adapters/latex/rendered.d.ts +10 -0
  56. package/dist/adapters/latex/rendered.d.ts.map +1 -0
  57. package/dist/adapters/latex/rendered.js +39 -0
  58. package/dist/adapters/latex/rendered.js.map +1 -0
  59. package/dist/adapters/memory/index.d.ts.map +1 -1
  60. package/dist/adapters/memory/index.js +8 -2
  61. package/dist/adapters/memory/index.js.map +1 -1
  62. package/dist/adapters/node/process.io.d.ts +6 -3
  63. package/dist/adapters/node/process.io.d.ts.map +1 -1
  64. package/dist/adapters/node/process.io.js.map +1 -1
  65. package/dist/adapters/node/workspace.io.d.ts.map +1 -1
  66. package/dist/adapters/node/workspace.io.js +4 -2
  67. package/dist/adapters/node/workspace.io.js.map +1 -1
  68. package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
  69. package/dist/adapters/pdfjs/fill.js +12 -12
  70. package/dist/adapters/pdfjs/fill.js.map +1 -1
  71. package/dist/adapters/references/index.d.ts.map +1 -1
  72. package/dist/adapters/references/index.js +4 -2
  73. package/dist/adapters/references/index.js.map +1 -1
  74. package/dist/build-engine.d.ts +1 -1
  75. package/dist/build-engine.d.ts.map +1 -1
  76. package/dist/build-engine.js +8 -1
  77. package/dist/build-engine.js.map +1 -1
  78. package/dist/build.d.ts +2 -3
  79. package/dist/build.d.ts.map +1 -1
  80. package/dist/build.js +9 -10
  81. package/dist/build.js.map +1 -1
  82. package/dist/caller-path.d.ts +3 -0
  83. package/dist/caller-path.d.ts.map +1 -0
  84. package/dist/caller-path.js +13 -0
  85. package/dist/caller-path.js.map +1 -0
  86. package/dist/cli.d.ts +10 -17
  87. package/dist/cli.d.ts.map +1 -1
  88. package/dist/cli.js +91 -17
  89. package/dist/cli.js.map +1 -1
  90. package/dist/doctor.d.ts +12 -3
  91. package/dist/doctor.d.ts.map +1 -1
  92. package/dist/doctor.js +2 -1
  93. package/dist/doctor.js.map +1 -1
  94. package/dist/domain/paths.d.ts +8 -2
  95. package/dist/domain/paths.d.ts.map +1 -1
  96. package/dist/domain/paths.js +12 -1
  97. package/dist/domain/paths.js.map +1 -1
  98. package/dist/domain/ready.d.ts +24 -0
  99. package/dist/domain/ready.d.ts.map +1 -0
  100. package/dist/domain/ready.js +11 -0
  101. package/dist/domain/ready.js.map +1 -0
  102. package/dist/domain/record.d.ts +8 -0
  103. package/dist/domain/record.d.ts.map +1 -0
  104. package/dist/domain/record.js +8 -0
  105. package/dist/domain/record.js.map +1 -0
  106. package/dist/domain/sha256.d.ts.map +1 -1
  107. package/dist/domain/sha256.js +4 -2
  108. package/dist/domain/sha256.js.map +1 -1
  109. package/dist/domain/tex-document.d.ts +84 -0
  110. package/dist/domain/tex-document.d.ts.map +1 -0
  111. package/dist/domain/tex-document.js +43 -0
  112. package/dist/domain/tex-document.js.map +1 -0
  113. package/dist/engine.d.ts +23 -22
  114. package/dist/engine.d.ts.map +1 -1
  115. package/dist/engine.js.map +1 -1
  116. package/dist/eslint-rules/bib-reachable-entry.d.ts +1 -1
  117. package/dist/eslint-rules/bib-reachable-entry.js.map +1 -1
  118. package/dist/eslint-rules/install-path-literals.d.ts +2 -2
  119. package/dist/eslint-rules/install-path-literals.js.map +1 -1
  120. package/dist/eslint-rules/paper-research-question.d.ts +1 -1
  121. package/dist/eslint-rules/paper-research-question.js.map +1 -1
  122. package/dist/eslint-rules/paper-stages.d.ts +2 -2
  123. package/dist/eslint-rules/paper-stages.js.map +1 -1
  124. package/dist/eslint-rules/paper-typography.d.ts +6 -6
  125. package/dist/eslint-rules/paper-typography.js.map +1 -1
  126. package/dist/eslint-rules/pdf-last-page-balance.d.ts +1 -1
  127. package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -1
  128. package/dist/eslint-rules/review-frontmatter.d.ts +2 -2
  129. package/dist/eslint-rules/review-frontmatter.js.map +1 -1
  130. package/dist/eslint-rules/sibling-frontmatter.d.ts +1 -1
  131. package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -1
  132. package/dist/eslint-rules/temp-root-realpath.d.ts +1 -1
  133. package/dist/eslint-rules/temp-root-realpath.js.map +1 -1
  134. package/dist/eslint-rules/tex-build.d.ts +2 -2
  135. package/dist/eslint-rules/tex-build.js.map +1 -1
  136. package/dist/facts-file.d.ts.map +1 -1
  137. package/dist/facts-file.js +31 -28
  138. package/dist/facts-file.js.map +1 -1
  139. package/dist/hooks-settings.d.ts.map +1 -1
  140. package/dist/hooks-settings.js +17 -2
  141. package/dist/hooks-settings.js.map +1 -1
  142. package/dist/init.d.ts +3 -4
  143. package/dist/init.d.ts.map +1 -1
  144. package/dist/init.js +18 -8
  145. package/dist/init.js.map +1 -1
  146. package/dist/lib/paper-config.d.ts.map +1 -1
  147. package/dist/lib/paper-config.js +1 -5
  148. package/dist/lib/paper-config.js.map +1 -1
  149. package/dist/new-paper.d.ts +5 -0
  150. package/dist/new-paper.d.ts.map +1 -1
  151. package/dist/new-paper.js +8 -1
  152. package/dist/new-paper.js.map +1 -1
  153. package/dist/package-dirs.d.ts +5 -0
  154. package/dist/package-dirs.d.ts.map +1 -0
  155. package/dist/package-dirs.js +14 -0
  156. package/dist/package-dirs.js.map +1 -0
  157. package/dist/paper-files.d.ts +4 -4
  158. package/dist/paper-files.d.ts.map +1 -1
  159. package/dist/paper-files.js +6 -6
  160. package/dist/paper-files.js.map +1 -1
  161. package/dist/paper-settings.js +3 -3
  162. package/dist/paper-settings.js.map +1 -1
  163. package/dist/pdf-facts.d.ts +51 -1
  164. package/dist/pdf-facts.d.ts.map +1 -1
  165. package/dist/pdf-facts.js +23 -15
  166. package/dist/pdf-facts.js.map +1 -1
  167. package/dist/pdf-geometry.d.ts.map +1 -1
  168. package/dist/pdf-geometry.js +2 -3
  169. package/dist/pdf-geometry.js.map +1 -1
  170. package/dist/ports/latex.d.ts +24 -0
  171. package/dist/ports/latex.d.ts.map +1 -0
  172. package/dist/ports/latex.js +2 -0
  173. package/dist/ports/latex.js.map +1 -0
  174. package/dist/ports/tool-installer.d.ts +2 -14
  175. package/dist/ports/tool-installer.d.ts.map +1 -1
  176. package/dist/presets.d.ts +15 -2
  177. package/dist/presets.d.ts.map +1 -1
  178. package/dist/presets.js +35 -10
  179. package/dist/presets.js.map +1 -1
  180. package/dist/references.d.ts.map +1 -1
  181. package/dist/references.js +4 -4
  182. package/dist/references.js.map +1 -1
  183. package/dist/rules-config.d.ts +26 -1
  184. package/dist/rules-config.d.ts.map +1 -1
  185. package/dist/rules-config.js +9 -9
  186. package/dist/rules-config.js.map +1 -1
  187. package/dist/tex-requirements.d.ts +15 -0
  188. package/dist/tex-requirements.d.ts.map +1 -1
  189. package/dist/tex-requirements.js +28 -10
  190. package/dist/tex-requirements.js.map +1 -1
  191. package/dist/tex-venue-rules.d.ts +83 -0
  192. package/dist/tex-venue-rules.d.ts.map +1 -0
  193. package/dist/tex-venue-rules.js +254 -0
  194. package/dist/tex-venue-rules.js.map +1 -0
  195. package/dist/types.d.ts +5 -3
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/venue-rules.d.ts +1 -1
  198. package/dist/venue-rules.d.ts.map +1 -1
  199. package/dist/venue-rules.js +20 -7
  200. package/dist/venue-rules.js.map +1 -1
  201. package/docs/configuration.md +5 -4
  202. package/docs/e2e.md +69 -40
  203. package/docs/install.md +1 -1
  204. package/docs/prior-art/README.md +1 -1
  205. package/docs/prior-art/package-location.md +5 -5
  206. package/docs/prior-art/repro/README.md +1 -1
  207. package/docs/prior-art/test-tooling.md +21 -10
  208. package/docs/rules/tex/required-section.md +73 -0
  209. package/docs/rules/tex/template.md +62 -0
  210. package/docs/rules/tex/venue-leftover.md +71 -0
  211. package/docs/rules.md +70 -48
  212. package/docs/testing.md +17 -4
  213. package/docs/texlive-install-decision.md +1 -1
  214. package/docs/toolchain.md +2 -2
  215. package/eslint-rules/bib-reachable-entry.ts +1 -1
  216. package/eslint-rules/install-path-literals.ts +2 -2
  217. package/eslint-rules/paper-research-question.ts +1 -1
  218. package/eslint-rules/paper-stages.ts +2 -2
  219. package/eslint-rules/paper-typography.ts +2 -2
  220. package/eslint-rules/pdf-last-page-balance.ts +1 -1
  221. package/eslint-rules/review-frontmatter.ts +1 -1
  222. package/eslint-rules/temp-root-realpath.ts +1 -1
  223. package/eslint-rules/tex-build.ts +2 -2
  224. package/eslint.config.mjs +162 -15
  225. package/fixtures/build-e2e/acmart/expect.json +26 -0
  226. package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
  227. package/fixtures/build-e2e/aidc/expect.json +14 -0
  228. package/fixtures/build-e2e/aidc/paper.tex +46 -0
  229. package/fixtures/build-e2e/aidc/paperlint.json +1 -0
  230. package/fixtures/build-e2e/broken/expect.json +11 -0
  231. package/fixtures/build-e2e/cite/expect.json +27 -0
  232. package/fixtures/build-e2e/empty/expect.json +12 -0
  233. package/fixtures/build-e2e/fallback/expect.json +53 -0
  234. package/fixtures/build-e2e/guards/expect.json +6 -0
  235. package/fixtures/build-e2e/no-source/expect.json +10 -0
  236. package/fixtures/build-e2e/unbalanced/expect.json +60 -0
  237. package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
  238. package/fixtures/install-path-literals/clean.md +2 -2
  239. package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
  240. package/fixtures/install-path-literals/defect.md +2 -2
  241. package/fixtures/pdf-facts/README.md +1 -1
  242. package/fixtures/real-markdown-paper/baseline.ts +1 -1
  243. package/fixtures/render-paper/build-clean.sh +3 -5
  244. package/hooks/paper-status-gates.sh +9 -19
  245. package/lib/paper-config.harness.mjs +1 -1
  246. package/lib/paper-config.ts +1 -5
  247. package/lib/skill-checks.mjs +24 -381
  248. package/lib/skill-checks.test.mjs +63 -115
  249. package/lib/skill-corpus.mjs +10 -251
  250. package/lib/skill-corpus.test.mjs +2 -13
  251. package/lib/skill-trigger-cases.mjs +1 -1
  252. package/package.json +13 -2
  253. package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
  254. package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
  255. package/presets/aidc.jsonc +73 -0
  256. package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
  257. package/presets/ieee-conference.jsonc +66 -0
  258. package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
  259. package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
  260. package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
  261. package/scripts/check.harness.ts +64 -10
  262. package/scripts/check.test.ts +60 -13
  263. package/scripts/check.ts +83 -30
  264. package/scripts/coverage-config.test.ts +0 -1
  265. package/scripts/harness-api.frozen.json +0 -1
  266. package/scripts/test.test.ts +1 -1
  267. package/scripts/test.ts +1 -1
  268. package/skills/analyze-sibling-paper/SKILL.md +1 -39
  269. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
  270. package/skills/argument-arc/SKILL.md +2 -36
  271. package/skills/argument-arc/SKILL.md.spec.ts +1 -43
  272. package/skills/build-benchmark/SKILL.md +1 -41
  273. package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
  274. package/skills/camera-ready/SKILL.md +1 -34
  275. package/skills/camera-ready/SKILL.md.spec.ts +0 -33
  276. package/skills/cold-read-diff/SKILL.md +1 -35
  277. package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
  278. package/skills/draft-paper/SKILL.md +2 -41
  279. package/skills/draft-paper/SKILL.md.spec.ts +1 -50
  280. package/skills/extend-paper/SKILL.md +2 -33
  281. package/skills/extend-paper/SKILL.md.spec.ts +1 -42
  282. package/skills/find-venue/SKILL.md +2 -38
  283. package/skills/find-venue/SKILL.md.spec.ts +1 -47
  284. package/skills/grade-paper-writing/SKILL.md +3 -38
  285. package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
  286. package/skills/grade-paper-writing/prose-lint.mjs +2 -3
  287. package/skills/grade-paper-writing/prose-lint.test.ts +1 -1
  288. package/skills/harden-paper/SKILL.md +1 -39
  289. package/skills/harden-paper/SKILL.md.spec.ts +0 -38
  290. package/skills/map-prior-work/SKILL.md +1 -35
  291. package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
  292. package/skills/osf-artifact-upload/osf-artifact-upload.harness.ts +3 -4
  293. package/skills/paper-adversarial-review/SKILL.md +2 -35
  294. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
  295. package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
  296. package/skills/paper-pipeline/SKILL.md +3 -29
  297. package/skills/paper-pipeline/SKILL.md.spec.ts +2 -28
  298. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1 -1
  299. package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
  300. package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
  301. package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
  302. package/skills/paper-pipeline/scripts/README.md +30 -225
  303. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
  304. package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
  305. package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
  306. package/skills/paper-pipeline/scripts/consumer.mjs +24 -119
  307. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
  308. package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
  309. package/skills/paper-status/SKILL.md +2 -42
  310. package/skills/paper-status/SKILL.md.spec.ts +1 -41
  311. package/skills/pc-panel-review/SKILL.md +2 -46
  312. package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
  313. package/skills/plan-paper-timeline/SKILL.md +2 -36
  314. package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
  315. package/skills/render-paper/SKILL.md +2 -38
  316. package/skills/render-paper/SKILL.md.spec.ts +1 -37
  317. package/skills/render-paper/check-render.sh +16 -63
  318. package/skills/render-paper/render-paper.harness.mjs +17 -59
  319. package/skills/research-ideate/SKILL.md +2 -39
  320. package/skills/research-ideate/SKILL.md.spec.ts +1 -47
  321. package/skills/study-accepted-papers/SKILL.md +1 -34
  322. package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
  323. package/skills/submit-paper/SKILL.md +1 -33
  324. package/skills/submit-paper/SKILL.md.spec.ts +0 -32
  325. package/skills/sweep-design-space/SKILL.md +1 -38
  326. package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
  327. package/skills/tighten-paper/SKILL.md +2 -39
  328. package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
  329. package/skills/tighten-paper/structure.mjs +4 -11
  330. package/skills/verify-citations/SKILL.md +2 -38
  331. package/skills/verify-citations/SKILL.md.spec.ts +1 -37
  332. package/src/adapters/banal/failure.ts +9 -10
  333. package/src/adapters/banal/index.test.ts +3 -3
  334. package/src/adapters/banal/index.ts +8 -10
  335. package/src/adapters/banal/install.ts +9 -4
  336. package/src/adapters/banal/invocation.test.ts +20 -8
  337. package/src/adapters/banal/invocation.ts +24 -9
  338. package/src/adapters/banal/locate.test.ts +2 -2
  339. package/src/adapters/banal/locate.ts +27 -21
  340. package/src/adapters/banal/output.test.ts +1 -1
  341. package/src/adapters/banal/run.test.ts +3 -3
  342. package/src/adapters/banal/run.ts +16 -10
  343. package/src/adapters/banal/settings.ts +2 -2
  344. package/src/adapters/banal/xml.test.ts +1 -1
  345. package/src/adapters/curl/download.test.ts +4 -4
  346. package/src/adapters/latex/document-class.ts +147 -0
  347. package/src/adapters/latex/hidden.ts +70 -0
  348. package/src/adapters/latex/index.test.ts +361 -0
  349. package/src/adapters/latex/index.ts +55 -0
  350. package/src/adapters/latex/nodes.ts +141 -0
  351. package/src/adapters/latex/outline.ts +64 -0
  352. package/src/adapters/latex/parse.ts +136 -0
  353. package/src/adapters/latex/rendered.ts +59 -0
  354. package/src/adapters/memory/index.ts +4 -3
  355. package/src/adapters/node/files.test.ts +5 -3
  356. package/src/adapters/node/process.io.ts +9 -2
  357. package/src/adapters/node/process.test.ts +4 -4
  358. package/src/adapters/node/workspace.io.ts +9 -4
  359. package/src/adapters/pdfjs/fill.test.ts +1 -1
  360. package/src/adapters/pdfjs/fill.ts +18 -10
  361. package/src/adapters/references/index.ts +7 -2
  362. package/src/build-engine.test.ts +11 -6
  363. package/src/build-engine.ts +10 -5
  364. package/src/build.harness.mjs +5 -7
  365. package/src/build.test.ts +10 -11
  366. package/src/build.ts +12 -13
  367. package/src/caller-path.ts +13 -0
  368. package/src/cli.harness.mjs +1 -3
  369. package/src/cli.test.ts +21 -16
  370. package/src/cli.ts +115 -33
  371. package/src/doctor.test.ts +1 -1
  372. package/src/doctor.ts +14 -5
  373. package/src/domain/paths.test.ts +15 -0
  374. package/src/domain/paths.ts +20 -1
  375. package/src/domain/ready.ts +31 -0
  376. package/src/domain/record.test.ts +20 -0
  377. package/src/domain/record.ts +12 -0
  378. package/src/domain/sha256.ts +5 -3
  379. package/src/domain/tex-document.test.ts +80 -0
  380. package/src/domain/tex-document.ts +110 -0
  381. package/src/engine.harness.mjs +1 -1
  382. package/src/engine.ts +30 -2
  383. package/src/facts-file.test.ts +4 -7
  384. package/src/facts-file.ts +45 -30
  385. package/src/hooks-settings.harness.ts +17 -0
  386. package/src/hooks-settings.ts +28 -4
  387. package/src/init.test.ts +25 -5
  388. package/src/init.ts +26 -12
  389. package/src/latex-loop.harness.ts +1 -1
  390. package/src/new-paper.harness.mjs +7 -23
  391. package/src/new-paper.test.ts +28 -3
  392. package/src/new-paper.ts +15 -3
  393. package/src/package-dirs.ts +17 -0
  394. package/src/paper-files.test.ts +22 -19
  395. package/src/paper-files.ts +16 -27
  396. package/src/paper-settings-commands.test.ts +6 -1
  397. package/src/paper-settings.test.ts +2 -2
  398. package/src/paper-settings.ts +5 -5
  399. package/src/pdf-facts.test.ts +16 -5
  400. package/src/pdf-facts.ts +79 -22
  401. package/src/pdf-geometry.ts +5 -3
  402. package/src/ports/latex.ts +29 -0
  403. package/src/ports/tool-installer.ts +2 -17
  404. package/src/presets.test.ts +65 -4
  405. package/src/presets.ts +65 -28
  406. package/src/references.test.ts +25 -19
  407. package/src/references.ts +4 -5
  408. package/src/rule-cases.test.ts +474 -0
  409. package/src/rule-docs.test.ts +159 -0
  410. package/src/rules-config.ts +40 -11
  411. package/src/tex-requirements.harness.ts +13 -23
  412. package/src/tex-requirements.test.ts +21 -0
  413. package/src/tex-requirements.ts +64 -14
  414. package/src/tex-venue-rules.test.ts +659 -0
  415. package/src/tex-venue-rules.ts +429 -0
  416. package/src/toolchain.harness.mjs +1 -1
  417. package/src/toolchain.test.ts +9 -5
  418. package/src/toolchain.ts +2 -2
  419. package/src/types.ts +5 -3
  420. package/src/venue-rules.test.ts +104 -8
  421. package/src/venue-rules.ts +22 -12
  422. package/templates/paper/paper.tex +2 -1
  423. package/skills/paper-pipeline/scripts/announce.mjs +0 -67
  424. package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
  425. package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
  426. package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
  427. package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
  428. package/skills/paper-pipeline/scripts/fixtures/scorecard.ts +0 -100
  429. package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
  430. package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
  431. package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
  432. package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
  433. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
  434. package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -750
  435. package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
  436. package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
  437. package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
  438. package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
  439. package/skills/paper-pipeline/scripts/status.mjs +0 -295
  440. package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
  441. /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
  442. /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
package/eslint.config.mjs CHANGED
@@ -19,6 +19,8 @@ import portRules from "#eslint-rules/install-path-literals";
19
19
  import n from "eslint-plugin-n";
20
20
  import tseslint from "typescript-eslint";
21
21
  import boundaries from "eslint-plugin-boundaries";
22
+ import functional from "eslint-plugin-functional";
23
+ import sonarjs from "eslint-plugin-sonarjs";
22
24
  import { dirname } from "node:path";
23
25
  import { fileURLToPath } from "node:url";
24
26
 
@@ -27,6 +29,21 @@ import { fileURLToPath } from "node:url";
27
29
  // or pinned at its current maximum in RATCHET.
28
30
  const TS_FILES = ["**/*.ts", "**/*.mts"];
29
31
 
32
+ /** Collection helpers come from Remeda; these would be a second library for the same job. */
33
+ const ONE_COLLECTION_LIBRARY = ["lodash", "lodash-es", "ramda"].map((name) => ({
34
+ name,
35
+ message: "use remeda (see CLAUDE.md)",
36
+ }));
37
+
38
+ /** An import of a skill's file, from code of the package itself (#133). */
39
+ const NO_SKILLS = [
40
+ {
41
+ regex: "(^|/)skills/",
42
+ message:
43
+ "The package's own code does not import from skills/: a skill is a consumer, not storage (#133). Move what you need into src/ or lib/.",
44
+ },
45
+ ];
46
+
30
47
  /** `x as unknown as T` tells the checker to look away (#76). Shared: a block REPLACES a rule's options. */
31
48
  const AS_UNKNOWN_AS = {
32
49
  selector:
@@ -54,33 +71,99 @@ const GIT_IN_A_RULE = [
54
71
  "A rule may not run git: a sha is deleted by routine maintenance and absent from a shallow CI checkout. Read the file content ESLint passes the rule.",
55
72
  }));
56
73
 
57
- const MAX_LINES = { max: 60, skipComments: true, skipBlankLines: true };
74
+ /** Source code: a function is one job, and thirty lines is where a second job starts to hide. */
75
+ const MAX_LINES = { max: 30, skipComments: true, skipBlankLines: true };
76
+ /**
77
+ * Tests keep sixty: a `describe`/`it` callback is a list of cases, not a function to split, and
78
+ * cutting it by line count scatters one scenario across helpers nobody reads twice.
79
+ */
80
+ const MAX_LINES_TESTS = { ...MAX_LINES, max: 60 };
81
+ const TEST_FILES = [
82
+ "**/*.test.{ts,mts}",
83
+ "**/*.harness.{ts,mts}",
84
+ "**/*.e2e.{ts,mts}",
85
+ "test/**/*.{ts,mts}",
86
+ ];
58
87
 
59
- // 🔴 A CEILING, NOT A PERMISSION: each number is the file's measured maximum on 2026-09-24, so a
60
- // function in these files can get simpler and cannot get worse. Lower a number when a refactor
61
- // lowers the maximum; never raise one. Paying the debt down is issue #72. A file leaves this table
62
- // when it passes the shared limits.
88
+ // 🔴 A CEILING, NOT A PERMISSION: each number is the file's measured maximum on the day its limit
89
+ // arrived (complexity and depth on 2026-09-24, the files #78 moved to TypeScript on 2026-09-27,
90
+ // cognitive complexity and the 30-line source limit on 2026-09-28), so a function in these files
91
+ // can get simpler and cannot get worse. Lower a number when a refactor lowers the maximum; never
92
+ // raise one. Paying the debt down is issue #137. A file leaves this table when it passes the
93
+ // shared limits.
63
94
  const RATCHET = {
64
- "src/init.ts": { complexity: 59, "max-lines-per-function": 219 },
65
- "src/cli.ts": { complexity: 43, "max-lines-per-function": 152 },
66
- "src/doctor.ts": { complexity: 38, "max-lines-per-function": 125 },
67
- "src/hooks-settings.ts": { complexity: 12, "max-depth": 4 },
68
- "src/structure.ts": { complexity: 12, "max-depth": 4 },
69
- "src/new-paper.ts": { complexity: 11 },
70
- // Moved from JavaScript on 2026-09-27 (#78) and measured that day: the move changed their
71
- // language, not their shape, so their debt is pinned here like the rest rather than paid in it.
72
95
  "eslint-rules/latex-language.ts": {
73
96
  complexity: 68,
97
+ "sonarjs/cognitive-complexity": 98,
74
98
  "max-lines-per-function": 314,
75
99
  "max-depth": 4,
76
100
  },
77
- "eslint-rules/paper-typography.ts": { complexity: 29, "max-depth": 5 },
78
101
  "eslint-rules/paper-stages.ts": {
79
102
  complexity: 15,
103
+ "sonarjs/cognitive-complexity": 21,
80
104
  "max-lines-per-function": 105,
81
105
  },
106
+ "eslint-rules/paper-typography.ts": {
107
+ complexity: 29,
108
+ "sonarjs/cognitive-complexity": 41,
109
+ "max-lines-per-function": 50,
110
+ "max-depth": 5,
111
+ },
112
+ "eslint-rules/pdf-last-page-balance.ts": { "max-lines-per-function": 31 },
113
+ "eslint-rules/review-frontmatter.ts": { "max-lines-per-function": 46 },
82
114
  "eslint-rules/temp-root-realpath.ts": { complexity: 13 },
115
+ "eslint-rules/tex-build.ts": { "max-lines-per-function": 34 },
116
+ "lib/agent-cli-version.ts": { "max-lines-per-function": 42 },
83
117
  "lib/markdown.ts": { complexity: 12 },
118
+ "scripts/check.ts": {
119
+ "sonarjs/cognitive-complexity": 13,
120
+ "max-lines-per-function": 39,
121
+ },
122
+ "skills/plan-paper-timeline/fixtures/fake-google-calendar.ts": {
123
+ "max-lines-per-function": 31,
124
+ },
125
+ "src/adapters/curl/download.io.ts": { "max-lines-per-function": 39 },
126
+ "src/adapters/references/index.ts": { "max-lines-per-function": 36 },
127
+ "src/build-engine.ts": { "max-lines-per-function": 33 },
128
+ "src/build.ts": {
129
+ "sonarjs/cognitive-complexity": 13,
130
+ "max-lines-per-function": 41,
131
+ },
132
+ "src/cli.ts": {
133
+ complexity: 43,
134
+ "sonarjs/cognitive-complexity": 30,
135
+ "max-lines-per-function": 152,
136
+ },
137
+ "src/doctor.ts": {
138
+ complexity: 38,
139
+ "sonarjs/cognitive-complexity": 20,
140
+ "max-lines-per-function": 125,
141
+ },
142
+ "src/domain/lookup-cache.ts": { "max-lines-per-function": 31 },
143
+ "src/hooks-settings.ts": {
144
+ complexity: 12,
145
+ "max-lines-per-function": 42,
146
+ "max-depth": 4,
147
+ },
148
+ "src/init.ts": {
149
+ complexity: 59,
150
+ "sonarjs/cognitive-complexity": 44,
151
+ "max-lines-per-function": 219,
152
+ },
153
+ "src/latex-log.ts": { "sonarjs/cognitive-complexity": 12 },
154
+ "src/link-skills.ts": { "max-lines-per-function": 32 },
155
+ "src/new-paper.ts": { complexity: 11, "max-lines-per-function": 38 },
156
+ "src/pdf-facts.ts": { "max-lines-per-function": 33 },
157
+ "src/presets.ts": { "max-lines-per-function": 50 },
158
+ "src/reference-rules.ts": { "max-lines-per-function": 36 },
159
+ "src/references.ts": { "max-lines-per-function": 43 },
160
+ "src/structure.ts": {
161
+ complexity: 12,
162
+ "sonarjs/cognitive-complexity": 24,
163
+ "max-lines-per-function": 39,
164
+ "max-depth": 4,
165
+ },
166
+ "src/toolchain.ts": { "max-lines-per-function": 50 },
84
167
  };
85
168
 
86
169
  // ── Hexagonal layers (src/CLAUDE.md, issue #76) ─────────────────────────────────────────
@@ -407,13 +490,24 @@ export default [
407
490
  tsconfigRootDir: dirname(fileURLToPath(import.meta.url)),
408
491
  },
409
492
  },
410
- plugins: { "@typescript-eslint": tseslint.plugin, local: localRules },
493
+ plugins: {
494
+ "@typescript-eslint": tseslint.plugin,
495
+ local: localRules,
496
+ sonarjs,
497
+ },
411
498
  rules: {
412
499
  // The same macOS-only defect as in the `.mjs` block; clean here, so it opens at `error`.
413
500
  "local/temp-root-realpath": "error",
414
501
  // `any` switches the checker off for everything it flows into. The preset has it too; named
415
502
  // here so that dropping the preset cannot drop it.
416
503
  "@typescript-eslint/no-explicit-any": "error",
504
+ // A type assertion (`x as T`, `<T>x`) is a claim the checker takes on trust (#126). A value
505
+ // from outside is parsed or guarded where it enters; a brand is minted by its own checked
506
+ // constructor. `as const` asserts nothing about the value and stays allowed.
507
+ "@typescript-eslint/consistent-type-assertions": [
508
+ "error",
509
+ { assertionStyle: "never" },
510
+ ],
417
511
  // The behaviour rules of the `.mjs` block, for the same reasons: each changes what the code
418
512
  // DOES, not how it looks, and a regex that escapes the wrong thing searches for the wrong thing.
419
513
  "no-empty": "error",
@@ -430,6 +524,10 @@ export default [
430
524
  // Branches per function. Ten is the rule's long-standing default; the worst function
431
525
  // measured was 59, and one that size cannot be read, only re-run.
432
526
  complexity: ["error", 10],
527
+ // `complexity` counts branches; cognitive complexity also charges NESTING, which is what makes
528
+ // a function hard to read. Only this one rule of the plugin: its recommended set is a style
529
+ // guide of its own, and each rule here is a decision.
530
+ "sonarjs/cognitive-complexity": ["error", 10],
433
531
  // `x as unknown as T` tells the checker to look away: two spellings of one port were once
434
532
  // reconciled that way (#76). A real conversion is a function; a real subset needs no cast.
435
533
  "no-restricted-syntax": ["error", AS_UNKNOWN_AS],
@@ -441,8 +539,15 @@ export default [
441
539
  "max-lines-per-function": ["error", MAX_LINES],
442
540
  // Three levels of callbacks is the ceiling before a promise chain or a named step is due.
443
541
  "max-nested-callbacks": ["error", 3],
542
+ // A `switch` over a union names every member (or says `default:` on purpose): a state added
543
+ // to the type is then a compile-time finding at every place that must handle it.
544
+ "@typescript-eslint/switch-exhaustiveness-check": "error",
444
545
  },
445
546
  },
547
+ {
548
+ files: TEST_FILES,
549
+ rules: { "max-lines-per-function": ["error", MAX_LINES_TESTS] },
550
+ },
446
551
  // Hexagonal layers, both axes (src/CLAUDE.md): knowledge by element, purity by file category.
447
552
  IO_GLOBALS,
448
553
  layerBoundaries(dirname(fileURLToPath(import.meta.url))),
@@ -594,6 +699,48 @@ export default [
594
699
  // one whose firing path nothing exercises, which is rule 4 wearing a different hat. The
595
700
  // pass/fail signal lives in `npm test`, not in the warning count of `npm run lint`.
596
701
  },
702
+ {
703
+ files: ["**/*.{ts,mts,mjs,js,cjs}"],
704
+ rules: {
705
+ "no-restricted-imports": ["error", { paths: ONE_COLLECTION_LIBRARY }],
706
+ },
707
+ },
708
+ // 🔴 THE LINTER DOES NOT READ A SKILL. Skills are consumers of the package, not its storage: the
709
+ // venue presets once lived in a skill and were located through a skill's script, so renaming the
710
+ // skill broke `paperlint lint` (#133). The package's own code — src/, eslint-rules/, lib/ — may
711
+ // not import from skills/. Tests and harnesses may: they check the skills.
712
+ {
713
+ files: [
714
+ "src/**/*.{ts,mts,mjs,js}",
715
+ "eslint-rules/**/*.{ts,mts,mjs,js}",
716
+ "lib/**/*.{ts,mts,mjs,js}",
717
+ ],
718
+ ignores: ["**/*.test.*", "**/*.harness.*"],
719
+ rules: {
720
+ "no-restricted-imports": [
721
+ "error",
722
+ { paths: ONE_COLLECTION_LIBRARY, patterns: NO_SKILLS },
723
+ ],
724
+ },
725
+ },
726
+ // 🔴 NEW CODE IS FUNCTIONAL: no `let`, no mutation, no loops, readonly types. Old code is listed
727
+ // in eslint-suppressions.json (ESLint's own bulk suppressions), which may only shrink: a fixed
728
+ // site that is not pruned fails `npm run lint`, and #134 burns the list down. Tests may mutate
729
+ // their fixtures.
730
+ {
731
+ files: ["src/**/*.ts", "eslint-rules/**/*.ts", "lib/**/*.ts"],
732
+ ignores: ["**/*.test.*", "**/*.harness.*"],
733
+ plugins: { functional },
734
+ rules: {
735
+ "functional/no-let": "error",
736
+ "functional/immutable-data": "error",
737
+ "functional/no-loop-statements": "error",
738
+ "functional/prefer-immutable-types": [
739
+ "error",
740
+ { enforcement: "ReadonlyShallow", ignoreInferredTypes: true },
741
+ ],
742
+ },
743
+ },
597
744
  // Rule sources, whatever their extension; tests and harnesses plant git calls on purpose.
598
745
  {
599
746
  files: ["eslint-rules/*.ts", "eslint-rules/*.mjs"],
@@ -0,0 +1,26 @@
1
+ {
2
+ "$comment": "acmart in its own fonts: the class silently falls back to Computer Modern when one of its font packages is missing, and that is what this fixture exists to catch. Only the rules whose answer does not depend on banal's page classification are named: the paper is one sentence long, banal calls such a page a one-column cover, and pdf/geometry rightly reports that — so neither it nor the exit code is part of the claim.",
3
+ "build": {
4
+ "outcome": "pdf"
5
+ },
6
+ "fonts": {
7
+ "all": "^(LinLibertine|LinBiolinum)",
8
+ "none": "^(CMR|CMBX|CMTI|CMTT|CMSS|LMRoman)",
9
+ "matchLog": true
10
+ },
11
+ "lint": {
12
+ "paperlintJson": {
13
+ "extends": "paperlint:agenticdev",
14
+ "kind": "short",
15
+ "rules": {
16
+ "pdf/last-page-balance": "error"
17
+ }
18
+ },
19
+ "rules": {
20
+ "pdf/last-page-balance": [],
21
+ "pdf/profile": [],
22
+ "pdf/fresh": [],
23
+ "pdf/fonts": []
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ # Fixture
2
+
3
+ Stage: draft
@@ -0,0 +1,14 @@
1
+ {
2
+ "$comment": "A real IEEEtran [conference,compsoc] paper for AIDC, built on paperlint's TeX Live: IEEE's Times, and not one finding from any rule — which is what judges pdf/limits, pdf/body-size and the rest against a real PDF of the class.",
3
+ "build": {
4
+ "outcome": "pdf"
5
+ },
6
+ "fonts": {
7
+ "some": "NimbusRomNo9L",
8
+ "none": "^(CMR|CMBX|CMTI|CMTT|CMSS|LMRoman)"
9
+ },
10
+ "lint": {
11
+ "exit": 0,
12
+ "findings": []
13
+ }
14
+ }
@@ -0,0 +1,46 @@
1
+ % A small real paper in the class AIDC requires, built by the e2e on paperlint's own TeX Live, so
2
+ % `paperlint toolchain` is proved to install what the ieee-conference preset declares, and every
3
+ % rule is judged against what a real build of the class measures. It conforms: the LLM Usage
4
+ % Statement closes it, and not one rule has anything to say.
5
+ \documentclass[conference,compsoc]{IEEEtran}
6
+ \begin{filecontents*}{refs.bib}
7
+ @article{knuth84,
8
+ title = {Literate Programming},
9
+ author = {Knuth, Donald E.},
10
+ journal = {The Computer Journal},
11
+ volume = {27},
12
+ number = {2},
13
+ pages = {97--111},
14
+ year = {1984},
15
+ doi = {10.1093/comjnl/27.2.97}
16
+ }
17
+ \end{filecontents*}
18
+ % banal calls a page with a few lines on it a one-column "cover", and pdf/geometry would rightly
19
+ % report that. So the body fills two columns of a page: one paragraph, set several times.
20
+ \newcommand{\filler}{This paragraph is ordinary prose, set in the two columns of the conference
21
+ format so that the page geometry is measured on a real body of text. It says nothing new, and it
22
+ does not need to: the fixture is about the class, the fonts and the page, not about its content.
23
+ A reviewer would find it dull, which is exactly what a measurement fixture should be.\par}
24
+ \begin{document}
25
+ \title{A Fixture Typeset in the Class the Venue Requires}
26
+ \author{\IEEEauthorblockN{A. Author}
27
+ \IEEEauthorblockA{Anonymous for double-blind review}}
28
+ \maketitle
29
+ \begin{abstract}
30
+ The abstract exists so the build has the front matter an IEEE paper carries.
31
+ \end{abstract}
32
+ \section{Introduction}
33
+ This paper has a body of ordinary prose, set in the two columns of the conference format, and
34
+ it cites one article~\cite{knuth84}.
35
+ \filler\filler\filler\filler
36
+ \section{Method}
37
+ The method is to build this file with pdflatex and bibtex, then read the facts of the PDF.
38
+ \filler\filler\filler\filler\filler\filler
39
+ \section{Conclusion}
40
+ The conclusion restates that the fixture builds.
41
+ \filler\filler\filler\filler
42
+ \bibliographystyle{IEEEtran}
43
+ \bibliography{refs}
44
+ \section*{LLM Usage Statement}
45
+ No LLMs were used in writing this fixture.
46
+ \end{document}
@@ -0,0 +1 @@
1
+ { "extends": "paperlint:aidc", "kind": "short" }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$comment": "Does not compile: an undefined control sequence on line 6. The build names pdflatex and quotes the log.",
3
+ "build": {
4
+ "outcome": "failed"
5
+ },
6
+ "says": [
7
+ "✗ compile: pdflatex exited with 1",
8
+ "./paper.tex:6: Undefined control sequence.",
9
+ "l.6 Text before, then \\undefinedmacro"
10
+ ]
11
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$comment": "The bibtex path: the citation and the \\ref resolve, and the leftover build.sh is ignored, not run.",
3
+ "build": {
4
+ "outcome": "pdf",
5
+ "pages": 1
6
+ },
7
+ "says": [
8
+ "build.sh is ignored — paperlint builds the paper itself"
9
+ ],
10
+ "text": {
11
+ "contains": [
12
+ "[1]"
13
+ ],
14
+ "lacks": [
15
+ "[?]",
16
+ "??"
17
+ ]
18
+ },
19
+ "files": {
20
+ "exist": [
21
+ "paper.bbl"
22
+ ],
23
+ "absent": [
24
+ "RAN"
25
+ ]
26
+ }
27
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$comment": "No pages: pdflatex exits 0 and writes no PDF, which is a failure, not a green paper.pdf.",
3
+ "build": {
4
+ "outcome": "failed"
5
+ },
6
+ "says": [
7
+ "✗ compile: pdflatex exited 0 but wrote no paper.pdf — does the document have any pages?"
8
+ ],
9
+ "saysNot": [
10
+ "✓"
11
+ ]
12
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "$comment": "Computer Modern standing in for acmart's silent fallback. It builds green; declared an agenticdev paper afterwards, the venue rules reject it.",
3
+ "build": {
4
+ "outcome": "pdf"
5
+ },
6
+ "fonts": {
7
+ "some": "^(CMR|CMBX|CMTI|CMTT|CMSS|LMRoman)",
8
+ "matchLog": true
9
+ },
10
+ "lint": {
11
+ "paperlintJson": {
12
+ "extends": "paperlint:agenticdev",
13
+ "kind": "short"
14
+ },
15
+ "exit": 1,
16
+ "rules": {
17
+ "pdf/fonts": [
18
+ {
19
+ "severity": 2,
20
+ "says": [
21
+ "LinLibertine"
22
+ ]
23
+ },
24
+ {
25
+ "severity": 2,
26
+ "says": [
27
+ "LinBiolinum"
28
+ ]
29
+ }
30
+ ],
31
+ "pdf/geometry": [
32
+ {
33
+ "severity": 2,
34
+ "says": [
35
+ "page width is 8.264 in, agenticdev requires 8.5 in"
36
+ ]
37
+ },
38
+ {
39
+ "severity": 2,
40
+ "says": [
41
+ "page height is 11.694 in, agenticdev requires 11 in"
42
+ ]
43
+ },
44
+ {
45
+ "severity": 2,
46
+ "says": [
47
+ "column(s), agenticdev requires 2"
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "$comment": "\\input{paper-guards} resolves with no configuration: paperlint's venues directory is on TEXINPUTS.",
3
+ "build": {
4
+ "outcome": "pdf"
5
+ }
6
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$comment": "No paper.tex: a refusal, named separately, and a stale paper.pdf is removed.",
3
+ "build": {
4
+ "outcome": "failed"
5
+ },
6
+ "says": [
7
+ "✗ nothing to compile: no paper.tex",
8
+ "paper.pdf removed — a stale PDF must not pass for this build"
9
+ ]
10
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$comment": "A two-column acmart paper whose last page ends unbalanced. The build measures it without judging; the optional lint rule reports it.",
3
+ "build": {
4
+ "outcome": "pdf"
5
+ },
6
+ "says": [
7
+ "✓ paper.pdf",
8
+ "facts: _build/paper.facts.json, last page 621.5 / 264.8 pt"
9
+ ],
10
+ "saysNot": [
11
+ " balance:"
12
+ ],
13
+ "files": {
14
+ "lacks": {
15
+ "paper.bbl": [
16
+ "\\balance"
17
+ ]
18
+ }
19
+ },
20
+ "facts": {
21
+ "schema": 2,
22
+ "lastPageColumnsPt": [
23
+ 621.5,
24
+ 264.8
25
+ ]
26
+ },
27
+ "lint": {
28
+ "paperlintJson": {
29
+ "rules": {
30
+ "pdf/last-page-balance": "error"
31
+ }
32
+ },
33
+ "exit": 1,
34
+ "rules": {
35
+ "pdf/last-page-balance": [
36
+ {
37
+ "severity": 2,
38
+ "says": [
39
+ "621.5 and 264.8 pt",
40
+ "pbalance"
41
+ ]
42
+ }
43
+ ],
44
+ "pdf/fresh": [],
45
+ "pdf/profile": [],
46
+ "pdf/fonts": [],
47
+ "pdf/geometry": [],
48
+ "pdf/limits": [],
49
+ "pdf/body-size": [],
50
+ "pdf/measured": [
51
+ {
52
+ "severity": 1,
53
+ "says": [
54
+ "names no venue preset yet"
55
+ ]
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ }
@@ -1,3 +1,3 @@
1
1
  // Clean: every answer to *where* comes from the port.
2
2
  import { consumerRoot } from "../../skills/paper-pipeline/scripts/consumer.mjs";
3
- export const LEDGER = (env, cwd) => `${consumerRoot({ env, cwd })}/runs.jsonl`;
3
+ export const CACHE = (env, cwd) => `${consumerRoot({ env, cwd })}/cache.json`;
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: planted-clean
3
- allowed-tools: [Bash(node ${CLAUDE_SKILL_DIR}/../paper-pipeline/scripts/ledger.mjs:*)]
3
+ allowed-tools: [Bash(node ${CLAUDE_SKILL_DIR}/../paper-pipeline/scripts/extract-ref-facts.mjs:*)]
4
4
  ---
5
5
 
6
6
  # A skill that asks the port where it is
@@ -8,7 +8,7 @@ allowed-tools: [Bash(node ${CLAUDE_SKILL_DIR}/../paper-pipeline/scripts/ledger.m
8
8
  Record the verdict:
9
9
 
10
10
  ```
11
- node ${CLAUDE_SKILL_DIR}/../paper-pipeline/scripts/ledger.mjs record planted . FINDING 1 report.md
11
+ node ${CLAUDE_SKILL_DIR}/../paper-pipeline/scripts/extract-ref-facts.mjs planted
12
12
  ```
13
13
 
14
14
  Paths inside the repository, such as `skills/paper-pipeline/scripts/consumer.mjs`, are not
@@ -1,3 +1,3 @@
1
1
  // Planted: a shipped module betting on one channel, in both literal forms.
2
- export const LEDGER = "node_modules/research-paper-pipeline/runs.jsonl";
2
+ export const CACHE = "node_modules/research-paper-pipeline/cache.json";
3
3
  export const skill = (n) => `.claude/skills/${n}/SKILL.md`;
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: planted-defect
3
- allowed-tools: [Bash(node .claude/skills/paper-pipeline/scripts/ledger.mjs:*)]
3
+ allowed-tools: [Bash(node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs:*)]
4
4
  ---
5
5
 
6
6
  # A skill that bets on one delivery channel
@@ -8,7 +8,7 @@ allowed-tools: [Bash(node .claude/skills/paper-pipeline/scripts/ledger.mjs:*)]
8
8
  Record the verdict:
9
9
 
10
10
  ```
11
- node .claude/skills/paper-pipeline/scripts/ledger.mjs record planted . FINDING 1 report.md
11
+ node .claude/skills/paper-pipeline/scripts/extract-ref-facts.mjs planted
12
12
  ```
13
13
 
14
14
  The module lives at `node_modules/research-paper-pipeline/skills/paper-pipeline/scripts/consumer.mjs`.
@@ -13,7 +13,7 @@ poppler's numbers, so a pass means agreement with the tool pdf.js replaced.
13
13
  | `encrypted.pdf` | `t3-all.pdf` saved with an AES-256 user password (PyMuPDF) | pdf.js refuses it with `PasswordException`; the reader reports `encrypted` |
14
14
  | `hidden-text.pdf` | `hidden-text.tex`, pdflatex | two-column body text beside near-white, rotated and render-mode-3 text; page 2 holds only hidden text, so banal calls it blank |
15
15
 
16
- `test/e2e/banal.ts` also reads these files, with the real banal: it pins what banal 1.2 printed for
16
+ `test/e2e/tex/banal.e2e.ts` also reads these files, with the real banal: it pins what banal 1.2 printed for
17
17
  each one on poppler's `pdftohtml` 24.02.0 (2026-09-25), so a pass there means paperlint's pdf.js-written
18
18
  XML measures the same as poppler's. `hidden-text.pdf` exists for that test: each kind of hidden text
19
19
  changes banal's answer if it is counted.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ONE reader of `baseline.json`, shared by the two runs that compare against it: the harness
3
- * beside this file (the repository's own `bin/paperlint.mjs`) and `test/e2e/install.ts` (the binary
3
+ * beside this file (the repository's own `bin/paperlint.mjs`) and `test/e2e/install/install.e2e.ts` (the binary
4
4
  * a consumer actually got). Two copies of "growth fails, a drop never does" would drift the first
5
5
  * time one of them is tightened, and the two runs would then disagree about the same article.
6
6
  */
@@ -5,18 +5,16 @@
5
5
  # A paper's own build script must not hard-code a path into the agent's skills directory:
6
6
  # once the skills move into a package that address points at nothing, and LaTeX's reaction to
7
7
  # a missing \input is SILENCE, not an error. So the script resolves the venues directory
8
- # through the package first, falls back to the in-repo copy, and then VERIFIES that kpsewhich
8
+ # through the installed package, and then VERIFIES that kpsewhich
9
9
  # can actually see the file before handing anything to pdflatex.
10
10
  set -euo pipefail
11
11
  ROOT=$(git rev-parse --show-toplevel)
12
12
 
13
- VENUES_PKG=$(node -e 'try{process.stdout.write(require.resolve("paperlint/venues/paper-guards.tex"))}catch{}' 2>/dev/null || true)
13
+ VENUES_PKG=$(cd "$ROOT" && node -e 'try{process.stdout.write(require.resolve("paperlint/presets/tex/paper-guards.tex"))}catch{}' 2>/dev/null || true)
14
14
  if [ -n "$VENUES_PKG" ] && [ -f "$VENUES_PKG" ]; then
15
15
  VENUES_DIR=$(dirname "$VENUES_PKG")
16
- elif [ -f "$ROOT/.claude/skills/submit-paper/references/venues/paper-guards.tex" ]; then
17
- VENUES_DIR="$ROOT/.claude/skills/submit-paper/references/venues"
18
16
  else
19
- echo "✗ paper-guards.tex found neither in the package nor in the repository" >&2
17
+ echo "✗ paper-guards.tex not found in the installed paperlint package" >&2
20
18
  exit 1
21
19
  fi
22
20
  export TEXINPUTS="$VENUES_DIR:"