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,170 @@
1
+ # Installation
2
+
3
+ This page covers what `npm i -D paperlint` and `npx paperlint init` set up, exactly what
4
+ `init` writes, which package managers work, what the install weighs, and what to do when something
5
+ is off. The README has the short version.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm i -D paperlint
11
+ npx paperlint init
12
+ ```
13
+
14
+ Two commands in one terminal, in this order: `init` links the skills and hooks to the copy in
15
+ your project's `node_modules`, so it needs the install first.
16
+
17
+ **Upgrading from `research-paper-pipeline`** (the package's name before 2.0.0):
18
+ `npm rm research-paper-pipeline && npm i -D paperlint`, then `npx paperlint init`. It moves the
19
+ `"research-paper-pipeline"` key in `package.json` to `"paperlint"`, and replaces the hook commands
20
+ and skill links that pointed into the old package. Until then the old key is still read, with a
21
+ warning.
22
+
23
+ The npm package carries everything: the `paperlint` command, the ESLint rules, the Claude Code skills
24
+ and hooks, and the scripts the skills run. External programs are separate:
25
+
26
+ - **TeX Live** — `npx paperlint toolchain` installs the packages your venues declare (or answer `Y` when
27
+ `paperlint build` offers). It also fetches **banal**, HotCRP's page-geometry script, which needs
28
+ `perl`. See [`toolchain.md`](toolchain.md).
29
+ - **Java and Python 3** — some skills call them; install them yourself. `paperlint init` and
30
+ `paperlint doctor` list what is missing and which skills go quiet without it. `paperlint lint` needs none of
31
+ these programs.
32
+
33
+ ## What `paperlint init` does
34
+
35
+ In order:
36
+
37
+ 1. **Finds the papers directory**, or asks for it. The project must have a `package.json`; without
38
+ one `init` stops and tells you to run `npm init -y` first.
39
+ 2. **Declares it once**, as the `paperlint` key in your `package.json`.
40
+ 3. **Links each shipped skill** into `.claude/skills/<name>`, where Claude Code looks for skills.
41
+ 4. **Writes the hook commands** into `.claude/settings.json`, beside your own entries.
42
+ 5. **Offers a GitHub Actions workflow**, pinned to the release tag of the version you installed.
43
+ 6. **Offers a first paper** (`paperlint new`) if the papers directory has none.
44
+ 7. **Reports missing external programs** and the command that installs each. It installs nothing.
45
+ 8. **Runs `paperlint doctor`** and exits with its verdict.
46
+
47
+ It asks only what it cannot guess or what costs something. A human at a terminal is asked; an
48
+ agent, CI or `--yes` takes the defaults, and `init` prints which default it took. An unanswered
49
+ question (Ctrl+D) also takes the default.
50
+
51
+ ## What `paperlint init` writes
52
+
53
+ | what | where | when |
54
+ | ------------------------------------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
+ | a `paperlint` key naming your papers directory | your `package.json` | always |
56
+ | the three hook commands, merged in beside your own entries | `.claude/settings.json` | by default. A human at a terminal is asked [Y/n]; an agent, CI or `--yes` gets YES; `--no-hooks` skips. Hand-wired under another spelling: nothing written, so nothing runs twice |
57
+ | a GitHub Actions workflow, pinned to `@v<installed version>` | `.github/workflows/` | only if you say yes; it asks once, and only when a human is at a terminal (stdin and stdout, no `CI`, no `--yes`) |
58
+ | a first paper, via `paperlint new` | `<papers>/<name>/` | only when the papers directory holds none: asked of a human at a terminal, otherwise only with `--paper <name>` |
59
+ | one relative symlink per shipped skill, into the installed package | `.claude/skills/<name>` | always — except where that name is already taken (a directory, a file, a link elsewhere): that entry is left as it is and named in the report |
60
+
61
+ It installs no software and touches nothing else. Commit `.claude/settings.json` so every clone
62
+ gets the hooks; the hook commands run files inside `node_modules`, so a fresh clone needs
63
+ `npm install` first.
64
+
65
+ ## Why it is shaped this way
66
+
67
+ **One declaration, in `package.json`.** A hook cannot import code or walk up a tree looking for a
68
+ config; it can only read a path it can spell, and the one it can always spell is
69
+ `$CLAUDE_PROJECT_DIR/package.json`. That key is read by the three hooks, `eslint-rules/papers.mjs`,
70
+ `lib/skill-trigger-cases.mjs` and `skills/paper-pipeline/scripts/consumer.mjs`; a separate
71
+ `rpp.json` was read only by the CLI. `rpp.json` is still read as a deprecated fallback, and
72
+ `paperlint lint` says so.
73
+
74
+ **Nothing runs at install time.** No postinstall script and no automatic TeX download. npm's rule
75
+ is that _"the only valid use of install or preinstall scripts is for compilation"_; husky removed
76
+ its install script in 5.0.0 and Playwright in 1.38.0, because a failed install is cached and its
77
+ output is hidden. An install that can fail quietly is worse than a step that says what it needs.
78
+
79
+ **Hooks go into `.claude/settings.json`, not a plugin.** Claude Code documents that file as the
80
+ way to share hooks with a team, `init` can write it, `paperlint doctor` can read it back, and it needs
81
+ nothing installed inside Claude Code. A plugin fetched from npm gets no `node_modules` (`npm pack`
82
+ strips the lockfile, and the host runs `npm ci` only when one is present), so it could not carry
83
+ code that runs (probes: [`prior-art/repro/`](prior-art/repro/README.md); decision:
84
+ [`prior-art/paper-folder-scaffolding.md`](prior-art/paper-folder-scaffolding.md) § 5). The package
85
+ ships no plugin.
86
+
87
+ **Skills are linked, not left in `node_modules`.** Claude Code finds project skills in
88
+ `.claude/skills/<name>/SKILL.md` and never inside `node_modules`. The links also make the
89
+ project-relative script paths inside the skills (`.claude/skills/paper-pipeline/scripts/x.mjs`)
90
+ resolve.
91
+
92
+ - **What is linked:** every subdirectory with a `SKILL.md` under the package's `skills/`
93
+ directory (`SHIPPED_SKILLS_DIR` in `skills/paper-pipeline/scripts/consumer.mjs`). No list is
94
+ written down.
95
+ - **Where a link points:** the package as it resolves by name from your project, spelled through
96
+ `node_modules/paperlint`. Under pnpm the resolved path is a version-stamped store
97
+ directory, and a link spelled that way would dangle after the next upgrade.
98
+ - **What it never does:** replace an entry it did not make. Such an entry is named in the report
99
+ and left alone, and `init` still succeeds; `paperlint doctor` repeats it as a warning.
100
+
101
+ **`paperlint doctor` exists because the edit guard is silent when it works.** `paper-edit-guard` says
102
+ nothing while guarding and says nothing while watching a directory that does not exist, so
103
+ "installed" and "protecting you" look the same from outside. `doctor` prints the papers directory
104
+ `paperlint lint` resolves and the one the hooks resolve, whether they match, whether the hooks are wired
105
+ (once, twice, or not at all), and which external programs are missing. It also warns when the
106
+ project still enables the old `research-paper-pipeline` plugin, because plugin plus settings would
107
+ run every hook twice. A plugin installed at user scope is outside what it can read, and it says so.
108
+
109
+ ### How comparable tools install
110
+
111
+ Read from their published tarballs on 2026-09-18. Most write their config from an `init`; the two
112
+ that do not (Prettier, lint-staged) need the most steps. Playwright, the closest analogue (config,
113
+ a CI workflow and a heavy toolchain), asks two questions and guesses the rest.
114
+
115
+ | tool | commands to working state | writes config? | wires hooks/CI? | install-time script? |
116
+ | ----------- | ----------------------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------- |
117
+ | ESLint | **1** — `npm init @eslint/config@latest`, installs deps too | yes, `eslint.config.js` | no | none |
118
+ | Playwright | **1** — `npm init playwright@latest` | yes | **yes — GH Actions workflow + browsers, both asked inside init** | **removed in 1.38.0** |
119
+ | Biome | 2 — install, `biome init` | yes, zero prompts | no | none (platform binaries via optionalDependencies) |
120
+ | husky | 2 — install, `husky init` | yes — edits `package.json`, writes `.husky/`, sets `core.hooksPath` | yes, git hooks | **removed in 5.0.0** |
121
+ | changesets | 2 — install, `changeset init` | yes | no | none |
122
+ | Tailwind v4 | 3 + hand edits | **no — `init` deleted, the package has no `bin` at all** | no | none |
123
+ | Prettier | 3 — config created by shelling out to `node --eval` | **no init command exists** | no | none |
124
+ | lint-staged | 4+, all manual | no | no, delegates to husky | none |
125
+
126
+ ## Package managers
127
+
128
+ **npm and pnpm are covered; Yarn Plug'n'Play is not supported.**
129
+
130
+ `test/e2e/install.mjs` packs the tarball, installs it into a clean project with each manager that
131
+ launches on the machine, and runs the hook command to see whether it resolves. A grep over
132
+ `hooks.json` would not do: the string is right under any manager, and whether it resolves depends
133
+ on the tree the manager laid out. It also checks that every shipped skill is reachable as
134
+ `.claude/skills/<name>/SKILL.md`, that a second `init` changes nothing, and that a foreign
135
+ directory under a skill's name survives.
136
+
137
+ Yarn Plug'n'Play has no `node_modules`, and the hook commands in `plugin/hooks/hooks.json` name
138
+ `${CLAUDE_PROJECT_DIR}/node_modules/paperlint/bin/rpp.mjs`. Supporting it would need
139
+ a different answer to "where is the runtime", not a flag.
140
+
141
+ ## Install size
142
+
143
+ Measured 2026-09-23 with npm 10.9.7 on a clean project (production dependencies only). Since then
144
+ zernie/vigiles#280 made the `vigiles` grammars optional, so the `@ast-grep/*` and `typescript` rows
145
+ are probably smaller now; re-measure before quoting them.
146
+
147
+ | | size |
148
+ | -------------------------------------- | ---------: |
149
+ | tarball | 1.2 MB |
150
+ | this package, unpacked | 3.7 MB |
151
+ | **`node_modules` in total** | **136 MB** |
152
+ | of which `@ast-grep/*` (via `vigiles`) | 51 MB |
153
+ | of which `typescript` (via `vigiles`) | 23 MB |
154
+ | of which `vigiles` itself | 6 MB |
155
+
156
+ Neither `paperlint lint` nor any of the three hooks loads `@ast-grep` or `typescript` (traced);
157
+ `vigiles` itself is loaded.
158
+
159
+ ## Troubleshooting
160
+
161
+ | symptom | cause and fix |
162
+ | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
163
+ | `init`: skills "nothing linked", or hooks that fail to start | the package is not installed in this project (`npx` ran a temporary copy). Run `npm i -D paperlint`, then `npx paperlint init` again |
164
+ | `init` stops: no `package.json` | run `npm init -y`, then `npx paperlint init` again |
165
+ | `paperlint lint`: `nothing to lint` | no declaration was found. Run `npx paperlint init`, or pass the directory: `paperlint lint papers` |
166
+ | hooks fail with `Cannot find module` in a fresh clone | the hook commands run files in `node_modules`: run `npm install` |
167
+ | `paperlint: not built` | installed from git with `--ignore-scripts`, or a clone before building: run `npm run build` in the package |
168
+ | every hook runs twice | the project still enables the old plugin. `init` and `doctor` print the uninstall command; also remove it from `enabledPlugins` |
169
+ | a skill does not show up in Claude Code | its name was already taken in `.claude/skills/`. `init` names it and leaves it alone; rename or remove yours and run `npx paperlint init` again |
170
+ | anything else | `npx paperlint doctor` — it checks the setup and exits non-zero on anything miswired |
@@ -0,0 +1,107 @@
1
+ # Optional rules
2
+
3
+ Some checks matter only for some venues. paperlint ships them **off**, and you turn them on for the
4
+ papers that need them, in the `rules` setting of your `package.json`
5
+ ([`configuration.md`](configuration.md#the-rules-key-turning-rules-on-and-off)).
6
+
7
+ | rule | what it checks | who needs it |
8
+ | ----------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
9
+ | `pdf/last-page-balance` | the two columns of the last page end at about the same height | two-column papers whose publisher asks for it — see below |
10
+
11
+ ## `pdf/last-page-balance`
12
+
13
+ ### Turning it on
14
+
15
+ ```json
16
+ {
17
+ "paperlint": {
18
+ "papersDir": "papers",
19
+ "rules": [
20
+ {
21
+ "files": ["papers/agenticdev-2026/**"],
22
+ "rules": { "pdf/last-page-balance": ["error", { "tolerancePt": 120 }] }
23
+ }
24
+ ]
25
+ }
26
+ }
27
+ ```
28
+
29
+ `files` is relative to the `package.json`. `tolerancePt` is how far apart, in points, the two
30
+ columns may end; it defaults to 120. On a real accepted paper the balanced build ended 2.7 pt apart
31
+ and the one the publisher sent back 321.4 pt apart — nothing in between — so the default leaves a
32
+ wide margin on both sides.
33
+
34
+ ### What it reads
35
+
36
+ The rule does not open the PDF to measure it. `paperlint build` measures every PDF it builds and writes
37
+ the result to `<paper>/_build/paper.facts.json` ([`configuration.md`](configuration.md#how-rpp-build-compiles-a-paper)),
38
+ and the rule judges that file, reporting on the paper's `paper.tex` at the `\documentclass` line.
39
+ So: **build, then lint.**
40
+
41
+ | the rule finds | it says |
42
+ | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
43
+ | columns further apart than `tolerancePt` | **the finding**, with both heights and how to fix it (below) |
44
+ | no `_build/paper.facts.json` | build the paper first |
45
+ | facts about a different PDF than the one on disk | the facts are stale (their SHA-256 differs) — rebuild |
46
+ | facts whose PDF is gone (a failed build removes it) | rebuild |
47
+ | a last page of a few lines | nothing — there is no layout to balance |
48
+ | a review build with numbered lines | nothing — the numbers run down the whole page, so both columns measure full height, and balance is a camera-ready requirement |
49
+
50
+ If you turn the rule on with a `files` glob that reaches no `paper.tex`, `paperlint lint` fails and says
51
+ so, rather than reporting a clean run for a rule that never ran.
52
+
53
+ ### Which venues need it
54
+
55
+ Researched 2026-09-24 from the publishers' own pages. The requirement comes from some **production
56
+ vendors**, not from the paper templates, and no standard format checker tests it — HotCRP's
57
+ [`checkformat.php`](https://github.com/kohler/hotcrp/blob/master/src/checkformat.php) with
58
+ [banal](https://github.com/kohler/hotcrp/blob/master/src/banal), ACL's
59
+ [aclpubcheck](https://github.com/acl-org/aclpubcheck) and IEEE PDF eXpress all leave it alone.
60
+
61
+ | venue or publisher | balance required? | source |
62
+ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | **Conference Publishing Consulting** (produces ICSE, ASE and other SIGSOFT/SIGPLAN proceedings for ACM) | **yes** — the paper "has balanced columns on the last page, if the page is not filled"; problem papers are sent back to the authors | [instructions](https://www.conference-publishing.com/Instructions.php?Conf=ICSE12) · [help](https://www.conference-publishing.com/Help.php) · [procedure](https://www.conference-publishing.com/Procedure.html) |
64
+ | **Sheridan Communications** (ACM SIG conferences, e.g. CCS) | **yes** — "balance the last page into 2 even length columns" | [CCS instructions](https://www.scomminc.com/pp/acmsig/ccs.htm) |
65
+ | ACM `acmart` class | the class balances by default (option `balance`); no ACM-wide written rule was found | [acmart on CTAN](https://ctan.org/pkg/acmart) · [acmart issue #328](https://github.com/borisveytsman/acmart/issues/328) |
66
+ | IEEE (`IEEEtran`) | **advised** for camera-ready, not a written rule; PDF eXpress does not check it | [IEEEtran on CTAN](https://ctan.org/pkg/ieeetran) (`IEEEtran_HOWTO`, pp. 16–17) |
67
+ | ACL | no | [aclpubcheck](https://github.com/acl-org/aclpubcheck) has no such check |
68
+ | USENIX, AAAI, ICML | not mentioned in their 2025–2026 author instructions | — |
69
+ | NeurIPS, Springer LNCS | not applicable — one column | — |
70
+
71
+ ⚠️ **ACM TAPS venues:** ACM's TAPS system compiles the final PDF itself from your source. A fix that
72
+ lives only in a generated file (an edited `.bbl`) may never reach the proceedings; put the fix in
73
+ your source.
74
+
75
+ ### Fixing it by hand
76
+
77
+ paperlint does not fix it for you, deliberately: every mechanism is documented as unreliable by its own
78
+ authors, and a layout change can silently move a page break in a paper whose page count is
79
+ limited. Conference Publishing Consulting's own advice, in its order:
80
+
81
+ 1. > "For the ACMART style, use option 'balance' or, if this does not work, option 'pbalance'."
82
+
83
+ `balance` is already acmart's default, and it calls `\balance` from the **second** column of the
84
+ last page — where balance.sty does nothing. That is the usual case when the last page is all
85
+ bibliography. So try `\documentclass[…,pbalance]{acmart}` (acmart calls it experimental; it
86
+ needs an extra LaTeX pass and may give up).
87
+
88
+ 2. `\usepackage{flushend}`. IEEEtran's guide warns of "a spacing anomaly between two lines within
89
+ a reference in the second column of the last page".
90
+ 3. `\balance` (from balance.sty) placed in what would be the **first** column of the last page. In
91
+ a bibliography that means inside `\begin{thebibliography}`, before the `\bibitem` that starts the
92
+ last page's first column; the balance documentation says it "should be issued somewhere in the
93
+ text of what would be the first column of the last page".
94
+ 4. A `\newpage` between references in the `.bbl` file (the vendor's fourth option) — the most
95
+ fragile, since bibtex rewrites the `.bbl` on every build.
96
+
97
+ For IEEEtran, the class's own guide recommends `\IEEEtriggeratref{N}` over any package.
98
+
99
+ Then `paperlint build` again and `paperlint lint` again.
100
+
101
+ ### Why it is not part of `paperlint build`
102
+
103
+ Until 2026-09-24 `paperlint build` searched for a `\balance` position itself — rebuilding once per
104
+ bibliography entry — and failed the build, deleting the PDF, when none worked. It was removed:
105
+ only some venues ask for balance, a build step cannot be turned on per venue, given a severity or
106
+ suppressed with a reason, and an automatic layout change was the one place the build rewrote a
107
+ paper's output behind the author's back. The build now only measures; the rule judges.
@@ -0,0 +1,262 @@
1
+ # Package shape — four options, and what was measured to get there
2
+
3
+ **Status:** a design pass run 2026-09-19 against [`prior-art/`](prior-art/README.md). It is a
4
+ PROPOSAL, not a decision. It sits beside the prior art rather than inside it because a proposal
5
+ and its evidence age at different rates.
6
+
7
+ The pass was given the repository, the recorded reasons behind today's design, and permission to
8
+ research further. It came back having **refuted four premises it was handed** before proposing
9
+ anything. Those corrections are the valuable part and are recorded first, each with its
10
+ verification status.
11
+
12
+ ---
13
+
14
+ ## Premise corrections
15
+
16
+ ### 1. VERIFIED — the 81 MB is a `vigiles/package.json` fact, not a hook-runtime fact
17
+
18
+ The recorded trade-off was "ordinary dependency (heavy) versus optional peer (light, but a manual
19
+ install step)". That framing is false, because the runtime does not use the heavy modules at all.
20
+
21
+ Measured on the v28 build, top-level `require` of `typescript`, `@ast-grep/*` or `mvdan-sh` in
22
+ each module of the `hook-runtime run-program` chain:
23
+
24
+ ```
25
+ dist/hook-runtime.js 0
26
+ dist/core/hook-program.js 0
27
+ dist/load-hook.js 0
28
+ dist/hook-install.js 0
29
+ dist/core/hook-providers.js 0
30
+ dist/hook-state-store.js 0
31
+ dist/observe.js 0
32
+ ```
33
+
34
+ They arrive because `vigiles` lists them under its own `dependencies`. Independently confirmed by
35
+ this repository's consumer-side measurement the same day: after the v28 lazy-boundary work the
36
+ adapter registry loads **18 modules instead of 107, with zero ast-grep and no native binding** on
37
+ the hook path.
38
+
39
+ ### 2. CORRECTED — `optionalDependencies` does NOT deliver the weight cut
40
+
41
+ The proposal was to move the heavy modules to `optionalDependencies`, keeping `vigiles` an
42
+ ordinary dependency and cutting the install from ~148 MB to ~67 MB. **Measured, and it does not
43
+ work that way:**
44
+
45
+ ```console
46
+ $ npm config get omit
47
+ # empty — nothing is omitted by default
48
+
49
+ $ cat package.json # { "optionalDependencies": { "is-odd": "^3.0.1" } }
50
+ $ npm install && ls node_modules/is-odd
51
+ is-odd # INSTALLED by default
52
+
53
+ $ npm install --omit=optional && ls node_modules/is-odd
54
+ # absent only with the explicit flag
55
+ ```
56
+
57
+ npm installs optional dependencies unless they fail to build or the user passes `--omit=optional`.
58
+ So this prescription reintroduces exactly the manual step that made the optional-peer design get
59
+ reverted.
60
+
61
+ **What would actually work, and it is a different change:** publish the runtime as its own light
62
+ package, so the heavy modules are not in the dependency closure of what a hook consumer installs.
63
+ `vigiles lint` and `vigiles compile` keep the full set; a project that only runs hooks pulls the
64
+ small one. This is a real upstream change rather than a manifest tweak, and the question it raises
65
+ is no longer "will optional deps be accepted" but **"is the runtime worth splitting into its own
66
+ package"**.
67
+
68
+ ### 3. MEASURED — two of the three platform claims needed correcting
69
+
70
+ These three were taken from the host's documentation and were load-bearing for Option B. They
71
+ have now been measured against the real `claude` CLI (2.1.278) driven by a scripted mock model, so
72
+ each run is deterministic and costs nothing. Scripts: [`prior-art/repro/`](prior-art/repro/README.md).
73
+
74
+ **(a) `${CLAUDE_SKILL_DIR}` substitutes in a skill's body — HOLDS, and it is the ONLY variable
75
+ that works in both doors.** Measured in the project channel and the plugin channel, the latter
76
+ with the session's cwd in an unrelated directory:
77
+
78
+ ```
79
+ === A. PROJECT-level skill (.claude/skills) — the npm+symlink door ===
80
+ CLAUDE_SKILL_DIR -> "/tmp/vigiles-harness-BHkoMg/.claude/skills/xchan-project"
81
+ CLAUDE_PLUGIN_ROOT NOT SUBSTITUTED -> "${CLAUDE_PLUGIN_ROOT}"
82
+ === B. PLUGIN-provided skill — the plugin door ===
83
+ CLAUDE_SKILL_DIR -> ".../xchanplugin/skills/xchan-plugin"
84
+ CLAUDE_PLUGIN_ROOT -> ".../xchanplugin"
85
+ ```
86
+
87
+ The documented `${CLAUDE_PLUGIN_ROOT}` is a literal no-op in the project channel, and where it
88
+ does work it anchors to the plugin root rather than the skill, so it would force two spellings of
89
+ every path. There is no second candidate. **Bonus the claim omitted:** `allowed-tools` frontmatter
90
+ substitutes too — which matters, because 18 of the 89 SKILL.md occurrences live there rather than
91
+ in the body.
92
+
93
+ **(b) The `hooks:` caveat is real and WORSE than "not substituted".** The placeholder is passed
94
+ through to the shell, which expands an unset variable to nothing, so
95
+ `node ${CLAUDE_SKILL_DIR}/scripts/x.mjs` silently becomes `node /scripts/x.mjs` — no error, and no
96
+ `${...}` literal left to grep for. The hook fires (`exitCode 0`, sentinel green), so this is "no
97
+ substitution", not "no run". Upstream `anthropics/claude-code#36135` describes exactly this and is
98
+ **closed as not planned**. ⇒ `plugin/hooks/hooks.json` must keep `${CLAUDE_PROJECT_DIR}` /
99
+ `${CLAUDE_PLUGIN_ROOT}` and must never adopt `${CLAUDE_SKILL_DIR}`.
100
+
101
+ **(c) The npm marketplace entry — HOLDS, but the shape recorded above was WRONG.** `source` is an
102
+ object whose own `source` key names the type. Verified twice, independently, capturing the real
103
+ exit code:
104
+
105
+ ```console
106
+ $ claude plugin validate <flat, as this note first recorded it> --strict # RC=1
107
+ > plugins[0].source: Bare source name "npm" requires metadata.pluginRoot.
108
+ $ claude plugin validate <nested> --strict # RC=0
109
+ √ Validation passed
110
+ $ claude plugin validate <"source": "nosuchsourcetype"> --strict # RC=1 (control)
111
+ ```
112
+
113
+ ```json
114
+ {
115
+ "name": "research-paper-pipeline",
116
+ "source": {
117
+ "source": "npm",
118
+ "package": "research-paper-pipeline",
119
+ "version": "^0.1.0"
120
+ }
121
+ }
122
+ ```
123
+
124
+ `version` is optional and accepts an exact version or a range. Because the wrong shape fails
125
+ `claude plugin validate --strict` with a nonzero code, this is a defect that a CI gate can make
126
+ unshippable rather than a thing to remember.
127
+
128
+ **(d) "Dependencies are not installed" — FALSE as worded; the conclusion survives for a different
129
+ reason.** Measured A/B on two byte-identical plugins differing only by a lockfile: with one,
130
+ `node_modules` materialised in the plugin cache; without one, nothing, and no log entry. Lifecycle
131
+ scripts did not run in either. The true rule is **`npm ci --ignore-scripts` runs iff `package.json`
132
+ AND a supported lockfile are both present in the fetched root**. What rescues the conclusion is a
133
+ separate fact: `npm pack` strips `package-lock.json` unconditionally — proven with a positive
134
+ control, where a newly created file listed in the same `files[]` array was included while the
135
+ lockfile in that array was not.
136
+
137
+ ⇒ **A plugin installed from npm gets no `node_modules`, silently.** Three scripts break on it, not
138
+ zero: `pipeline-check.mjs`, `extract-ref-facts.mjs` and `bib-authors.mjs`, all reaching
139
+ `markdown-it`. Which retires a number this note carried: the closure is **two** third-party
140
+ packages over **eight** referenced scripts, not one over seven — `extract-ref-facts.mjs` reaches
141
+ `@retorquere/bibtex-parser` through a dynamic `await import()` that a static grep does not see.
142
+
143
+ ⏳ **Not measured, recorded as such:** the documented 60-second install timeout; the personal
144
+ `~/.claude/skills` and `--add-dir` channels (2 of 4 locations verified); and a genuine
145
+ `{"source":"npm"}` install end-to-end — the A/B used a local git source, since publishing to a
146
+ registry was out of scope.
147
+
148
+ ### 4. VERIFIED INDEPENDENTLY — the mtime check is wrong on every fresh checkout
149
+
150
+ git does not preserve modification times; a CI checkout gives every file the same timestamp. A
151
+ "build log older than its source" rule keyed on mtime is therefore decided by clone order, not by
152
+ content. Recording `sha256(source)` at build time makes it a pure comparison. See
153
+ [`prior-art/nondeterministic-checks.md`](prior-art/nondeterministic-checks.md).
154
+
155
+ ---
156
+
157
+ ## The four options
158
+
159
+ Each was asked for a complete `--help`, a delivery table, a config, an install counted in actions,
160
+ the structural guarantee it buys, and its migration cost. Condensed here; the shape is what
161
+ matters.
162
+
163
+ ### A — "A linter, full stop"
164
+
165
+ paperlint is Ruff for papers. Skills and hooks leave the package entirely and become a separate,
166
+ self-contained Claude Code plugin that _calls_ paperlint when present and says so loudly when absent.
167
+
168
+ ```
169
+ paperlint — lint and build for a paper kept in git
170
+
171
+ npx paperlint init find the papers directory, declare it, offer the CI step
172
+ npx paperlint lint [dir…] run every rule; warnings never fail (--strict makes them)
173
+ npx paperlint build [paper] run the paper's own build script; record sha256 of source and pdf
174
+
175
+ --json machine-readable findings --strict promote warnings to errors
176
+ ```
177
+
178
+ Removed: `doctor` (folded into an idempotent `init`), `hook` (no hooks in the package),
179
+ `--config` and the `rpp.json` fallback, `--max-warnings` (a threshold nobody chooses on purpose)
180
+ → binary `--strict`, `--dry-run`, `--all`.
181
+
182
+ **Buys:** the linter cannot depend on an agent runtime, so the weight cut needs nobody's
183
+ permission. A plugin cannot be half-installed. **Costs:** two repos, two version lines, three
184
+ hooks rewritten as plain stdin-JSON/exit-2 scripts outside the vigiles machinery.
185
+
186
+ ### B — "One artifact, two doors"
187
+
188
+ One published tarball that is simultaneously the npm package and the plugin: the marketplace entry
189
+ points at npm, the plugin manifest sits at package root, `skills/` is the default scanned
190
+ directory, and `paperlint init` opens both doors.
191
+
192
+ ```
193
+ paperlint — machine-checkable gates for a paper kept in git
194
+
195
+ npx paperlint init declare where the papers live; wire CI and the plugin; then doctor
196
+ npx paperlint lint [dir…] run every rule; --strict to fail on warnings; --json for machines
197
+ npx paperlint build [paper] run the paper's own build script; record sha256 of source and pdf
198
+ npx paperlint doctor what is wired vs. what only looks wired
199
+
200
+ `paperlint hook <name>` exists for the plugin wiring and is not for typing.
201
+ ```
202
+
203
+ `doctor` survives here _because_ this option puts two copies of the package on disk (plugin cache
204
+ and `node_modules`) and something must say whether their versions agree.
205
+
206
+ **Buys:** the plugin and the npm package cannot ship different skills — there is one tarball, so
207
+ the "installs all 24 skills" contradiction is resolved by fact rather than by editing prose. Every
208
+ skill path becomes `${CLAUDE_SKILL_DIR}/…`, which the host resolves instead of the prose guessing.
209
+ **Costs:** publishing to npm becomes a precondition; the SKILL.md literals rewritten (measured:
210
+ **89** occurrences across 23 skills — 71 in bodies, 18 in `allowed-tools`, both of which
211
+ substitute) and the existing advisory rule flipped to error; and vendoring `markdown-it`, because
212
+ the plugin door gets no `node_modules` at all (correction 3d). **Gives up:** nothing structural — the
213
+ package stays a 26-skill monolith, and a researcher who never uses an agent still downloads 2.9 MB
214
+ of markdown.
215
+
216
+ ### C — "Plugin-first"
217
+
218
+ The plugin is the primary artifact; the npm package shrinks to the two commands skills shell out
219
+ to. Honest about audience, and explicitly not recommended by the pass itself: this repository's
220
+ own primary consumer installs through npm and symlinks, so the primary consumer would sit on the
221
+ secondary path.
222
+
223
+ ### D — "A runner, and rule packs like Vale"
224
+
225
+ paperlint becomes a runner; rules are packages declared in config and pulled with `paperlint add`, following
226
+ Vale's `Packages:` + `sync`, textlint's rule packages, and `astro add`. Severity is data inside a
227
+ pack, capped by the runner. **Right shape for a second author; ceremony for one publisher.** Named
228
+ here so it is not reinvented, with its trigger: a second rule author.
229
+
230
+ ---
231
+
232
+ ## Ranking, as delivered
233
+
234
+ 1. **B**, because it removes a delivery channel instead of documenting it and closes each open
235
+ problem by construction.
236
+ 2. **A**, because its weight cut depends on no upstream change — take it if the runtime split is
237
+ refused.
238
+ 3. **C** — coherent, wrong primary audience.
239
+ 4. **D** — 2027.
240
+
241
+ ⚠️ **The stated deciding question was "will vigiles move the heavy modules to optional
242
+ dependencies". Correction 2 retires that question**: optional dependencies install by default. The
243
+ real question is whether the hook runtime is worth publishing as its own package. If it is not, A
244
+ moves to first for exactly the reason given — it is the only option that gets light without
245
+ asking anyone.
246
+
247
+ ## Three fixes that no option makes optional
248
+
249
+ 1. **Declare `exports`.** An empty `exports` map beside a README that documents importing from
250
+ `bin/rpp.mjs` is a false statement about the API surface.
251
+ 2. **Replace the mtime and wall-clock checks with hashes recorded by `build`.**
252
+ 3. **Make hooks consume `paperlint lint --json` and read the `severity` field.** Anything that
253
+ re-derives severity from rendered text — an emoji, a prefix — is lossily reconstructing a field
254
+ that already exists one layer down.
255
+
256
+ ## One proposal deliberately NOT acted on
257
+
258
+ The pass suggests `paperlint init` write `enabledPlugins` and the marketplace entry into the consumer's
259
+ `.claude/settings.json` so the plugin is enabled without `/plugin` commands. It is a coherent
260
+ idea with a precedent, and it is recorded here as a proposal only: a tool writing into a user's
261
+ agent settings is a decision for the person who owns those settings, not a detail of the install
262
+ script.
@@ -0,0 +1,76 @@
1
+ # Prior art — how comparable tools are shaped
2
+
3
+ **Why this folder lives in the repository.** These are technical records that code and other docs
4
+ cite by path: `test/e2e/install.mjs` cites `package-location.md`, `scripts/marketplace-shape.mjs`
5
+ and `docs/install.md` point at the probes in `repro/`, and `eslint.config.mjs` excludes `repro/`
6
+ by name. Moving the folder out would leave those references pointing nowhere. The notes are for
7
+ contributors and maintainers, not for someone checking a paper, which is why the README does not
8
+ link them and `CONTRIBUTING.md` does.
9
+
10
+ Why this folder exists: this package is four things at once (a linter over papers, a build
11
+ front-end, a host for agent hooks, and a carrier of skills), and every design argument about
12
+ its command surface kept being settled by taste. These notes settle them by looking at tools
13
+ that already solved the same problem, with the URL that was checked.
14
+
15
+ **Files are split by QUESTION, not by tool** — Quarto answers two different questions and has to
16
+ be citable for each one separately.
17
+
18
+ | file | the question | the verdict in one line |
19
+ | ------------------------------------------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | [`multi-mode-tools.md`](multi-mode-tools.md) | how do linter + builder + extension host live in one CLI? | small-feeling tools have fewer **nouns**, not fewer capabilities — the project config supplies what would be an argument |
21
+ | [`blocking-vs-advisory.md`](blocking-vs-advisory.md) | who decides to fail the run — the rule or the runner? | severity is **data on the finding**; failing is the **runner's** decision, and the default is not to fail |
22
+ | [`nondeterministic-checks.md`](nondeterministic-checks.md) | may a check read the clock, an mtime, or a diff? | yes, but make it opt-in, never blocking — and prefer a **recorded fact** to an inferred one, which dissolves most of them |
23
+ | [`content-delivery.md`](content-delivery.md) | how is installable content (skills, styles, extensions) delivered? | the **config declares it and a command fetches it**; content that hard-codes its own install path is betting on one channel |
24
+ | [`test-tooling.md`](test-tooling.md) | what do we test with — a framework? a local registry? | **change nothing**: a runner buys a reporter and a second exit code; a registry is what MULTI-package repos need. Both have named triggers |
25
+ | [`package-location.md`](package-location.md) | how does a package find its own installed files, and its own bin? | resolve the package dir **once** from `<pkg>/package.json`; **keep** the `.bin` launch — it is the only check that observes the manager's own work. Yarn PnP stays out, for a harder reason than previously recorded |
26
+ | [`readme-structure.md`](readme-structure.md) | how do comparable linters lead a new reader to a first working run? | one sentence, then a real run with its real output; install on the first screen; design reasoning never on the front page |
27
+ | [`paper-folder-scaffolding.md`](paper-folder-scaffolding.md) | who creates a paper folder, and who wires the hooks — a command or a human? | ship the template as a FILE and scaffold from it (`rpp new`); `init` writes the hooks into `.claude/settings.json` instead of printing `/plugin` lines |
28
+
29
+ ## Tools examined, and for what
30
+
31
+ | tool | why it is here |
32
+ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
33
+ | **Quarto** | the domain analogue — scientific publishing with `render` / `check` / `publish` and extensions |
34
+ | **Vale** | the content analogue — a prose linter whose styles are declared in config and fetched by `vale sync` |
35
+ | **Biome** | the "one tool, one config, one command" position, stated by its own authors |
36
+ | **Danger JS** | a linter for the PROCESS rather than the code; levels as separate functions |
37
+ | **reviewdog** | non-blocking by default; failing is a flag on the runner |
38
+ | **eslint-plugin-unicorn** (`expiring-todo-comments`) | proof that a time-dependent lint rule is respectable — and how its author contained it |
39
+ | **Semgrep** (`--baseline-commit`) | a before/after check without an event: same analysis, two revisions, subtract |
40
+ | **ESLint**, **Clippy**, **pre-commit** | severity as config data; content declared, fetched and pinned |
41
+
42
+ ## How to use this folder
43
+
44
+ - **Before changing the command surface**, read `multi-mode-tools.md`. The argument "we need
45
+ another command for X" is usually an argument that the config is not declaring something.
46
+ - **Before adding a check that can fail a build**, read `blocking-vs-advisory.md` and
47
+ `nondeterministic-checks.md` in that order.
48
+ - **Before adding anything to the plugin or the skills**, read `content-delivery.md` — it
49
+ records the measured state in which the plugin channel delivers zero skills.
50
+
51
+ ## What this folder is NOT
52
+
53
+ It does not argue whether this package should exist, or how it compares to other academic
54
+ skill suites — that question is answered in `CONTRIBUTING.md`, § "Why not one of the existing
55
+ academic skill suites". Different question, deliberately not merged.
56
+
57
+ ## The scripts behind the numbers
58
+
59
+ [`repro/`](repro/README.md) holds the probes for the platform measurements that
60
+ `../package-shape-options.md` § "Premise corrections" rests on — the `${CLAUDE_SKILL_DIR}`
61
+ substitution table, the five marketplace shapes with their real exit codes, and the dependency
62
+ closure scan. They are kept so a verdict here can be disagreed with by running a program.
63
+
64
+ It also holds the CLAIM 4–6 probes behind [`package-location.md`](package-location.md): the
65
+ resolution matrix under npm and pnpm, the Yarn PnP zip-path measurement, the four ways to launch
66
+ an installed bin, and the `exports`-map mutation that is the entire case for changing anything.
67
+
68
+ ## Status
69
+
70
+ Written 2026-09-19 from first-hand fetches of the sources cited in each file.
71
+
72
+ The design pass run against these notes landed the same day:
73
+ [`../package-shape-options.md`](../package-shape-options.md) — four options for the package's
74
+ shape, a ranking, and four premise corrections that came out of it. It sits BESIDE this folder,
75
+ not inside it, because a proposal and its evidence age at different rates: these notes stay true
76
+ as long as the tools they cite do; that proposal expires the moment a shape is chosen.