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,54 @@
1
+ /**
2
+ * pipeline-check.mutations.mjs — plant the defect `pipeline-check.harness.mjs` claims to catch, and require it to die.
3
+ *
4
+ * Run: `node skills/paper-pipeline/scripts/pipeline-check.mutations.mjs` (it rewrites the source and restores
5
+ * it, so it is not part of the plain test run).
6
+ *
7
+ * WHY IT EXISTS. A harness whose success state is silence cannot be observed working: «it passed»
8
+ * and «it cannot fail» produce identical output. Written 2026-09-12, when the mechanism moved into
9
+ * this package and `scripts/run-mutations.mjs` reported this harness as one nothing could kill.
10
+ *
11
+ * ⚠️ HONEST SCOPE: ONE case. It kills the checker outright — the shape that makes every other
12
+ * assertion in the harness vacuous at once — and that is the property worth holding first, not the
13
+ * only one worth holding. Narrower cases were probed and left out DELIBERATELY: the assertions
14
+ * they trip carry no message of their own (a bare deepEqual diff), so `expect` could only match
15
+ * text any unrelated failure also prints, and a case killed by someone else's assertion is not
16
+ * evidence about its own defect. Adding one means giving that assertion a message first.
17
+ */
18
+ import { dirname, join } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ import { runMutations } from "../../../lib/mutation-driver.mjs";
21
+ import { consumerRoot } from "./consumer.mjs";
22
+
23
+ const HERE = dirname(fileURLToPath(import.meta.url));
24
+ const SRC = join(HERE, "pipeline-check.mjs");
25
+ const HARNESS = join(HERE, "pipeline-check.harness.mjs");
26
+
27
+ const MUTATIONS = [
28
+ [
29
+ "check() goes blind",
30
+ [
31
+ [
32
+ SRC,
33
+ "export function check({ sections, header }, { sourceDate, today, dir }) {",
34
+ "export function check({ sections, header }, { sourceDate, today, dir }) {\n if (1) return [];",
35
+ ],
36
+ ],
37
+ "stale-continuous: wrong finding set",
38
+ "every gate-staleness finding: a PIPELINE-STATUS that reports nothing reads as a clean one",
39
+ ],
40
+ ];
41
+
42
+ process.exit(
43
+ runMutations({
44
+ root: consumerRoot(),
45
+ runner: "vigiles",
46
+ cases: MUTATIONS.map(([name, edits, expect, disables]) => ({
47
+ name,
48
+ disables,
49
+ edits,
50
+ harness: HARNESS,
51
+ expect,
52
+ })),
53
+ }),
54
+ );
@@ -0,0 +1,169 @@
1
+ /**
2
+ * pipeline-edges — the ONE declaration of which scorecard row feeds which gate.
3
+ *
4
+ * WHY THIS FILE EXISTS. The dependencies between pipeline skills are already enforced: every paper's
5
+ * `PIPELINE-STATUS.md` has a `Requires` column in its `### GATES` table, and `pipeline-check.mjs`
6
+ * reads it and reports `gate-missing-input` / `gate-stale-input` / `unknown-input`. The machinery is
7
+ * right. What was hand-copied is the DECLARATION — each new paper retypes the cells, so a paper can
8
+ * drop an edge and the checker will then cheerfully confirm a gate that never had its input. That is
9
+ * not a hypothetical: on 2026-08-08 the live `<paper-a>` scorecard had `structure` requiring
10
+ * only `render`, while `argument-arc/SKILL.md` says in its own words that it runs *before*
11
+ * `tighten-paper`.
12
+ *
13
+ * WHY ONE TABLE HERE AND NOT A `requires:` FIELD IN EACH SKILL'S FRONTMATTER. The join key is the
14
+ * SCORECARD ROW ID (`structure`, `writing`, `panel`, …) — that is what the status files and the
15
+ * checker already speak — and a row id is not a property of a skill:
16
+ * - one skill owns two rows: `plan-paper-timeline` owns `access` and `schedule`; `map-prior-work`
17
+ * owns `priorwork` (SETUP sweep) and `priordelta` (CONTINUOUS delta); `argument-arc` owns `frame`
18
+ * (frame) and `arc` (arc). Frontmatter keyed by skill cannot say which of its rows an edge lands on.
19
+ * - one row has no skill at all: `claims` is "Fable diff", a protocol in `references/writing-craft.md`.
20
+ * Frontmatter would therefore need a second table mapping skills to rows, i.e. this table plus a
21
+ * layer. So: one table, keyed the way the rest of the system is keyed.
22
+ *
23
+ * 🔴 WHY THE IDS ARE WORDS AND NOT TWO LETTERS (renamed 2026-08-09). The join key used to be a
24
+ * two-letter code (`St`, `Wc`, `Pn`, …). That space was exhausted and had already collided: in
25
+ * `<paper-a>/PIPELINE-STATUS.md`, `Cr` was BOTH `cold-read-diff` (in the 06.08 gate log)
26
+ * and `Camera-ready` (in `### AFTER`) — one join key, two rows, and the checker merges rows by id.
27
+ * A second collision was live and silent: the row id `Id` (research-ideate) is exactly the table's
28
+ * own header word, so `parseStatus` dropped that row on every paper as if it were a header. Both are
29
+ * unrepresentable once the ids are words: `coldread` ≠ `cameraready`, and `idea` ≠ `id`.
30
+ *
31
+ * SCOPE, deliberately narrow. Only rows in the `### GATES` section appear here, because `Requires` is
32
+ * a GATES column — SETUP/LOOP/CONTINUOUS rows have nowhere to declare an input, so an edge into them
33
+ * is unexpressible and listing it would just be prose. The real ones found in the audit are recorded
34
+ * in `UNEXPRESSED` below rather than dropped.
35
+ *
36
+ * ADMISSION RULE (why an edge is here and not in `NOT_EDGES`): the owning skill states a REQUIREMENT
37
+ * ("required input", "blocked on", "STOP and run those first", "mandatory") or an ORDERING ("run it
38
+ * before X", "run X first") in normative prose — a `Run me` / axis / when-to-run section. An entry in
39
+ * a `## Compose with` list is a suggestion and does NOT qualify, even when it contains the word
40
+ * "first". Every edge carries the sentence that admitted it, so the judgement stays auditable.
41
+ *
42
+ * This is a table and a loop on purpose. There is no graph engine here, and there should not be one:
43
+ * a dozen edges do not need transitive closure, cycle detection or topological sorting, and building
44
+ * those is how a table nobody can read stops being maintained.
45
+ */
46
+
47
+ /** row id → { skill (for the message), requires: { input row id → the sentence that admitted it } } */
48
+ export const CANONICAL_INPUTS = {
49
+ structure: {
50
+ skill: "tighten-paper",
51
+ requires: {
52
+ render:
53
+ 'render-paper/SKILL.md: "that page count is the hard input the `tighten-paper` structure gate is blocked on"',
54
+ arc: 'argument-arc/SKILL.md: "Run it *before* them" (them = tighten-paper, grade-paper-writing) — "cutting words inside a broken arc is how a session burns a day"',
55
+ },
56
+ },
57
+ writing: {
58
+ skill: "grade-paper-writing",
59
+ requires: {
60
+ draft:
61
+ "the template's own cell: the writing grade reads the current draft",
62
+ arc: 'argument-arc/SKILL.md: "Before `tighten-paper` / `grade-paper-writing` / `pc-panel-review` on any draft whose thesis has changed since those gates last ran"',
63
+ structure:
64
+ 'tighten-paper/SKILL.md: "Run it BEFORE those on a bloated draft — no point polishing sentences in a section that should be cut"; harden-paper/SKILL.md: "Order matters: STRUCTURE before SENTENCES. Run axis 0 FIRST."',
65
+ },
66
+ },
67
+ panel: {
68
+ skill: "pc-panel-review",
69
+ requires: {
70
+ structure:
71
+ 'pc-panel-review/SKILL.md: "there must exist for THIS draft … the **structural verdict** from `tighten-paper` … If either doesn\'t exist yet, STOP and run those skills first."',
72
+ writing:
73
+ 'pc-panel-review/SKILL.md: "there must exist for THIS draft: (a) the **persona stall inventory** from `grade-paper-writing`"',
74
+ },
75
+ },
76
+ claims: {
77
+ skill: "Fable diff",
78
+ requires: {
79
+ draft:
80
+ "the template's own cell: the diff is against the pre-pass draft baseline",
81
+ writing:
82
+ 'grade-paper-writing/SKILL.md: "**After applying sentence fixes, run the `claims` claim-preservation diff** … This step is mandatory after EVERY rewrite, not optional."',
83
+ },
84
+ },
85
+ harden: {
86
+ skill: "harden-paper",
87
+ requires: {
88
+ panel:
89
+ 'harden-paper/SKILL.md axis 1: "run `paper-adversarial-review` (quick) and, as the final decision gate, `pc-panel-review`"',
90
+ structure:
91
+ 'harden-paper/SKILL.md axis 0: "**Structure / editorial (run FIRST)** — run **`tighten-paper`**"; paper-pipeline/SKILL.md: "Its structure and writing axes are the **re-run/confirm** pass, not the first invocation."',
92
+ writing:
93
+ 'harden-paper/SKILL.md axis 8: "run **`grade-paper-writing`** … Without it this axis silently does not run"',
94
+ cites:
95
+ 'harden-paper/SKILL.md axis 4: "**Citations** — run `verify-citations`" under "## The axes (run all; each is a gate)"',
96
+ priordelta:
97
+ 'map-prior-work/SKILL.md, the `priordelta` row: "**Whenever the contribution\'s framing moves**, and again before `harden-paper`."',
98
+ },
99
+ },
100
+ submit: {
101
+ skill: "submit-paper",
102
+ requires: {
103
+ harden:
104
+ 'submit-paper/SKILL.md: "Harden first: `pc-panel-review` (incl. its venue-fit mode), `paper-adversarial-review`, `render-paper`."',
105
+ access:
106
+ 'paper-pipeline/SKILL.md GATES table, Submit: "🔴 **The `access` row green.**"',
107
+ },
108
+ },
109
+ };
110
+
111
+ /**
112
+ * Orderings that are REAL but have nowhere to be declared: they end in a SETUP or LOOP row, and only
113
+ * GATES rows carry a `Requires` cell. Kept here so the audit is complete and so nobody re-derives
114
+ * them and then invents a column for them.
115
+ */
116
+ export const UNEXPRESSED = [
117
+ [
118
+ "schedule ← venue",
119
+ 'plan-paper-timeline/SKILL.md: "`find-venue` — the source of the four CFP dates this skill consumes; run it first."',
120
+ ],
121
+ [
122
+ "access ← venue",
123
+ "same sentence: the portal and its form are venue facts, so the venue is picked first.",
124
+ ],
125
+ [
126
+ "draft ← study",
127
+ 'draft-paper/SKILL.md: "**FAIL** — it declined to draft because a required input was missing: no numbers from build-benchmark"',
128
+ ],
129
+ [
130
+ "priorwork ← idea",
131
+ 'paper-pipeline/SKILL.md SETUP: map-prior-work runs "right after `research-ideate` says go"',
132
+ ],
133
+ ];
134
+
135
+ /**
136
+ * Claims read during the audit and JUDGED TOO WEAK to be an edge, with the sentence that made the
137
+ * call hesitate. Recorded because "we looked and decided no" and "we never looked" are
138
+ * indistinguishable afterwards, and the first is only worth anything if it is written down.
139
+ */
140
+ export const NOT_EDGES = [
141
+ [
142
+ "panel ← arc",
143
+ 'argument-arc/SKILL.md: "Before `tighten-paper` / `grade-paper-writing` / `pc-panel-review` **on any draft whose thesis has changed since those gates last ran**." — conditional, and panel already requires structure and writing, which both require arc, so declaring it adds no enforcement.',
144
+ ],
145
+ [
146
+ "writing ← render",
147
+ 'grade-paper-writing/SKILL.md: "`render-paper` — build the PDF/PNGs first so you grade what the reviewer will actually see." — it is an entry in the `## Compose with` list and about fidelity of grading, not a block. `tighten-paper` states its render dependency in normative prose; this one does not.',
148
+ ],
149
+ [
150
+ "harden ← siblings",
151
+ 'harden-paper/SKILL.md axis 3: "For every genuine sibling found, run **`analyze-sibling-paper`**" — conditional on a sibling existing, and `siblings` is itself a trigger row that is n/a on a paper with no close neighbour.',
152
+ ],
153
+ [
154
+ "harden ← venuebar",
155
+ 'study-accepted-papers/SKILL.md: "`harden-paper` — the multi-axis pre-submit gate; this skill **feeds it** the venue-specific axis" — a compose-with, and harden\'s own axis list never calls it.',
156
+ ],
157
+ [
158
+ "harden ← siblings (via analyze-sibling-paper)",
159
+ 'analyze-sibling-paper/SKILL.md: "Compose with … harden-paper (its nearest-neighbor-scoop axis calls this)" — a compose-with sentence in a description, mirrored by the conditional above.',
160
+ ],
161
+ [
162
+ "structure ← draft",
163
+ 'tighten-paper/SKILL.md description: "The developmental / editorial pass on a **DRAFTED** paper" — states a precondition on the object, not on a scorecard row, and no sentence orders it against `draft`.',
164
+ ],
165
+ [
166
+ "writing ← panel / claims ← panel / harden ← claims (the chain read off the GATES table order)",
167
+ 'paper-pipeline/SKILL.md: "Ordered, and the order is real: every one refuses to run without the output of the one before it." — the same table\'s own `Requires (hard)` column contradicts it (Writing craft requires "The current draft", not the structure verdict). Rhetoric about the table, not a declaration; the column is the declaration.',
168
+ ],
169
+ ];
@@ -0,0 +1,178 @@
1
+ /**
2
+ * population-map.harness.mjs — the population gate, made visible to the metric, plus the layer
3
+ * its own self-test cannot reach. `npx vigiles test .claude/skills/paper-pipeline/scripts/population-map.harness.mjs`.
4
+ *
5
+ * 🔴 SCOPE NARROWED 2026-08-26. Two findings out of four (`untied`, `undeclared`) moved to ESLint
6
+ * rules — `paper/population-untied` and `paper/population-undeclared`, tests for them in
7
+ * `eslint-rules/paper-registry.harness.mjs`. Two remain here: `stale` and `badref`, both are about
8
+ * THE REGISTRY ITSELF (`repro/populations.tsv`), both have no address in the paper, and so cannot be expressed by ESLint,
9
+ * which reports only to the linted file.
10
+ *
11
+ * WHY THIS FILE AND NOT MORE CASES IN THE SELF-TEST. `population-map.selftest.mjs` is thorough —
12
+ * 7 planted cases, both directions. Nothing here duplicates it. Two things were wrong with it
13
+ * anyway:
14
+ *
15
+ * 1. IT IS INVISIBLE TO THE MEASUREMENT. `vigiles audit` scores this repo `Tested: 0`, and the
16
+ * metric counts `*.harness.mjs` / `*.eval.mjs`. Verified 2026-08-07: bare `npx vigiles test`
17
+ * reports no harness files found — its glob does not descend into dot-dirs AND the self-test
18
+ * does not carry a name the collector looks for. Given an explicit path it runs
19
+ * fine, which is the trap: it is green, correct, and uncounted, so the repo reads
20
+ * as having no test for its own registry hygiene.
21
+ *
22
+ * 2. IT ONLY EXERCISES `findings()`. Everything between the command line and that function —
23
+ * finding paper.md, finding repro/populations.tsv, `--flags-only` staying silent on a clean
24
+ * paper — is what the CI step and `run-mechanical.mjs` actually invoke, and none of it was
25
+ * under test. This repository's three dead hooks all had correct logic; what was broken was
26
+ * the wiring around it.
27
+ *
28
+ * 🔴 THE SELF-TEST IS RUN AS A SUBPROCESS, NEVER IMPORTED. It ends in `process.exit(fail ? 1 : 0)`.
29
+ * Import it and a PASSING self-test terminates this process at that line — every assertion below it
30
+ * never runs, and `vigiles test` reports ✓ because nothing threw. That is trap #1 from
31
+ * hooks.harness.mjs wearing a different hat, and it would have been undetectable from the output.
32
+ *
33
+ * Every fixture is a throwaway in a temp dir; nothing here reads or writes a real paper.
34
+ */
35
+ import assert from "node:assert/strict";
36
+ import { execFileSync } from "node:child_process";
37
+ import {
38
+ mkdtempSync,
39
+ mkdirSync,
40
+ writeFileSync,
41
+ rmSync,
42
+ realpathSync,
43
+ } from "node:fs";
44
+ import { tmpdir } from "node:os";
45
+ import { join, dirname } from "node:path";
46
+ import { fileURLToPath } from "node:url";
47
+ import { consumerRoot } from "./consumer.mjs";
48
+ const HERE = dirname(fileURLToPath(import.meta.url));
49
+
50
+ const ROOT = consumerRoot();
51
+ const SCRIPT = join(HERE, "population-map.mjs");
52
+ const SELFTEST = join(HERE, "population-map.selftest.mjs");
53
+ const tmp = realpathSync(mkdtempSync(join(tmpdir(), "popmap-harness-")));
54
+
55
+ /** Run a node script; never throws on a non-zero exit. */
56
+ function run(argv) {
57
+ try {
58
+ return {
59
+ code: 0,
60
+ out: execFileSync("node", argv, {
61
+ cwd: ROOT,
62
+ encoding: "utf8",
63
+ stdio: ["ignore", "pipe", "pipe"],
64
+ }),
65
+ };
66
+ } catch (e) {
67
+ return { code: e.status ?? 1, out: (e.stdout || "") + (e.stderr || "") };
68
+ }
69
+ }
70
+
71
+ /** A paper dir carrying `body`, and optionally a population registry. */
72
+ function paper(name, body, tsv) {
73
+ const d = join(tmp, name);
74
+ mkdirSync(join(d, "repro"), { recursive: true });
75
+ writeFileSync(
76
+ join(d, "paper.md"),
77
+ `## Abstract\n\n${body}\n\n## References\n\n[1] x.\n`,
78
+ );
79
+ if (tsv !== undefined)
80
+ writeFileSync(join(d, "repro", "populations.tsv"), tsv);
81
+ return d;
82
+ }
83
+
84
+ const REG = [
85
+ "id\tprinted\trelation\trelated_to\tgloss",
86
+ "broad\t1,921\troot\t\tthe corpus",
87
+ "census\t134\tdisjoint\tbroad\ta separate sample",
88
+ ].join("\n");
89
+
90
+ // ── 1. the self-test still kills every mutant it claims to ─────────────────────────────
91
+ // Owning it from a *.harness.mjs is the whole point: the metric now counts it, and a regression in
92
+ // the self-test itself fails this file rather than passing unnoticed in a step nobody reads.
93
+ {
94
+ const r = run([SELFTEST]);
95
+ assert.equal(r.code, 0, "population-map.selftest.mjs failed:\n" + r.out);
96
+ const m = r.out.match(/(\d+) passed, (\d+) failed/);
97
+ assert.ok(
98
+ m,
99
+ "the self-test printed no result line — it may have exited before running:\n" +
100
+ r.out,
101
+ );
102
+ // A self-test that runs ZERO cases also prints "0 failed" and exits 0. That shape is exactly how
103
+ // this repo's earlier harness reported ✓ on `assert.equal(1, 2)`, so the count is asserted too.
104
+ // Threshold lowered 13 → 7 along with the move of two findings to ESLint. The assertion on COUNT is kept:
105
+ // a self-test that ran zero cases also prints «0 failed» and exits zero.
106
+ assert.ok(
107
+ Number(m[1]) >= 7,
108
+ `the self-test ran only ${m[1]} case(s) — cases have gone missing`,
109
+ );
110
+ assert.equal(Number(m[2]), 0, "the self-test reported failures:\n" + r.out);
111
+ }
112
+
113
+ // ── 2. the CLI finds the paper and the registry, and fires on a stale registry row ─────
114
+ // 🔴 FIXTURE REWRITTEN 2026-08-26. Before that it planted an UNLINKED POPULATION, and that
115
+ // finding moved to `paper/population-untied` — meaning the old «dirty» input became clean, and
116
+ // the block would silently stop checking the argv → findings() wiring while staying green. Exactly that
117
+ // class of failure this file was written for.
118
+ {
119
+ const dirty = paper(
120
+ "cli-stale",
121
+ "The resolver ran over all 1,921 repositories at pinned commits.",
122
+ REG,
123
+ );
124
+ const r = run([SCRIPT, dirty, "--flags-only"]);
125
+ assert.match(
126
+ r.out,
127
+ /134 \(census\) is declared and the body no longer prints it/,
128
+ "the CLI did not report a stale registry row — the self-test proves findings() sees it, so what " +
129
+ "is broken is the path from argv to findings():\n" +
130
+ r.out,
131
+ );
132
+ }
133
+
134
+ // ── 3. …and says NOTHING when every declared population is printed ──────────────────────
135
+ // The direction that matters more. A gate that fires on a paper which already obeys the rule gets
136
+ // muted the same week, and a muted gate is indistinguishable from an absent one.
137
+ {
138
+ const clean = paper(
139
+ "cli-clean",
140
+ "The resolver ran over all 1,921 repositories at pinned commits, and a census of 134\nrepositories is a separate sample from those 1,921, overlapping in seven.",
141
+ REG,
142
+ );
143
+ const r = run([SCRIPT, clean, "--flags-only"]);
144
+ assert.equal(
145
+ r.out.trim(),
146
+ "",
147
+ "--flags-only printed something about a paper whose registry matches its body:\n" +
148
+ r.out,
149
+ );
150
+ }
151
+
152
+ // ── 4. KNOWN GAP, asserted so it stays visible ─────────────────────────────────────────
153
+ // 🔴 A paper with NO repro/populations.tsv exits 0 in silence, and `run-mechanical.mjs` records
154
+ // that silence as PASS for draft-paper. So a paper that never declared its populations scores
155
+ // identically to one that declared them and tied every one. Absence is invisible again — the same
156
+ // shape artifact-coverage.mjs was written to close for the released bundle.
157
+ //
158
+ // This asserts the CURRENT behaviour. When population-map learns to say "this paper has no
159
+ // registry", this assertion flips and must be updated; writing the gap down is how it stops being
160
+ // mistaken for coverage.
161
+ {
162
+ const bare = paper(
163
+ "cli-no-registry",
164
+ "We measured 1,921 repositories and a census of 134 repositories.",
165
+ undefined,
166
+ );
167
+ const r = run([SCRIPT, bare, "--flags-only"]);
168
+ assert.equal(
169
+ r.out.trim(),
170
+ "",
171
+ "population-map now says something about a paper with no registry — good; update this assertion",
172
+ );
173
+ }
174
+
175
+ rmSync(tmp, { recursive: true, force: true });
176
+ console.log(
177
+ "✓ population-map: self-test owned (7 cases), CLI wiring fires on a stale row and stays quiet, 1 known gap recorded",
178
+ );
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * population-map.mjs — does the population registry keep its link to the body of the paper?
4
+ *
5
+ * (Before 2026-08-26 this file answered the question "can the reader tell WHICH SET each number
6
+ * counts" in full; half of the answer now lives in ESLint rules — see below.)
7
+ *
8
+ * node population-map.mjs <paper-dir> report
9
+ * node population-map.mjs <paper-dir> --flags-only for hooks and pre-commit
10
+ *
11
+ * 🔴 WHY THIS EXISTS. `papers/CLAUDE.md` has carried this as writing rule #3 since 2026-08-05:
12
+ *
13
+ * "A number arrives together with what it counts, and out of what. […] If two numbers come
14
+ * from different experiments — say so IN THE SAME SENTENCE."
15
+ *
16
+ * It was written after a cold reader gave up tracking the abstract's populations halfway through.
17
+ * It is prose. Nothing enforced it. On 2026-08-06 the author read the finished paper and said he
18
+ * could not follow his own §4 — and the cause was not the count of measurements, it was that §4
19
+ * moves between TWO SAMPLES THAT BARELY OVERLAP (a targeted census of 134 repositories and a broad
20
+ * corpus of 1,921; seven repositories are in both) and never says so. §4.1's rate and §4.3's rate
21
+ * read as if one replicated the other. They cannot: they are different populations.
22
+ *
23
+ * That is this repository's own thesis turned on its own manuscript — a rule written in a rules
24
+ * file, with no mechanism behind it, that therefore did nothing for a year of edits. So the rule
25
+ * gets a leg.
26
+ *
27
+ * 🔴 TWO OF THE FOUR FINDINGS MOVED INTO ESLint RULES ON 2026-08-26. What is left here is only
28
+ * what speaks about THE REGISTRY ITSELF, not about the paper:
29
+ *
30
+ * stale a registry row for a quantity the body NO LONGER PRINTS. The registry shrinks together
31
+ * with the paper and does not rot.
32
+ * badref a row points at a `related_to` that is not a row anywhere.
33
+ *
34
+ * Both are inseparable from the file `repro/populations.tsv` and HAVE NO ADDRESS IN THE PAPER:
35
+ * `stale` by definition speaks about a number that is not in the paper, and `badref` about two
36
+ * fields of one TSV row. An ESLint rule can only report into the file being linted, so moving them
37
+ * would mean pointing a finger at the paper while talking about a different file.
38
+ *
39
+ * MOVED INTO `eslint-rules/paper-registry.mjs` (the rules `paper/population-untied` and
40
+ * `paper/population-undeclared`):
41
+ * untied — a population is not named IN THE SAME SENTENCE as the one it is derived from;
42
+ * undeclared — a number is printed in the shape of a population, and no registry row declares it.
43
+ * Both speak about a place IN THE PAPER, and for them the registry is configuration, exactly like a
44
+ * venue profile for `pdf/profile`. Parity was proved before the deletion (the real paper + 17
45
+ * fixtures), the write-up is in `the author's private research notes`.
46
+ *
47
+ * Advisory.
48
+ */
49
+ import { readFileSync, existsSync } from "node:fs";
50
+ import { join } from "node:path";
51
+ import {
52
+ headings as mdHeadings,
53
+ requireMarkdown,
54
+ } from "../../../lib/markdown.mjs";
55
+ import { isMain } from "./consumer.mjs";
56
+
57
+ // Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). We fail rather than degrade: without
58
+ // the body boundaries `bodyOf()` would return an empty string, and out of an empty string this
59
+ // check concludes "not a single registry number is printed", that is, ALL rows stale — a confident
60
+ // list of findings about a paper nobody read.
61
+ requireMarkdown();
62
+
63
+ /** The body is everything before the bibliography; appendices sit after it in this paper and are
64
+ * not under the page limit, so a relation stated only there does not reach the reader who stops
65
+ * at the References. That asymmetry is the whole point of checking the body separately. */
66
+ export function bodyOf(md) {
67
+ const stripped = md.replace(/<!--[\s\S]*?-->/g, "");
68
+ // 2026-08-11: both body boundaries come from the parser. `/^##\s+References\s*$/m` opened and
69
+ // closed the body on hashes at the start of a line — including hashes inside a ```-block, and this
70
+ // paper quotes whole chunks of other people's papers. The `-1` convention and the `< 0`
71
+ // comparisons are left as they were.
72
+ const at = (re) => {
73
+ const h = mdHeadings(stripped).find(
74
+ (x) => x.depth === 2 && re.test(x.text),
75
+ );
76
+ return h ? h.offset : -1;
77
+ };
78
+ const cut = at(/^References$/u);
79
+ const start = at(/^Abstract$/u);
80
+ return stripped.slice(start < 0 ? 0 : start, cut < 0 ? stripped.length : cut);
81
+ }
82
+
83
+ const asLiteral = (n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
84
+ /** A printed quantity, not a substring of a longer one: 189 must not match inside 1,189 or 1893.
85
+ * The trailing guard must still allow ORDINARY PUNCTUATION after the number — "of the 1,921, we
86
+ * could enumerate 189" and a sentence-final "...over all 1,921." are both the number being
87
+ * printed. Forbidding any following `.` or `,` silently loses exactly the positions a summary
88
+ * figure lands in; only a DIGIT after the separator makes it part of a longer quantity. */
89
+ const printed = (n) => new RegExp(`(?<![\\d,.])${asLiteral(n)}(?!\\d|[,.]\\d)`);
90
+
91
+ export function parseRegistry(tsv) {
92
+ const rows = [];
93
+ for (const line of tsv.split("\n")) {
94
+ if (!line.trim() || line.startsWith("#")) continue;
95
+ const [id, print, relation, relatedTo, gloss] = line
96
+ .split("\t")
97
+ .map((c) => (c ?? "").trim());
98
+ if (!id || id === "id") continue;
99
+ rows.push({ id, print, relation, relatedTo, gloss });
100
+ }
101
+ return rows;
102
+ }
103
+
104
+ export function findings(md, tsv) {
105
+ const body = bodyOf(md);
106
+ const reg = parseRegistry(tsv);
107
+ const byId = new Map(reg.map((r) => [r.id, r]));
108
+ const out = [];
109
+
110
+ for (const row of reg) {
111
+ const appears = printed(row.print).test(body);
112
+ if (!appears) {
113
+ // A registry row for a quantity the body no longer prints. Same discipline as the
114
+ // grandfathered numbers list: the registry shrinks when the paper does, and never rots.
115
+ if (row.relation !== "retired") out.push({ kind: "stale", row });
116
+ continue;
117
+ }
118
+ if (row.relation === "root" || row.relation === "external") continue;
119
+ // The order is preserved from the original: `untied` was computed AFTER this check and moved
120
+ // into `paper/population-untied`; the dangling reference stayed here because it is about a TSV
121
+ // row.
122
+ if (!byId.get(row.relatedTo)) out.push({ kind: "badref", row });
123
+ }
124
+ return out;
125
+ }
126
+
127
+ /* Importable above this line; the CLI only runs when this file is the entry point, so the selftest
128
+ * can exercise findings() without the argv handling firing. */
129
+ if (!isMain(import.meta.url)) {
130
+ // imported — nothing to do
131
+ } else main();
132
+
133
+ function main() {
134
+ const args = process.argv.slice(2);
135
+ const dir = args.find((a) => !a.startsWith("--"));
136
+ const flagsOnly = args.includes("--flags-only");
137
+ if (!dir) {
138
+ console.error("usage: node population-map.mjs <paper-dir> [--flags-only]");
139
+ process.exit(0);
140
+ }
141
+
142
+ const paperPath = ["paper.md", "draft.md"]
143
+ .map((f) => join(dir, f))
144
+ .find(existsSync);
145
+ const regPath = join(dir, "repro", "populations.tsv");
146
+ // Same silent-skip class as artifact-coverage.mjs — see the note there (measured 2026-08-25).
147
+ if (!paperPath || !existsSync(regPath)) {
148
+ const why = !paperPath
149
+ ? "no paper.md/draft.md — this checker reads markdown only, a .tex paper is NOT covered"
150
+ : `no registry at ${regPath}`;
151
+ console.error(`⏭️ population map SKIPPED for ${dir} — ${why}.`);
152
+ process.exit(0);
153
+ }
154
+
155
+ const found = findings(
156
+ readFileSync(paperPath, "utf8"),
157
+ readFileSync(regPath, "utf8"),
158
+ );
159
+
160
+ if (!found.length) {
161
+ if (!flagsOnly)
162
+ console.log(
163
+ "population map: the registry matches the body — no stale rows, no dangling related_to.",
164
+ );
165
+ process.exit(0);
166
+ }
167
+
168
+ console.log(`population map — ${found.length} finding(s) in ${paperPath}`);
169
+ for (const f of found) {
170
+ if (f.kind === "stale") {
171
+ console.log(
172
+ ` · ${f.row.print} (${f.row.id}) is declared and the body no longer prints it — drop the row or mark it retired`,
173
+ );
174
+ } else if (f.kind === "badref") {
175
+ console.log(
176
+ ` 🔴 ${f.row.id} says it relates to "${f.row.relatedTo}", which is not a row in populations.tsv`,
177
+ );
178
+ }
179
+ }
180
+ process.exit(0);
181
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * population-map.mutations.mjs — plant each defect `population-map.harness.mjs` claims to catch, and require it to die.
3
+ *
4
+ * Run: `node skills/paper-pipeline/scripts/population-map.mutations.mjs` (~1 min; it rewrites the source and
5
+ * restores it, so it is not part of the plain test run).
6
+ *
7
+ * WHY IT EXISTS. A harness whose success state is silence cannot be observed working: «it passed»
8
+ * and «it cannot fail» produce identical output. This battery is the difference. It was written on
9
+ * 2026-09-12, when the mechanism moved into this package and `scripts/run-mutations.mjs` reported
10
+ * this harness as one nothing could kill — a gap that had been open for as long as the harness had
11
+ * existed, invisible because nothing asked the question.
12
+ *
13
+ * Every replacement below differs from the text it replaces (the driver refuses a no-op, which
14
+ * would leave a green harness proving nothing), every case names the assertion that must fire, and
15
+ * the driver verifies the patch actually landed before believing a result.
16
+ */
17
+ import { dirname, join } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { runMutations } from "../../../lib/mutation-driver.mjs";
20
+ import { consumerRoot } from "./consumer.mjs";
21
+
22
+ const HERE = dirname(fileURLToPath(import.meta.url));
23
+ const SRC = join(HERE, "population-map.mjs");
24
+ const HARNESS = join(HERE, "population-map.harness.mjs");
25
+
26
+ const MUTATIONS = [
27
+ [
28
+ "findings() goes blind",
29
+ [
30
+ [
31
+ SRC,
32
+ "export function findings(md, tsv) {",
33
+ "export function findings(md, tsv) {\n if (1) return [];",
34
+ ],
35
+ ],
36
+ "population-map.selftest.mjs failed",
37
+ "the whole checker: a registry map that reports nothing reads exactly like a clean paper",
38
+ ],
39
+ [
40
+ "the registry is never parsed",
41
+ [
42
+ [
43
+ SRC,
44
+ "export function parseRegistry(tsv) {",
45
+ "export function parseRegistry(tsv) {\n if (1) return [];",
46
+ ],
47
+ ],
48
+ "population-map.selftest.mjs failed",
49
+ "every row-level finding — an empty registry makes stale rows and dangling refs unrepresentable",
50
+ ],
51
+ ];
52
+
53
+ process.exit(
54
+ runMutations({
55
+ root: consumerRoot(),
56
+ runner: "vigiles",
57
+ cases: MUTATIONS.map(([name, edits, expect, disables]) => ({
58
+ name,
59
+ disables,
60
+ edits,
61
+ harness: HARNESS,
62
+ expect,
63
+ })),
64
+ }),
65
+ );