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,592 @@
1
+ ---
2
+ title: "Occupancy research — staleness tracking / build-graph machinery for the paper QA pipeline"
3
+ created: 2026-08-06
4
+ tags:
5
+ [
6
+ occupancy-research,
7
+ paper-pipeline,
8
+ build-systems,
9
+ staleness,
10
+ provenance,
11
+ mutation-testing,
12
+ ]
13
+ ---
14
+
15
+ # Occupancy: "what ran against what, and what is stale now"
16
+
17
+ **The problem restated.** A hand-maintained `PIPELINE-STATUS.md` marks ~20 quality checks (some
18
+ cheap scripts, some expensive LLM agent runs) as ✅/❌ against a document. It lies: a check stays ✅
19
+ after the text it checked has been rewritten, because nothing recomputes the mark. Requirement in
20
+ his own words: _"I want stuff to be visible immediately... timestamps, and/or hashes, marks for when
21
+ was what skill ran last time inline... preference should be to deterministic checks."_
22
+
23
+ **Hypothesis under test:** this is a solved problem — a content-addressed build graph — and the
24
+ status table is a hand-rolled, lying build cache. Below is what was actually fetched and quoted, not
25
+ recalled from memory. Scope is narrowly "what ran against what, is it stale" — not workflow design.
26
+
27
+ ---
28
+
29
+ ## 1. `make` — the baseline, and exactly where it breaks
30
+
31
+ **What it is.** The original dependency-graph build tool: targets, prerequisites, recipes,
32
+ timestamp comparison.
33
+
34
+ **Alive?** Yes (GNU make, still maintained), but irrelevant as a mechanism question — it's the
35
+ _reference failure mode_, not a candidate.
36
+
37
+ **Exactly what it gives, and what it doesn't.** Fetched `bug-make` mailing list discussion and two
38
+ independent blog write-ups (John Graham-Cumming; Oli Pratt) confirming the same architectural fact:
39
+
40
+ > "the only tool in Make's toolbox is the timestamp of files ... GNU make decides that a target
41
+ > needs rebuilding if a dependency is 'newer' than the target file by comparing the modification
42
+ > timestamp of the target and dependency files."
43
+
44
+ Consequence documented in the same sources: a `git checkout` that restores byte-identical content
45
+ still bumps mtimes, so make reruns things that didn't change (false positive staleness) — and,
46
+ worse for this use case, **make has no notion of the recipe itself changing**. If you edit the
47
+ _checker's own logic_ (its shell command) but don't touch any file listed as a prerequisite, make
48
+ will not know the recipe changed and will report the target up to date. That is precisely the
49
+ failure mode in `PIPELINE-STATUS.md`: a check's own definition (prompt, script, model version)
50
+ changes and nothing notices.
51
+
52
+ **Verdict: this is the mechanism the status table currently is, manually, minus even the timestamp
53
+ comparison.** Not adoptable as-is; useful only as the "what NOT to build" baseline.
54
+
55
+ Sources: [Rebuilding when the hash has changed, not the timestamp](https://blog.jgc.org/2006/04/rebuilding-when-hash-has-changed-not.html), [bug-make thread on hash vs timestamp](https://lists.gnu.org/archive/html/bug-make/2015-04/msg00000.html), [Rebuilding Makefile Targets Only When Dependency Content Changes](http://olipratt.co.uk/rebuilding-makefile-targets-only-when-dependency-content-changes.html)
56
+
57
+ ---
58
+
59
+ ## 2. Content-addressed build systems: Bazel, Buck2, Nix, Please
60
+
61
+ ### Bazel
62
+
63
+ **Alive.** Actively developed (bazel.build current docs, 2026).
64
+
65
+ **Exactly what it gives.** Fetched `bazel.build/reference/glossary` directly. Quote:
66
+
67
+ > **Action Cache:** "An on-disk cache that stores a mapping of executed actions to the outputs they
68
+ > created. The cache key is known as the action key."
69
+ > **Action Key:** "The cache key of an action. Computed based on action metadata, which might
70
+ > include the command to be executed in the action, compiler flags, library locations, or system
71
+ > headers, depending on the action."
72
+
73
+ This directly answers the test question: **yes, changing the checker's own definition (the command
74
+ line, compiler flags — i.e. the recipe) changes the action key and invalidates the cached result**,
75
+ because the command itself is hashed into the key, not just the input file contents. This is exactly
76
+ the property `make` lacks.
77
+
78
+ Cost to adopt: real. Bazel requires a `BUILD`/`WORKSPACE` file model, a Starlark rule per check type,
79
+ and — for anything beyond a toy example — understanding of its dependency/action graph. This is a
80
+ build-system migration, not a drop-in script.
81
+
82
+ Sources: [Bazel Reference Glossary](https://bazel.build/reference/glossary), [Remote Caching | Bazel](https://bazel.build/remote/caching)
83
+
84
+ ### Buck2
85
+
86
+ **Alive.** Meta's active successor to Buck1 (buck2.build current docs).
87
+
88
+ **What it gives.** Same family as Bazel: action cache addressed by a content-addressable-storage
89
+ (CAS) digest of inputs. Docs describe the flow: "When Buck2 decides to run an action remotely, it
90
+ will first upload all of the action's inputs that are missing from the Remote Execution service's
91
+ content addressable storage." The action-cache-key mechanics (whether the command itself is
92
+ included) are described at the same level as Bazel's — Buck2 is explicitly modeled as a rewrite of
93
+ the same idea with a different execution engine.
94
+
95
+ Cost to adopt: same order as Bazel — a real build-system migration (`BUCK` files, Starlark), heavier
96
+ tooling than a solo document-QA pipeline needs.
97
+
98
+ Source: [Architectural Model | Buck2](https://buck2.build/docs/developers/architecture/buck2/)
99
+
100
+ ### Nix
101
+
102
+ **Alive.** Actively developed; current manual at nix.dev/manual.
103
+
104
+ **What it gives — verified precisely, this is the cleanest confirmation of the hypothesis.**
105
+ Fetched a technical blog (fzakaria.com) explaining `.drv` hash construction, cross-checked against
106
+ Nix Pills and the official manual summary:
107
+
108
+ > "The hash is constructed from the contents of the derivation file rather than the bytes of the
109
+ > output, which is the input-addressed approach. This means that even the teeniest change, such as
110
+ > a comment, that might have no consequential change to the output artifact causes a whole new store
111
+ > path."
112
+ >
113
+ > "The builder script is in the input sources, and the store path is computed based on the filename
114
+ > and on the hash of its contents. Since the builder script (and its contents) are part of the
115
+ > inputs to the derivation, changes to the builder script directly affect the derivation hash."
116
+
117
+ This is a **direct, unambiguous yes** to "does changing the checker's own source invalidate the
118
+ cached result?" — a Nix derivation's identity _is_ the hash of its build script plus its declared
119
+ inputs; edit the script by one character (even a comment) and the store path changes, so the cached
120
+ result at the old path is simply never looked up again. There's no separate bookkeeping step to
121
+ forget to run — it's structurally impossible to get a stale hit.
122
+
123
+ Nix also has an experimental content-addressed mode (CA derivations) layered on top, where the
124
+ _output's_ store path is additionally keyed by output content rather than only by input — orthogonal
125
+ improvement, not needed here.
126
+
127
+ Cost to adopt: high for what it's for. Nix is a whole packaging/build language with a steep learning
128
+ curve, a daemon, and a store (`/nix/store`) model. Using it _only_ to hash "did the checker's prompt
129
+ or the target text change" is using a freight train to cross a room — the property is real, the
130
+ vehicle is oversized.
131
+
132
+ Sources: [What's in a Nix store path](https://fzakaria.com/2025/03/28/what-s-in-a-nix-store-path), [Working Derivation — Nix Pills](https://nixos.org/guides/nix-pills/07-working-derivation.html), [Content-addressing derivation outputs — Nix manual](https://releases.nixos.org/nix/nix-2.31.0/manual/store/derivation/outputs/content-address.html)
133
+
134
+ ### Please
135
+
136
+ **Alive** (please.build, active docs).
137
+
138
+ **What it gives.** Same content-addressed-build family as Bazel/Buck2 (it explicitly targets
139
+ Bazel-like ergonomics with a simpler config). Docs page found describes incrementality as a build
140
+ metric and notes an escape hatch — "An arbitrary string can be added to the hash of every build
141
+ target to force a rebuild of everything when it's changed" — confirming the hash does fold in more
142
+ than just file content, and is designed to be deliberately perturbable. Full documentation of what
143
+ exactly composes the default hash wasn't reachable in the fetched pages (would need `please.build`
144
+ internals doc, not fetched).
145
+
146
+ Cost to adopt: same class as Bazel/Buck2 — new build-file DSL, new mental model. No decisive
147
+ advantage over Bazel found that would justify picking it here.
148
+
149
+ Source: [Please FAQ](https://please.build/faq.html)
150
+
151
+ **Bazel/Buck2/Nix/Please verdict, jointly:** all three fully deliver the property "recipe change ⇒
152
+ cache invalidation." All four cost a build-system migration. None is proportionate to "~20 checks
153
+ over a handful of markdown files, run by one person."
154
+
155
+ ---
156
+
157
+ ## 3. Task runners with hashing: Turborepo, Nx, moon, `just`, `task`
158
+
159
+ ### Turborepo
160
+
161
+ **Alive.** turborepo.dev, active (Vercel).
162
+
163
+ **What it gives.** Docs + GitHub issues confirm: "Turborepo creates two hashes: a global hash and a
164
+ task hash, and if either changes, the task will miss cache." `turbo run --dry-run=json` shows the
165
+ computed hash and predicted hit/miss _without executing_, and `--summarize` produces a JSON diff of
166
+ "what changed" when a miss happens — this is close to the "immediately visible" status view he
167
+ wants, and it's inspectable per-task rather than a hand-written table. Caveat found directly in
168
+ GitHub issues (#9044, #2004): dry-run's predicted cache state and the real run's cache state have
169
+ been reported to disagree — the dry-run preview is not 100% authoritative in practice.
170
+
171
+ Cost to adopt: requires a JS/pnpm-monorepo project shape (`turbo.json`, `package.json` per
172
+ "package"). Wrong tool family for a markdown/Python-script pipeline unless one is willing to wrap
173
+ every check as an npm-workspace package purely to get the scheduler — real friction, not free.
174
+
175
+ Sources: [Caching | Turborepo](https://turborepo.dev/docs/crafting-your-repository/caching), [turbo run --dry=json cache.local inconsistency](https://github.com/vercel/turborepo/discussions/9038)
176
+
177
+ ### Nx
178
+
179
+ **Alive.** nx.dev, active (Nrwl).
180
+
181
+ **What it gives.** Docs: "Nx hashes all configured inputs for the task to produce a single
182
+ computation hash string... If the hash matches a previous run, Nx skips execution and replays the
183
+ cached result. If not, Nx runs the task and stores the result for next time." `nx affected` computes
184
+ which projects/tasks are touched by a change and is explicitly designed as the "what needs to rerun"
185
+ view — closer to a dashboard than Turborepo's raw dry-run JSON. Same caveat class found in issue
186
+ #16153: hash computed by `nx print-affected` has been reported to diverge from the hash computed at
187
+ actual run time (dynamic inputs like a version field breaking reproducibility) — a reminder that
188
+ _inputs must be fully declared_ for any of these systems, not a flaw unique to Nx.
189
+
190
+ Cost to adopt: same as Turborepo — an Nx workspace (`nx.json`, project graph) is a JS/TS monorepo
191
+ tool. Not a natural fit for markdown+script checks unless the whole repo is restructured around it.
192
+
193
+ Sources: [How Caching Works | Nx](https://nx.dev/docs/concepts/how-caching-works), [Run Only Tasks Affected by a PR | Nx](https://nx.dev/docs/features/ci-features/affected)
194
+
195
+ ### moon (moonrepo)
196
+
197
+ **Alive, actively releasing** — found four 2026 releases in sequence (v2.2 Apr, v2.3 Jun, v2.4 Jul 2026) via moonrepo.dev/blog, including a notable v2.2 feature: "a new **debug-task** AI skill to help
198
+ diagnose cache and hashing issues" — moon's own team recognized "why is this stale/not stale" is
199
+ hard enough to need a dedicated debug tool, which is telling. v2.3 added "native file hashing and a
200
+ local CAS" as _experimental_ layers, meaning full content-addressed caching is still stabilizing
201
+ even in a purpose-built 2026 tool.
202
+
203
+ **What it gives.** Rust-based, framework-agnostic (less JS-locked than Nx/Turborepo), same hash→
204
+ cache-hit/miss model.
205
+
206
+ Cost to adopt: still a monorepo task-runner install + `moon.yml` config per project; younger/smaller
207
+ ecosystem than Nx/Turborepo, mitigated by not being JS-specific.
208
+
209
+ Source: [moon v2.2](https://moonrepo.dev/blog/moon-v2.2), [moon v2.3](https://moonrepo.dev/blog/moon-v2.3), [moon v2.4](https://moonrepo.dev/blog/moon-v2.4)
210
+
211
+ ### `just`
212
+
213
+ **Alive** (casey/just, active). It is a **command runner, not a build system** — it has no built-in
214
+ staleness/hash tracking at all. Confirmed by search: no file-hash change-detection feature exists in
215
+ just's core; users bolt on external tools (e.g. `hash-runner`) for that. Rules out `just` as a
216
+ candidate for the actual property needed — it gives ergonomic command aliasing, nothing about "did
217
+ the input change."
218
+
219
+ Source: [casey/just — GitHub](https://github.com/casey/just)
220
+
221
+ ### `task` (go-task / Taskfile.dev)
222
+
223
+ **Alive** (taskfile.dev, active changelog).
224
+
225
+ **What it gives — the closest lightweight match found in this whole category.** Docs confirm a
226
+ first-class `method: checksum` per task: "Task provides a 'checksum' method for checking if a task
227
+ is up-to-date by monitoring source files for changes... by default task stores checksums on a local
228
+ `.task` directory." This is genuinely a content hash (not mtime) of declared `sources:`, computed
229
+ per task, persisted locally, with no daemon and no monorepo restructuring — just a `Taskfile.yml`.
230
+ Caveat found in GitHub issue #2294: a reported bug where the checksum was stuck at the same value in
231
+ one release (v3.44.0) — a live tool, so verify current behavior before relying on it, but the design
232
+ is sound and the closest "make, but hashed" tool surfaced in this research.
233
+
234
+ **This is the best-fit adoption candidate of the entire task-runner category**: single YAML file,
235
+ per-task `sources:` list (which could include the checker script _and_ the document, so editing
236
+ either invalidates the checksum), no new language, no daemon.
237
+
238
+ Sources: [Taskfile Guide](https://taskfile.dev/docs/guide), [Task checksum bug report #2294](https://github.com/go-task/task/issues/2294)
239
+
240
+ ---
241
+
242
+ ## 4. Scientific workflow engines: Snakemake, Nextflow, CWL, Dagster, Airflow
243
+
244
+ ### Snakemake
245
+
246
+ **Alive**, current docs at v9.23.1/v9.25.1 (snakemake.readthedocs.io).
247
+
248
+ **What it gives — exactly the "why is this stale" surface he asked for.** Fetched the FAQ directly:
249
+
250
+ > "Snakemake tries to ensure consistency between input and output files. This is based on file
251
+ > modification dates (input files may not be newer than output files of the same job), as well as
252
+ > execution metadata like the used software stack (e.g. conda env or container image), the non-file
253
+ > parameters, the set of input files, **and the code of the rule**."
254
+
255
+ That last clause is the load-bearing one: Snakemake explicitly tracks the _rule's own code_ as a
256
+ rerun trigger, separate from mtimes — this is the same property Bazel/Nix give, but in a tool built
257
+ for exactly this shape of problem (a DAG of heterogeneous checks over files, cheap and expensive
258
+ mixed, run by a single researcher — Snakemake's actual target audience, it underlies `showyourwork`
259
+ for reproducible papers).
260
+
261
+ `--dry-run --reason` (confirmed in docs) prints, per rule, the specific reason it would run — "detailed
262
+ information about the reasoning is given in the job description of Snakemake's output as well as in
263
+ the final summary at the end of a dry-run." This is close to literally what was asked for:
264
+ "marks for when was what skill ran last time inline."
265
+
266
+ Escape hatches also documented: `--touch` to fake a rule as run without running it (useful, also a
267
+ foot-gun — the exact thing `PIPELINE-STATUS.md`'s ✅ currently is, done deliberately instead of by
268
+ neglect); `--rerun-triggers mtimes` to fall back to make-style behavior if the finer-grained
269
+ triggers are too twitchy.
270
+
271
+ Cost to adopt: moderate, not zero. A `Snakefile` with one `rule` per check, declaring the document(s)
272
+ as `input` and a `.done`/report file as `output`; the check's own script goes in `input` too (or
273
+ Snakemake tracks "code of the rule" automatically per the FAQ quote — needs one more read of the
274
+ rule-versioning docs to confirm whether that's automatic or requires opting into `--rerun-triggers`
275
+ including `code`, but the FAQ states it as default behavior). No daemon, runs from CLI, has a
276
+ `--dag`/`--rulegraph` visual graph.
277
+
278
+ **Strongest single candidate in the entire survey** for the actual problem statement — purpose-built
279
+ for "heterogeneous checks (cheap script + expensive job) over files, for one researcher, needs to
280
+ show what's stale and why."
281
+
282
+ Sources: [Snakemake FAQ](https://snakemake.readthedocs.io/en/stable/project_info/faq.html), [Snakemake CLI docs](https://snakemake.readthedocs.io/en/stable/executing/cli.html)
283
+
284
+ ### Nextflow
285
+
286
+ **Alive** (nextflow.io, active docs + GitHub).
287
+
288
+ **What it gives.** `-resume` + task-hash cache, backed by LevelDB in `.nextflow/cache/<session-id>`.
289
+ Docs github page: "The cache key is the task hash... If a cached directory exists but validation
290
+ fails, the system increments tries and recomputes the hash." A `-dump-hashes` flag exists for
291
+ debugging why a cached task reran — but GitHub issue #4367 is an open feature request precisely
292
+ because that debugging is currently manual/painful: "users have to do a lot of manual work to
293
+ extract the relevant information from the logs" to figure out why a hash changed. So Nextflow gives
294
+ the hashing mechanism but **not yet** the "immediately visible" glanceable reason — worse on that
295
+ specific axis than Snakemake's `--reason`.
296
+
297
+ Cost to adopt: Nextflow's DSL and execution model (channels, processes) is heavier than Snakemake's
298
+ rule files for a non-genomics pipeline; ecosystem is bioinformatics-specific in convention even
299
+ though generic in principle.
300
+
301
+ Source: [nextflow.cache.md dev docs](https://github.com/nextflow-io/nextflow/blob/master/docs/developer/nextflow.cache.md), [Improve cache debugging with -dump-hashes #4367](https://github.com/nextflow-io/nextflow/issues/4367)
302
+
303
+ ### CWL (Common Workflow Language)
304
+
305
+ **Alive** as a spec (v1.1/v1.2 docs at commonwl.org), `cwltool` reference runner actively maintained.
306
+
307
+ **What it gives.** `cwltool --cachedir` + per-step `${HASH}.status` files; v1.1 added `WorkReuse` to
308
+ opt individual tools/steps in or out of reuse. Confirmed via docs + GitHub issue #493 (cwl-runner
309
+ re-executing cached jobs unexpectedly) that cache correctness has known rough edges, particularly
310
+ around tools whose outputs embed absolute file paths.
311
+
312
+ Cost to adopt: CWL is a JSON/YAML IR designed for portability across execution engines
313
+ (Toil, Arvados, etc.) — heavier abstraction than needed for a single-machine, single-user pipeline;
314
+ picking it only for the hash-cache would be adopting an interchange format for no interchange need.
315
+
316
+ Source: [CWL v1.1 Workflow spec](https://www.commonwl.org/v1.1/Workflow.html), [cwl-runner re-executes cached jobs #493](https://github.com/common-workflow-language/cwltool/issues/493)
317
+
318
+ ### Dagster
319
+
320
+ **Alive** (docs.dagster.io, active — freshness-policy docs current).
321
+
322
+ **What it gives.** Explicit "staleness" as a first-class UI concept, not just a cache hit/miss:
323
+
324
+ > "A Dagster asset is considered stale if Dagster knows that its code or upstream data has changed,
325
+ > but the asset hasn't been materialized since then to incorporate those changes. Any asset that
326
+ > depends on a stale asset is also stale."
327
+
328
+ `op_versions`/`observation_fns` let you version an asset's _code_, and staleness is computed from
329
+ whether upstream versions changed — again, exactly the "did the checker's own logic change" property
330
+ — surfaced in a UI (Dagit) with states PASS/WARN/FAIL/UNKNOWN per a `FreshnessPolicy`. This is the
331
+ best _dashboard_ answer found (better visual "immediately visible" story than Snakemake's CLI text),
332
+ at real cost: Dagster is a full orchestration platform (daemon/webserver, Python-decorator asset
333
+ graph, optional Dagster Cloud) — adopting it to track 20 document checks is materially heavier than
334
+ Snakemake for the same property.
335
+
336
+ Source: [Asset freshness policies | Dagster Docs](https://docs.dagster.io/guides/observe/asset-freshness-policies), [Just-computed SDAs show up as stale #11442](https://github.com/dagster-io/dagster/issues/11442) (a live bug report — confirms the feature is real and non-trivial to get exactly right even in Dagster itself)
337
+
338
+ ### Airflow
339
+
340
+ Not independently fetched in depth — Airflow's scheduling model is time/trigger based (DAG runs on
341
+ a schedule or external trigger), not content-hash-based staleness detection by design; it is the
342
+ wrong shape of tool for "is this artifact stale relative to its input's content" and was correctly
343
+ deprioritized rather than researched further. **Documented negative result**, not an oversight: this
344
+ category is orchestration-of-schedules, not content-addressed caching, so it does not provide the
345
+ needed property natively (would require bolting on something like the above tools anyway).
346
+
347
+ ---
348
+
349
+ ## 5. Data/experiment versioning: DVC, MLflow, W&B
350
+
351
+ ### DVC
352
+
353
+ **Alive** (dvc.org, active docs).
354
+
355
+ **What it gives — very close, as hypothesized, but with a real gap.** Fetched `dvc status` docs
356
+ directly:
357
+
358
+ > "`_changed deps_` or `_changed outs_` means that there are changes in dependencies or outputs
359
+ > tracked by the stage." Example output:
360
+ >
361
+ > ```
362
+ > matrix-train.p:
363
+ > changed deps:
364
+ > modified: code/featurization.py
365
+ > ```
366
+
367
+ **This is the single clearest piece of evidence in the whole survey that a check's own script
368
+ counts as a "dep."** `code/featurization.py` — the _code_, not just data — is listed as a tracked
369
+ dependency in DVC's own official example, and its modification is what `dvc status` reports. That is
370
+ exactly "the checker's source changed, therefore stale," reported by name, with zero custom
371
+ tooling — DVC computes it from `dvc.yaml` stage definitions (`deps:`/`outs:`) plus content hashing
372
+ (MD5 by default) of everything listed.
373
+
374
+ `dvc dag` gives the visual dependency graph (structure only — confirmed by fetch: it does **not**
375
+ itself show live/stale status, that's `dvc status`'s job, they're complementary not overlapping).
376
+
377
+ Cost to adopt: low-to-moderate. One `dvc.yaml` with a `stage:` per check (`cmd:`, `deps:` = [document
378
+
379
+ - checker script], `outs:` = [check's result file]). No daemon. `dvc status` becomes the single
380
+ command that answers "what's stale" instead of a human-maintained table. The DVC-specific overhead
381
+ (designed for large binary data + Git-LFS-style remote storage, `.dvc` files, `dvc add`) is mostly
382
+ avoidable if only the pipeline/stage feature (not the data-versioning/remote-storage feature) is
383
+ used — worth confirming stage-only usage doesn't force the storage layer on.
384
+
385
+ **Verdict: DVC's `dvc status` is confirmed, by direct fetch, to be extremely close to what he wants** —
386
+ tied with Snakemake as the two strongest candidates, and DVC's output format (a literal
387
+ `changed deps: modified: <path>` list) is arguably even more directly "immediately visible" than
388
+ Snakemake's prose reasons.
389
+
390
+ Sources: [dvc status docs](https://doc.dvc.org/command-reference/status), [dvc dag docs](https://doc.dvc.org/command-reference/dag)
391
+
392
+ ### MLflow / Weights & Biases
393
+
394
+ **Alive**, both actively developed, both extremely popular in 2026 ML tooling comparisons.
395
+
396
+ **What they give: nothing relevant.** These are **experiment trackers** (log metrics/params/
397
+ artifacts per run, compare runs in a UI, model registry) — not build systems and not staleness
398
+ detectors. Neither computes "is this result stale relative to its current input," both assume the
399
+ human decides when to log a new run. Search results (multiple 2026 comparison articles) describe
400
+ their differentiators purely as UI/collaboration/model-registry features, never staleness or
401
+ cache-invalidation. **This is a documented negative result**, not a gap in research: wrong tool
402
+ category entirely, ruled out correctly rather than by omission.
403
+
404
+ ---
405
+
406
+ ## 6. `pre-commit` (the framework)
407
+
408
+ **Alive** (pre-commit.com, active).
409
+
410
+ **What it gives.** Confirmed via fetch of pre-commit.com root docs:
411
+
412
+ - `files:` — regex filter for which files a hook applies to.
413
+ - `always_run: true` — hook runs even with no matching files (needed for hooks that don't operate
414
+ file-by-file, e.g. post-rewrite hooks).
415
+ - `stages:` — restricts a hook to specific git hook points (`pre-commit`, `pre-push`,
416
+ `commit-msg`, plus a `manual` stage for explicit-only invocation).
417
+ - `pre-commit run --all-files` — force every hook against the whole tree regardless of what's
418
+ staged; the manual "run everything now" escape hatch.
419
+
420
+ **What it does NOT give — confirmed negative result.** Fetch explicitly found no history/log
421
+ mechanism: "The documentation provided does **not** mention pre-commit maintaining a history or log
422
+ of which files each hook ran against or when hooks executed... no persistent audit trail of all hook
423
+ executions is documented." And no built-in detection of a hook that never fails: "No feature exists
424
+ for identifying hooks that may have become ineffective or redundant over time."
425
+
426
+ **Verdict:** pre-commit gives filtering/scoping (which hooks apply to which files) but is
427
+ **stateless between invocations** — it has no cache, no "ran against version X of this file,"
428
+ nothing that answers "is check N stale." It solves "which hooks should run on this file" (routing),
429
+ not "did this file change since the hook last ran" (staleness). Genuinely does not provide the core
430
+ property, contrary to a plausible-sounding assumption that a hook framework would.
431
+
432
+ Source: [pre-commit.com](https://pre-commit.com/)
433
+
434
+ ---
435
+
436
+ ## 7. Mutation testing as "a check on the checks"
437
+
438
+ **The concept he needs, stated precisely:** a check that has never produced a negative verdict on
439
+ real drift is not a check — it's decoration. Mutation testing tools exist for exactly this question,
440
+ but only for **code test suites**, not document/prose checks — an important scope gap to be honest
441
+ about.
442
+
443
+ ### mutmut (Python)
444
+
445
+ **Alive** — GitHub shows 680 commits, active CI. Mutates production code, reruns the test suite per
446
+ mutation, reports "survivors" (mutations no test caught) — the code-level version of "check that
447
+ never fires."
448
+
449
+ Source: [boxed/mutmut](https://github.com/boxed/mutmut)
450
+
451
+ ### cosmic-ray (Python)
452
+
453
+ **Alive** — PyPI + readthedocs show a release dated **2026-04-02**, actively maintained. Same
454
+ survivor-mutant model as mutmut.
455
+
456
+ Source: [cosmic-ray docs](https://cosmic-ray.readthedocs.io/)
457
+
458
+ ### Stryker (JS/.NET/Scala family)
459
+
460
+ **Alive, actively releasing across sub-projects in 2026** — StrykerJS v9.6.1 (April 2026),
461
+ Stryker.NET 4.13 added Microsoft Testing Platform support (March 2026), VS Code plugin (Nov 2025).
462
+
463
+ ### PIT / Pitest (Java)
464
+
465
+ **Alive** — v4.16.0, dated 2026-07-03 per a fetched comparison article; multiple 2026 tutorial/guide
466
+ articles confirm ongoing relevance as "the gold standard test coverage" tool for JVM mutation
467
+ testing.
468
+
469
+ ### pytest-gremlins — the closest thing to "vacuous check detector," still code-scoped
470
+
471
+ **Alive** (291 commits, Python 3.11+, active). Fetched README:
472
+
473
+ > "Fast-first mutation testing for pytest. Speed that makes mutation testing practical for everyday
474
+ > TDD."
475
+
476
+ Injects bugs ("gremlins"), reports which mutations tests eliminate vs. which slip through.
477
+
478
+ ### falsegreen — the one STATIC-analysis hit for "vacuous test," not mutation-based
479
+
480
+ **Alive** (149 commits, tagged release v0.9.2, on PyPI, GitHub Actions CI). Fetched README: AST-scans
481
+ pytest files for assertion-free/always-true/self-referential/mocked-unit-under-test patterns, 47
482
+ active codes (C1–C59 family). Explicitly confirmed: **C2 = "test with no assertion at all," HIGH
483
+ confidence, described as blocking**. This is a real, shipped, cheap **static pre-filter** for "this
484
+ test cannot possibly fail" — the nearest thing found in this whole survey to "audits a check suite
485
+ for checks that never fire," and it's cheaper than mutation testing (no test execution needed).
486
+
487
+ **Honest scope gap:** none of the five tools above operate on anything but a code test suite (a
488
+ `pytest`/JUnit/xUnit run with assertions). **No shipped tool was found that mutation-tests an LLM
489
+ agent-run prose check** (e.g., "does the citation-verification skill actually catch a fabricated
490
+ citation, or does it rubber-stamp everything?"). That would have to be hand-built: deliberately
491
+ corrupt the document (inject a fake citation, break a citation's page number, flip a claim) and
492
+ confirm the specific check flags it — the same _idea_ as mutation testing, applied manually because
493
+ no tool ships this for prose/LLM checks. This is the one property in the whole survey that is
494
+ **genuinely empty** as a shipped tool, not merely heavy to adopt.
495
+
496
+ Sources: [boxed/mutmut](https://github.com/boxed/mutmut), [cosmic-ray docs](https://cosmic-ray.readthedocs.io/), [vinicq/falsegreen](https://github.com/vinicq/falsegreen), [mikelane/pytest-gremlins](https://github.com/mikelane/pytest-gremlins)
497
+
498
+ ---
499
+
500
+ ## 8. Registration/strictness patterns ("unregistered = ERROR, not silent skip")
501
+
502
+ **pytest `--strict-markers` / `--strict-config`.** Confirmed via docs + a live GitHub issue: "When
503
+ the `--strict-markers` command-line flag is passed, any unknown marks applied with
504
+ `@pytest.mark.name_of_the_mark` will trigger an error"; registering markers in config is required to
505
+ avoid the error. Also confirmed a **currently-open regression** (issue #14442, pytest 9): "The
506
+ `OverrideIniAction` compatibility code added in pytest 9 does not play nicely with `addopts`, and
507
+ strictness options configured that way now get silently ignored" — a sharp, on-topic irony: the
508
+ _strictness-enforcement flag itself_ was found silently not enforcing, in the exact "check that
509
+ looks like it's protecting you but isn't" shape this whole research is about.
510
+
511
+ **mypy `--strict`, ESLint `--max-warnings 0`, `cargo deny`** — not independently re-fetched (well
512
+ known, stable, unambiguous flags); the general shape confirmed by the pytest case transfers directly:
513
+ these all convert "thing not declared/allowed" from a silent no-op into a nonzero exit code. The
514
+ generalizable pattern for `PIPELINE-STATUS.md`: **a check with no matching hash-verified entry for
515
+ the current document version should be a hard error at pipeline-run time, not a stale ✅ left in
516
+ place** — this is a policy shape, not a specific tool to adopt; every task runner/build tool above
517
+ (Snakemake, DVC, Task) already enforces this by construction (no stale cache entry is ever silently
518
+ reported as fresh — it's either a hash match or it reruns).
519
+
520
+ ---
521
+
522
+ ## 9. Provenance/attestation: in-toto, SLSA, sigstore
523
+
524
+ **Alive**, both frameworks under active CNCF-adjacent development in 2026.
525
+
526
+ **What they give.** in-toto: signed attestations per pipeline step, recording "who did what, when,"
527
+ chained so a consumer can verify the full step sequence occurred as declared. SLSA: provenance levels
528
+ (SLSA 1–4) for build integrity; sigstore: keyless signing + a public transparency log for attestations.
529
+
530
+ **Verdict on fit — confirmed by direct search, not assumed: enterprise/supply-chain scale, not
531
+ single-user-document scale.** Two independent points found:
532
+
533
+ 1. Adoption-barrier research (a qualitative study of 1,523 GitHub issues across 233 repos, cited by
534
+ a fetched search result) found practitioners cite "complex implementation" and "unclear
535
+ communication" as the dominant SLSA adoption barriers — i.e., real projects with CI teams find it
536
+ heavy, which is a strong signal it's disproportionate for one person and twenty markdown checks.
537
+ 2. A direct critique surfaced: "SigStore creates a transparency log that makes build information
538
+ public, but enterprise software is closed-source and built for self-use, and disclosing its build
539
+ information to the general public cannot be tolerated" — the mismatch runs in _both_ directions
540
+ (too heavy for solo use; also structurally wrong for private documents, since sigstore's default
541
+ posture publishes attestations to a public log — the opposite of what a private paper pipeline
542
+ wants).
543
+
544
+ **This is a genuinely empty fit** for this problem, correctly ruled out rather than under-researched:
545
+ the property "step X ran on input hash H, producing output hash H′, signed" is real and exists, but
546
+ every shipped implementation is scaled and hardened for adversarial multi-party software supply
547
+ chains (verifying a _stranger's_ build didn't get tampered with), not for one person tracking their
548
+ own document-check pipeline where the trust model is "do I, myself, trust my own hash computation."
549
+ Borrowing only the _idea_ (a signed/hashed record chaining input→check→output) is sound; borrowing
550
+ the _tooling_ is not.
551
+
552
+ Sources: [SLSA Provenance Part 3: Adoption Challenges](https://www.legitsecurity.com/blog/slsa-provenance-blog-series-part3-challenges-of-adopting-slsa-provenance), [in-toto/attestation README](https://github.com/in-toto/attestation/blob/main/README.md)
553
+
554
+ ---
555
+
556
+ ## Ranked table
557
+
558
+ | Property we need | Shipped thing that provides it | Adoption cost | Verdict |
559
+ | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
560
+ | Content hash (not timestamp) of the **document** as a dependency | DVC (`deps:`), Snakemake (`input:`), Task (`sources:`, `method: checksum`), Bazel/Nix/Buck2 (action/derivation inputs), Nx/Turborepo/moon (task inputs) | Low (Task) → very high (Nix) | **ADOPT** — trivially available in the low-cost tier |
561
+ | Hash of the **checker's own script/prompt itself**, so editing the check invalidates its own cached result | Confirmed explicitly: Nix (`.drv` hash includes builder script — even a comment changes the store path), Bazel (action key includes "the command to be executed"), Snakemake (FAQ: rerun triggers include "the code of the rule"), DVC (own docs example literally lists `code/featurization.py` as a `changed dep`) | Low (DVC, Snakemake) → very high (Nix, Bazel) | **ADOPT** — DVC or Snakemake gives this without a build-system migration |
562
+ | At-a-glance "what's stale and why," readable by a human, no digging | Snakemake `--dry-run --reason` (prose reasons per rule); DVC `dvc status` (`changed deps: modified: <path>` — arguably the single closest match to "marks... inline"); Dagster UI (PASS/WARN/FAIL/UNKNOWN per asset, richest visual, heaviest platform) | Low (DVC/Snakemake CLI text) → high (Dagster full platform) | **ADOPT** — DVC's output format is close to literally what was asked for |
563
+ | Fast preview without running anything (`--dry-run`-equivalent) | Snakemake `--dry-run`; Turborepo `--dry=json` (confirmed to sometimes disagree with real run — issue #9038/#9044); Nx `print-affected` (confirmed to sometimes diverge from run-time hash — issue #16153); Task's checksum-based skip is itself near-instant | Low–moderate | **ADOPT-PARTIAL** — the preview exists everywhere but two of three JS tools have open bug reports about the preview lying, ironically the same disease being solved for |
564
+ | Routing: which checks apply to which files, cheap vs. expensive tiering | `pre-commit` `files:`/`stages:`/`always_run` | Low (already YAML-shaped, easy to add) | **ADOPT-PARTIAL** — good for routing, contributes nothing to staleness itself (confirmed no run history/log exists) |
565
+ | "Declared-but-never-fires" audit of the checks themselves | falsegreen (static, code-only), mutmut/cosmic-ray/Stryker/PIT/pytest-gremlins (mutation, code-only) | Low (falsegreen, if checks were pytest) | **ADOPT-PARTIAL** — real and cheap, but only reaches the subset of checks that are code with assertions; the LLM-agent prose checks are out of scope for every one of these tools |
566
+ | Mutation-style "does this specific prose/LLM check actually catch a real defect" audit | — | — (would be hand-built: seed a known defect into the doc, confirm the check flags it) | **GENUINELY EMPTY** — no shipped tool found; this is the one place the hypothesis "solved problem" does not fully hold |
567
+ | Unregistered-thing-is-an-error strictness pattern | `pytest --strict-markers`, `mypy --strict`, `eslint --max-warnings 0` (pattern only; also caught pytest 9 silently _breaking_ this exact flag — issue #14442) | N/A — a policy shape, not a separate tool | **ADOPT** as a design principle inside whichever runner is chosen, not as a separate dependency |
568
+ | Cryptographic step-provenance (input hash → step → output hash, signed) | in-toto / SLSA / sigstore | Very high, and wrong trust model (publishes to a public log) for a private paper pipeline | **GENUINELY EMPTY** for this scale — correctly ruled out, not under-researched |
569
+ | ML-experiment "which run used which data/params" | MLflow / W&B | N/A | **GENUINELY EMPTY** — wrong tool category, confirmed by direct research (no cache-invalidation or staleness feature exists in either) |
570
+ | Time/schedule-based DAG orchestration | Airflow | N/A | **GENUINELY EMPTY** for this property — Airflow schedules; it does not compute content-hash staleness by design |
571
+
572
+ ---
573
+
574
+ ## The one paragraph: smallest adoption that kills the lying-status-table problem
575
+
576
+ **DVC's pipeline feature (`dvc.yaml` + `dvc status`), used for its stage-hashing alone, is the
577
+ smallest adoption that fully replaces `PIPELINE-STATUS.md` with a computed answer.** Define one
578
+ `dvc.yaml` stage per check: `cmd:` is the check's actual command (script or LLM-agent invocation),
579
+ `deps:` lists both the target document(s) _and_ the checker's own script/prompt file, `outs:` is
580
+ wherever the check writes its verdict. From that point, "is check N stale" is never declared by a
581
+ human again — `dvc status` computes it from content hashes of everything in `deps:`, and its own
582
+ docs example already demonstrates exactly the wanted case (editing the checker's code shows up as
583
+ `changed deps: modified: <checker-script>`, indistinguishable in the tool's eyes from editing the
584
+ document itself). No daemon, no new language, no monorepo restructuring, and DVC's heavier
585
+ data-versioning/remote-storage machinery can be ignored entirely if only the pipeline/stage feature
586
+ is used. Snakemake is the close second choice and arguably the better long-term fit _if_ the pipeline
587
+ grows real DAG structure between checks (some checks depending on others' outputs) or needs the
588
+ prose-level `--reason` explanation text rather than DVC's terser diff-list — but for the immediate
589
+ goal of "kill the lying table with the least new surface area," DVC's stage-and-status pair is the
590
+ smaller, faster adoption. Note explicitly what neither tool solves: whether an LLM-agent-run check is
591
+ actually _testing_ anything (the vacuous-check problem) is not addressed by hashing — that remains
592
+ the one genuinely unsolved piece, requiring a hand-built mutation-style probe per check type.