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
+ /**
2
+ * mutation-driver.mjs — the shared engine behind every `*.mutations.mjs` in this repo.
3
+ *
4
+ * NOT IN vigiles, AND THAT IS A DECISION WITH A DATE. On 2026-08-14 this engine was extracted into
5
+ * vigiles as `runMutations` (`vigiles/testing`, shipped in 15.3.0). The owner killed it the next
6
+ * day with one question: «how is this vigiles' responsibility, it's just nodejs?» — and he is
7
+ * right. Measured against the rest of
8
+ * that package: `runHook` knows the Claude Code hook protocol, `measureTriggerRate` knows what a
9
+ * skill is, `skillContract` reads `allowed-tools` out of frontmatter. This file knows none of that.
10
+ * It replaces a substring, spawns `node`, and reads stdout; lift it into any Node repo and it works
11
+ * unchanged. Confirming measurement: no test in vigiles used it — the product did not dogfood its
12
+ * own published API.
13
+ *
14
+ * The rule "mechanism → vigiles" was applied too bluntly, and this is the correction worth keeping:
15
+ * what fixed the ten drifted copies was CONSOLIDATION, not RELOCATION. One copy instead of ten
16
+ * buys every protection listed below. Moving that copy into a product for harness authors was a
17
+ * second, separate step, and it was never justified — it only bought a newcomer the question the
18
+ * owner asked. `checkProductCodeInKb` flags this file on sight, which is correct and stays visible: the
19
+ * heuristic is right that this is mechanism-shaped, and the answer is «yes, and it was rejected
20
+ * upstream on 2026-08-15», not «no».
21
+ *
22
+ * 🔴 2026-09-12 — IT NOW LIVES IN A PACKAGE ANYWAY, AND THAT DOES NOT REOPEN THE DECISION
23
+ * ABOVE. The rejected move was into vigiles: a product for people writing agent harnesses, where
24
+ * a generic string-replacing test driver is off-topic and, measured, was not used by a single one
25
+ * of its own tests. This package is the opposite case — it IS the batteries' own repository, they
26
+ * are its tests, and it cannot run them without the engine. So the destination differs on the
27
+ * exact axis the rejection turned on: «does the package this lives in actually use it». Do not
28
+ * read this file's presence here as the vigiles decision being reversed; it was not re-argued.
29
+ *
30
+ * The one shape that WOULD belong in vigiles is a different function that does not exist yet:
31
+ * mutate the HOOK — remove one branch of a guard — and require the assembled harness test to
32
+ * notice. Only vigiles knows what a hook is. Backlog, not this file.
33
+ *
34
+ * WHAT A MUTATION RUN IS. Plant one defect in a checker, run that checker's harness, and require
35
+ * the harness to go red AND to name the case that owns the defect. A harness that stays green has
36
+ * an assertion that cannot fail; a harness that reddens with someone else's message has two
37
+ * defects sharing one assertion, which means neither is really watched.
38
+ *
39
+ * WHY THIS FILE EXISTS (2026-08-14). Ten mutation files, 1799 lines, ran the same twenty-five-line
40
+ * driver ten times — and they had DRIFTED, so the copies were not equivalent. Counted against the
41
+ * committed versions, not estimated:
42
+ *
43
+ * - NO-OP GUARD (a replacement equal to the original leaves a green harness that proves nothing):
44
+ * present in 4 of 10 — `ledger`, `round-diff`, `textidote`, `uncited-refs`. SIX lacked it.
45
+ * - RETRY (a non-kill is re-run once before it is believed, because a row killed as named came
46
+ * back DIFFERENT in a later full run and reproduced clean when replayed alone): present in
47
+ * 1 of 10 — `ledger`. NINE lacked it.
48
+ * - `skills.mutations.mjs` alone mutated a COPY of the tree; the other nine rewrite the real
49
+ * source and restore it in a `finally`.
50
+ * - Two files pointed at `.claude/pipeline/skills.harness.mjs`, deleted by the 2026-08-11
51
+ * colocation (`ledger`, six cases; and `skills` itself). `vigiles test` on a path matching
52
+ * nothing exits 0, so those cases reported SURVIVED forever — a mutation runner producing
53
+ * confident wrong answers about mutation runs.
54
+ *
55
+ * Every protection above was written AFTER it caught something, in one file, and never travelled
56
+ * to the other nine. That is the argument for one engine, and it is stronger than "less code".
57
+ *
58
+ * So the split is not cosmetic DRY: nine files GAIN two protections they never had, and the dead
59
+ * path becomes expressible once instead of three times.
60
+ *
61
+ * WHAT STAYS IN THE CALLER. The CASES TABLE — which defect, which bytes, which message. That is
62
+ * data about OUR checkers and belongs next to them (root CLAUDE.md: "mechanism — in vigiles,
63
+ * data — in mine"; the same line separates mechanism from data inside this repo). This file knows
64
+ * nothing about papers, skills or ledgers.
65
+ *
66
+ * WHAT DOES NOT USE THIS ENGINE, AND WHY. `skills.mutations.mjs` mutates a COPY of `.claude` in a
67
+ * temp root and runs the harnesses there with `CLAUDE_PROJECT_DIR` repointed. That is the right
68
+ * strategy for it — its cases break the SKILL CORPUS itself, so a copy is cheap (one directory) and
69
+ * the real corpus is never touched. It is the only one CI runs, and it stays as it is. Two
70
+ * strategies is a smell only when they differ by accident; here the difference is the subject.
71
+ *
72
+ * 🔴 THE IN-PLACE STRATEGY IS PRESERVED, NOT "FIXED". Nine callers rewrite a real file, and moving
73
+ * them to a temp copy is not a refactor: their harnesses resolve every path from the repo root, so
74
+ * a copy means copying the whole repo per mutation — minutes instead of seconds. What IS fixed is
75
+ * the failure mode: the restore now also runs on SIGINT/SIGTERM, and a run refuses to start when
76
+ * any target file is already dirty, so an interrupted run cannot leave a neutered checker behind
77
+ * and then pass.
78
+ */
79
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
80
+ import { spawnSync } from "node:child_process";
81
+
82
+ /**
83
+ * @typedef {object} MutationCase
84
+ * @property {string} name Short id, printed in the table.
85
+ * @property {string} disables What the mutation takes away, in words — the column a reader scans.
86
+ * @property {[string, string, string][]} edits `[absolutePath, find, replace]`. A LIST because some
87
+ * defects are only expressible as more than one edit: an acquittal reaching the ledger needs both
88
+ * the retired-name guard and the vocabulary check removed, and either alone throws for an
89
+ * unrelated reason.
90
+ * @property {string} harness Absolute path to the harness that must go red.
91
+ * @property {string} expect Substring the harness's complaint must contain.
92
+ */
93
+
94
+ /** Runs a harness file. `node` for a plain harness, `npx vigiles test` when the caller says so. */
95
+ const runners = {
96
+ node: (harness, root) =>
97
+ spawnSync("node", [harness], { cwd: root, encoding: "utf8" }),
98
+ vigiles: (harness, root) =>
99
+ spawnSync("npx", ["vigiles", "test", harness], {
100
+ cwd: root,
101
+ encoding: "utf8",
102
+ }),
103
+ };
104
+
105
+ /**
106
+ * vigiles' own protocol constant: `skip()` ends the process with this code. Its docs state it —
107
+ * `harness-assert.d.ts`: «Exit 77 is the runner's `SKIP_EXIT_CODE` (run-scripts.ts)» — but the
108
+ * package exports `skip` and not the number, so it is NAMED here instead of appearing as a bare
109
+ * literal in a comparison.
110
+ *
111
+ * ⚠️ If a future vigiles changes it, this degrades GRACEFULLY rather than silently: an unknown
112
+ * non-zero code reads as "red", so the battery reports UNJUDGEABLE instead of claiming a
113
+ * survivor. Wrong, but loudly wrong — which is the direction this whole file errs in.
114
+ */
115
+ const VIGILES_SKIP_EXIT = 77;
116
+
117
+ /**
118
+ * 🔴 A SKIPPED HARNESS IS A THIRD OUTCOME, AND CONFLATING IT WITH "GREEN" PRODUCES A CONFIDENT
119
+ * WRONG DIAGNOSIS — the failure this file already documents twice, arriving by a new door.
120
+ *
121
+ * MEASURED 2026-09-15, chain complete, every link observed rather than argued:
122
+ *
123
+ * the harness itself, `claude` CLI absent exit 77 "SKIPPED: … no run can be observed"
124
+ * `npx vigiles test <harness>` exit 0 "0 passed, 1 skipped."
125
+ * the driver reads 0 ⇒ "🔴 SURVIVED"
126
+ *
127
+ * So `plan-paper-timeline.effects.mutations.mjs` reported TWO survivors in CI and none locally,
128
+ * and the difference was not the code under test — it was that CI has no `claude` binary. The
129
+ * reader is sent to rewrite a working assertion, which is strictly worse than a red with no
130
+ * explanation.
131
+ *
132
+ * WHY THE FLAG AND NOT A SUBSTRING. `--no-skip` exists precisely to turn "skipped" into a
133
+ * failure, so asking the tool with it is asking the THING; matching "SKIPPED" in the output
134
+ * would be matching its shadow, and the shadow has spellings. The plain run then disambiguates:
135
+ * a harness that is green without the flag and red with it SKIPPED; one that is red both ways was
136
+ * already red, which the caller below already handles.
137
+ *
138
+ * ⚠️ The extra probe runs ONLY for a harness that is not plainly green, so the common path costs
139
+ * exactly what it cost before.
140
+ *
141
+ * @returns {"green"|"red"|"skip"}
142
+ */
143
+ function probeHarness(runner, harness, root) {
144
+ // The node runner needs no probe: a skipping harness exits with vigiles' own skip code, and
145
+ // that code IS the signal. It is only the `vigiles` wrapper that swallows it into a 0.
146
+ if (runner === "node") {
147
+ const r = runners.node(harness, root);
148
+ return r.status === 0
149
+ ? "green"
150
+ : r.status === VIGILES_SKIP_EXIT
151
+ ? "skip"
152
+ : "red";
153
+ }
154
+ const strict = spawnSync("npx", ["vigiles", "test", harness, "--no-skip"], {
155
+ cwd: root,
156
+ encoding: "utf8",
157
+ });
158
+ if (strict.status === 0) return "green";
159
+ return runners.vigiles(harness, root).status === 0 ? "skip" : "red";
160
+ }
161
+
162
+ /**
163
+ * @param {object} o
164
+ * @param {string} o.root Repo root; every path in `edits` and `harness` is absolute.
165
+ * @param {MutationCase[]} o.cases
166
+ * @param {"node"|"vigiles"} [o.runner] How to invoke a harness. Default `node`.
167
+ * @param {string[]} [o.cleanHarnesses] Run after restoring to prove the restore worked. Defaults
168
+ * to the distinct harnesses named by the cases.
169
+ * @returns {number} process exit code — 0 only if every mutation was killed by its OWN case.
170
+ */
171
+ export function runMutations({ root, cases, runner = "node", cleanHarnesses }) {
172
+ const run = runners[runner];
173
+ if (!run)
174
+ throw new Error(
175
+ `unknown runner ${JSON.stringify(runner)}; expected node or vigiles`,
176
+ );
177
+ if (!cases.length)
178
+ throw new Error(
179
+ "no mutation cases were passed — a runner with nothing to run reports success",
180
+ );
181
+
182
+ const targets = [...new Set(cases.flatMap((c) => c.edits.map(([f]) => f)))];
183
+ const harnesses = [...new Set(cases.map((c) => c.harness))];
184
+
185
+ // COVERAGE MODE — report which harnesses this battery can kill, then stop without touching a
186
+ // file. Read by `scripts/run-mutations.mjs` to answer «is every harness in the repo killable by
187
+ // SOMETHING», which it previously answered by FILENAME (`x.harness.mjs` ⇄ `x.mutations.mjs`).
188
+ //
189
+ // 🔴 THE FILENAME WAS A PROXY, AND IT WAS WRONG ON THIS CORPUS. `ledger.mutations.mjs` is the
190
+ // battery for `gates.harness.mjs` — it plants defects in the ledger and requires the GATES
191
+ // harness to notice — and no naming convention expresses that. Under the old rule it read as
192
+ // two problems at once: a battery covering nothing and a harness covered by nobody. Pairing is
193
+ // now derived from the `harness` field of the cases that actually run, i.e. from the same data
194
+ // structure that does the work, so it cannot disagree with what the battery does.
195
+ //
196
+ // ⚠️ An exemption list was the other available answer and is the one NOT taken: a guard with a
197
+ // list of things it agrees not to look at is decoration. Nothing here is exempt — a harness is
198
+ // covered only when some battery names it, and a battery that names a harness it does not
199
+ // really exercise is caught by that case reporting SURVIVED.
200
+ if (process.env.MUTATIONS_REPORT_COVERAGE) {
201
+ for (const h of harnesses) console.log(`MUTATION-COVERS\t${h}`);
202
+ return 0;
203
+ }
204
+
205
+ // 🔴 A harness path that resolves to nothing is the defect that hid for three days: `vigiles
206
+ // test` prints "No files found" and exits 0, so every case naming it reports SURVIVED and the
207
+ // reader goes looking for a broken assertion that was never reached. Checked BEFORE any file is
208
+ // touched, so the message arrives with a clean working tree.
209
+ const missing = harnesses.filter((h) => !existsSync(h));
210
+ if (missing.length) {
211
+ console.error(
212
+ `refusing to run: harness file(s) do not exist:\n ${missing.join("\n ")}`,
213
+ );
214
+ return 2;
215
+ }
216
+
217
+ const git = (...a) =>
218
+ spawnSync("git", a, { cwd: root, encoding: "utf8" }).stdout ?? "";
219
+ const dirty = targets.filter((f) => git("status", "--porcelain", f).trim());
220
+ if (dirty.length) {
221
+ console.error(
222
+ `refusing to run: this script rewrites the file(s) below and they have uncommitted changes.\n ${dirty.join("\n ")}\nCommit first (do NOT stash — the stash is repository-global and shared with any other agent).`,
223
+ );
224
+ return 2;
225
+ }
226
+
227
+ // 🔴 BASELINE FIRST, and it is not an optimisation. The old "did the restore work" check ran the
228
+ // harnesses at the END and called any red one a FAILED RESTORE. `gates.harness.mjs` is red today
229
+ // on purpose — its own message says "these are OPEN FINDINGS about the gates themselves, not
230
+ // harness bugs" — so every ledger run printed "🔴 the restore failed" about a working restore and
231
+ // exited 1. A confident wrong diagnosis, produced by the tool whose whole job is catching those.
232
+ // Knowing which harnesses were ALREADY red makes the end-of-run claim mean what it says.
233
+ // Three outcomes, not two — see `probeHarness`. A harness that SKIPS here can never testify
234
+ // about a mutation, so the battery is refused BEFORE a single file is touched, exactly like the
235
+ // missing-path guard above: the message arrives with a clean working tree.
236
+ const probes = new Map(
237
+ harnesses.map((h) => [h, probeHarness(runner, h, root)]),
238
+ );
239
+ const skipped = harnesses.filter((h) => probes.get(h) === "skip");
240
+ if (skipped.length) {
241
+ console.error(
242
+ `refusing to run: harness(es) SKIP on clean source, so this battery can prove nothing:\n ` +
243
+ skipped.join("\n ") +
244
+ `\nA skipped harness exits 0 through \`vigiles test\`, which this driver would read as ` +
245
+ `"the mutation survived" — a claim about the TEST that is really a claim about the ` +
246
+ `ENVIRONMENT. Install the capability the harness names (it says so on its skip line), ` +
247
+ `or run the battery where that capability exists.`,
248
+ );
249
+ return 2;
250
+ }
251
+ const redBefore = new Set(harnesses.filter((h) => probes.get(h) === "red"));
252
+ if (redBefore.size) {
253
+ console.log(
254
+ `ℹ️ already red before any mutation: ${[...redBefore].map(base).join(", ")} — excluded from the restore check, and any case naming one is reported as UNJUDGEABLE.`,
255
+ );
256
+ }
257
+
258
+ const originals = new Map(targets.map((f) => [f, readFileSync(f, "utf8")]));
259
+ const restore = () => {
260
+ for (const [f, text] of originals) writeFileSync(f, text);
261
+ };
262
+
263
+ // Restoring only in `finally` leaves a neutered checker on the disk when the run is killed —
264
+ // and the next run then measures a repo that is already broken.
265
+ const onSignal = (sig) => {
266
+ restore();
267
+ process.exit(sig === "SIGINT" ? 130 : 143);
268
+ };
269
+ process.on("SIGINT", onSignal);
270
+ process.on("SIGTERM", onSignal);
271
+
272
+ const rows = [];
273
+ try {
274
+ for (const c of cases) {
275
+ let problem = null;
276
+ for (const [file, find, replace] of c.edits) {
277
+ const src = readFileSync(file, "utf8");
278
+ const n = src.split(find).length - 1;
279
+ if (n !== 1) {
280
+ problem =
281
+ n === 0
282
+ ? `NOT FOUND in ${base(file)}`
283
+ : `AMBIGUOUS (${n}) in ${base(file)}`;
284
+ break;
285
+ }
286
+ const next = src.replace(find, replace);
287
+ // The mutation-that-does-not-mutate guard, confirmed against the BYTES rather than the
288
+ // intent. Nine of the ten files lacked it; a replacement equal to the original leaves a
289
+ // green harness that proves nothing and reads exactly like a killed mutation.
290
+ if (next === src) {
291
+ problem = "NO-OP (replacement equals original)";
292
+ break;
293
+ }
294
+ writeFileSync(file, next);
295
+ }
296
+ if (problem) {
297
+ rows.push([c.name, problem, c.disables]);
298
+ restore();
299
+ continue;
300
+ }
301
+
302
+ // A non-kill is retried ONCE before it is believed. Observed on the ledger table: a row
303
+ // killed as named came back DIFFERENT in a later full run and then reproduced as a clean
304
+ // kill when replayed alone. Reporting a flake as a survivor sends the next reader to rewrite
305
+ // a working assertion — a worse outcome than one extra run. A row that fails BOTH times is
306
+ // a real finding and is printed without hedging.
307
+ const attempt = () => {
308
+ const r = run(c.harness, root);
309
+ const out = (r.stdout || "") + (r.stderr || "");
310
+ if (r.status === 0) return "🔴 SURVIVED";
311
+ if (out.includes(c.expect)) return "RED (expected case)";
312
+ // When the harness was ALREADY red, "red" carries no information — but the MESSAGE still
313
+ // does, because node's assert aborts at the first failure, so a message that appears was
314
+ // genuinely reached. Absent it, the two causes are indistinguishable and saying "different
315
+ // case" would be a guess dressed as a verdict.
316
+ return redBefore.has(c.harness)
317
+ ? `🔴 UNJUDGEABLE (harness red before the run; "${c.expect}" never printed)`
318
+ : `RED (but a DIFFERENT case: expected "${c.expect}")`;
319
+ };
320
+ let verdict = attempt();
321
+ if (verdict !== "RED (expected case)") {
322
+ const second = attempt();
323
+ if (second === "RED (expected case)")
324
+ verdict = "RED (expected case, on retry)";
325
+ else verdict = second;
326
+ }
327
+ rows.push([c.name, verdict, c.disables]);
328
+ restore();
329
+ }
330
+ } finally {
331
+ restore();
332
+ process.off("SIGINT", onSignal);
333
+ process.off("SIGTERM", onSignal);
334
+ }
335
+
336
+ const width = Math.max(...rows.map((r) => r[0].length));
337
+ for (const [name, verdict, disables] of rows)
338
+ console.log(`${name.padEnd(width)} ${verdict.padEnd(38)} ${disables}`);
339
+
340
+ // The restore is asserted, not assumed: every harness the run touched must be green again.
341
+ // Only harnesses that were GREEN before can testify about the restore. One that was already red
342
+ // proves nothing either way, and counting it was how this line came to lie.
343
+ const cleanSet = (cleanHarnesses ?? harnesses).filter(
344
+ (h) => !redBefore.has(h),
345
+ );
346
+ const stillRed = cleanSet.filter((h) => run(h, root).status !== 0);
347
+ console.log(
348
+ `\nrestored source: ${
349
+ cleanSet.length === 0
350
+ ? "NOT CHECKED — every harness here was already red before the run"
351
+ : stillRed.length === 0
352
+ ? "harness GREEN"
353
+ : `🔴 RED — the restore failed (${stillRed.map(base).join(", ")})`
354
+ }`,
355
+ );
356
+
357
+ // 🔴 `RED (expected`, not `RED`. Nine of the ten files counted a mutation killed by SOMEONE
358
+ // ELSE'S assertion as a kill; only `ledger` required the named one. Taking the weak rule while
359
+ // printing "each at its own assertion" below would have been a claim the mechanism did not back —
360
+ // exactly the defect this whole directory exists to catch. Measured cost of the strict rule on
361
+ // today's corpus: zero rows change verdict, because every row already came back as its own case.
362
+ // 🔴 A MUTATION THAT NEVER LANDED IS NOT A SURVIVOR, and until 2026-09-17 this line said it
363
+ // was: every row that did not start with `RED (expected` went into `survived`, including
364
+ // `NOT FOUND`, `AMBIGUOUS` and `NO-OP` — cases where the file was never edited at all. The two
365
+ // verdicts send a reader to opposite places: a survivor means STRENGTHEN THE TEST, an unusable
366
+ // case means FIX THE MUTATION. Observed cost: a stale `find` string (the rule had been
367
+ // rewritten) was reported as "1 mutation survived", and the next step would have been rewriting
368
+ // a harness that was fine. The docstring two hundred lines above warns about exactly this
369
+ // confusion — the summary contradicted it.
370
+ const unusable = rows.filter(
371
+ (r) =>
372
+ r[1].startsWith("NOT FOUND") ||
373
+ r[1].startsWith("AMBIGUOUS") ||
374
+ r[1].startsWith("NO-OP"),
375
+ );
376
+ const survived = rows.filter(
377
+ (r) => !r[1].startsWith("RED (expected") && !unusable.includes(r),
378
+ );
379
+ if (unusable.length)
380
+ console.log(
381
+ `🔴 ${unusable.length} mutation(s) UNUSABLE — the patch never landed, so nothing was ` +
382
+ `measured about the test: ${unusable.map((r) => `${r[0]} [${r[1]}]`).join(", ")}`,
383
+ );
384
+ console.log(
385
+ survived.length
386
+ ? `🔴 ${survived.length} mutation(s) survived: ${survived.map((r) => r[0]).join(", ")}`
387
+ : // "each at its own assertion" is not decoration — a kill only counts when the harness went
388
+ // red with the message the case named. A mutation killed by SOMEONE ELSE'S assertion means
389
+ // two defects share one assertion and neither is really watched, and it lands in `survived`.
390
+ unusable.length
391
+ ? `(${rows.length - unusable.length} of ${rows.length} mutations killed; the rest were never applied)`
392
+ : `✓ all ${rows.length} mutations killed by the harness, each at its own assertion`,
393
+ );
394
+ return survived.length || unusable.length || stillRed.length ? 1 : 0;
395
+ }
396
+
397
+ const base = (p) => p.split("/").pop();
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Battery for the mutation engine itself — the one it didn't have.
3
+ *
4
+ * Both cases target ONE property, added 2026-09-15: a skipped harness is a third outcome,
5
+ * and a battery whose harness skips must REFUSE to judge. The first removes the refusal
6
+ * itself, the second removes the ability to recognize a skip. Different causes, one
7
+ * observable effect, and that effect already cost a false "two mutations survived"
8
+ * diagnosis in CI (run 34966606186).
9
+ */
10
+ import { fileURLToPath } from "node:url";
11
+ import { dirname, join, resolve } from "node:path";
12
+ import { runMutations } from "./mutation-driver.mjs";
13
+
14
+ const HERE = dirname(fileURLToPath(import.meta.url));
15
+ const ROOT = resolve(HERE, "..");
16
+ const DRIVER = join(HERE, "mutation-driver.mjs");
17
+ const HARNESS = join(HERE, "mutation-driver.harness.mjs");
18
+
19
+ process.exit(
20
+ runMutations({
21
+ root: ROOT,
22
+ runner: "node",
23
+ cases: [
24
+ {
25
+ // Revert to how it was before 09-17: anything not killed is declared survived — and
26
+ // "the patch never landed" becomes indistinguishable from "the test is weak". The
27
+ // mutation is caught ONLY by the harness's third outcome; the first two pass through
28
+ // it unchanged.
29
+ name: '"never landed" collapses back into "survived"',
30
+ harness: HARNESS,
31
+ expect: "the verdict must be named unusable, not survived",
32
+ disables:
33
+ "the distinction between two verdicts that send the reader in opposite directions: " +
34
+ "survived — strengthen the test, unusable — fix the mutation. Conflating them sends " +
35
+ "the engine to rewrite a working harness",
36
+ edits: [
37
+ [
38
+ DRIVER,
39
+ "if (unusable.length)\n console.log(",
40
+ "if (false)\n console.log(",
41
+ ],
42
+ ],
43
+ },
44
+ {
45
+ name: "the refusal for a skipping harness is removed",
46
+ harness: HARNESS,
47
+ expect: "a skip must REFUSE",
48
+ disables:
49
+ "the refusal itself — the driver goes back to judging by a harness that never ran",
50
+ edits: [[DRIVER, " if (skipped.length) {", " if (false) {"]],
51
+ },
52
+ {
53
+ name: "the skip exit code stops being recognized",
54
+ harness: HARNESS,
55
+ expect: "a skip must REFUSE",
56
+ disables:
57
+ 'recognizing the protocol code — a skip starts reading as "red"',
58
+ edits: [
59
+ [
60
+ DRIVER,
61
+ "const VIGILES_SKIP_EXIT = 77;",
62
+ "const VIGILES_SKIP_EXIT = 76;",
63
+ ],
64
+ ],
65
+ },
66
+ ],
67
+ }),
68
+ );
@@ -0,0 +1,34 @@
1
+ // Types for lib/paper-config.mjs, which `src/*.ts` imports. Without this file TypeScript sees
2
+ // `any` and each import needs a `@ts-expect-error`, which covers only the next line and stops
3
+ // working as soon as a formatter wraps the import across several lines.
4
+ //
5
+ // The field names are typed as `string`, not as their values, so that the value is written
6
+ // once, in paper-config.mjs.
7
+
8
+ export declare const CONFIG_KEY: string;
9
+ export declare const LEGACY_CONFIG_KEY: string;
10
+ export declare const LEGACY_KEY_MESSAGE: string;
11
+
12
+ /** Where a parsed package.json keeps the settings: the new key, else the old one. */
13
+ export declare function declaredSettings(pkg: unknown): {
14
+ readonly settings: unknown;
15
+ readonly legacy: boolean;
16
+ readonly conflict: string | null;
17
+ };
18
+
19
+ /** The settings object alone — undefined when absent or on a conflict. */
20
+ export declare function settingsOf(
21
+ pkg: unknown,
22
+ ): Readonly<Record<string, unknown>> | undefined;
23
+ export declare const DEFAULT_PAPERS_ROOT: string;
24
+ export declare const PAPERS_DIR_FIELD: string;
25
+ export declare const OLD_PAPERS_DIR_FIELD: string;
26
+
27
+ /** The error text for settings that still use the old field name, or `null` when they do not. */
28
+ export declare function renamedFieldMessage(
29
+ settings: unknown,
30
+ where?: string,
31
+ ): string | null;
32
+
33
+ /** Every key the settings object may hold, mapped to who reads it. */
34
+ export declare const SETTINGS_KEYS: Readonly<Record<string, string>>;