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,228 @@
1
+ /**
2
+ * `review/findings-cause` — both halves on real ESLint, plus a case where the old
3
+ * implementation LIED.
4
+ *
5
+ * The rule came over from the consumer (`checkReviewFindingsCause` in `paper-lint.mjs`),
6
+ * where findings were counted by regexes over the text. The third fixture here is not
7
+ * decoration: a table inside a ``` fence is an EXAMPLE of the format, not a report, and
8
+ * the text-based counter recorded it as a finding. In the AST it's a `code` node, and
9
+ * `tableRow` does not exist inside it. That is, the move removed a whole class of bugs,
10
+ * and this fixture pins it down.
11
+ */
12
+ import assert from "node:assert/strict";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { ESLint } from "eslint";
16
+ import markdown from "@eslint/markdown";
17
+ import { recordCheck } from "vigiles";
18
+ import reviewRules from "./review-findings-cause.mjs";
19
+
20
+ const HERE = dirname(fileURLToPath(import.meta.url));
21
+ const FIX = join(HERE, "..", "fixtures", "review-findings-cause");
22
+
23
+ /** Same language as the consumer: the rule judges markdown, not lines of text. */
24
+ const eslint = new ESLint({
25
+ overrideConfigFile: true,
26
+ overrideConfig: [
27
+ {
28
+ files: ["**/*.md"],
29
+ plugins: { markdown, review: reviewRules },
30
+ language: "markdown/gfm",
31
+ languageOptions: { frontmatter: "yaml" },
32
+ rules: { "review/findings-cause": "error" },
33
+ },
34
+ ],
35
+ });
36
+
37
+ const on = async (file) =>
38
+ (await eslint.lintFiles([join(FIX, file)]))[0].messages;
39
+ const cases = [];
40
+
41
+ // ── 1. FIRES: three findings, no cause analysis.
42
+ {
43
+ const m = await on("defect.md");
44
+ assert.equal(
45
+ m.length,
46
+ 1,
47
+ `one finding was expected, got ${m.length}: ${JSON.stringify(m)}`,
48
+ );
49
+ assert.equal(m[0].ruleId, "review/findings-cause");
50
+ assert.match(
51
+ m[0].message,
52
+ /3 findings/,
53
+ "the message must name the NUMBER of findings",
54
+ );
55
+ assert.match(
56
+ m[0].message,
57
+ /PIPELINE/,
58
+ "and say the tool needs fixing, not the paragraph",
59
+ );
60
+ assert.equal(
61
+ m[0].line,
62
+ 1,
63
+ "the finding is about the FILE, so the position is the start of the document",
64
+ );
65
+ cases.push(
66
+ "a report with findings and no cause analysis → a finding, the count is named",
67
+ );
68
+ }
69
+
70
+ // ── 2. STAYS SILENT on a report with an analysis. Without this half the rule is
71
+ // indistinguishable from one that always screams.
72
+ {
73
+ const m = await on("clean.md");
74
+ assert.deepEqual(
75
+ m,
76
+ [],
77
+ `on a report with "Cause:" the rule must stay silent, got: ${JSON.stringify(m)}`,
78
+ );
79
+ cases.push("the same report with a cause analysis → silence");
80
+ }
81
+
82
+ // ── 3. 🔴 STAYS SILENT on a TABLE INSIDE A FENCE — the case the old text-based version
83
+ // counted as findings. This is the move's payoff, shown as a test.
84
+ {
85
+ const m = await on("quiet-in-fence.md");
86
+ assert.deepEqual(
87
+ m,
88
+ [],
89
+ `a table inside a \`\`\` fence is an EXAMPLE of the format, not a report; the text-based ` +
90
+ `counter counted it, the AST must not. Got: ${JSON.stringify(m)}`,
91
+ );
92
+ cases.push(
93
+ "a table inside a fence → silence (the text-based version got this wrong)",
94
+ );
95
+ }
96
+
97
+ // ── 4. THRESHOLD — a consumer option, not a mechanism constant.
98
+ {
99
+ const strict = new ESLint({
100
+ overrideConfigFile: true,
101
+ overrideConfig: [
102
+ {
103
+ files: ["**/*.md"],
104
+ plugins: { markdown, review: reviewRules },
105
+ language: "markdown/gfm",
106
+ languageOptions: { frontmatter: "yaml" },
107
+ rules: { "review/findings-cause": ["error", { minFindings: 99 }] },
108
+ },
109
+ ],
110
+ });
111
+ const m = (await strict.lintFiles([join(FIX, "defect.md")]))[0].messages;
112
+ assert.deepEqual(
113
+ m,
114
+ [],
115
+ "with a threshold above the finding count the rule must stay silent — the threshold is data",
116
+ );
117
+ cases.push(
118
+ "the threshold is passed as an option → with minFindings: 99, silence on the same file",
119
+ );
120
+ }
121
+
122
+ // ── 5. 🔴 "RULE FROM A DATE": an old report is known debt, not a finding. Without this
123
+ // option the rule would have opened at the first consumer with FORTY-NINE findings
124
+ // (measured: 84 reports, 0 new, 49 old), and a check that opens with a wall gets muted.
125
+ {
126
+ const dated = new ESLint({
127
+ overrideConfigFile: true,
128
+ overrideConfig: [
129
+ {
130
+ files: ["**/*.md"],
131
+ plugins: { markdown, review: reviewRules },
132
+ language: "markdown/gfm",
133
+ languageOptions: { frontmatter: "yaml" },
134
+ rules: {
135
+ "review/findings-cause": ["error", { sinceCreated: "2026-08-23" }],
136
+ },
137
+ },
138
+ ],
139
+ });
140
+ const old = (await dated.lintFiles([join(FIX, "old-debt.md")]))[0].messages;
141
+ assert.deepEqual(
142
+ old,
143
+ [],
144
+ `a report older than the rule's date — debt, not a finding; got: ${JSON.stringify(old)}`,
145
+ );
146
+
147
+ // AND THE SECOND HALF OF THE OPTION: on a fresh report it must NOT exempt anything, or
148
+ // "rule from a date" turns into an off switch.
149
+ const fresh = (await dated.lintFiles([join(FIX, "defect.md")]))[0].messages;
150
+ assert.equal(
151
+ fresh.length,
152
+ 1,
153
+ `a report AFTER the rule's date must be caught; got: ${JSON.stringify(fresh)}`,
154
+ );
155
+ cases.push(
156
+ "a report older than the rule's date → silence; a fresh one of the same shape → a finding",
157
+ );
158
+ }
159
+
160
+ recordCheck(cases.length);
161
+ console.log(`review/findings-cause: ${cases.length} cases:`);
162
+ for (const c of cases) console.log(` ok ${c}`);
163
+
164
+ // ── THE MARKER COMES FROM AN OPTION, IT IS NOT HARDCODED ──────────────────────────────────────
165
+ //
166
+ // The default has been English since 2026-09-17. Before that it was the Russian word
167
+ // "Причина:", and there was no way to change it: `causeMarker` was not threaded through the
168
+ // CLI, so an English-speaking user could not satisfy an `error`-level rule at all. Both halves:
169
+ // a) with a foreign marker in the options, a report using IT passes;
170
+ // b) the same report without the option — a finding. Otherwise "option accepted" is
171
+ // indistinguishable from "the rule stays silent".
172
+ //
173
+ // ⚠️ The fixture's shape is load-bearing: findings are counted by NUMBERED table rows (and
174
+ // bold list items), not by any list. The first draft of this block used a plain `- finding
175
+ // one`, the rule never fired, and BOTH halves passed vacuously.
176
+ {
177
+ const body = [
178
+ "---",
179
+ "created: 2026-09-01",
180
+ "---",
181
+ "",
182
+ "| # | what | where |",
183
+ "|---|---|---|",
184
+ "| 1 | a | §1 |",
185
+ "| 2 | b | §2 |",
186
+ "| 3 | c | §3 |",
187
+ "",
188
+ "Причина: the pipeline step that let them through.",
189
+ "",
190
+ ].join("\n");
191
+
192
+ const lintWith = async (options) => {
193
+ const e = new ESLint({
194
+ overrideConfigFile: true,
195
+ overrideConfig: [
196
+ {
197
+ files: ["**/*.md"],
198
+ plugins: { markdown, review: reviewRules },
199
+ language: "markdown/gfm",
200
+ languageOptions: { frontmatter: "yaml" },
201
+ rules: { "review/findings-cause": ["error", options] },
202
+ },
203
+ ],
204
+ });
205
+ return (
206
+ await e.lintText(body, { filePath: join(FIX, "option-probe.md") })
207
+ )[0].messages;
208
+ };
209
+
210
+ const withOpt = await lintWith({ minFindings: 3, causeMarker: "Причина:" });
211
+ assert.deepEqual(
212
+ withOpt,
213
+ [],
214
+ `the marker from the options must be accepted, got: ${JSON.stringify(withOpt)}`,
215
+ );
216
+
217
+ const withoutOpt = await lintWith({ minFindings: 3 });
218
+ assert.equal(
219
+ withoutOpt.length,
220
+ 1,
221
+ "without the option the same marker is NOT counted as a cause — otherwise the option decides nothing",
222
+ );
223
+ assert.match(
224
+ withoutOpt[0].message,
225
+ /Cause:/,
226
+ "and the finding's text carries the ENGLISH default, not something absent from the file",
227
+ );
228
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * `review/findings-cause` — a review report with findings and no analysis of the ROOT CAUSE.
3
+ *
4
+ * ── WHAT THIS RULE IS ──────────────────────────────────────────────────────────
5
+ * Pipeline convention: a paper review does not end with a list of findings, it ends
6
+ * with an answer to "what in the PIPELINE let this through" — a defective skill, a
7
+ * missing skill, a hook, a rule. Otherwise the paper text gets fixed while the tool
8
+ * that missed it stays the same, and the next paper arrives with the same defect.
9
+ *
10
+ * ── WHY THIS IS A MOVE, NOT A NEW RULE ──────────────────────────────────────────
11
+ * First unit of step 9 of the extraction: until 2026-09-15 the check lived in the
12
+ * consumer as `checkReviewFindingsCause` in `.claude/hooks/paper-lint.mjs` — a
13
+ * directory walk plus three regexes over the text.
14
+ *
15
+ * 🔴 AND THE MOVE HERE IS NOT A COPY, IT REMOVES A WHOLE CLASS OF BUGS. The old
16
+ * version counted findings like this:
17
+ *
18
+ * tableRows ← ^\|\s*\d+\s*\| a line starting with "| <number> |"
19
+ * boldItems ← ^\s*[-*]\s+\*\* a list item starting bold
20
+ *
21
+ * (the regexes are given WITHOUT trailing slashes and flags on purpose: an
22
+ * "asterisk-slash" sequence inside a block comment closes it — I tripped on this
23
+ * twice in one hour, the second time right here.)
24
+ *
25
+ * That is, a "table row" was recognized by how it's written, not by its markup: the
26
+ * same line inside a ``` fence counted as a finding, a leading space in the cell
27
+ * broke the count, and `*` and `-` had to be listed by hand. In the AST a table row
28
+ * is a `tableRow` node, and all three misses become inexpressible. This is exactly
29
+ * the base rule "parse markdown with a PARSER".
30
+ *
31
+ * ── WHAT STAYS AS CONSUMER DATA ───────────────────────────────────────────────
32
+ * The findings threshold is an option. The "rule from a date" (reports older than a
33
+ * given date are known debt, not a finding) DOES NOT MOVE HERE AT ALL: it's a fact
34
+ * about one consumer's corpus, and its home is that consumer's config via `ignores`,
35
+ * not the mechanism. The mechanism goes in the package, the data stays with the consumer.
36
+ */
37
+
38
+ /** Numbered cell: the first column of a table row holding a single number. */
39
+ const isNumbered = (row) => {
40
+ const first = row.children?.[0];
41
+ const text = (first?.children ?? [])
42
+ .map((c) => c.value ?? "")
43
+ .join("")
44
+ .trim();
45
+ return /^\d+$/.test(text);
46
+ };
47
+
48
+ /** Finding item: a list element starting with bold text — the finding's heading. */
49
+ const isBoldItem = (item) => {
50
+ const para = item.children?.find((c) => c.type === "paragraph");
51
+ return para?.children?.[0]?.type === "strong";
52
+ };
53
+
54
+ export default {
55
+ rules: {
56
+ "findings-cause": {
57
+ meta: {
58
+ type: "suggestion",
59
+ docs: {
60
+ description:
61
+ "a review report with findings names what in the pipeline let them through",
62
+ },
63
+ schema: [
64
+ {
65
+ type: "object",
66
+ properties: {
67
+ minFindings: { type: "integer", minimum: 1 },
68
+ causeMarker: { type: "string" },
69
+ sinceCreated: {
70
+ type: "string",
71
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$",
72
+ },
73
+ },
74
+ additionalProperties: false,
75
+ },
76
+ ],
77
+ messages: {
78
+ noCause:
79
+ "{{count}} findings and not one «{{marker}}» note. First name what in the PIPELINE let them through (a defective skill · a missing skill · a hook · a rule) and fix THAT: the text edit falls out of running the fixed tool, not instead of it.",
80
+ },
81
+ },
82
+ create(context) {
83
+ const {
84
+ minFindings = 3,
85
+ causeMarker = "Cause:",
86
+ sinceCreated,
87
+ } = context.options[0] ?? {};
88
+ let findings = 0;
89
+ let hasCause = false;
90
+ let created = "";
91
+
92
+ return {
93
+ // 🔴 "RULE FROM A DATE" — MECHANISM, DATE — DATA. A new check that opens with a
94
+ // wall of findings on a historical corpus gets muted the same day; so the
95
+ // consumer needs a way to say "before such-and-such date this is known debt,
96
+ // not a finding". Measured on the first consumer: 84 reports, 0 new would fire
97
+ // and 49 old — without this option the rule would have opened with forty-nine
98
+ // findings. The date is NOT hardcoded: it comes in as an option, read from
99
+ // `created` in the frontmatter, and a document with no date is treated as OLD
100
+ // only when the option is set — otherwise a missing frontmatter would become a
101
+ // way to dodge the rule.
102
+ yaml(node) {
103
+ created =
104
+ /^created:\s*(\d{4}-\d{2}-\d{2})/m.exec(node.value ?? "")?.[1] ??
105
+ "";
106
+ },
107
+ tableRow(node) {
108
+ if (isNumbered(node)) findings++;
109
+ },
110
+ listItem(node) {
111
+ if (isBoldItem(node)) findings++;
112
+ },
113
+ text(node) {
114
+ if (node.value.includes(causeMarker)) hasCause = true;
115
+ },
116
+ "root:exit"(node) {
117
+ if (hasCause || findings < minFindings) return;
118
+ if (sinceCreated && (!created || created < sinceCreated)) return;
119
+ // A finding about the FILE, not a line: what's missing isn't anywhere. So the
120
+ // position is the start of the document, the only honest place for "this is missing".
121
+ context.report({
122
+ loc: {
123
+ start: { line: 1, column: 0 },
124
+ end: { line: 1, column: 1 },
125
+ },
126
+ messageId: "noCause",
127
+ data: { count: String(findings), marker: causeMarker },
128
+ node,
129
+ });
130
+ },
131
+ };
132
+ },
133
+ },
134
+ },
135
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Battery for `review/findings-cause`. Three mutations, three different asserts: the rule
3
+ * must FIRE, must STAY SILENT when there's a cause analysis, and the threshold must come
4
+ * in as an option.
5
+ */
6
+ import { fileURLToPath } from "node:url";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { runMutations } from "../lib/mutation-driver.mjs";
9
+
10
+ const HERE = dirname(fileURLToPath(import.meta.url));
11
+ const ROOT = resolve(HERE, "..");
12
+ const RULE = join(HERE, "review-findings-cause.mjs");
13
+ const HARNESS = join(HERE, "review-findings-cause.harness.mjs");
14
+
15
+ process.exit(
16
+ runMutations({
17
+ root: ROOT,
18
+ runner: "node",
19
+ cases: [
20
+ {
21
+ name: "the rule stops reporting",
22
+ harness: HARNESS,
23
+ expect: "one finding was expected",
24
+ disables:
25
+ "the verdict itself — a report without a cause analysis passes silently",
26
+ edits: [
27
+ [
28
+ RULE,
29
+ "if (hasCause || findings < minFindings) return;",
30
+ "if (true) return;",
31
+ ],
32
+ ],
33
+ },
34
+ {
35
+ name: 'the "Cause:" marker stops being noticed',
36
+ harness: HARNESS,
37
+ expect: "the rule must stay silent",
38
+ disables:
39
+ "the exemption — a report WITH a cause analysis starts going red",
40
+ edits: [
41
+ [
42
+ RULE,
43
+ "if (node.value.includes(causeMarker)) hasCause = true;",
44
+ "void node;",
45
+ ],
46
+ ],
47
+ },
48
+ {
49
+ name: '"rule from a date" stops exempting',
50
+ harness: HARNESS,
51
+ expect: "debt, not a finding",
52
+ disables:
53
+ "exemption of the historical corpus — the rule opens with a wall of findings",
54
+ edits: [
55
+ [
56
+ RULE,
57
+ "if (sinceCreated && (!created || created < sinceCreated)) return;",
58
+ "if (false) return;",
59
+ ],
60
+ ],
61
+ },
62
+ {
63
+ name: "the threshold stops being an option",
64
+ harness: HARNESS,
65
+ expect: "the threshold is data",
66
+ disables:
67
+ "passing the threshold in from outside — the mechanism claims the data for itself",
68
+ edits: [[RULE, "context.options[0] ?? {}", "{}"]],
69
+ },
70
+ ],
71
+ }),
72
+ );
@@ -0,0 +1,176 @@
1
+ /**
2
+ * `local/temp-root-realpath` — both halves on real ESLint, plus the corpus itself.
3
+ *
4
+ * 🔴 WHY THIS HARNESS EXISTS AT ALL, rather than stopping at the twenty-four fixes. The
5
+ * defect (issue #9) ONLY REPRODUCES ON macOS: there `/var` is a symlink to `/private/var`,
6
+ * and one directory gets two spellings. On Linux `realpathSync` is the identity, so no
7
+ * behavioral test can tell a fixed root apart from a broken one: removing the wrapper leaves
8
+ * the run GREEN. The only check that can even go red on Linux is structural: ask the AST
9
+ * whether the root is resolved at the point it's created. That is exactly why it is written
10
+ * as a linter rule and not an assert inside someone's harness.
11
+ *
12
+ * ⚠️ And a boundary, so the harness isn't mistaken for more: it asserts that the IDIOM is in
13
+ * place, not that macOS is now green. The latter is only checked on macOS, and CI here is one
14
+ * `ubuntu-latest` — recorded as a known limit, not papered over.
15
+ *
16
+ * Fixtures are strings through `lintText`, not files on disk: a defect file under `fixtures/`
17
+ * would fall under the config's own `**\/*.mjs` block and would make `npx eslint .` red on a
18
+ * healthy checkout. This is the same reason the `.tex` fixtures sit on `warn`.
19
+ */
20
+ import assert from "node:assert/strict";
21
+ import { ESLint } from "eslint";
22
+ import { recordCheck } from "vigiles";
23
+ import localRules from "./temp-root-realpath.mjs";
24
+
25
+ const eslint = new ESLint({
26
+ overrideConfigFile: true,
27
+ overrideConfig: [
28
+ {
29
+ files: ["**/*.mjs"],
30
+ languageOptions: { ecmaVersion: 2024, sourceType: "module" },
31
+ plugins: { local: localRules },
32
+ rules: { "local/temp-root-realpath": "error" },
33
+ },
34
+ ],
35
+ });
36
+
37
+ /** @returns {Promise<import("eslint").Linter.LintMessage[]>} */
38
+ const on = async (code) =>
39
+ (await eslint.lintText(code, { filePath: "probe.mjs" }))[0].messages;
40
+
41
+ const HEAD =
42
+ 'import { mkdtempSync, realpathSync } from "node:fs";\n' +
43
+ 'import { tmpdir } from "node:os";\nimport { join } from "node:path";\n';
44
+
45
+ const cases = [];
46
+
47
+ // ── 1. FIRES: a root from `tmpdir()` with no resolve — exactly the shape from issue #9.
48
+ {
49
+ const m = await on(
50
+ `${HEAD}const TMP = mkdtempSync(join(tmpdir(), "probe-"));\n`,
51
+ );
52
+ assert.equal(
53
+ m.length,
54
+ 1,
55
+ `one finding was expected, got ${m.length}: ${JSON.stringify(m)}`,
56
+ );
57
+ assert.equal(m[0].ruleId, "local/temp-root-realpath");
58
+ assert.match(
59
+ m[0].message,
60
+ /realpathSync\(mkdtempSync/,
61
+ "the message must carry the FIX, not just the diagnosis",
62
+ );
63
+ assert.match(
64
+ m[0].message,
65
+ /private\/var/,
66
+ "and name the cause — why one directory gets two names",
67
+ );
68
+ cases.push(
69
+ "mkdtempSync(join(tmpdir(), …)) with no resolve → a finding, the fix is in the message",
70
+ );
71
+ }
72
+
73
+ // ── 2. STAYS SILENT on the fixed form. Without this half the rule is indistinguishable
74
+ // from one that screams at every `mkdtempSync`.
75
+ {
76
+ const m = await on(
77
+ `${HEAD}const TMP = realpathSync(mkdtempSync(join(tmpdir(), "probe-")));\n`,
78
+ );
79
+ assert.deepEqual(
80
+ m,
81
+ [],
82
+ `the rule must stay silent on a resolved root, got: ${JSON.stringify(m)}`,
83
+ );
84
+ cases.push("realpathSync(mkdtempSync(join(tmpdir(), …))) → silence");
85
+ }
86
+
87
+ // ── 3. 🔴 STAYS SILENT ON A NESTED ROOT, and this is load-bearing, not a concession. The
88
+ // corpus has five of these (`mkdtempSync(join(TMP, "repo-"))`); they inherit their
89
+ // spelling from `TMP`, which is caught at ITS OWN spot. A rule that screamed here too
90
+ // would demand a double resolve — and for an `error`-level rule a false positive is
91
+ // worse than a miss: it gets turned off.
92
+ {
93
+ const m = await on(
94
+ `${HEAD}const TMP = realpathSync(mkdtempSync(join(tmpdir(), "probe-")));\n` +
95
+ `const sub = mkdtempSync(join(TMP, "repo-"));\nvoid sub;\n`,
96
+ );
97
+ assert.deepEqual(
98
+ m,
99
+ [],
100
+ `a nested root inherits its spelling from the parent; got: ${JSON.stringify(m)}`,
101
+ );
102
+ cases.push(
103
+ "a nested mkdtempSync(join(TMP, …)) → silence (the parent is already resolved)",
104
+ );
105
+ }
106
+
107
+ // ── 4. A COMMENT AND A STRING ARE NOT A CALL. This is exactly why the rule parses the AST: a
108
+ // text-based guard looking for "mkdtempSync(join(tmpdir()" would find this very file and
109
+ // would have to exclude itself — the class of check string-based checks are banned for here.
110
+ {
111
+ const m = await on(
112
+ `${HEAD}// mkdtempSync(join(tmpdir(), "in-a-comment-"))\n` +
113
+ `const doc = 'mkdtempSync(join(tmpdir(), "in-a-string-"))';\nvoid doc;\n`,
114
+ );
115
+ assert.deepEqual(
116
+ m,
117
+ [],
118
+ `text ABOUT a call is not a call; got: ${JSON.stringify(m)}`,
119
+ );
120
+ cases.push("the same sequence in a comment and a string → silence");
121
+ }
122
+
123
+ // ── 5. `fs.mkdtempSync` / `os.tmpdir()` through a namespace — the same thing under a
124
+ // different spelling.
125
+ {
126
+ const ns =
127
+ 'import * as fs from "node:fs";\nimport * as os from "node:os";\nimport { join } from "node:path";\n';
128
+ const bad = await on(
129
+ `${ns}const TMP = fs.mkdtempSync(join(os.tmpdir(), "probe-"));\n`,
130
+ );
131
+ assert.equal(
132
+ bad.length,
133
+ 1,
134
+ `the namespaced spelling must be caught; got: ${JSON.stringify(bad)}`,
135
+ );
136
+ const good = await on(
137
+ `${ns}const TMP = fs.realpathSync(fs.mkdtempSync(join(os.tmpdir(), "probe-")));\n`,
138
+ );
139
+ assert.deepEqual(
140
+ good,
141
+ [],
142
+ `and it must be exempted too; got: ${JSON.stringify(good)}`,
143
+ );
144
+ cases.push(
145
+ "fs.mkdtempSync(join(os.tmpdir(), …)) is caught, fs.realpathSync(…) exempts it",
146
+ );
147
+ }
148
+
149
+ // ── 6. 🔴 THE CORPUS ITSELF IS CLEAN. The "silent" half on a made-up string says nothing
150
+ // about the repository: the rule could also be silent because it was never handed a
151
+ // single file (the recorded class — `scripts/rules-see-files.mjs`). So this check runs
152
+ // THE RULE OVER THE REAL TREE and requires many files, zero findings.
153
+ {
154
+ const corpus = new ESLint({ cwd: new URL("..", import.meta.url).pathname });
155
+ const results = await corpus.lintFiles(["."]);
156
+ const hits = results.flatMap((r) =>
157
+ r.messages
158
+ .filter((msg) => msg.ruleId === "local/temp-root-realpath")
159
+ .map((msg) => `${r.filePath}:${msg.line}`),
160
+ );
161
+ assert.deepEqual(
162
+ hits,
163
+ [],
164
+ `the corpus must be clean of unresolved roots:\n${hits.join("\n")}`,
165
+ );
166
+ const linted = results.filter((r) => r.filePath.endsWith(".mjs")).length;
167
+ assert.ok(
168
+ linted > 50,
169
+ `the rule was only handed ${linted} .mjs files — zero findings on an empty input is NOT cleanliness`,
170
+ );
171
+ cases.push(`corpus: ${linted} .mjs files, zero unresolved roots`);
172
+ }
173
+
174
+ recordCheck(cases.length);
175
+ console.log(`local/temp-root-realpath: ${cases.length} cases:`);
176
+ for (const c of cases) console.log(` ok ${c}`);