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,419 @@
1
+ #!/usr/bin/env bash
2
+ # check-render.sh — mechanical render-quality gate for a LaTeX paper.
3
+ #
4
+ # WHY: eyeballing page 1-2 misses layout defects on later pages. LaTeX already
5
+ # reports them in the .log ("Overfull \hbox (12.3pt too wide) at lines X--Y").
6
+ # This compiles the paper and FAILS on visible layout defects, so "renders fine"
7
+ # is a checked fact, not a hope. Spirit: prose isn't policy — compile the check.
8
+ #
9
+ # Usage: check-render.sh <paper-dir> [texbasename=paper] [threshold_pt=5]
10
+ # NO_COMPILE=1 check-render.sh <dir> ... # parse an existing .log, don't recompile
11
+ #
12
+ # Exit: 0 = clean; 1 = layout/ref defects found; 2 = usage/compile/log error.
13
+ # Threshold: hbox overfulls >= threshold_pt are "visible → must fix"; smaller ones
14
+ # are counted but not fatal (sub-point overfulls are usually invisible).
15
+
16
+ set -uo pipefail
17
+
18
+ # ── WHERE `paper-guards.tex` LIVES AND WHAT HAPPENS IF IT IS NOT FOUND ────────
19
+ # Papers pull in the reference guard with one line, `\input{paper-guards}`. The path is NOT
20
+ # spelled out there — LaTeX looks the file up via `TEXINPUTS`, and the caller must set that variable.
21
+ #
22
+ # 🔴 THE FAILURE MODE HERE IS SILENT, AND THE WHOLE FUNCTION EXISTS FOR THAT. An `\input` that is
23
+ # not found does NOT fail the build: LaTeX writes `File \`paper-guards.tex' not found` into the log
24
+ # and carries on. The PDF comes out, looks fine, and simply no longer contains the dangling-reference
25
+ # check — that is, the defect the guard was set up for comes back together with a green report.
26
+ # A broken `import` in JS fails loudly; here we have to fail on our own.
27
+ #
28
+ # A ladder of THREE candidates, because the directory MOVED (12.09): `venues/` is now carried by the
29
+ # `paperlint` package, and at the consumer's old location there is a symlink into it.
30
+ # The order "the declared package → a sibling inside the package → the consumer's directory" is
31
+ # deliberate: the package must win silently. It only gets loud when there is NOT A SINGLE ONE.
32
+ # 🔴 THE SCRIPT'S OWN DIRECTORY — TAKEN ABSOLUTE AND BEFORE ANY `cd` (12.09.2026, evening).
33
+ # Found by a RED HARNESS, `gates.harness.mjs`, not by proofreading, and it is my own regression from
34
+ # the same day: below, the script does `cd "$DIR"` into the paper directory, and the venues resolve
35
+ # stood AFTER that `cd`. So `git rev-parse --show-toplevel` was already running in the fixture's
36
+ # temporary directory, the root was not found, and the build honestly stopped — on a test that had
37
+ # been passing until then.
38
+ #
39
+ # ⚠️ The previous version was wrong THE SAME WAY, but silently: it did not check the result and built
40
+ # the PDF without the reference guard. That is, going red is not a new breakage but a manifestation
41
+ # of an old one; still, a test that was green yesterday and is red today is mine to fix.
42
+ #
43
+ # The neighbouring `build.sh` already carries this same lesson in a comment ("The script directory is
44
+ # taken ONCE and ABSOLUTE, before cd") — CI failed on it there on 30.08. A second instance of the same
45
+ # class in a file right next door.
46
+ SELF_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
47
+
48
+ resolve_venues() {
49
+ local root pkg
50
+ # Three sources for the root, from the most explicit to the most reliable. The last one is the
51
+ # SCRIPT's location (`.claude/skills/render-paper/` ⇒ three levels up), and it is the only one that
52
+ # depends neither on the current directory nor on whether the caller sits inside a git tree.
53
+ # ⚠️ THE PARENTHESES ARE MANDATORY, and the harness caught this a minute after I wrote the line
54
+ # without them. In bash `A || B && C` parses as `(A || B) && C`, so on a SUCCESSFUL `git rev-parse`
55
+ # the `pwd` ran as well, and the substitution returned TWO lines — the repository root plus the
56
+ # current directory. By hand from a directory outside git this did not reproduce: there git failed,
57
+ # and only one branch ran.
58
+ # ⚠️ THE LAST RUNG SEARCHES, IT DOES NOT COUNT LEVELS. `cd "$SELF_DIR/../../.."` used to stand here
59
+ # — correct for `.claude/skills/render-paper/` (three levels to the root) and silently wrong after
60
+ # the skill moved into the package, where it lies in `skills/render-paper/` (two): the climb
61
+ # overshot ABOVE the repository, and rung 2 looked for venues in someone else's directory. Climbing
62
+ # up to the directory that holds `.claude` does not depend on the depth.
63
+ local probe="$SELF_DIR" fallback=""
64
+ while [ "$probe" != "/" ]; do
65
+ if [ -d "$probe/.claude" ]; then fallback="$probe"; break; fi
66
+ probe=$(dirname "$probe")
67
+ done
68
+ root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || printf '%s' "$fallback")}"
69
+
70
+ # 1) the package, if it already carries venues/ (after step 5)
71
+ pkg=$(node -e 'try{process.stdout.write(require.resolve("paperlint/venues/paper-guards.tex"))}catch{}' 2>/dev/null || true)
72
+ if [ -n "$pkg" ] && [ -f "$pkg" ]; then
73
+ dirname "$pkg"
74
+ return 0
75
+ fi
76
+
77
+ # 2) THE SIBLING SKILL, from THIS script's directory. Added 12.09 together with the move of
78
+ # `submit-paper` into the package, and it is the only one of the three that works in BOTH worlds at
79
+ # once:
80
+ # • package checkout `skills/render-paper/..` → `skills/submit-paper/references/venues`
81
+ # • consumer `.claude/skills/render-paper/..` → `.claude/skills/submit-paper/...`
82
+ # (both directories are symlinks into the package, `-f` goes through them, and `cd`+`pwd`
83
+ # leaves the logical path)
84
+ # Rung 1 depends on cwd (`node -e` resolves from the current directory), rung 3 depends on the
85
+ # consumer's root having been found at all. This one depends on neither — exactly the anchor whose
86
+ # absence had `gates.harness.mjs` red on 12.09.
87
+ local sibling="$SELF_DIR/../submit-paper/references/venues"
88
+ if [ -f "$sibling/paper-guards.tex" ]; then
89
+ (cd "$sibling" && pwd)
90
+ return 0
91
+ fi
92
+
93
+ # 3) the skill directory in the consumer — for a consumer that mounted the skills differently
94
+ local local_dir="$root/.claude/skills/submit-paper/references/venues"
95
+ if [ -f "$local_dir/paper-guards.tex" ]; then
96
+ printf '%s\n' "$local_dir"
97
+ return 0
98
+ fi
99
+
100
+ return 1
101
+ }
102
+
103
+ # A separate mode so that the resolve can be CHECKED and not merely executed: without it the only
104
+ # way to learn where the script looks is to build the whole paper.
105
+ if [ "${1:-}" = "--print-venues" ]; then
106
+ if VD=$(resolve_venues); then
107
+ printf '%s\n' "$VD"
108
+ exit 0
109
+ fi
110
+ echo "✗ paper-guards.tex not found: not under the package name paperlint, not beside the script" >&2
111
+ echo " (skills/submit-paper/references/venues), not at the consumer (.claude/skills/submit-paper/references/venues)." >&2
112
+ echo " Building the paper without it is not allowed: \\input{paper-guards} will not fail, it will vanish silently," >&2
113
+ echo " and the PDF will be built WITHOUT the check for dangling \\ref and \\cite." >&2
114
+ exit 2
115
+ fi
116
+
117
+ DIR="${1:?usage: check-render.sh <paper-dir> [texbasename=paper] [threshold_pt=5]}"
118
+ BASE="${2:-paper}"
119
+ THRESH="${3:-5}"
120
+
121
+ cd "$DIR" || { echo "FAIL: cannot cd $DIR"; exit 2; }
122
+ LOG="$BASE.log"
123
+
124
+ # 🔴 THE CONDITION `-f "$BASE.tex"` WAS ADDED ON 26.08 — without it the branch "no log → exit 2"
125
+ # became UNREACHABLE, and that is my own regression from the same day. The final pass is invoked as
126
+ # `pdflatex -jobname="$BASE" '\input{...}'`, and that form creates `$BASE.log` EVEN when there is no
127
+ # source: the script found a log, reached the common `exit 1` and reported "FAIL: fix the findings"
128
+ # instead of the honest "no log, the build was never run".
129
+ #
130
+ # The measurement that isolates the cause (a directory without `paper.tex`):
131
+ # version before 22642552 → exit 2, only texput.log on disk
132
+ # version after → exit 1, paper.log AND texput.log on disk
133
+ # The difference is exactly `paper.log`, which my third pass writes.
134
+ if [ "${NO_COMPILE:-0}" != "1" ] && [ -f "$BASE.tex" ]; then
135
+ # Two passes: overfull boxes surface on every pass; a second pass settles refs
136
+ # so Reference/Citation-undefined warnings are accurate.
137
+ # 🔴 TEXINPUTS is mandatory, and that was found by a crash on 26.08. Papers pull in their venue's
138
+ # numbers via `\input{<venue>}`, and the file lies in `.claude/skills/submit-paper/references/venues/`.
139
+ # Without that path the build fails with `File \`agenticdev.tex' not found` — and the `|| true`
140
+ # below swallows it, after which the script reads a log of UNKNOWN origin and reports on it. That
141
+ # is, compiling this paper here silently did not work, and it only became visible once a pass
142
+ # WITHOUT `|| true` appeared.
143
+ if ! VENUES_DIR=$(resolve_venues); then
144
+ echo "✗ paper-guards.tex not found — the build is stopped BEFORE pdflatex." >&2
145
+ echo " Otherwise \\input{paper-guards} vanishes silently and the PDF comes out without the reference check." >&2
146
+ exit 2
147
+ fi
148
+ export TEXINPUTS="${VENUES_DIR}:${TEXINPUTS:-}"
149
+ # The second half: ask TeX itself whether it sees the file by that path. `resolve_venues` checked
150
+ # that the file is present on disk; `kpsewhich` checks that it is visible EXACTLY THE WAY `\input`
151
+ # will look for it — that is, taking TEXINPUTS, the ls-R cache and permissions into account.
152
+ if ! kpsewhich paper-guards.tex >/dev/null 2>&1; then
153
+ echo "✗ the directory was found ($VENUES_DIR), but kpsewhich does not see paper-guards.tex — TEXINPUTS did not take effect." >&2
154
+ exit 2
155
+ fi
156
+ pdflatex -interaction=nonstopmode "$BASE.tex" >/dev/null 2>&1 || true
157
+ pdflatex -interaction=nonstopmode "$BASE.tex" >/dev/null 2>&1 || true
158
+ # 🔴 THE FINAL pass with `\finalpass` — it activates the preamble guards, which need a `.aux` that
159
+ # has CONVERGED. Today that means "no dangling references and citations": LaTeX already knows this
160
+ # fact, and asking it is strictly stronger than grepping its diary afterwards (see the undefined
161
+ # block below — it stays as insurance for papers without a guard in the preamble).
162
+ #
163
+ # Why a separate pass and not a flag on all three: on the first pass the `.aux` is empty, every
164
+ # reference is undefined BY CONSTRUCTION. Measured 26.08 with the unconditional form: pass 1 fails →
165
+ # the `.aux` is not written → pass 2 fails on the same thing → the cycle NEVER CONVERGES.
166
+ #
167
+ # There is deliberately NO `|| true` here: if the guard fired, that is a finding, not noise.
168
+ pdflatex -interaction=nonstopmode -jobname="$BASE" '\def\finalpass{}\input{'"$BASE"'}' >/dev/null 2>&1
169
+ fi
170
+
171
+ [ -f "$LOG" ] || { echo "FAIL: no $DIR/$LOG (compile failed — run render-paper toolchain first)"; exit 2; }
172
+
173
+ fail=0
174
+ big=0
175
+ small=0
176
+
177
+ echo "== check-render: $DIR/$BASE (threshold ${THRESH}pt) =="
178
+
179
+ # --- Overfull \hbox (text/tables bleeding past the column/margin) ---
180
+ while IFS= read -r line; do
181
+ [ -z "$line" ] && continue
182
+ pt=""
183
+ if [[ $line =~ \(([0-9]+\.?[0-9]*)pt\ too\ wide\) ]]; then
184
+ pt="${BASH_REMATCH[1]}"
185
+ fi
186
+ if [ -n "$pt" ] && awk -v a="$pt" -v b="$THRESH" 'BEGIN{exit !(a>=b)}'; then
187
+ echo " OVERFULL hbox ${pt}pt ${line#*) }"
188
+ big=$((big+1))
189
+ else
190
+ small=$((small+1))
191
+ fi
192
+ done < <(grep -F 'Overfull \hbox' "$LOG" || true)
193
+
194
+ # --- Overfull \vbox (content past the bottom of the text block) ---
195
+ # Apply the same pt threshold as hbox: a sub-threshold vbox (< a few pt) is an
196
+ # invisible fraction of a line, not a visible defect.
197
+ while IFS= read -r line; do
198
+ [ -z "$line" ] && continue
199
+ pt=""
200
+ if [[ $line =~ \(([0-9]+\.?[0-9]*)pt\ too\ high\) ]]; then
201
+ pt="${BASH_REMATCH[1]}"
202
+ fi
203
+ if [ -z "$pt" ] || awk -v a="$pt" -v b="$THRESH" 'BEGIN{exit !(a>=b)}'; then
204
+ echo " OVERFULL vbox ${line}"
205
+ big=$((big+1))
206
+ else
207
+ small=$((small+1))
208
+ fi
209
+ done < <(grep -F 'Overfull \vbox' "$LOG" || true)
210
+
211
+ # --- Reference / citation / control-sequence problems ---
212
+ undef=$(grep -icE "Undefined control sequence|Citation .* undefined|Reference .* undefined|LaTeX Warning: There were undefined references" "$LOG" || true)
213
+ fatal=$(grep -icE "^!|Fatal error|Emergency stop" "$LOG" || true)
214
+
215
+ # --- BibTeX's OWN log (.blg) — a SECOND log nobody was reading ---
216
+ # 🔴 ADDED 2026-08-24, same evening as the class-warning check, after the same shape of failure
217
+ # bit twice. BibTeX does not abort the build: on a malformed entry it prints
218
+ # "You're missing a field name---line N of file refs.bib"
219
+ # "I'm skipping whatever remains of this entry"
220
+ # and CARRIES ON. pdflatex then produces a perfectly clean PDF with those references SILENTLY
221
+ # ABSENT from the bibliography. Measured: two entries vanished and the LaTeX log stayed at
222
+ # undefined=0, because the stale .aux still resolved the keys from the previous run.
223
+ # The trigger that day: a `%` comment placed INSIDE a @inproceedings entry. `%` is a LaTeX
224
+ # comment, NOT a BibTeX one — inside an entry it is a parse error that eats the whole record.
225
+ blgerr=0
226
+ BLG="$BASE.blg"
227
+ if [ -f "$BLG" ]; then
228
+ blgerr=$(grep -cE "I'm skipping whatever remains|Error may have been|repeated entry|I couldn't open" "$BLG" || true)
229
+ if [ "$blgerr" -gt 0 ]; then
230
+ echo " BIBTEX errors ($blgerr) — entries are DROPPED from the bibliography, build stays green:"
231
+ grep -E "^(You're|I'm skipping|Repeated entry|I couldn't open)" "$BLG" | sed 's/^/ /' | head -12
232
+ fi
233
+ # A cited key that never made it into the .bbl is the same failure seen from the other side.
234
+ if [ -f "$BASE.aux" ] && [ -f "$BASE.bbl" ]; then
235
+ missing=0
236
+ while read -r key; do
237
+ [ -z "$key" ] && continue
238
+ grep -qF "{$key}" "$BASE.bbl" || { echo " cited but NOT in the bibliography: $key"; missing=$((missing+1)); }
239
+ done < <(grep -oE '\\citation\{[^}]*\}' "$BASE.aux" | sed 's/.*{//;s/}//' | tr ',' '\n' | sort -u)
240
+ [ "$missing" -gt 0 ] && { echo " CITED-BUT-ABSENT references: $missing"; blgerr=$((blgerr+missing)); }
241
+ fi
242
+ fi
243
+
244
+ # --- Which PUBLISHER is this paper? The gate was publisher-blind until 2026-08-24 ---
245
+ # Measured that day: this repo holds THREE shapes — acmart (agenticdev, aisec), ACL's acl.sty
246
+ # (compile-rules, built via repro/build-submission.sh), and a bare `article` (scored). The
247
+ # checks above are publisher-independent and stay that way. What is NOT publisher-independent
248
+ # is which EXTERNAL checker exists, and naming it is the whole point: the author-side tool for
249
+ # an ACL paper (aclpubcheck) is not the one for an ACM paper, and for ACM there is no offline
250
+ # community checker at all — the publisher checks server-side. Saying so here stops the next
251
+ # person re-deriving it, and stops them running the ACL checker on an ACM paper.
252
+ publisher="unknown"
253
+ if [ -f "$BASE.tex" ]; then
254
+ if grep -qE '^[^%]*\\documentclass[^{]*\{acmart\}' "$BASE.tex"; then publisher="ACM"
255
+ elif grep -qE '^[^%]*\\usepackage\{acl\}|acl_natbib' "$BASE.tex"; then publisher="ACL"
256
+ elif grep -qE '^[^%]*\\documentclass[^{]*\{IEEEtran\}' "$BASE.tex"; then publisher="IEEE"
257
+ fi
258
+ fi
259
+ echo " publisher: $publisher"
260
+ case "$publisher" in
261
+ ACM)
262
+ # No offline community checker exists for ACM by design — HotCRP's format checker and ACM
263
+ # TAPS validate on upload. The author-side mechanism IS acmart's own warnings, checked below.
264
+ ;;
265
+ ACL)
266
+ echo " NOTE: ACL paper — the official author-side checker is aclpubcheck, and it is NOT run here."
267
+ echo " Run it on the CAMERA-READY build (a line-numbered review PDF yields thousands of"
268
+ echo " spurious margin errors — measured 2026-08-24: 2192 errors, ALL of category Margin):"
269
+ echo " python3 -m venv env && env/bin/pip install 'setuptools<60' wheel"
270
+ echo " env/bin/pip install 'aclpubcheck @ git+https://github.com/acl-org/aclpubcheck'"
271
+ echo " env/bin/aclpubcheck --paper_type long <camera-ready.pdf>"
272
+ echo " (the setuptools pin is load-bearing: this container's setuptools breaks every"
273
+ echo " legacy setup.py package with 'AttributeError: install_layout')"
274
+ ;;
275
+ IEEE)
276
+ echo " NOTE: IEEE paper — validate with IEEE PDF eXpress / LaTeX Analyzer before upload."
277
+ ;;
278
+ esac
279
+
280
+ # --- The document CLASS's own warnings (acmart / IEEEtran / llncs) ---
281
+ # 🔴 ADDED 2026-08-24 after three camera-ready blockers shipped past this gate in one
282
+ # evening — every one of them ANNOUNCED IN THIS LOG, none of them read:
283
+ # "You do not have the libertine package installed" -> acmart does NOT fail; it falls
284
+ # back to Computer Modern, so the PDF compiles clean and is set in the WRONG FONTS.
285
+ # "A possible image without description" -> ACM requires alt text on figures.
286
+ # "CCS concepts are mandatory for papers over two pages" -> a hard venue requirement.
287
+ # None of these is an Overfull, an undefined ref, or a fatal error, so the gate passed all
288
+ # three. The class is the venue's own voice about venue compliance; not reading it was the
289
+ # whole defect. Blocking, not advisory: each of the three would have been shipped.
290
+ # 🔴 STAGE-AWARENESS, added 2026-08-24 after running this gate on the HELD-OUT submitted
291
+ # version (commit eac95937). It fired 4 warnings there and TWO were false: at submission time
292
+ # `printacmref=false` and a missing CCS block are DELIBERATE and correct — the venue requires
293
+ # neither until camera-ready. A gate that cries on a legitimate submission build gets muted,
294
+ # so camera-ready-only requirements are suppressed while the document is still in review mode.
295
+ # Detection is self-contained (the document says what it is), not a flag someone must remember.
296
+ review_mode=0
297
+ if [ -f "$BASE.tex" ]; then
298
+ grep -qE '^[^%]*\\documentclass\[[^]]*\breview\b' "$BASE.tex" && review_mode=1
299
+ grep -qE '^[^%]*printacmref=false' "$BASE.tex" && review_mode=1
300
+ fi
301
+ CAMERA_ONLY='ACM reference format is mandatory|CCS concepts are mandatory'
302
+ CLASS_RE="^(Class|Package) (acmart|IEEEtran|llncs|refcheck) Warning"
303
+
304
+ # 🔴 ADVISORY class warnings: printed, but NOT blocking (2026-09-02).
305
+ #
306
+ # The same argument as the paragraph above about CAMERA_ONLY, only from the other side: a gate that
307
+ # fails a paper over something the author cannot influence gets muted entirely — together with the
308
+ # real findings. The measurement that produced the list is the very first run of the fixed
309
+ # render-gate over the corpus:
310
+ #
311
+ # agenticdev-2026 1 warning, `\vspace`. It is NOT in paper.tex, NOT in the paper's .sty — the
312
+ # package generates it. The publisher accepted this PDF on the second submission
313
+ # with ZERO findings, that is, its own check had no objections.
314
+ # aisec-2026 3, TWO of them about images with no descriptions — that one IS an ACM
315
+ # requirement, it is fixed by the author, and it stays blocking.
316
+ #
317
+ # That is, one bucket held a finding that must be fixed and noise that cannot be fixed at all.
318
+ # Splitting them is not a weakening: `\vspace` is still visible in the output on its own line.
319
+ #
320
+ # ⚠️ Keep the list NARROW and extend it only by measurement: every line here is a class of findings
321
+ # that stopped failing the build. Extending it "just in case" turns the gate back into a printer.
322
+ CLASS_ADVISORY='\\vspace should only be used'
323
+ if [ "$review_mode" = 1 ]; then
324
+ echo " stage: SUBMISSION (review mode) — camera-ready-only requirements not gated"
325
+ class_hits=$(grep -E "$CLASS_RE" "$LOG" | grep -vE "$CAMERA_ONLY" || true)
326
+ else
327
+ echo " stage: CAMERA-READY"
328
+ class_hits=$(grep -E "$CLASS_RE" "$LOG" || true)
329
+ fi
330
+ # One selection, two buckets: blocking and advisory. Counting them as one number WAS the defect.
331
+ class_block=$(printf '%s\n' "$class_hits" | grep -vE '^$' | grep -vE "$CLASS_ADVISORY" || true)
332
+ class_advis=$(printf '%s\n' "$class_hits" | grep -vE '^$' | grep -E "$CLASS_ADVISORY" || true)
333
+ classwarn=$(printf '%s\n' "$class_block" | grep -cE '^.' || true)
334
+ classadv=$(printf '%s\n' "$class_advis" | grep -cE '^.' || true)
335
+ if [ "$classwarn" -gt 0 ]; then
336
+ echo " DOCUMENT-CLASS warnings ($classwarn) — venue compliance, not cosmetics:"
337
+ printf '%s\n' "$class_block" | sed 's/^/ /' | head -20
338
+ fi
339
+ if [ "$classadv" -gt 0 ]; then
340
+ echo " document-class advisory ($classadv) — printed, NOT gated (see CLASS_ADVISORY):"
341
+ printf '%s\n' "$class_advis" | sed 's/^/ /' | head -20
342
+ fi
343
+
344
+ # ⌫ ACM fonts: the check was REMOVED 2026-08-26 — it moved into the `pdf/fonts` rule.
345
+ # (deliberately NOT a `# ---` block: the ratchet counts blocks, and a tombstone is not a check)
346
+ # The block lived here from 25.08 and was the first to catch "acmart silently fell back to Computer
347
+ # Modern". The same thing is now done by `eslint-rules/pdf-facts.mjs` over `_build/paper.facts.json`,
348
+ # and it does it WIDER: the block here skipped review mode, the rule judges the artifact always. Two
349
+ # sources of truth about one fact drift apart — so the duplicate was deleted, not kept "just in case".
350
+ #
351
+ # 🔴 The condition under which this becomes a loss: the rule looks at papers that declared their venue
352
+ # in `venue.json`. Should a paper appear with a build but without `venue.json`, nobody will check its
353
+ # fonts. Today there are none (all three real papers are declared).
354
+
355
+ # --- chktex, if installed: LaTeX-source typography the log cannot see ---
356
+ # Adopted 2026-08-24 instead of writing our own. Measured first: out of the box it produced 28
357
+ # findings on a clean camera-ready and exactly ONE was real (`95\% CI. Averages` — a period after
358
+ # capitals that LaTeX renders with a NARROW space; the fix is `CI\@.`). The five noisy rules are
359
+ # disabled BY NAME in $REPO/.chktexrc with the measurement recorded there; ~30 rules stay armed.
360
+ # Advisory, not blocking: the two survivors of rule 13 on our own corpus are false, and a gate
361
+ # that fails on known-false findings is a gate someone disables.
362
+ if command -v chktex >/dev/null 2>&1 && [ -f "$BASE.tex" ]; then
363
+ # 🔴 Walk UP from the paper dir first. The three candidates below all failed on 2026-08-24 in a
364
+ # plain `bash check-render.sh <dir>` call: $CLAUDE_PROJECT_DIR is unset outside the harness, and
365
+ # $OLDPWD is whatever the caller sat in before this script's own `cd` — here the paper dir, which
366
+ # holds no rc. Result: 32 advisory findings printed with the five noisy rules ARMED, i.e. the
367
+ # config we measured and wrote that morning was silently not in effect. The tell is the absence
368
+ # of `rc=` in the header line; nothing else says so. Same class as every other config-not-applied
369
+ # defect in this repo, so the lookup no longer depends on an env var or on the caller's cwd.
370
+ RC=""
371
+ d=$(cd "$(dirname "$BASE.tex")" && pwd)
372
+ while [ -n "$d" ] && [ "$d" != "/" ]; do
373
+ [ -f "$d/.chktexrc" ] && { RC="$d/.chktexrc"; break; }
374
+ d=$(dirname "$d")
375
+ done
376
+ [ -z "$RC" ] && for cand in "${CLAUDE_PROJECT_DIR:-}/.chktexrc" "$OLDPWD/.chktexrc" "$HOME/.chktexrc"; do
377
+ [ -n "$cand" ] && [ -f "$cand" ] && { RC="$cand"; break; }
378
+ done
379
+ ck=$(chktex -q ${RC:+-l "$RC"} "$BASE.tex" 2>/dev/null | grep -cE "^(Warning|Error)" || true)
380
+ if [ "$ck" -gt 0 ]; then
381
+ echo " chktex ($ck, advisory${RC:+, rc=$(basename "$RC")}):"
382
+ chktex -q ${RC:+-l "$RC"} "$BASE.tex" 2>/dev/null | grep -E "^(Warning|Error)" | sed 's/^/ /' | head -10
383
+ fi
384
+ fi
385
+
386
+ # --- refcheck: unreferenced labels. NOT our code — the package ships with TeX Live (2013) and
387
+ # writes `Package refcheck Warning: Unused label 'tab:orphan'` into the same .log, which the
388
+ # CLASS_RE above already reads. Turn it on for a CHECK build only:
389
+ # \usepackage{refcheck}
390
+ # ⚠️ never in the shipped source — by default it prints label names in the margins. Verified
391
+ # 2026-08-24 on a fixture: it names both an orphan table and an orphan figure. Our own paper had
392
+ # exactly this defect the same day (`tab:tasks`, a table added AT THE REVIEWERS' REQUEST and never
393
+ # referenced), found by hand because nothing was looking.
394
+
395
+ # --- Advisory: literal space inside \texttt{...} (a multi-token command can wrap
396
+ # at that space with NO Overfull warning — e.g. rm -rf breaking as rm | -rf). Tie
397
+ # command tokens with ~ (\texttt{rm~-rf}). Advisory only: many \texttt spaces are fine.
398
+ if [ -f "$BASE.tex" ]; then
399
+ ttspace=$(grep -oE '\\texttt\{[^{}]* [^{}]*\}' "$BASE.tex" 2>/dev/null | sort -u | head -20 || true)
400
+ if [ -n "$ttspace" ]; then
401
+ ttn=$(printf '%s\n' "$ttspace" | wc -l | tr -d ' ')
402
+ echo " NOTE ($ttn): \\texttt{} block(s) with a literal space (can wrap mid-command — tie tokens with ~ if it's a shell command):"
403
+ printf '%s\n' "$ttspace" | sed 's/^/ /'
404
+ fi
405
+ fi
406
+
407
+ [ "$big" -gt 0 ] && fail=1
408
+ [ "$undef" -gt 0 ] && { echo " UNDEFINED refs/citations/control-seq: $undef line(s)"; fail=1; }
409
+ [ "$fatal" -gt 0 ] && { echo " FATAL/error lines: $fatal"; fail=1; }
410
+ [ "$classwarn" -gt 0 ] && fail=1
411
+ [ "$blgerr" -gt 0 ] && fail=1
412
+
413
+ echo "-- summary: ${big} visible overfull(s) >=${THRESH}pt, ${small} sub-threshold, undef=${undef}, fatal=${fatal}, class-warnings=${classwarn}, class-advisory=${classadv}, bibtex=${blgerr}"
414
+ if [ "$fail" -eq 0 ]; then
415
+ echo "PASS: no visible layout defects, no undefined refs."
416
+ else
417
+ echo "FAIL: fix the items above before declaring the render clean."
418
+ fi
419
+ exit $fail
@@ -0,0 +1,55 @@
1
+ # Python dependencies for the paper pipeline. Installed with ONE command, no manual steps:
2
+ #
3
+ # pip install -r node_modules/paperlint/skills/render-paper/checkers-requirements.txt
4
+ #
5
+ # Tools that need something other than Python (the TeXtidote jar) are installed by
6
+ # ensure-checkers.sh nearby — which also VERIFIES that each checker actually runs, not just that
7
+ # it's "installed."
8
+
9
+ jinja2>=3.0 # {{macro}} substitution in paper_numbers.py
10
+
11
+ # 🔴 THIS PIN IS LOAD-BEARING, not cosmetic. aclpubcheck pulls in bibtexparser; the 2.x branch is
12
+ # beta (2.0.0b9) with a different API, and the 1.4.x branch's sdist does not build under
13
+ # setuptools 68+. 1.3.0 has a prebuilt WHEEL (bibtexparser-1.3.0-py3-none-any.whl), so `<2`
14
+ # resolves to it and there's nothing to build. Verified 2026-09-04 in a clean venv: install exits
15
+ # 0, `python -m aclpubcheck.formatchecker --help` runs.
16
+ bibtexparser<2
17
+
18
+ # 🔴 aclpubcheck IS INSTALLED SEPARATELY, WITH `--no-deps`, AND IT IS DELIBERATELY NOT LISTED HERE.
19
+ # Measured 2026-09-17: it declares `tsv>=1.2,<2.0`, which has NO PREBUILT WHEEL AT ALL
20
+ # (`pip download --only-binary :all: tsv` → "from versions: none"), and its source does not build
21
+ # under a modern setuptools:
22
+ # AttributeError: install_layout. Did you mean: 'install_platlib'?
23
+ # That one line alone made installing the WHOLE file fail, and that's why the paper went a month
24
+ # without an official format check.
25
+ # Verified, not assumed: `tsv` is imported NOWHERE in aclpubcheck's source (grep for
26
+ # `import tsv|from tsv` — 0 hits), and without it
27
+ # `python3 -m aclpubcheck.formatchecker --help` prints usage and exits zero.
28
+ # So what follows is aclpubcheck's OWN dependencies, taken verbatim from the package's metadata,
29
+ # minus tsv. A list, rather than `aclpubcheck` on one line, so the next unrepresentable dependency
30
+ # reads as a missing line rather than an unverified assumption.
31
+ # 🔴 THE UPPER BOUNDS ARE REMOVED, AND THAT IS A MEASUREMENT, NOT CARELESSNESS. The first edition
32
+ # of this list copied aclpubcheck's bounds verbatim — and turned red on CI, where Python is newer:
33
+ # ERROR: Could not find a version that satisfies the requirement pandas<2.0.0,>=1.5.0
34
+ # (from versions: 2.1.1, 2.1.2, … 3.0.5)
35
+ # In other words, the package's upper bound had gone stale: pandas 1.x is not published for that
36
+ # interpreter at all. Verified by running against a REAL PDF (aisec-2026/paper.pdf) on numpy 2.4.6
37
+ # + pandas 3.0.5: the checker ran and reported 9 format findings (A4, font). The lower bounds stay
38
+ # — they rule out things that are known to be too old; there are no upper bounds because none of
39
+ # them has been verified, and an unverified bound breaks the install exactly when the environment
40
+ # gets updated.
41
+ # ⚠️ `--help` is NOT proof of anything: it prints usage and exits zero even when pandas cannot be
42
+ # imported in that same interpreter. Verified 09-17, caught the hard way.
43
+ Unidecode>=1.3.6
44
+ pandas>=1.5.0
45
+ pdfplumber>=0.7.5
46
+ pybtex>=0.24.0
47
+ pylatexenc>=2.10
48
+ termcolor>=2.0.1
49
+ tqdm>=4.64.1
50
+
51
+ rebiber>=1.1.3 # an aclpubcheck dependency AND our own tool
52
+
53
+ # ⚠️ Call ONLY as `python3 -m aclpubcheck.formatchecker`. The package has no __main__.py, and the
54
+ # form `python3 -m aclpubcheck` fails with "cannot be directly executed" — that's exactly how this
55
+ # step once lived its whole life without ever running.
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bash
2
+ # ensure-checkers.sh — installs ALL the external paper checkers with one command and PROVES that
3
+ # each one runs. Idempotent: running it again breaks nothing.
4
+ #
5
+ # bash node_modules/paperlint/skills/render-paper/ensure-checkers.sh
6
+ #
7
+ # WHY IT EXISTS (2026-09-04). The install used to live as a COMMENT in requirements.txt:
8
+ # five commands, `cp -r … site-packages/` among them. That block was wrong along three axes at
9
+ # once, and all three showed up on the first attempt to use it:
10
+ # 1. NOT NEEDED — bibtexparser 1.3.0 has a prebuilt wheel, the pin `<2` is enough;
11
+ # 2. INCOMPLETE — it said nothing about cffi and unidecode, both needed, both surfaced by a crash;
12
+ # 3. HARMFUL — copying into site-packages leaves pip's metadata lying
13
+ # (`pip list` showed 2.0.0b9 on top of 1.4.4 files).
14
+ # Prose is not executed and therefore not checked. A script is executed.
15
+ set -euo pipefail
16
+
17
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ JAR_DIR="${TEXTIDOTE_DIR:-/opt/textidote}"
19
+ JAR="$JAR_DIR/textidote.jar"
20
+ JAR_URL="https://github.com/sylvainhalle/textidote/releases/download/v0.9/textidote.jar"
21
+ JAR_SHA="3ad1aaa0922709f8" # sha256 prefix, checked 2026-09-04
22
+
23
+ echo "== python dependencies =="
24
+ # `--only-binary :all:` IS LOAD-BEARING: it turns "a dependency with no prebuilt wheel (.whl)" from
25
+ # an inscrutable build failure into a refusal that names the package. That is exactly how `tsv` was
26
+ # found on 17.09 — before that the install failed with a stack from the depths of setuptools, and
27
+ # bibtexparser looked like the guilty one.
28
+ pip install -q --only-binary :all: -r "$HERE/checkers-requirements.txt"
29
+ # The checker itself — WITHOUT its dependencies: they are listed in requirements.txt by name, minus
30
+ # the unbuildable `tsv`, which it does not import. The reason is in the header of requirements.txt.
31
+ pip install -q --no-deps aclpubcheck
32
+
33
+ echo "== TeXtidote (a jar, not a pip package) =="
34
+ if [ -f "$JAR" ]; then
35
+ echo " already in place: $JAR"
36
+ else
37
+ mkdir -p "$JAR_DIR"
38
+ curl -sSL --max-time 300 -o "$JAR" "$JAR_URL"
39
+ fi
40
+ got="$(sha256sum "$JAR" | cut -c1-16)"
41
+ if [ "$got" != "$JAR_SHA" ]; then
42
+ echo "🔴 sha256 does not match: expected $JAR_SHA…, got $got…" >&2
43
+ echo " The file is NOT the one that was checked. Do not use it, find out why." >&2
44
+ exit 1
45
+ fi
46
+
47
+ # 🔴 THE MAIN PART. The installer cannot be trusted — the only thing that can be trusted is that
48
+ # the tool RUNS. "Installed" and "works" are different, and this repo already records a separate
49
+ # failure on that difference: a handoff recorded "aclpubcheck: All Clear" for a check that was
50
+ # never executed once. A missing checker and a passing one look the same.
51
+ echo "== checking that every checker WORKS =="
52
+ fail=0
53
+ # 🔴 IMPORT THE MODULE, NOT `--help`. Measured 17.09: `--help` prints usage and exits ZERO on an
54
+ # interpreter where pandas does not import at all (numpy/pandas built against each other:
55
+ # "numpy.dtype size changed"). That is, the acceptance check that stood here let through exactly
56
+ # the failure it was written for. `import aclpubcheck.formatchecker` pulls in numpy and pandas on
57
+ # lines 15 and 20 of the checker itself and therefore fails honestly.
58
+ python3 -c "import aclpubcheck.formatchecker" >/dev/null 2>&1 \
59
+ && echo " ✅ aclpubcheck" || { echo " ❌ aclpubcheck is installed, but does not run"; fail=1; }
60
+ python3 -c "import rebiber" >/dev/null 2>&1 \
61
+ && echo " ✅ rebiber" || { echo " ❌ rebiber does not import"; fail=1; }
62
+ python3 -c "import jinja2" >/dev/null 2>&1 \
63
+ && echo " ✅ jinja2" || { echo " ❌ jinja2 does not import"; fail=1; }
64
+ java -jar "$JAR" --version >/dev/null 2>&1 \
65
+ && echo " ✅ textidote ($JAR)" || { echo " ❌ textidote does not run (a JRE is needed; java 21 works)"; fail=1; }
66
+
67
+ [ "$fail" -eq 0 ] || { echo; echo "🔴 not everything works — see above" >&2; exit 1; }
68
+ echo
69
+ echo "✅ all checkers are installed AND run"