paperlint 2.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 (762) hide show
  1. package/.github/dependabot.yml +72 -0
  2. package/.github/workflows/ci.yml +297 -0
  3. package/.github/workflows/dependabot-automerge.yml +70 -0
  4. package/.github/workflows/pr-title.yml +59 -0
  5. package/.github/workflows/release.yml +54 -0
  6. package/CLAUDE.md +598 -0
  7. package/CONTRIBUTING.md +159 -0
  8. package/LICENSE +21 -0
  9. package/README.md +240 -0
  10. package/action.harness.mjs +287 -0
  11. package/action.mutations.mjs +162 -0
  12. package/action.yml +138 -0
  13. package/bin/rpp.mjs +43 -0
  14. package/dist/action-ref.d.ts +12 -0
  15. package/dist/action-ref.d.ts.map +1 -0
  16. package/dist/action-ref.js +16 -0
  17. package/dist/action-ref.js.map +1 -0
  18. package/dist/adapters/banal/failure.d.ts +73 -0
  19. package/dist/adapters/banal/failure.d.ts.map +1 -0
  20. package/dist/adapters/banal/failure.js +58 -0
  21. package/dist/adapters/banal/failure.js.map +1 -0
  22. package/dist/adapters/banal/index.d.ts +17 -0
  23. package/dist/adapters/banal/index.d.ts.map +1 -0
  24. package/dist/adapters/banal/index.js +56 -0
  25. package/dist/adapters/banal/index.js.map +1 -0
  26. package/dist/adapters/banal/install.d.ts +26 -0
  27. package/dist/adapters/banal/install.d.ts.map +1 -0
  28. package/dist/adapters/banal/install.js +15 -0
  29. package/dist/adapters/banal/install.js.map +1 -0
  30. package/dist/adapters/banal/invocation.d.ts +48 -0
  31. package/dist/adapters/banal/invocation.d.ts.map +1 -0
  32. package/dist/adapters/banal/invocation.js +43 -0
  33. package/dist/adapters/banal/invocation.js.map +1 -0
  34. package/dist/adapters/banal/locate.d.ts +50 -0
  35. package/dist/adapters/banal/locate.d.ts.map +1 -0
  36. package/dist/adapters/banal/locate.js +34 -0
  37. package/dist/adapters/banal/locate.js.map +1 -0
  38. package/dist/adapters/banal/output.d.ts +27 -0
  39. package/dist/adapters/banal/output.d.ts.map +1 -0
  40. package/dist/adapters/banal/output.js +112 -0
  41. package/dist/adapters/banal/output.js.map +1 -0
  42. package/dist/adapters/banal/pin.d.ts +19 -0
  43. package/dist/adapters/banal/pin.d.ts.map +1 -0
  44. package/dist/adapters/banal/pin.js +15 -0
  45. package/dist/adapters/banal/pin.js.map +1 -0
  46. package/dist/adapters/banal/probe.d.ts +12 -0
  47. package/dist/adapters/banal/probe.d.ts.map +1 -0
  48. package/dist/adapters/banal/probe.js +27 -0
  49. package/dist/adapters/banal/probe.js.map +1 -0
  50. package/dist/adapters/banal/run.d.ts +89 -0
  51. package/dist/adapters/banal/run.d.ts.map +1 -0
  52. package/dist/adapters/banal/run.js +104 -0
  53. package/dist/adapters/banal/run.js.map +1 -0
  54. package/dist/adapters/banal/settings.d.ts +18 -0
  55. package/dist/adapters/banal/settings.d.ts.map +1 -0
  56. package/dist/adapters/banal/settings.js +29 -0
  57. package/dist/adapters/banal/settings.js.map +1 -0
  58. package/dist/adapters/banal/xml.d.ts +48 -0
  59. package/dist/adapters/banal/xml.d.ts.map +1 -0
  60. package/dist/adapters/banal/xml.js +67 -0
  61. package/dist/adapters/banal/xml.js.map +1 -0
  62. package/dist/adapters/curl/download.io.d.ts +14 -0
  63. package/dist/adapters/curl/download.io.d.ts.map +1 -0
  64. package/dist/adapters/curl/download.io.js +69 -0
  65. package/dist/adapters/curl/download.io.js.map +1 -0
  66. package/dist/adapters/curl/index.d.ts +6 -0
  67. package/dist/adapters/curl/index.d.ts.map +1 -0
  68. package/dist/adapters/curl/index.js +6 -0
  69. package/dist/adapters/curl/index.js.map +1 -0
  70. package/dist/adapters/memory/index.d.ts +43 -0
  71. package/dist/adapters/memory/index.d.ts.map +1 -0
  72. package/dist/adapters/memory/index.js +79 -0
  73. package/dist/adapters/memory/index.js.map +1 -0
  74. package/dist/adapters/node/files.io.d.ts +3 -0
  75. package/dist/adapters/node/files.io.d.ts.map +1 -0
  76. package/dist/adapters/node/files.io.js +31 -0
  77. package/dist/adapters/node/files.io.js.map +1 -0
  78. package/dist/adapters/node/host.io.d.ts +3 -0
  79. package/dist/adapters/node/host.io.d.ts.map +1 -0
  80. package/dist/adapters/node/host.io.js +14 -0
  81. package/dist/adapters/node/host.io.js.map +1 -0
  82. package/dist/adapters/node/index.d.ts +25 -0
  83. package/dist/adapters/node/index.d.ts.map +1 -0
  84. package/dist/adapters/node/index.js +14 -0
  85. package/dist/adapters/node/index.js.map +1 -0
  86. package/dist/adapters/node/process.io.d.ts +14 -0
  87. package/dist/adapters/node/process.io.d.ts.map +1 -0
  88. package/dist/adapters/node/process.io.js +41 -0
  89. package/dist/adapters/node/process.io.js.map +1 -0
  90. package/dist/adapters/node/workspace.io.d.ts +4 -0
  91. package/dist/adapters/node/workspace.io.d.ts.map +1 -0
  92. package/dist/adapters/node/workspace.io.js +33 -0
  93. package/dist/adapters/node/workspace.io.js.map +1 -0
  94. package/dist/adapters/pdfjs/fill.d.ts +42 -0
  95. package/dist/adapters/pdfjs/fill.d.ts.map +1 -0
  96. package/dist/adapters/pdfjs/fill.js +91 -0
  97. package/dist/adapters/pdfjs/fill.js.map +1 -0
  98. package/dist/build-engine.d.ts +48 -0
  99. package/dist/build-engine.d.ts.map +1 -0
  100. package/dist/build-engine.js +148 -0
  101. package/dist/build-engine.js.map +1 -0
  102. package/dist/build.d.ts +163 -0
  103. package/dist/build.d.ts.map +1 -0
  104. package/dist/build.js +575 -0
  105. package/dist/build.js.map +1 -0
  106. package/dist/cli.d.ts +151 -0
  107. package/dist/cli.d.ts.map +1 -0
  108. package/dist/cli.js +951 -0
  109. package/dist/cli.js.map +1 -0
  110. package/dist/doctor.d.ts +42 -0
  111. package/dist/doctor.d.ts.map +1 -0
  112. package/dist/doctor.js +280 -0
  113. package/dist/doctor.js.map +1 -0
  114. package/dist/domain/geometry.d.ts +71 -0
  115. package/dist/domain/geometry.d.ts.map +1 -0
  116. package/dist/domain/geometry.js +35 -0
  117. package/dist/domain/geometry.js.map +1 -0
  118. package/dist/domain/host.d.ts +16 -0
  119. package/dist/domain/host.d.ts.map +1 -0
  120. package/dist/domain/host.js +8 -0
  121. package/dist/domain/host.js.map +1 -0
  122. package/dist/domain/page-layout.d.ts +34 -0
  123. package/dist/domain/page-layout.d.ts.map +1 -0
  124. package/dist/domain/page-layout.js +8 -0
  125. package/dist/domain/page-layout.js.map +1 -0
  126. package/dist/domain/paths.d.ts +5 -0
  127. package/dist/domain/paths.d.ts.map +1 -0
  128. package/dist/domain/paths.js +2 -0
  129. package/dist/domain/paths.js.map +1 -0
  130. package/dist/domain/result.d.ts +23 -0
  131. package/dist/domain/result.d.ts.map +1 -0
  132. package/dist/domain/result.js +10 -0
  133. package/dist/domain/result.js.map +1 -0
  134. package/dist/domain/sha256.d.ts +7 -0
  135. package/dist/domain/sha256.d.ts.map +1 -0
  136. package/dist/domain/sha256.js +14 -0
  137. package/dist/domain/sha256.js.map +1 -0
  138. package/dist/domain/text.d.ts +6 -0
  139. package/dist/domain/text.d.ts.map +1 -0
  140. package/dist/domain/text.js +7 -0
  141. package/dist/domain/text.js.map +1 -0
  142. package/dist/engine.d.ts +93 -0
  143. package/dist/engine.d.ts.map +1 -0
  144. package/dist/engine.js +119 -0
  145. package/dist/engine.js.map +1 -0
  146. package/dist/exit-code.d.ts +22 -0
  147. package/dist/exit-code.d.ts.map +1 -0
  148. package/dist/exit-code.js +10 -0
  149. package/dist/exit-code.js.map +1 -0
  150. package/dist/facts-file.d.ts +96 -0
  151. package/dist/facts-file.d.ts.map +1 -0
  152. package/dist/facts-file.js +134 -0
  153. package/dist/facts-file.js.map +1 -0
  154. package/dist/hooks-settings.d.ts +141 -0
  155. package/dist/hooks-settings.d.ts.map +1 -0
  156. package/dist/hooks-settings.js +306 -0
  157. package/dist/hooks-settings.js.map +1 -0
  158. package/dist/init.d.ts +201 -0
  159. package/dist/init.d.ts.map +1 -0
  160. package/dist/init.js +579 -0
  161. package/dist/init.js.map +1 -0
  162. package/dist/latex-log.d.ts +80 -0
  163. package/dist/latex-log.d.ts.map +1 -0
  164. package/dist/latex-log.js +187 -0
  165. package/dist/latex-log.js.map +1 -0
  166. package/dist/latex-loop.d.ts +129 -0
  167. package/dist/latex-loop.d.ts.map +1 -0
  168. package/dist/latex-loop.js +113 -0
  169. package/dist/latex-loop.js.map +1 -0
  170. package/dist/link-skills.d.ts +51 -0
  171. package/dist/link-skills.d.ts.map +1 -0
  172. package/dist/link-skills.js +199 -0
  173. package/dist/link-skills.js.map +1 -0
  174. package/dist/new-paper.d.ts +48 -0
  175. package/dist/new-paper.d.ts.map +1 -0
  176. package/dist/new-paper.js +110 -0
  177. package/dist/new-paper.js.map +1 -0
  178. package/dist/pdf-facts.d.ts +44 -0
  179. package/dist/pdf-facts.d.ts.map +1 -0
  180. package/dist/pdf-facts.js +239 -0
  181. package/dist/pdf-facts.js.map +1 -0
  182. package/dist/pdf-geometry.d.ts +170 -0
  183. package/dist/pdf-geometry.d.ts.map +1 -0
  184. package/dist/pdf-geometry.js +158 -0
  185. package/dist/pdf-geometry.js.map +1 -0
  186. package/dist/ports/download.d.ts +9 -0
  187. package/dist/ports/download.d.ts.map +1 -0
  188. package/dist/ports/download.js +2 -0
  189. package/dist/ports/download.js.map +1 -0
  190. package/dist/ports/files.d.ts +11 -0
  191. package/dist/ports/files.d.ts.map +1 -0
  192. package/dist/ports/files.js +2 -0
  193. package/dist/ports/files.js.map +1 -0
  194. package/dist/ports/measure-geometry.d.ts +8 -0
  195. package/dist/ports/measure-geometry.d.ts.map +1 -0
  196. package/dist/ports/measure-geometry.js +2 -0
  197. package/dist/ports/measure-geometry.js.map +1 -0
  198. package/dist/ports/process.d.ts +45 -0
  199. package/dist/ports/process.d.ts.map +1 -0
  200. package/dist/ports/process.js +2 -0
  201. package/dist/ports/process.js.map +1 -0
  202. package/dist/ports/tool-installer.d.ts +29 -0
  203. package/dist/ports/tool-installer.d.ts.map +1 -0
  204. package/dist/ports/tool-installer.js +2 -0
  205. package/dist/ports/tool-installer.js.map +1 -0
  206. package/dist/ports/workspace.d.ts +18 -0
  207. package/dist/ports/workspace.d.ts.map +1 -0
  208. package/dist/ports/workspace.js +2 -0
  209. package/dist/ports/workspace.js.map +1 -0
  210. package/dist/rules-config.d.ts +34 -0
  211. package/dist/rules-config.d.ts.map +1 -0
  212. package/dist/rules-config.js +132 -0
  213. package/dist/rules-config.js.map +1 -0
  214. package/dist/structure.d.ts +34 -0
  215. package/dist/structure.d.ts.map +1 -0
  216. package/dist/structure.js +149 -0
  217. package/dist/structure.js.map +1 -0
  218. package/dist/tex-requirements.d.ts +43 -0
  219. package/dist/tex-requirements.d.ts.map +1 -0
  220. package/dist/tex-requirements.js +127 -0
  221. package/dist/tex-requirements.js.map +1 -0
  222. package/dist/toolchain.d.ts +159 -0
  223. package/dist/toolchain.d.ts.map +1 -0
  224. package/dist/toolchain.js +542 -0
  225. package/dist/toolchain.js.map +1 -0
  226. package/dist/types.d.ts +110 -0
  227. package/dist/types.d.ts.map +1 -0
  228. package/dist/types.js +2 -0
  229. package/dist/types.js.map +1 -0
  230. package/docs/configuration.md +235 -0
  231. package/docs/e2e.md +152 -0
  232. package/docs/incidents.md +59 -0
  233. package/docs/install.md +170 -0
  234. package/docs/optional-rules.md +107 -0
  235. package/docs/package-shape-options.md +262 -0
  236. package/docs/prior-art/README.md +76 -0
  237. package/docs/prior-art/blocking-vs-advisory.md +83 -0
  238. package/docs/prior-art/content-delivery.md +124 -0
  239. package/docs/prior-art/multi-mode-tools.md +106 -0
  240. package/docs/prior-art/nondeterministic-checks.md +99 -0
  241. package/docs/prior-art/package-location.md +422 -0
  242. package/docs/prior-art/paper-folder-scaffolding.md +538 -0
  243. package/docs/prior-art/readme-structure.md +69 -0
  244. package/docs/prior-art/repro/README.md +92 -0
  245. package/docs/prior-art/repro/claim1-allowedtools.mjs +66 -0
  246. package/docs/prior-art/repro/claim1-at2.mjs +40 -0
  247. package/docs/prior-art/repro/claim1-crosschannel.mjs +54 -0
  248. package/docs/prior-art/repro/claim1-frontmatter.mjs +76 -0
  249. package/docs/prior-art/repro/claim1-hook-payload-reporter.mjs +10 -0
  250. package/docs/prior-art/repro/claim1-plugin-frontmatter.mjs +27 -0
  251. package/docs/prior-art/repro/claim1-plugin-skill.mjs +52 -0
  252. package/docs/prior-art/repro/claim1-project-skill.mjs +81 -0
  253. package/docs/prior-art/repro/claim2-marketplace-flat-asclaimed.json +1 -0
  254. package/docs/prior-art/repro/claim2-marketplace-negative-control.json +1 -0
  255. package/docs/prior-art/repro/claim2-marketplace-nested-exact.json +9 -0
  256. package/docs/prior-art/repro/claim2-marketplace-nested-noversion.json +9 -0
  257. package/docs/prior-art/repro/claim2-marketplace-nested-range.json +1 -0
  258. package/docs/prior-art/repro/claim3-imports.mjs +50 -0
  259. package/docs/prior-art/repro/claim4-find-package-json.mjs +8 -0
  260. package/docs/prior-art/repro/claim4-package-dir.mjs +39 -0
  261. package/docs/prior-art/repro/claim4-parent-arg.mjs +17 -0
  262. package/docs/prior-art/repro/claim4-resolve-apis.mjs +21 -0
  263. package/docs/prior-art/repro/claim4-setup-consumers.mjs +45 -0
  264. package/docs/prior-art/repro/claim4-yarn-pnp.mjs +70 -0
  265. package/docs/prior-art/repro/claim5-bin-launch.mjs +39 -0
  266. package/docs/prior-art/repro/claim5-exports-mutation.mjs +57 -0
  267. package/docs/prior-art/repro/claim5-resolved-location-and-bin.mjs +33 -0
  268. package/docs/prior-art/repro/claim6-candidate-ambiguity.mjs +17 -0
  269. package/docs/prior-art/repro/claim6-doc-path-candidates.mjs +27 -0
  270. package/docs/prior-art/test-tooling.md +131 -0
  271. package/docs/rules.md +58 -0
  272. package/docs/texlive-install-decision.md +230 -0
  273. package/docs/toolchain.md +152 -0
  274. package/eslint-rules/doc-fields.harness.mjs +336 -0
  275. package/eslint-rules/doc-fields.mjs +186 -0
  276. package/eslint-rules/doc-fields.mutations.mjs +96 -0
  277. package/eslint-rules/install-path-literals.harness.mjs +121 -0
  278. package/eslint-rules/install-path-literals.mjs +108 -0
  279. package/eslint-rules/install-path-literals.mutations.mjs +62 -0
  280. package/eslint-rules/latex-language.harness.mjs +599 -0
  281. package/eslint-rules/latex-language.mjs +591 -0
  282. package/eslint-rules/latex-language.mutations.mjs +196 -0
  283. package/eslint-rules/paper-research-question.harness.mjs +146 -0
  284. package/eslint-rules/paper-research-question.mjs +180 -0
  285. package/eslint-rules/paper-research-question.mutations.mjs +127 -0
  286. package/eslint-rules/paper-stages.harness.mjs +356 -0
  287. package/eslint-rules/paper-stages.mjs +455 -0
  288. package/eslint-rules/paper-stages.mutations.mjs +157 -0
  289. package/eslint-rules/paper-typography.harness.mjs +291 -0
  290. package/eslint-rules/paper-typography.mjs +313 -0
  291. package/eslint-rules/paper-typography.mutations.mjs +131 -0
  292. package/eslint-rules/papers.harness.mjs +259 -0
  293. package/eslint-rules/papers.mjs +166 -0
  294. package/eslint-rules/papers.mutations.mjs +186 -0
  295. package/eslint-rules/pdf-last-page-balance.harness.mjs +206 -0
  296. package/eslint-rules/pdf-last-page-balance.mjs +208 -0
  297. package/eslint-rules/review-findings-cause.harness.mjs +228 -0
  298. package/eslint-rules/review-findings-cause.mjs +135 -0
  299. package/eslint-rules/review-findings-cause.mutations.mjs +72 -0
  300. package/eslint-rules/temp-root-realpath.harness.mjs +176 -0
  301. package/eslint-rules/temp-root-realpath.mjs +129 -0
  302. package/eslint-rules/temp-root-realpath.mutations.mjs +99 -0
  303. package/eslint-rules/tex-build.harness.mjs +753 -0
  304. package/eslint-rules/tex-build.mjs +322 -0
  305. package/eslint-rules/tex-build.mutations.mjs +258 -0
  306. package/eslint.config.mjs +521 -0
  307. package/fixtures/build-e2e/acmart/PIPELINE-STATUS.md +3 -0
  308. package/fixtures/build-e2e/acmart/paper.tex +11 -0
  309. package/fixtures/build-e2e/acmart/venue.json +1 -0
  310. package/fixtures/build-e2e/broken/PIPELINE-STATUS.md +3 -0
  311. package/fixtures/build-e2e/broken/paper.tex +7 -0
  312. package/fixtures/build-e2e/cite/PIPELINE-STATUS.md +3 -0
  313. package/fixtures/build-e2e/cite/build.sh +5 -0
  314. package/fixtures/build-e2e/cite/paper.tex +10 -0
  315. package/fixtures/build-e2e/cite/refs.bib +9 -0
  316. package/fixtures/build-e2e/empty/PIPELINE-STATUS.md +3 -0
  317. package/fixtures/build-e2e/empty/paper.tex +6 -0
  318. package/fixtures/build-e2e/fallback/PIPELINE-STATUS.md +3 -0
  319. package/fixtures/build-e2e/fallback/paper.tex +11 -0
  320. package/fixtures/build-e2e/guards/PIPELINE-STATUS.md +3 -0
  321. package/fixtures/build-e2e/guards/paper.tex +10 -0
  322. package/fixtures/build-e2e/no-source/PIPELINE-STATUS.md +3 -0
  323. package/fixtures/build-e2e/unbalanced/PIPELINE-STATUS.md +3 -0
  324. package/fixtures/build-e2e/unbalanced/paper.tex +28 -0
  325. package/fixtures/build-e2e/unbalanced/refs.bib +269 -0
  326. package/fixtures/install-path-literals/clean.fixture.mjs +3 -0
  327. package/fixtures/install-path-literals/clean.md +15 -0
  328. package/fixtures/install-path-literals/defect.fixture.mjs +3 -0
  329. package/fixtures/install-path-literals/defect.md +14 -0
  330. package/fixtures/latex-language/clean.tex +50 -0
  331. package/fixtures/latex-language/defect.tex +52 -0
  332. package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +9 -0
  333. package/fixtures/paper-research-question/comment-only/paper.tex +7 -0
  334. package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +10 -0
  335. package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +6 -0
  336. package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +6 -0
  337. package/fixtures/paper-research-question/draft/paper.tex +2 -0
  338. package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +9 -0
  339. package/fixtures/paper-research-question/markdown-no-rq/paper.md +4 -0
  340. package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +12 -0
  341. package/fixtures/paper-research-question/shipped-no-rq/paper.tex +3 -0
  342. package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +10 -0
  343. package/fixtures/paper-research-question/shipped-with-rq/paper.tex +2 -0
  344. package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +16 -0
  345. package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +17 -0
  346. package/fixtures/paper-stages/nofile/PIPELINE-STATUS.md +8 -0
  347. package/fixtures/paper-stages/noheader/PIPELINE-STATUS.md +1 -0
  348. package/fixtures/paper-stages/noheader/versions/2026-07-22-submitted.pdf +0 -0
  349. package/fixtures/paper-stages/nothing/PIPELINE-STATUS.md +3 -0
  350. package/fixtures/paper-stages/ok/PIPELINE-STATUS.md +9 -0
  351. package/fixtures/paper-stages/ok/versions/2026-07-22-submitted.pdf +0 -0
  352. package/fixtures/paper-stages/stale/PIPELINE-STATUS.md +1 -0
  353. package/fixtures/paper-stages/stale/versions/2026-07-22-submitted.STALE-WRONG-FILE.pdf +0 -0
  354. package/fixtures/paper-stages/twice/PIPELINE-STATUS.md +14 -0
  355. package/fixtures/paper-stages/twice/versions/2026-08-06-submitted.pdf +0 -0
  356. package/fixtures/paper-stages/twice/versions/2026-10-24-submitted.pdf +0 -0
  357. package/fixtures/paper-stages/undeclared/PIPELINE-STATUS.md +8 -0
  358. package/fixtures/paper-stages/undeclared/versions/2026-07-22-submitted.pdf +0 -0
  359. package/fixtures/paper-stages/undeclared/versions/2026-08-29-camera-ready.pdf +0 -0
  360. package/fixtures/paper-stages/wrongsize/PIPELINE-STATUS.md +8 -0
  361. package/fixtures/paper-stages/wrongsize/versions/2026-07-22-submitted.pdf +0 -0
  362. package/fixtures/paper-typography/clean-paper/paper.tex +29 -0
  363. package/fixtures/paper-typography/messy-paper/paper.tex +27 -0
  364. package/fixtures/pdf-facts/README.md +22 -0
  365. package/fixtures/pdf-facts/corrupt-font.pdf +0 -0
  366. package/fixtures/pdf-facts/encrypted.pdf +0 -0
  367. package/fixtures/pdf-facts/hidden-text.pdf +0 -0
  368. package/fixtures/pdf-facts/hidden-text.tex +28 -0
  369. package/fixtures/pdf-facts/t3-all.pdf +0 -0
  370. package/fixtures/pdf-facts/t3-all.tex +8 -0
  371. package/fixtures/pdf-facts/t3-mixed.pdf +0 -0
  372. package/fixtures/pdf-facts/t3-mixed.tex +9 -0
  373. package/fixtures/pdf-facts/ttf.pdf +2240 -1
  374. package/fixtures/pdf-facts/ttf.tex +6 -0
  375. package/fixtures/real-markdown-paper/baseline.json +24 -0
  376. package/fixtures/real-markdown-paper/baseline.mjs +48 -0
  377. package/fixtures/render-paper/build-clean.sh +25 -0
  378. package/fixtures/render-paper/build-defect.sh +15 -0
  379. package/fixtures/review-findings-cause/clean.md +17 -0
  380. package/fixtures/review-findings-cause/defect.md +14 -0
  381. package/fixtures/review-findings-cause/old-debt.md +14 -0
  382. package/fixtures/review-findings-cause/quiet-in-fence.md +16 -0
  383. package/fixtures/tex-build/clean.tex +21 -0
  384. package/fixtures/tex-build/defect.tex +24 -0
  385. package/fixtures/tex-build/frontmatter-clean.tex +25 -0
  386. package/fixtures/tex-build/frontmatter-defect.tex +23 -0
  387. package/fixtures/toolchain-mirror/catalog.txt +5 -0
  388. package/fixtures/toolchain-mirror/install-tl +27 -0
  389. package/fixtures/toolchain-mirror/release-texlive.txt +3 -0
  390. package/fixtures/toolchain-mirror/release-year +1 -0
  391. package/fixtures/toolchain-mirror/stub-kpsewhich +8 -0
  392. package/fixtures/toolchain-mirror/stub-pdflatex +3 -0
  393. package/fixtures/toolchain-mirror/stub-tlmgr +44 -0
  394. package/hooks/hooks.harness.mjs +713 -0
  395. package/hooks/hooks.mutations.mjs +337 -0
  396. package/hooks/paper-edit-guard.hook.d.mts +13 -0
  397. package/hooks/paper-edit-guard.hook.mjs +457 -0
  398. package/hooks/paper-skills-nudge.hook.mjs +136 -0
  399. package/hooks/paper-status-gates.hook.mjs +156 -0
  400. package/hooks/paper-status-gates.sh +91 -0
  401. package/lib/agent-cli-version.harness.mjs +165 -0
  402. package/lib/agent-cli-version.mjs +106 -0
  403. package/lib/agent-cli-version.mutations.mjs +109 -0
  404. package/lib/markdown.mjs +386 -0
  405. package/lib/mutation-driver.harness.mjs +227 -0
  406. package/lib/mutation-driver.mjs +397 -0
  407. package/lib/mutation-driver.mutations.mjs +68 -0
  408. package/lib/paper-config.d.mts +34 -0
  409. package/lib/paper-config.harness.mjs +286 -0
  410. package/lib/paper-config.mjs +142 -0
  411. package/lib/paper-config.mutations.mjs +143 -0
  412. package/lib/skill-checks.mjs +701 -0
  413. package/lib/skill-corpus.mjs +403 -0
  414. package/lib/skill-eval-fixture.mjs +63 -0
  415. package/lib/skill-eval-kit.mjs +257 -0
  416. package/lib/skill-trigger-cases.harness.mjs +170 -0
  417. package/lib/skill-trigger-cases.mjs +446 -0
  418. package/lib/skill-trigger-cases.mutations.mjs +65 -0
  419. package/lib/trigger-ledger.mjs +215 -0
  420. package/package.json +97 -0
  421. package/plugin/.claude-plugin/plugin.json +8 -0
  422. package/plugin/hooks/hooks.json +30 -0
  423. package/scripts/check.harness.mjs +177 -0
  424. package/scripts/check.mjs +239 -0
  425. package/scripts/check.mutations.mjs +110 -0
  426. package/scripts/eslint-report-guard.mjs +82 -0
  427. package/scripts/exclusive.mjs +138 -0
  428. package/scripts/harness-api.frozen.json +76 -0
  429. package/scripts/harness-api.test.ts +175 -0
  430. package/scripts/layer-legacy-frozen.d.mts +28 -0
  431. package/scripts/layer-legacy-frozen.mjs +152 -0
  432. package/scripts/layer-legacy-frozen.test.ts +115 -0
  433. package/scripts/layer-legacy.frozen.json +50 -0
  434. package/scripts/mutation-batteries-frozen.harness.mjs +204 -0
  435. package/scripts/mutation-batteries-frozen.mjs +238 -0
  436. package/scripts/mutation-batteries.frozen.json +117 -0
  437. package/scripts/release-config.test.ts +90 -0
  438. package/scripts/rules-are-content-only.harness.mjs +113 -0
  439. package/scripts/rules-are-content-only.mjs +138 -0
  440. package/scripts/rules-are-content-only.mutations.mjs +81 -0
  441. package/scripts/rules-see-files.harness.mjs +115 -0
  442. package/scripts/rules-see-files.mjs +99 -0
  443. package/scripts/rules-see-files.mutations.mjs +131 -0
  444. package/scripts/run-mutations.mjs +100 -0
  445. package/scripts/semantic-release-plugins.d.ts +16 -0
  446. package/skills/README.md +15 -0
  447. package/skills/analyze-sibling-paper/SKILL.md +170 -0
  448. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +186 -0
  449. package/skills/analyze-sibling-paper/analyze-sibling-paper.eval.mjs +19 -0
  450. package/skills/analyze-sibling-paper/analyze-sibling-paper.harness.mjs +23 -0
  451. package/skills/argument-arc/SKILL.md +177 -0
  452. package/skills/argument-arc/SKILL.md.spec.ts +192 -0
  453. package/skills/argument-arc/argument-arc.eval.mjs +19 -0
  454. package/skills/argument-arc/argument-arc.harness.mjs +23 -0
  455. package/skills/build-benchmark/SKILL.md +213 -0
  456. package/skills/build-benchmark/SKILL.md.spec.ts +220 -0
  457. package/skills/build-benchmark/build-benchmark.eval.mjs +19 -0
  458. package/skills/build-benchmark/build-benchmark.harness.mjs +23 -0
  459. package/skills/build-benchmark/references/adversarial-cold-repro.md +68 -0
  460. package/skills/camera-ready/SKILL.md +148 -0
  461. package/skills/camera-ready/SKILL.md.spec.ts +164 -0
  462. package/skills/camera-ready/camera-ready.eval.mjs +19 -0
  463. package/skills/camera-ready/camera-ready.harness.mjs +23 -0
  464. package/skills/cold-read-diff/SKILL.md +160 -0
  465. package/skills/cold-read-diff/SKILL.md.spec.ts +166 -0
  466. package/skills/cold-read-diff/cold-read-diff.eval.mjs +19 -0
  467. package/skills/cold-read-diff/cold-read-diff.harness.mjs +23 -0
  468. package/skills/draft-paper/SKILL.md +152 -0
  469. package/skills/draft-paper/SKILL.md.spec.ts +169 -0
  470. package/skills/draft-paper/draft-paper.eval.mjs +19 -0
  471. package/skills/draft-paper/draft-paper.harness.mjs +23 -0
  472. package/skills/extend-paper/SKILL.md +99 -0
  473. package/skills/extend-paper/SKILL.md.spec.ts +116 -0
  474. package/skills/extend-paper/extend-paper.eval.mjs +19 -0
  475. package/skills/extend-paper/extend-paper.harness.mjs +23 -0
  476. package/skills/find-venue/SKILL.md +128 -0
  477. package/skills/find-venue/SKILL.md.spec.ts +145 -0
  478. package/skills/find-venue/find-venue.eval.mjs +19 -0
  479. package/skills/find-venue/find-venue.harness.mjs +23 -0
  480. package/skills/grade-paper-writing/SKILL.md +436 -0
  481. package/skills/grade-paper-writing/SKILL.md.spec.ts +453 -0
  482. package/skills/grade-paper-writing/fixtures/control_gopen.txt +1 -0
  483. package/skills/grade-paper-writing/fixtures/control_human_paper.txt +1 -0
  484. package/skills/grade-paper-writing/fixtures/rewrite.txt +1 -0
  485. package/skills/grade-paper-writing/fixtures/specimen.txt +1 -0
  486. package/skills/grade-paper-writing/fixtures/structure-checks.md +22 -0
  487. package/skills/grade-paper-writing/grade-paper-writing.eval.mjs +19 -0
  488. package/skills/grade-paper-writing/grade-paper-writing.harness.mjs +23 -0
  489. package/skills/grade-paper-writing/prose-lint.mjs +713 -0
  490. package/skills/harden-paper/SKILL.md +318 -0
  491. package/skills/harden-paper/SKILL.md.spec.ts +336 -0
  492. package/skills/harden-paper/check-numbers.sh +33 -0
  493. package/skills/harden-paper/check-release-claims.sh +35 -0
  494. package/skills/harden-paper/fixtures/uncited-assertions-sample.md +43 -0
  495. package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +77 -0
  496. package/skills/harden-paper/harden-paper.eval.mjs +19 -0
  497. package/skills/harden-paper/harden-paper.harness.mjs +23 -0
  498. package/skills/map-prior-work/SKILL.md +211 -0
  499. package/skills/map-prior-work/SKILL.md.spec.ts +227 -0
  500. package/skills/map-prior-work/map-prior-work.eval.mjs +19 -0
  501. package/skills/map-prior-work/map-prior-work.harness.mjs +23 -0
  502. package/skills/osf-artifact-upload/SKILL.md +52 -0
  503. package/skills/osf-artifact-upload/SKILL.md.spec.ts +59 -0
  504. package/skills/osf-artifact-upload/osf-artifact-upload.eval.mjs +22 -0
  505. package/skills/osf-artifact-upload/osf-artifact-upload.harness.mjs +103 -0
  506. package/skills/paper-adversarial-review/SKILL.md +126 -0
  507. package/skills/paper-adversarial-review/SKILL.md.spec.ts +142 -0
  508. package/skills/paper-adversarial-review/paper-adversarial-review.eval.mjs +19 -0
  509. package/skills/paper-adversarial-review/paper-adversarial-review.harness.mjs +23 -0
  510. package/skills/paper-pipeline/PIPELINE-MAP.md +371 -0
  511. package/skills/paper-pipeline/SKILL.md +499 -0
  512. package/skills/paper-pipeline/SKILL.md.spec.ts +517 -0
  513. package/skills/paper-pipeline/description-language.eval.mjs +347 -0
  514. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +891 -0
  515. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1254 -0
  516. package/skills/paper-pipeline/paper-pipeline.eval.mjs +22 -0
  517. package/skills/paper-pipeline/paper-pipeline.harness.mjs +143 -0
  518. package/skills/paper-pipeline/pipeline-firing.baseline.json +270 -0
  519. package/skills/paper-pipeline/pipeline-firing.eval.mjs +664 -0
  520. package/skills/paper-pipeline/pipeline-language.eval.mjs +672 -0
  521. package/skills/paper-pipeline/references/acceptance-gate.md +329 -0
  522. package/skills/paper-pipeline/references/acl-venue-rules.md +142 -0
  523. package/skills/paper-pipeline/references/anonymization.md +68 -0
  524. package/skills/paper-pipeline/references/artifact-checklist.md +93 -0
  525. package/skills/paper-pipeline/references/body-vs-appendix.md +97 -0
  526. package/skills/paper-pipeline/references/credit-criteria.md +69 -0
  527. package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/README.md +35 -0
  528. package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/run_retext.mjs +24 -0
  529. package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/sentences.txt +11 -0
  530. package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/test_sentences.py +25 -0
  531. package/skills/paper-pipeline/references/occupancy-2026-08-06-prose-checkers.md +538 -0
  532. package/skills/paper-pipeline/references/occupancy-2026-08-06-reproducible-tooling.md +431 -0
  533. package/skills/paper-pipeline/references/occupancy-2026-08-06-staleness-and-orchestration.md +592 -0
  534. package/skills/paper-pipeline/references/pipeline-status-template.md +162 -0
  535. package/skills/paper-pipeline/references/review-ratchet.md +36 -0
  536. package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +585 -0
  537. package/skills/paper-pipeline/references/writing-craft.md +448 -0
  538. package/skills/paper-pipeline/repro/2026-08-07-description-language-control.log +63 -0
  539. package/skills/paper-pipeline/repro/2026-08-07-fork-check.log +52 -0
  540. package/skills/paper-pipeline/repro/2026-08-07-fork-check2.log +33 -0
  541. package/skills/paper-pipeline/repro/2026-08-07-language-eval-pilot.log +33 -0
  542. package/skills/paper-pipeline/repro/2026-08-07-language-eval-raw.log +166 -0
  543. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.json +338 -0
  544. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.log +118 -0
  545. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-raw.log +245 -0
  546. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary.json +776 -0
  547. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-oracle.log +53 -0
  548. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-raw.log +89 -0
  549. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.json +450 -0
  550. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.log +136 -0
  551. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-raw.log +242 -0
  552. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-setupdiff.log +59 -0
  553. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation.json +1032 -0
  554. package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.json +139 -0
  555. package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.log +98 -0
  556. package/skills/paper-pipeline/repro/2026-08-08-parent-replication.mjs +92 -0
  557. package/skills/paper-pipeline/repro/README.md +129 -0
  558. package/skills/paper-pipeline/repro/analyze-language-eval.py +116 -0
  559. package/skills/paper-pipeline/scripts/README.md +344 -0
  560. package/skills/paper-pipeline/scripts/announce.mjs +67 -0
  561. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +496 -0
  562. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +397 -0
  563. package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +218 -0
  564. package/skills/paper-pipeline/scripts/check-provenance.mjs +184 -0
  565. package/skills/paper-pipeline/scripts/consumer.d.mts +32 -0
  566. package/skills/paper-pipeline/scripts/consumer.harness.mjs +562 -0
  567. package/skills/paper-pipeline/scripts/consumer.mjs +535 -0
  568. package/skills/paper-pipeline/scripts/consumer.mutations.mjs +190 -0
  569. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +457 -0
  570. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +656 -0
  571. package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +54 -0
  572. package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +51 -0
  573. package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +52 -0
  574. package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +6 -0
  575. package/skills/paper-pipeline/scripts/fixtures/real-bib/refs.bib +153 -0
  576. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +466 -0
  577. package/skills/paper-pipeline/scripts/generated-code.mjs +338 -0
  578. package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +254 -0
  579. package/skills/paper-pipeline/scripts/ledger.mjs +623 -0
  580. package/skills/paper-pipeline/scripts/ledger.selftest.mjs +286 -0
  581. package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +389 -0
  582. package/skills/paper-pipeline/scripts/pipeline-check.mjs +737 -0
  583. package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +54 -0
  584. package/skills/paper-pipeline/scripts/pipeline-edges.mjs +169 -0
  585. package/skills/paper-pipeline/scripts/population-map.harness.mjs +178 -0
  586. package/skills/paper-pipeline/scripts/population-map.mjs +181 -0
  587. package/skills/paper-pipeline/scripts/population-map.mutations.mjs +65 -0
  588. package/skills/paper-pipeline/scripts/population-map.selftest.mjs +122 -0
  589. package/skills/paper-pipeline/scripts/provenance.harness.mjs +240 -0
  590. package/skills/paper-pipeline/scripts/provenance.mutations.mjs +59 -0
  591. package/skills/paper-pipeline/scripts/round-diff.harness.mjs +881 -0
  592. package/skills/paper-pipeline/scripts/round-diff.mjs +576 -0
  593. package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +276 -0
  594. package/skills/paper-pipeline/scripts/run-mechanical.mjs +633 -0
  595. package/skills/paper-pipeline/scripts/status.mjs +295 -0
  596. package/skills/paper-status/SKILL.md +183 -0
  597. package/skills/paper-status/SKILL.md.spec.ts +190 -0
  598. package/skills/paper-status/paper-status.eval.mjs +22 -0
  599. package/skills/paper-status/paper-status.harness.mjs +25 -0
  600. package/skills/pc-panel-review/SKILL.md +263 -0
  601. package/skills/pc-panel-review/SKILL.md.spec.ts +280 -0
  602. package/skills/pc-panel-review/pc-panel-review.eval.mjs +19 -0
  603. package/skills/pc-panel-review/pc-panel-review.harness.mjs +23 -0
  604. package/skills/plan-paper-timeline/SKILL.md +182 -0
  605. package/skills/plan-paper-timeline/SKILL.md.spec.ts +200 -0
  606. package/skills/plan-paper-timeline/fixtures/fake-google-calendar.mjs +239 -0
  607. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +431 -0
  608. package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +65 -0
  609. package/skills/plan-paper-timeline/plan-paper-timeline.eval.mjs +19 -0
  610. package/skills/plan-paper-timeline/plan-paper-timeline.harness.mjs +23 -0
  611. package/skills/render-paper/SKILL.md +159 -0
  612. package/skills/render-paper/SKILL.md.spec.ts +166 -0
  613. package/skills/render-paper/check-render.sh +419 -0
  614. package/skills/render-paper/checkers-requirements.txt +55 -0
  615. package/skills/render-paper/ensure-checkers.sh +69 -0
  616. package/skills/render-paper/extract-pdf-facts.harness.mjs +166 -0
  617. package/skills/render-paper/extract-pdf-facts.mjs +144 -0
  618. package/skills/render-paper/render-paper.eval.mjs +19 -0
  619. package/skills/render-paper/render-paper.harness.mjs +339 -0
  620. package/skills/research-ideate/SKILL.md +136 -0
  621. package/skills/research-ideate/SKILL.md.spec.ts +152 -0
  622. package/skills/research-ideate/research-ideate.eval.mjs +19 -0
  623. package/skills/research-ideate/research-ideate.harness.mjs +23 -0
  624. package/skills/skill-contract.mutations.mjs +179 -0
  625. package/skills/study-accepted-papers/SKILL.md +206 -0
  626. package/skills/study-accepted-papers/SKILL.md.spec.ts +223 -0
  627. package/skills/study-accepted-papers/study-accepted-papers.eval.mjs +19 -0
  628. package/skills/study-accepted-papers/study-accepted-papers.harness.mjs +23 -0
  629. package/skills/submit-paper/SKILL.md +182 -0
  630. package/skills/submit-paper/SKILL.md.spec.ts +199 -0
  631. package/skills/submit-paper/check-deanon.sh +149 -0
  632. package/skills/submit-paper/references/publishers/acm.md +92 -0
  633. package/skills/submit-paper/references/venues/agenticdev.jsonc +108 -0
  634. package/skills/submit-paper/references/venues/agenticdev.md +139 -0
  635. package/skills/submit-paper/references/venues/agenticdev.tex +19 -0
  636. package/skills/submit-paper/references/venues/aisec.jsonc +101 -0
  637. package/skills/submit-paper/references/venues/aisec.md +105 -0
  638. package/skills/submit-paper/references/venues/paper-guards.tex +41 -0
  639. package/skills/submit-paper/references/venues/realm.jsonc +81 -0
  640. package/skills/submit-paper/references/venues/realm.md +155 -0
  641. package/skills/submit-paper/references/venues/tex-base.jsonc +50 -0
  642. package/skills/submit-paper/references/venues/venue-profile.schema.json +74 -0
  643. package/skills/submit-paper/submit-paper.eval.mjs +19 -0
  644. package/skills/submit-paper/submit-paper.harness.mjs +23 -0
  645. package/skills/sweep-design-space/SKILL.md +269 -0
  646. package/skills/sweep-design-space/SKILL.md.spec.ts +285 -0
  647. package/skills/sweep-design-space/sweep-design-space.eval.mjs +19 -0
  648. package/skills/sweep-design-space/sweep-design-space.harness.mjs +23 -0
  649. package/skills/tighten-paper/SKILL.md +368 -0
  650. package/skills/tighten-paper/SKILL.md.spec.ts +384 -0
  651. package/skills/tighten-paper/structure.mjs +371 -0
  652. package/skills/tighten-paper/tighten-paper.eval.mjs +19 -0
  653. package/skills/tighten-paper/tighten-paper.harness.mjs +23 -0
  654. package/skills/verify-citations/SKILL.md +328 -0
  655. package/skills/verify-citations/SKILL.md.spec.ts +345 -0
  656. package/skills/verify-citations/scripts/bib-authors.mjs +479 -0
  657. package/skills/verify-citations/scripts/bib-authors.test.mjs +175 -0
  658. package/skills/verify-citations/scripts/verify-cites.mjs +1108 -0
  659. package/skills/verify-citations/scripts/verify-cites.test.mjs +735 -0
  660. package/skills/verify-citations/verify-citations.eval.mjs +19 -0
  661. package/skills/verify-citations/verify-citations.harness.mjs +23 -0
  662. package/src/CLAUDE.md +51 -0
  663. package/src/action-ref.test.ts +26 -0
  664. package/src/action-ref.ts +15 -0
  665. package/src/adapters/banal/failure.test.ts +63 -0
  666. package/src/adapters/banal/failure.ts +118 -0
  667. package/src/adapters/banal/index.test.ts +119 -0
  668. package/src/adapters/banal/index.ts +100 -0
  669. package/src/adapters/banal/install.test.ts +20 -0
  670. package/src/adapters/banal/install.ts +41 -0
  671. package/src/adapters/banal/invocation.test.ts +74 -0
  672. package/src/adapters/banal/invocation.ts +95 -0
  673. package/src/adapters/banal/locate.test.ts +52 -0
  674. package/src/adapters/banal/locate.ts +84 -0
  675. package/src/adapters/banal/output.test.ts +140 -0
  676. package/src/adapters/banal/output.ts +141 -0
  677. package/src/adapters/banal/pin.ts +30 -0
  678. package/src/adapters/banal/probe.ts +35 -0
  679. package/src/adapters/banal/run.test.ts +191 -0
  680. package/src/adapters/banal/run.ts +244 -0
  681. package/src/adapters/banal/settings.test.ts +31 -0
  682. package/src/adapters/banal/settings.ts +55 -0
  683. package/src/adapters/banal/xml.test.ts +111 -0
  684. package/src/adapters/banal/xml.ts +112 -0
  685. package/src/adapters/curl/download.io.ts +73 -0
  686. package/src/adapters/curl/download.test.ts +55 -0
  687. package/src/adapters/curl/index.ts +5 -0
  688. package/src/adapters/memory/index.ts +131 -0
  689. package/src/adapters/node/files.io.ts +39 -0
  690. package/src/adapters/node/files.test.ts +28 -0
  691. package/src/adapters/node/host.io.ts +15 -0
  692. package/src/adapters/node/index.ts +36 -0
  693. package/src/adapters/node/process.io.ts +49 -0
  694. package/src/adapters/node/process.test.ts +46 -0
  695. package/src/adapters/node/workspace.io.ts +40 -0
  696. package/src/adapters/node/workspace.test.ts +58 -0
  697. package/src/adapters/pdfjs/fill.test.ts +111 -0
  698. package/src/adapters/pdfjs/fill.ts +141 -0
  699. package/src/build-engine.harness.mjs +314 -0
  700. package/src/build-engine.ts +219 -0
  701. package/src/build.harness.mjs +631 -0
  702. package/src/build.mutations.mjs +195 -0
  703. package/src/build.ts +793 -0
  704. package/src/cli.harness.mjs +2007 -0
  705. package/src/cli.mutations.mjs +448 -0
  706. package/src/cli.ts +1189 -0
  707. package/src/doctor.harness.mjs +396 -0
  708. package/src/doctor.mutations.mjs +175 -0
  709. package/src/doctor.ts +356 -0
  710. package/src/domain/geometry.ts +108 -0
  711. package/src/domain/host.ts +23 -0
  712. package/src/domain/page-layout.ts +32 -0
  713. package/src/domain/paths.ts +5 -0
  714. package/src/domain/result.test.ts +26 -0
  715. package/src/domain/result.ts +29 -0
  716. package/src/domain/sha256.test.ts +12 -0
  717. package/src/domain/sha256.ts +21 -0
  718. package/src/domain/text.ts +11 -0
  719. package/src/engine.harness.mjs +252 -0
  720. package/src/engine.ts +176 -0
  721. package/src/exit-code.test.ts +21 -0
  722. package/src/exit-code.ts +38 -0
  723. package/src/facts-file.test.ts +240 -0
  724. package/src/facts-file.ts +241 -0
  725. package/src/hooks-settings.harness.mjs +386 -0
  726. package/src/hooks-settings.mutations.mjs +116 -0
  727. package/src/hooks-settings.ts +434 -0
  728. package/src/init.ts +900 -0
  729. package/src/latex-log.harness.mjs +226 -0
  730. package/src/latex-log.ts +234 -0
  731. package/src/latex-loop.harness.mjs +449 -0
  732. package/src/latex-loop.ts +211 -0
  733. package/src/link-skills.harness.mjs +273 -0
  734. package/src/link-skills.mutations.mjs +136 -0
  735. package/src/link-skills.ts +258 -0
  736. package/src/new-paper.harness.mjs +216 -0
  737. package/src/new-paper.mutations.mjs +79 -0
  738. package/src/new-paper.ts +158 -0
  739. package/src/pdf-facts.harness.mjs +188 -0
  740. package/src/pdf-facts.ts +327 -0
  741. package/src/pdf-geometry.harness.mjs +254 -0
  742. package/src/pdf-geometry.ts +300 -0
  743. package/src/ports/download.ts +10 -0
  744. package/src/ports/files.ts +11 -0
  745. package/src/ports/measure-geometry.ts +8 -0
  746. package/src/ports/process.ts +46 -0
  747. package/src/ports/tool-installer.ts +33 -0
  748. package/src/ports/workspace.ts +20 -0
  749. package/src/rules-config.harness.mjs +114 -0
  750. package/src/rules-config.ts +178 -0
  751. package/src/structure.harness.mjs +179 -0
  752. package/src/structure.mutations.mjs +83 -0
  753. package/src/structure.ts +166 -0
  754. package/src/tex-requirements.harness.mjs +238 -0
  755. package/src/tex-requirements.ts +181 -0
  756. package/src/toolchain.harness.mjs +651 -0
  757. package/src/toolchain.ts +755 -0
  758. package/src/types.ts +106 -0
  759. package/templates/paper/PIPELINE-STATUS.md +72 -0
  760. package/templates/paper/paper.md +4 -0
  761. package/templates/paper/paper.tex +8 -0
  762. package/tsconfig.json +23 -0
@@ -0,0 +1,110 @@
1
+ import type { RuleBlock } from "./rules-config.ts";
2
+ /**
3
+ * The shapes the CLI commands exchange. Before the move to TypeScript this was all `{}` and lived
4
+ * in someone's head: a typo in a config key read as "field not set", not as an error.
5
+ */
6
+ /** The contents of the consumer's `rpp.json`. */
7
+ export interface RppConfig {
8
+ /** 🔴 REQUIRED. The paper director(ies), relative to the config file ITSELF. The field name is
9
+ * `PAPERS_DIR_FIELD` in lib/paper-config.mjs; code reads it through `papersDirOf()` in cli.ts. */
10
+ papersDir?: string | string[];
11
+ /** The command that prints the author list from .bib — each corpus has its own. */
12
+ authorListCommand?: string;
13
+ /** Per-paper typography debt: how many findings already exist; the number may only go down. */
14
+ typographyDebt?: Record<string, Record<string, number>>;
15
+ /** Frontmatter fields required for review notes, and the permitted values of each. */
16
+ docFields?: Record<string, {
17
+ values: string[];
18
+ }>;
19
+ /** Ignore review findings older than this date. */
20
+ reviewSince?: string;
21
+ /** How many findings a cold read must produce to count as a cold read. */
22
+ minFindings?: number;
23
+ /** The word with which review notes introduce the cause. */
24
+ causeMarker?: string;
25
+ /** Which files a paper directory must carry; `false` turns the check off entirely. */
26
+ structure?: StructureConfig | false;
27
+ /** REMOVED — `paperlint build` compiles the paper itself. Still typed so a leftover key can be named. */
28
+ buildScripts?: unknown;
29
+ /** ESLint config blocks appended after rpp's own — PARSED by `parseSettings` in cli.ts. */
30
+ rules?: readonly RuleBlock[];
31
+ }
32
+ export interface StructureConfig {
33
+ /** These files are what IDENTIFY a directory as a paper. Detection is generous. */
34
+ markers?: string[];
35
+ /** These files must exist. The requirements are strict. */
36
+ require?: string[];
37
+ /** At least one from each group. */
38
+ requireOneOf?: string[][];
39
+ /** Directories exempted from the check by name. */
40
+ ignore?: string[];
41
+ }
42
+ /** The parsed command line. */
43
+ export interface Args {
44
+ cmd: string | null;
45
+ paths: string[];
46
+ config: string | null;
47
+ json: boolean;
48
+ all: boolean;
49
+ dryRun: boolean;
50
+ /** `--check`: `toolchain` reports what is missing and changes nothing. */
51
+ check: boolean;
52
+ maxWarnings: number;
53
+ /** `--yes` / `-y`: take every default, ask nothing. */
54
+ yes: boolean;
55
+ /** `--no-hooks`: `init` does not touch `.claude/settings.json`. */
56
+ noHooks: boolean;
57
+ /** `--paper <name>`: `init` creates this paper. */
58
+ paper: string | null;
59
+ /** `--format tex|md` for `new` and `init --paper`. Validated by the command, not here. */
60
+ format: string | null;
61
+ /** `--hooks=<mode>` — parsed only so it can be REFUSED by name rather than read as a path. */
62
+ hooksMode: string | null;
63
+ help?: boolean;
64
+ /** A flag that turned out to have no value. A non-empty field is a REFUSAL, not a default. */
65
+ missingValue?: string;
66
+ }
67
+ /** A finding about the PRESENCE of a file — what an ESLint rule cannot express. */
68
+ export interface StructureFinding {
69
+ file: string;
70
+ message: string;
71
+ }
72
+ /** One line of a build plan: a step, whether it applies to this paper, and why. */
73
+ export interface PlanLine {
74
+ step: string;
75
+ applies: boolean;
76
+ /** A required step that does not apply refuses the build instead of being skipped. */
77
+ required: boolean;
78
+ why: string;
79
+ }
80
+ /**
81
+ * The build outcome for one paper. `no-source` — there is no `paper.tex` to compile — is a
82
+ * REFUSAL, not a skip: "nothing to build" and "built" must never look alike.
83
+ */
84
+ export interface BuildResult {
85
+ dir: string;
86
+ status: "built" | "failed" | "no-source";
87
+ plan: PlanLine[];
88
+ /** `--dry-run`: the plan was printed and nothing ran. */
89
+ dry?: boolean;
90
+ /** The step that failed and what to show for it. */
91
+ failure?: {
92
+ step: string;
93
+ lines: string[];
94
+ };
95
+ /** What the steps reported on success: pass counts, undefined-reference warnings. */
96
+ notes?: string[];
97
+ /** A paper.pdf from an earlier run was on disk and `buildPapers` removed it before building. */
98
+ staleRemoved?: boolean;
99
+ }
100
+ /** The result of reading the config: either data, or the exit code the caller exits with. */
101
+ export type ConfigRead = {
102
+ opts: RppConfig;
103
+ configPath: string | null;
104
+ code?: undefined;
105
+ } | {
106
+ code: number;
107
+ opts?: undefined;
108
+ configPath?: undefined;
109
+ };
110
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;GAGG;AAEH,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACxB;sGACkG;IAClG,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACjD,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,SAAS,CAAC,EAAE,eAAe,GAAG,KAAK,CAAC;IACpC,yGAAyG;IACzG,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;IAC1B,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,+BAA+B;AAC/B,MAAM,WAAW,IAAI;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,GAAG,EAAE,OAAO,CAAC;IACb,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0FAA0F;IAC1F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mFAAmF;AACnF,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;IACzC,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,yDAAyD;IACzD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC5C,qFAAqF;IACrF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gGAAgG;IAChG,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,UAAU,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,235 @@
1
+ # Configuration reference
2
+
3
+ One key in your `package.json`, written by `paperlint init`. It holds the facts only your repository can
4
+ supply — nothing in it is guessable by a package that has never seen your corpus.
5
+
6
+ The README carries the minimal version of this. Everything below is the full surface, moved out on
7
+ 2026-09-19.
8
+
9
+ ```json
10
+ {
11
+ "paperlint": {
12
+ "papersDir": "papers",
13
+ "authorListCommand": "node scripts/bib-authors.mjs",
14
+ "typographyDebt": { "papers/my-paper": { "sectionSign": 12 } },
15
+ "docFields": { "read": { "values": ["full", "abstract", "none"] } },
16
+ "reviewSince": "2026-08-23",
17
+ "minFindings": 3,
18
+ "causeMarker": "Cause:",
19
+ "rules": [
20
+ {
21
+ "files": ["papers/agenticdev-2026/**"],
22
+ "rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
23
+ }
24
+ ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ | key | required | what it is |
30
+ | ------------------- | -------- | ----------------------------------------------------------------------------------------- |
31
+ | `papersDir` | **yes** | the directory your papers live in, relative to the file holding it. One string or a list. |
32
+ | `structure` | no | which files every paper directory must contain — see below. `false` turns it off. |
33
+ | `authorListCommand` | no | the command `paper/author-list` tells you to run when the check is missing |
34
+ | `typographyDebt` | no | per-paper allowance of existing typography findings, so the count can only go down |
35
+ | `docFields` | no | required front-matter fields in review files, and the values each may hold |
36
+ | `reviewSince` | no | only review files created on or after this date are checked |
37
+ | `minFindings` | no | a review with fewer findings than this is not required to name causes |
38
+ | `causeMarker` | no | the phrase a review uses to introduce a cause (default `Cause:`), in any language |
39
+ | `rules` | no | extra ESLint config blocks: turn optional rules on, change a rule's severity — see below |
40
+
41
+ The skill scripts read a few more keys of the same object — `ledger`, `scripts`, `timezone`,
42
+ `contactEmail`, `citeChecks`, `triggerCases` — documented with the skills that use them.
43
+
44
+ **Any other key is an error**, named in the message: `package.json → "paperlint":
45
+ unknown key "typographyDept"`. A misspelt key would otherwise read as "not set", and the setting
46
+ you meant would silently do nothing. The list of known keys is `SETTINGS_KEYS` in
47
+ `lib/paper-config.mjs`.
48
+
49
+ `papersDir` is required because the scope is the one thing that must not default: a default of `"."`
50
+ turns every run into a green report over the whole checkout. `paperlint init` fills it by measuring —
51
+ and when nothing on disk looks like a papers directory, it writes the documented default and says
52
+ in the same breath that it is a guess.
53
+
54
+ Until 2026-09-24 this field was called `papers`. The old name is not read as a fallback: `paperlint lint`,
55
+ `paperlint init`, `paperlint doctor`, the ESLint helper and the edit guard all stop with
56
+ `"papers" was renamed to "papersDir" in package.json → "paperlint"`. The two advisory
57
+ hooks stay silent instead. The name is defined once, as `PAPERS_DIR_FIELD` in
58
+ `lib/paper-config.mjs`.
59
+
60
+ ## Why the key lives in `package.json`
61
+
62
+ Because of a count: the `package.json` key has **five** readers — the three editor hooks, the
63
+ ESLint helper, the skill scripts — and a separate config file had **one**, the CLI. A hook cannot
64
+ import code and cannot walk up a tree looking for a config; it can read a path it is able to name,
65
+ and the one path it can always name is the project's `package.json`.
66
+
67
+ `paperlint lint` looks for it in the current directory and then upwards, the way eslint and tsc find
68
+ theirs, and prints which file it found. `--config <file>` overrides the search.
69
+
70
+ ⚠️ **`rpp.json` is deprecated and still read.** Earlier versions of `init` created it; `init` no
71
+ longer does, and a run that reads one says so on its first line. The hooks never read it, so
72
+ leaving settings there is how the linter and the guard end up watching different directories —
73
+ `paperlint init` copies the value across for you.
74
+
75
+ ## The `rules` key: turning rules on and off
76
+
77
+ `rules` is a list of blocks in ESLint's own
78
+ [flat-config shape](https://eslint.org/docs/latest/use/configure/configuration-files), limited to
79
+ the three keys that make sense in JSON — `files`, `ignores` and `rules`. paperlint appends the blocks
80
+ **after** its own configuration, so, as in ESLint, a later block wins: a block can turn on a rule
81
+ that is off by default, or change the severity of one that is on.
82
+
83
+ ```json
84
+ "rules": [
85
+ {
86
+ "files": ["papers/agenticdev-2026/**"],
87
+ "rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
88
+ },
89
+ {
90
+ "files": ["papers/old-draft/**"],
91
+ "rules": { "paper/typography": "off" }
92
+ }
93
+ ]
94
+ ```
95
+
96
+ - **`files` and `ignores` are globs relative to the file that holds the settings** — the
97
+ directory of your `package.json` — exactly as ESLint resolves them relative to its config file,
98
+ whatever directory you run `paperlint lint` from. A block without `files` applies to every linted file.
99
+ A pattern ending in `/**` is the usual way to name one paper.
100
+ - **A rule entry** is a severity (`"off"`, `"warn"`, `"error"`, or `0`/`1`/`2`), or a list whose
101
+ first element is a severity and the rest are the rule's options.
102
+ - **Only rules paperlint ships can be named** — the ones in [`docs/rules.md`](rules.md) and
103
+ [`docs/optional-rules.md`](optional-rules.md). A rule id paperlint does not ship, a bad severity, a
104
+ `rules` that is not a list, or a block key other than `files`, `ignores` and `rules` stops the run
105
+ with a message naming the exact key, before anything is linted.
106
+ - **An optional rule you turned on must reach a paper.** If no linted `paper.tex` gets the rule —
107
+ usually a `files` glob with a typo — `paperlint lint` fails and says so: a rule that never runs
108
+ reports exactly like one that passed.
109
+
110
+ ## Required files
111
+
112
+ A rule runs on a file it was handed. A file that is missing is never handed to anything — so no
113
+ rule can report it, and a paper directory without `PIPELINE-STATUS.md` gets **zero** rules and a
114
+ clean report. `paperlint lint` therefore checks presence itself, before ESLint runs.
115
+
116
+ Detection is generous and requirements are strict, on purpose. A directory counts as a paper only
117
+ once it already holds one of the marker files, so `research/`, `plans/` and other neighbours in
118
+ the corpus are left alone; an error-level check that fires on a correct tree gets switched off,
119
+ and the real findings leave with it.
120
+
121
+ ```json
122
+ "structure": {
123
+ "markers": ["PIPELINE-STATUS.md", "paper.tex", "paper.md", "venue.json"],
124
+ "require": ["PIPELINE-STATUS.md"],
125
+ "requireOneOf": [["paper.tex", "paper.md"]],
126
+ "ignore": []
127
+ }
128
+ ```
129
+
130
+ Those are the defaults; you only write the block to change them. `paper.md` is still in them
131
+ because Markdown papers are deprecated but not yet removed
132
+ ([#57](https://github.com/zernie/research-paper-pipeline/issues/57)). They were measured against a
133
+ real five-paper corpus rather than chosen — it passes with zero findings, while adding
134
+ `paper.pdf` to `require` produces two findings on papers that are perfectly fine, which is why it
135
+ is not there.
136
+
137
+ This is the half [ls-lint](https://ls-lint.org/) cannot do. ls-lint judges the **names** of files
138
+ that exist; it has nothing to compare against for a file that does not. Use both: ls-lint for
139
+ "what is there is named right", this for "what must be there is there".
140
+
141
+ ## How `paperlint build` compiles a paper
142
+
143
+ `paperlint build <paper>` compiles `paper.tex` to `paper.pdf` itself, with TeX Live's `pdflatex` and
144
+ `bibtex` ([`docs/toolchain.md`](toolchain.md)). There is nothing to configure and no script to
145
+ write. It prints its plan first, one line per step, then runs it:
146
+
147
+ ```
148
+ papers/my-paper
149
+ inputs: TEXINPUTS += <paperlint>/skills/submit-paper/references/venues
150
+ compile: paper.tex (\documentclass[sigconf,screen]{acmart}, venue agenticdev)
151
+ measure: pdf.js → _build/paper.facts.json (facts for the lint rules; nothing is judged here)
152
+ ✓ paper.pdf — 4 pdflatex passes, 1 bibtex run; facts: _build/paper.facts.json, last page 621.5 / 264.8 pt
153
+ ```
154
+
155
+ - **inputs** — paperlint's own venue files (`paper-guards.tex`, `<venue>.tex`) are put on `TEXINPUTS`,
156
+ so `\input{paper-guards}` in a preamble resolves with no setup. The system tree still resolves
157
+ after them.
158
+ - **compile** — `pdflatex -interaction=nonstopmode -halt-on-error -file-line-error`, then `bibtex`
159
+ when the `.aux` names a bibliography, then pdflatex again until the `.aux`, `.toc`, `.out` and
160
+ `.bbl` stop changing and the log stops asking for a rerun. bibtex runs again only when the cited
161
+ keys or a `.bib` file changed. After that, one **final** pass defines `\finalpass`, which arms
162
+ the reference guards in `paper-guards.tex`: an undefined `\ref` or `\cite` fails the build
163
+ there instead of printing `??`. A document that still changes after five passes fails, naming
164
+ the file that kept changing.
165
+ - **measure** — after a green compile, the PDF is read with pdf.js and what it measures is written
166
+ to `_build/paper.facts.json`: the page count, every font the pages draw text with (and whether
167
+ its program is embedded, and whether it is Type 3), and the heights of the last page's two
168
+ columns — or why they were not measured (a stub page of a few lines, or a review build with
169
+ numbered lines). [banal](https://github.com/kohler/hotcrp/blob/master/src/banal) — installed by
170
+ `paperlint toolchain`, or a project's own `vendor/banal` or `$BANAL` — adds the page size, column count
171
+ and font sizes, measured from the same pdf.js read (no poppler; see
172
+ [`toolchain.md`](toolchain.md#page-geometry-banal-without-poppler)); without banal those fields
173
+ are `null` and the build says so. A PDF pdf.js cannot read fails the build. The file is
174
+ written by one function, which `skills/render-paper/extract-pdf-facts.mjs` also calls for PDFs
175
+ paperlint did not build. Keep `_build/` out of git: the facts carry the PDF's SHA-256, and a rule
176
+ refuses facts about a different PDF than the one on disk.
177
+
178
+ The build does **not** judge the layout. A balanced last page, a page limit, the fonts a venue
179
+ wants — those are verdicts about the finished PDF, and they belong to lint rules that can be
180
+ turned on per venue, given a severity and suppressed with a reason. paperlint once searched for a
181
+ `\balance` position itself and failed the build when none worked; that was removed on
182
+ 2026-09-24.
183
+
184
+ The class and its options and the venue in `venue.json` are read from the paper and shown in the
185
+ plan; later steps decide from them whether they apply.
186
+
187
+ **`paper.pdf` is deleted before anything runs**, for every targeted paper — before the TeX Live is
188
+ chosen and before the first step. So no outcome leaves an old PDF looking current: not a failed
189
+ build, not a run that stops because there is no TeX Live with the packages the papers need, not a
190
+ paper with no `paper.tex`. A green `✓ paper.pdf` therefore always means this run wrote it, and a
191
+ pdflatex that exits 0 without writing one (a document with no pages) is a failure:
192
+
193
+ ```
194
+ ✗ compile: pdflatex exited 0 but wrote no paper.pdf — does the document have any pages?
195
+ ```
196
+
197
+ **On failure** the command names the program that failed, quotes the first error line from the log
198
+ with its `l.NNN` source context, and says the PDF is gone — the same line on every path that ends
199
+ without a new one:
200
+
201
+ ```
202
+ ✗ compile: pdflatex exited with 1
203
+ ./paper.tex:6: Undefined control sequence.
204
+ l.6 Text before, then \undefinedmacro
205
+ {} after.
206
+ full log: papers/my-paper/paper.log
207
+ paper.pdf removed — a stale PDF must not pass for this build
208
+ ```
209
+
210
+ **A paper with no `paper.tex` is a FAILURE, not a skip.** "Nothing to build" and "built" must never
211
+ look alike: the corpus this came from once let a paper reach its venue without a single paper job
212
+ having run on it, because a missing build read as nothing to do.
213
+
214
+ `--dry-run` prints the plan and runs nothing — and deletes nothing, `paper.pdf` included.
215
+
216
+ ⚠️ **A `build.sh` or `repro/build-submission.sh` in the paper directory is IGNORED.** Earlier
217
+ versions ran it; `paperlint build` now says one line — `build.sh is ignored — paperlint builds the paper
218
+ itself` — and builds the paper itself. The `buildScripts` key is ignored the same way.
219
+ Why: [#59](https://github.com/zernie/research-paper-pipeline/issues/59).
220
+
221
+ ## Using the rules from an existing ESLint config
222
+
223
+ Under the hood `paperlint lint` builds an ESLint flat config and runs it. If your repository already
224
+ lints with ESLint, you can import the rule modules from `paperlint/eslint-rules/`
225
+ and wire them yourself; `bin/rpp.mjs` exports `buildConfig(options, texLanguage)` that returns
226
+ the exact config the CLI uses, so the shortest path is:
227
+
228
+ ```js
229
+ // eslint.config.mjs
230
+ import { buildConfig } from "paperlint/bin/rpp.mjs";
231
+ import { texLanguage } from "paperlint/eslint-rules/latex-language.mjs";
232
+ export default buildConfig({ minFindings: 3 }, texLanguage);
233
+ ```
234
+
235
+ Then point the CI action's `config` input at that file.
package/docs/e2e.md ADDED
@@ -0,0 +1,152 @@
1
+ # End-to-end tests
2
+
3
+ They are `test/e2e/install.mjs` and `test/e2e/build.mjs`. Both are
4
+ part of `npm run check`, so nobody has to remember to call them. `npm run test:e2e` runs just
5
+ these, install first; if the install e2e fails or is skipped, the build e2e does not run.
6
+
7
+ This page says what they prove, what they deliberately do not, and when a change owes a new one.
8
+
9
+ ## The line between a harness and an end-to-end run
10
+
11
+ Most of this package is tested by harnesses, described in [`CONTRIBUTING.md`](../CONTRIBUTING.md):
12
+ a rule is handed a file with a defect and must find it, then a clean file and must stay silent.
13
+ That is the right shape for a rule, and it cannot answer a different kind of question.
14
+
15
+ A harness runs **in this repository's process, against this repository's working tree**. It
16
+ imports the module directly. Every path resolves, every dependency is already installed, and the
17
+ file it reads is the file in `git`. An end-to-end run exists because all three of those are
18
+ assumptions that stop holding the moment somebody else installs the package.
19
+
20
+ | question | answered by |
21
+ | ------------------------------------------------------------------------------------ | --------------------------------------------------------- |
22
+ | does this rule find this defect, and stay quiet otherwise | harness |
23
+ | does the CLI pick the right script, with the right interpreter | harness (`src/build.harness.mjs` substitutes `spawnSync`) |
24
+ | does the tarball, installed into a tree that is not this one, contain what it claims | **e2e** |
25
+ | do the paths written inside a skill resolve where the package actually lands | **e2e** |
26
+ | did a PDF come out, and is it typeset in the fonts the venue requires | **e2e** |
27
+
28
+ The third and fourth rows are the ones a harness cannot reach even in principle: the defect only
29
+ exists once the code is somewhere else.
30
+
31
+ ## `test/e2e/install.mjs` — the package, installed
32
+
33
+ `test/e2e/install.mjs`. It runs `npm pack`, then installs the resulting tarball into a fresh
34
+ temporary tree, **under npm and under pnpm separately**, and drives the installed binary:
35
+
36
+ - the install itself finishes
37
+ - `paperlint --help` answers with zero
38
+ - `paperlint init` declares the papers directory in `package.json`, and does _not_ leave a second
39
+ carrier `rpp.json` behind
40
+ - `paperlint init` finishes with zero — its doctor found no discrepancy
41
+ - `paperlint init` wires the hooks into `.claude/settings.json` — the same commands `hooks.json`
42
+ publishes, once each — and says they need `npm install` in a fresh clone; a second `init`
43
+ leaves that file byte-identical
44
+ - `paperlint lint` passes the staged corpus
45
+ - `paperlint new demo` scaffolds a paper from the templates that shipped in the tarball, its own lint is
46
+ clean, and `paperlint lint` stays clean with it in the corpus
47
+ - `hooks.json` arrived and parses, and every hook command named in it resolves to a file
48
+ - every skill arrived, and every script path named inside a skill resolves **in the consumer**
49
+
50
+ The last two rows are the reason this file exists. A skill is a markdown document with paths in
51
+ it; nothing in this repository's own test run would notice that those paths only work here.
52
+
53
+ 🔴 **Two package managers, not one, and it is not belt-and-braces.** The wiring addresses the
54
+ runtime from the project root, and pnpm does not put transitive dependencies at the root. A layout
55
+ that works under npm can be dead under pnpm with no error anywhere. The same run also launches the
56
+ binary **directly** rather than through `node <path>`: under npm `.bin` holds a symlink, under
57
+ pnpm a shell wrapper, and calling `node bin` measures the caller's habit instead of the package.
58
+
59
+ ## `test/e2e/build.mjs` — a real `pdflatex`
60
+
61
+ `test/e2e/build.mjs`. It copies `fixtures/build-e2e/` — eight papers, none with a build script paperlint
62
+ would run — into a temporary tree, points a config at it, and runs `paperlint build --all`. paperlint compiles
63
+ each paper itself with the real `pdflatex` and `bibtex`; the artifacts are then measured with
64
+ paperlint's own pdf.js reader from `dist/`, and the fonts are cross-checked against the list of
65
+ programs pdfTeX writes into `paper.log`:
66
+
67
+ | fixture | what it is there to prove |
68
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
69
+ | `acmart` | a real `\documentclass{acmart}` source builds, and the PDF carries the class's **own** font families — not one family of the silent substitution |
70
+ | `fallback` | the same document with the fonts missing still **builds green**, and is **rejected** by the font check. The failure is in the artifact, not in the exit code |
71
+ | `cite` | the bibtex path: the PDF shows `[1]`, not `[?]`, and no `??`. Its `build.sh` would leave a trace if run — it must not run, and the run must say it is ignored |
72
+ | `guards` | `\input{paper-guards}` resolves with no configuration, because paperlint puts its own venues directory on `TEXINPUTS` |
73
+ | `unbalanced` | a two-column acmart paper whose last page comes out 621.5 / 264.8 pt builds **green** — the build does not judge the layout and rewrites nothing — while `_build/paper.facts.json` records both heights, and `paperlint lint` with `pdf/last-page-balance` turned on in `rules` reports the page |
74
+ | `broken` | a failing build names pdflatex and its exit code, quotes the error line and its `l.NNN` context, and deletes the stale `paper.pdf` planted before the run |
75
+ | `no-source` | a paper with no `paper.tex` is named separately, and the run as a whole is a failure |
76
+
77
+ The `fallback` row is the point of the whole file. `acmart.cls` checks for `libertine.sty`,
78
+ `zi4.sty` and `newtxmath.sty`, and failing to find any of them sets `\@ACM@newfontsfalse` and
79
+ typesets the paper in Computer Modern. The build is green, the PDF looks fine, the metrics differ,
80
+ and therefore so does the pagination. A submitted paper went out that way. **An exit code cannot
81
+ see it, so the content of the artifact is what gets measured.**
82
+
83
+ Before the fixtures, the run proves the refusal with no TeX at all: PATH holds `node` alone, the
84
+ cache directory is empty and `CI` is set, and `paperlint build` must exit 1 with one line naming
85
+ `npx paperlint toolchain` and the venue's packages, print no plan and create no PDF and no cache. It then
86
+ asks `paperlint build --dry-run` which TeX Live the real run will use; under `--strict` (CI) that must be
87
+ paperlint's own cache, because the runner has no other.
88
+
89
+ ## `test/e2e/toolchain.mjs` — real TeX Live, and only it
90
+
91
+ `paperlint toolchain` into `$RPP_TEXLIVE_DIR` against real CTAN; a second run must say "nothing to do"
92
+ within seconds; `--check` must exit 0; then the `acmart` fixture is built with PATH holding `node`
93
+ only, so no other TeX Live and no PDF tool can stand in, and the PDF must carry Libertine and Biolinum
94
+ and no Computer Modern face. Without `RPP_TEXLIVE_DIR` it is a declared skip: installing ~270 MB
95
+ into a home directory as a side effect of `npm run check` is the unasked install rule 11 forbids.
96
+ `src/toolchain.harness.mjs` covers the installer's logic (mirror fallback, archive check, time
97
+ limit, verification, idempotence) against a fake mirror on disk, without the network.
98
+
99
+ ## Skips are declared, never silent
100
+
101
+ A clone without TeX Live genuinely cannot run the build e2e; a machine without pnpm cannot run
102
+ half of the install e2e. Each says so and exits **77** — the skip code vigiles' own runner uses —
103
+ not zero, and `npm run check` reports it as `SKIPPED — not run, not passed`. In CI the same absence
104
+ means a broken environment, so `--strict` turns the skip into a failure.
105
+
106
+ A skipped step and a passed one look identical in a CI interface. That is the class this whole
107
+ package is written against, so it is not allowed to happen inside it either.
108
+
109
+ ## When a change owes an e2e
110
+
111
+ Not every change does. The trigger is countable — ask whether the change touches something that is
112
+ **only true after installation**:
113
+
114
+ - a new file or directory that has to arrive on the consumer's disk (a skill, a hook, a template)
115
+ - a path written inside a document rather than resolved by a module loader
116
+ - anything read through `package.json` fields — `bin`, `files`, `exports`
117
+ - a new external program the pipeline shells out to
118
+ - a new artifact the pipeline produces whose **content** can be wrong while the exit code is zero
119
+
120
+ If none of those apply, a harness beside the changed module is the right test, and an e2e run
121
+ would only make the suite slower without making it stricter.
122
+
123
+ ## What these two do NOT cover
124
+
125
+ Named here on purpose: a test suite that does not say where it stops is read as covering
126
+ everything.
127
+
128
+ - **macOS and Windows.** Locally these run on whatever machine you have. In CI `build-e2e` is a
129
+ matrix over `ubuntu-latest` and `macos-latest`; Windows is not run anywhere, and `paperlint toolchain`
130
+ refuses it.
131
+ - **The rules, on a real document.** `fixtures/real-markdown-paper/` holds a published article and
132
+ a recorded baseline of what the rules say about it. That is a **lint** fixture, driven by a
133
+ harness — it does not go through the installed package. Wiring it into the install corpus is
134
+ open work, not something already done.
135
+ - **The skills as behaviour.** The install run proves a skill arrived and that its paths resolve.
136
+ Whether the agent then does the right thing with it is a different measurement, and it belongs
137
+ to the harness tier of `vigiles`, not here.
138
+ - **Registry publication.** The package is on npm: `research-paper-pipeline@1.0.0` was published
139
+ on 2026-09-25 by `.github/workflows/release.yml`; from 2.0.0 it is published as `paperlint`, the
140
+ same way. The
141
+ e2e still covers the packlist through `npm pack`, which produces the same tarball `npm publish`
142
+ uploads; the registry round-trip — install from npm, then run — is still not run by any test.
143
+
144
+ ## The corpus
145
+
146
+ `stageCorpus()` in `test/e2e/install.mjs` writes a small paper by hand — a declared stage, its
147
+ PDF, its byte counts, the cross-check between them — and copies `fixtures/build-e2e/acmart` beside
148
+ it so the LaTeX rules see LaTeX rather than a placeholder.
149
+
150
+ ⚠️ **Every line of that corpus was written by somebody who already knew which rule would read it.**
151
+ It keeps the stage machinery honest and it cannot tell you anything about false positives. That is
152
+ what the real-article fixture is for, and why the two tiers are not substitutes.
@@ -0,0 +1,59 @@
1
+ # Incidents — the measurements the rules stand on
2
+
3
+ `CLAUDE.md` is loaded on every turn, so it carries **the claim and the remedy** and nothing
4
+ else. The measurement that produced a rule lives here, and is read only when someone argues
5
+ with the rule or wants to know how it was found.
6
+
7
+ Two things never move into this file: a measurement that **is** the rule (rule 5's table of
8
+ claims that fell to one command — without it the rule reads "measure things", which nobody
9
+ obeys), and evidence that already lives in a code comment beside the code it explains. Those
10
+ get a pointer, never a copy.
11
+
12
+ Entries are append-only and dated. A superseded entry is marked, not deleted.
13
+
14
+ ---
15
+
16
+ ## 2026-09-17 — four fixes proposed and withdrawn in one session (rule 9)
17
+
18
+ Each was proposed before the defect it claimed to fix had been exhibited, and each fell to a
19
+ single command:
20
+
21
+ | proposed | what one command showed instead |
22
+ | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
23
+ | a new `skip` API in the test runner | the existing classification had never been shown to be wrong |
24
+ | a `link` subcommand as the single install path | this package is consumed three ways, and a plugin cannot serve two of them |
25
+ | "one delivery channel instead of two" | the channels carry different things: eight ESLint imports and a composite action on one side, skills and hooks on the other |
26
+ | "symlinked skills are our own invention" | the documentation describes them, and third-party repositories install exactly that way |
27
+
28
+ Two of the four were demolished by an adversarial second pass **before** either was built. A
29
+ third pass over six claims about the plugin channel corrected four of them — including a count
30
+ attributed to the wrong file (110 was one script's occurrences; the prefix has 208 across 190
31
+ lines) and a "nothing is installed" claim that was false in the consumer, where 54 symlinks
32
+ make the path resolve.
33
+
34
+ Cost of the pattern: the four proposals took more of the session than the work they proposed.
35
+
36
+ ## 2026-09-17 — the install-location class, four silent breakages (rule 10)
37
+
38
+ One class — _code that knows where it is on disk_ — has broken this repository four times, and
39
+ not one of the four announced itself:
40
+
41
+ | when | what was assumed | how it failed |
42
+ | ---------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
43
+ | until 2026-08-15 | `resolve(HERE, "..", "skills")`, correct while the code lived beside the skills | a hash over a missing directory is stable, so every verdict read FRESH forever, with no error |
44
+ | 2026-09-12 | the same walk, after the move into a package | broke the same way, in the same silent direction |
45
+ | 2026-09-12 | the ledger defaults to a file beside its own module | inside `node_modules`, which `npm ci` deletes: appending SUCCEEDS, so rows look recorded until the next install removes them |
46
+ | 2026-09-17 | skills name their scripts by an install-specific path — 208 literals across 190 lines, in bodies and in `allowed-tools` | resolves through a symlink for one consumer and does not exist for another |
47
+
48
+ The first three are documented in the code they broke, and that is where the detail stays:
49
+ `skills/paper-pipeline/scripts/ledger.mjs` (the three rungs) and
50
+ `skills/paper-pipeline/scripts/consumer.mjs` (`ledgerPath`, three steps).
51
+
52
+ 🔴 The load-bearing observation is not the count. All four repetitions happened **while
53
+ comments explaining the exact hazard sat directly above the code** — which is why rule 10 owes
54
+ a lint rule and not another paragraph.
55
+
56
+ A fifth instance of the same class was measured the same day and is not yet fixed: the guard
57
+ in `ledgerPath` asks `insideNodeModules(hereDir)`, and a plugin cache is not `node_modules`, so
58
+ a plugin-only consumer without a `package.json` gets its ledger written into the cache — which
59
+ the documentation says changes when the plugin updates.