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,455 @@
1
+ /**
2
+ * `paper/stages` — the stage a paper has reached is an author's CLAIM, so it is a FIELD, and
3
+ * the bytes on disk are the evidence that claim is checked against.
4
+ *
5
+ * ── WHAT THIS REPLACES, and why the replacement is not cosmetic ─────────────────────────
6
+ * The predecessor kept a vocabulary of stage names in a JS const and applied its regexes to
7
+ * the WHOLE of `PIPELINE-STATUS.md`:
8
+ *
9
+ * { stage: "submitted", declaredBy: /(?<!\bas\s)(?<!\bbe\s)\bsubmitted\b/i }
10
+ *
11
+ * The lookbehinds are not decoration — they were added after the only match in one paper's
12
+ * file turned out to be a reviewer's idiom, "Weak Accept as submitted", which says nothing
13
+ * about submission. The check then demanded a frozen pdf and was RIGHT BY ACCIDENT.
14
+ *
15
+ * 🔴 And it stayed wrong in the other direction, measured 2026-09-16 on the live corpus:
16
+ * `versions/2026-08-29-camera-ready.pdf` had been on disk for 16 days at 616 175 bytes while
17
+ * the `camera-ready` pattern matched ZERO times anywhere in the file. The artefact preceded
18
+ * the declaration, so all three checks gated on that declaration were blind by construction.
19
+ *
20
+ * ── TWO DIRECTIONS, and neither alone is the check ──────────────────────────────────────
21
+ * declared -> bytes a record without its file, or with the wrong size, is a false claim
22
+ * bytes -> declared a frozen version nobody declared is the case above, and it is also
23
+ * what stops the whole rule being switched off by deleting a line
24
+ *
25
+ * Absence of the field is therefore LEGITIMATE — a paper that has shipped nothing owes
26
+ * nothing — without being an escape hatch: the second direction still speaks if bytes exist.
27
+ * That asymmetry is deliberate and is the reason this rule does not copy `doc/fields`, where
28
+ * a missing frontmatter IS the finding.
29
+ *
30
+ * ── WHY A FIELD CANNOT BECOME A TICKED BOX HERE ─────────────────────────────────────────
31
+ * The usual objection to turning a check into a field is that a field gets ticked instead of
32
+ * the work being done. It does not apply, and the reason is the direction of the incentive:
33
+ * a tick normally REMOVES an obligation, while `stages` CREATES them — declare a stage and
34
+ * you owe a pdf of the right size. Nobody writes a line in order to receive findings. The
35
+ * real-world pressure is to UNDER-declare, which is exactly what direction two catches.
36
+ *
37
+ * ── THE FIELD IS THE ONLY MACHINE-READABLE CARRIER ──────────────────────────────────────
38
+ * ⚠️ A paper's status file also carries a `State:` line in its header and one or two
39
+ * scorecard tables. Those are DISPLAY. No consistency check between them is written here on
40
+ * purpose: one paper in the source corpus carries two scorecards in two different column
41
+ * formats, and a rule reconciling them would drown in noise within a day.
42
+ *
43
+ * ── A LIST, NOT A MAP, and that is load-bearing ─────────────────────────────────────────
44
+ * A paper can reach the same stage twice: one in the source corpus was submitted to a venue
45
+ * in August, rejected in September, and is being resubmitted elsewhere in October. A map
46
+ * keyed by stage name holds one. The `versions/<date>-<stage>.pdf` convention already holds
47
+ * many, and the field must not be weaker than the filenames it describes.
48
+ */
49
+ import { existsSync, statSync, readdirSync } from "node:fs";
50
+ import { join, dirname } from "node:path";
51
+ import { load } from "js-yaml";
52
+
53
+ const STAGES = ["submitted", "camera-ready", "arxiv"];
54
+
55
+ /**
56
+ * A YAML date without quotes parses to a `Date`, not a string — js-yaml honours YAML 1.1
57
+ * timestamps. Comparing `Date === "2026-08-29"` is silently false, so every date is
58
+ * normalised before it is compared with one taken from a filename.
59
+ */
60
+ function isoDate(v) {
61
+ if (v instanceof Date) return v.toISOString().slice(0, 10);
62
+ if (typeof v === "string") return /^\d{4}-\d{2}-\d{2}/.exec(v)?.[0] ?? "";
63
+ return "";
64
+ }
65
+
66
+ /** Frozen versions on disk, as `{name, date, stage}`. */
67
+ function frozenPdfs(versionsDir) {
68
+ let names;
69
+ try {
70
+ names = readdirSync(versionsDir);
71
+ } catch {
72
+ return [];
73
+ }
74
+ const out = [];
75
+ for (const name of names) {
76
+ if (!name.endsWith(".pdf")) continue;
77
+ // ⚠️ A name carrying `STALE` is a version WITHDRAWN on purpose — one such file records
78
+ // that the wrong pdf sat in the folder for a month. Demanding a declaration for it would
79
+ // turn a deliberate record of a mistake into a finding.
80
+ if (name.includes("STALE")) continue;
81
+ const m = /^(\d{4}-\d{2}-\d{2})-(.+)\.pdf$/.exec(name);
82
+ if (!m) continue;
83
+ if (!STAGES.includes(m[2])) continue;
84
+ out.push({ name, date: m[1], stage: m[2] });
85
+ }
86
+ return out;
87
+ }
88
+
89
+ export default {
90
+ rules: {
91
+ stages: {
92
+ meta: {
93
+ type: "problem",
94
+ docs: {
95
+ description:
96
+ "a paper's stage is declared as a FIELD, and every declaration is checked against the bytes on disk in both directions",
97
+ },
98
+ schema: [],
99
+ messages: {
100
+ badYaml: "the frontmatter does not parse as YAML: {{reason}}",
101
+ notAList:
102
+ "`stages` must be a LIST of entries, not {{got}} — a paper can reach the same stage twice",
103
+ badStage: "unknown stage «{{stage}}» — the vocabulary is: {{known}}",
104
+ missingKey: "the «{{stage}}» entry has no `{{key}}` field",
105
+ badDate:
106
+ "the date «{{date}}» in the «{{stage}}» entry is not YYYY-MM-DD",
107
+ declaredNoFile:
108
+ "stage «{{stage}}» ({{date}}) is declared, but `{{pdf}}` is not on disk",
109
+ bytesDiffer:
110
+ "«{{stage}}» ({{date}}): {{want}} bytes declared, {{got}} on disk — this is NOT that file",
111
+ fileNotDeclared:
112
+ "`versions/{{file}}` is frozen, but no «{{stage}}» stage on {{date}} is declared in `stages` — the artefact ran ahead of the declaration",
113
+ },
114
+ },
115
+ create(context) {
116
+ const dir = dirname(context.filename);
117
+ let declared = null; // null = there was no frontmatter at all
118
+
119
+ return {
120
+ yaml(node) {
121
+ let data;
122
+ try {
123
+ data = load(node.value ?? "");
124
+ } catch (e) {
125
+ context.report({
126
+ node,
127
+ messageId: "badYaml",
128
+ data: { reason: String(e.message) },
129
+ });
130
+ return;
131
+ }
132
+ const raw = data?.stages;
133
+ if (raw === undefined) return;
134
+ if (!Array.isArray(raw)) {
135
+ context.report({
136
+ node,
137
+ messageId: "notAList",
138
+ data: { got: raw === null ? "empty" : typeof raw },
139
+ });
140
+ return;
141
+ }
142
+ declared = [];
143
+ for (const rec of raw) {
144
+ const stage = String(rec?.stage ?? "");
145
+ if (!STAGES.includes(stage)) {
146
+ context.report({
147
+ node,
148
+ messageId: "badStage",
149
+ data: { stage, known: STAGES.join(" · ") },
150
+ });
151
+ continue;
152
+ }
153
+ let complete = true;
154
+ for (const key of ["date", "pdf", "bytes"]) {
155
+ if (rec[key] === undefined) {
156
+ context.report({
157
+ node,
158
+ messageId: "missingKey",
159
+ data: { stage, key },
160
+ });
161
+ complete = false;
162
+ }
163
+ }
164
+ if (!complete) continue;
165
+ const date = isoDate(rec.date);
166
+ if (date === "") {
167
+ context.report({
168
+ node,
169
+ messageId: "badDate",
170
+ data: { stage, date: String(rec.date) },
171
+ });
172
+ continue;
173
+ }
174
+ declared.push({
175
+ stage,
176
+ date,
177
+ pdf: String(rec.pdf),
178
+ bytes: Number(rec.bytes),
179
+ });
180
+
181
+ // ── direction one: a claim owes its bytes ────────────────────────────────
182
+ const abs = join(dir, String(rec.pdf));
183
+ if (!existsSync(abs)) {
184
+ context.report({
185
+ node,
186
+ messageId: "declaredNoFile",
187
+ data: { stage, date, pdf: String(rec.pdf) },
188
+ });
189
+ continue;
190
+ }
191
+ const got = statSync(abs).size;
192
+ if (got !== Number(rec.bytes)) {
193
+ context.report({
194
+ node,
195
+ messageId: "bytesDiffer",
196
+ data: {
197
+ stage,
198
+ date,
199
+ want: String(rec.bytes),
200
+ got: String(got),
201
+ },
202
+ });
203
+ }
204
+ }
205
+ },
206
+
207
+ // ── direction two: bytes owe their declaration ────────────────────────────────
208
+ // 🔴 On `root:exit` rather than inside `yaml`, because the case worth catching is
209
+ // precisely the one with NO frontmatter at all — where the `yaml` visitor never runs.
210
+ "root:exit"(node) {
211
+ const records = declared ?? [];
212
+ for (const f of frozenPdfs(join(dir, "versions"))) {
213
+ if (records.some((r) => r.stage === f.stage && r.date === f.date))
214
+ continue;
215
+ context.report({
216
+ node,
217
+ messageId: "fileNotDeclared",
218
+ data: { file: f.name, stage: f.stage, date: f.date },
219
+ });
220
+ }
221
+ },
222
+ };
223
+ },
224
+ },
225
+
226
+ /**
227
+ * `paper/source` — a declared stage must have its SOURCE frozen on disk, beside the pdf,
228
+ * and the bytes must match. Not a commit reference. Not a hash of one.
229
+ *
230
+ * ── WHY NOT A COMMIT, measured 2026-09-16 and it is not a close call ──────────────────
231
+ * The predecessor recorded provenance as `commit <sha>` in prose and checked that the sha
232
+ * RESOLVED. Its stated premise was "git holds those bytes immutably, materialising a copy
233
+ * duplicates a guarantee we already have". That premise is false wherever branches are
234
+ * SQUASH-merged: the squash destroys the branch commits, and the next `git gc` removes
235
+ * the objects.
236
+ *
237
+ * 🔴 Measured on the live corpus, inside ninety minutes of ONE session: two recorded shas
238
+ * resolved, then stopped resolving after a routine `gc` following a branch reset. Of the
239
+ * four declared stages in that corpus, THREE had lost their source entirely — including
240
+ * papers already submitted to a venue, which is exactly the case the check existed for
241
+ * (a reviewer cites a line number and there is no layout left to resolve it against).
242
+ *
243
+ * So a sha is not a pointer to bytes; it is a pointer to a pointer, and the outer one
244
+ * evaporates. Checking it verifies that the REFERENCE is alive, which is a different
245
+ * claim from the one anybody wants.
246
+ *
247
+ * ── COST, because "duplicating" was the objection ─────────────────────────────────────
248
+ * A paper source is 57-68 KB of LaTeX beside a pdf of 305-382 KB that is already
249
+ * committed. Freezing it adds under a fifth to what the folder holds anyway, and turns a
250
+ * claim about the world into bytes this rule can actually compare.
251
+ *
252
+ * ── SEVERITY: nudge, and the reason CHANGED ──────────────────────────────────────────
253
+ * Still `warn` at the consumer, but no longer because "only the author knows". Now it is
254
+ * because a stage frozen BEFORE this convention existed cannot be fixed at all — those
255
+ * bytes are gone. Failing a build over unrecoverable history is a gate nobody can clear.
256
+ */
257
+ /**
258
+ * `paper/author-list` — a shipped paper OWES ITSELF a run of the author-list cross-check.
259
+ *
260
+ * The class this cross-check catches is invisible to an existence check on citations: the
261
+ * citation exists, the identifier resolves, and the authors are taken from the PREPRINT
262
+ * while the entry declares a conference. On the live corpus this turned up seven records
263
+ * across three papers, including a DROPPED LIVING PERSON (`schick2023toolformer` — Eric
264
+ * Hambro is missing; the NeurIPS version has nine authors, the preprint has eight). Two of
265
+ * the seven were found on a paper ALREADY SUBMITTED.
266
+ *
267
+ * 🔴 WHAT THE MOVE FIXED, and this is a measurement, not taste. The predecessor derived the
268
+ * declared stage with a REGEX OVER THE PROSE of that same file. Remeasured 2026-09-17 on the
269
+ * live corpus: for `agenticdev-2026` the prose reads `submitted`, the frontmatter declares
270
+ * `submitted, camera-ready` — the pattern `/camera-ready (?:uploaded|submitted|shipped)/i`
271
+ * does not catch the form the stage is actually recorded in. Today's set of findings did not
272
+ * change from this (both checks only asked "is there ANY stage at all"), but the MESSAGE
273
+ * printed the wrong list of stages. The subject here is the same `stages` field that
274
+ * `paper/stages` already checks against the bytes in both directions.
275
+ *
276
+ * 🔴 THE MARKER IS LOOKED FOR IN TABLE CELLS, NOT GREPPED OVER THE FILE. The first draft did
277
+ * `context.sourceCode.text.includes(marker)` and justified it with a comment saying "a
278
+ * footnote cell has no node of its own". That turned out to be SIMPLY WRONG — a 2026-09-17
279
+ * measurement showed the markdown parser hands back `table`, `tableRow` and `tableCell`
280
+ * (twelve cells on a three-row table). The base rule says, word for word, "parse markdown
281
+ * with a parser".
282
+ *
283
+ * Parsing is also STRICTER than grep, and the difference is real: a marker mentioned in
284
+ * prose outside the scorecard — in a heading, in a paragraph saying "still need to run
285
+ * bib-authors", in a quote from someone else — no longer counts as a record of a run. To
286
+ * grep, those three cases are indistinguishable from a genuine record.
287
+ *
288
+ * ⚠️ WHAT THIS STILL DOES NOT FIX: inside the cell the evidence is still PROSE, and a run
289
+ * phrased in different words will not be seen by the rule. The real fix is a frontmatter
290
+ * field (`gates.cites.ran`), not a smarter text search; that's separate work, touching four
291
+ * live scorecards.
292
+ *
293
+ * So severity is set by the CONSUMER, and the default is not `error`: the proof of a run is
294
+ * prose, and a false positive at a blocking level costs more than a miss.
295
+ */
296
+ "author-list": {
297
+ meta: {
298
+ type: "suggestion",
299
+ docs: {
300
+ description:
301
+ "a paper that declares a stage records that the author-list check ran — a class the existence check cannot see",
302
+ },
303
+ schema: [
304
+ {
305
+ type: "object",
306
+ properties: {
307
+ // The run marker in the scorecard. This is consumer data: the package cannot
308
+ // know EXACTLY how a given consumer records that the cross-check happened.
309
+ marker: { type: "string" },
310
+ // What to run. This is a CONSUMER-SPECIFIC PATH, and it has no place in a public
311
+ // package: the predecessor hardcoded
312
+ // `.claude/skills/verify-citations/scripts/bib-authors.mjs` right into the message text.
313
+ command: { type: "string" },
314
+ },
315
+ additionalProperties: false,
316
+ },
317
+ ],
318
+ messages: {
319
+ neverRan:
320
+ "stage «{{stages}}» is declared, but the scorecard records no author-list run (looked for «{{marker}}» in its table). It catches what an existence check cannot see: the citation resolves, the id resolves, and the authors are the PREPRINT's while the entry declares a conference{{how}}",
321
+ },
322
+ },
323
+ create(context) {
324
+ const opts = context.options?.[0] ?? {};
325
+ const marker = opts.marker ?? "bib-authors";
326
+ const command = opts.command ?? "";
327
+
328
+ // The verdict is deferred to the end of the file: the frontmatter node arrives FIRST,
329
+ // and the table comes after it. Reporting on `yaml` would mean a verdict rendered
330
+ // without ever seeing the scorecard.
331
+ let declaredAt = null;
332
+ let stages = [];
333
+ let recorded = false;
334
+
335
+ return {
336
+ yaml(node) {
337
+ let data;
338
+ try {
339
+ data = load(node.value ?? "");
340
+ } catch {
341
+ return; // `paper/stages` has already reported the unreadable YAML
342
+ }
343
+ const raw = data?.stages;
344
+ if (!Array.isArray(raw)) return;
345
+ stages = raw.map((r) => r?.stage).filter(Boolean);
346
+ // One guard, not two: `raw.length === 0` would be a SPECIAL CASE of this same
347
+ // condition, and a mutation against it would turn out unkillable — the second guard
348
+ // mutes it. This also covers a non-empty list made of records with no `stage` field.
349
+ if (stages.length === 0) return; // nothing shipped — nothing is owed
350
+ declaredAt = node;
351
+ },
352
+
353
+ // The evidence is a SCORECARD CELL, not any occurrence of the string in the file. It
354
+ // has a node of its own; the first draft claimed otherwise and grepped the whole text.
355
+ tableCell(node) {
356
+ if (recorded) return;
357
+ if (context.sourceCode.getText(node).includes(marker))
358
+ recorded = true;
359
+ },
360
+
361
+ "root:exit"() {
362
+ if (!declaredAt || recorded) return;
363
+ context.report({
364
+ node: declaredAt,
365
+ messageId: "neverRan",
366
+ data: {
367
+ stages: stages.join("/"),
368
+ marker,
369
+ how: command ? `. Run: ${command}` : "",
370
+ },
371
+ });
372
+ },
373
+ };
374
+ },
375
+ },
376
+
377
+ source: {
378
+ meta: {
379
+ type: "problem",
380
+ docs: {
381
+ description:
382
+ "a declared stage freezes its source beside the pdf and is checked by bytes — not by a commit reference",
383
+ },
384
+ schema: [],
385
+ messages: {
386
+ noSource:
387
+ "stage «{{stage}}» ({{date}}) carries no frozen source. A commit reference will not do: squash and gc destroy it — three of four sources were lost that way in this corpus",
388
+ sourceMissing:
389
+ "«{{stage}}» ({{date}}): source `{{src}}` is declared, but the file is not on disk",
390
+ sourceBytes:
391
+ "«{{stage}}» ({{date}}): the source is declared as {{want}} bytes, {{got}} on disk — this is NOT that file",
392
+ lostAcknowledged:
393
+ "stage «{{stage}}» ({{date}}): the source is declared LOST. There is nothing left to match a build against a reviewer's line — if a copy turns up, put it in versions/ and clear the flag",
394
+ },
395
+ },
396
+ create(context) {
397
+ const dir = dirname(context.filename);
398
+ return {
399
+ yaml(node) {
400
+ let data;
401
+ try {
402
+ data = load(node.value ?? "");
403
+ } catch {
404
+ return; // `paper/stages` has already reported the unreadable YAML
405
+ }
406
+ const raw = data?.stages;
407
+ if (!Array.isArray(raw)) return;
408
+ for (const rec of raw) {
409
+ const stage = String(rec?.stage ?? "");
410
+ if (!STAGES.includes(stage)) continue;
411
+ const date = isoDate(rec?.date);
412
+
413
+ // Acknowledging the loss is a RECORD, not an exemption: the rule keeps speaking,
414
+ // because the state stays defective, just unfixable today.
415
+ if (rec?.sourceLost === true) {
416
+ context.report({
417
+ node,
418
+ messageId: "lostAcknowledged",
419
+ data: { stage, date },
420
+ });
421
+ continue;
422
+ }
423
+ const src = rec?.source === undefined ? "" : String(rec.source);
424
+ if (src === "") {
425
+ context.report({
426
+ node,
427
+ messageId: "noSource",
428
+ data: { stage, date },
429
+ });
430
+ continue;
431
+ }
432
+ const abs = join(dir, src);
433
+ if (!existsSync(abs)) {
434
+ context.report({
435
+ node,
436
+ messageId: "sourceMissing",
437
+ data: { stage, date, src },
438
+ });
439
+ continue;
440
+ }
441
+ const got = statSync(abs).size;
442
+ const want = Number(rec?.sourceBytes);
443
+ if (Number.isFinite(want) && got !== want)
444
+ context.report({
445
+ node,
446
+ messageId: "sourceBytes",
447
+ data: { stage, date, want: String(want), got: String(got) },
448
+ });
449
+ }
450
+ },
451
+ };
452
+ },
453
+ },
454
+ },
455
+ };
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Battery for `paper/stages` and `paper/source` — four mutations, chosen so that each one
3
+ * removes ITS OWN load-bearing property, rather than just breaking the file.
4
+ *
5
+ * 🔴 Why the battery matters here specifically. For both rules the success state is SILENCE, and
6
+ * the harness catches them on fixtures that it lays out itself. "It passed" and "it cannot fail"
7
+ * look identical from the outside; the battery is the only thing that tells them apart.
8
+ *
9
+ * The first two mutations hit the TWO DIRECTIONS of `paper/stages`, and this is not symmetry for
10
+ * its own sake: without the second direction the rule turns itself off by deleting the
11
+ * frontmatter — no declarations, so no mismatches, so green. The `declared-without-bytes`
12
+ * mutation and the `bytes-without-declaration` mutation must die on DIFFERENT asserts, or only
13
+ * one half is proven.
14
+ */
15
+ import { fileURLToPath } from "node:url";
16
+ import { dirname, join, resolve } from "node:path";
17
+ import { runMutations } from "../lib/mutation-driver.mjs";
18
+
19
+ const HERE = dirname(fileURLToPath(import.meta.url));
20
+ const ROOT = resolve(HERE, "..");
21
+ const RULE = join(HERE, "paper-stages.mjs");
22
+ const HARNESS = join(HERE, "paper-stages.harness.mjs");
23
+
24
+ process.exit(
25
+ runMutations({
26
+ root: ROOT,
27
+ runner: "node",
28
+ cases: [
29
+ {
30
+ name: "the DECLARED→BYTES direction stops checking",
31
+ harness: HARNESS,
32
+ expect: "wrong byte count is reported",
33
+ disables:
34
+ "the check of declared `bytes` against the file on disk — a stage could declare any " +
35
+ "number, and the pdf underneath it could be anything",
36
+ edits: [[RULE, "if (got !== Number(rec.bytes)) {", "if (false) {"]],
37
+ },
38
+ {
39
+ name: "the BYTES→DECLARED direction stops checking",
40
+ harness: HARNESS,
41
+ expect: "a frozen version nobody declared is reported",
42
+ disables:
43
+ "the second half — the one without which the rule is muted by deleting the " +
44
+ "frontmatter: no declarations ⇒ no mismatches ⇒ green",
45
+ edits: [
46
+ [
47
+ RULE,
48
+ "if (records.some((r) => r.stage === f.stage && r.date === f.date))\n continue;",
49
+ "if (true)\n continue;",
50
+ ],
51
+ ],
52
+ },
53
+ {
54
+ name: "`paper/source` stops checking the source's size",
55
+ harness: HARNESS,
56
+ expect: "a byte mismatch on the source is reported",
57
+ disables:
58
+ "the IDENTITY check on a frozen source: `existsSync` answers 'a file exists', " +
59
+ "and only the size answers 'this is that exact file'",
60
+ edits: [
61
+ [RULE, "if (Number.isFinite(want) && got !== want)", "if (false)"],
62
+ ],
63
+ },
64
+ {
65
+ name: "acknowledging a loss starts EXEMPTING instead of recording",
66
+ harness: HARNESS,
67
+ expect: "an acknowledged loss is still reported, not silenced",
68
+ disables:
69
+ "the decision that `sourceLost` is a RECORD, not an indulgence: the rule must keep " +
70
+ "speaking, because the state is still defective, merely unfixable today",
71
+ edits: [
72
+ [
73
+ RULE,
74
+ "if (rec?.sourceLost === true) {",
75
+ "if (rec?.sourceLost === true && false) {",
76
+ ],
77
+ ],
78
+ },
79
+ {
80
+ // 🔴 A REGRESSION BACK TO GREP. The rule's first draft did exactly this and justified it
81
+ // by saying "a footnote cell has no node of its own" — a measurement showed the
82
+ // opposite: the parser hands back `tableCell`. The mutation is caught by ONE fixture out
83
+ // of six: in the rest the marker already sits in a cell, so grep and parsing are
84
+ // indistinguishable. Without `marker-in-prose` this regression would have passed
85
+ // silently, and the rule would again read an intention as a fact.
86
+ name: "the evidence is grepped over the WHOLE FILE again",
87
+ harness: HARNESS,
88
+ expect: "a marker in prose OUTSIDE the table is not a record of a run",
89
+ disables:
90
+ "parsing in favor of a string search: 'still need to run bib-authors' — an intention, " +
91
+ "not a record — counts as a run again, and the paper silently stops being a debtor",
92
+ edits: [
93
+ [
94
+ RULE,
95
+ "if (context.sourceCode.getText(node).includes(marker))\n recorded = true;",
96
+ "if (context.sourceCode.text.includes(marker))\n recorded = true;",
97
+ ],
98
+ ],
99
+ },
100
+ {
101
+ name: "the rule stops requiring an author cross-check run",
102
+ harness: HARNESS,
103
+ expect: "a stage is declared, no run — a finding",
104
+ disables:
105
+ "the debt itself: a shipped paper no longer owes anything, and the rule stays silent " +
106
+ "across the whole corpus — silence is its success state, so from the outside this is " +
107
+ "indistinguishable",
108
+ // Target retargeted 09-17: the old line disappeared when the rule moved to parsing, and
109
+ // the driver honestly said UNUSABLE — "the patch did not land", not "the test is weak".
110
+ // Now the mutation declares the run recorded before the file is even read.
111
+ edits: [[RULE, "let recorded = false;", "let recorded = true;"]],
112
+ },
113
+ {
114
+ name: "the exemption for an unshipped paper becomes WIDER than it should",
115
+ harness: HARNESS,
116
+ expect: "an empty stage list — silent",
117
+ disables:
118
+ "the distinction between 'no stages' and 'there are stages': a draft starts getting a " +
119
+ "finding, and a rule that scolds drafts gets turned off within a week",
120
+ edits: [
121
+ [
122
+ RULE,
123
+ "if (stages.length === 0) return; // nothing shipped — nothing is owed",
124
+ "",
125
+ ],
126
+ ],
127
+ },
128
+ {
129
+ name: "the stage list is taken from somewhere OTHER than the field again",
130
+ harness: HARNESS,
131
+ expect:
132
+ "the stage list in the message comes from the FIELD and carries all of them",
133
+ disables:
134
+ "the exact reason the move was made. The predecessor derived the stage with a regex " +
135
+ "over the prose, and on agenticdev printed `submitted` where `submitted, " +
136
+ "camera-ready` was declared. The mutation brings back a hardcoded list — the set of " +
137
+ "findings does not change, only the TEXT lies, and without this assert the regression " +
138
+ "would have passed silently",
139
+ edits: [[RULE, 'stages: stages.join("/"),', 'stages: "submitted",']],
140
+ },
141
+ {
142
+ name: "a consumer-specific path comes back into the package's text",
143
+ harness: HARNESS,
144
+ expect:
145
+ "the run command comes in as an option and lands in the message",
146
+ disables:
147
+ "the boundary 'the mechanism goes in the package, the data stays with the consumer': " +
148
+ "the command stops arriving as an option. The predecessor hardcoded " +
149
+ "`.claude/skills/verify-citations/...` — the path of one private repository — right " +
150
+ "into a public rule's message",
151
+ edits: [
152
+ [RULE, 'const command = opts.command ?? "";', 'const command = "";'],
153
+ ],
154
+ },
155
+ ],
156
+ }),
157
+ );