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,672 @@
1
+ /**
2
+ * pipeline-language.eval.mjs — does prompt LANGUAGE change whether a skill fires?
3
+ *
4
+ * Run: node .claude/skills/paper-pipeline/pipeline-language.eval.mjs [flags]
5
+ * --trials N trials per prompt (default 3)
6
+ * --concurrency N parallel runs (default 3)
7
+ * --only <skill> run one case (repeatable)
8
+ * --update-baseline record this run as the committed baseline
9
+ *
10
+ * ─────────────────────────────────────────────────────────────────────────────
11
+ * WHAT THIS SETTLES, AND WHY IT IS A SEPARATE FILE
12
+ *
13
+ * `pipeline-firing.eval.mjs` reported a lead it explicitly refused to call a result: eight of its
14
+ * nine misses were Russian-language prompts, recall splitting 10/18 (56%) Russian against 13/14
15
+ * (93%) English, Fisher exact two-sided p = 0.044. Its header names the three reasons not to act
16
+ * on that number and then names the experiment that would settle it:
17
+ *
18
+ * > THE DESIGNED VERSION, if this is worth settling: take the 14 English prompts, translate each
19
+ * > to Russian, and run both sets at --trials 5. That is a matched pair — same content, one
20
+ * > variable — and it costs about the same as one full run above.
21
+ *
22
+ * This is that experiment, with one addition argued for below. It is a separate file because the
23
+ * other one measures a STANDING property that should be re-run on a schedule (do the descriptions
24
+ * still route?), while this asks a ONE-TIME question with a yes/no answer. Folding a designed
25
+ * experiment into a monitoring eval makes the monitor's baseline diff meaningless the moment the
26
+ * experiment's arms change.
27
+ *
28
+ * ─────────────────────────────────────────────────────────────────────────────
29
+ * THE THREE CONFOUNDS, AND WHICH ONES THIS ACTUALLY CLOSES
30
+ *
31
+ * 1. CONFOUNDED CONTENT — closed. The prior run compared 14 English prompts against 18 DIFFERENT
32
+ * Russian prompts, so "Russian" and "these particular questions" were the same variable. Here
33
+ * every prompt exists in both languages with the same content, so content cancels.
34
+ *
35
+ * 2. ONE TRIAL — closed by construction, not by assertion. At one trial a per-prompt rate is a coin
36
+ * flip observed once, and the prior run watched `tighten-paper` flip 0.00 → 1.00 on the same
37
+ * prompt within one morning. Default here is 3 trials (n = 96 per language arm).
38
+ *
39
+ * 3. POST HOC — closed only in the weak sense that the hypothesis is now stated BEFORE the run
40
+ * rather than read off the output. That is worth something and it is not the same as
41
+ * pre-registration: the direction being tested was chosen because a previous run suggested it.
42
+ * A confirmation here is evidence; it is not independent discovery, and writing it up as though
43
+ * the effect were found twice would be counting one observation twice.
44
+ *
45
+ * ─────────────────────────────────────────────────────────────────────────────
46
+ * 🔴 THE CONFOUND THE ORIGINAL DESIGN DID NOT CLOSE: TRANSLATIONESE
47
+ *
48
+ * Translating the 14 English prompts into Russian and comparing gives matched content — but every
49
+ * Russian prompt in that design is a TRANSLATION and every English one is an ORIGINAL. If translated
50
+ * text is stilted, or lands closer to the English description's vocabulary, or reads as less natural
51
+ * to the model, then "language" and "was this text translated" move together and the experiment
52
+ * reproduces its own answer.
53
+ *
54
+ * So this file translates in BOTH directions: the 14 English originals get Russian counterparts, and
55
+ * the 18 Russian originals get English counterparts. Translation direction is then BALANCED across
56
+ * the language arms, and it is also recorded per prompt (`origin`), so the run reports the language
57
+ * effect split by origin. If the effect appears only among translated prompts, the finding is about
58
+ * translation, not about Russian — and that is a result worth having rather than a confound worth
59
+ * hiding.
60
+ *
61
+ * 🔴 THE ONE IT STILL DOES NOT CLOSE, STATED PLAINLY: the translations in both directions were
62
+ * written by the same model that is being measured. A native speaker did not review them. If the
63
+ * Russian counterparts are subtly unidiomatic, that shows up as a language effect here and this
64
+ * design cannot tell the difference. The honest fix is a human pass over the 32 Russian strings
65
+ * before any of this is published anywhere; until that happens the finding is "good enough to act on
66
+ * for our own repo" and NOT "good enough to write up".
67
+ *
68
+ * ─────────────────────────────────────────────────────────────────────────────
69
+ * WHY NO `irrelevantPrompts` HERE
70
+ *
71
+ * The prior run measured false positives across all eight cases and got 0% on every one, precision
72
+ * 100%. Nothing about a language question is answered by re-measuring that, and including the
73
+ * irrelevant sets would double the run count to buy a number already known. Precision is therefore
74
+ * NOT measured here; if a future change makes routing suspect again, that belongs in the monitoring
75
+ * eval, which is where the baseline for it lives.
76
+ *
77
+ * ─────────────────────────────────────────────────────────────────────────────
78
+ * COST
79
+ *
80
+ * 8 cases × 4 prompts × 2 languages × 3 trials = 192 runs. Measured rate on the prior run was
81
+ * ~$0.14 API-equivalent per run, so ~$27 API-equivalent, ~15 min at concurrency 3 — billed to a
82
+ * Claude subscription, $0 metered. Not wired into CI at any cadence: this answers a question once.
83
+ *
84
+ * ─────────────────────────────────────────────────────────────────────────────
85
+ * READING THE OUTPUT
86
+ *
87
+ * The per-case table is scenery. The two lines that matter are the OVERALL arm comparison and the
88
+ * SPLIT BY ORIGIN underneath it. Read them in that order and refuse to read the per-case cells as
89
+ * eight independent findings — at n = 12 per cell a difference of one run moves a cell by 8 points.
90
+ *
91
+ * ─────────────────────────────────────────────────────────────────────────────
92
+ * FIRST RUN — 2026-08-07, 192 runs, 3 trials, 36 competitors, ~$26.59 API-equivalent ($0 metered).
93
+ * Raw log + the analysis script: `repro/2026-08-07-language-eval-raw.log`,
94
+ * `repro/analyze-language-eval.py`. Statistics live in that script, not in this file.
95
+ *
96
+ * OVERALL EN 75/96 (78.1%) RU 58/96 (60.4%) -17.7pp
97
+ * paired sign test: 13 prompts favour EN, 2 favour RU, 17 tied — p = 0.0074
98
+ *
99
+ * en-original EN 81.0% RU 59.5% -21.4pp sign 6v0 p = 0.031
100
+ * ru-original EN 75.9% RU 61.1% -14.8pp sign 7v2 p = 0.180
101
+ *
102
+ * 1. THE EFFECT IS REAL AND IT IS NOT TRANSLATIONESE. The gap runs the same direction in BOTH
103
+ * translation directions, including the pairs whose RUSSIAN is the original human-typed text and
104
+ * whose English is the translation. That was the confound this file was built to close, and it
105
+ * did not explain the effect away. Read the ru-original row carefully though: at 9 discordant
106
+ * pairs it is NOT individually significant (p = 0.18). The honest statement is "same direction
107
+ * in both, individually significant only where the English is the original" — not "confirmed
108
+ * twice".
109
+ *
110
+ * 2. 🔴 THE LEAD OVERSTATED THE GAP BY ROUGHLY HALF, AND THE CORRECTION IS THE POINT. The prior
111
+ * single-trial run reported 93% English against 56% Russian, a 37-point gap. Here it is 78%
112
+ * against 60%, an 18-point gap. The RUSSIAN figure replicated almost exactly (56 → 60); the
113
+ * ENGLISH figure fell fifteen points. So the original gap was not mostly a Russian problem being
114
+ * discovered — it was an English arm of 14 prompts measured once each, sitting high by chance.
115
+ * A designed replication that confirms an effect at half the claimed size is the ordinary result
116
+ * of measuring properly, and reporting the -37pp number now would be citing the weaker run.
117
+ *
118
+ * 3. WHAT IT MEANS FOR THIS REPO. Every skill description here is written in English and the owner
119
+ * types Russian constantly. An 18-point recall penalty on the language he actually uses is worth
120
+ * acting on. The obvious intervention — Russian trigger vocabulary in the descriptions — is now
121
+ * measurable: re-run this file after the edit and the arms say whether it worked. Do NOT edit the
122
+ * descriptions and declare victory without re-running; that is the failure mode this whole
123
+ * directory exists to prevent.
124
+ *
125
+ * 4. STILL OPEN, unchanged by this run: the translations in both directions were written by the
126
+ * same model family being measured, and no native speaker has reviewed the 32 Russian strings.
127
+ * Good enough to act on inside this repo; not good enough to publish.
128
+ */
129
+
130
+ import {
131
+ assertPromptDiversity,
132
+ skillResolved,
133
+ readBaseline,
134
+ writeBaseline,
135
+ skip,
136
+ } from "vigiles";
137
+ import { paid_measureTriggerRate as measureTriggerRate } from "vigiles/eval";
138
+ import { existsSync, readFileSync } from "node:fs";
139
+ import { join } from "node:path";
140
+ import { frontmatterBlock } from "../../lib/markdown.mjs";
141
+ import { parseFm } from "../../lib/skill-corpus.mjs";
142
+ import { installedSkills } from "./scripts/consumer.mjs";
143
+
144
+ // The name a SKILL.md DECLARES, parsed rather than matched. The old expression took
145
+ // `(\S+)` after `name:`, which silently truncates a quoted name and cannot see one
146
+ // carried onto a continuation line — and this is a guard whose whole job is to fail
147
+ // when the declared name disagrees with the directory.
148
+ const declaredName = (md) => {
149
+ const block = frontmatterBlock(md);
150
+ if (block === null) return undefined;
151
+ const v = parseFm(block, "skill fixture").name;
152
+ return typeof v === "string" ? v : undefined;
153
+ };
154
+
155
+ import { execFileSync } from "node:child_process";
156
+
157
+ const ROOT = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
158
+ const SKILLS_DIR = join(ROOT, ".claude", "skills");
159
+ const BASELINE = join(
160
+ SKILLS_DIR,
161
+ "paper-pipeline",
162
+ "pipeline-language.baseline.json",
163
+ );
164
+
165
+ /** The namespace `packageSkillsDir` installs a LOOSE skills dir under. Same as the sibling eval. */
166
+ const NS = "vigiles-loose-skills";
167
+ const id = (skill) => `${NS}:${skill}`;
168
+
169
+ // ── flags ────────────────────────────────────────────────────────────────────
170
+ const argv = process.argv.slice(2);
171
+ const flag = (name) => argv.includes(`--${name}`);
172
+ const val = (name, dflt) => {
173
+ const i = argv.indexOf(`--${name}`);
174
+ return i >= 0 && argv[i + 1] ? argv[i + 1] : dflt;
175
+ };
176
+ const onlys = argv.reduce(
177
+ (acc, a, i) => (a === "--only" && argv[i + 1] ? [...acc, argv[i + 1]] : acc),
178
+ [],
179
+ );
180
+ const TRIALS = Number(val("trials", "3"));
181
+ const CONCURRENCY = Number(val("concurrency", "3"));
182
+
183
+ // ── the fixture ──────────────────────────────────────────────────────────────
184
+ // Byte-identical to the sibling eval's FIXTURE on purpose. If the two files drift, a difference
185
+ // between this run and that one stops being attributable to language.
186
+ const FIXTURE = {
187
+ "paper/paper.md": [
188
+ "# Prose Isn't Policy: Measuring Whether Agent-Config Rules Are Enforceable",
189
+ "",
190
+ "## Abstract",
191
+ "Agent configuration files state rules in prose and assume the model obeys them. We compile a",
192
+ "corpus of real rules and measure what fraction can be mechanically enforced. We find that 84%",
193
+ "of rules in our corpus are enforceable, and that LLM-authored checkers for the remainder leak",
194
+ "silently in 84-96% of adversarial cases \\cite{greshake2023}.",
195
+ "",
196
+ "## 1 Introduction",
197
+ "Every agent harness ships a natural-language rulebook. Nothing checks it. This is the same",
198
+ "mistake as a code comment that claims an invariant no test enforces \\cite{thompson1984}.",
199
+ "",
200
+ "## 2 Method",
201
+ "We gather rules from public repositories, classify each by enforceability, and build a",
202
+ "two-stage adversarial gate that validates a synthesized rule against a blind gold set.",
203
+ "",
204
+ "## 3 Results",
205
+ "See Table 1. The headline number is 84%.",
206
+ "",
207
+ "## 4 Discussion",
208
+ "The result generalizes beyond our corpus in the sense that the mechanism is not corpus-specific,",
209
+ "though of course the specific percentages are, and it is important to note in this context that",
210
+ "the framing itself may be what carries, rather than the measurement.",
211
+ "",
212
+ "## 5 Threats to Validity",
213
+ "Our corpus is drawn from public repositories and may not represent private configurations.",
214
+ "",
215
+ "## 6 Related Work",
216
+ "TODO",
217
+ "",
218
+ "## 7 Conclusion",
219
+ "Prose is not policy.",
220
+ ].join("\n"),
221
+ "paper/repro/README.md":
222
+ "# Reproduction artifact\n\n`python3 paper_numbers.py` recomputes every bolded figure in paper.md.\n",
223
+ "paper/PIPELINE-STATUS.md":
224
+ "# Pipeline status\n\n| gate | state |\n|---|---|\n| numbers | pass |\n| structure | not run |\n| citations | not run |\n",
225
+ };
226
+
227
+ // ── the matched pairs ────────────────────────────────────────────────────────
228
+ // Every entry is ONE question in TWO languages. `origin` records which side was written first, so
229
+ // the run can report whether the effect survives among prompts whose Russian is the ORIGINAL — the
230
+ // translationese control. The 32 originals are copied verbatim from pipeline-firing.eval.mjs; only
231
+ // the counterparts are new, so the two runs stay comparable on the shared half.
232
+ //
233
+ // Origin counts: 14 English, 18 Russian — exactly the split whose imbalance produced the lead.
234
+ const CASES = [
235
+ {
236
+ skill: "tighten-paper",
237
+ why: "structural bloat — cut/fold/merge, NOT sentence craft",
238
+ pairs: [
239
+ {
240
+ origin: "ru",
241
+ ru: "статья раздулась, середина провисает — что резать?",
242
+ en: "the paper has bloated and the middle sags — what do I cut?",
243
+ },
244
+ {
245
+ origin: "en",
246
+ en: "this draft is 14 pages against a 9 page limit, what goes",
247
+ ru: "в черновике 14 страниц при лимите 9, что убираем",
248
+ },
249
+ {
250
+ origin: "ru",
251
+ ru: "после трёх раундов ревью там одна вода и хеджи, нужен план сокращения",
252
+ en: "after three review rounds it is all filler and hedges, I need a cut plan",
253
+ },
254
+ {
255
+ origin: "en",
256
+ en: "sections 4 and 5 say the same thing twice, and nobody would skim any of it",
257
+ ru: "секции 4 и 5 говорят одно и то же дважды, и это невозможно пролистать",
258
+ },
259
+ ],
260
+ },
261
+ {
262
+ skill: "grade-paper-writing",
263
+ why: "prose craft — the sentence is the unit, NOT the section",
264
+ pairs: [
265
+ {
266
+ origin: "ru",
267
+ ru: "оцени как написано — читается как стена жаргона",
268
+ en: "grade how it is written — it reads like a wall of jargon",
269
+ },
270
+ {
271
+ origin: "en",
272
+ en: "is the writing any good or does it read like shit",
273
+ ru: "текст вообще нормальный или читается как дерьмо",
274
+ },
275
+ {
276
+ origin: "ru",
277
+ ru: "abstract звучит криво хотя по смыслу всё на месте, дай оценку прозе",
278
+ en: "the abstract sounds clumsy even though the substance is fine, grade the prose",
279
+ },
280
+ {
281
+ origin: "en",
282
+ en: "grade the craft: title, abstract, sentence clarity, hedge stacking",
283
+ ru: "оцени ремесло: заголовок, аннотация, ясность предложений, нагромождение хеджей",
284
+ },
285
+ ],
286
+ },
287
+ {
288
+ skill: "argument-arc",
289
+ why: "argument architecture — does one conclusion become inevitable",
290
+ pairs: [
291
+ {
292
+ origin: "ru",
293
+ ru: "ревьюер второй раз пишет что мы кидаем в него идеи без связи",
294
+ en: "a reviewer has now said twice that we throw disconnected ideas at him",
295
+ },
296
+ {
297
+ origin: "en",
298
+ en: "does the paper actually carry a reader to one conclusion or just list stuff",
299
+ ru: "статья реально ведёт читателя к одному выводу или просто перечисляет",
300
+ },
301
+ {
302
+ origin: "ru",
303
+ ru: "мы вводим пять именованных штук и три числа — по-моему это перебор",
304
+ en: "we introduce five named things and three numbers — that feels like too much",
305
+ },
306
+ {
307
+ origin: "en",
308
+ en: "before the big rewrite I want one sentence per section, bottom up",
309
+ ru: "перед большой переписью хочу по одному предложению на секцию, снизу вверх",
310
+ },
311
+ ],
312
+ },
313
+ {
314
+ skill: "paper-adversarial-review",
315
+ why: "ONE hostile reviewer, fast",
316
+ pairs: [
317
+ {
318
+ origin: "ru",
319
+ ru: "red-team эту статью, чем будет бить reviewer 2",
320
+ en: "red-team this paper, what will reviewer 2 hit it with",
321
+ },
322
+ {
323
+ origin: "en",
324
+ en: "would reviewer 2 buy this claim about the hook finding",
325
+ ru: "купится ли reviewer 2 на это утверждение про находку с хуком",
326
+ },
327
+ {
328
+ origin: "ru",
329
+ ru: "найди слабые места до сабмита — один злой но честный рецензент",
330
+ en: "find the weak spots before submission — one hostile but fair reviewer",
331
+ },
332
+ {
333
+ origin: "en",
334
+ en: "what is our desk reject risk and where do we overclaim",
335
+ ru: "какой у нас риск desk reject и где мы переобещаем",
336
+ },
337
+ ],
338
+ },
339
+ {
340
+ skill: "pc-panel-review",
341
+ why: "the WHOLE committee + an accept probability, not one reviewer",
342
+ pairs: [
343
+ {
344
+ origin: "ru",
345
+ ru: "какая вероятность принятия у этой статьи, если честно",
346
+ en: "honestly, what is the acceptance probability for this paper",
347
+ },
348
+ {
349
+ origin: "en",
350
+ en: "simulate the whole program committee, not one reviewer",
351
+ ru: "смоделируй весь программный комитет, а не одного рецензента",
352
+ },
353
+ {
354
+ origin: "ru",
355
+ ru: "что решат на PC discussion — accept или reject",
356
+ en: "what will the PC discussion decide — accept or reject",
357
+ },
358
+ {
359
+ origin: "ru",
360
+ ru: "нужно несколько независимых ревьюеров с разными линзами плюс мета-ревью от чейра",
361
+ en: "I need several independent reviewers with different lenses plus a meta-review from the chair",
362
+ },
363
+ ],
364
+ },
365
+ {
366
+ skill: "cold-read-diff",
367
+ why: "fires AFTER a prose edit — the reader with no context",
368
+ pairs: [
369
+ {
370
+ origin: "ru",
371
+ ru: "я переписал третий абзац intro — проверь что предложения вообще что-то значат",
372
+ en: "I rewrote the third paragraph of the intro — check the sentences actually mean anything",
373
+ },
374
+ {
375
+ origin: "en",
376
+ en: "just edited the threats section, would a reader with no context get it",
377
+ ru: "только что правил секцию threats, поймёт ли её читатель без контекста",
378
+ },
379
+ {
380
+ origin: "ru",
381
+ ru: "поправил формулировки в 4.2, прогони свежим читателем до того как я закрою правку",
382
+ en: "I fixed the wording in 4.2, run a fresh reader over it before I close the edit",
383
+ },
384
+ {
385
+ origin: "ru",
386
+ ru: "эти предложения короткие, правдивые и всё равно непонятно что они утверждают",
387
+ en: "these sentences are short, true, and it is still unclear what they claim",
388
+ },
389
+ ],
390
+ },
391
+ {
392
+ skill: "verify-citations",
393
+ why: "are the cites REAL — a pre-submit metadata gate",
394
+ pairs: [
395
+ {
396
+ origin: "ru",
397
+ ru: "проверь что все цитаты настоящие перед сабмитом",
398
+ en: "check that every citation is real before submission",
399
+ },
400
+ {
401
+ origin: "en",
402
+ en: "did we hallucinate any of these refs",
403
+ ru: "мы не выдумали какие-нибудь из этих ссылок",
404
+ },
405
+ {
406
+ origin: "ru",
407
+ ru: "сверь метаданные по каждому cite — год, венью, авторы, doi",
408
+ en: "verify the metadata on every cite — year, venue, authors, doi",
409
+ },
410
+ {
411
+ origin: "en",
412
+ en: "one bibtex entry looks invented to me, check the whole bibliography",
413
+ ru: "одна bibtex-запись выглядит выдуманной, проверь всю библиографию",
414
+ },
415
+ ],
416
+ },
417
+ {
418
+ skill: "map-prior-work",
419
+ why: "who already did this — BEFORE drafting, reshapes the contribution",
420
+ pairs: [
421
+ {
422
+ origin: "ru",
423
+ ru: "кто уже это сделал до нас — хочу знать до того как начну писать",
424
+ en: "who has already done this before us — I want to know before I start writing",
425
+ },
426
+ {
427
+ origin: "en",
428
+ en: "sweep the landscape: everyone working on this, prior versus concurrent",
429
+ ru: "прочеши ландшафт: все кто работает над этим, prior против concurrent",
430
+ },
431
+ {
432
+ origin: "ru",
433
+ ru: "нужен скелет related work и вердикт что мы ещё можем клеймить своим",
434
+ en: "I need a related-work skeleton and a verdict on what we can still claim as ours",
435
+ },
436
+ {
437
+ origin: "en",
438
+ en: "find every competing group in this space and date them against our submission",
439
+ ru: "найди все конкурирующие группы в этой области и датируй их относительно нашего сабмита",
440
+ },
441
+ ],
442
+ },
443
+ ];
444
+
445
+ // ── preflight ────────────────────────────────────────────────────────────────
446
+ // Same guard as the sibling eval, same reason: a misspelled skill or a changed namespace makes
447
+ // every `fired` predicate permanently false, and the run then reports a wall of confident 0.00s as
448
+ // though eight descriptions had simultaneously died.
449
+ function assertSkillIdsExist() {
450
+ if (!existsSync(SKILLS_DIR))
451
+ throw new Error(`no skills dir at ${SKILLS_DIR}`);
452
+ // Through `installedSkills`, which follows the links `paperlint init` makes (rpp#62).
453
+ const installed = new Set(installedSkills(SKILLS_DIR));
454
+ const missing = CASES.map((c) => c.skill).filter((s) => !installed.has(s));
455
+ if (missing.length)
456
+ throw new Error(
457
+ `these cases name skills that are not installed under ${SKILLS_DIR}: ${missing.join(", ")}. ` +
458
+ `Every \`fired\` predicate for them would be permanently false.`,
459
+ );
460
+ for (const c of CASES) {
461
+ const fm = readFileSync(join(SKILLS_DIR, c.skill, "SKILL.md"), "utf-8");
462
+ const declared = declaredName(fm);
463
+ if (declared && declared !== c.skill)
464
+ throw new Error(
465
+ `${c.skill}/SKILL.md declares name: ${declared} — id mismatch, fix one of them`,
466
+ );
467
+ }
468
+ return installed.size;
469
+ }
470
+
471
+ // A pair that is identical in both languages measures nothing, and a pair where one side is empty
472
+ // silently shrinks an arm. Both are free to check and neither is hypothetical — the strings below
473
+ // were written by hand.
474
+ for (const c of CASES) {
475
+ for (const p of c.pairs) {
476
+ if (!p.en?.trim() || !p.ru?.trim())
477
+ throw new Error(
478
+ `${c.skill}: a pair is missing a side: ${JSON.stringify(p)}`,
479
+ );
480
+ if (p.en.trim() === p.ru.trim())
481
+ throw new Error(
482
+ `${c.skill}: both sides of a pair are identical: ${p.en}`,
483
+ );
484
+ if (!/[а-яё]/i.test(p.ru))
485
+ throw new Error(`${c.skill}: the "ru" side has no Cyrillic: ${p.ru}`);
486
+ if (p.origin !== "en" && p.origin !== "ru")
487
+ throw new Error(`${c.skill}: origin must be "en" or "ru"`);
488
+ }
489
+ }
490
+
491
+ const installedCount = assertSkillIdsExist();
492
+
493
+ // Free and deterministic, and it runs before a token is spent. Checked PER LANGUAGE ARM: a set whose
494
+ // four prompts all read the same way makes that arm's rate one question asked four times, and an
495
+ // arm-vs-arm comparison between two such sets is worth nothing at all.
496
+ for (const c of CASES)
497
+ for (const lang of ["en", "ru"])
498
+ assertPromptDiversity(
499
+ c.pairs.map((p) => p[lang]),
500
+ { minPrompts: 4, minDistance: 0.3, label: `${c.skill}:${lang}` },
501
+ );
502
+
503
+ const originCounts = CASES.flatMap((c) => c.pairs).reduce(
504
+ (a, p) => ({ ...a, [p.origin]: (a[p.origin] ?? 0) + 1 }),
505
+ {},
506
+ );
507
+ console.log(
508
+ `matched pairs: OK (${CASES.length} cases × 4 pairs × 2 languages × ${TRIALS} trial(s) = ` +
509
+ `${CASES.length * 4 * 2 * TRIALS} runs; origins: ${originCounts.en} en, ${originCounts.ru} ru; ` +
510
+ `${installedCount} skills installed → ${installedCount - 1} competitors per run)`,
511
+ );
512
+
513
+ try {
514
+ execFileSync("claude", ["--version"], { stdio: "ignore" });
515
+ } catch {
516
+ skip(
517
+ "`claude` CLI not on PATH — the eval tier drives the real harness and cannot be faked",
518
+ );
519
+ }
520
+
521
+ const selected = onlys.length
522
+ ? CASES.filter((c) => onlys.includes(c.skill))
523
+ : CASES;
524
+ if (selected.length === 0)
525
+ throw new Error(
526
+ `--only matched nothing. Known: ${CASES.map((c) => c.skill).join(", ")}`,
527
+ );
528
+
529
+ // ── the measurement ──────────────────────────────────────────────────────────
530
+ // One `measureTriggerRate` per (case, language). Everything except the prompt strings is held
531
+ // identical across the two arms — same skillsDir, same fixture, same allowedTools, same predicate,
532
+ // same trials — so the arm difference has one input.
533
+ const rows = [];
534
+ for (const c of selected) {
535
+ const perLang = {};
536
+ for (const lang of ["en", "ru"]) {
537
+ const report = await measureTriggerRate({
538
+ name: `pipeline-language:${c.skill}:${lang}`,
539
+ skillsDir: SKILLS_DIR,
540
+ prompts: c.pairs.map((p) => p[lang]),
541
+ fired: (t) => skillResolved(t, id(c.skill)),
542
+ fixture: FIXTURE,
543
+ minPrompts: 4,
544
+ minDistance: 0.3,
545
+ trials: TRIALS,
546
+ allowedTools: ["Skill", "Read"],
547
+ concurrency: CONCURRENCY,
548
+ spacingSec: 2,
549
+ timeoutMs: 180000,
550
+ });
551
+ perLang[lang] = report;
552
+ }
553
+ rows.push({ case: c, perLang });
554
+ console.log(`\n=== ${c.skill} — ${c.why}`);
555
+ for (const lang of ["en", "ru"]) {
556
+ const r = perLang[lang];
557
+ console.log(` ${lang}: ${(r.rate * 100).toFixed(0)}% (${r.n} runs)`);
558
+ r.perPrompt.forEach((p, i) =>
559
+ console.log(
560
+ ` ${(p.fired / p.trials).toFixed(2)} [${c.pairs[i].origin}] ${p.prompt.slice(0, 66)}`,
561
+ ),
562
+ );
563
+ }
564
+ }
565
+
566
+ // ── the two lines that matter ────────────────────────────────────────────────
567
+ const pct = (x) => `${(x * 100).toFixed(0)}%`.padStart(5);
568
+ console.log(`\n${"skill".padEnd(26)} en ru delta`);
569
+ for (const { case: c, perLang } of rows) {
570
+ const d = perLang.ru.rate - perLang.en.rate;
571
+ console.log(
572
+ `${c.skill.padEnd(26)} ${pct(perLang.en.rate)} ${pct(perLang.ru.rate)} ${(d >= 0 ? "+" : "") + (d * 100).toFixed(0)}pp`,
573
+ );
574
+ }
575
+
576
+ /** Fired/total across every selected case for one language, optionally filtered by pair origin. */
577
+ const tally = (lang, originFilter) => {
578
+ let fired = 0;
579
+ let n = 0;
580
+ for (const { case: c, perLang } of rows)
581
+ perLang[lang].perPrompt.forEach((p, i) => {
582
+ if (originFilter && c.pairs[i].origin !== originFilter) return;
583
+ fired += p.fired;
584
+ n += p.trials;
585
+ });
586
+ return { fired, n, rate: n > 0 ? fired / n : 0 };
587
+ };
588
+
589
+ const EN = tally("en");
590
+ const RU = tally("ru");
591
+ console.log(
592
+ `\nOVERALL en ${EN.fired}/${EN.n} (${(EN.rate * 100).toFixed(0)}%) ` +
593
+ `ru ${RU.fired}/${RU.n} (${(RU.rate * 100).toFixed(0)}%) ` +
594
+ `delta ${((RU.rate - EN.rate) * 100).toFixed(0)}pp`,
595
+ );
596
+
597
+ // The translationese control. If the language gap exists among ru-ORIGINAL pairs (where the Russian
598
+ // is the natural text and the English is the translation) as well as among en-original pairs, then
599
+ // it is about language. If it exists only where the Russian was translated, it is about translation
600
+ // — a real finding, a different one, and NOT the one the lead claimed.
601
+ console.log(
602
+ `\nSPLIT BY ORIGIN — is the gap about language, or about which side was translated?`,
603
+ );
604
+ console.log(`${"origin".padEnd(12)} en ru delta`);
605
+ for (const o of ["en", "ru"]) {
606
+ const e = tally("en", o);
607
+ const r = tally("ru", o);
608
+ const d = (r.rate - e.rate) * 100;
609
+ console.log(
610
+ `${`${o}-original`.padEnd(12)} ${pct(e.rate)} ${pct(r.rate)} ${(d >= 0 ? "+" : "") + d.toFixed(0)}pp` +
611
+ ` (${e.fired}/${e.n} vs ${r.fired}/${r.n})`,
612
+ );
613
+ }
614
+
615
+ // The same guard the sibling eval carries, for the same reason: a run that measured nothing must
616
+ // not be readable as a finding.
617
+ if (EN.fired === 0 && RU.fired === 0)
618
+ throw new Error(
619
+ "EVERY run in BOTH arms scored 0. That is far more likely a harness fault (wrong namespace, " +
620
+ "skills not installed, `fired` predicate broken) than sixteen simultaneously dead descriptions.",
621
+ );
622
+
623
+ // 🔴 NO GATE, AND THAT IS DELIBERATE. This eval answers a question; it does not police a property.
624
+ // A threshold here would have to encode the very effect being measured, which is how a check that
625
+ // can only confirm its own hypothesis gets written. The sibling eval owns the floor and the
626
+ // regression gate; this one reports and exits 0.
627
+ console.log(
628
+ `\nNo gate: this file measures an effect, it does not enforce one. ` +
629
+ `Read OVERALL first, then SPLIT BY ORIGIN — a gap that appears only on translated Russian is a ` +
630
+ `finding about translation, not about Russian.`,
631
+ );
632
+
633
+ const snapshot = {
634
+ name: "pipeline-language",
635
+ trials: TRIALS,
636
+ arms: {
637
+ en: { runs: EN.n, metrics: { recall: EN.rate } },
638
+ ru: { runs: RU.n, metrics: { recall: RU.rate } },
639
+ "en:en-original": {
640
+ runs: tally("en", "en").n,
641
+ metrics: { recall: tally("en", "en").rate },
642
+ },
643
+ "ru:en-original": {
644
+ runs: tally("ru", "en").n,
645
+ metrics: { recall: tally("ru", "en").rate },
646
+ },
647
+ "en:ru-original": {
648
+ runs: tally("en", "ru").n,
649
+ metrics: { recall: tally("en", "ru").rate },
650
+ },
651
+ "ru:ru-original": {
652
+ runs: tally("ru", "ru").n,
653
+ metrics: { recall: tally("ru", "ru").rate },
654
+ },
655
+ },
656
+ totalCostUsd: rows.reduce(
657
+ (s, x) =>
658
+ s + x.perLang.en.usage.totalCostUsd + x.perLang.ru.usage.totalCostUsd,
659
+ 0,
660
+ ),
661
+ aborted: false,
662
+ };
663
+ console.log(
664
+ `\ntotal spend: $${snapshot.totalCostUsd.toFixed(2)} API-equivalent`,
665
+ );
666
+
667
+ if (flag("update-baseline")) {
668
+ writeBaseline(BASELINE, [snapshot]);
669
+ console.log(`baseline recorded → ${BASELINE}`);
670
+ } else if (!readBaseline(BASELINE)) {
671
+ console.log(`no baseline at ${BASELINE} — record one with --update-baseline`);
672
+ }