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
package/CLAUDE.md ADDED
@@ -0,0 +1,598 @@
1
+ # CLAUDE.md — paperlint
2
+
3
+ Machine-checkable gates for writing a research paper in git, extracted from a private
4
+ knowledge base.
5
+
6
+ ## 🎯 THE GOAL, in one sentence
7
+
8
+ **Move every paper-writing convention a machine can decide OUT of prose and INTO an engine
9
+ that fails the build** — and keep everything else honestly labelled as prose. The engine is
10
+ ESLint. The unit of progress is "one more verdict decided by the engine instead of by a
11
+ hand-written script".
12
+
13
+ That last clause is the whole direction, and it is the part that keeps being forgotten:
14
+
15
+ ```
16
+ prose in a guideline → hand-written script → ESLint rule in the engine
17
+ rots silently runs, but is OURS editor-time · free AST · suppressible
18
+ ← where this started ← where most of it is ← where it is going
19
+ ```
20
+
21
+ 🔴 **A hand-written script is a WAYPOINT, not a destination.** Writing a new one is allowed
22
+ only when the engine genuinely cannot express the check — and "cannot" means MEASURED, not
23
+ assumed. Two things that sound like limits and are not:
24
+
25
+ - _"ESLint only sees one file"_ — true of its AST, false of the rule: a rule is an ordinary
26
+ JS module and may call `execFileSync("git", …)` or read a sibling file. If the reason to
27
+ stay a script is "it needs git", that reason is weak; measure the real cost before using it.
28
+ - _"this runs programs, not lints files"_ — that is a real limit, and the answer is the seam
29
+ already proven here: **a script PRODUCES facts into a JSON file, and ESLint JUDGES that
30
+ file.** The verdict lands in the engine even though the work did not.
31
+
32
+ ## 📊 STATE — measured 2026-09-16 (re-measure, never cite)
33
+
34
+ | | |
35
+ | ----------------------- | -----------------------------------------------------------------------------------------: |
36
+ | ESLint rules | **5** — `latex-language` · `tex-build` · `papers` · `review-findings-cause` · `doc-fields` |
37
+ | harnesses | **57** |
38
+ | mutation batteries | **34** |
39
+ | skills | **24** |
40
+ | hooks (runnable `.mjs`) | **5** |
41
+ | repo-wide scripts | 5 |
42
+ | files tracked / commits | 308 / 43 |
43
+
44
+ **A real consumer dogfoods this package on every CI run**, so a breaking change here turns a
45
+ paper pipeline red somewhere else the same day. That is deliberate — it is the only thing
46
+ keeping the extraction honest.
47
+
48
+ ⚠️ **This table is a SNAPSHOT, not a fact about today, and it has already gone stale once.**
49
+ The line standing here until 2026-09-16 said "two ESLint rule modules" while six were shipped,
50
+ 24 skills had moved in, and hooks existed at all. Re-measure with `git ls-files` before
51
+ repeating any number from it.
52
+
53
+ ## First command in a fresh container
54
+
55
+ ```bash
56
+ npm install
57
+ ```
58
+
59
+ Not optional and not "when something breaks": `vigiles` is a real dependency, and every
60
+ harness, spec and hook resolves through it. A container where `npm install` never ran fails
61
+ in ways that look like broken code rather than a missing install.
62
+
63
+ ## The ten rules that decide what may live here — and what may not be written
64
+
65
+ **1. Mechanism goes to vigiles, data stays here.** A file that names nothing local — no rule
66
+ of ours, no fixture of ours — is machinery, and machinery belongs in
67
+ [vigiles](https://github.com/zernie/vigiles). Ask it in two steps: is this mechanism or data?
68
+ If mechanism — does it know about _this_ domain? If not, it is not ours.
69
+
70
+ **2. A check over an AST is a LINT RULE, not a script.** If it walks `.ts`/`.js`/`.tex` and
71
+ looks at declarations, names or nodes, write an ESLint rule: it fires in the editor on save,
72
+ gets the AST for free, and has a suppression syntax people already know. Scripts are for
73
+ corpus-wide questions — index connectivity, ratios across many files — not for one file's
74
+ nodes.
75
+
76
+ **3. Every check needs BOTH halves, or it is not tested.** It must FIRE on a planted defect
77
+ and stay QUIET on a clean fixture. A check that has only been seen quiet is
78
+ indistinguishable from a dead one — silence is its success state. Prove the fire half with a
79
+ mutation, and assert the patch actually landed before trusting a green run.
80
+
81
+ **4. `exit 0` with empty output is NOT "clean".** A rule whose glob matched no files reports
82
+ exactly like a rule that passed. Any rule shipped here must be loud when its input set is
83
+ empty. This is the specific defect that blocks stage 1 of the plan: in the source base a
84
+ fresh clone yields RC=0, 652 findings, zero errors — because 19 rules saw no files at all.
85
+
86
+ **5. "It can't be done in the engine" must be MEASURED, not assumed.** Every one of these was
87
+ stated confidently on 2026-09-16 and every one fell to a single command:
88
+
89
+ | the claim | what one command showed |
90
+ | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | "a lint rule can't know a git date, so this stays a script" | a rule is plain JS; `execFileSync("git", …)` is legal in it. The real costs (per-file invocation, `--cache` keyed on content) are solvable, so this was a preference dressed as a limit |
92
+ | "we need our own glob expander" | `fs.globSync` ships in Node 22 and returned the identical set. 31 hand-written lines of regex existed for nothing |
93
+ | "the tool can't run a file that config excludes" | it can, and says so: `matches exclude … — running because you named it` |
94
+
95
+ ⇒ Before writing machinery, **run the thing you are about to replace and paste its output.**
96
+ "I couldn't get it to work" is data about the attempt, not about the tool.
97
+
98
+ **6. A program shipped by this package MUST NOT depend on the consumer's cwd.** Measured
99
+ 2026-09-16: three hooks here read their config as `provide("pkg", "cat package.json")`. The
100
+ consumer's session changed directory into a subfolder for unrelated reasons, `cat` failed, and
101
+ the Bash gate — which fails closed, correctly — denied **every command in that session**,
102
+ including the one that would undo it. The nudges next to it would have failed _silently_,
103
+ which is worse.
104
+
105
+ Resolve paths from the repository root (`git rev-parse --show-toplevel`) or from the module's
106
+ own location, never from where the caller happens to stand. The blast radius of a cwd
107
+ assumption is not this package — it is somebody else's whole session.
108
+
109
+ **7. Do not describe what you have not opened.** A `README` here nearly shipped the line
110
+ "MIT — see LICENSE" on 2026-09-16. There is no `LICENSE` file and `package.json` says
111
+ `UNLICENSED`. Publishing is irreversible and this repo is public: every factual claim in a
112
+ document meant for strangers gets checked against the disk in the same pass that writes it.
113
+
114
+ **8. 🔴 NEVER WRITE A GLOB OR A REGEX INSIDE A BLOCK COMMENT.** An asterisk followed by a
115
+ slash **ends the comment**, wherever it appears — in a path (a folder wildcard), in a regex
116
+ whose last literal is an asterisk (one matching bold markup, for instance), in a quoted
117
+ example. The rest of the comment becomes code, the file stops parsing, and the error points
118
+ at a line further down that is perfectly fine.
119
+
120
+ ⚠️ Note this rule does not quote the sequence either, not even here. A documented example is
121
+ the thing that gets copied into code — and that is exactly how the fourth occurrence
122
+ happened: it was copied out of a comment written to explain the first three.
123
+
124
+ This is not a hypothetical and not a rare slip. **It fired four times in a single session on
125
+ 2026-09-16** — three in the consumer, once here — and each time the diagnosis cost minutes
126
+ because `SyntaxError: Unexpected token '.'` says nothing about comments.
127
+
128
+ | instead of | write |
129
+ | --------------------------------------- | ----------------------------------------------------- |
130
+ | a glob with an asterisk and slash | spell it: "every folder under papers" |
131
+ | a regex with an asterisk before a slash | describe what it matched, in words |
132
+ | an example needing both | put it in a line comment (two slashes), never a block |
133
+
134
+ ⇒ **In a block comment, prose describes the pattern; it never quotes it.** If the exact
135
+ characters matter, they belong in the code or in a line comment beside it.
136
+
137
+ **9. Measure the DEFECT before proposing the fix — and read "this is quick" as a warning.**
138
+ Rule 5 is about the tool you are replacing; this one is about the order of work.
139
+
140
+ 1. **Show the defect**: command output, or a file quote with a line number. A proposed fix with
141
+ no exhibited defect is not a fix, it is a preference.
142
+ 2. **Name the layer and the channel** it touches: ESLint rule · skill · hook · CLI · CI action
143
+ · path resolution. More than one is a conversation, not a commit.
144
+ 3. **An adversarial second pass is encouraged, and it is not free.** Spend it on a fork in the
145
+ road, on anything that goes outward, and on a conclusion you are about to act on.
146
+ 4. **A quick fix is almost never quick** — it is quick to _propose_ precisely because nothing
147
+ was opened.
148
+
149
+ Four proposals were made and withdrawn in one session on 2026-09-17 for exactly this reason —
150
+ [`docs/incidents.md`](docs/incidents.md).
151
+
152
+ **10. Effects live in adapters, and WHERE rpp IS INSTALLED lives in ONE of them.** Rule 6 generalised: the caller's cwd is one case of it. Checking logic — lint rules,
153
+ skills, hooks — must not know its own location, nor its distance from anything else. Every
154
+ answer to _where_ comes from `skills/paper-pipeline/scripts/consumer.mjs`, which adapts per
155
+ channel: own checkout · `node_modules` · plugin cache · CI. A skill naming a script by an
156
+ install-specific path in its own prose walks around that door, and 208 such literals across 190
157
+ lines do exactly that.
158
+
159
+ <!-- The port's path above is resolved by `npm run check` (vigiles lint), since #67. -->
160
+ <!-- vigiles:file skills/paper-pipeline/scripts/consumer.mjs -->
161
+
162
+ The layer rules for `src/` live in [`src/CLAUDE.md`](src/CLAUDE.md) and are enforced by the linter.
163
+
164
+ ⏳ **Still owed: the install-path half** — a lint rule that makes an install-specific path literal
165
+ outside `consumer.mjs` a finding. Prose will not hold this class — four silent breakages happened
166
+ _while_ comments explaining the hazard sat directly above the code
167
+ ([`docs/incidents.md`](docs/incidents.md)).
168
+
169
+ **11. Installing and using rpp must be as smooth as possible.** Count the actions between "I
170
+ want this" and "it works": every command to copy, flag to pass or file to edit is one more place
171
+ to give up. The target is `npm i` plus one command. A per-paper script, a manual TeX install or a
172
+ "now add this to your config" step is a defect in rpp, not a user task. The only exception is a
173
+ choice that really belongs to the user (irreversible, paid, privacy), and then it is named at the
174
+ moment it is asked. The converse holds too: an automatic step that can fail silently is worse
175
+ than an explicit one — it works, or it says loudly that it did not. Measured example of the
176
+ converse: Tectonic installs as one file but silently replaced Times with Latin Modern on a plain
177
+ `article` paper and still exited 0 (#35, #59).
178
+
179
+ ## Before changing the command surface or a delivery channel — read the prior art
180
+
181
+ [`docs/prior-art/`](docs/prior-art/README.md) records how comparable tools solved the same
182
+ problems, each claim with the URL that was checked: Quarto and Vale (the domain and content
183
+ analogues), Biome (one tool, one config, one command), Danger and reviewdog (who decides to
184
+ fail a run), `unicorn/expiring-todo-comments` and Semgrep (checks that read a clock or a diff).
185
+
186
+ It is here as a POINTER and stays a pointer: this file is read on every turn, so it carries
187
+ the instruction and never the evidence — the same split as rules 9 and 10 and `docs/incidents.md`.
188
+
189
+ 🔴 The argument "we need another command for X" is, in every tool examined, an argument that
190
+ the CONFIG is not declaring something. Check that before adding a verb.
191
+
192
+ ## Distribution — one install path: npm, then `paperlint init`
193
+
194
+ `npm i -D paperlint` brings all the code — rules, skills, hooks, scripts. `paperlint init`
195
+ then does what only a command can, because it depends on the project it lands in: it finds the
196
+ papers directory and declares it in `package.json`, links each skill into `.claude/skills/`, writes
197
+ the hook commands into `.claude/settings.json` (vigiles' `mergeRegistrations`, reading
198
+ `plugin/hooks/hooks.json` as the one source), and offers a CI workflow pinned to the installed
199
+ release's tag. `paperlint doctor` reads all of it back. Details: `docs/install.md`.
200
+
201
+ **There is no Claude Code plugin or marketplace entry; it was removed in the release after 1.0.0
202
+ (#82).** Do not bring it back without answering these, each measured:
203
+
204
+ - **A plugin cannot carry the code.** Claude Code runs `npm ci --ignore-scripts` for a plugin only
205
+ when its root holds a `package.json` and a lockfile, with a 60-second timeout, and _"a failed or
206
+ skipped install never blocks the plugin"_: on a slow network the hooks load and fail with
207
+ `cannot find module vigiles`, silently.
208
+ - **The skills need the npm package anyway.** 23 of 24 skills run scripts under
209
+ `paper-pipeline/scripts`, which resolve only through `node_modules/paperlint/`.
210
+ A plugin-only consumer got skills whose first command fails.
211
+ - **Its manifests carried versions nothing updated** (0.0.1 and 0.1.0 while npm was at 1.0.0), and
212
+ Claude Code decides plugin updates from that number.
213
+
214
+ `plugin/hooks/hooks.json` stays where it is: it is not a plugin any more, it is the hook wiring
215
+ `paperlint init` merges into the consumer's settings.
216
+
217
+ ## Delivery — how this repo's contents reach a consumer (measured 2026-09-11)
218
+
219
+ Three channels, each measured on a fixture rather than assumed. The consumer here is the
220
+ private knowledge base this was extracted from; nothing below is specific to it.
221
+
222
+ ### Skills ship as `skills/`, and the consumer SYMLINKS them
223
+
224
+ **Do not move skills to `.claude/skills/` inside this repo.** They live in `skills/`
225
+ (`SHIPPED_SKILLS_DIR` in `skills/paper-pipeline/scripts/consumer.mjs`, read by the linker and the
226
+ install e2e), which is the standard layout. An ecosystem scan of 855 npm packages (by published
227
+ tarball, not repository) found **336 shipping `skills/<n>/SKILL.md` against 15 shipping `.claude/skills/`** — 22 : 1. The
228
+ top of the market is entirely on `skills/`: `@vitejs/devtools-kit` (330 896 downloads/wk),
229
+ `@slidev/cli` (56 809), `anthropics/skills` (175 673 stars).
230
+
231
+ **The consumer's side is a symlink per skill, made by `paperlint init` (`src/link-skills.ts`):**
232
+
233
+ ```
234
+ <consumer>/.claude/skills/<name> -> node_modules/paperlint/skills/<name>
235
+ ```
236
+
237
+ ⚠️ The skill's name in the listing comes from the **link directory's name**, not from
238
+ `name:` in the frontmatter — so the link must be named exactly as the skill.
239
+
240
+ ⚠️ Do NOT rely on a consumer picking `.claude/skills/` up out of `node_modules` on its own.
241
+ It does happen — such a directory is an ordinary nested one — but only **lazily and
242
+ silently**, the first time the agent happens to read a file inside that package. A symlink
243
+ loads at startup, deterministically. Measured both ways on claude 2.1.268.
244
+
245
+ ### Hooks ship as `.mjs`, NEVER as `.hook.ts`
246
+
247
+ Measured on a fixture — one hook, four locations, both halves (an input that must be denied
248
+ and one that must pass), exit code taken without a pipe:
249
+
250
+ | hook location | deny input | allow input |
251
+ | ----------------------------------------------------- | ----------------------- | --------------------------- |
252
+ | `node_modules/<pkg>/.claude/hooks/probe.hook.**mjs**` | RC=2, fires | RC=0, silent |
253
+ | symlink into `node_modules`, `.mjs` | RC=2, fires | RC=0, silent |
254
+ | local control, `.mjs` | RC=2, fires | RC=0, silent |
255
+ | `node_modules/<pkg>/.claude/hooks/probe.hook.**ts**` | RC=2 `cannot be loaded` | **RC=2 `cannot be loaded`** |
256
+
257
+ The last row is not "it blocks the dangerous thing" — it fails to load and therefore blocks
258
+ **everything**, including `echo hi`. A consumer in that state cannot run any Bash command,
259
+ and the one command that would repair it is also Bash.
260
+
261
+ ⇒ **The package ships `.mjs`** — the consumer gets something that loads.
262
+
263
+ (Not established: _why_ the TypeScript loader refuses a path inside `node_modules`. The real
264
+ cause is swallowed by a `catch` in vigiles' `hook-runtime.js`, and calling `loadHookProgram`
265
+ directly measures a different load path — it fails even on the control. Knowing _that_ is
266
+ enough to choose the carrier.)
267
+
268
+ #### 🔴 CORRECTED 2026-09-12, when the first three hooks actually moved: THERE IS NO `.hook.ts` TWIN
269
+
270
+ This section used to promise «the `.hook.ts` source lives HERE and is typechecked HERE; the
271
+ package ships the compiled `.mjs`». Shipping the first three hooks retired that plan, and the
272
+ reason is worth keeping: **a twin can drift from its build, and nothing would notice.** One file
273
+ cannot.
274
+
275
+ What the twin was for was the CAPABILITY CHECK — `vigiles compile` refuses a hook that imports
276
+ anything but `vigiles/hook`, because the import list _is_ the capability surface. That check is a
277
+ function, `checkHookImports`, and `hooks/hooks.harness.mjs` runs it over every shipped
278
+ `.hook.mjs` directly. Same check, applied to the artifact that actually executes, with no second
279
+ file to keep in step. What is lost is `tsc` on the hook body and the typed `e.ctx` — named here
280
+ rather than left as an omission.
281
+
282
+ ⚠️ **`checkHookImports` IS A TEXT REGEX, so it counts an import-shaped sentence in a COMMENT.**
283
+ Measured 2026-09-12: the check failed on `paper-edit-guard.hook.mjs`'s own docblock, which quoted
284
+ a rejected import while explaining why it was rejected. Do not loosen the check to make prose
285
+ fit — it is the same check any future `compile`/`lint` pass applies to the shipped file. Describe
286
+ a forbidden import in words instead of writing one.
287
+
288
+ #### 🔴 AND NOT A THIN SPEC IN THE CONSUMER EITHER — measured, and it is the form that looks right
289
+
290
+ The obvious alternative is a small `.hook.ts` in the consumer that pulls the decision logic out of
291
+ this package. It RUNS — deny input RC=2 with the reason, allow input RC=0 and silent — and it
292
+ cannot be maintained:
293
+
294
+ ```
295
+ $ npx vigiles compile
296
+ ✗ .vigiles/hooks/probe.hook.ts — hook program uses capabilities outside `vigiles/hook`:
297
+ <pkg>/hooks/decide.mjs — only the sanctioned API is allowed (capability = API surface).
298
+ ```
299
+
300
+ `compile` is also what writes the tamper-evident stamp, so a hook it refuses **can never be
301
+ re-stamped** — and the runtime fails CLOSED on a stamp that no longer matches its source:
302
+
303
+ ```
304
+ vigiles: hook … does not match its compiled stamp (tampered).
305
+ … the way out is a FILE WRITE, not a command — this refusal blocks the recompile too.
306
+ ```
307
+
308
+ Measured end to end: editing such a file makes the gate refuse `echo hi`, and the only steady
309
+ state is clearing the stamp to `{}` and running permanently unstamped. Shipping the whole program
310
+ keeps the stamp question from arising (no sidecar ⇒ no check) and pins the source by lockfile
311
+ integrity instead — stronger than a local stamp, since a consumer cannot hand-edit an installed
312
+ tree without the next install reverting it.
313
+
314
+ #### How a consumer wires a shipped hook
315
+
316
+ `.claude/settings.json`, one block per hook, pointing straight into the install — no symlink and
317
+ no compile step on the consumer's side:
318
+
319
+ ```json
320
+ {
321
+ "type": "command",
322
+ "command": "node \"$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js\" hook-runtime run-program \"$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs\""
323
+ }
324
+ ```
325
+
326
+ ⚠️ **A shipped hook cannot import a sibling module of this package** — capability closure being
327
+ the point — so the papers-root resolver is spelled out in all three hook files. Duplication that
328
+ cannot be removed is CHECKED instead: part VII of the harness compares the captured values
329
+ against each other, rather than grepping for a literal (a substring search finds the same text in
330
+ the prose _about_ the value one line above it).
331
+
332
+ ### `vigiles` is a devDependency, and its pin is TIED to the consumer's
333
+
334
+ `vigiles/hook` resolves **upward** from a hook inside a package — measured:
335
+
336
+ ```
337
+ resolve OK -> <consumer>/node_modules/vigiles/dist/hook.js
338
+ ```
339
+
340
+ So this package needs no copy of its own at the consumer's runtime; it needs `vigiles` only
341
+ for its own `vigiles test` and `vigiles compile`. That is `devDependencies`, which `npm i` of
342
+ a dependency does not install. Putting it in `dependencies` risks npm installing a **second**
343
+ copy under `node_modules/paperlint/node_modules/vigiles` whenever the ranges
344
+ drift — two runtimes, two sets of stamps and state.
345
+
346
+ 🔴 **THE PARAGRAPH ABOVE WAS TRUE AND THE INSTALL DID THE OPPOSITE — measured 2026-09-17.**
347
+ `devDependencies` is not the only entry naming `vigiles`: `peerDependencies` names it too, and
348
+ **npm 7+ installs peers automatically**. So every consumer got it anyway, together with its
349
+ transitive weight. `npm pack`, then install the tarball into an empty project:
350
+
351
+ | | packages | `du -sm node_modules` |
352
+ | ------------------------------------------------------- | -------: | --------------------: |
353
+ | peer as declared before | 188 | **142 MB** |
354
+ | `peerDependenciesMeta: { vigiles: { optional: true } }` | 164 | **56 MB** |
355
+
356
+ The 86 MB are `@ast-grep/napi` (51 MB) and `typescript` (23 MB), pulled through `vigiles` — and
357
+ paid for by a consumer who only wants the ESLint rules and never loads a hook.
358
+
359
+ `optional: true` is the entry that matches what this section already argues: the consumer brings
360
+ its own `vigiles` _when it uses the hooks_, and npm stops deciding that for them. Both halves
361
+ measured on the 56 MB tree: `eslint-rules/latex-language.mjs` and
362
+ `skills/paper-pipeline/scripts/pipeline-check.mjs` load and run (RC=0), while
363
+ `hooks/paper-edit-guard.hook.mjs` fails with `ERR_MODULE_NOT_FOUND` — which is this contract
364
+ working, not a defect, exactly as argued below.
365
+
366
+ ⚠️ The consumer in this project's own base is unaffected: it declares `vigiles` itself
367
+ (`devDependencies: ^27.2.0`), so nothing about its tree changes.
368
+
369
+ ### The `.bib` parser is optional for the same reason, and the failure says so out loud
370
+
371
+ `@retorquere/bibtex-parser` is imported at exactly one site
372
+ (`skills/paper-pipeline/scripts/extract-ref-facts.mjs`, and already through a dynamic
373
+ `await import`), and it costs **15 MB of a 56 MB tree**: 9 MB itself, plus
374
+ `wink-eng-lite-web-model` (4 MB, an English NLP model) and `unicode2latex` (2 MB). That is 27%
375
+ of the install for one call that only a consumer extracting bibliography facts ever makes.
376
+
377
+ | | packages | `du -sm node_modules` |
378
+ | ------------------------------------------ | -------: | --------------------: |
379
+ | after the `vigiles` peer was made optional | 164 | 56 MB |
380
+ | parser moved to an optional peer as well | 149 | **39 MB** |
381
+
382
+ 🔴 **`optionalDependencies` is the wrong entry and was tried first** — npm _installs_ those and
383
+ only tolerates failure, so the weight stays. What makes a dependency genuinely opt-in is
384
+ `peerDependencies` + `peerDependenciesMeta: { optional: true }`, the same pair used for `vigiles`.
385
+ It stays in `devDependencies` too, because this package's own harnesses parse `.bib`.
386
+
387
+ ⚠️ A silent skip here would be the worst outcome: a missing checker and a passing one look
388
+ identical, and "the bibliography was not checked" reads as "the bibliography is fine". So the
389
+ absence throws, and the message carries the cure rather than the diagnosis:
390
+
391
+ ```
392
+ parsing .bib requires @retorquere/bibtex-parser — it is declared OPTIONAL because it weighs 15 MB…
393
+ Install: npm i -D @retorquere/bibtex-parser
394
+ Why not our own regex: measured 26.08 — the regex gave 0 entries on both real files…
395
+ ```
396
+
397
+ 🔴 **Therefore the pin here and the pin in the consumer move TOGETHER, in one pass.** A major
398
+ mismatch means a hook compiled by one version is executed by another: the stamp does not
399
+ verify, the hook does not load, and `PreToolUse` refuses every command. That already happened
400
+ in the consumer on 2026-09-10 (25.1.0 -> 27.1.4) and cost real recovery work.
401
+
402
+ ```bash
403
+ npm ls vigiles # prints `invalid` when what is installed does not satisfy the manifest
404
+ ```
405
+
406
+ ⚠️ **What that command does NOT tell you, and the boundary matters because the command reads
407
+ like a freshness check.** It compares what is INSTALLED against this repo's MANIFEST. It says
408
+ nothing about the registry. Measured 2026-09-16: manifest `^27.1.4`, installed `27.1.6`,
409
+ published `27.2.0` — exit code **0**, because the range is satisfied. The repo had been one
410
+ minor behind for days and every local check was green.
411
+
412
+ #### Dependabot carries the half `npm ls` cannot — and its two delays STACK
413
+
414
+ That staleness is why `.github/dependabot.yml` exists here. The reasoning is worth keeping
415
+ because the obvious objection to a bot in this org is already recorded and does NOT apply:
416
+ a bot was switched off in a sibling repository for burning Actions minutes — **that
417
+ repository is private**. This one is public, minutes are free, so the objection does not
418
+ travel. If this repo is ever made private, revisit the file along with it.
419
+
420
+ 🔴 **A new release does NOT wake the bot.** Two delays add up, and the second one is invisible
421
+ until you read the reference:
422
+
423
+ | | default | what the docs say |
424
+ | ------------------- | ---------: | --------------------------------------------------------------------------------------- |
425
+ | `schedule.interval` | — | the check runs on the schedule and only on the schedule |
426
+ | **`cooldown`** | **3 days** | _"a new version is not considered for a version update until 3 days after its release"_ |
427
+
428
+ With the weekly schedule this file shipped with first, the window was **3–10 days**. It is now
429
+ `daily`, and `vigiles` is listed in `cooldown.exclude`, so for THIS package the window is one
430
+ schedule tick.
431
+
432
+ **Why `vigiles` and nothing else is exempt:** the cooldown guards against a release that gets
433
+ yanked hours later. That is a real risk for a third-party package and an empty one for our own
434
+ — we would be the ones yanking it, and we can ship several versions of it in a single day, so
435
+ a three-day hold would have the bot proposing the version from the day before yesterday.
436
+
437
+ 🔴 **And the conclusion is bigger than a cadence knob: for our OWN package no bot schedule is
438
+ the primary path, because no schedule can outrun same-day releases.** The primary path is the
439
+ rule already recorded in the consumer's base — merge a PR in `vigiles`, bump every consumer in
440
+ the same pass. The bot is the backstop for the case that actually bit us: the rule named ONE
441
+ consumer while there were two, and this repo sat forgotten on `^27.1.4`.
442
+
443
+ ⚠️ **What the file does not control**, recorded because the sibling repo already lost a day to
444
+ it: `dependabot.yml` configures _version_ updates only. **Security** updates are a separate
445
+ mechanism driven by advisories and a repository SETTING; their cadence cannot be changed from
446
+ this file, and deleting the file would not stop them.
447
+
448
+ Need it now rather than at the next tick: **Insights → Dependency graph → Dependabot → Check
449
+ for updates**.
450
+
451
+ ## The guard against a green zero
452
+
453
+ Rule 4 is enforced, not asserted: `scripts/rules-see-files.mjs` loads `eslint.config.mjs`,
454
+ lints the repository, and asks ESLint for the effective config of every linted file. A rule
455
+ enabled for **zero** files is named and the script exits 1.
456
+
457
+ ```bash
458
+ node scripts/rules-see-files.mjs # also part of npm run check
459
+ ```
460
+
461
+ It is per RULE, not per glob, and that distinction is the point: a rule can be enabled in one
462
+ block whose glob is empty while a different block is busy, so "some glob matched something" is
463
+ not evidence about the rule you care about. Both halves are tested
464
+ (`scripts/rules-see-files.harness.mjs`) and both directions are mutated
465
+ (`scripts/rules-see-files.mutations.mjs` — under-reporting and over-reporting must die on
466
+ _different_ assertions, or only one half of the guard is really tested).
467
+
468
+ ## Mutations — hand-written batteries are deprecated (#52)
469
+
470
+ The `*.mutations.mjs` batteries (string replacements of source lines, run through
471
+ `lib/mutation-driver.mjs`) are being removed. The idea stays — a test must be seen going red
472
+ when the code breaks — but the vehicle is not this one.
473
+
474
+ - **Do not create a new `*.mutations.mjs`**, and **do not add cases to an existing one.**
475
+ - **Record what a test guards as a comment directly above its assertion** (`// Guards: …`).
476
+ - The rule is enforced, not asked for: `scripts/mutation-batteries-frozen.mjs` (part of
477
+ `npm run check` and CI) fails on a battery missing from `scripts/mutation-batteries.frozen.json`,
478
+ on a listed battery with more or fewer cases than recorded, and on a listed file that is gone.
479
+ The list may only shrink — delete a battery or a case, then delete or lower its entry.
480
+ - The intended replacement is a real mutation-testing tool (StrykerJS) or nothing; that is
481
+ decided in #52, not in a pull request that happens to touch a battery.
482
+
483
+ The batteries that remain still run (`node scripts/run-mutations.mjs`) until #52 retires them.
484
+
485
+ ## Cost
486
+
487
+ ⛽ **This repository is PUBLIC, so its Actions minutes are FREE.** Verified against the API on
488
+ 2026-09-17: `"private": false`, `"visibility": "public"`, and three active workflows — `ci`,
489
+ `dependabot auto-merge`, and Dependabot's own updates runner.
490
+
491
+ 🔴 **This paragraph said the exact opposite until now, and the correction is the lesson, not the
492
+ fact.** It read «This is a **private** repository … Until then there is no CI here, and that is
493
+ deliberate» — both halves false, and false in the file an agent loads FIRST. The flip to public
494
+ happened on 2026-09-12 and _was_ recorded, at `.github/workflows/ci.yml:8-9`, which is a file
495
+ nobody opens before deciding whether there is any CI to check. Reported as issue #6.
496
+
497
+ ⚠️ So the rule this leaves behind is about WHERE a correction lands: a measurement written into
498
+ the artifact it describes is not written down for the reader who needs it. **Status that changes
499
+ what a session DOES belongs in this file**; the workflow header can carry the detail.
500
+
501
+ What stays true, because the reasoning outlives the flip: minutes on a **private** repo come out
502
+ of the account-wide 3000/month shared with every other private repo, and the budget is decided
503
+ **before** the first workflow file, not after the first bill. **If this repository is ever made
504
+ private again, this section and `.github/dependabot.yml` are revisited together** — the bot is
505
+ justified two hundred lines above precisely by these minutes being free.
506
+
507
+ ## Testing
508
+
509
+ ```bash
510
+ npm test # vitest over *.test.ts, then the vigiles harnesses
511
+ npx vitest run <file> # one unit test
512
+ npx vigiles test <file> # one harness
513
+ ```
514
+
515
+ **Two kinds of test, told apart by what the file imports.** A `*.harness.*` file tests the agent
516
+ surface and imports `runHook`, `runHarnessTest` or `runEval` from vigiles; everything else is a plain
517
+ unit test, `*.test.ts`, run by vitest — and new tests are TypeScript. Older harnesses that
518
+ import none of the three are frozen in `scripts/harness-api.frozen.json`, which only shrinks;
519
+ `scripts/harness-api.test.ts` parses every harness's imports and holds both halves (#77). vitest
520
+ exits 1 when no file matches, and it transpiles without type-checking, so `npm run check` runs
521
+ `tsc -p tsconfig.test.json` as its own gate.
522
+
523
+ ⚠️ **Not `vigiles test .`** — the `.` is read as a FILE, the runner dies with
524
+ `ERR_UNSUPPORTED_DIR_IMPORT`, and it still exits 0. See the measured table below.
525
+
526
+ Skills are tested **through vigiles** — a colocated `<skill>.harness.mjs` beside the skill.
527
+ (This read «Skills, if and when they arrive» until 2026-09-17; there are 24 of them under
528
+ `skills/` carrying a `SKILL.md`, and the README's opening line claimed the repository was
529
+ empty — issue #6.) Not through a bespoke script: a home-grown runner here once printed
530
+ confident, byte-identical "clean" verdicts for three different skills that had never loaded.
531
+
532
+ ### Every npm script takes an exclusive lock, and that is not ceremony
533
+
534
+ `npm run *` in this repository goes through `scripts/exclusive.mjs`, which holds
535
+ `.vigiles/exclusive.lock` for the duration. A second gate started while one is running does not
536
+ queue and does not race — it **refuses**, names the holder, and exits 3.
537
+
538
+ 🔴 **The reason is that the mutation batteries edit the working tree in place.** That strategy is
539
+ deliberate (see `lib/mutation-driver.mjs` — copying the repo per mutation costs minutes instead
540
+ of seconds), and its one cost is that any parallel reader sees a source file mid-mutation. The
541
+ resulting failure is **false, non-deterministic, and blames the wrong file**: it reports a broken
542
+ assertion, not a mutation, and it reads as "the suite is flaky". That has already cost a wrong
543
+ conclusion here — two runs in a row produced _different_ error messages and the diagnosis "I broke
544
+ round-diff" was incorrect.
545
+
546
+ A prose instruction "don't run them at the same time" existed and did not work: prose does not
547
+ execute, so it does not apply to the person in the other terminal, the agent, or the editor with
548
+ tests on save. Measured live, with the batteries running:
549
+
550
+ ```
551
+ $ npm test
552
+ 🔴 refused: this repository is busy with a run that EDITS FILES IN PLACE.
553
+ held by: pid 6645, "node scripts/run-mutations.mjs", since 2026-09-17T05:22:28.757Z
554
+ RC=3
555
+ ```
556
+
557
+ ⚠️ A lock left behind by a process that no longer exists is **taken over** with a message, not
558
+ respected. Otherwise one interrupted run would block the repository forever, and the first cure
559
+ anybody reaches for would be "delete the lock by hand" — i.e. switching the mechanism off.
560
+
561
+ ## `npm test` — `--min=1` stays, and here is what it is for
562
+
563
+ The script is `vigiles test --min=1`. It went green on 2026-09-11 when the first harnesses
564
+ landed; before that it correctly exited 1:
565
+
566
+ ```
567
+ ✗ vigiles test: --min=1 but only 0 test file(s) matched — evals never executed
568
+ (check the paths/globs, or that the run was reached).
569
+ ```
570
+
571
+ Do **not** "fix" a future red by dropping `--min` — the flag is the only thing standing
572
+ between "every test passed" and "no test ran", which is rule 4 applied to the test runner
573
+ itself.
574
+
575
+ 🔴 **Two ways this command lies if written differently, both measured 2026-09-11:**
576
+
577
+ | form | what happens | exit |
578
+ | ---------------------- | --------------------------------------------------------------------------- | ----- |
579
+ | `vigiles test .` | `.` is read as a FILE — `ERR_UNSUPPORTED_DIR_IMPORT`, uncaught, runner dies | **0** |
580
+ | `vigiles test` | `No **/*.harness.{mjs,cjs,js,mts,cts,ts} files found.` | **0** |
581
+ | `vigiles test --min=1` | names the empty match and fails | **1** |
582
+
583
+ The first row is the worse one: the runner crashed with a stack trace and still reported
584
+ success. `package.json` shipped `vigiles test .` from the initial scaffold until this was
585
+ measured — so the repo's own test command had never once executed a test, and said nothing.
586
+
587
+ ## Commits
588
+
589
+ Conventional-commit subject, body says what was MEASURED, not what was intended. A number in
590
+ a commit message that no command produced is the thing this repo exists to make impossible.
591
+
592
+ **The subject is also the release.** Every push to `main` runs semantic-release
593
+ (`.github/workflows/release.yml`), and it reads the squash commit, which is the PR title:
594
+ `feat:` → minor, `fix:` or `perf:` → patch, `!` or a `BREAKING CHANGE:` footer → major,
595
+ `docs:` `chore:` `ci:` `test:` `refactor:` → no release. `pr-title.yml` rejects a title without
596
+ a conventional prefix, and for a one-commit PR checks the commit subject too, since GitHub
597
+ squashes to that. npm, the git tag and the GitHub Release always carry the same version. Never
598
+ `npm publish` by hand.