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,397 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * artifact-coverage.mjs — does the released bundle actually contain what the paper points at?
4
+ *
5
+ * node artifact-coverage.mjs <paper-dir> report
6
+ * node artifact-coverage.mjs <paper-dir> --flags-only for hooks and pre-commit
7
+ *
8
+ * 🔴 WHY THIS EXISTS. On 2026-08-06, hours before a submission, the author asked a plain question —
9
+ * "did we ever actually run the artifact?" — and the answer exposed that the released bundle held NO
10
+ * DATA for the paper's §5.2: the agent experiment behind "stopped and asked a human in 34 of 48
11
+ * gated runs". That result is in the ABSTRACT, it is the single reason an agent workshop is the
12
+ * right venue for the paper, and the artifact reference promises "every data file behind the
13
+ * numbers in this paper".
14
+ *
15
+ * Six `harden-paper` passes and five review panels had not caught it, and none of them was broken.
16
+ * They were all asking a different question:
17
+ *
18
+ * the anonymity gate asks — does anything in the bundle leak an identity?
19
+ * the numbers gate asks — does a printed number match its data file?
20
+ * the artifact reviewer asks— does the code in the bundle run?
21
+ * NOBODY asked — is the data for this experiment IN THE BUNDLE AT ALL?
22
+ *
23
+ * A missing directory passes every one of those: nothing leaks, nothing mismatches, and everything
24
+ * present still runs. Absence is invisible to a checker that only inspects what is there.
25
+ *
26
+ * The two checks below are deliberately dumb, because dumb is what survives:
27
+ * 1. every body section that REPORTS A NUMBER is named somewhere in the artifact's index;
28
+ * 2. every path the index names EXISTS in the bundle.
29
+ *
30
+ * ── 2026-08-10: THE THIRD CHECK RUNS THE OTHER WAY ────────────────────────────────────────────
31
+ *
32
+ * 🔴 Both checks above run FORWARD, from the paper outward, and neither can see the opposite
33
+ * failure: a result that EXISTS ON DISK and that the paper never mentions. Ported from
34
+ * `check_artifact_coverage.py` in MedSci Skills (Nam, Jeong & Kim, arXiv:2606.09500v4, tag
35
+ * v3.8.0), where it is a Major verdict named `DISK_UNREPORTED` and defined as
36
+ *
37
+ * "an analysis output that exists on disk … is never mentioned in the manuscript. The work was
38
+ * done and run but its result, which may contradict the headline, is silently absent."
39
+ *
40
+ * That is a CHERRY-PICKING DETECTOR, and it is the one direction no other gate in this pipeline
41
+ * covers. `check-anon` reads the bundle. `paper_numbers` reads printed quantities. `arm_permutation`
42
+ * reads a quantity against its arm label. Every one of them starts from something the paper says.
43
+ * An experiment that was run and quietly dropped says nothing, so nothing looks at it.
44
+ *
45
+ * ⚠️ WHAT WE PORTED IS THE MECHANISM, NOT THE PAPER'S HEADLINE. That paper's 27/27-versus-11/27
46
+ * result does not survive its own artifact: seven of the 27 defects were in files never shown to
47
+ * the model, and one of its eleven "hits" is a substring match on the word `random` in a sentence
48
+ * about random forests. The honest number is 10/27, and 10/20 on what the model could see. Read
49
+ * `the author's private research notes` §2.4 before citing
50
+ * anything from it. The DETECTOR is a good idea regardless of how its evaluation was scored.
51
+ *
52
+ * 🔴 FALSE POSITIVES ARE THE WHOLE DESIGN PROBLEM, AND THE ANSWER IS AN IGNORE SET YOU CAN COUNT.
53
+ * `repro/` is mostly things nobody should ever report: fixtures, fetch caches, working clones,
54
+ * harness scratch, per-repository dumps. So this check needs an ignore set — and an ignore set that
55
+ * is applied silently would turn a cherry-picking detector into the very thing it exists to catch.
56
+ * So it is PRINTED IN FULL ON EVERY RUN, findings or none, exactly as `check_grandfather` in
57
+ * `repro/paper_numbers.py` prints the quantities the numbers gate does not cover. Two halves:
58
+ * — structural rules, in the code below, each one printed with the names it swallowed;
59
+ * — `repro/unreported-grandfathered.txt`, one row per knowingly-unreported analysis, and rows of
60
+ * the form `ships-as:<path>` are RE-VERIFIED against the bundle so that reason cannot rot.
61
+ */
62
+ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
63
+ import { join } from "node:path";
64
+ import {
65
+ headings as mdHeadings,
66
+ splitSections,
67
+ requireMarkdown,
68
+ } from "../../../lib/markdown.mjs";
69
+
70
+ // Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). We fail rather than degrade: without
71
+ // the parser NOT A SINGLE section would be found in the paper, and the check "every section with a
72
+ // number is named in the artifact index" would pass clean — that is exactly the failure this file
73
+ // was written for (absence is invisible to a checker that looks only at what is present).
74
+ requireMarkdown();
75
+
76
+ const args = process.argv.slice(2);
77
+ const dir = args.find((a) => !a.startsWith("--"));
78
+ const flagsOnly = args.includes("--flags-only");
79
+ if (!dir) {
80
+ console.error("usage: node artifact-coverage.mjs <paper-dir> [--flags-only]");
81
+ process.exit(0);
82
+ }
83
+
84
+ const paperPath = ["paper.md", "draft.md"]
85
+ .map((f) => join(dir, f))
86
+ .find(existsSync);
87
+ // 🔴 A SILENT exit(0) here read as «clean» for four of five papers. Measured 2026-08-25: only
88
+ // <paper-a> has a markdown source; agenticdev/aisec/<paper-e>/scored ship
89
+ // `paper.tex`, so this checker printed NOTHING and returned 0 on the camera-ready — which has
90
+ // an artifact (reproduce.py, data/) it was built to inspect. Nothing anywhere said so, and the
91
+ // CI step mirrored the same condition, so both layers agreed to say nothing. A skip that cannot
92
+ // be told apart from a pass is the failure this whole file exists to prevent.
93
+ if (!paperPath) {
94
+ console.error(
95
+ `⏭️ artifact coverage SKIPPED for ${dir} — no paper.md/draft.md. This checker\n reads markdown only; a .tex paper is NOT covered by it. Absence of findings here is\n absence of checking, not a clean bill.`,
96
+ );
97
+ process.exit(0);
98
+ }
99
+
100
+ // The bundle is whatever directory the paper releases. `artifact-anon` is the anonymised one that
101
+ // actually ships; a working `repro/` is NOT the release and must never be treated as one.
102
+ const bundle = [join(dir, "repro/artifact-anon"), join(dir, "artifact")].find(
103
+ existsSync,
104
+ );
105
+ if (!bundle) {
106
+ console.error(
107
+ `⏭️ artifact coverage SKIPPED for ${dir} — no repro/artifact-anon and no artifact/ directory.`,
108
+ );
109
+ process.exit(0);
110
+ }
111
+ const indexPath = ["NUMBERS.md", "README.md"]
112
+ .map((f) => join(bundle, f))
113
+ .find(existsSync);
114
+ if (!indexPath) {
115
+ const msg = `${bundle} has no NUMBERS.md and no README.md — nothing maps the paper's numbers to its data`;
116
+ console.error(`📦 artifact — ${msg}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ const paper = readFileSync(paperPath, "utf8").replace(/<!--[\s\S]*?-->/g, "");
121
+ const index = readFileSync(indexPath, "utf8");
122
+ const findings = [];
123
+
124
+ // ---- 1. every number-reporting section is represented in the index --------------------------
125
+ // "Reports a number" means a BOLD span containing digits — the paper's own convention for a figure
126
+ // it is asserting, as opposed to a section number or a citation. A section that merely mentions
127
+ // §4.3 in passing does not trip this.
128
+ // 2026-08-11: both the body boundary and the split into sections come from the parser. The former
129
+ // `^## ` / `^### ` counted any line with hashes as a section, including hashes inside a ```-block:
130
+ // a paper QUOTING somebody else's `## 4.2 …` in an example got an extra "section", and its bold
131
+ // numbers got a requirement to be named in the artifact index. The `-1 / >>> 0` convention is kept:
132
+ // the former expression did not treat a heading on the very first line of the file as a boundary
133
+ // either.
134
+ const freeH = mdHeadings(paper).find(
135
+ (h) => h.depth === 2 && /^(Limitations|References|Ethical)/u.test(h.text),
136
+ );
137
+ const body = paper.slice(0, (freeH ? freeH.offset : -1) >>> 0 || paper.length);
138
+ const SECTION_NUM = /^(\d+(?:\.\d+)?)\.?\s+(.+)$/u;
139
+ const SUBSECTION_NUM = /^(\d+\.\d+)\s+(.+)$/u;
140
+ for (const chunk of splitSections(body, { min: 2, max: 2 })) {
141
+ const h = chunk.heading && SECTION_NUM.exec(chunk.heading.text);
142
+ if (!h) continue;
143
+ const subs = splitSections(chunk.raw, { min: 3, max: 3 });
144
+ for (const subSec of subs) {
145
+ // `subs.length === 1` means "there are no subsections at all", exactly what used to be checked
146
+ // as `sub === chunk`: the numbers are then counted against the section itself. If subsections DO
147
+ // exist, the section's leading chunk (before the first `###`) is not checked — as it was before.
148
+ const sh = subSec.heading
149
+ ? SUBSECTION_NUM.exec(subSec.heading.text)
150
+ : subs.length === 1
151
+ ? [null, h[1], h[2]]
152
+ : null;
153
+ if (!sh) continue;
154
+ const sub = subSec.raw;
155
+ const num = sh[1];
156
+ // Bold spans with digits that are not section pointers or bracketed citations.
157
+ const reported = (
158
+ sub
159
+ .replace(/§\s*\d+(\.\d+)*/g, "")
160
+ .replace(/\[[\d,\s–-]+\]/g, "")
161
+ .match(/\*\*[^*]*\d[^*]*\*\*/g) || []
162
+ ).length;
163
+ if (!reported) continue;
164
+ const named = new RegExp(
165
+ `§\\s*${num.replace(".", "\\.")}\\b|\\b${num.replace(".", "\\.")}\\b`,
166
+ ).test(index);
167
+ if (!named)
168
+ findings.push(
169
+ `§${num} reports ${reported} bolded figure(s) and is named NOWHERE in ` +
170
+ `${indexPath.replace(dir + "/", "")} — the released bundle may hold no data for it`,
171
+ );
172
+ }
173
+ }
174
+
175
+ // ---- 2. every path the index names actually exists -------------------------------------------
176
+ // Backtick-quoted things that look like a path into the bundle. A promise to a file that is not
177
+ // there is worse than no promise: it reads as released.
178
+ const seen = new Set();
179
+ for (const m of index.matchAll(/`([A-Za-z0-9_./-]+\/[A-Za-z0-9_.*/-]+)`/g)) {
180
+ let p = m[1];
181
+ if (seen.has(p) || p.includes("*") || p.startsWith("http")) continue;
182
+ seen.add(p);
183
+ // A paper about repositories names a LOT of `owner/repo`, and those are not paths. Require a file
184
+ // extension: it is the one signal that separates `census/rows.tsv` from `saleor/apps`. Caught on
185
+ // the first run, when three GitHub repositories were reported as missing directories.
186
+ if (!/\.[a-z0-9]{1,5}$/i.test(p)) continue;
187
+ // `repro/` is the WORKING tree, deliberately not in the anonymised bundle — the whole reason the
188
+ // bundle exists separately is that repro holds real names in fixtures.
189
+ if (p.startsWith("repro/")) continue;
190
+ // A directory prefix is enough: the index names files inside runs that may be regenerated.
191
+ const head = p.split("/")[0];
192
+ if (!existsSync(join(bundle, head)) && !readdirSync(bundle).includes(head))
193
+ findings.push(
194
+ `the index names \`${p}\` and \`${head}\` is not in the bundle`,
195
+ );
196
+ }
197
+
198
+ // ---- 3. reverse: a result on disk that neither the paper nor the index mentions ---------------
199
+ // The unit is an EXPERIMENT DIRECTORY under `repro/` that holds a headline write-up — this
200
+ // project's own convention, stated in `repro/README.md`: "you go into the folder, and there is a
201
+ // RESULTS.md with the full write-up". A directory with a RESULTS.md is a run somebody finished and
202
+ // wrote up.
203
+ //
204
+ // "Mentioned" is: the directory's name, or its name with a trailing -YYYY-MM-DD stripped, appears
205
+ // in the paper OR in the released index. Matching is LITERAL on purpose. Prefix or fuzzy matching
206
+ // would quietly absolve `gate-effect-spike-2026-08-05` on the strength of the shipped
207
+ // `gate-effect/`, and a false NEGATIVE in a cherry-picking detector is the failure that costs
208
+ // something. A rename is therefore a row in the ignore file, where it is visible and re-verified,
209
+ // rather than an inference nobody can see.
210
+ //
211
+ // 🔴 THIS LEG READS EVERY INDEX FILE, NOT JUST THE FIRST. Check 1 above resolves `indexPath` to
212
+ // NUMBERS.md *or* README.md and stops, which is right for it: it asks whether the map from numbers
213
+ // to data exists. Reused here it produced three false positives on the first run —
214
+ // `enforcement-surface`, `ladder-prototype` and `tier-a-prime` are all named in the bundle's
215
+ // README.md and in no other file, so a reverse check reading only NUMBERS.md accused the bundle of
216
+ // hiding three analyses it ships and documents. A cherry-picking detector that cries wolf gets
217
+ // muted, and a muted detector is how the failure happens twice.
218
+ const ledger = [];
219
+ const HEADLINE = /^(RESULTS|SUMMARY|FINDINGS|METRICS)\.(md|json|tsv|csv|txt)$/i;
220
+ const repro = join(dir, "repro");
221
+ const bundleName = bundle
222
+ .slice(dir.length + 1)
223
+ .split("/")
224
+ .pop();
225
+ const indexAll = ["NUMBERS.md", "README.md"]
226
+ .map((f) => join(bundle, f))
227
+ .filter(existsSync)
228
+ .map((f) => readFileSync(f, "utf8"))
229
+ .join("\n");
230
+
231
+ if (!existsSync(repro)) {
232
+ ledger.push(
233
+ " reverse check (results on disk nobody reports): SKIPPED — no repro/ directory",
234
+ );
235
+ } else {
236
+ // Structural ignores. Each is a RULE, and each prints what it swallowed.
237
+ const swallowed = new Map([
238
+ [
239
+ `the released bundle itself (${bundleName}/) — it is the shipped copy of these same analyses, ` +
240
+ "so counting it would report every one of them a second time",
241
+ [],
242
+ ],
243
+ [
244
+ "dot-directories and build/dependency trees (node_modules, __pycache__)",
245
+ [],
246
+ ],
247
+ ]);
248
+ const allowPath = join(repro, "unreported-grandfathered.txt");
249
+ const allowed = new Map();
250
+ if (existsSync(allowPath)) {
251
+ for (const line of readFileSync(allowPath, "utf8").split("\n")) {
252
+ const row = line.split("#")[0].trimEnd();
253
+ if (!row.trim()) continue;
254
+ const [name, reason, ...rest] = row.split("\t").map((c) => c.trim());
255
+ if (name)
256
+ allowed.set(
257
+ name,
258
+ [reason, ...rest].filter(Boolean).join(" — ") || "(NO REASON GIVEN)",
259
+ );
260
+ }
261
+ }
262
+
263
+ const candidates = [];
264
+ for (const name of readdirSync(repro)) {
265
+ const p = join(repro, name);
266
+ if (!statSync(p).isDirectory()) continue;
267
+ if (name === bundleName) {
268
+ swallowed.get([...swallowed.keys()][0]).push(name);
269
+ continue;
270
+ }
271
+ if (
272
+ name.startsWith(".") ||
273
+ name === "node_modules" ||
274
+ name === "__pycache__"
275
+ ) {
276
+ swallowed.get([...swallowed.keys()][1]).push(name);
277
+ continue;
278
+ }
279
+ const heads = readdirSync(p).filter((f) => HEADLINE.test(f));
280
+ if (heads.length) candidates.push({ name, heads });
281
+ }
282
+
283
+ // 🔴 WHOLE-TOKEN, AND NEVER ON A SHORT SLUG. `includes()` was the first version and it acquitted
284
+ // a directory called `a-2026-01-01` on the strength of the letter "a" appearing in the prose —
285
+ // caught by the harness case that asserts the stated finding count. Substring matching in a
286
+ // detector like this fails in the dangerous direction: it invents evidence that a result was
287
+ // mentioned. Hyphens count as part of the token, so `gate-effect` does not match inside
288
+ // `gate-effect-spike`, and a slug shorter than four characters is not used for matching at all.
289
+ const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
290
+ const mentions = (t, s) =>
291
+ s.length >= 4 && new RegExp(`(?<![\\w-])${esc(s)}(?![\\w-])`).test(t);
292
+
293
+ const reported = [],
294
+ ignored = [],
295
+ unreported = [];
296
+ for (const c of candidates) {
297
+ const slug = c.name.replace(/-\d{4}-\d{2}-\d{2}$/, "");
298
+ const named = (t) => mentions(t, c.name) || mentions(t, slug);
299
+ if (named(paper)) {
300
+ reported.push(`${c.name} (named in the paper)`);
301
+ continue;
302
+ }
303
+ if (named(indexAll)) {
304
+ reported.push(`${c.name} (named in the index)`);
305
+ continue;
306
+ }
307
+ if (allowed.has(c.name)) {
308
+ ignored.push([c.name, allowed.get(c.name)]);
309
+ continue;
310
+ }
311
+ unreported.push(c);
312
+ }
313
+
314
+ // A `ships-as:` reason carries a witness, so it is checked rather than believed. Everything else
315
+ // is prose and is reprinted verbatim for a human to disbelieve.
316
+ const stale = [];
317
+ for (const [name, reason] of ignored) {
318
+ const m = /^ships-as:(\S+)/.exec(reason);
319
+ if (m && !existsSync(join(bundle, m[1])))
320
+ stale.push(
321
+ `the ignore row for \`${name}\` claims it ships as \`${m[1]}\`, and that path is ` +
322
+ "not in the bundle — the allowance has rotted and is now hiding a real absence",
323
+ );
324
+ }
325
+ for (const name of allowed.keys())
326
+ if (!candidates.some((c) => c.name === name))
327
+ stale.push(
328
+ `the ignore row for \`${name}\` names no directory under repro/ that holds a ` +
329
+ "headline result — retire the row rather than leaving it to grow stale",
330
+ );
331
+ findings.push(...stale);
332
+
333
+ for (const c of unreported)
334
+ findings.push(
335
+ `DISK_UNREPORTED repro/${c.name}/ holds ${c.heads.join(", ")} and is named neither in the ` +
336
+ "paper nor in the released index — an analysis that ran and whose result a reader cannot reach",
337
+ );
338
+
339
+ ledger.push(
340
+ "",
341
+ ` ── what the reverse check ignored, in full (${candidates.length} candidate analyses under repro/) ──`,
342
+ " An ignore set applied in silence would make this a cherry-picking detector that cherry-picks,",
343
+ " so it is printed whether or not anything was found. Same discipline as check_grandfather in",
344
+ " repro/paper_numbers.py: the uncovered set is a list you can count, not an absence of warnings.",
345
+ );
346
+ for (const [rule, names] of swallowed)
347
+ ledger.push(
348
+ ` • ${names.length} before they were counted — ${rule}`,
349
+ ...(names.length ? [` ${names.join(", ")}`] : []),
350
+ );
351
+ ledger.push(
352
+ ` • ${reported.length} reported: ${reported.join(" · ") || "(none)"}`,
353
+ );
354
+ ledger.push(
355
+ ` • ${ignored.length} in repro/unreported-grandfathered.txt — every row, every reason:`,
356
+ );
357
+ for (const [name, reason] of ignored)
358
+ ledger.push(` ${name} → ${reason}`);
359
+ if (!ignored.length)
360
+ ledger.push(" (the file is empty — nothing is being waved through)");
361
+ ledger.push(
362
+ ` • ${unreported.length} left over, and those are the findings above.`,
363
+ ` Ratio: ${unreported.length} reported as unreported, ${ignored.length} deliberately ignored, ` +
364
+ `${reported.length} reachable. If the ignored column ever dwarfs the other two, this check has`,
365
+ " become a formality and the honest move is to say so rather than to keep running it.",
366
+ "",
367
+ " 🔴 WHAT THIS LEG IS SILENT ABOUT, stated so nobody reads its silence as coverage:",
368
+ " — an analysis SHIPPED in the bundle but never discussed in prose is counted as reported here,",
369
+ " because the index is treated as the paper's delegate. MedSci's DISK_UNREPORTED is scoped to",
370
+ " the manuscript alone and would flag those; ours would not.",
371
+ " — a result that was never written up (no RESULTS.md) is invisible. Deleting the write-up is a",
372
+ " one-command way past this check, and nothing here can tell that from an experiment nobody",
373
+ " finished.",
374
+ ' — nothing reads the CONTENT of a result, so "mentioned" and "reported honestly" are different',
375
+ " things and only the first is checked.",
376
+ );
377
+ }
378
+
379
+ const header = `📦 artifact coverage — ${findings.length} finding(s) in ${dir}`;
380
+ if (findings.length) {
381
+ const out = flagsOnly ? console.error : console.log;
382
+ out(header);
383
+ findings.forEach((f) => out(" " + f));
384
+ out(
385
+ " absence is invisible to the leak gate, the numbers gate and the artifact runner:",
386
+ );
387
+ out(" nothing leaks, nothing mismatches, and what IS there still runs.");
388
+ ledger.forEach((l) => out(l));
389
+ process.exit(1);
390
+ }
391
+ if (!flagsOnly) {
392
+ console.log(header);
393
+ console.log(
394
+ " every number-reporting section is indexed, every named path exists",
395
+ );
396
+ ledger.forEach((l) => console.log(l));
397
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * artifact-coverage.mutations.mjs — proof that every check in `artifact-coverage.mjs` is watched
3
+ * FAILING, with the reverse (DISK_UNREPORTED) leg and its ignore set as the reason this file exists.
4
+ * Run: `node .claude/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs`
5
+ *
6
+ * This is not a test. It is the evidence behind the claim that the tests test something.
7
+ *
8
+ * WHY THIS LEG IN PARTICULAR. The reverse leg is the only thing in the pipeline that looks for a
9
+ * result nobody reported, and it is guarded by an ignore set. Both halves fail silently in the same
10
+ * direction: a broken detector finds nothing, and an over-wide ignore set also finds nothing, and
11
+ * from outside those are the same output. A green harness proves the CHECKER fires on planted
12
+ * defects; it does not prove the HARNESS would notice if a check were deleted. Every mutations file
13
+ * written in this repository has found something on its first run, and this one is no exception —
14
+ * see the notes in the table.
15
+ *
16
+ * WHAT IT DOES. For each check, it neuters exactly that check in the source, runs the harness, and
17
+ * requires the harness to go RED with the named assertion. Then it restores the file. A mutation
18
+ * the harness survives is a check nobody is watching, and it is reported as SURVIVED.
19
+ *
20
+ * The source is restored in a `finally`, and the run refuses to start on a dirty working tree, so
21
+ * an interrupted run cannot leave a neutered checker behind.
22
+ */
23
+ import { runMutations } from "../../../lib/mutation-driver.mjs";
24
+ import { dirname, join } from "node:path";
25
+ import { fileURLToPath } from "node:url";
26
+ import { consumerRoot } from "./consumer.mjs";
27
+
28
+ const HERE = dirname(fileURLToPath(import.meta.url));
29
+ const ROOT = consumerRoot();
30
+ const SRC = join(HERE, "artifact-coverage.mjs");
31
+ const HARNESS = join(HERE, "artifact-coverage.harness.mjs");
32
+
33
+ /** [name, what it disables, exact source substring, replacement, harness case expected to go red] */
34
+ const MUTATIONS = [
35
+ // ── the two forward legs, which had a harness and no mutation proof until today ──
36
+ [
37
+ "fwd/section-indexed",
38
+ "the rule that a number-reporting section must be named in the index",
39
+ " if (!named)\n",
40
+ " if (false)\n",
41
+ "is named NOWHERE",
42
+ ],
43
+ [
44
+ "fwd/index-path-exists",
45
+ "the rule that a path the index names must exist",
46
+ " if (!existsSync(join(bundle, head)) && !readdirSync(bundle).includes(head))",
47
+ " if (false)",
48
+ "is not in the bundle",
49
+ ],
50
+ [
51
+ "fwd/no-index",
52
+ "the refusal to accept a bundle that maps nothing to anything",
53
+ "if (!indexPath) {",
54
+ "if (false) {",
55
+ "a bundle that maps nothing to anything was accepted",
56
+ ],
57
+ [
58
+ "fwd/repo-not-path",
59
+ "the guard that `owner/repo` is not a path (extension required)",
60
+ " if (!/\\.[a-z0-9]{1,5}$/i.test(p)) continue;",
61
+ " if (false) continue;",
62
+ "a complete bundle was failed",
63
+ ],
64
+
65
+ // ── the reverse leg: the detector itself ──
66
+ [
67
+ "rev/finding",
68
+ "the DISK_UNREPORTED finding",
69
+ " for (const c of unreported)\n findings.push(",
70
+ " for (const c of [])\n findings.push(",
71
+ "cherry-picking case this leg was ported for",
72
+ ],
73
+ [
74
+ "rev/headline-results",
75
+ "RESULTS.md as a headline write-up",
76
+ "/^(RESULTS|SUMMARY|FINDINGS|METRICS)\\.",
77
+ "/^(SUMMARY|FINDINGS|METRICS)\\.",
78
+ "cherry-picking case this leg was ported for",
79
+ ],
80
+ [
81
+ "rev/paper-mention",
82
+ "crediting a mention in the paper",
83
+ "if (named(paper)) {",
84
+ "if (false) {",
85
+ "an experiment the paper discusses by name was reported as unmentioned",
86
+ ],
87
+ [
88
+ "rev/index-mention",
89
+ "crediting a mention in the index",
90
+ "if (named(indexAll)) {",
91
+ "if (false) {",
92
+ "named in the bundle's README.md and nowhere else",
93
+ ],
94
+ [
95
+ "rev/all-index-files",
96
+ "reading BOTH index files (reverts to check 1's single indexPath)",
97
+ " if (named(indexAll)) {",
98
+ " if (named(index)) {",
99
+ "named in the bundle's README.md and nowhere else",
100
+ ],
101
+ [
102
+ "rev/date-slug",
103
+ "stripping the -YYYY-MM-DD suffix before matching",
104
+ 'const slug = c.name.replace(/-\\d{4}-\\d{2}-\\d{2}$/, "");',
105
+ "const slug = c.name;",
106
+ "names without its date suffix",
107
+ ],
108
+ [
109
+ "rev/whole-token",
110
+ "whole-token matching (reverts to substring `includes`)",
111
+ "const mentions = (t, s) =>\n s.length >= 4 && new RegExp(`(?<![\\\\w-])${esc(s)}(?![\\\\w-])`).test(t);",
112
+ "const mentions = (t, s) => t.includes(s);",
113
+ "the header does not state the real finding count",
114
+ ],
115
+ [
116
+ "rev/bundle-skipped",
117
+ "the structural ignore that skips the released bundle",
118
+ " if (name === bundleName) {\n swallowed.get([...swallowed.keys()][0]).push(name);\n continue;\n }",
119
+ " if (false) {\n swallowed.get([...swallowed.keys()][0]).push(name);\n continue;\n }",
120
+ "the bundle's own RESULTS.md was counted",
121
+ ],
122
+
123
+ // ── the ignore set, which can neuter the detector without changing a line of it ──
124
+ [
125
+ "ign/applied",
126
+ "applying an allow row at all",
127
+ "if (allowed.has(c.name)) {",
128
+ "if (false) {",
129
+ "an allowed row still counted as a finding",
130
+ ],
131
+ [
132
+ "ign/rows-parsed",
133
+ "parsing the allow file",
134
+ " if (name)\n allowed.set(",
135
+ " if (false)\n allowed.set(",
136
+ "an allowed row still counted as a finding",
137
+ ],
138
+ [
139
+ "ign/ships-as-verified",
140
+ "re-verifying a `ships-as:` claim against the bundle",
141
+ "if (m && !existsSync(join(bundle, m[1])))",
142
+ "if (false)",
143
+ "the allowance has rotted",
144
+ ],
145
+ [
146
+ "ign/stale-rows",
147
+ "reporting a row that names no directory",
148
+ " if (!candidates.some((c) => c.name === name))",
149
+ " if (false)",
150
+ "sat in the ignore file unreported",
151
+ ],
152
+ [
153
+ "ign/printed-on-clean",
154
+ "printing the ledger when there are no findings",
155
+ ' console.log(\n " every number-reporting section is indexed, every named path exists",\n );\n ledger.forEach((l) => console.log(l));',
156
+ ' console.log(\n " every number-reporting section is indexed, every named path exists",\n );',
157
+ // Killed by case 9 rather than case 12: case 9 is also a 0-finding report-mode run and asserts
158
+ // the same line, and it runs first. Both cases assert the property; the row records WHICH one
159
+ // catches it so a future reader does not mistake this for an off-target kill.
160
+ "was applied and NOT printed",
161
+ ],
162
+ [
163
+ "ign/reasons-printed",
164
+ "printing each allow row's reason",
165
+ " for (const [name, reason] of ignored)\n ledger.push(` ${name} → ${reason}`);",
166
+ " for (const [name, reason] of ignored)\n ledger.push(` ${name}`);",
167
+ "was applied and NOT printed",
168
+ ],
169
+ [
170
+ "ign/empty-file-said",
171
+ "saying so when nothing is being waved through",
172
+ ' if (!ignored.length)\n ledger.push(" (the file is empty — nothing is being waved through)");',
173
+ ' if (false)\n ledger.push(" (the file is empty — nothing is being waved through)");',
174
+ "an empty allow file printed nothing at all",
175
+ ],
176
+ [
177
+ "ign/ratio",
178
+ "the ratio line that says whether this check still does anything",
179
+ " ` Ratio: ${unreported.length} reported as unreported, ${ignored.length} deliberately ignored, ` +",
180
+ " ` Ratio: ` +",
181
+ "printed no ratio",
182
+ ],
183
+
184
+ // ── the machine-readable contract with run-mechanical.mjs ──
185
+ [
186
+ "out/stated-count",
187
+ "the stated finding count in the header",
188
+ "const header = `📦 artifact coverage — ${findings.length} finding(s) in ${dir}`;",
189
+ "const header = `📦 artifact coverage — ${dir}`;",
190
+ "does not state `— 0 finding(s)`",
191
+ ],
192
+ [
193
+ "out/flags-quiet",
194
+ "the silence of --flags-only on a clean tree",
195
+ "if (!flagsOnly) {\n console.log(header);",
196
+ "if (true) {\n console.log(header);",
197
+ "--flags-only printed something about a complete bundle",
198
+ ],
199
+ ];
200
+
201
+ // The table above is DATA about this checker; the engine below is shared with the other mutation
202
+ // files (`mutation-driver.mjs`). The split gave this file two protections it never had: the no-op
203
+ // guard (a replacement equal to the original leaves a green harness proving nothing) and one retry
204
+ // on a non-kill (a flake reported as a survivor sends the next reader to rewrite a working
205
+ // assertion). It also refuses to start when a named harness does not exist — the defect that made
206
+ // three files report SURVIVED for every case after the 2026-08-11 colocation.
207
+ process.exit(
208
+ runMutations({
209
+ root: ROOT,
210
+ cases: MUTATIONS.map(([name, disables, find, replace, expect]) => ({
211
+ name,
212
+ disables,
213
+ edits: [[SRC, find, replace]],
214
+ harness: HARNESS,
215
+ expect,
216
+ })),
217
+ }),
218
+ );