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,396 @@
1
+ /**
2
+ * Both halves for `paperlint doctor` — a command whose subject is the STATE OF THE INSTALL, not a
3
+ * file.
4
+ *
5
+ * 🔴 WHY A TEST MATTERS ESPECIALLY MUCH HERE. Doctor exists precisely because
6
+ * `paper-edit-guard` cannot say "I am guarding nothing": silence is its success state. A broken
7
+ * doctor has exactly the same property — it will print a cheerful list of checkmarks and never
8
+ * notice a mismatch. That is, the check for a silent failure can itself fail silently, and only
9
+ * this file can tell the two apart.
10
+ *
11
+ * Run: npx vigiles test src/doctor.harness.mjs
12
+ * Killed by: src/doctor.mutations.mjs
13
+ */
14
+ import assert from "node:assert/strict";
15
+ import {
16
+ OLD_PAPERS_DIR_FIELD,
17
+ PAPERS_DIR_FIELD,
18
+ } from "../lib/paper-config.mjs";
19
+ import {
20
+ mkdtempSync,
21
+ mkdirSync,
22
+ writeFileSync,
23
+ rmSync,
24
+ realpathSync,
25
+ } from "node:fs";
26
+ import { tmpdir } from "node:os";
27
+ import { dirname, join } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+
30
+ const HERE = dirname(fileURLToPath(import.meta.url));
31
+ const { doctor, detectPapers, PROGRAMS, found } = await import(
32
+ join(HERE, "doctor.ts")
33
+ );
34
+ const { papersRoot } = await import(
35
+ join(HERE, "..", "hooks", "paper-edit-guard.hook.mjs")
36
+ );
37
+
38
+ let n = 0;
39
+ const check = (label, cond) => {
40
+ n++;
41
+ assert.ok(cond, label);
42
+ };
43
+
44
+ /** A consumer on disk: a papers directory, declarations in one or both places. */
45
+ function consumer({ papersDir, pkgKey, rppJson, makeDir = true }) {
46
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "rpp-doctor-")));
47
+ if (makeDir && papersDir) {
48
+ mkdirSync(join(dir, papersDir, "some-paper"), { recursive: true });
49
+ writeFileSync(
50
+ join(dir, papersDir, "some-paper", "PIPELINE-STATUS.md"),
51
+ "# s\n",
52
+ );
53
+ }
54
+ const pkg = { name: "consumer", version: "1.0.0" };
55
+ if (pkgKey !== undefined) pkg["paperlint"] = { [PAPERS_DIR_FIELD]: pkgKey };
56
+ writeFileSync(join(dir, "package.json"), JSON.stringify(pkg, null, 2));
57
+ if (rppJson !== undefined)
58
+ writeFileSync(
59
+ join(dir, "rpp.json"),
60
+ JSON.stringify({ [PAPERS_DIR_FIELD]: rppJson }, null, 2),
61
+ );
62
+ return dir;
63
+ }
64
+
65
+ /** Runs doctor in memory: all output is collected, external programs are faked so it does not depend on the machine. */
66
+ const runDoctor = (
67
+ dir,
68
+ { cliPapers = null, have = () => 0, skillLinks } = {},
69
+ ) => {
70
+ const lines = [];
71
+ const code = doctor({
72
+ log: (...a) => lines.push(a.join(" ")),
73
+ cwd: dir,
74
+ projectDir: dir,
75
+ cliPapers,
76
+ run: (_bin, _args) => ({ status: have(_args?.[1]) }),
77
+ ...(skillLinks ? { skillLinks } : {}),
78
+ });
79
+ return { code, out: lines.join("\n") };
80
+ };
81
+
82
+ // ── I. A CONSISTENT INSTALL IS SILENT ───────────────────────────────────────────────────────
83
+ {
84
+ const dir = consumer({ papersDir: "papers", pkgKey: "papers" });
85
+ const r = runDoctor(dir, { cliPapers: "papers" });
86
+ check("a consistent install — exit ZERO", r.code === 0);
87
+ check(
88
+ "and it says outright that the guarded directory is the linted one",
89
+ /the same directory/.test(r.out),
90
+ );
91
+ rmSync(dir, { recursive: true, force: true });
92
+ }
93
+
94
+ // ── I½. THE OLD FIELD NAME IS A FAILURE, NAMED ─────────────────────────────────────────────
95
+ {
96
+ const dir = consumer({ papersDir: "papers", pkgKey: "papers" });
97
+ writeFileSync(
98
+ join(dir, "package.json"),
99
+ JSON.stringify({
100
+ name: "consumer",
101
+ paperlint: { [OLD_PAPERS_DIR_FIELD]: "papers" },
102
+ }),
103
+ );
104
+ const r = runDoctor(dir, { cliPapers: null });
105
+ check("the old field name — exit NON-zero", r.code !== 0);
106
+ check(
107
+ "and doctor says what the field was renamed to",
108
+ r.out.includes(
109
+ `"${OLD_PAPERS_DIR_FIELD}" was renamed to "${PAPERS_DIR_FIELD}" in package.json → "paperlint"`,
110
+ ),
111
+ );
112
+ rmSync(dir, { recursive: true, force: true });
113
+ }
114
+
115
+ // ── II. THE VERY DEFECT: AN INSTALL FOLLOWING THE DOCS ─────────────────────────────────────
116
+ // `paperlint init` writes rpp.json and does not touch package.json; the hook reads package.json.
117
+ // Measured 09-18.
118
+ {
119
+ const dir = consumer({
120
+ papersDir: "writing/drafts",
121
+ rppJson: "writing/drafts",
122
+ });
123
+ const r = runDoctor(dir, { cliPapers: "writing/drafts" });
124
+ check(
125
+ "an install that follows the docs — a FAILURE, not a cheerful report",
126
+ r.code === 2,
127
+ );
128
+ check(
129
+ "and BOTH directories are named, so the mismatch is visible rather than inferred",
130
+ /will lint\s+writing\/drafts/.test(r.out) &&
131
+ /will guard\s+papers/.test(r.out),
132
+ );
133
+ check(
134
+ "🔴 and the CONSEQUENCE is stated: writes pass the guard unseen",
135
+ /passes the guard unseen/.test(r.out),
136
+ );
137
+ check(
138
+ "the directory that does not exist is named as a guard watching nothing",
139
+ /watching nothing/.test(r.out),
140
+ );
141
+ check(
142
+ "and it hints where the papers ACTUALLY live — measured, not guessed",
143
+ /papers look like they live in: writing\/drafts/.test(r.out),
144
+ );
145
+ rmSync(dir, { recursive: true, force: true });
146
+ }
147
+
148
+ // ── II-bis. A MISSING DECLARATION THAT DOES NO HARM YET ────────────────────────────────────
149
+ // The papers live exactly where the hook's default points. The install WORKS — by coincidence.
150
+ // It must not fail here (an error-level false positive costs more than a miss), but it must
151
+ // not stay silent either.
152
+ {
153
+ const dir = consumer({ papersDir: "papers", rppJson: "papers" });
154
+ const r = runDoctor(dir, { cliPapers: "papers" });
155
+ check("an install that works by coincidence does NOT crash", r.code === 0);
156
+ check(
157
+ "but the missing declaration is NAMED, not skipped",
158
+ /⚠ package\.json has no "paperlint"/.test(r.out),
159
+ );
160
+ check(
161
+ "and it says exactly why that is risky — it works only until the directory moves",
162
+ /works only while your papers happen to live there/.test(r.out),
163
+ );
164
+ rmSync(dir, { recursive: true, force: true });
165
+ }
166
+
167
+ // ── II-ter. A FRESH PROJECT WITH NO PAPERS YET IS NOT BROKEN ───────────────────────────────
168
+ // `init --yes` declares the default and creates no paper. The declared directory does not exist,
169
+ // and no papers live anywhere else: the guard has nothing to protect yet. A warning with the next
170
+ // step, not a failure. (A missing directory while papers DO live elsewhere stays ✗ — case II.)
171
+ {
172
+ const dir = consumer({
173
+ papersDir: "papers",
174
+ pkgKey: "papers",
175
+ makeDir: false,
176
+ });
177
+ const r = runDoctor(dir, { cliPapers: "papers" });
178
+ check("no papers anywhere yet — NOT a failure", r.code === 0);
179
+ check(
180
+ "but it is named, with the command that creates the first paper",
181
+ /⚠ papers does not exist yet — no papers yet/.test(r.out) &&
182
+ /new <name>/.test(r.out) &&
183
+ !/watching nothing/.test(r.out),
184
+ );
185
+ rmSync(dir, { recursive: true, force: true });
186
+ }
187
+
188
+ // ── II-quater. THE KEY'S OLD NAME IS READ AND NAMED; TWO DIFFERENT KEYS ARE A FAILURE ──────
189
+ {
190
+ const dir = consumer({ papersDir: "papers" });
191
+ writeFileSync(
192
+ join(dir, "package.json"),
193
+ JSON.stringify({
194
+ "research-paper-pipeline": { [PAPERS_DIR_FIELD]: "papers" },
195
+ }),
196
+ );
197
+ const r = runDoctor(dir, { cliPapers: "papers" });
198
+ check(
199
+ "the old key — read (not a failure), and named with the new one",
200
+ r.code === 0 &&
201
+ /⚠ "research-paper-pipeline" in package\.json is the old name/.test(
202
+ r.out,
203
+ ) &&
204
+ /the hooks will guard papers/.test(r.out),
205
+ );
206
+ writeFileSync(
207
+ join(dir, "package.json"),
208
+ JSON.stringify({
209
+ paperlint: { [PAPERS_DIR_FIELD]: "papers" },
210
+ "research-paper-pipeline": { [PAPERS_DIR_FIELD]: "elsewhere" },
211
+ }),
212
+ );
213
+ const both = runDoctor(dir, { cliPapers: "papers" });
214
+ check(
215
+ "both keys with different contents — a failure",
216
+ both.code === 2 && /✗ package\.json has both/.test(both.out),
217
+ );
218
+ rmSync(dir, { recursive: true, force: true });
219
+ }
220
+
221
+ // ── III. TWO DECLARATIONS HAVE DRIFTED APART ────────────────────────────────────────────────
222
+ {
223
+ const dir = consumer({
224
+ papersDir: "writing/drafts",
225
+ pkgKey: "papers",
226
+ rppJson: "writing/drafts",
227
+ });
228
+ mkdirSync(join(dir, "papers"), { recursive: true });
229
+ const r = runDoctor(dir, { cliPapers: "writing/drafts" });
230
+ check("both declarations exist, but differ — a FAILURE", r.code === 2);
231
+ check(
232
+ "the stale rpp.json is called out ⚠",
233
+ /rpp\.json is present/.test(r.out),
234
+ );
235
+ rmSync(dir, { recursive: true, force: true });
236
+ }
237
+
238
+ // ── IV. THE ROOT IS ASKED FOR FROM THE HOOK ITSELF, NOT RETOLD ─────────────────────────────
239
+ // This is load-bearing: a copy of the logic would drift silently and print a confident wrong
240
+ // answer.
241
+ {
242
+ const dir = consumer({ papersDir: "docs/papers", pkgKey: "docs/papers/" });
243
+ const r = runDoctor(dir, { cliPapers: "docs/papers" });
244
+ const fromHook = papersRoot(
245
+ JSON.stringify({
246
+ paperlint: { [PAPERS_DIR_FIELD]: "docs/papers/" },
247
+ }),
248
+ );
249
+ check("the hook itself trims the trailing slash", fromHook === "docs/papers");
250
+ check(
251
+ "and doctor prints EXACTLY what the hook returned, not its own reading",
252
+ new RegExp(`will guard\\s+${fromHook}$`, "m").test(r.out),
253
+ );
254
+ check(
255
+ "a trailing slash does not make the install look inconsistent",
256
+ r.code === 0,
257
+ );
258
+ rmSync(dir, { recursive: true, force: true });
259
+ }
260
+
261
+ // ── V. THE HOOK'S REFUSAL IS PASSED ALONG, NOT TURNED INTO A DIRECTORY ─────────────────────
262
+ {
263
+ const dir = consumer({ papersDir: "papers", pkgKey: "" });
264
+ const r = runDoctor(dir, { cliPapers: "papers" });
265
+ check(
266
+ "an empty string in the declaration — the hook refuses, and doctor NAMES it",
267
+ /the guard refuses/.test(r.out),
268
+ );
269
+ rmSync(dir, { recursive: true, force: true });
270
+ }
271
+
272
+ // ── VI. EXTERNAL PROGRAMS ARE A FACT, NOT A VERDICT ─────────────────────────────────────────
273
+ {
274
+ const dir = consumer({ papersDir: "papers", pkgKey: "papers" });
275
+ const none = runDoctor(dir, { cliPapers: "papers", have: () => 1 });
276
+ check(
277
+ "🔴 a missing tex install does NOT fail the run — a gate on advice would mute the whole thing",
278
+ none.code === 0,
279
+ );
280
+ check("but every absence is NAMED", /✗ pdflatex/.test(none.out));
281
+ check(
282
+ "and it carries a REMEDY, not just a diagnosis",
283
+ /npx paperlint toolchain/.test(none.out),
284
+ );
285
+ check(
286
+ "and the consequence: which checks silently don't run without it",
287
+ /nothing else spell-checks the text/.test(none.out),
288
+ );
289
+ check(
290
+ "the program list is DECLARED, not baked into the printing",
291
+ PROGRAMS.length >= 5,
292
+ );
293
+ check(
294
+ "no poppler row: paperlint reads PDFs with pdf.js, which it installs itself",
295
+ !PROGRAMS.some((p) => /^pdf(info|fonts|totext|tohtml)$/.test(p.bin)) &&
296
+ !/poppler/.test(none.out),
297
+ );
298
+ // Guards: banal's one system requirement is named — a missing perl reads as null geometry, silently.
299
+ check(
300
+ "a perl row, naming what goes missing and how banal gets installed",
301
+ /✗ perl/.test(none.out) &&
302
+ /paperlint toolchain \(it fetches banal\)/.test(none.out),
303
+ none.out,
304
+ );
305
+ rmSync(dir, { recursive: true, force: true });
306
+ }
307
+
308
+ // ── VII. DETECTING THE PAPERS DIRECTORY ─────────────────────────────────────────────────────
309
+ {
310
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "rpp-detect-")));
311
+ mkdirSync(join(dir, "writing", "drafts", "p1"), { recursive: true });
312
+ writeFileSync(join(dir, "writing", "drafts", "p1", "paper.tex"), "x");
313
+ mkdirSync(join(dir, "node_modules", "pkg", "papers", "p"), {
314
+ recursive: true,
315
+ });
316
+ writeFileSync(
317
+ join(dir, "node_modules", "pkg", "papers", "p", "paper.tex"),
318
+ "x",
319
+ );
320
+ const hits = detectPapers(dir);
321
+ check(
322
+ "finds the root by a marker inside a subdirectory",
323
+ hits.includes("writing/drafts"),
324
+ );
325
+ check(
326
+ "🔴 and it is the ROOT, not the paper itself — otherwise the config would point at one document",
327
+ !hits.includes("writing/drafts/p1"),
328
+ );
329
+ check(
330
+ "node_modules is not searched — someone else's papers are not ours",
331
+ !hits.some((h) => h.startsWith("node_modules")),
332
+ );
333
+ // A folder holding only the project's paper TEMPLATE (`paperlint new` reads `<papers>/.template/`)
334
+ // carries every marker, and is still not a papers root: discovery skips dot-directories.
335
+ mkdirSync(join(dir, "only-template", ".template"), { recursive: true });
336
+ writeFileSync(join(dir, "only-template", ".template", "paper.tex"), "x");
337
+ check(
338
+ "🔴 a directory whose only marked child is .template/ is NOT a papers root",
339
+ !detectPapers(dir).includes("only-template"),
340
+ );
341
+ rmSync(dir, { recursive: true, force: true });
342
+ }
343
+
344
+ // ── VII-bis. SKILLS THAT ARE NOT LINKED ARE NAMED — AND DO NOT FAIL THE RUN ─────────────────
345
+ // Before this section a consumer without `.claude/skills/` links had no `/paper-pipeline`, and
346
+ // nothing anywhere said so. The link state itself is `link-skills.harness.mjs`'s subject; here
347
+ // only doctor's REPORTING of it is judged, so the state is handed in rather than built on disk.
348
+ {
349
+ const dir = consumer({ papersDir: "papers", pkgKey: "papers" });
350
+ const state = {
351
+ ok: true,
352
+ home: join(dir, ".claude", "skills"),
353
+ example: "../../node_modules/paperlint/skills/a",
354
+ links: [
355
+ { name: "a", status: "present" },
356
+ { name: "b", status: "missing" },
357
+ { name: "c", status: "foreign", reason: "a directory" },
358
+ ],
359
+ };
360
+ const r = runDoctor(dir, { cliPapers: "papers", skillLinks: () => state });
361
+ check(
362
+ "an unlinked skill is NAMED, not summed into a checkmark",
363
+ /2 of 3 shipped skills are NOT reachable/.test(r.out) &&
364
+ /\bb — not linked/.test(r.out),
365
+ );
366
+ check(
367
+ "a name taken by something else says WHAT is there",
368
+ /\bc — a directory, not the shipped skill/.test(r.out),
369
+ );
370
+ check(
371
+ "🔴 an unlinked skill does NOT fail the run — lint, hooks and CI work without it",
372
+ r.code === 0,
373
+ );
374
+ const all = runDoctor(dir, {
375
+ cliPapers: "papers",
376
+ skillLinks: () => ({ ...state, links: [{ name: "a", status: "present" }] }),
377
+ });
378
+ check(
379
+ "every skill linked — one line says so",
380
+ /✓ all 1 shipped skills are reachable/.test(all.out),
381
+ );
382
+ rmSync(dir, { recursive: true, force: true });
383
+ }
384
+
385
+ // ── VIII. `found` ASKS THE SYSTEM, IT DOES NOT GUESS BY NAME ────────────────────────────────
386
+ {
387
+ check("a program that really exists is found", found("node") === true);
388
+ check(
389
+ "a made-up one is not (otherwise the check answers the form, not the subject)",
390
+ found("rpp-definitely-not-a-real-binary-xyz") === false,
391
+ );
392
+ }
393
+
394
+ console.log(
395
+ `✓ ${n} assertions passed — paperlint doctor: an install can vouch for itself`,
396
+ );
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Battery for `paperlint doctor`.
3
+ *
4
+ * The subject here is unusual and therefore fragile: doctor is a check ABOUT A CHECK. Broken,
5
+ * it prints a column of checkmarks and exits zero — i.e. it fails in exactly the way it was
6
+ * written to catch. Every case below reintroduces one of those silent failures.
7
+ */
8
+ import { fileURLToPath } from "node:url";
9
+ import { dirname, join, resolve } from "node:path";
10
+ import { runMutations } from "../lib/mutation-driver.mjs";
11
+
12
+ const HERE = dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = resolve(HERE, "..");
14
+ const SRC = join(HERE, "doctor.ts");
15
+ const HARNESS = join(HERE, "doctor.harness.mjs");
16
+
17
+ process.exit(
18
+ runMutations({
19
+ root: ROOT,
20
+ runner: "node",
21
+ cases: [
22
+ {
23
+ name: "comparing the two roots stops meaning anything",
24
+ harness: HARNESS,
25
+ expect: "writes pass the guard unseen",
26
+ disables:
27
+ "the only reason this command exists. A mismatch between what the CLI lints and what " +
28
+ "the hook guards is a silent hole: both sides look installed and green, while writes " +
29
+ "into real papers pass the gate unseen",
30
+ edits: [
31
+ [
32
+ SRC,
33
+ " const same = resolve(root, cliPapers) === resolve(root, hookSays);",
34
+ " const same = true;",
35
+ ],
36
+ ],
37
+ },
38
+ {
39
+ name: "the hook's root is retold instead of asked for",
40
+ harness: HARNESS,
41
+ expect: "doctor prints EXACTLY what the hook returned",
42
+ disables:
43
+ "the defense against a SECOND COPY OF THE LOGIC — exactly the defect this command " +
44
+ "exists to report. A retelling drifts from the original silently and prints a " +
45
+ "confident wrong answer: here it loses the trailing-slash trim the hook does on purpose",
46
+ edits: [
47
+ [
48
+ SRC,
49
+ " const hookRoot = rawPkg ? papersRoot(rawPkg) : null;",
50
+ ' const hookRoot = rawPkg ? (JSON.parse(rawPkg)?.["research-paper-pipeline"]?.papersDir ?? "papers") : null;',
51
+ ],
52
+ ],
53
+ },
54
+ {
55
+ name: "a missing external program starts failing the run",
56
+ harness: HARNESS,
57
+ expect: "a missing tex install does NOT fail the run",
58
+ disables:
59
+ "the distinction between a fact and a verdict. Which programs are needed depends on " +
60
+ "which skills you use; a command that fails on advice gets piped into `|| true` or " +
61
+ "/dev/null entirely, taking the real binary findings it was written for down with it",
62
+ edits: [
63
+ [
64
+ SRC,
65
+ " out.push(` ${p.install}`);",
66
+ " out.push(` ${p.install}`);\n bad++;",
67
+ ],
68
+ ],
69
+ },
70
+ {
71
+ name: "detection loses the root and returns the papers themselves",
72
+ harness: HARNESS,
73
+ expect: "finds the root by a marker inside a subdirectory",
74
+ disables:
75
+ "the whole point of the hint. A config pointing at ONE document instead of a " +
76
+ "directory passes every check and lints one paper out of ten — while reporting clean",
77
+ edits: [
78
+ [
79
+ SRC,
80
+ " if (isPapersRoot) hits.push(relative(cwd, here));",
81
+ " if (isPapersRoot) hits.push(...children.filter((c) => c.isDirectory()).map((c) => relative(cwd, join(here, c.name))));",
82
+ ],
83
+ ],
84
+ },
85
+ {
86
+ name: "the papers themselves get mixed into the found root",
87
+ harness: HARNESS,
88
+ expect: "it is the ROOT, not the paper itself",
89
+ disables:
90
+ "the distinction between a papers directory and a paper. The root stays in the list, " +
91
+ 'so the neighboring "found by marker" check notices nothing — while a config built ' +
92
+ "from such a hint points at one document and lints one paper out of ten, reporting clean",
93
+ edits: [
94
+ [
95
+ SRC,
96
+ " if (isPapersRoot) hits.push(relative(cwd, here));",
97
+ " if (isPapersRoot) hits.push(relative(cwd, here), ...children.filter((c) => c.isDirectory()).map((c) => relative(cwd, join(here, c.name))));",
98
+ ],
99
+ ],
100
+ },
101
+ {
102
+ name: "the walk stops skipping node_modules",
103
+ harness: HARNESS,
104
+ expect: "node_modules is not searched",
105
+ disables:
106
+ "the boundary between your own papers and someone else's. Any installed dependency " +
107
+ "carrying paper examples gets mistaken for the consumer's directory, and the hint " +
108
+ "points the config into node_modules",
109
+ edits: [
110
+ [
111
+ SRC,
112
+ 'const skip = new Set(["node_modules", ".git", "dist", "_build", ".claude"]);',
113
+ "const skip = new Set();",
114
+ ],
115
+ ],
116
+ },
117
+ {
118
+ name: "a missing declaration is reported as all-clear",
119
+ harness: HARNESS,
120
+ expect: "the missing declaration is NAMED, not skipped",
121
+ disables:
122
+ "the only trace of issue #33 left on an install that STILL works: `paperlint init` writes " +
123
+ "one file, the hook reads another, and the directories matching up rests on the " +
124
+ "default. A cheerful checkmark instead of a warning turns a coincidence into a confirmation",
125
+ edits: [
126
+ [
127
+ SRC,
128
+ ' ` ⚠ package.json has no "${CONFIG_KEY}": { "${PAPERS_DIR_FIELD}": … } — the hooks fall back to "${DEFAULT_PAPERS_ROOT}"`,',
129
+ " ` ✓ package.json`,",
130
+ ],
131
+ ],
132
+ },
133
+ {
134
+ name: "an unlinked skill is folded into the checkmark",
135
+ harness: HARNESS,
136
+ expect: "an unlinked skill is NAMED",
137
+ disables:
138
+ "the one place a consumer learns that `/paper-pipeline` is missing. Claude Code does not " +
139
+ "look in node_modules, so an install without links has no skills at all and says nothing",
140
+ edits: [
141
+ [
142
+ SRC,
143
+ ' const gaps = links.links.filter((l) => l.status !== "present");',
144
+ ' const gaps = links.links.filter((l) => l.status === "foreign");',
145
+ ],
146
+ ],
147
+ },
148
+ {
149
+ name: "an unlinked skill starts failing the run",
150
+ harness: HARNESS,
151
+ expect: "an unlinked skill does NOT fail the run",
152
+ disables:
153
+ "the line between a fact and a verdict. A consumer who keeps their own skill under the " +
154
+ "same name would get a red doctor — and a red `init` — for a decision init itself respected",
155
+ edits: [
156
+ [
157
+ SRC,
158
+ " out.push(\n ` \\`npx paperlint init\\` links the missing ones; it never replaces an entry it did not make`,\n );",
159
+ " out.push(\n ` \\`npx paperlint init\\` links the missing ones; it never replaces an entry it did not make`,\n );\n bad++;",
160
+ ],
161
+ ],
162
+ },
163
+ {
164
+ name: "a .template child makes a papers root",
165
+ harness: HARNESS,
166
+ expect:
167
+ "a directory whose only marked child is .template/ is NOT a papers root",
168
+ disables:
169
+ "the override slot being invisible to discovery. `paperlint init` in a project that has only " +
170
+ "a house template would declare the template's parent as the papers directory, measured",
171
+ edits: [[SRC, ' !c.name.startsWith(".") &&\n', ""]],
172
+ },
173
+ ],
174
+ }),
175
+ );