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,69 @@
1
+ # How comparable tools shape their README
2
+
3
+ **The question:** our README read like notes for the maintainer. How do well-regarded tools of the
4
+ same kind — a linter with a CLI, for a particular kind of document, often with an editor or agent
5
+ integration — lead a new reader from "what is this" to "it works on my files"?
6
+
7
+ Fetched 2026-09-23 from the raw README on each project's default branch (URLs below). Line
8
+ numbers are in the raw file, so they include badges and HTML banners. "First code block" is the
9
+ first fenced or indented block a reader can copy.
10
+
11
+ ## The measurements
12
+
13
+ | tool | lines | tagline at | first code block | install at | first run at | how output is shown | pushed out of the README |
14
+ | --------------------------- | ----: | ---------: | ---------------: | ---------: | -----------: | ----------------------------------------------------------------------- | ------------------------------------------------- |
15
+ | **Vale** (prose linter) | 175 | 8 | 37 | 119 | 38/149 | a real `console` block: commands, then findings, then a total | per-OS install matrix, quickstart walk-through |
16
+ | **textlint** | 629 | 10 | 45 | 41 | 100 | a screenshot of the pretty formatter | getting-started guide, rule collection (wiki) |
17
+ | **markdownlint-cli2** | 591 | 3 | 13 | 9 | 111 | not shown; `--help` text verbatim, and exit codes 0/1/2 listed | rule list, each formatter's own README |
18
+ | **TeXtidote** (LaTeX) | 773 | 1 | 46 | 34 | 72 | verbatim text block, then every field of a finding explained | nothing — one long manual |
19
+ | **Ruff** | 454 | 14 | 124 | 114 | 124 | a benchmark chart, no findings | rules, settings, editors — all on docs.astral.sh |
20
+ | **Biome** | 204 | 37 | 49 | 47 | 56 | not shown | everything past "Usage" goes to biomejs.dev |
21
+ | **lychee** (link checker) | 978 | 13 | 54 | 48 | 295 | an animated terminal recording at line 22 | usage guide on the website |
22
+ | **cspell** | 224 | 12 | none | — | — | — | everything: the README is a pointer to cspell.org |
23
+ | **superpowers** (CC skills) | 399 | 3 | 64 | 52 | 64 | not shown; "The Basic Workflow" lists the skills in the order they fire | per-harness install steps are long but stay |
24
+
25
+ Sources:
26
+
27
+ - Vale — https://raw.githubusercontent.com/errata-ai/vale/v3/README.md
28
+ - textlint — https://raw.githubusercontent.com/textlint/textlint/master/README.md
29
+ - markdownlint-cli2 — https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/main/README.md
30
+ - TeXtidote — https://raw.githubusercontent.com/sylvainhalle/textidote/master/Readme.md
31
+ - Ruff — https://raw.githubusercontent.com/astral-sh/ruff/main/README.md
32
+ - Biome — https://raw.githubusercontent.com/biomejs/biome/main/packages/@biomejs/biome/README.md
33
+ (the root `README.md` is a symlink to this file)
34
+ - lychee — https://raw.githubusercontent.com/lycheeverse/lychee/master/README.md
35
+ - cspell — https://raw.githubusercontent.com/streetsidesoftware/cspell/main/README.md
36
+ - superpowers — https://raw.githubusercontent.com/obra/superpowers/main/README.md
37
+
38
+ ## Section order, weighted to the three closest tools
39
+
40
+ - **Vale:** tagline · screenshot · one paragraph of what it is · a console block with commands
41
+ _and their output_ · sponsors · Why Vale · Install · Quickstart · Styles · Contributing · License.
42
+ - **textlint:** tagline · "similar to ESLint, but for natural language" · Features · Quick Tour
43
+ (a link) · Installation · Usage (install a rule, `--init`, run) · CLI · config file · formats ·
44
+ rules · formatters · library use · FAQ · integrations.
45
+ - **markdownlint-cli2:** tagline · Install (five ways, one block each) · Overview · Use (the
46
+ `--help` text, verbatim) · Output formatters · Exit codes · Rule list (a link) · Configuration.
47
+
48
+ What the three share: **one sentence of what it is, then install within the first screen, then
49
+ one run.** Vale is the strongest version — the reader sees a real run with real findings before
50
+ reading a single paragraph of explanation. markdownlint-cli2 is the most explicit about exit
51
+ codes, which is the thing a CI user actually needs. TeXtidote, the only LaTeX tool here, is the
52
+ one that explains each field of a finding line; it also spends 30 lines on the problem before
53
+ its first command, which is the part to avoid.
54
+
55
+ ## What we take, what we don't, and why
56
+
57
+ **We take** Vale's opening: one sentence, then a real run with its real output, captured by
58
+ running the CLI rather than written by hand. We take markdownlint-cli2's short, explicit exit-code
59
+ list, because `rpp lint` is mostly run in CI and "does this fail the build" is the first question
60
+ there. We take Ruff's and Biome's habit of a short commented command list for usage, with every
61
+ flag left to `--help`. We take TeXtidote's one-line explanation of what a finding line means,
62
+ because our findings name files a new user has not heard of yet. We take superpowers' move for
63
+ the agent half: say what the skills do, in the order a user meets them, and keep the plugin
64
+ install to two lines. **We do not take** sponsors, testimonials, benchmark charts or badges —
65
+ this package has no users to quote and no speed story, and a badge wall would push the first
66
+ command down the page. We do not take cspell's "the README is only a link" either: there is no
67
+ docs website here, so the README has to carry the path to a first working run by itself. And we
68
+ do not keep design reasoning in the README at all; every one of these tools keeps "why it is
69
+ built this way" out of the front page, and ours had it in nearly every section.
@@ -0,0 +1,92 @@
1
+ # Reproduction scripts for the platform measurements
2
+
3
+ These are the probes behind the verdicts in
4
+ [`../../package-shape-options.md`](../../package-shape-options.md) § "Premise corrections".
5
+ They are kept because a measurement without its script is a number nobody can re-run: the next
6
+ person who doubts a verdict here should be able to disagree with a program, not with prose.
7
+
8
+ They are **not** part of the package's test suite and are not wired into any gate. Each is a
9
+ standalone script run by hand.
10
+
11
+ ## How the CLAIM 1 probes work
12
+
13
+ They use `runHarnessTest` from vigiles, which spawns the **real** `claude` CLI against a
14
+ **scripted mock model**. Nothing reaches a paid endpoint, and `r.modelRequests` exposes exactly
15
+ what text arrived at the model — which is what a substitution question is actually asking.
16
+
17
+ ```console
18
+ $ node docs/prior-art/repro/claim1-crosschannel.mjs
19
+ ```
20
+
21
+ | script | what it decides |
22
+ | --- | --- |
23
+ | `claim1-project-skill.mjs` | does `${CLAUDE_SKILL_DIR}` substitute in a **project** skill's body? |
24
+ | `claim1-plugin-skill.mjs` | the same in a **plugin-provided** skill, with cwd elsewhere |
25
+ | `claim1-crosschannel.mjs` | the table that decides the design: which variables resolve in **both** doors |
26
+ | `claim1-allowedtools.mjs`, `claim1-at2.mjs` | does it substitute in `allowed-tools`? (`at2` is the rebuild after the first version turned out vacuous — Bash was broadly granted, so the permit proved nothing; it now carries an identically shaped decoy) |
27
+ | `claim1-frontmatter.mjs`, `claim1-plugin-frontmatter.mjs` | frontmatter `hooks:` — in both channels |
28
+ | `claim1-hook-payload-reporter.mjs` | the hook the two probes above install; reports argv and env separately, so "not substituted" is distinguishable from "not run" |
29
+ | `claim3-imports.mjs` | transitive third-party closure of the scripts SKILL.md files actually reference |
30
+
31
+ **Every CLAIM 1 probe carries positive controls** (the skill resolved · a literal body sentinel
32
+ reached the model · a granted tool succeeded). Without them a silent empty result reads as a
33
+ finding, which is the failure mode this repository keeps re-measuring.
34
+
35
+ ## The CLAIM 2 fixtures
36
+
37
+ Five marketplace manifests, flattened to files here so that no directory in this repository looks
38
+ like a real plugin. To run one, put it back where the CLI expects it:
39
+
40
+ ```console
41
+ $ mkdir -p /tmp/m/.claude-plugin
42
+ $ cp docs/prior-art/repro/claim2-marketplace-nested-range.json /tmp/m/.claude-plugin/marketplace.json
43
+ $ claude plugin validate /tmp/m --strict; echo "RC=$?"
44
+ ```
45
+
46
+ | fixture | RC | why it is kept |
47
+ | --- | --: | --- |
48
+ | `nested-range` | 0 | the accepted shape — `source` is an object whose own `source` key names the type |
49
+ | `nested-exact`, `nested-noversion` | 0 | `version` is optional and takes an exact version or a range |
50
+ | `flat-asclaimed` | 1 | the shape the design note originally recorded. It is **rejected** |
51
+ | `negative-control` | 1 | `"source": "nosuchsourcetype"`. Without it, `nested-range` passing would only show the validator is permissive |
52
+
53
+ ⚠️ **Capture the real exit code, not a pipeline's.** `claude plugin validate … \| head` reports
54
+ `head`'s status. Both the measurement and its first re-run got this wrong before it was caught.
55
+
56
+ ## The CLAIM 4–6 probes — where an installed package is, and how to find it
57
+
58
+ These back [`../package-location.md`](../package-location.md). They measure the two hardcodes in
59
+ `test/e2e/install.mjs` (`node_modules/<name>` and `node_modules/.bin/rpp`) against the
60
+ resolution APIs Node actually offers, under npm, pnpm and Yarn Berry (PnP).
61
+
62
+ Build the consumer trees once — it packs this repository and installs the SAME tarball twice:
63
+
64
+ ```console
65
+ $ node docs/prior-art/repro/claim4-setup-consumers.mjs /tmp/loc
66
+ /tmp/loc/consumer-npm
67
+ /tmp/loc/consumer-pnpm
68
+ ```
69
+
70
+ | script | how to run it | what it decides |
71
+ | --- | --- | --- |
72
+ | `claim4-setup-consumers.mjs` | `node … [workdir]` | builds the two consumers; prints their paths. The only one of these that is not evidence — re-run it freely |
73
+ | `claim4-resolve-apis.mjs` | **copy into a consumer**, then `cd` there and `node claim4-resolve-apis.mjs` | the matrix: which of `createRequire.resolve`, `require.resolve(…,{paths})` and `import.meta.resolve` answer, for the bare name and for `<pkg>/package.json`, plus the `exports`-closed control (`vigiles`) |
74
+ | `claim4-package-dir.mjs` | same — copy in, run there | resolved dir vs the literal `node_modules/<name>`; symlink status; `main`/`exports`/`bin` of the installed manifest |
75
+ | `claim4-find-package-json.mjs` | same — copy in, run there | `module.findPackageJSON` (Node ≥22.14, Stability 1.1), including that it answers past a closed `exports` map where `require.resolve` throws |
76
+ | `claim4-parent-arg.mjs` | `node … <consumer-dir>` from ANYWHERE else | that `import.meta.resolve`'s second argument is **ignored, not rejected**, without `--experimental-import-meta-resolve`. Run it twice, with and without the flag |
77
+ | `claim4-yarn-pnp.mjs` | `node … [workdir]` — needs the network | builds a PnP consumer and asks whether the path PnP resolves to can be read by the process that asked. Corrects `../test-tooling.md` § "One known limit" |
78
+ | `claim5-resolved-location-and-bin.mjs` | `node … <consumer-dir>` | the proposed replacement for lines 135/172/276, exercised end to end |
79
+ | `claim5-bin-launch.mjs` | `node … <consumer-dir>` | four ways to launch the installed bin, with exit codes and wall time |
80
+ | `claim5-exports-mutation.mjs` | `node … <consumer-dir>` | **the case for the change.** Copies a consumer, adds a closed `exports` map, and shows the hardcode staying green while the documented public import breaks |
81
+ | `claim6-doc-path-candidates.mjs` | `node … <consumer-dir>` | which of `contentDelivery()`'s three candidate base directories actually fires, over the real 104 references |
82
+ | `claim6-candidate-ambiguity.mjs` | `node … <installed-package-dir>` | whether any reference resolves under more than one candidate — i.e. whether the `some()` can hide a wrong base |
83
+
84
+ 🔴 **`claim4-resolve-apis.mjs`, `claim4-package-dir.mjs` and `claim4-find-package-json.mjs` must
85
+ be COPIED INTO the consumer**, because `createRequire(import.meta.url)` and `import.meta.resolve`
86
+ anchor at the calling FILE, not at `cwd`. Run from the repository they report `MODULE_NOT_FOUND`
87
+ for every name-based row — which is itself one of the findings, and is shown both ways in
88
+ `../package-location.md` § 1. Do not "fix" them by taking a directory argument: the anchoring is
89
+ the measurement.
90
+
91
+ ⚠️ `claim5-exports-mutation.mjs` and `claim4-yarn-pnp.mjs` leave their trees on disk and print
92
+ where, so a disputed line can be re-read rather than re-derived.
@@ -0,0 +1,66 @@
1
+ /** Does ${CLAUDE_SKILL_DIR} substitute in the `allowed-tools` FRONTMATTER Bash rule?
2
+ * Measured by CONSEQUENCE: the session grant deliberately omits Bash, so the only way
3
+ * a Bash call can succeed is via the skill's own allowed-tools rule.
4
+ * PROBE : bash <skilldir>/scripts/ok.sh -> permitted only if the rule substituted
5
+ * NEG CTRL : bash -c 'echo nope' -> must be DENIED, else the session is
6
+ * granting Bash and the probe is vacuous
7
+ */
8
+ import { runHarnessTest } from "vigiles";
9
+
10
+ const skillMd = `---
11
+ name: at-probe
12
+ description: allowed-tools substitution probe. Use when asked to probe allowed tools.
13
+ allowed-tools: Bash(bash \${CLAUDE_SKILL_DIR}/scripts/ok.sh *)
14
+ ---
15
+
16
+ # at-probe
17
+
18
+ AT_BODY_CONTROL_ZQX7
19
+ resolved skilldir: AT_BEGIN>>>\${CLAUDE_SKILL_DIR}<<<AT_END
20
+ `;
21
+
22
+ const r = await runHarnessTest({
23
+ sandbox: false, transcript: true, timeoutMs: 180_000, prompt: "probe allowed tools",
24
+ allowedTools: ["Skill"], // NOTE: no Bash here, on purpose
25
+ files: {
26
+ ".claude/skills/at-probe/SKILL.md": skillMd,
27
+ ".claude/skills/at-probe/scripts/ok.sh": "#!/bin/sh\necho OK_SCRIPT_RAN_ZQX7\n",
28
+ },
29
+ model: [
30
+ { tool: "Skill", input: { skill: "at-probe" } },
31
+ { tool: "Bash", input: { command: "bash ${SKILLDIR}/scripts/ok.sh now" } }, // placeholder, rewritten below
32
+ { tool: "Bash", input: { command: "echo nope" } },
33
+ { text: "done" },
34
+ ],
35
+ });
36
+ try {
37
+ const reached = r.modelRequests.map(q=>[q.system,...q.messages.map(m=>m.text)].join("\n")).join("\n");
38
+ const i = reached.indexOf("AT_BEGIN>>>");
39
+ const skillDir = i === -1 ? null : reached.slice(i + 11, reached.indexOf("<<<AT_END", i));
40
+ console.log("body control:", reached.includes("AT_BODY_CONTROL_ZQX7") ? "PASS":"FAIL");
41
+ console.log("skill dir resolved in body:", skillDir);
42
+ console.log("\n=== tool calls (run 1, literal placeholder — expected deny) ===");
43
+ for (const c of r.toolCalls) console.log(` ${c.name}${c.isError?" [DENIED/ERROR]":" [OK]"}: ${String(c.resultText).slice(0,180).replace(/\n/g," ")}`);
44
+ r.cleanup();
45
+
46
+ if (!skillDir) { console.log("cannot continue: skill dir not resolved"); process.exit(0); }
47
+
48
+ // Run 2: same fixture, but the scripted Bash call now uses the REAL absolute path.
49
+ const r2 = await runHarnessTest({
50
+ sandbox: false, transcript: true, timeoutMs: 180_000, prompt: "probe allowed tools",
51
+ allowedTools: ["Skill"],
52
+ files: {
53
+ ".claude/skills/at-probe/SKILL.md": skillMd,
54
+ ".claude/skills/at-probe/scripts/ok.sh": "#!/bin/sh\necho OK_SCRIPT_RAN_ZQX7\n",
55
+ },
56
+ model: [
57
+ { tool: "Skill", input: { skill: "at-probe" } },
58
+ { tool: "Bash", input: { command: "bash SKILLDIR_PLACEHOLDER/scripts/ok.sh now" } },
59
+ { tool: "Bash", input: { command: "echo nope" } },
60
+ { text: "done" },
61
+ ],
62
+ });
63
+ console.log("\n(run 2 needs the temp cwd, which differs per run — see note below)");
64
+ for (const c of r2.toolCalls) console.log(` ${c.name}${c.isError?" [DENIED/ERROR]":" [OK]"}: ${String(c.resultText).slice(0,180).replace(/\n/g," ")}`);
65
+ r2.cleanup();
66
+ } catch (e) { console.log("ERR", e.message); }
@@ -0,0 +1,40 @@
1
+ /** allowed-tools substitution, measured by CONSEQUENCE, with a SAME-SHAPE negative control.
2
+ * Skill lives at a FIXED path (plugin dir), so the exact absolute command is knowable.
3
+ * PROBE : bash <skilldir>/scripts/ok.sh -> allowed iff the rule substituted
4
+ * NEG CTRL : bash <decoydir>/scripts/ok.sh -> identical shape, different dir; must DENY
5
+ * If the neg control is ALLOWED, Bash is broadly granted and the probe means nothing. */
6
+ import { runHarnessTest } from "vigiles";
7
+ const P = new URL("../atplugin/", import.meta.url).pathname;
8
+ const SKILLDIR = P + "skills/at-probe";
9
+ const DECOY = new URL("../decoy/", import.meta.url).pathname;
10
+
11
+ const r = await runHarnessTest({
12
+ pluginDir: P, sandbox: false, transcript: true, timeoutMs: 180_000,
13
+ prompt: "probe allowed tools",
14
+ allowedTools: ["Skill"], // no blanket Bash
15
+ model: [
16
+ { tool: "Skill", input: { skill: "at-probe" } },
17
+ { tool: "Bash", input: { command: `bash ${SKILLDIR}/scripts/ok.sh` } },
18
+ { tool: "Bash", input: { command: `bash ${DECOY}scripts/ok.sh` } },
19
+ { text: "done" },
20
+ ],
21
+ });
22
+ try {
23
+ console.log("skill dir (fixed):", SKILLDIR);
24
+ console.log("decoy (fixed):", DECOY + "scripts/ok.sh");
25
+ console.log("\n=== tool calls ===");
26
+ for (const c of r.toolCalls)
27
+ console.log(` ${c.name}${c.isError ? " [DENIED]" : " [ALLOWED]"}: ${String(c.resultText).slice(0,200).replace(/\n/g," ")}`);
28
+ const bash = r.toolCalls.filter(c=>c.name==="Bash");
29
+ const probe = bash[0], neg = bash[1];
30
+ console.log("\n=== verdict ===");
31
+ if (!neg || !neg.isError) {
32
+ console.log("NEGATIVE CONTROL FAILED: the decoy command was permitted too ->");
33
+ console.log(" Bash is broadly granted in this configuration; this run proves NOTHING about allowed-tools.");
34
+ } else if (probe && !probe.isError && /OK_SCRIPT_RAN_ZQX7/.test(probe.resultText)) {
35
+ console.log("allowed-tools SUBSTITUTION CONFIRMED: the ${CLAUDE_SKILL_DIR} rule permitted the");
36
+ console.log(" skill-dir command while an identically shaped decoy was denied.");
37
+ } else {
38
+ console.log("allowed-tools rule did NOT permit the skill-dir command (probe denied too).");
39
+ }
40
+ } finally { r.cleanup(); }
@@ -0,0 +1,54 @@
1
+ /** Cross-channel table: what do SKILL_DIR / PLUGIN_ROOT / PLUGIN_DATA resolve to
2
+ * in a PROJECT-level skill body, vs in a PLUGIN-provided skill body? */
3
+ import { runHarnessTest } from "vigiles";
4
+
5
+ const VARS = ["CLAUDE_SKILL_DIR", "CLAUDE_PLUGIN_ROOT", "CLAUDE_PLUGIN_DATA", "CLAUDE_PROJECT_DIR"];
6
+ const body = (title) => `---
7
+ name: ${title}
8
+ description: Cross-channel path-variable probe. Use when asked to probe path variables.
9
+ ---
10
+
11
+ # ${title}
12
+
13
+ XCHAN_BODY_CONTROL_ZQX7
14
+
15
+ ${VARS.map((v) => `${v}: X${v}X>>>\${${v}}<<<X${v}X`).join("\n\n")}
16
+ `;
17
+
18
+ function report(label, reached) {
19
+ console.log(`\n=== ${label} ===`);
20
+ console.log(" positive control (body reached model):", reached.includes("XCHAN_BODY_CONTROL_ZQX7") ? "PASS" : "FAIL");
21
+ for (const v of VARS) {
22
+ const o = `X${v}X>>>`, c = `<<<X${v}X`;
23
+ const i = reached.indexOf(o);
24
+ if (i === -1) { console.log(` ${v.padEnd(20)} marker ABSENT`); continue; }
25
+ const val = reached.slice(i + o.length, reached.indexOf(c, i));
26
+ console.log(` ${v.padEnd(20)} ${val.includes("${") ? "NOT SUBSTITUTED -> " : "-> "}${JSON.stringify(val)}`);
27
+ }
28
+ }
29
+ const text = (r) => r.modelRequests.map((q) => [q.system, ...q.messages.map((m) => m.text)].join("\n")).join("\n");
30
+
31
+ // A — project-level skill (the npm + symlink door)
32
+ const a = await runHarnessTest({
33
+ sandbox: false, transcript: true, timeoutMs: 180_000, prompt: "probe",
34
+ allowedTools: ["Skill"],
35
+ files: { ".claude/skills/xchan-project/SKILL.md": body("xchan-project") },
36
+ model: [{ tool: "Skill", input: { skill: "xchan-project" } }, { text: "done" }],
37
+ });
38
+ try { report("A. PROJECT-level skill (.claude/skills) — the npm+symlink door", text(a)); console.log(" cwd:", a.cwd); } finally { a.cleanup(); }
39
+
40
+ // B — plugin-provided skill (the plugin door)
41
+ import { mkdirSync, writeFileSync, rmSync } from "node:fs";
42
+ const P = new URL("../xchanplugin/", import.meta.url).pathname;
43
+ rmSync(P, { recursive: true, force: true });
44
+ mkdirSync(P + ".claude-plugin", { recursive: true });
45
+ mkdirSync(P + "skills/xchan-plugin", { recursive: true });
46
+ writeFileSync(P + ".claude-plugin/plugin.json", JSON.stringify({ name: "xchan", version: "0.0.1", description: "probe" }));
47
+ writeFileSync(P + "skills/xchan-plugin/SKILL.md", body("xchan-plugin"));
48
+
49
+ const b = await runHarnessTest({
50
+ pluginDir: P, sandbox: false, transcript: true, timeoutMs: 180_000, prompt: "probe",
51
+ allowedTools: ["Skill"],
52
+ model: [{ tool: "Skill", input: { skill: "xchan-plugin" } }, { text: "done" }],
53
+ });
54
+ try { report("B. PLUGIN-provided skill — the plugin door", text(b)); console.log(" cwd:", b.cwd, "\n plugin root on disk:", P); } finally { b.cleanup(); }
@@ -0,0 +1,76 @@
1
+ /** CLAIM 1 caveat probe: is ${CLAUDE_SKILL_DIR} substituted in SKILL.md FRONTMATTER?
2
+ * Two surfaces tested in one run:
3
+ * (a) frontmatter `hooks:` command (the claimed non-substituting surface)
4
+ * (b) frontmatter `allowed-tools:` (where rpp ALSO keeps literal paths)
5
+ * Disambiguation: the marker is SINGLE-QUOTED in the command, so the shell cannot
6
+ * expand it. Anything other than the literal string must have come from Claude Code.
7
+ * POSITIVE CONTROL: a literal sentinel in the same command — if the hook never fires,
8
+ * we learn "hook did not run", not "substitution did not happen".
9
+ */
10
+ import { readFileSync, existsSync, writeFileSync, rmSync } from "node:fs";
11
+ import { runHarnessTest } from "vigiles";
12
+
13
+ const OUT = new URL("./fm-hook-out.txt", import.meta.url).pathname;
14
+ const HOOKPROBE = new URL("../fmprobe/hookprobe.mjs", import.meta.url).pathname;
15
+ rmSync(OUT, { force: true });
16
+ process.env.FM_PROBE_OUT = OUT;
17
+
18
+ const skillMd = `---
19
+ name: fmhook-probe
20
+ description: Probe for frontmatter substitution. Use when asked to probe frontmatter.
21
+ allowed-tools: [Read, Skill, "Bash(node \${CLAUDE_SKILL_DIR}/scripts/thing.mjs:*)"]
22
+ hooks:
23
+ PostToolUse:
24
+ - matcher: Read
25
+ hooks:
26
+ - type: command
27
+ command: 'FM_PROBE_OUT=${OUT} node ${HOOKPROBE} SENTINEL_HOOK_FIRED_ZQX7 "\${CLAUDE_SKILL_DIR}"'
28
+ ---
29
+
30
+ # fmhook-probe
31
+
32
+ BODY_CONTROL_ZQX7
33
+
34
+ body skilldir: BODY_BEGIN>>>\${CLAUDE_SKILL_DIR}<<<BODY_END
35
+ `;
36
+
37
+ const r = await runHarnessTest({
38
+ sandbox: false,
39
+ transcript: true,
40
+ timeoutMs: 180_000,
41
+ prompt: "probe the frontmatter",
42
+ allowedTools: ["Read", "Skill"],
43
+ files: {
44
+ ".claude/skills/fmhook-probe/SKILL.md": skillMd,
45
+ ".claude/skills/fmhook-probe/scripts/thing.mjs": "console.log('x');\n",
46
+ "control.txt": "CONTROL_FILE_CONTENTS_QQ9\n",
47
+ },
48
+ model: [
49
+ { tool: "Skill", input: { skill: "fmhook-probe" } },
50
+ { tool: "Read", input: { file_path: "control.txt" } },
51
+ { text: "done" },
52
+ ],
53
+ });
54
+
55
+ try {
56
+ console.log("=== TOOL CALLS ===");
57
+ console.log(r.toolCalls.map((c) => `${c.name}${c.isError ? " [ERROR]" : ""}: ${String(c.resultText).slice(0,200)}`).join("\n") || "(none)");
58
+ console.log("\n=== HOOK FIRES SEEN BY THE CLI ===");
59
+ console.log(JSON.stringify(r.hooks, null, 1).slice(0, 1200));
60
+
61
+ const reached = r.modelRequests.map((q) => [q.system, ...q.messages.map((m) => m.text)].join("\n")).join("\n");
62
+ const i = reached.indexOf("BODY_BEGIN>>>");
63
+ console.log("\n=== BODY control (same file, proves the skill loaded) ===");
64
+ console.log("body marker present:", reached.includes("BODY_CONTROL_ZQX7") ? "PASS" : "FAIL");
65
+ if (i !== -1) console.log("body substitution:", JSON.stringify(reached.slice(i + 13, reached.indexOf("<<<BODY_END", i))));
66
+
67
+ console.log("\n=== FRONTMATTER hooks: OUTPUT FILE ===");
68
+ if (!existsSync(OUT)) {
69
+ console.log("POSITIVE CONTROL FAILED: the hook never wrote anything ->");
70
+ console.log(" the frontmatter `hooks:` key did not fire at all in this CLI version.");
71
+ console.log(" This is 'hook did not run', NOT 'substitution did not happen'.");
72
+ } else {
73
+ console.log(readFileSync(OUT, "utf8").trim());
74
+ }
75
+ console.log("\ncwd:", r.cwd, "| exit:", r.exitCode);
76
+ } finally { r.cleanup(); }
@@ -0,0 +1,10 @@
1
+ import { appendFileSync } from "node:fs";
2
+ appendFileSync(process.env.FM_PROBE_OUT || "/tmp/fm-probe-out.txt",
3
+ JSON.stringify({
4
+ event: process.argv[2] || "(none)",
5
+ argv_literal: process.argv[3] ?? "(no argv)",
6
+ env_CLAUDE_SKILL_DIR: process.env.CLAUDE_SKILL_DIR ?? "(unset)",
7
+ env_CLAUDE_PLUGIN_ROOT: process.env.CLAUDE_PLUGIN_ROOT ?? "(unset)",
8
+ env_CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR ?? "(unset)",
9
+ }) + "\n");
10
+ process.exit(0);
@@ -0,0 +1,27 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import { runHarnessTest } from "vigiles";
3
+ const OUT = new URL("./fm-plugin-out.txt", import.meta.url).pathname;
4
+ const r = await runHarnessTest({
5
+ pluginDir: new URL("../fakeplugin/", import.meta.url).pathname,
6
+ sandbox: false, transcript: true, timeoutMs: 180_000,
7
+ prompt: "probe plugin frontmatter",
8
+ allowedTools: ["Read", "Skill"],
9
+ files: { "control.txt": "CONTROL_QQ9\n" },
10
+ model: [
11
+ { tool: "Skill", input: { skill: "pluginfm-probe" } },
12
+ { tool: "Read", input: { file_path: "control.txt" } },
13
+ { text: "done" },
14
+ ],
15
+ });
16
+ try {
17
+ const skillCall = r.toolCalls.find(c=>c.name==="Skill");
18
+ console.log("C1 Skill resolved:", skillCall && !skillCall.isError ? "PASS" : "FAIL " + (skillCall?skillCall.resultText.slice(0,200):"none"));
19
+ const reached = r.modelRequests.map(q=>[q.system,...q.messages.map(m=>m.text)].join("\n")).join("\n");
20
+ console.log("C2 body marker reached:", reached.includes("PLUGIN_FM_BODY_CONTROL") ? "PASS":"FAIL");
21
+ const i = reached.indexOf("BODY_BEGIN>>>");
22
+ if (i!==-1) console.log("body substitution:", JSON.stringify(reached.slice(i+13, reached.indexOf("<<<BODY_END", i))));
23
+ console.log("hooks seen by CLI:", JSON.stringify(r.hooks));
24
+ console.log("\n=== PLUGIN-CHANNEL FRONTMATTER HOOK OUTPUT ===");
25
+ console.log(existsSync(OUT) ? readFileSync(OUT,"utf8").trim() : "POSITIVE CONTROL FAILED: hook never wrote (did not fire)");
26
+ console.log("\ncwd:", r.cwd);
27
+ } finally { r.cleanup(); }
@@ -0,0 +1,52 @@
1
+ /** CLAIM 1 probe, PLUGIN channel: is ${CLAUDE_SKILL_DIR} substituted in a
2
+ * plugin-provided SKILL.md body? Installed natively via `claude --plugin-dir`. */
3
+ import { writeFileSync } from "node:fs";
4
+ import { runHarnessTest } from "vigiles";
5
+
6
+ const BODY_CONTROL = "POSITIVE_CONTROL_ZQX7_BODY_REACHED_MODEL";
7
+ const PLUGIN = new URL("../fakeplugin/", import.meta.url).pathname;
8
+
9
+ const r = await runHarnessTest({
10
+ pluginDir: PLUGIN,
11
+ sandbox: false,
12
+ transcript: true,
13
+ timeoutMs: 180_000,
14
+ prompt: "probe the plugin skill dir",
15
+ allowedTools: ["Read", "Skill"],
16
+ files: { "control.txt": "CONTROL_FILE_CONTENTS_QQ9\n" },
17
+ model: [
18
+ { tool: "Skill", input: { skill: "pluginskill-probe" } },
19
+ { tool: "Read", input: { file_path: "control.txt" } },
20
+ { text: "done" },
21
+ ],
22
+ });
23
+
24
+ try {
25
+ console.log("=== TOOL CALLS ===");
26
+ console.log(r.toolCalls.map((c) => `${c.name}${c.isError ? " [ERROR]" : ""}: ${String(c.resultText).slice(0, 300)}`).join("\n") || "(none)");
27
+
28
+ const reached = r.modelRequests.map((q) => [q.system, ...q.messages.map((m) => m.text)].join("\n")).join("\n");
29
+ writeFileSync(new URL("./claim1-plugin-reached.txt", import.meta.url), reached);
30
+
31
+ const skillCall = r.toolCalls.find((c) => c.name === "Skill");
32
+ const readCall = r.toolCalls.find((c) => c.name === "Read");
33
+ console.log("\n=== POSITIVE CONTROLS ===");
34
+ console.log("C1 Skill call resolved:", skillCall && !skillCall.isError ? "PASS" : `FAIL (${skillCall ? skillCall.resultText.slice(0,250) : "no Skill call"})`);
35
+ console.log("C2 body marker reached model:", reached.includes(BODY_CONTROL) ? "PASS" : "FAIL");
36
+ console.log("C3 Read control succeeded:", readCall && !readCall.isError ? "PASS" : "FAIL");
37
+
38
+ console.log("\n=== SUBSTITUTION VERDICT (plugin channel) ===");
39
+ for (const [label, open, close] of [
40
+ ["CLAUDE_SKILL_DIR", "SKILLDIR_BEGIN>>>", "<<<SKILLDIR_END"],
41
+ ["CLAUDE_PLUGIN_ROOT", "PLUGINROOT_BEGIN>>>", "<<<PLUGINROOT_END"],
42
+ ["CLAUDE_PROJECT_DIR", "PROJDIR_BEGIN>>>", "<<<PROJDIR_END"],
43
+ ]) {
44
+ const i = reached.indexOf(open);
45
+ if (i === -1) { console.log(`${label}: marker ABSENT`); continue; }
46
+ const between = reached.slice(i + open.length, reached.indexOf(close, i));
47
+ console.log(`${label}: ${JSON.stringify(between)} -> ${between.includes("${") ? "LITERAL (no substitution)" : "SUBSTITUTED"}`);
48
+ }
49
+ console.log("\ncwd was:", r.cwd, "| plugin was:", PLUGIN, "| turns:", r.turns, "| exit:", r.exitCode);
50
+ } finally {
51
+ r.cleanup();
52
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * CLAIM 1 probe — is ${CLAUDE_SKILL_DIR} substituted in a SKILL.md BODY,
3
+ * for a PROJECT-level skill (.claude/skills/), regardless of cwd?
4
+ *
5
+ * Positive controls, so a silent empty result cannot be read as a finding:
6
+ * C1 the Skill tool call must resolve (not isError)
7
+ * C2 a literal marker from the BODY must appear in what reached the model
8
+ * C3 a granted tool (Read) must succeed
9
+ * Only after all three pass is the substitution verdict meaningful.
10
+ */
11
+ import { writeFileSync, mkdirSync } from "node:fs";
12
+ import { runHarnessTest } from "vigiles";
13
+
14
+ const BODY_CONTROL = "POSITIVE_CONTROL_ZQX7_BODY_REACHED_MODEL";
15
+ const OPEN = "SKILLDIR_BEGIN>>>";
16
+ const CLOSE = "<<<SKILLDIR_END";
17
+
18
+ const skillMd = `---
19
+ name: skilldir-probe
20
+ description: Probe that reports the literal text at the CLAUDE_SKILL_DIR spot. Use when asked to probe the skill dir.
21
+ ---
22
+
23
+ # skilldir-probe
24
+
25
+ ${BODY_CONTROL}
26
+
27
+ The sibling script lives at ${OPEN}\${CLAUDE_SKILL_DIR}${CLOSE}/scripts/thing.mjs
28
+
29
+ Also bare: BARE_BEGIN>>>\${CLAUDE_SKILL_DIR}<<<BARE_END
30
+ `;
31
+
32
+ const r = await runHarnessTest({
33
+ sandbox: false,
34
+ transcript: true,
35
+ timeoutMs: 180_000,
36
+ prompt: "probe the skill dir",
37
+ allowedTools: ["Read", "Skill", "Bash"],
38
+ files: {
39
+ ".claude/skills/skilldir-probe/SKILL.md": skillMd,
40
+ ".claude/skills/skilldir-probe/scripts/thing.mjs": "console.log('sibling');\n",
41
+ "control.txt": "CONTROL_FILE_CONTENTS_QQ9\n",
42
+ },
43
+ model: [
44
+ { tool: "Skill", input: { skill: "skilldir-probe" } },
45
+ { tool: "Read", input: { file_path: "control.txt" } },
46
+ { text: "done" },
47
+ ],
48
+ });
49
+
50
+ try {
51
+ const calls = r.toolCalls.map((c) => `${c.name}${c.isError ? " [ERROR]" : ""}: ${String(c.resultText).slice(0, 300)}`);
52
+ console.log("=== TOOL CALLS ===");
53
+ console.log(calls.join("\n") || "(none)");
54
+
55
+ const reached = r.modelRequests
56
+ .map((q) => [q.system, ...q.messages.map((m) => m.text)].join("\n"))
57
+ .join("\n");
58
+ writeFileSync(new URL("./claim1-reached.txt", import.meta.url), reached);
59
+
60
+ const skillCall = r.toolCalls.find((c) => c.name === "Skill");
61
+ const readCall = r.toolCalls.find((c) => c.name === "Read");
62
+
63
+ console.log("\n=== POSITIVE CONTROLS ===");
64
+ console.log("C1 Skill call resolved:", skillCall && !skillCall.isError ? "PASS" : `FAIL (${skillCall ? skillCall.resultText.slice(0,200) : "no Skill call"})`);
65
+ console.log("C2 body marker reached model:", reached.includes(BODY_CONTROL) ? "PASS" : "FAIL");
66
+ console.log("C3 Read control succeeded:", readCall && !readCall.isError ? "PASS" : "FAIL");
67
+
68
+ console.log("\n=== SUBSTITUTION VERDICT ===");
69
+ for (const [label, open, close] of [["sentence form", OPEN, CLOSE], ["bare form", "BARE_BEGIN>>>", "<<<BARE_END"]]) {
70
+ const i = reached.indexOf(open);
71
+ if (i === -1) { console.log(`${label}: marker ABSENT from model requests`); continue; }
72
+ const j = reached.indexOf(close, i);
73
+ const between = reached.slice(i + open.length, j);
74
+ console.log(`${label}: between markers = ${JSON.stringify(between)}`);
75
+ console.log(`${label}: -> ${between.includes("${CLAUDE_SKILL_DIR}") ? "LITERAL (no substitution)" : "SUBSTITUTED"}`);
76
+ }
77
+ console.log("\ncwd was:", r.cwd, "| turns:", r.turns, "| exit:", r.exitCode);
78
+ console.log("full reached-text saved next to this script as claim1-reached.txt");
79
+ } finally {
80
+ r.cleanup();
81
+ }
@@ -0,0 +1 @@
1
+ { "name": "rpp-probe", "owner": { "name": "zernie", "url": "https://github.com/zernie" }, "metadata": { "description": "probe marketplace" }, "plugins": [ { "name": "research-paper-pipeline", "source": "npm", "package": "research-paper-pipeline", "version": "^0.1.0", "description": "flat shape as the CLAIM words it" } ] }
@@ -0,0 +1 @@
1
+ { "name": "rpp-probe", "owner": { "name": "zernie", "url": "https://github.com/zernie" }, "metadata": { "description": "probe marketplace" }, "plugins": [ { "name": "research-paper-pipeline", "source": { "source": "nosuchsourcetype", "package": "x" }, "description": "negative control" } ] }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "rpp-probe",
3
+ "owner": { "name": "zernie", "url": "https://github.com/zernie" },
4
+ "plugins": [
5
+ { "name": "research-paper-pipeline",
6
+ "source": { "source": "npm", "package": "research-paper-pipeline", "version": "0.1.0", "registry": "https://registry.npmjs.org" },
7
+ "description": "npm-sourced plugin probe" }
8
+ ]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "rpp-probe",
3
+ "owner": { "name": "zernie", "url": "https://github.com/zernie" },
4
+ "plugins": [
5
+ { "name": "research-paper-pipeline",
6
+ "source": { "source": "npm", "package": "research-paper-pipeline" },
7
+ "description": "npm-sourced plugin probe" }
8
+ ]
9
+ }
@@ -0,0 +1 @@
1
+ { "name": "rpp-probe", "owner": { "name": "zernie", "url": "https://github.com/zernie" }, "metadata": { "description": "probe marketplace" }, "plugins": [ { "name": "research-paper-pipeline", "source": { "source": "npm", "package": "research-paper-pipeline", "version": "^0.1.0" }, "description": "npm-sourced plugin probe" } ] }