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,50 @@
1
+ /** CLAIM 3, script half: transitive third-party import closure over the scripts
2
+ * the SKILL.md files actually reference. Reads only; never executes them. */
3
+ import { readFileSync, existsSync, readdirSync } from "node:fs";
4
+ import { join, resolve, dirname } from "node:path";
5
+
6
+ const REPO = "/home/user/research-paper-pipeline";
7
+ const SKILLS = join(REPO, "skills");
8
+
9
+ // 1. what do SKILL.md files reference?
10
+ const refs = new Set();
11
+ for (const d of readdirSync(SKILLS)) {
12
+ const md = join(SKILLS, d, "SKILL.md");
13
+ if (!existsSync(md)) continue;
14
+ for (const m of readFileSync(md, "utf8").matchAll(/\.claude\/skills\/paper-pipeline\/(scripts\/[A-Za-z0-9_-]+\.mjs)/g))
15
+ refs.add(m[1]);
16
+ }
17
+ console.log("=== scripts referenced by SKILL.md files ===");
18
+ const entry = [];
19
+ for (const r of [...refs].sort()) {
20
+ const p = join(SKILLS, "paper-pipeline", r);
21
+ const ok = existsSync(p);
22
+ console.log(` ${ok ? "EXISTS " : "MISSING"} ${r}`);
23
+ if (ok) entry.push(p);
24
+ }
25
+
26
+ // 2. transitive closure of local imports; collect bare specifiers
27
+ const IMPORT = /(?:^|\s)(?:import|export)\s[^;]*?from\s*["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)|await import\(\s*([A-Za-z_$][\w$]*)\s*\)/gm;
28
+ const seen = new Set(), thirdParty = new Map(), dynamicNonLiteral = [];
29
+ const queue = [...entry];
30
+ while (queue.length) {
31
+ const f = resolve(queue.pop());
32
+ if (seen.has(f) || !existsSync(f)) continue;
33
+ seen.add(f);
34
+ const src = readFileSync(f, "utf8");
35
+ for (const m of src.matchAll(IMPORT)) {
36
+ const spec = m[1] ?? m[2];
37
+ if (!spec) { dynamicNonLiteral.push(`${f.replace(REPO+"/","")}: import(<variable ${m[3]}>)`); continue; }
38
+ if (spec.startsWith("node:")) continue;
39
+ if (spec.startsWith(".") || spec.startsWith("/")) { queue.push(resolve(dirname(f), spec)); continue; }
40
+ if (!thirdParty.has(spec)) thirdParty.set(spec, []);
41
+ thirdParty.get(spec).push(f.replace(REPO + "/", ""));
42
+ }
43
+ }
44
+ console.log("\n=== transitive local module closure (" + seen.size + " files) ===");
45
+ for (const f of [...seen].sort()) console.log(" " + f.replace(REPO + "/", ""));
46
+ console.log("\n=== THIRD-PARTY (bare) SPECIFIERS IN THAT CLOSURE ===");
47
+ if (!thirdParty.size) console.log(" (none)");
48
+ for (const [spec, where] of [...thirdParty].sort()) console.log(` ${spec} <- ${[...new Set(where)].join(", ")}`);
49
+ console.log("\n=== non-literal dynamic imports (cannot be resolved statically) ===");
50
+ console.log(dynamicNonLiteral.length ? dynamicNonLiteral.map(s=>" "+s).join("\n") : " (none)");
@@ -0,0 +1,8 @@
1
+ import module, { findPackageJSON } from "node:module";
2
+ import { pathToFileURL } from "node:url";
3
+ const consumer = process.cwd();
4
+ console.log("typeof findPackageJSON:", typeof findPackageJSON);
5
+ try { console.log("findPackageJSON('research-paper-pipeline', consumer/x.js) ->", findPackageJSON("research-paper-pipeline", pathToFileURL(consumer+"/x.js").href)); }
6
+ catch(e){ console.log("FAIL:", e.code, String(e.message).split("\n")[0]); }
7
+ try { console.log("findPackageJSON('vigiles', consumer/x.js) ->", findPackageJSON("vigiles", pathToFileURL(consumer+"/x.js").href)); }
8
+ catch(e){ console.log("vigiles FAIL:", e.code, String(e.message).split("\n")[0]); }
@@ -0,0 +1,39 @@
1
+ import { createRequire } from "node:module";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { dirname, join, relative } from "node:path";
4
+ import { lstatSync, readlinkSync, readFileSync, existsSync, realpathSync } from "node:fs";
5
+ const req = createRequire(import.meta.url);
6
+ const NAME = "research-paper-pipeline";
7
+
8
+ const pkgJson = req.resolve(`${NAME}/package.json`);
9
+ const pkgDir = dirname(pkgJson);
10
+ console.log("resolved package.json :", pkgJson);
11
+ console.log("=> package dir :", pkgDir);
12
+
13
+ const literal = join(process.cwd(), "node_modules", NAME);
14
+ console.log("literal node_modules/ :", literal);
15
+ console.log(" exists? :", existsSync(literal));
16
+ if (existsSync(literal)) {
17
+ const st = lstatSync(literal);
18
+ console.log(" isSymbolicLink? :", st.isSymbolicLink(), st.isSymbolicLink() ? "-> " + readlinkSync(literal) : "");
19
+ console.log(" realpath :", realpathSync(literal));
20
+ console.log(" SAME as resolved? :", realpathSync(literal) === realpathSync(pkgDir));
21
+ }
22
+
23
+ // the manifest itself: main / exports / bin
24
+ const man = JSON.parse(readFileSync(pkgJson, "utf8"));
25
+ console.log("manifest.main :", JSON.stringify(man.main));
26
+ console.log("manifest.exports :", JSON.stringify(man.exports));
27
+ console.log("manifest.bin :", JSON.stringify(man.bin));
28
+ console.log("bin from manifest:", join(pkgDir, man.bin.rpp), existsSync(join(pkgDir, man.bin.rpp)) ? "EXISTS" : "MISSING");
29
+
30
+ // two-arg import.meta.resolve (the parent form)
31
+ try { console.log("import.meta.resolve(spec, parent):", import.meta.resolve(`${NAME}/package.json`, pathToFileURL(process.cwd() + "/x.js").href)); }
32
+ catch (e) { console.log("import.meta.resolve(spec,parent) FAIL:", e.code, String(e.message).split("\n")[0]); }
33
+
34
+ // resolve vigiles FROM INSIDE the installed package (what cli.ts does)
35
+ const inside = createRequire(pathToFileURL(join(pkgDir, "dist", "cli.js")).href);
36
+ for (const spec of ["vigiles", "vigiles/package.json"]) {
37
+ try { console.log(`from inside pkg: resolve(${spec}) ->`, inside.resolve(spec)); }
38
+ catch (e) { console.log(`from inside pkg: resolve(${spec}) FAIL:`, e.code, String(e.message).split("\n")[0]); }
39
+ }
@@ -0,0 +1,17 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { createRequire } from "node:module";
3
+ const NAME = "research-paper-pipeline";
4
+ const consumer = process.argv[2];
5
+ console.log("running from:", import.meta.url);
6
+ // 1) bare, from a dir that does NOT have the package
7
+ try { console.log(" import.meta.resolve(pkg/package.json) 1-arg ->", import.meta.resolve(`${NAME}/package.json`)); }
8
+ catch (e) { console.log(" 1-arg FAIL:", e.code, String(e.message).split("\n")[0]); }
9
+ // 2) with an explicit parent inside the consumer
10
+ try { console.log(" import.meta.resolve(..., parent=consumer) ->", import.meta.resolve(`${NAME}/package.json`, pathToFileURL(consumer + "/x.mjs").href)); }
11
+ catch (e) { console.log(" 2-arg FAIL:", e.code, String(e.message).split("\n")[0]); }
12
+ // 3) require.resolve with paths:
13
+ try { console.log(" require.resolve(..., {paths:[consumer]}) ->", createRequire(import.meta.url).resolve(`${NAME}/package.json`, { paths: [consumer] })); }
14
+ catch (e) { console.log(" paths: FAIL:", e.code, String(e.message).split("\n")[0]); }
15
+ // 4) createRequire anchored at the consumer
16
+ try { console.log(" createRequire(consumer/x.js).resolve(...) ->", createRequire(pathToFileURL(consumer + "/x.js").href).resolve(`${NAME}/package.json`)); }
17
+ catch (e) { console.log(" anchored FAIL:", e.code, String(e.message).split("\n")[0]); }
@@ -0,0 +1,21 @@
1
+ import { createRequire } from "node:module";
2
+ import { fileURLToPath } from "node:url";
3
+ const req = createRequire(import.meta.url);
4
+ const NAME = "research-paper-pipeline";
5
+ const tries = [
6
+ ["createRequire.resolve('<pkg>/package.json')", () => req.resolve(`${NAME}/package.json`)],
7
+ ["createRequire.resolve('<pkg>')", () => req.resolve(NAME)],
8
+ ["createRequire.resolve('<pkg>/bin/rpp.mjs')", () => req.resolve(`${NAME}/bin/rpp.mjs`)],
9
+ ["require.resolve(name,{paths:[cwd]}) pkg.json",() => req.resolve(`${NAME}/package.json`, { paths: [process.cwd()] })],
10
+ ["import.meta.resolve('<pkg>/package.json')", () => fileURLToPath(import.meta.resolve(`${NAME}/package.json`))],
11
+ ["import.meta.resolve('<pkg>')", () => fileURLToPath(import.meta.resolve(NAME))],
12
+ // the closed-exports control: vigiles is a dep OF the package, exports map is closed
13
+ ["createRequire.resolve('vigiles/package.json')", () => req.resolve("vigiles/package.json")],
14
+ ["createRequire.resolve('vigiles')", () => req.resolve("vigiles")],
15
+ ["createRequire.resolve('vigiles/dist/cli.js')", () => req.resolve("vigiles/dist/cli.js")],
16
+ ];
17
+ console.log(`node ${process.version} cwd=${process.cwd()}`);
18
+ for (const [label, fn] of tries) {
19
+ try { console.log(` OK ${label}\n -> ${fn()}`); }
20
+ catch (e) { console.log(` FAIL ${label}\n ${e.code ?? ""} ${String(e.message).split("\n")[0]}`); }
21
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Builds the consumer trees the CLAIM 4/5 probes measure: `npm pack` this repository once, then
3
+ * install the SAME tarball into a clean project with npm and with pnpm.
4
+ *
5
+ * Kept separate from the probes on purpose. The probes are the files that produced the numbers in
6
+ * `../package-location.md` and must stay byte-identical to what was run; this one only makes a
7
+ * tree for them to look at, so it may be re-run freely.
8
+ *
9
+ * Usage: node docs/prior-art/repro/claim4-setup-consumers.mjs [workdir]
10
+ * Prints the two consumer directories, one per line, in the order npm, pnpm.
11
+ */
12
+ import { execFileSync, spawnSync } from "node:child_process";
13
+ import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { join, dirname } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const ROOT = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
19
+ const work = process.argv[2] ?? mkdtempSync(join(tmpdir(), "rpp-location-"));
20
+ mkdirSync(work, { recursive: true });
21
+
22
+ const packed = execFileSync("npm", ["pack", "--silent", "--pack-destination", work], {
23
+ cwd: ROOT,
24
+ encoding: "utf8",
25
+ })
26
+ .trim()
27
+ .split("\n")
28
+ .pop();
29
+ const tgz = join(work, packed);
30
+ if (!existsSync(tgz)) throw new Error(`npm pack left no tarball: ${tgz}`);
31
+
32
+ const out = [];
33
+ for (const [manager, args] of [
34
+ ["npm", ["install", "--silent", tgz]],
35
+ ["pnpm", ["install", "--silent", tgz]],
36
+ ]) {
37
+ const consumer = join(work, `consumer-${manager}`);
38
+ rmSync(consumer, { recursive: true, force: true });
39
+ mkdirSync(consumer, { recursive: true });
40
+ writeFileSync(join(consumer, "package.json"), '{"name":"c","version":"1.0.0","private":true}');
41
+ const r = spawnSync(manager, args, { cwd: consumer, encoding: "utf8" });
42
+ if (r.status !== 0) throw new Error(`${manager} install failed:\n${r.stderr ?? ""}${r.stdout ?? ""}`);
43
+ out.push(consumer);
44
+ }
45
+ console.log(out.join("\n"));
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Yarn Berry (Plug'n'Play): builds a PnP consumer from this repository's tarball and asks the ONE
3
+ * question that decides whether a resolution-based rewrite would make PnP supportable.
4
+ *
5
+ * It is not "does resolution work under PnP" — it does, inside a process that loaded the PnP
6
+ * runtime. It is whether the path resolution HANDS BACK can be read by the outside process that
7
+ * asked. Under PnP that path points inside a zip, and only PnP's patched `fs` can open it.
8
+ *
9
+ * Needs the network (corepack downloads yarn, yarn downloads the transitive tree).
10
+ *
11
+ * Usage: node docs/prior-art/repro/claim4-yarn-pnp.mjs [workdir]
12
+ */
13
+ import { execFileSync, spawnSync } from "node:child_process";
14
+ import { mkdtempSync, mkdirSync, writeFileSync, copyFileSync, existsSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join, dirname } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const ROOT = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
20
+ const work = process.argv[2] ?? mkdtempSync(join(tmpdir(), "rpp-pnp-"));
21
+ mkdirSync(work, { recursive: true });
22
+ const env = { ...process.env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" };
23
+
24
+ const packed = execFileSync("npm", ["pack", "--silent", "--pack-destination", work], {
25
+ cwd: ROOT,
26
+ encoding: "utf8",
27
+ }).trim().split("\n").pop();
28
+
29
+ const consumer = join(work, "consumer-yarn-pnp");
30
+ mkdirSync(consumer, { recursive: true });
31
+ writeFileSync(
32
+ join(consumer, "package.json"),
33
+ '{"name":"c-yarn","version":"1.0.0","private":true,"packageManager":"yarn@4.9.2"}',
34
+ );
35
+ copyFileSync(join(work, packed), join(consumer, "rpp.tgz"));
36
+
37
+ const yarn = (args) => spawnSync("corepack", ["yarn", ...args], { cwd: consumer, encoding: "utf8", env });
38
+ const add = yarn(["add", "./rpp.tgz"]);
39
+ if (add.status !== 0) throw new Error(`yarn add failed:\n${add.stdout}${add.stderr}`);
40
+ console.log("yarn add: ok");
41
+ console.log("node_modules present?", existsSync(join(consumer, "node_modules")) ? "YES" : "NO");
42
+
43
+ const probe = `
44
+ import { createRequire } from "node:module";
45
+ const req = createRequire(import.meta.url);
46
+ try { console.log(" resolve(pkg/package.json) ->", req.resolve("research-paper-pipeline/package.json")); }
47
+ catch (e) { console.log(" resolve FAILS:", e.code, String(e.message).split("\\n")[0]); }
48
+ `;
49
+ writeFileSync(join(consumer, "probe.mjs"), probe);
50
+
51
+ console.log("1) plain node, inside the PnP project (no PnP runtime):");
52
+ process.stdout.write(spawnSync(process.execPath, ["probe.mjs"], { cwd: consumer, encoding: "utf8" }).stdout ?? "");
53
+
54
+ console.log("2) yarn node (PnP runtime loaded):");
55
+ const withRuntime = yarn(["node", "probe.mjs"]);
56
+ process.stdout.write(withRuntime.stdout ?? "");
57
+
58
+ const resolved = (withRuntime.stdout ?? "").split("->")[1]?.trim();
59
+ if (resolved) {
60
+ console.log("3) can the OUTSIDE process read the path PnP handed back?");
61
+ const outside = spawnSync(process.execPath, ["-e", "console.log(' plain node existsSync:', require('node:fs').existsSync(process.argv[1]))", resolved], { encoding: "utf8" });
62
+ process.stdout.write(outside.stdout ?? "");
63
+ const inside = yarn(["node", "-e", "console.log(' yarn node existsSync:', require('node:fs').existsSync(process.argv[1]))", resolved]);
64
+ process.stdout.write(inside.stdout ?? "");
65
+ }
66
+ console.log("4) `yarn bin rpp` (the supported way to ask for the bin path):");
67
+ process.stdout.write(" " + (yarn(["bin", "rpp"]).stdout ?? ""));
68
+ console.log("5) does the CLI itself run under PnP?");
69
+ process.stdout.write(" " + ((yarn(["rpp", "--help"]).stdout ?? "").split("\n")[0]) + "\n");
70
+ console.log(`(tree kept at ${consumer})`);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * How the installed bin can be launched, and what each way costs. Four ways against one consumer:
3
+ * the `.bin` shim the manager wrote, `node <shim>`, `node <the real file named by `bin` in the
4
+ * resolved manifest>`, and `npx --no-install`.
5
+ *
6
+ * The point of the third row is that the manifest's `bin` value is the REAL file under every
7
+ * manager, so `node <it>` works where `node <shim>` does not.
8
+ *
9
+ * Usage: node docs/prior-art/repro/claim5-bin-launch.mjs <consumer-dir>
10
+ */
11
+ import { createRequire } from "node:module";
12
+ import { pathToFileURL } from "node:url";
13
+ import { dirname, join } from "node:path";
14
+ import { readFileSync } from "node:fs";
15
+ import { spawnSync } from "node:child_process";
16
+
17
+ const consumer = process.argv[2];
18
+ const from = createRequire(pathToFileURL(join(consumer, "__placeholder__.js")).href);
19
+ const manifestPath = from.resolve("research-paper-pipeline/package.json");
20
+ const installed = dirname(manifestPath);
21
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
22
+ const realBin = join(installed, typeof manifest.bin === "string" ? manifest.bin : manifest.bin.rpp);
23
+ const shim = join(consumer, "node_modules", ".bin", "rpp");
24
+
25
+ const time = (label, cmd, args) => {
26
+ const t = Date.now();
27
+ const r = spawnSync(cmd, args, { cwd: consumer, encoding: "utf8" });
28
+ const ms = Date.now() - t;
29
+ const status = r.error ? `ERR ${r.error.code}` : String(r.status);
30
+ const first = ((r.stdout ?? "") + (r.stderr ?? "")).split("\n").find((l) => l.trim()) ?? "";
31
+ console.log(` ${label.padEnd(34)} rc=${status.padEnd(4)} ${String(ms).padStart(5)}ms ${first.slice(0, 70)}`);
32
+ };
33
+
34
+ console.log("installed:", installed);
35
+ console.log("real bin :", realBin);
36
+ time("<.bin shim> --help", shim, ["--help"]);
37
+ time("node <.bin shim> --help", process.execPath, [shim, "--help"]);
38
+ time("node <manifest bin> --help", process.execPath, [realBin, "--help"]);
39
+ time("npx --no-install rpp --help", "npx", ["--no-install", "rpp", "--help"]);
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The mutation that decides whether replacing the `node_modules/<name>` hardcode with resolution
3
+ * buys anything MEASURABLE, rather than only looking tidier.
4
+ *
5
+ * It copies an installed consumer, adds a CLOSED `exports` map to the installed package's manifest
6
+ * — one that omits `./bin/rpp.mjs` and `./package.json` — and then asks four questions of the same
7
+ * broken tree. The hardcode's answers do not change; the documented public import breaks.
8
+ *
9
+ * Usage: node docs/prior-art/repro/claim5-exports-mutation.mjs <consumer-dir>
10
+ */
11
+ import { cpSync, rmSync, readFileSync, writeFileSync, existsSync, mkdtempSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { createRequire } from "node:module";
15
+ import module from "node:module";
16
+ import { spawnSync } from "node:child_process";
17
+ import { pathToFileURL } from "node:url";
18
+
19
+ const src = process.argv[2];
20
+ const mut = mkdtempSync(join(tmpdir(), "rpp-mutation-"));
21
+ rmSync(mut, { recursive: true, force: true });
22
+ cpSync(src, mut, { recursive: true, verbatimSymlinks: true });
23
+
24
+ const manifest = join(mut, "node_modules", "research-paper-pipeline", "package.json");
25
+ const json = JSON.parse(readFileSync(manifest, "utf8"));
26
+ json.exports = { ".": "./bin/rpp.mjs" };
27
+ writeFileSync(manifest, JSON.stringify(json, null, 2));
28
+ console.log(`MUTATION applied: exports = ${JSON.stringify(json.exports)} (no ./package.json, no ./bin/rpp.mjs)`);
29
+
30
+ const dir = join(mut, "node_modules", "research-paper-pipeline");
31
+ console.log("1) the HARDCODE still sees everything:");
32
+ console.log(" dir exists :", existsSync(dir));
33
+ console.log(" plugin/hooks/h.json :", existsSync(join(dir, "plugin", "hooks", "hooks.json")));
34
+ console.log(" skills/ :", existsSync(join(dir, "skills")));
35
+
36
+ console.log("2) the documented public import is BROKEN:");
37
+ const r = spawnSync(
38
+ process.execPath,
39
+ ["--input-type=module", "-e", 'import("research-paper-pipeline/bin/rpp.mjs").then(()=>console.log(" import OK")).catch(e=>console.log(" import FAILS:",e.code,String(e.message).split("\\n")[0]))'],
40
+ { cwd: mut, encoding: "utf8" },
41
+ );
42
+ process.stdout.write(r.stdout ?? "");
43
+
44
+ console.log("3) resolution-based location reports it:");
45
+ try {
46
+ console.log(" resolve pkg/package.json ->", createRequire(pathToFileURL(join(mut, "__placeholder__.js")).href).resolve("research-paper-pipeline/package.json"));
47
+ } catch (e) {
48
+ console.log(" resolve FAILS:", e.code, String(e.message).split("\n")[0]);
49
+ }
50
+
51
+ console.log("4) node:module.findPackageJSON, same mutation:");
52
+ try {
53
+ console.log(" findPackageJSON ->", module.findPackageJSON("research-paper-pipeline", join(mut, "__placeholder__.js")));
54
+ } catch (e) {
55
+ console.log(" FAILS:", e.code);
56
+ }
57
+ console.log(`(tree kept at ${mut})`);
@@ -0,0 +1,33 @@
1
+ // Drop-in replacement for the three hardcodes, exercised against a real consumer.
2
+ import { createRequire } from "node:module";
3
+ import { pathToFileURL } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { spawnSync } from "node:child_process";
7
+
8
+ const consumer = process.argv[2];
9
+ const NAME = "research-paper-pipeline";
10
+
11
+ // ONE anchor, ESLint's __placeholder__.js trick: a file that need not exist.
12
+ const from = createRequire(pathToFileURL(join(consumer, "__placeholder__.js")).href);
13
+ const manifestPath = from.resolve(`${NAME}/package.json`);
14
+ const installed = dirname(manifestPath); // replaces line 172
15
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
16
+ const binRel = typeof manifest.bin === "string" ? manifest.bin : manifest.bin.rpp;
17
+ const bin = join(installed, binRel); // replaces line 276
18
+
19
+ console.log("installed :", installed);
20
+ console.log("bin :", bin, existsSync(bin) ? "" : "MISSING");
21
+
22
+ // (a) hooks.json from the resolved tree (replaces line 135)
23
+ const hj = join(installed, "plugin", "hooks", "hooks.json");
24
+ console.log("hooks.json:", existsSync(hj) ? "found" : "ABSENT");
25
+
26
+ // (b) the bin runs — via node <resolved real file>, no .bin shim
27
+ const r = spawnSync(process.execPath, [bin, "--help"], { cwd: consumer, encoding: "utf8" });
28
+ console.log("node <bin> --help ->", r.status, (r.stdout||"").split("\n")[0]);
29
+
30
+ // (c) and via the .bin shim, which is what npm/pnpm actually create
31
+ const shim = join(consumer, "node_modules", ".bin", "rpp");
32
+ const r2 = spawnSync(shim, ["--help"], { cwd: consumer, encoding: "utf8" });
33
+ console.log(".bin shim --help ->", r2.status ?? `ERR ${r2.error?.code}`);
@@ -0,0 +1,17 @@
1
+ import { readdirSync, existsSync, readFileSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ const installed = process.argv[2];
4
+ const dir = join(installed,"skills");
5
+ const names = readdirSync(dir,{withFileTypes:true}).filter(d=>d.isDirectory()&&existsSync(join(dir,d.name,"SKILL.md"))).map(d=>d.name);
6
+ let amb=0, tot=0;
7
+ const kinds={};
8
+ for (const name of names){
9
+ const body=readFileSync(join(installed,"skills",name,"SKILL.md"),"utf8");
10
+ for(const m of body.matchAll(/([\w./-]*scripts\/[\w-]+\.mjs)/g)){
11
+ tot++; const raw=m[1];
12
+ const hits=[["c1",join(installed,raw)],["c2",join(installed,"skills",name,raw)],["c3",join(installed,raw.replace(/^\.claude\/skills\//,"skills/"))]].filter(([,p])=>existsSync(p)).map(([k])=>k);
13
+ if(hits.length>1){amb++; if(amb<6)console.log("AMBIGUOUS:",name,raw,hits.join("+"));}
14
+ kinds[hits.join("+")||"none"]=(kinds[hits.join("+")||"none"]??0)+1;
15
+ }
16
+ }
17
+ console.log("total refs",tot,"ambiguous",amb, JSON.stringify(kinds));
@@ -0,0 +1,27 @@
1
+ import { readdirSync, existsSync, readFileSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { pathToFileURL } from "node:url";
5
+ const consumer = process.argv[2];
6
+ const installed = dirname(createRequire(pathToFileURL(join(consumer,"x.js")).href).resolve("research-paper-pipeline/package.json"));
7
+ const dir = join(installed, "skills");
8
+ const names = readdirSync(dir,{withFileTypes:true}).filter(d=>d.isDirectory()&&existsSync(join(dir,d.name,"SKILL.md"))).map(d=>d.name);
9
+ const tally = {c1:0,c2:0,c3:0,none:0};
10
+ const seen = new Set();
11
+ for (const name of names) {
12
+ const body = readFileSync(join(installed,"skills",name,"SKILL.md"),"utf8");
13
+ for (const m of body.matchAll(/([\w./-]*scripts\/[\w-]+\.mjs)/g)) {
14
+ const raw = m[1]; seen.add(raw);
15
+ const c1 = join(installed, raw);
16
+ const c2 = join(installed, "skills", name, raw);
17
+ const c3 = join(installed, raw.replace(/^\.claude\/skills\//, "skills/"));
18
+ if (existsSync(c1)) tally.c1++;
19
+ else if (existsSync(c2)) tally.c2++;
20
+ else if (existsSync(c3)) tally.c3++;
21
+ else tally.none++;
22
+ }
23
+ }
24
+ console.log("installed dir :", installed);
25
+ console.log("skills :", names.length);
26
+ console.log("distinct raw spellings:", [...seen].join("\n "));
27
+ console.log("which candidate resolved:", JSON.stringify(tally));
@@ -0,0 +1,131 @@
1
+ # What we test with, and why there is no test framework here
2
+
3
+ **Question this file answers:** this package has 57 `*.harness.mjs` files of plain
4
+ `node:assert/strict`, three bespoke e2e scripts, and no jest, vitest, mocha, ava or `node:test`.
5
+ Is that a gap? Should an npm package's install be tested through a local registry?
6
+
7
+ **Verdict: change nothing.** A general-purpose runner would buy a reporter and a second answer to
8
+ "do the tests pass". A local registry is what MULTI-package repositories reach for; a single-package
9
+ CLI packs a tarball and installs it. Both conclusions have named triggers for revisiting, below.
10
+
11
+ ---
12
+
13
+ ## The measurement that settles the house rule
14
+
15
+ The rule "testing goes through vigiles" was narrowed on 2026-09-19 to agent-facing surfaces only.
16
+ The corpus already had that shape, which is the strongest evidence the narrowing is descriptive
17
+ rather than a concession — counted the same day:
18
+
19
+ ```
20
+ harness files 57
21
+ import anything from "vigiles" 10
22
+ …of which import ONLY recordCheck 8
23
+ ```
24
+
25
+ So the genuinely agent-facing harnesses are three: `hooks/hooks.harness.mjs` (`runHook`,
26
+ `checkHookImports`), `skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs`
27
+ (`runHarnessTest`, which spawns the real `claude` binary against a scripted model), and the skill
28
+ contract checks. Everything else is ordinary Node testing that happens to carry the suffix.
29
+
30
+ ⚠️ **The suffix is a CONTRACT, not a style choice.** `lib/mutation-driver.mjs` pairs batteries to
31
+ harnesses by it. Renaming files for a new runner would break that, which has nothing to do with
32
+ running tests.
33
+
34
+ ## Why the install e2e stays a script
35
+
36
+ `test/e2e/install.mjs` is one linear scenario per package manager with strictly dependent steps
37
+ — install, bin, `init`, `lint`, hook commands, content delivery — and a summary. A runner adds
38
+ named subtests and a reporter; the script already prints per-check `✓`/`✗` and a per-manager
39
+ verdict. The pack-and-install work stays in our code under any host, so the host buys only the
40
+ reporter, at the cost of a second exit-code path.
41
+
42
+ It also stays OUT of `npm test`: it needs the network for the consumer's transitive dependencies
43
+ and takes minutes. It is its own CI step with its own timeout.
44
+
45
+ ## Tarball-by-path versus a local registry
46
+
47
+ We install the tarball by path. A real consumer resolves by name from a registry. The difference
48
+ is smaller than it looks, and the reason is mechanical:
49
+
50
+ > `npm publish --dry-run` and `npm pack --dry-run` use the same packlist.
51
+
52
+ So "what lands on disk" is faithful by construction. What the shortcut genuinely misses, and the
53
+ status of each here:
54
+
55
+ | missed | status in this package |
56
+ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
57
+ | `prepublishOnly`, `publishConfig` — they run/apply only on `npm publish` | neither is declared, so the gap is zero today |
58
+ | a cold `npx <name>` in an empty directory: packument fetch, `latest`, bin resolution by name | the documented install is `npm i`, not `npx` |
59
+ | a dependency on a sibling package not yet published, referenced by semver range | none today; would appear if the hook runtime is split out (see `../package-shape-options.md` §2) |
60
+
61
+ **Not missed, so not arguments for a registry:** the published file list, `bundleDependencies`,
62
+ `engines`, peer auto-install (npm ≥7 and pnpm ≥8 are both exercised for real), and `prepare`.
63
+
64
+ ### Prior art, and what it is actually about
65
+
66
+ - **Angular CLI** and **Nx** spawn verdaccio and publish the workspace's packages into it before
67
+ e2e. Both are multi-package: the packages reference each other by range, which a second tarball
68
+ cannot satisfy.
69
+ - **pnpm** uses `@pnpm/registry-mock` because its tests need controlled packuments, not tarballs.
70
+ - **Single-package CLIs** pack and install into a clean temp project — the shape `r2g` packages as
71
+ a tool ("tests your package after `npm pack` and `npm install --production`").
72
+
73
+ ✅ **Measured 2026-09-19 that verdaccio DOES work in our environment**, so adopting it later is a
74
+ decision and not an unknown: `npm i -g verdaccio` (v6.10.3) installs, it serves on 4873, its uplink
75
+ to registry.npmjs.org reaches through the agent proxy (`@eslint/markdown` → HTTP 200), `npm publish`
76
+ into it succeeds, and a consumer's `npm i research-paper-pipeline` then resolves **by name** —
77
+ `"resolved": "http://localhost:4873/…"` in the consumer's lockfile — with the installed binary
78
+ answering. Recording this so the question "would it even run here" never has to be asked again.
79
+
80
+ **Triggers to revisit:** a second published package that this one depends on by range, **or** the
81
+ documented install becoming a cold `npx <name>`. Then: verdaccio as a devDependency spawned by the
82
+ script, Angular-style — not a Docker service in CI.
83
+
84
+ ## Why no vitest, and no `node:test` either
85
+
86
+ vitest's value is its Vite transform pipeline, jsdom, module mocking and a watch UI. The tests here
87
+ are plain `.mjs`, the sources are compiled by `tsc`, there is no DOM, and the design deliberately
88
+ does not mock — it spawns real processes into temp directories, which is the right shape for a CLI.
89
+ So vitest would add vite and a native esbuild binary to a package whose install weight is itself
90
+ under scrutiny, and nothing breaks without it.
91
+
92
+ `node:test` costs no dependency but creates the two-answers problem verbatim: a second discovery
93
+ mechanism (`--test` globs against the `.harness.mjs` suffix) and a second exit code.
94
+
95
+ **What staying honestly costs:** a top-level `assert` stops at the FIRST failure, so a table-driven
96
+ test over many fixtures reports one row per run. Also no name-level filtering (file-level exists:
97
+ `vigiles test <file>`).
98
+
99
+ **Trigger to revisit:** when continue-after-failure is measurably costing iteration time on a
100
+ table-driven test. The answer then is `node:test`, never vitest, and it must run under the same
101
+ `npm test` line rather than beside it.
102
+
103
+ ## One known limit, recorded so it is not claimed
104
+
105
+ **Yarn Berry (PnP) cannot be supported as things stand.** `plugin/hooks/hooks.json` addresses
106
+ `${CLAUDE_PROJECT_DIR}/node_modules/research-paper-pipeline/bin/rpp.mjs` literally, and PnP has no
107
+ `node_modules` directory at all. Adding a third `managers()` row for it would be a red test, not a
108
+ feature. Yarn _classic_ is one row away if support is ever claimed.
109
+
110
+ ➕ **Correction, 2026-09-19 — the conclusion holds, the reason above is incomplete.** Measured
111
+ against a real Yarn 4.9.2 PnP consumer (`repro/claim4-yarn-pnp.mjs`): the CLI itself **runs** under
112
+ PnP (`yarn rpp --help` answers), so the package is not PnP-incompatible; the literal path in
113
+ `hooks.json` is the _movable_ blocker, since Claude Code exposes `${CLAUDE_PLUGIN_ROOT}` for
114
+ exactly it. The immovable blocker is one this note does not name — PnP resolves to a path **inside
115
+ a zip**, and the outside process gets `existsSync: false` for the very path PnP handed it, so the
116
+ e2e's `existsSync`/`readdirSync`/`readFileSync` inspection reads nothing whatever API produced the
117
+ path. Supporting PnP means re-executing the inspection under `yarn node`, not swapping a string.
118
+ Full run and the three corrections: [`package-location.md`](package-location.md) § 8.
119
+
120
+ ---
121
+
122
+ ## Status
123
+
124
+ Written 2026-09-19 from a design pass over this repository plus the sources below, with the
125
+ vigiles-import count and the verdaccio run measured here rather than recalled.
126
+
127
+ Sources: [Nx — Creating an Install Package](https://nx.dev/docs/extending-nx/create-install-package) ·
128
+ [Verdaccio — End to End testing](https://www.verdaccio.org/docs/e2e/) ·
129
+ [@pnpm/registry-mock](https://www.npmjs.com/package/@pnpm/registry-mock) ·
130
+ [r2g](https://github.com/ORESoftware/r2g) ·
131
+ [npm Docs — scripts, `prepack` vs `prepublishOnly`](https://docs.npmjs.com/cli/v11/using-npm/scripts/)
package/docs/rules.md ADDED
@@ -0,0 +1,58 @@
1
+ # The checks, one by one
2
+
3
+ The README lists what each check catches in one line. This page says exactly which file each one
4
+ reads and when it fails. Errors fail `paperlint lint`; warnings print and do not.
5
+
6
+ | Rule | Level | Reads | Fails when |
7
+ | ------------------------------ | ----- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
8
+ | `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares |
9
+ | `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it) |
10
+ | `paper/author-list` | warn | `PIPELINE-STATUS.md` | a stage is declared and **no cell of the scorecard's table contains the run marker** (default `bib-authors`). It checks that you recorded the cross-check; it does not read your `.bib`. What to run is your own `authorListCommand`, empty by default |
11
+ | `paper/research-question` | warn | `paper.tex`, `paper.md`, `PIPELINE-STATUS.md` | a stage is declared and either the scorecard has no `researchQuestion` field, or it has one the paper does not contain. The declared sentence is compared against the source with whitespace collapsed — your words, not a pattern. Advisory, because nothing here can judge whether what you declared _is_ a research question |
12
+ | `paper/typography` | warn | `paper.tex`, `paper.md` | any of four counts rises above the per-paper allowance you set: `§` or `\S\ref` instead of "Section"; a decimal with no leading zero (`.05` instead of `0.05`, IEEE / ISO 80000-1 style), counted only where the reader sees it — prose, math and table cells, not macro options, column specs, comments, code or tikz; `Fig.` and `Figure` mixed in one document; bibliography entries with no doi, url or arXiv id. Existing debt is tolerated, growth is not |
13
+ | `tex/future-promise` | warn | `paper.tex` | a camera-ready build still says "will be released" about something already handed over |
14
+ | `tex/acm-frontmatter-override` | error | `paper.tex` | an `acmart` build overrides ACM's front-matter commands and drops template elements from page 1 |
15
+ | `review/findings-cause` | error | `reviews/*.md` | a review lists at least `minFindings` (default 3) findings and no cell introduces a cause with the marker (default `Cause:`) |
16
+ | `doc/fields` | warn | `reviews/*.md` | a front-matter field is missing or holds a value outside the list you configured. Off entirely unless you configure `docFields` |
17
+
18
+ Optional rules — off unless you turn them on in the `rules` setting, because only some venues need
19
+ them — are on their own page: [`optional-rules.md`](optional-rules.md). Today there is one,
20
+ `pdf/last-page-balance`.
21
+
22
+ Besides these rules, `paperlint lint` reports a paper directory that is missing a required file (by
23
+ default `PIPELINE-STATUS.md`) as an error. Which files are required is configurable — see
24
+ [`configuration.md`](configuration.md#required-files).
25
+
26
+ ## The paper is LaTeX
27
+
28
+ The paper body is `paper.tex`, and it gets four rules: `paper/research-question`,
29
+ `paper/typography`, `tex/future-promise` and `tex/acm-frontmatter-override`. The scorecard and
30
+ the review notes are Markdown files, and the other five rules read those.
31
+
32
+ A Markdown body (`paper.md`, or `draft.md`) is still read today and gets only the two `paper/`
33
+ rules, which is why they list it above. Markdown papers are deprecated and being removed
34
+ ([#57](https://github.com/zernie/research-paper-pipeline/issues/57)); do not start a new one.
35
+
36
+ ## The scorecard's `bytes:` and `sourceBytes:`
37
+
38
+ A stage in `PIPELINE-STATUS.md` looks like this:
39
+
40
+ ```yaml
41
+ ---
42
+ stages:
43
+ - stage: submitted
44
+ date: 2026-07-22
45
+ venue: A Venue 2026
46
+ pdf: versions/2026-07-22-submitted.pdf
47
+ bytes: 305412
48
+ source: versions/2026-07-22-submitted.tex
49
+ sourceBytes: 57210
50
+ researchQuestion: "Does pruning the state space reduce review cost?"
51
+ ---
52
+ ```
53
+
54
+ Today you write `bytes:` and `sourceBytes:` yourself, or the agent does, once, when the stage is
55
+ recorded; no command in this package generates them. They are not a checksum to maintain: a
56
+ frozen PDF is never supposed to change, so `bytes:` disagreeing with the file means the file was
57
+ replaced after it was declared, and that is exactly the finding. If you really did re-freeze a
58
+ stage, update the number in the same commit.