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,286 @@
1
+ /**
2
+ * Both halves for `lib/paper-config.mjs` — and, more broadly, for the SET of places that
3
+ * publish these two constants.
4
+ *
5
+ * 🔴 WHY THIS CHECK MOVED HERE FROM `hooks/hooks.harness.mjs`. There it compared three hooks
6
+ * against each other — and was right only as far as its COVERAGE was right. Measured
7
+ * 2026-09-18: there were eight declarations across five files, four per constant, and for each
8
+ * constant exactly one copy sat OUTSIDE the comparison (`eslint-rules/papers.mjs`,
9
+ * `skills/paper-pipeline/scripts/consumer.mjs`). Had they drifted, nobody would have noticed. A
10
+ * check that lives with the hooks could, by construction, never see the non-hooks; moving it
11
+ * next to the subject itself fixes that.
12
+ *
13
+ * 🔴 THE VALUES ARE IMPORTED. The previous version read them with a regex over the source, and
14
+ * adding `export` — an edit that changed nothing about behavior — turned it red. A pattern over
15
+ * code is the shadow of a declaration, and a shadow has spellings; an import gives you the
16
+ * value itself.
17
+ *
18
+ * ⚠️ WHERE THE RISK REMAINS, NAMED RATHER THAN HIDDEN. The carrier list below is explicit. A
19
+ * sixth file with its own copy will show up with nothing to compare it against. That is why a
20
+ * second check exists below, for COMPLETENESS: it searches the whole corpus for declarations
21
+ * and requires every file it finds to be on the list. Its subject is different — "is there a
22
+ * file I don't know about" — and a text search is legitimate for that: it looks for CANDIDATES,
23
+ * while the value itself is still taken by import.
24
+ *
25
+ * Run: npx vigiles test lib/paper-config.harness.mjs
26
+ * Killed by: lib/paper-config.mutations.mjs
27
+ */
28
+ import assert from "node:assert/strict";
29
+ import { readFileSync, readdirSync } from "node:fs";
30
+ import { dirname, join, relative, resolve } from "node:path";
31
+ import { fileURLToPath, pathToFileURL } from "node:url";
32
+
33
+ const HERE = dirname(fileURLToPath(import.meta.url));
34
+ const ROOT = resolve(HERE, "..");
35
+
36
+ let n = 0;
37
+ const check = (label, cond) => {
38
+ n++;
39
+ assert.ok(cond, label);
40
+ };
41
+
42
+ /**
43
+ * Every place that publishes at least one of the two constants. The three hooks duplicate them
44
+ * out of necessity (a compiled hook is forbidden to import anything but `vigiles/hook`); the
45
+ * rest re-export from `lib/paper-config.mjs` and are kept in this list for exactly one reason:
46
+ * so a re-export that is one day swapped back for its own declaration gets caught.
47
+ */
48
+ const CARRIERS = [
49
+ "lib/paper-config.mjs",
50
+ "hooks/paper-edit-guard.hook.mjs",
51
+ "hooks/paper-skills-nudge.hook.mjs",
52
+ "hooks/paper-status-gates.hook.mjs",
53
+ "eslint-rules/papers.mjs",
54
+ "skills/paper-pipeline/scripts/consumer.mjs",
55
+ ];
56
+
57
+ const loaded = await Promise.all(
58
+ CARRIERS.map(async (rel) => {
59
+ const mod = await import(pathToFileURL(join(ROOT, rel)).href);
60
+ return {
61
+ rel,
62
+ key: mod.CONFIG_KEY,
63
+ def: mod.DEFAULT_PAPERS_ROOT,
64
+ field: mod.PAPERS_DIR_FIELD,
65
+ old: mod.OLD_PAPERS_DIR_FIELD,
66
+ legacyKey: mod.LEGACY_CONFIG_KEY,
67
+ };
68
+ }),
69
+ );
70
+
71
+ // ── I. THE SOURCE SAYS WHAT IT SHOULD ───────────────────────────────────────────────────────
72
+ {
73
+ // 🔴 THERE IS NO KEY LITERAL HERE, AND THAT IS DELIBERATE. The first version compared
74
+ // `src.key` against "research-paper-pipeline" right here — and by doing so made the check
75
+ // below, that the key equals the package name, UNKILLABLE: any corruption of the value would
76
+ // fail earlier, on the literal. The value is pinned in exactly one place — the one place it
77
+ // has a REASON to be what it is.
78
+ const src = loaded[0];
79
+ check(
80
+ "the source declares both constants as non-empty strings",
81
+ typeof src.key === "string" &&
82
+ src.key.length > 0 &&
83
+ typeof src.def === "string" &&
84
+ src.def.length > 0,
85
+ );
86
+ check("the default papers directory is `papers`", src.def === "papers");
87
+ }
88
+
89
+ // ── II. ALL CARRIERS AGREE WITH THE SOURCE ──────────────────────────────────────────────────
90
+ {
91
+ const keys = new Set(loaded.map((c) => c.key).filter((v) => v !== undefined));
92
+ const defs = new Set(loaded.map((c) => c.def).filter((v) => v !== undefined));
93
+ check(
94
+ `all carriers agree on CONFIG_KEY (${[...keys].join(" / ")})`,
95
+ keys.size === 1,
96
+ );
97
+ check(
98
+ `all carriers agree on DEFAULT_PAPERS_ROOT (${[...defs].join(" / ")})`,
99
+ defs.size === 1,
100
+ );
101
+ // The papers-directory field name, and its old name. Only the source and the three hooks
102
+ // (which cannot import it) declare these; the value itself is pinned nowhere but the source,
103
+ // so a rename stays a one-line change there plus the hook copies this checks.
104
+ const fields = new Set(
105
+ loaded.map((c) => c.field).filter((v) => v !== undefined),
106
+ );
107
+ const olds = new Set(loaded.map((c) => c.old).filter((v) => v !== undefined));
108
+ check(
109
+ `all carriers agree on PAPERS_DIR_FIELD (${[...fields].join(" / ")})`,
110
+ fields.size === 1,
111
+ );
112
+ check(
113
+ `all carriers agree on OLD_PAPERS_DIR_FIELD (${[...olds].join(" / ")})`,
114
+ olds.size === 1,
115
+ );
116
+ check(
117
+ "every hook declares both field names — a hook without them would read an undefined field",
118
+ loaded
119
+ .filter((c) => c.rel.endsWith(".hook.mjs"))
120
+ .every((c) => typeof c.field === "string" && typeof c.old === "string"),
121
+ );
122
+ check(
123
+ "the new and the old field names differ",
124
+ loaded[0].field !== loaded[0].old,
125
+ );
126
+ check(
127
+ "and no carrier is silent about both at once — such an entry is dead weight on the list",
128
+ loaded.every((c) => c.key !== undefined || c.def !== undefined),
129
+ );
130
+ // The key is the PACKAGE'S NAME, not a string that merely matches it. They drift apart when
131
+ // the package is renamed — the consumer would declare settings under one name while a
132
+ // different one is read.
133
+ check(
134
+ "config key equals the package name in package.json",
135
+ loaded[0].key ===
136
+ JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).name,
137
+ );
138
+ // The old key is still read — by the source and by each hook, which keep their own copy.
139
+ const legacyKeys = new Set(
140
+ loaded.map((c) => c.legacyKey).filter((v) => v !== undefined),
141
+ );
142
+ const { LEGACY_PACKAGE_NAME } = await import(
143
+ pathToFileURL(join(ROOT, "skills/paper-pipeline/scripts/consumer.mjs")).href
144
+ );
145
+ check(
146
+ `all carriers agree on LEGACY_CONFIG_KEY, the package's old name (${[...legacyKeys].join(" / ")})`,
147
+ legacyKeys.size === 1 && legacyKeys.has(LEGACY_PACKAGE_NAME),
148
+ );
149
+ check(
150
+ "and every hook carries it — a hook without it would ignore settings under the old key",
151
+ loaded
152
+ .filter((c) => c.rel.endsWith(".hook.mjs"))
153
+ .every((c) => c.legacyKey !== undefined),
154
+ );
155
+ }
156
+
157
+ // ── III. THE HOOKS DUPLICATE, AND THAT IS CHECKED SEPARATELY ───────────────────────────────
158
+ // A hook that started importing the constants would compile, run, and pass the value
159
+ // cross-check — but it would violate the `checkHookImports` contract, i.e. stop being a hook
160
+ // with a proven capability surface. The values have nothing to do with that, so it gets its own
161
+ // check.
162
+ {
163
+ const { checkHookImports } = await import("vigiles/hook");
164
+ for (const rel of CARRIERS.filter((c) => c.endsWith(".hook.mjs"))) {
165
+ const offending = checkHookImports(
166
+ readFileSync(join(ROOT, rel), "utf8"),
167
+ rel,
168
+ );
169
+ check(
170
+ `${rel} pulls in nothing outside the hook vocabulary (${offending.join(", ") || "clean"})`,
171
+ offending.length === 0,
172
+ );
173
+ }
174
+ }
175
+
176
+ // ── IV. COMPLETENESS OF THE LIST: IS THERE A SIXTH CARRIER ─────────────────────────────────
177
+ // The subject of this check is not the value but the EXISTENCE of a file the list doesn't know
178
+ // about.
179
+ {
180
+ const skip = new Set([
181
+ "node_modules",
182
+ ".git",
183
+ "dist",
184
+ "_build",
185
+ "fixtures",
186
+ "repro",
187
+ ]);
188
+ const declares =
189
+ /^(?:export )?const (?:CONFIG_KEY|DEFAULT_PAPERS_ROOT|PAPERS_DIR_FIELD|OLD_PAPERS_DIR_FIELD)\s*=/m;
190
+ const found = [];
191
+ const walk = (dir) => {
192
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
193
+ if (e.name.startsWith(".") || skip.has(e.name)) continue;
194
+ const p = join(dir, e.name);
195
+ if (e.isDirectory()) walk(p);
196
+ else if (
197
+ /\.(mjs|ts)$/.test(e.name) &&
198
+ !/\.(harness|mutations)\.(mjs|ts)$/.test(e.name)
199
+ ) {
200
+ if (declares.test(readFileSync(p, "utf8")))
201
+ found.push(relative(ROOT, p));
202
+ }
203
+ }
204
+ };
205
+ walk(ROOT);
206
+ const unknown = found.filter((f) => !CARRIERS.includes(f));
207
+ check(
208
+ `no declaration outside the carrier list (${unknown.join(", ") || "clean"})`,
209
+ unknown.length === 0,
210
+ );
211
+ check(
212
+ "and the search itself is not empty — otherwise it is searching the wrong place, and its zero reads as all-clear",
213
+ found.length >= 4,
214
+ );
215
+ }
216
+
217
+ // ── SETTINGS_KEYS against every reader of the settings ─────────────────────────────────────
218
+ // The settings object has readers all over the package, each reading its own keys, and the CLI
219
+ // refuses any key not in SETTINGS_KEYS. So the list must hold every key some reader reads — or a
220
+ // consumer's valid key is refused — and nothing no one reads. The readers are FOUND, by the AST:
221
+ // every `[CONFIG_KEY]?.<key>` (or the literal package name in the brackets), plus every field of
222
+ // `RppConfig`, the CLI's typed view of the same object.
223
+ {
224
+ const { SETTINGS_KEYS } = await import(join(HERE, "paper-config.mjs"));
225
+ const ts = (await import("typescript")).default;
226
+ const readKeys = new Set();
227
+ const isConfigKey = (e) =>
228
+ (ts.isIdentifier(e) &&
229
+ (e.text === "CONFIG_KEY" || e.text === "LEGACY_CONFIG_KEY")) ||
230
+ (ts.isStringLiteral(e) &&
231
+ (e.text === "paperlint" || e.text === "research-paper-pipeline"));
232
+ // A read is `pkg[CONFIG_KEY]?.<key>` or `settingsOf(pkg)?.<key>` — the helper that also reads
233
+ // the old key.
234
+ const isSettings = (e) =>
235
+ (ts.isElementAccessExpression(e) && isConfigKey(e.argumentExpression)) ||
236
+ (ts.isCallExpression(e) &&
237
+ ts.isIdentifier(e.expression) &&
238
+ e.expression.text === "settingsOf");
239
+ const visit = (node) => {
240
+ if (ts.isPropertyAccessExpression(node) && isSettings(node.expression))
241
+ readKeys.add(node.name.text);
242
+ if (ts.isInterfaceDeclaration(node) && node.name.text === "RppConfig")
243
+ for (const m of node.members)
244
+ if (m.name && ts.isIdentifier(m.name)) readKeys.add(m.name.text);
245
+ ts.forEachChild(node, visit);
246
+ };
247
+ const skipDirs = new Set(["node_modules", "dist", "fixtures", "docs"]);
248
+ const scan = (dir) => {
249
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
250
+ if (e.name.startsWith(".") || skipDirs.has(e.name)) continue;
251
+ const p = join(dir, e.name);
252
+ if (e.isDirectory()) scan(p);
253
+ else if (/\.(mjs|ts)$/.test(e.name) && !/\.harness\.mjs$/.test(e.name))
254
+ visit(
255
+ ts.createSourceFile(
256
+ p,
257
+ readFileSync(p, "utf8"),
258
+ ts.ScriptTarget.Latest,
259
+ true,
260
+ ),
261
+ );
262
+ }
263
+ };
264
+ scan(ROOT);
265
+ const listed = new Set(Object.keys(SETTINGS_KEYS));
266
+ const unlisted = [...readKeys].filter((k) => !listed.has(k));
267
+ const unread = [...listed].filter((k) => !readKeys.has(k));
268
+ check(
269
+ `🔴 every settings key some reader reads is in SETTINGS_KEYS — else a valid key is refused (${unlisted.join(", ") || "clean"})`,
270
+ unlisted.length === 0,
271
+ );
272
+ check(
273
+ `every key in SETTINGS_KEYS has a reader (${unread.join(", ") || "clean"})`,
274
+ unread.length === 0,
275
+ );
276
+ check(
277
+ "and the scan found the readers it must find — the skill scripts' keys and RppConfig's",
278
+ ["ledger", "citeChecks", "triggerCases", "papersDir", "rules"].every((k) =>
279
+ readKeys.has(k),
280
+ ),
281
+ );
282
+ }
283
+
284
+ console.log(
285
+ `✓ ${n} assertions passed — one source for the consumer's config key`,
286
+ );
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Where the consumer declares its settings — the ONE SOURCE for everything that is allowed to
3
+ * import it.
4
+ *
5
+ * 🔴 WHY THIS MODULE DOES NOT REMOVE THE DUPLICATION COMPLETELY, AND THAT IS NOT AN OVERSIGHT.
6
+ * The three hooks (`hooks/*.hook.mjs`) still have to keep their own copies of these values: a
7
+ * compiled hook is forbidden to import anything but `vigiles/hook`, and that ban is not
8
+ * arbitrary — a hook's capabilities equal its API surface, so allowing imports would mean
9
+ * allowing a reach into `child_process`. The restriction is wider than strictly necessary
10
+ * (importing plain constants adds no capability), and that is discussed in zernie/vigiles#245,
11
+ * but until it is relaxed the hooks duplicate, and drift is caught by the cross-check in
12
+ * `hooks/hooks.harness.mjs`.
13
+ *
14
+ * 🔴 BUT THE REST WERE DUPLICATING FOR NO REASON AT ALL. `eslint-rules/papers.mjs` and
15
+ * `skills/paper-pipeline/scripts/consumer.mjs` are not hooks, carry no import restriction, and
16
+ * could always have taken the value from here. Worse, their copies were not covered by the
17
+ * cross-check at all — it compared the three hooks against each other, while these two lived
18
+ * next door and could drift apart silently. Found 2026-09-18 by grepping AFTER the cross-check
19
+ * itself had already been fixed — i.e. the check's wording was fixed, but nobody measured its
20
+ * COVERAGE.
21
+ */
22
+
23
+ /** The key in the consumer's `package.json` this package's settings are read from. */
24
+ export const CONFIG_KEY = "paperlint";
25
+
26
+ /**
27
+ * The key's name before 2.0.0, when the package was `research-paper-pipeline`. Still READ, with a
28
+ * deprecation line from the CLI and `doctor`; `init` renames it. Both keys at once with different
29
+ * contents is refused: there is no way to know which one the author means.
30
+ */
31
+ export const LEGACY_CONFIG_KEY = "research-paper-pipeline";
32
+
33
+ /**
34
+ * Where a parsed `package.json` keeps this package's settings — the new key, else the old one.
35
+ *
36
+ * @returns `settings` (undefined when neither key is there, or on a conflict), `legacy` (read
37
+ * from the old key), `conflict` (the message when both keys are there and differ, else null).
38
+ */
39
+ export function declaredSettings(pkg) {
40
+ const has = (k) =>
41
+ pkg !== null && typeof pkg === "object" && Object.hasOwn(pkg, k);
42
+ const current = has(CONFIG_KEY);
43
+ const legacy = has(LEGACY_CONFIG_KEY);
44
+ if (
45
+ current &&
46
+ legacy &&
47
+ JSON.stringify(pkg[CONFIG_KEY]) !== JSON.stringify(pkg[LEGACY_CONFIG_KEY])
48
+ )
49
+ return {
50
+ settings: undefined,
51
+ legacy: false,
52
+ conflict:
53
+ `package.json has both "${CONFIG_KEY}" and "${LEGACY_CONFIG_KEY}", and they differ. ` +
54
+ `Keep "${CONFIG_KEY}" and delete "${LEGACY_CONFIG_KEY}" (its old name).`,
55
+ };
56
+ if (current)
57
+ return { settings: pkg[CONFIG_KEY], legacy: false, conflict: null };
58
+ if (legacy)
59
+ return { settings: pkg[LEGACY_CONFIG_KEY], legacy: true, conflict: null };
60
+ return { settings: undefined, legacy: false, conflict: null };
61
+ }
62
+
63
+ /**
64
+ * The settings object alone, for readers that only need a value: the new key, else the old one,
65
+ * and nothing on a conflict (the CLI, `doctor` and the edit guard report the conflict).
66
+ */
67
+ export function settingsOf(pkg) {
68
+ const d = declaredSettings(pkg);
69
+ return d.conflict === null ? d.settings : undefined;
70
+ }
71
+
72
+ /** The deprecation line for settings read from the old key. */
73
+ export const LEGACY_KEY_MESSAGE = `"${LEGACY_CONFIG_KEY}" in package.json is the old name of this package's key — rename it to "${CONFIG_KEY}". \`npx paperlint init\` does it for you.`;
74
+
75
+ /** Default: a consumer that declares nothing is assumed to keep papers in `papers/`. */
76
+ export const DEFAULT_PAPERS_ROOT = "papers";
77
+
78
+ /**
79
+ * The field under CONFIG_KEY that names the papers directory:
80
+ * `"paperlint": { "papersDir": "papers" }`.
81
+ *
82
+ * This is the one place the name is spelled. Code reads the field as
83
+ * `settings[PAPERS_DIR_FIELD]`, never by a literal, so renaming it again is a one-line change
84
+ * here. The three hooks cannot import this module, so they keep their own copy, and
85
+ * `lib/paper-config.harness.mjs` checks that every copy matches this one.
86
+ */
87
+ export const PAPERS_DIR_FIELD = "papersDir";
88
+
89
+ /**
90
+ * The field's old name. A config that still has it is REFUSED with `renamedFieldMessage()`,
91
+ * even if it also has the new one: the package is unpublished, so there is nobody to migrate
92
+ * quietly, and reading the old name as a fallback would leave it working forever.
93
+ */
94
+ export const OLD_PAPERS_DIR_FIELD = "papers";
95
+
96
+ /**
97
+ * The error text for a config that still uses the old field name, or `null` when it does not.
98
+ *
99
+ * @param settings the object under CONFIG_KEY (or the whole `rpp.json`)
100
+ * @param where where that object lives, as the reader should see it in the message
101
+ */
102
+ export function renamedFieldMessage(
103
+ settings,
104
+ where = `package.json → "${CONFIG_KEY}"`,
105
+ ) {
106
+ if (settings === null || typeof settings !== "object") return null;
107
+ if (!Object.hasOwn(settings, OLD_PAPERS_DIR_FIELD)) return null;
108
+ return `"${OLD_PAPERS_DIR_FIELD}" was renamed to "${PAPERS_DIR_FIELD}" in ${where}`;
109
+ }
110
+
111
+ /**
112
+ * Every key the settings object may hold, with who reads it. A key not listed here is REFUSED
113
+ * with its name (`paperlint lint`, `paperlint build`, `paperlint new`, `paperlint doctor` all read through the same
114
+ * parser), because a typo in a key otherwise reads as "not set" — the defect `src/types.ts` names
115
+ * in its first line.
116
+ *
117
+ * 🔴 ONE LIST, AND IT IS CHECKED AGAINST THE READERS, NOT TRUSTED. The settings object has readers
118
+ * all over the package (the CLI, the skill scripts, the trigger-case loader), each reading its own
119
+ * keys. `lib/paper-config.harness.mjs` walks the package's source, finds every
120
+ * `[CONFIG_KEY]?.<key>` read and every field of `RppConfig`, and requires each to be here — so a
121
+ * new reader that forgets this list turns the harness red before a consumer's config does.
122
+ */
123
+ export const SETTINGS_KEYS = Object.freeze({
124
+ papersDir: "the papers directory (required) — every command, the hooks",
125
+ structure: "which files a paper directory must hold — paperlint lint",
126
+ authorListCommand: "paper/author-list — paperlint lint",
127
+ typographyDebt: "paper/typography — paperlint lint",
128
+ docFields: "doc/fields — paperlint lint",
129
+ reviewSince: "review/findings-cause — paperlint lint",
130
+ minFindings: "review/findings-cause — paperlint lint",
131
+ causeMarker: "review/findings-cause — paperlint lint",
132
+ rules:
133
+ "extra ESLint config blocks, appended after rpp's own — paperlint lint",
134
+ buildScripts: "REMOVED; still named so paperlint build can say it is ignored",
135
+ ledger: "the run ledger — skills/paper-pipeline/scripts/consumer.mjs",
136
+ scripts: "how skill prose names the scripts — consumer.mjs",
137
+ timezone: "dates in the ledger — consumer.mjs",
138
+ contactEmail: "the polite-pool address for citation lookups — consumer.mjs",
139
+ citeChecks: "the consumer's citation checkers — run-mechanical.mjs",
140
+ triggerCases:
141
+ "the consumer's own skill trigger cases — lib/skill-trigger-cases.mjs",
142
+ });
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Battery for `lib/paper-config.mjs` and the cross-check of its carriers.
3
+ *
4
+ * The subject is agreement among several files about one value. The failure here is silent by
5
+ * construction: a drifted key breaks neither the build nor the run, it only makes one carrier
6
+ * read settings the consumer never wrote. Every case below reintroduces that kind of drift.
7
+ */
8
+ import { fileURLToPath } from "node:url";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { runMutations } from "./mutation-driver.mjs";
11
+
12
+ const HERE = dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = resolve(HERE, "..");
14
+ const HARNESS = join(HERE, "paper-config.harness.mjs");
15
+ const HOOK = join(ROOT, "hooks", "paper-skills-nudge.hook.mjs");
16
+ const RULE = join(ROOT, "eslint-rules", "papers.mjs");
17
+ const DOCTOR = join(ROOT, "src", "doctor.ts");
18
+
19
+ process.exit(
20
+ runMutations({
21
+ root: ROOT,
22
+ runner: "node",
23
+ cases: [
24
+ {
25
+ name: "the key drifts in one of the hooks",
26
+ harness: HARNESS,
27
+ expect: "all carriers agree on CONFIG_KEY",
28
+ disables:
29
+ "the whole point of the forced duplication. A hook with a mismatched key reads " +
30
+ "settings the consumer never wrote, falls back to the default, and guards the wrong " +
31
+ "directory — silently, because silence is exactly what its success looks like",
32
+ edits: [
33
+ [
34
+ HOOK,
35
+ 'export const CONFIG_KEY = "paperlint";',
36
+ 'export const CONFIG_KEY = "paperlints";',
37
+ ],
38
+ ],
39
+ },
40
+ {
41
+ name: "a hook's copy of the papers-directory field name drifts",
42
+ harness: HARNESS,
43
+ expect: "all carriers agree on PAPERS_DIR_FIELD",
44
+ disables:
45
+ "the check on the one value this package renamed. A hook reading a different field " +
46
+ "name finds nothing, falls back to the default directory, and says nothing about it",
47
+ edits: [
48
+ [
49
+ HOOK,
50
+ // Prefix the value rather than spell it, so a future rename does not touch this file.
51
+ 'export const PAPERS_DIR_FIELD = "',
52
+ 'export const PAPERS_DIR_FIELD = "drifted-',
53
+ ],
54
+ ],
55
+ },
56
+ {
57
+ name: "the ESLint rule grows its OWN copy of the default again",
58
+ harness: HARNESS,
59
+ expect: "all carriers agree on DEFAULT_PAPERS_ROOT",
60
+ disables:
61
+ "the re-export the consolidation existed for in the first place. A non-hook's own " +
62
+ "copy serves nothing — the import restriction does not apply to it — and exactly this " +
63
+ "kind of copy sat OUTSIDE the cross-check for two days, because the cross-check " +
64
+ "compared hooks against each other",
65
+ edits: [
66
+ [
67
+ RULE,
68
+ "import {\n CONFIG_KEY,\n DEFAULT_PAPERS_ROOT,\n PAPERS_DIR_FIELD,",
69
+ 'const DEFAULT_PAPERS_ROOT = "drafts";\nimport {\n CONFIG_KEY,\n PAPERS_DIR_FIELD,',
70
+ ],
71
+ ],
72
+ },
73
+ {
74
+ name: "the config key stops being the package name",
75
+ harness: HARNESS,
76
+ expect: "config key equals the package name",
77
+ disables:
78
+ "the link between what the package is called and the name the consumer declares its " +
79
+ "settings under. They drift apart when the package is renamed: the docs will say one " +
80
+ "thing, a different one gets read, and there will be no error at all",
81
+ // The PACKAGE is renamed, not the constant: the carriers stay in agreement with each
82
+ // other, and the only thing that drifts is the "key = package name" link. Swapping the
83
+ // constant itself would kill the neighboring carrier-agreement assertion first and
84
+ // never reach this one.
85
+ edits: [
86
+ [
87
+ join(ROOT, "package.json"),
88
+ '"name": "paperlint",',
89
+ '"name": "paperlint-renamed",',
90
+ ],
91
+ ],
92
+ },
93
+ {
94
+ name: "a hook smuggles a capability past the vocabulary",
95
+ harness: HARNESS,
96
+ expect: "pulls in nothing outside the hook vocabulary",
97
+ disables:
98
+ "the one guarantee the hooks put up with duplication for: a hook's capabilities equal " +
99
+ "its API surface. A hook that reaches `node:fs` still runs and passes the value " +
100
+ "cross-check — the violation is visible only through this check",
101
+ edits: [
102
+ [
103
+ HOOK,
104
+ "import {\n experimental_defineReact,",
105
+ 'import { readFileSync } from "node:fs";\nimport {\n experimental_defineReact,',
106
+ ],
107
+ ],
108
+ },
109
+ {
110
+ name: "a constant is written into a sixth file instead of imported",
111
+ harness: HARNESS,
112
+ expect: "no declaration outside the carrier list",
113
+ disables:
114
+ "the COMPLETENESS check. The carrier list is explicit, so a new copy planted anywhere " +
115
+ "else would have nothing to compare against — which is exactly how the duplication " +
116
+ "spread last time",
117
+ edits: [
118
+ [
119
+ DOCTOR,
120
+ 'import {\n papersRoot,\n CONFIG_KEY,\n DEFAULT_PAPERS_ROOT,\n PAPERS_DIR_FIELD,\n OLD_PAPERS_DIR_FIELD,\n} from "../hooks/paper-edit-guard.hook.mjs";',
121
+ 'import {\n papersRoot,\n DEFAULT_PAPERS_ROOT,\n PAPERS_DIR_FIELD,\n OLD_PAPERS_DIR_FIELD,\n} from "../hooks/paper-edit-guard.hook.mjs";\nconst CONFIG_KEY = "research-paper-pipeline";',
122
+ ],
123
+ ],
124
+ },
125
+ {
126
+ name: "the completeness walk stops looking into the hooks",
127
+ harness: HARNESS,
128
+ expect: "the search itself is not empty",
129
+ disables:
130
+ "the guard against a FALSE ZERO. A search that found nothing looks like a clean " +
131
+ "corpus; without this assertion, the completeness check, having started looking in " +
132
+ "the wrong place, would report all-clear",
133
+ edits: [
134
+ [
135
+ HARNESS,
136
+ 'const skip = new Set([\n "node_modules",\n ".git",\n "dist",\n "_build",\n "fixtures",\n "repro",\n ]);',
137
+ 'const skip = new Set([\n "node_modules",\n ".git",\n "dist",\n "_build",\n "fixtures",\n "repro",\n "hooks",\n "lib",\n ]);',
138
+ ],
139
+ ],
140
+ },
141
+ ],
142
+ }),
143
+ );