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.
- package/.github/dependabot.yml +72 -0
- package/.github/workflows/ci.yml +297 -0
- package/.github/workflows/dependabot-automerge.yml +70 -0
- package/.github/workflows/pr-title.yml +59 -0
- package/.github/workflows/release.yml +54 -0
- package/CLAUDE.md +598 -0
- package/CONTRIBUTING.md +159 -0
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/action.harness.mjs +287 -0
- package/action.mutations.mjs +162 -0
- package/action.yml +138 -0
- package/bin/rpp.mjs +43 -0
- package/dist/action-ref.d.ts +12 -0
- package/dist/action-ref.d.ts.map +1 -0
- package/dist/action-ref.js +16 -0
- package/dist/action-ref.js.map +1 -0
- package/dist/adapters/banal/failure.d.ts +73 -0
- package/dist/adapters/banal/failure.d.ts.map +1 -0
- package/dist/adapters/banal/failure.js +58 -0
- package/dist/adapters/banal/failure.js.map +1 -0
- package/dist/adapters/banal/index.d.ts +17 -0
- package/dist/adapters/banal/index.d.ts.map +1 -0
- package/dist/adapters/banal/index.js +56 -0
- package/dist/adapters/banal/index.js.map +1 -0
- package/dist/adapters/banal/install.d.ts +26 -0
- package/dist/adapters/banal/install.d.ts.map +1 -0
- package/dist/adapters/banal/install.js +15 -0
- package/dist/adapters/banal/install.js.map +1 -0
- package/dist/adapters/banal/invocation.d.ts +48 -0
- package/dist/adapters/banal/invocation.d.ts.map +1 -0
- package/dist/adapters/banal/invocation.js +43 -0
- package/dist/adapters/banal/invocation.js.map +1 -0
- package/dist/adapters/banal/locate.d.ts +50 -0
- package/dist/adapters/banal/locate.d.ts.map +1 -0
- package/dist/adapters/banal/locate.js +34 -0
- package/dist/adapters/banal/locate.js.map +1 -0
- package/dist/adapters/banal/output.d.ts +27 -0
- package/dist/adapters/banal/output.d.ts.map +1 -0
- package/dist/adapters/banal/output.js +112 -0
- package/dist/adapters/banal/output.js.map +1 -0
- package/dist/adapters/banal/pin.d.ts +19 -0
- package/dist/adapters/banal/pin.d.ts.map +1 -0
- package/dist/adapters/banal/pin.js +15 -0
- package/dist/adapters/banal/pin.js.map +1 -0
- package/dist/adapters/banal/probe.d.ts +12 -0
- package/dist/adapters/banal/probe.d.ts.map +1 -0
- package/dist/adapters/banal/probe.js +27 -0
- package/dist/adapters/banal/probe.js.map +1 -0
- package/dist/adapters/banal/run.d.ts +89 -0
- package/dist/adapters/banal/run.d.ts.map +1 -0
- package/dist/adapters/banal/run.js +104 -0
- package/dist/adapters/banal/run.js.map +1 -0
- package/dist/adapters/banal/settings.d.ts +18 -0
- package/dist/adapters/banal/settings.d.ts.map +1 -0
- package/dist/adapters/banal/settings.js +29 -0
- package/dist/adapters/banal/settings.js.map +1 -0
- package/dist/adapters/banal/xml.d.ts +48 -0
- package/dist/adapters/banal/xml.d.ts.map +1 -0
- package/dist/adapters/banal/xml.js +67 -0
- package/dist/adapters/banal/xml.js.map +1 -0
- package/dist/adapters/curl/download.io.d.ts +14 -0
- package/dist/adapters/curl/download.io.d.ts.map +1 -0
- package/dist/adapters/curl/download.io.js +69 -0
- package/dist/adapters/curl/download.io.js.map +1 -0
- package/dist/adapters/curl/index.d.ts +6 -0
- package/dist/adapters/curl/index.d.ts.map +1 -0
- package/dist/adapters/curl/index.js +6 -0
- package/dist/adapters/curl/index.js.map +1 -0
- package/dist/adapters/memory/index.d.ts +43 -0
- package/dist/adapters/memory/index.d.ts.map +1 -0
- package/dist/adapters/memory/index.js +79 -0
- package/dist/adapters/memory/index.js.map +1 -0
- package/dist/adapters/node/files.io.d.ts +3 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -0
- package/dist/adapters/node/files.io.js +31 -0
- package/dist/adapters/node/files.io.js.map +1 -0
- package/dist/adapters/node/host.io.d.ts +3 -0
- package/dist/adapters/node/host.io.d.ts.map +1 -0
- package/dist/adapters/node/host.io.js +14 -0
- package/dist/adapters/node/host.io.js.map +1 -0
- package/dist/adapters/node/index.d.ts +25 -0
- package/dist/adapters/node/index.d.ts.map +1 -0
- package/dist/adapters/node/index.js +14 -0
- package/dist/adapters/node/index.js.map +1 -0
- package/dist/adapters/node/process.io.d.ts +14 -0
- package/dist/adapters/node/process.io.d.ts.map +1 -0
- package/dist/adapters/node/process.io.js +41 -0
- package/dist/adapters/node/process.io.js.map +1 -0
- package/dist/adapters/node/workspace.io.d.ts +4 -0
- package/dist/adapters/node/workspace.io.d.ts.map +1 -0
- package/dist/adapters/node/workspace.io.js +33 -0
- package/dist/adapters/node/workspace.io.js.map +1 -0
- package/dist/adapters/pdfjs/fill.d.ts +42 -0
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -0
- package/dist/adapters/pdfjs/fill.js +91 -0
- package/dist/adapters/pdfjs/fill.js.map +1 -0
- package/dist/build-engine.d.ts +48 -0
- package/dist/build-engine.d.ts.map +1 -0
- package/dist/build-engine.js +148 -0
- package/dist/build-engine.js.map +1 -0
- package/dist/build.d.ts +163 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +575 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +151 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +951 -0
- package/dist/cli.js.map +1 -0
- package/dist/doctor.d.ts +42 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +280 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain/geometry.d.ts +71 -0
- package/dist/domain/geometry.d.ts.map +1 -0
- package/dist/domain/geometry.js +35 -0
- package/dist/domain/geometry.js.map +1 -0
- package/dist/domain/host.d.ts +16 -0
- package/dist/domain/host.d.ts.map +1 -0
- package/dist/domain/host.js +8 -0
- package/dist/domain/host.js.map +1 -0
- package/dist/domain/page-layout.d.ts +34 -0
- package/dist/domain/page-layout.d.ts.map +1 -0
- package/dist/domain/page-layout.js +8 -0
- package/dist/domain/page-layout.js.map +1 -0
- package/dist/domain/paths.d.ts +5 -0
- package/dist/domain/paths.d.ts.map +1 -0
- package/dist/domain/paths.js +2 -0
- package/dist/domain/paths.js.map +1 -0
- package/dist/domain/result.d.ts +23 -0
- package/dist/domain/result.d.ts.map +1 -0
- package/dist/domain/result.js +10 -0
- package/dist/domain/result.js.map +1 -0
- package/dist/domain/sha256.d.ts +7 -0
- package/dist/domain/sha256.d.ts.map +1 -0
- package/dist/domain/sha256.js +14 -0
- package/dist/domain/sha256.js.map +1 -0
- package/dist/domain/text.d.ts +6 -0
- package/dist/domain/text.d.ts.map +1 -0
- package/dist/domain/text.js +7 -0
- package/dist/domain/text.js.map +1 -0
- package/dist/engine.d.ts +93 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +119 -0
- package/dist/engine.js.map +1 -0
- package/dist/exit-code.d.ts +22 -0
- package/dist/exit-code.d.ts.map +1 -0
- package/dist/exit-code.js +10 -0
- package/dist/exit-code.js.map +1 -0
- package/dist/facts-file.d.ts +96 -0
- package/dist/facts-file.d.ts.map +1 -0
- package/dist/facts-file.js +134 -0
- package/dist/facts-file.js.map +1 -0
- package/dist/hooks-settings.d.ts +141 -0
- package/dist/hooks-settings.d.ts.map +1 -0
- package/dist/hooks-settings.js +306 -0
- package/dist/hooks-settings.js.map +1 -0
- package/dist/init.d.ts +201 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +579 -0
- package/dist/init.js.map +1 -0
- package/dist/latex-log.d.ts +80 -0
- package/dist/latex-log.d.ts.map +1 -0
- package/dist/latex-log.js +187 -0
- package/dist/latex-log.js.map +1 -0
- package/dist/latex-loop.d.ts +129 -0
- package/dist/latex-loop.d.ts.map +1 -0
- package/dist/latex-loop.js +113 -0
- package/dist/latex-loop.js.map +1 -0
- package/dist/link-skills.d.ts +51 -0
- package/dist/link-skills.d.ts.map +1 -0
- package/dist/link-skills.js +199 -0
- package/dist/link-skills.js.map +1 -0
- package/dist/new-paper.d.ts +48 -0
- package/dist/new-paper.d.ts.map +1 -0
- package/dist/new-paper.js +110 -0
- package/dist/new-paper.js.map +1 -0
- package/dist/pdf-facts.d.ts +44 -0
- package/dist/pdf-facts.d.ts.map +1 -0
- package/dist/pdf-facts.js +239 -0
- package/dist/pdf-facts.js.map +1 -0
- package/dist/pdf-geometry.d.ts +170 -0
- package/dist/pdf-geometry.d.ts.map +1 -0
- package/dist/pdf-geometry.js +158 -0
- package/dist/pdf-geometry.js.map +1 -0
- package/dist/ports/download.d.ts +9 -0
- package/dist/ports/download.d.ts.map +1 -0
- package/dist/ports/download.js +2 -0
- package/dist/ports/download.js.map +1 -0
- package/dist/ports/files.d.ts +11 -0
- package/dist/ports/files.d.ts.map +1 -0
- package/dist/ports/files.js +2 -0
- package/dist/ports/files.js.map +1 -0
- package/dist/ports/measure-geometry.d.ts +8 -0
- package/dist/ports/measure-geometry.d.ts.map +1 -0
- package/dist/ports/measure-geometry.js +2 -0
- package/dist/ports/measure-geometry.js.map +1 -0
- package/dist/ports/process.d.ts +45 -0
- package/dist/ports/process.d.ts.map +1 -0
- package/dist/ports/process.js +2 -0
- package/dist/ports/process.js.map +1 -0
- package/dist/ports/tool-installer.d.ts +29 -0
- package/dist/ports/tool-installer.d.ts.map +1 -0
- package/dist/ports/tool-installer.js +2 -0
- package/dist/ports/tool-installer.js.map +1 -0
- package/dist/ports/workspace.d.ts +18 -0
- package/dist/ports/workspace.d.ts.map +1 -0
- package/dist/ports/workspace.js +2 -0
- package/dist/ports/workspace.js.map +1 -0
- package/dist/rules-config.d.ts +34 -0
- package/dist/rules-config.d.ts.map +1 -0
- package/dist/rules-config.js +132 -0
- package/dist/rules-config.js.map +1 -0
- package/dist/structure.d.ts +34 -0
- package/dist/structure.d.ts.map +1 -0
- package/dist/structure.js +149 -0
- package/dist/structure.js.map +1 -0
- package/dist/tex-requirements.d.ts +43 -0
- package/dist/tex-requirements.d.ts.map +1 -0
- package/dist/tex-requirements.js +127 -0
- package/dist/tex-requirements.js.map +1 -0
- package/dist/toolchain.d.ts +159 -0
- package/dist/toolchain.d.ts.map +1 -0
- package/dist/toolchain.js +542 -0
- package/dist/toolchain.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/configuration.md +235 -0
- package/docs/e2e.md +152 -0
- package/docs/incidents.md +59 -0
- package/docs/install.md +170 -0
- package/docs/optional-rules.md +107 -0
- package/docs/package-shape-options.md +262 -0
- package/docs/prior-art/README.md +76 -0
- package/docs/prior-art/blocking-vs-advisory.md +83 -0
- package/docs/prior-art/content-delivery.md +124 -0
- package/docs/prior-art/multi-mode-tools.md +106 -0
- package/docs/prior-art/nondeterministic-checks.md +99 -0
- package/docs/prior-art/package-location.md +422 -0
- package/docs/prior-art/paper-folder-scaffolding.md +538 -0
- package/docs/prior-art/readme-structure.md +69 -0
- package/docs/prior-art/repro/README.md +92 -0
- package/docs/prior-art/repro/claim1-allowedtools.mjs +66 -0
- package/docs/prior-art/repro/claim1-at2.mjs +40 -0
- package/docs/prior-art/repro/claim1-crosschannel.mjs +54 -0
- package/docs/prior-art/repro/claim1-frontmatter.mjs +76 -0
- package/docs/prior-art/repro/claim1-hook-payload-reporter.mjs +10 -0
- package/docs/prior-art/repro/claim1-plugin-frontmatter.mjs +27 -0
- package/docs/prior-art/repro/claim1-plugin-skill.mjs +52 -0
- package/docs/prior-art/repro/claim1-project-skill.mjs +81 -0
- package/docs/prior-art/repro/claim2-marketplace-flat-asclaimed.json +1 -0
- package/docs/prior-art/repro/claim2-marketplace-negative-control.json +1 -0
- package/docs/prior-art/repro/claim2-marketplace-nested-exact.json +9 -0
- package/docs/prior-art/repro/claim2-marketplace-nested-noversion.json +9 -0
- package/docs/prior-art/repro/claim2-marketplace-nested-range.json +1 -0
- package/docs/prior-art/repro/claim3-imports.mjs +50 -0
- package/docs/prior-art/repro/claim4-find-package-json.mjs +8 -0
- package/docs/prior-art/repro/claim4-package-dir.mjs +39 -0
- package/docs/prior-art/repro/claim4-parent-arg.mjs +17 -0
- package/docs/prior-art/repro/claim4-resolve-apis.mjs +21 -0
- package/docs/prior-art/repro/claim4-setup-consumers.mjs +45 -0
- package/docs/prior-art/repro/claim4-yarn-pnp.mjs +70 -0
- package/docs/prior-art/repro/claim5-bin-launch.mjs +39 -0
- package/docs/prior-art/repro/claim5-exports-mutation.mjs +57 -0
- package/docs/prior-art/repro/claim5-resolved-location-and-bin.mjs +33 -0
- package/docs/prior-art/repro/claim6-candidate-ambiguity.mjs +17 -0
- package/docs/prior-art/repro/claim6-doc-path-candidates.mjs +27 -0
- package/docs/prior-art/test-tooling.md +131 -0
- package/docs/rules.md +58 -0
- package/docs/texlive-install-decision.md +230 -0
- package/docs/toolchain.md +152 -0
- package/eslint-rules/doc-fields.harness.mjs +336 -0
- package/eslint-rules/doc-fields.mjs +186 -0
- package/eslint-rules/doc-fields.mutations.mjs +96 -0
- package/eslint-rules/install-path-literals.harness.mjs +121 -0
- package/eslint-rules/install-path-literals.mjs +108 -0
- package/eslint-rules/install-path-literals.mutations.mjs +62 -0
- package/eslint-rules/latex-language.harness.mjs +599 -0
- package/eslint-rules/latex-language.mjs +591 -0
- package/eslint-rules/latex-language.mutations.mjs +196 -0
- package/eslint-rules/paper-research-question.harness.mjs +146 -0
- package/eslint-rules/paper-research-question.mjs +180 -0
- package/eslint-rules/paper-research-question.mutations.mjs +127 -0
- package/eslint-rules/paper-stages.harness.mjs +356 -0
- package/eslint-rules/paper-stages.mjs +455 -0
- package/eslint-rules/paper-stages.mutations.mjs +157 -0
- package/eslint-rules/paper-typography.harness.mjs +291 -0
- package/eslint-rules/paper-typography.mjs +313 -0
- package/eslint-rules/paper-typography.mutations.mjs +131 -0
- package/eslint-rules/papers.harness.mjs +259 -0
- package/eslint-rules/papers.mjs +166 -0
- package/eslint-rules/papers.mutations.mjs +186 -0
- package/eslint-rules/pdf-last-page-balance.harness.mjs +206 -0
- package/eslint-rules/pdf-last-page-balance.mjs +208 -0
- package/eslint-rules/review-findings-cause.harness.mjs +228 -0
- package/eslint-rules/review-findings-cause.mjs +135 -0
- package/eslint-rules/review-findings-cause.mutations.mjs +72 -0
- package/eslint-rules/temp-root-realpath.harness.mjs +176 -0
- package/eslint-rules/temp-root-realpath.mjs +129 -0
- package/eslint-rules/temp-root-realpath.mutations.mjs +99 -0
- package/eslint-rules/tex-build.harness.mjs +753 -0
- package/eslint-rules/tex-build.mjs +322 -0
- package/eslint-rules/tex-build.mutations.mjs +258 -0
- package/eslint.config.mjs +521 -0
- package/fixtures/build-e2e/acmart/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/acmart/paper.tex +11 -0
- package/fixtures/build-e2e/acmart/venue.json +1 -0
- package/fixtures/build-e2e/broken/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/broken/paper.tex +7 -0
- package/fixtures/build-e2e/cite/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/cite/build.sh +5 -0
- package/fixtures/build-e2e/cite/paper.tex +10 -0
- package/fixtures/build-e2e/cite/refs.bib +9 -0
- package/fixtures/build-e2e/empty/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/empty/paper.tex +6 -0
- package/fixtures/build-e2e/fallback/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/fallback/paper.tex +11 -0
- package/fixtures/build-e2e/guards/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/guards/paper.tex +10 -0
- package/fixtures/build-e2e/no-source/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/unbalanced/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/unbalanced/paper.tex +28 -0
- package/fixtures/build-e2e/unbalanced/refs.bib +269 -0
- package/fixtures/install-path-literals/clean.fixture.mjs +3 -0
- package/fixtures/install-path-literals/clean.md +15 -0
- package/fixtures/install-path-literals/defect.fixture.mjs +3 -0
- package/fixtures/install-path-literals/defect.md +14 -0
- package/fixtures/latex-language/clean.tex +50 -0
- package/fixtures/latex-language/defect.tex +52 -0
- package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +9 -0
- package/fixtures/paper-research-question/comment-only/paper.tex +7 -0
- package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +10 -0
- package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +6 -0
- package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +6 -0
- package/fixtures/paper-research-question/draft/paper.tex +2 -0
- package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +9 -0
- package/fixtures/paper-research-question/markdown-no-rq/paper.md +4 -0
- package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +12 -0
- package/fixtures/paper-research-question/shipped-no-rq/paper.tex +3 -0
- package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +10 -0
- package/fixtures/paper-research-question/shipped-with-rq/paper.tex +2 -0
- package/fixtures/paper-stages/authors-ran/PIPELINE-STATUS.md +16 -0
- package/fixtures/paper-stages/marker-in-prose/PIPELINE-STATUS.md +17 -0
- package/fixtures/paper-stages/nofile/PIPELINE-STATUS.md +8 -0
- package/fixtures/paper-stages/noheader/PIPELINE-STATUS.md +1 -0
- package/fixtures/paper-stages/noheader/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-stages/nothing/PIPELINE-STATUS.md +3 -0
- package/fixtures/paper-stages/ok/PIPELINE-STATUS.md +9 -0
- package/fixtures/paper-stages/ok/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-stages/stale/PIPELINE-STATUS.md +1 -0
- package/fixtures/paper-stages/stale/versions/2026-07-22-submitted.STALE-WRONG-FILE.pdf +0 -0
- package/fixtures/paper-stages/twice/PIPELINE-STATUS.md +14 -0
- package/fixtures/paper-stages/twice/versions/2026-08-06-submitted.pdf +0 -0
- package/fixtures/paper-stages/twice/versions/2026-10-24-submitted.pdf +0 -0
- package/fixtures/paper-stages/undeclared/PIPELINE-STATUS.md +8 -0
- package/fixtures/paper-stages/undeclared/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-stages/undeclared/versions/2026-08-29-camera-ready.pdf +0 -0
- package/fixtures/paper-stages/wrongsize/PIPELINE-STATUS.md +8 -0
- package/fixtures/paper-stages/wrongsize/versions/2026-07-22-submitted.pdf +0 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +29 -0
- package/fixtures/paper-typography/messy-paper/paper.tex +27 -0
- package/fixtures/pdf-facts/README.md +22 -0
- package/fixtures/pdf-facts/corrupt-font.pdf +0 -0
- package/fixtures/pdf-facts/encrypted.pdf +0 -0
- package/fixtures/pdf-facts/hidden-text.pdf +0 -0
- package/fixtures/pdf-facts/hidden-text.tex +28 -0
- package/fixtures/pdf-facts/t3-all.pdf +0 -0
- package/fixtures/pdf-facts/t3-all.tex +8 -0
- package/fixtures/pdf-facts/t3-mixed.pdf +0 -0
- package/fixtures/pdf-facts/t3-mixed.tex +9 -0
- package/fixtures/pdf-facts/ttf.pdf +2240 -1
- package/fixtures/pdf-facts/ttf.tex +6 -0
- package/fixtures/real-markdown-paper/baseline.json +24 -0
- package/fixtures/real-markdown-paper/baseline.mjs +48 -0
- package/fixtures/render-paper/build-clean.sh +25 -0
- package/fixtures/render-paper/build-defect.sh +15 -0
- package/fixtures/review-findings-cause/clean.md +17 -0
- package/fixtures/review-findings-cause/defect.md +14 -0
- package/fixtures/review-findings-cause/old-debt.md +14 -0
- package/fixtures/review-findings-cause/quiet-in-fence.md +16 -0
- package/fixtures/tex-build/clean.tex +21 -0
- package/fixtures/tex-build/defect.tex +24 -0
- package/fixtures/tex-build/frontmatter-clean.tex +25 -0
- package/fixtures/tex-build/frontmatter-defect.tex +23 -0
- package/fixtures/toolchain-mirror/catalog.txt +5 -0
- package/fixtures/toolchain-mirror/install-tl +27 -0
- package/fixtures/toolchain-mirror/release-texlive.txt +3 -0
- package/fixtures/toolchain-mirror/release-year +1 -0
- package/fixtures/toolchain-mirror/stub-kpsewhich +8 -0
- package/fixtures/toolchain-mirror/stub-pdflatex +3 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +44 -0
- package/hooks/hooks.harness.mjs +713 -0
- package/hooks/hooks.mutations.mjs +337 -0
- package/hooks/paper-edit-guard.hook.d.mts +13 -0
- package/hooks/paper-edit-guard.hook.mjs +457 -0
- package/hooks/paper-skills-nudge.hook.mjs +136 -0
- package/hooks/paper-status-gates.hook.mjs +156 -0
- package/hooks/paper-status-gates.sh +91 -0
- package/lib/agent-cli-version.harness.mjs +165 -0
- package/lib/agent-cli-version.mjs +106 -0
- package/lib/agent-cli-version.mutations.mjs +109 -0
- package/lib/markdown.mjs +386 -0
- package/lib/mutation-driver.harness.mjs +227 -0
- package/lib/mutation-driver.mjs +397 -0
- package/lib/mutation-driver.mutations.mjs +68 -0
- package/lib/paper-config.d.mts +34 -0
- package/lib/paper-config.harness.mjs +286 -0
- package/lib/paper-config.mjs +142 -0
- package/lib/paper-config.mutations.mjs +143 -0
- package/lib/skill-checks.mjs +701 -0
- package/lib/skill-corpus.mjs +403 -0
- package/lib/skill-eval-fixture.mjs +63 -0
- package/lib/skill-eval-kit.mjs +257 -0
- package/lib/skill-trigger-cases.harness.mjs +170 -0
- package/lib/skill-trigger-cases.mjs +446 -0
- package/lib/skill-trigger-cases.mutations.mjs +65 -0
- package/lib/trigger-ledger.mjs +215 -0
- package/package.json +97 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/hooks/hooks.json +30 -0
- package/scripts/check.harness.mjs +177 -0
- package/scripts/check.mjs +239 -0
- package/scripts/check.mutations.mjs +110 -0
- package/scripts/eslint-report-guard.mjs +82 -0
- package/scripts/exclusive.mjs +138 -0
- package/scripts/harness-api.frozen.json +76 -0
- package/scripts/harness-api.test.ts +175 -0
- package/scripts/layer-legacy-frozen.d.mts +28 -0
- package/scripts/layer-legacy-frozen.mjs +152 -0
- package/scripts/layer-legacy-frozen.test.ts +115 -0
- package/scripts/layer-legacy.frozen.json +50 -0
- package/scripts/mutation-batteries-frozen.harness.mjs +204 -0
- package/scripts/mutation-batteries-frozen.mjs +238 -0
- package/scripts/mutation-batteries.frozen.json +117 -0
- package/scripts/release-config.test.ts +90 -0
- package/scripts/rules-are-content-only.harness.mjs +113 -0
- package/scripts/rules-are-content-only.mjs +138 -0
- package/scripts/rules-are-content-only.mutations.mjs +81 -0
- package/scripts/rules-see-files.harness.mjs +115 -0
- package/scripts/rules-see-files.mjs +99 -0
- package/scripts/rules-see-files.mutations.mjs +131 -0
- package/scripts/run-mutations.mjs +100 -0
- package/scripts/semantic-release-plugins.d.ts +16 -0
- package/skills/README.md +15 -0
- package/skills/analyze-sibling-paper/SKILL.md +170 -0
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +186 -0
- package/skills/analyze-sibling-paper/analyze-sibling-paper.eval.mjs +19 -0
- package/skills/analyze-sibling-paper/analyze-sibling-paper.harness.mjs +23 -0
- package/skills/argument-arc/SKILL.md +177 -0
- package/skills/argument-arc/SKILL.md.spec.ts +192 -0
- package/skills/argument-arc/argument-arc.eval.mjs +19 -0
- package/skills/argument-arc/argument-arc.harness.mjs +23 -0
- package/skills/build-benchmark/SKILL.md +213 -0
- package/skills/build-benchmark/SKILL.md.spec.ts +220 -0
- package/skills/build-benchmark/build-benchmark.eval.mjs +19 -0
- package/skills/build-benchmark/build-benchmark.harness.mjs +23 -0
- package/skills/build-benchmark/references/adversarial-cold-repro.md +68 -0
- package/skills/camera-ready/SKILL.md +148 -0
- package/skills/camera-ready/SKILL.md.spec.ts +164 -0
- package/skills/camera-ready/camera-ready.eval.mjs +19 -0
- package/skills/camera-ready/camera-ready.harness.mjs +23 -0
- package/skills/cold-read-diff/SKILL.md +160 -0
- package/skills/cold-read-diff/SKILL.md.spec.ts +166 -0
- package/skills/cold-read-diff/cold-read-diff.eval.mjs +19 -0
- package/skills/cold-read-diff/cold-read-diff.harness.mjs +23 -0
- package/skills/draft-paper/SKILL.md +152 -0
- package/skills/draft-paper/SKILL.md.spec.ts +169 -0
- package/skills/draft-paper/draft-paper.eval.mjs +19 -0
- package/skills/draft-paper/draft-paper.harness.mjs +23 -0
- package/skills/extend-paper/SKILL.md +99 -0
- package/skills/extend-paper/SKILL.md.spec.ts +116 -0
- package/skills/extend-paper/extend-paper.eval.mjs +19 -0
- package/skills/extend-paper/extend-paper.harness.mjs +23 -0
- package/skills/find-venue/SKILL.md +128 -0
- package/skills/find-venue/SKILL.md.spec.ts +145 -0
- package/skills/find-venue/find-venue.eval.mjs +19 -0
- package/skills/find-venue/find-venue.harness.mjs +23 -0
- package/skills/grade-paper-writing/SKILL.md +436 -0
- package/skills/grade-paper-writing/SKILL.md.spec.ts +453 -0
- package/skills/grade-paper-writing/fixtures/control_gopen.txt +1 -0
- package/skills/grade-paper-writing/fixtures/control_human_paper.txt +1 -0
- package/skills/grade-paper-writing/fixtures/rewrite.txt +1 -0
- package/skills/grade-paper-writing/fixtures/specimen.txt +1 -0
- package/skills/grade-paper-writing/fixtures/structure-checks.md +22 -0
- package/skills/grade-paper-writing/grade-paper-writing.eval.mjs +19 -0
- package/skills/grade-paper-writing/grade-paper-writing.harness.mjs +23 -0
- package/skills/grade-paper-writing/prose-lint.mjs +713 -0
- package/skills/harden-paper/SKILL.md +318 -0
- package/skills/harden-paper/SKILL.md.spec.ts +336 -0
- package/skills/harden-paper/check-numbers.sh +33 -0
- package/skills/harden-paper/check-release-claims.sh +35 -0
- package/skills/harden-paper/fixtures/uncited-assertions-sample.md +43 -0
- package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +77 -0
- package/skills/harden-paper/harden-paper.eval.mjs +19 -0
- package/skills/harden-paper/harden-paper.harness.mjs +23 -0
- package/skills/map-prior-work/SKILL.md +211 -0
- package/skills/map-prior-work/SKILL.md.spec.ts +227 -0
- package/skills/map-prior-work/map-prior-work.eval.mjs +19 -0
- package/skills/map-prior-work/map-prior-work.harness.mjs +23 -0
- package/skills/osf-artifact-upload/SKILL.md +52 -0
- package/skills/osf-artifact-upload/SKILL.md.spec.ts +59 -0
- package/skills/osf-artifact-upload/osf-artifact-upload.eval.mjs +22 -0
- package/skills/osf-artifact-upload/osf-artifact-upload.harness.mjs +103 -0
- package/skills/paper-adversarial-review/SKILL.md +126 -0
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +142 -0
- package/skills/paper-adversarial-review/paper-adversarial-review.eval.mjs +19 -0
- package/skills/paper-adversarial-review/paper-adversarial-review.harness.mjs +23 -0
- package/skills/paper-pipeline/PIPELINE-MAP.md +371 -0
- package/skills/paper-pipeline/SKILL.md +499 -0
- package/skills/paper-pipeline/SKILL.md.spec.ts +517 -0
- package/skills/paper-pipeline/description-language.eval.mjs +347 -0
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +891 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1254 -0
- package/skills/paper-pipeline/paper-pipeline.eval.mjs +22 -0
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +143 -0
- package/skills/paper-pipeline/pipeline-firing.baseline.json +270 -0
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +664 -0
- package/skills/paper-pipeline/pipeline-language.eval.mjs +672 -0
- package/skills/paper-pipeline/references/acceptance-gate.md +329 -0
- package/skills/paper-pipeline/references/acl-venue-rules.md +142 -0
- package/skills/paper-pipeline/references/anonymization.md +68 -0
- package/skills/paper-pipeline/references/artifact-checklist.md +93 -0
- package/skills/paper-pipeline/references/body-vs-appendix.md +97 -0
- package/skills/paper-pipeline/references/credit-criteria.md +69 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/README.md +35 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/run_retext.mjs +24 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/sentences.txt +11 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-probe/test_sentences.py +25 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-prose-checkers.md +538 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-reproducible-tooling.md +431 -0
- package/skills/paper-pipeline/references/occupancy-2026-08-06-staleness-and-orchestration.md +592 -0
- package/skills/paper-pipeline/references/pipeline-status-template.md +162 -0
- package/skills/paper-pipeline/references/review-ratchet.md +36 -0
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +585 -0
- package/skills/paper-pipeline/references/writing-craft.md +448 -0
- package/skills/paper-pipeline/repro/2026-08-07-description-language-control.log +63 -0
- package/skills/paper-pipeline/repro/2026-08-07-fork-check.log +52 -0
- package/skills/paper-pipeline/repro/2026-08-07-fork-check2.log +33 -0
- package/skills/paper-pipeline/repro/2026-08-07-language-eval-pilot.log +33 -0
- package/skills/paper-pipeline/repro/2026-08-07-language-eval-raw.log +166 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.json +338 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.log +118 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-raw.log +245 -0
- package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary.json +776 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-oracle.log +53 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-raw.log +89 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.json +450 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.log +136 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-raw.log +242 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-setupdiff.log +59 -0
- package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation.json +1032 -0
- package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.json +139 -0
- package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.log +98 -0
- package/skills/paper-pipeline/repro/2026-08-08-parent-replication.mjs +92 -0
- package/skills/paper-pipeline/repro/README.md +129 -0
- package/skills/paper-pipeline/repro/analyze-language-eval.py +116 -0
- package/skills/paper-pipeline/scripts/README.md +344 -0
- package/skills/paper-pipeline/scripts/announce.mjs +67 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +496 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +397 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +218 -0
- package/skills/paper-pipeline/scripts/check-provenance.mjs +184 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +32 -0
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +562 -0
- package/skills/paper-pipeline/scripts/consumer.mjs +535 -0
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +190 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +457 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +656 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +54 -0
- package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +51 -0
- package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +52 -0
- package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +6 -0
- package/skills/paper-pipeline/scripts/fixtures/real-bib/refs.bib +153 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +466 -0
- package/skills/paper-pipeline/scripts/generated-code.mjs +338 -0
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +254 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +623 -0
- package/skills/paper-pipeline/scripts/ledger.selftest.mjs +286 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +389 -0
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +737 -0
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +54 -0
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +169 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +178 -0
- package/skills/paper-pipeline/scripts/population-map.mjs +181 -0
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +65 -0
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +122 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +240 -0
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +59 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +881 -0
- package/skills/paper-pipeline/scripts/round-diff.mjs +576 -0
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +276 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +633 -0
- package/skills/paper-pipeline/scripts/status.mjs +295 -0
- package/skills/paper-status/SKILL.md +183 -0
- package/skills/paper-status/SKILL.md.spec.ts +190 -0
- package/skills/paper-status/paper-status.eval.mjs +22 -0
- package/skills/paper-status/paper-status.harness.mjs +25 -0
- package/skills/pc-panel-review/SKILL.md +263 -0
- package/skills/pc-panel-review/SKILL.md.spec.ts +280 -0
- package/skills/pc-panel-review/pc-panel-review.eval.mjs +19 -0
- package/skills/pc-panel-review/pc-panel-review.harness.mjs +23 -0
- package/skills/plan-paper-timeline/SKILL.md +182 -0
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +200 -0
- package/skills/plan-paper-timeline/fixtures/fake-google-calendar.mjs +239 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +431 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +65 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.eval.mjs +19 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.harness.mjs +23 -0
- package/skills/render-paper/SKILL.md +159 -0
- package/skills/render-paper/SKILL.md.spec.ts +166 -0
- package/skills/render-paper/check-render.sh +419 -0
- package/skills/render-paper/checkers-requirements.txt +55 -0
- package/skills/render-paper/ensure-checkers.sh +69 -0
- package/skills/render-paper/extract-pdf-facts.harness.mjs +166 -0
- package/skills/render-paper/extract-pdf-facts.mjs +144 -0
- package/skills/render-paper/render-paper.eval.mjs +19 -0
- package/skills/render-paper/render-paper.harness.mjs +339 -0
- package/skills/research-ideate/SKILL.md +136 -0
- package/skills/research-ideate/SKILL.md.spec.ts +152 -0
- package/skills/research-ideate/research-ideate.eval.mjs +19 -0
- package/skills/research-ideate/research-ideate.harness.mjs +23 -0
- package/skills/skill-contract.mutations.mjs +179 -0
- package/skills/study-accepted-papers/SKILL.md +206 -0
- package/skills/study-accepted-papers/SKILL.md.spec.ts +223 -0
- package/skills/study-accepted-papers/study-accepted-papers.eval.mjs +19 -0
- package/skills/study-accepted-papers/study-accepted-papers.harness.mjs +23 -0
- package/skills/submit-paper/SKILL.md +182 -0
- package/skills/submit-paper/SKILL.md.spec.ts +199 -0
- package/skills/submit-paper/check-deanon.sh +149 -0
- package/skills/submit-paper/references/publishers/acm.md +92 -0
- package/skills/submit-paper/references/venues/agenticdev.jsonc +108 -0
- package/skills/submit-paper/references/venues/agenticdev.md +139 -0
- package/skills/submit-paper/references/venues/agenticdev.tex +19 -0
- package/skills/submit-paper/references/venues/aisec.jsonc +101 -0
- package/skills/submit-paper/references/venues/aisec.md +105 -0
- package/skills/submit-paper/references/venues/paper-guards.tex +41 -0
- package/skills/submit-paper/references/venues/realm.jsonc +81 -0
- package/skills/submit-paper/references/venues/realm.md +155 -0
- package/skills/submit-paper/references/venues/tex-base.jsonc +50 -0
- package/skills/submit-paper/references/venues/venue-profile.schema.json +74 -0
- package/skills/submit-paper/submit-paper.eval.mjs +19 -0
- package/skills/submit-paper/submit-paper.harness.mjs +23 -0
- package/skills/sweep-design-space/SKILL.md +269 -0
- package/skills/sweep-design-space/SKILL.md.spec.ts +285 -0
- package/skills/sweep-design-space/sweep-design-space.eval.mjs +19 -0
- package/skills/sweep-design-space/sweep-design-space.harness.mjs +23 -0
- package/skills/tighten-paper/SKILL.md +368 -0
- package/skills/tighten-paper/SKILL.md.spec.ts +384 -0
- package/skills/tighten-paper/structure.mjs +371 -0
- package/skills/tighten-paper/tighten-paper.eval.mjs +19 -0
- package/skills/tighten-paper/tighten-paper.harness.mjs +23 -0
- package/skills/verify-citations/SKILL.md +328 -0
- package/skills/verify-citations/SKILL.md.spec.ts +345 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +479 -0
- package/skills/verify-citations/scripts/bib-authors.test.mjs +175 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +1108 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +735 -0
- package/skills/verify-citations/verify-citations.eval.mjs +19 -0
- package/skills/verify-citations/verify-citations.harness.mjs +23 -0
- package/src/CLAUDE.md +51 -0
- package/src/action-ref.test.ts +26 -0
- package/src/action-ref.ts +15 -0
- package/src/adapters/banal/failure.test.ts +63 -0
- package/src/adapters/banal/failure.ts +118 -0
- package/src/adapters/banal/index.test.ts +119 -0
- package/src/adapters/banal/index.ts +100 -0
- package/src/adapters/banal/install.test.ts +20 -0
- package/src/adapters/banal/install.ts +41 -0
- package/src/adapters/banal/invocation.test.ts +74 -0
- package/src/adapters/banal/invocation.ts +95 -0
- package/src/adapters/banal/locate.test.ts +52 -0
- package/src/adapters/banal/locate.ts +84 -0
- package/src/adapters/banal/output.test.ts +140 -0
- package/src/adapters/banal/output.ts +141 -0
- package/src/adapters/banal/pin.ts +30 -0
- package/src/adapters/banal/probe.ts +35 -0
- package/src/adapters/banal/run.test.ts +191 -0
- package/src/adapters/banal/run.ts +244 -0
- package/src/adapters/banal/settings.test.ts +31 -0
- package/src/adapters/banal/settings.ts +55 -0
- package/src/adapters/banal/xml.test.ts +111 -0
- package/src/adapters/banal/xml.ts +112 -0
- package/src/adapters/curl/download.io.ts +73 -0
- package/src/adapters/curl/download.test.ts +55 -0
- package/src/adapters/curl/index.ts +5 -0
- package/src/adapters/memory/index.ts +131 -0
- package/src/adapters/node/files.io.ts +39 -0
- package/src/adapters/node/files.test.ts +28 -0
- package/src/adapters/node/host.io.ts +15 -0
- package/src/adapters/node/index.ts +36 -0
- package/src/adapters/node/process.io.ts +49 -0
- package/src/adapters/node/process.test.ts +46 -0
- package/src/adapters/node/workspace.io.ts +40 -0
- package/src/adapters/node/workspace.test.ts +58 -0
- package/src/adapters/pdfjs/fill.test.ts +111 -0
- package/src/adapters/pdfjs/fill.ts +141 -0
- package/src/build-engine.harness.mjs +314 -0
- package/src/build-engine.ts +219 -0
- package/src/build.harness.mjs +631 -0
- package/src/build.mutations.mjs +195 -0
- package/src/build.ts +793 -0
- package/src/cli.harness.mjs +2007 -0
- package/src/cli.mutations.mjs +448 -0
- package/src/cli.ts +1189 -0
- package/src/doctor.harness.mjs +396 -0
- package/src/doctor.mutations.mjs +175 -0
- package/src/doctor.ts +356 -0
- package/src/domain/geometry.ts +108 -0
- package/src/domain/host.ts +23 -0
- package/src/domain/page-layout.ts +32 -0
- package/src/domain/paths.ts +5 -0
- package/src/domain/result.test.ts +26 -0
- package/src/domain/result.ts +29 -0
- package/src/domain/sha256.test.ts +12 -0
- package/src/domain/sha256.ts +21 -0
- package/src/domain/text.ts +11 -0
- package/src/engine.harness.mjs +252 -0
- package/src/engine.ts +176 -0
- package/src/exit-code.test.ts +21 -0
- package/src/exit-code.ts +38 -0
- package/src/facts-file.test.ts +240 -0
- package/src/facts-file.ts +241 -0
- package/src/hooks-settings.harness.mjs +386 -0
- package/src/hooks-settings.mutations.mjs +116 -0
- package/src/hooks-settings.ts +434 -0
- package/src/init.ts +900 -0
- package/src/latex-log.harness.mjs +226 -0
- package/src/latex-log.ts +234 -0
- package/src/latex-loop.harness.mjs +449 -0
- package/src/latex-loop.ts +211 -0
- package/src/link-skills.harness.mjs +273 -0
- package/src/link-skills.mutations.mjs +136 -0
- package/src/link-skills.ts +258 -0
- package/src/new-paper.harness.mjs +216 -0
- package/src/new-paper.mutations.mjs +79 -0
- package/src/new-paper.ts +158 -0
- package/src/pdf-facts.harness.mjs +188 -0
- package/src/pdf-facts.ts +327 -0
- package/src/pdf-geometry.harness.mjs +254 -0
- package/src/pdf-geometry.ts +300 -0
- package/src/ports/download.ts +10 -0
- package/src/ports/files.ts +11 -0
- package/src/ports/measure-geometry.ts +8 -0
- package/src/ports/process.ts +46 -0
- package/src/ports/tool-installer.ts +33 -0
- package/src/ports/workspace.ts +20 -0
- package/src/rules-config.harness.mjs +114 -0
- package/src/rules-config.ts +178 -0
- package/src/structure.harness.mjs +179 -0
- package/src/structure.mutations.mjs +83 -0
- package/src/structure.ts +166 -0
- package/src/tex-requirements.harness.mjs +238 -0
- package/src/tex-requirements.ts +181 -0
- package/src/toolchain.harness.mjs +651 -0
- package/src/toolchain.ts +755 -0
- package/src/types.ts +106 -0
- package/templates/paper/PIPELINE-STATUS.md +72 -0
- package/templates/paper/paper.md +4 -0
- package/templates/paper/paper.tex +8 -0
- package/tsconfig.json +23 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# CLAUDE.md — paperlint
|
|
2
|
+
|
|
3
|
+
Machine-checkable gates for writing a research paper in git, extracted from a private
|
|
4
|
+
knowledge base.
|
|
5
|
+
|
|
6
|
+
## 🎯 THE GOAL, in one sentence
|
|
7
|
+
|
|
8
|
+
**Move every paper-writing convention a machine can decide OUT of prose and INTO an engine
|
|
9
|
+
that fails the build** — and keep everything else honestly labelled as prose. The engine is
|
|
10
|
+
ESLint. The unit of progress is "one more verdict decided by the engine instead of by a
|
|
11
|
+
hand-written script".
|
|
12
|
+
|
|
13
|
+
That last clause is the whole direction, and it is the part that keeps being forgotten:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
prose in a guideline → hand-written script → ESLint rule in the engine
|
|
17
|
+
rots silently runs, but is OURS editor-time · free AST · suppressible
|
|
18
|
+
← where this started ← where most of it is ← where it is going
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
🔴 **A hand-written script is a WAYPOINT, not a destination.** Writing a new one is allowed
|
|
22
|
+
only when the engine genuinely cannot express the check — and "cannot" means MEASURED, not
|
|
23
|
+
assumed. Two things that sound like limits and are not:
|
|
24
|
+
|
|
25
|
+
- _"ESLint only sees one file"_ — true of its AST, false of the rule: a rule is an ordinary
|
|
26
|
+
JS module and may call `execFileSync("git", …)` or read a sibling file. If the reason to
|
|
27
|
+
stay a script is "it needs git", that reason is weak; measure the real cost before using it.
|
|
28
|
+
- _"this runs programs, not lints files"_ — that is a real limit, and the answer is the seam
|
|
29
|
+
already proven here: **a script PRODUCES facts into a JSON file, and ESLint JUDGES that
|
|
30
|
+
file.** The verdict lands in the engine even though the work did not.
|
|
31
|
+
|
|
32
|
+
## 📊 STATE — measured 2026-09-16 (re-measure, never cite)
|
|
33
|
+
|
|
34
|
+
| | |
|
|
35
|
+
| ----------------------- | -----------------------------------------------------------------------------------------: |
|
|
36
|
+
| ESLint rules | **5** — `latex-language` · `tex-build` · `papers` · `review-findings-cause` · `doc-fields` |
|
|
37
|
+
| harnesses | **57** |
|
|
38
|
+
| mutation batteries | **34** |
|
|
39
|
+
| skills | **24** |
|
|
40
|
+
| hooks (runnable `.mjs`) | **5** |
|
|
41
|
+
| repo-wide scripts | 5 |
|
|
42
|
+
| files tracked / commits | 308 / 43 |
|
|
43
|
+
|
|
44
|
+
**A real consumer dogfoods this package on every CI run**, so a breaking change here turns a
|
|
45
|
+
paper pipeline red somewhere else the same day. That is deliberate — it is the only thing
|
|
46
|
+
keeping the extraction honest.
|
|
47
|
+
|
|
48
|
+
⚠️ **This table is a SNAPSHOT, not a fact about today, and it has already gone stale once.**
|
|
49
|
+
The line standing here until 2026-09-16 said "two ESLint rule modules" while six were shipped,
|
|
50
|
+
24 skills had moved in, and hooks existed at all. Re-measure with `git ls-files` before
|
|
51
|
+
repeating any number from it.
|
|
52
|
+
|
|
53
|
+
## First command in a fresh container
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Not optional and not "when something breaks": `vigiles` is a real dependency, and every
|
|
60
|
+
harness, spec and hook resolves through it. A container where `npm install` never ran fails
|
|
61
|
+
in ways that look like broken code rather than a missing install.
|
|
62
|
+
|
|
63
|
+
## The ten rules that decide what may live here — and what may not be written
|
|
64
|
+
|
|
65
|
+
**1. Mechanism goes to vigiles, data stays here.** A file that names nothing local — no rule
|
|
66
|
+
of ours, no fixture of ours — is machinery, and machinery belongs in
|
|
67
|
+
[vigiles](https://github.com/zernie/vigiles). Ask it in two steps: is this mechanism or data?
|
|
68
|
+
If mechanism — does it know about _this_ domain? If not, it is not ours.
|
|
69
|
+
|
|
70
|
+
**2. A check over an AST is a LINT RULE, not a script.** If it walks `.ts`/`.js`/`.tex` and
|
|
71
|
+
looks at declarations, names or nodes, write an ESLint rule: it fires in the editor on save,
|
|
72
|
+
gets the AST for free, and has a suppression syntax people already know. Scripts are for
|
|
73
|
+
corpus-wide questions — index connectivity, ratios across many files — not for one file's
|
|
74
|
+
nodes.
|
|
75
|
+
|
|
76
|
+
**3. Every check needs BOTH halves, or it is not tested.** It must FIRE on a planted defect
|
|
77
|
+
and stay QUIET on a clean fixture. A check that has only been seen quiet is
|
|
78
|
+
indistinguishable from a dead one — silence is its success state. Prove the fire half with a
|
|
79
|
+
mutation, and assert the patch actually landed before trusting a green run.
|
|
80
|
+
|
|
81
|
+
**4. `exit 0` with empty output is NOT "clean".** A rule whose glob matched no files reports
|
|
82
|
+
exactly like a rule that passed. Any rule shipped here must be loud when its input set is
|
|
83
|
+
empty. This is the specific defect that blocks stage 1 of the plan: in the source base a
|
|
84
|
+
fresh clone yields RC=0, 652 findings, zero errors — because 19 rules saw no files at all.
|
|
85
|
+
|
|
86
|
+
**5. "It can't be done in the engine" must be MEASURED, not assumed.** Every one of these was
|
|
87
|
+
stated confidently on 2026-09-16 and every one fell to a single command:
|
|
88
|
+
|
|
89
|
+
| the claim | what one command showed |
|
|
90
|
+
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| "a lint rule can't know a git date, so this stays a script" | a rule is plain JS; `execFileSync("git", …)` is legal in it. The real costs (per-file invocation, `--cache` keyed on content) are solvable, so this was a preference dressed as a limit |
|
|
92
|
+
| "we need our own glob expander" | `fs.globSync` ships in Node 22 and returned the identical set. 31 hand-written lines of regex existed for nothing |
|
|
93
|
+
| "the tool can't run a file that config excludes" | it can, and says so: `matches exclude … — running because you named it` |
|
|
94
|
+
|
|
95
|
+
⇒ Before writing machinery, **run the thing you are about to replace and paste its output.**
|
|
96
|
+
"I couldn't get it to work" is data about the attempt, not about the tool.
|
|
97
|
+
|
|
98
|
+
**6. A program shipped by this package MUST NOT depend on the consumer's cwd.** Measured
|
|
99
|
+
2026-09-16: three hooks here read their config as `provide("pkg", "cat package.json")`. The
|
|
100
|
+
consumer's session changed directory into a subfolder for unrelated reasons, `cat` failed, and
|
|
101
|
+
the Bash gate — which fails closed, correctly — denied **every command in that session**,
|
|
102
|
+
including the one that would undo it. The nudges next to it would have failed _silently_,
|
|
103
|
+
which is worse.
|
|
104
|
+
|
|
105
|
+
Resolve paths from the repository root (`git rev-parse --show-toplevel`) or from the module's
|
|
106
|
+
own location, never from where the caller happens to stand. The blast radius of a cwd
|
|
107
|
+
assumption is not this package — it is somebody else's whole session.
|
|
108
|
+
|
|
109
|
+
**7. Do not describe what you have not opened.** A `README` here nearly shipped the line
|
|
110
|
+
"MIT — see LICENSE" on 2026-09-16. There is no `LICENSE` file and `package.json` says
|
|
111
|
+
`UNLICENSED`. Publishing is irreversible and this repo is public: every factual claim in a
|
|
112
|
+
document meant for strangers gets checked against the disk in the same pass that writes it.
|
|
113
|
+
|
|
114
|
+
**8. 🔴 NEVER WRITE A GLOB OR A REGEX INSIDE A BLOCK COMMENT.** An asterisk followed by a
|
|
115
|
+
slash **ends the comment**, wherever it appears — in a path (a folder wildcard), in a regex
|
|
116
|
+
whose last literal is an asterisk (one matching bold markup, for instance), in a quoted
|
|
117
|
+
example. The rest of the comment becomes code, the file stops parsing, and the error points
|
|
118
|
+
at a line further down that is perfectly fine.
|
|
119
|
+
|
|
120
|
+
⚠️ Note this rule does not quote the sequence either, not even here. A documented example is
|
|
121
|
+
the thing that gets copied into code — and that is exactly how the fourth occurrence
|
|
122
|
+
happened: it was copied out of a comment written to explain the first three.
|
|
123
|
+
|
|
124
|
+
This is not a hypothetical and not a rare slip. **It fired four times in a single session on
|
|
125
|
+
2026-09-16** — three in the consumer, once here — and each time the diagnosis cost minutes
|
|
126
|
+
because `SyntaxError: Unexpected token '.'` says nothing about comments.
|
|
127
|
+
|
|
128
|
+
| instead of | write |
|
|
129
|
+
| --------------------------------------- | ----------------------------------------------------- |
|
|
130
|
+
| a glob with an asterisk and slash | spell it: "every folder under papers" |
|
|
131
|
+
| a regex with an asterisk before a slash | describe what it matched, in words |
|
|
132
|
+
| an example needing both | put it in a line comment (two slashes), never a block |
|
|
133
|
+
|
|
134
|
+
⇒ **In a block comment, prose describes the pattern; it never quotes it.** If the exact
|
|
135
|
+
characters matter, they belong in the code or in a line comment beside it.
|
|
136
|
+
|
|
137
|
+
**9. Measure the DEFECT before proposing the fix — and read "this is quick" as a warning.**
|
|
138
|
+
Rule 5 is about the tool you are replacing; this one is about the order of work.
|
|
139
|
+
|
|
140
|
+
1. **Show the defect**: command output, or a file quote with a line number. A proposed fix with
|
|
141
|
+
no exhibited defect is not a fix, it is a preference.
|
|
142
|
+
2. **Name the layer and the channel** it touches: ESLint rule · skill · hook · CLI · CI action
|
|
143
|
+
· path resolution. More than one is a conversation, not a commit.
|
|
144
|
+
3. **An adversarial second pass is encouraged, and it is not free.** Spend it on a fork in the
|
|
145
|
+
road, on anything that goes outward, and on a conclusion you are about to act on.
|
|
146
|
+
4. **A quick fix is almost never quick** — it is quick to _propose_ precisely because nothing
|
|
147
|
+
was opened.
|
|
148
|
+
|
|
149
|
+
Four proposals were made and withdrawn in one session on 2026-09-17 for exactly this reason —
|
|
150
|
+
[`docs/incidents.md`](docs/incidents.md).
|
|
151
|
+
|
|
152
|
+
**10. Effects live in adapters, and WHERE rpp IS INSTALLED lives in ONE of them.** Rule 6 generalised: the caller's cwd is one case of it. Checking logic — lint rules,
|
|
153
|
+
skills, hooks — must not know its own location, nor its distance from anything else. Every
|
|
154
|
+
answer to _where_ comes from `skills/paper-pipeline/scripts/consumer.mjs`, which adapts per
|
|
155
|
+
channel: own checkout · `node_modules` · plugin cache · CI. A skill naming a script by an
|
|
156
|
+
install-specific path in its own prose walks around that door, and 208 such literals across 190
|
|
157
|
+
lines do exactly that.
|
|
158
|
+
|
|
159
|
+
<!-- The port's path above is resolved by `npm run check` (vigiles lint), since #67. -->
|
|
160
|
+
<!-- vigiles:file skills/paper-pipeline/scripts/consumer.mjs -->
|
|
161
|
+
|
|
162
|
+
The layer rules for `src/` live in [`src/CLAUDE.md`](src/CLAUDE.md) and are enforced by the linter.
|
|
163
|
+
|
|
164
|
+
⏳ **Still owed: the install-path half** — a lint rule that makes an install-specific path literal
|
|
165
|
+
outside `consumer.mjs` a finding. Prose will not hold this class — four silent breakages happened
|
|
166
|
+
_while_ comments explaining the hazard sat directly above the code
|
|
167
|
+
([`docs/incidents.md`](docs/incidents.md)).
|
|
168
|
+
|
|
169
|
+
**11. Installing and using rpp must be as smooth as possible.** Count the actions between "I
|
|
170
|
+
want this" and "it works": every command to copy, flag to pass or file to edit is one more place
|
|
171
|
+
to give up. The target is `npm i` plus one command. A per-paper script, a manual TeX install or a
|
|
172
|
+
"now add this to your config" step is a defect in rpp, not a user task. The only exception is a
|
|
173
|
+
choice that really belongs to the user (irreversible, paid, privacy), and then it is named at the
|
|
174
|
+
moment it is asked. The converse holds too: an automatic step that can fail silently is worse
|
|
175
|
+
than an explicit one — it works, or it says loudly that it did not. Measured example of the
|
|
176
|
+
converse: Tectonic installs as one file but silently replaced Times with Latin Modern on a plain
|
|
177
|
+
`article` paper and still exited 0 (#35, #59).
|
|
178
|
+
|
|
179
|
+
## Before changing the command surface or a delivery channel — read the prior art
|
|
180
|
+
|
|
181
|
+
[`docs/prior-art/`](docs/prior-art/README.md) records how comparable tools solved the same
|
|
182
|
+
problems, each claim with the URL that was checked: Quarto and Vale (the domain and content
|
|
183
|
+
analogues), Biome (one tool, one config, one command), Danger and reviewdog (who decides to
|
|
184
|
+
fail a run), `unicorn/expiring-todo-comments` and Semgrep (checks that read a clock or a diff).
|
|
185
|
+
|
|
186
|
+
It is here as a POINTER and stays a pointer: this file is read on every turn, so it carries
|
|
187
|
+
the instruction and never the evidence — the same split as rules 9 and 10 and `docs/incidents.md`.
|
|
188
|
+
|
|
189
|
+
🔴 The argument "we need another command for X" is, in every tool examined, an argument that
|
|
190
|
+
the CONFIG is not declaring something. Check that before adding a verb.
|
|
191
|
+
|
|
192
|
+
## Distribution — one install path: npm, then `paperlint init`
|
|
193
|
+
|
|
194
|
+
`npm i -D paperlint` brings all the code — rules, skills, hooks, scripts. `paperlint init`
|
|
195
|
+
then does what only a command can, because it depends on the project it lands in: it finds the
|
|
196
|
+
papers directory and declares it in `package.json`, links each skill into `.claude/skills/`, writes
|
|
197
|
+
the hook commands into `.claude/settings.json` (vigiles' `mergeRegistrations`, reading
|
|
198
|
+
`plugin/hooks/hooks.json` as the one source), and offers a CI workflow pinned to the installed
|
|
199
|
+
release's tag. `paperlint doctor` reads all of it back. Details: `docs/install.md`.
|
|
200
|
+
|
|
201
|
+
**There is no Claude Code plugin or marketplace entry; it was removed in the release after 1.0.0
|
|
202
|
+
(#82).** Do not bring it back without answering these, each measured:
|
|
203
|
+
|
|
204
|
+
- **A plugin cannot carry the code.** Claude Code runs `npm ci --ignore-scripts` for a plugin only
|
|
205
|
+
when its root holds a `package.json` and a lockfile, with a 60-second timeout, and _"a failed or
|
|
206
|
+
skipped install never blocks the plugin"_: on a slow network the hooks load and fail with
|
|
207
|
+
`cannot find module vigiles`, silently.
|
|
208
|
+
- **The skills need the npm package anyway.** 23 of 24 skills run scripts under
|
|
209
|
+
`paper-pipeline/scripts`, which resolve only through `node_modules/paperlint/`.
|
|
210
|
+
A plugin-only consumer got skills whose first command fails.
|
|
211
|
+
- **Its manifests carried versions nothing updated** (0.0.1 and 0.1.0 while npm was at 1.0.0), and
|
|
212
|
+
Claude Code decides plugin updates from that number.
|
|
213
|
+
|
|
214
|
+
`plugin/hooks/hooks.json` stays where it is: it is not a plugin any more, it is the hook wiring
|
|
215
|
+
`paperlint init` merges into the consumer's settings.
|
|
216
|
+
|
|
217
|
+
## Delivery — how this repo's contents reach a consumer (measured 2026-09-11)
|
|
218
|
+
|
|
219
|
+
Three channels, each measured on a fixture rather than assumed. The consumer here is the
|
|
220
|
+
private knowledge base this was extracted from; nothing below is specific to it.
|
|
221
|
+
|
|
222
|
+
### Skills ship as `skills/`, and the consumer SYMLINKS them
|
|
223
|
+
|
|
224
|
+
**Do not move skills to `.claude/skills/` inside this repo.** They live in `skills/`
|
|
225
|
+
(`SHIPPED_SKILLS_DIR` in `skills/paper-pipeline/scripts/consumer.mjs`, read by the linker and the
|
|
226
|
+
install e2e), which is the standard layout. An ecosystem scan of 855 npm packages (by published
|
|
227
|
+
tarball, not repository) found **336 shipping `skills/<n>/SKILL.md` against 15 shipping `.claude/skills/`** — 22 : 1. The
|
|
228
|
+
top of the market is entirely on `skills/`: `@vitejs/devtools-kit` (330 896 downloads/wk),
|
|
229
|
+
`@slidev/cli` (56 809), `anthropics/skills` (175 673 stars).
|
|
230
|
+
|
|
231
|
+
**The consumer's side is a symlink per skill, made by `paperlint init` (`src/link-skills.ts`):**
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
<consumer>/.claude/skills/<name> -> node_modules/paperlint/skills/<name>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
⚠️ The skill's name in the listing comes from the **link directory's name**, not from
|
|
238
|
+
`name:` in the frontmatter — so the link must be named exactly as the skill.
|
|
239
|
+
|
|
240
|
+
⚠️ Do NOT rely on a consumer picking `.claude/skills/` up out of `node_modules` on its own.
|
|
241
|
+
It does happen — such a directory is an ordinary nested one — but only **lazily and
|
|
242
|
+
silently**, the first time the agent happens to read a file inside that package. A symlink
|
|
243
|
+
loads at startup, deterministically. Measured both ways on claude 2.1.268.
|
|
244
|
+
|
|
245
|
+
### Hooks ship as `.mjs`, NEVER as `.hook.ts`
|
|
246
|
+
|
|
247
|
+
Measured on a fixture — one hook, four locations, both halves (an input that must be denied
|
|
248
|
+
and one that must pass), exit code taken without a pipe:
|
|
249
|
+
|
|
250
|
+
| hook location | deny input | allow input |
|
|
251
|
+
| ----------------------------------------------------- | ----------------------- | --------------------------- |
|
|
252
|
+
| `node_modules/<pkg>/.claude/hooks/probe.hook.**mjs**` | RC=2, fires | RC=0, silent |
|
|
253
|
+
| symlink into `node_modules`, `.mjs` | RC=2, fires | RC=0, silent |
|
|
254
|
+
| local control, `.mjs` | RC=2, fires | RC=0, silent |
|
|
255
|
+
| `node_modules/<pkg>/.claude/hooks/probe.hook.**ts**` | RC=2 `cannot be loaded` | **RC=2 `cannot be loaded`** |
|
|
256
|
+
|
|
257
|
+
The last row is not "it blocks the dangerous thing" — it fails to load and therefore blocks
|
|
258
|
+
**everything**, including `echo hi`. A consumer in that state cannot run any Bash command,
|
|
259
|
+
and the one command that would repair it is also Bash.
|
|
260
|
+
|
|
261
|
+
⇒ **The package ships `.mjs`** — the consumer gets something that loads.
|
|
262
|
+
|
|
263
|
+
(Not established: _why_ the TypeScript loader refuses a path inside `node_modules`. The real
|
|
264
|
+
cause is swallowed by a `catch` in vigiles' `hook-runtime.js`, and calling `loadHookProgram`
|
|
265
|
+
directly measures a different load path — it fails even on the control. Knowing _that_ is
|
|
266
|
+
enough to choose the carrier.)
|
|
267
|
+
|
|
268
|
+
#### 🔴 CORRECTED 2026-09-12, when the first three hooks actually moved: THERE IS NO `.hook.ts` TWIN
|
|
269
|
+
|
|
270
|
+
This section used to promise «the `.hook.ts` source lives HERE and is typechecked HERE; the
|
|
271
|
+
package ships the compiled `.mjs`». Shipping the first three hooks retired that plan, and the
|
|
272
|
+
reason is worth keeping: **a twin can drift from its build, and nothing would notice.** One file
|
|
273
|
+
cannot.
|
|
274
|
+
|
|
275
|
+
What the twin was for was the CAPABILITY CHECK — `vigiles compile` refuses a hook that imports
|
|
276
|
+
anything but `vigiles/hook`, because the import list _is_ the capability surface. That check is a
|
|
277
|
+
function, `checkHookImports`, and `hooks/hooks.harness.mjs` runs it over every shipped
|
|
278
|
+
`.hook.mjs` directly. Same check, applied to the artifact that actually executes, with no second
|
|
279
|
+
file to keep in step. What is lost is `tsc` on the hook body and the typed `e.ctx` — named here
|
|
280
|
+
rather than left as an omission.
|
|
281
|
+
|
|
282
|
+
⚠️ **`checkHookImports` IS A TEXT REGEX, so it counts an import-shaped sentence in a COMMENT.**
|
|
283
|
+
Measured 2026-09-12: the check failed on `paper-edit-guard.hook.mjs`'s own docblock, which quoted
|
|
284
|
+
a rejected import while explaining why it was rejected. Do not loosen the check to make prose
|
|
285
|
+
fit — it is the same check any future `compile`/`lint` pass applies to the shipped file. Describe
|
|
286
|
+
a forbidden import in words instead of writing one.
|
|
287
|
+
|
|
288
|
+
#### 🔴 AND NOT A THIN SPEC IN THE CONSUMER EITHER — measured, and it is the form that looks right
|
|
289
|
+
|
|
290
|
+
The obvious alternative is a small `.hook.ts` in the consumer that pulls the decision logic out of
|
|
291
|
+
this package. It RUNS — deny input RC=2 with the reason, allow input RC=0 and silent — and it
|
|
292
|
+
cannot be maintained:
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
$ npx vigiles compile
|
|
296
|
+
✗ .vigiles/hooks/probe.hook.ts — hook program uses capabilities outside `vigiles/hook`:
|
|
297
|
+
<pkg>/hooks/decide.mjs — only the sanctioned API is allowed (capability = API surface).
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`compile` is also what writes the tamper-evident stamp, so a hook it refuses **can never be
|
|
301
|
+
re-stamped** — and the runtime fails CLOSED on a stamp that no longer matches its source:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
vigiles: hook … does not match its compiled stamp (tampered).
|
|
305
|
+
… the way out is a FILE WRITE, not a command — this refusal blocks the recompile too.
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Measured end to end: editing such a file makes the gate refuse `echo hi`, and the only steady
|
|
309
|
+
state is clearing the stamp to `{}` and running permanently unstamped. Shipping the whole program
|
|
310
|
+
keeps the stamp question from arising (no sidecar ⇒ no check) and pins the source by lockfile
|
|
311
|
+
integrity instead — stronger than a local stamp, since a consumer cannot hand-edit an installed
|
|
312
|
+
tree without the next install reverting it.
|
|
313
|
+
|
|
314
|
+
#### How a consumer wires a shipped hook
|
|
315
|
+
|
|
316
|
+
`.claude/settings.json`, one block per hook, pointing straight into the install — no symlink and
|
|
317
|
+
no compile step on the consumer's side:
|
|
318
|
+
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"type": "command",
|
|
322
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/node_modules/vigiles/dist/cli.js\" hook-runtime run-program \"$CLAUDE_PROJECT_DIR/node_modules/paperlint/hooks/paper-edit-guard.hook.mjs\""
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
⚠️ **A shipped hook cannot import a sibling module of this package** — capability closure being
|
|
327
|
+
the point — so the papers-root resolver is spelled out in all three hook files. Duplication that
|
|
328
|
+
cannot be removed is CHECKED instead: part VII of the harness compares the captured values
|
|
329
|
+
against each other, rather than grepping for a literal (a substring search finds the same text in
|
|
330
|
+
the prose _about_ the value one line above it).
|
|
331
|
+
|
|
332
|
+
### `vigiles` is a devDependency, and its pin is TIED to the consumer's
|
|
333
|
+
|
|
334
|
+
`vigiles/hook` resolves **upward** from a hook inside a package — measured:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
resolve OK -> <consumer>/node_modules/vigiles/dist/hook.js
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
So this package needs no copy of its own at the consumer's runtime; it needs `vigiles` only
|
|
341
|
+
for its own `vigiles test` and `vigiles compile`. That is `devDependencies`, which `npm i` of
|
|
342
|
+
a dependency does not install. Putting it in `dependencies` risks npm installing a **second**
|
|
343
|
+
copy under `node_modules/paperlint/node_modules/vigiles` whenever the ranges
|
|
344
|
+
drift — two runtimes, two sets of stamps and state.
|
|
345
|
+
|
|
346
|
+
🔴 **THE PARAGRAPH ABOVE WAS TRUE AND THE INSTALL DID THE OPPOSITE — measured 2026-09-17.**
|
|
347
|
+
`devDependencies` is not the only entry naming `vigiles`: `peerDependencies` names it too, and
|
|
348
|
+
**npm 7+ installs peers automatically**. So every consumer got it anyway, together with its
|
|
349
|
+
transitive weight. `npm pack`, then install the tarball into an empty project:
|
|
350
|
+
|
|
351
|
+
| | packages | `du -sm node_modules` |
|
|
352
|
+
| ------------------------------------------------------- | -------: | --------------------: |
|
|
353
|
+
| peer as declared before | 188 | **142 MB** |
|
|
354
|
+
| `peerDependenciesMeta: { vigiles: { optional: true } }` | 164 | **56 MB** |
|
|
355
|
+
|
|
356
|
+
The 86 MB are `@ast-grep/napi` (51 MB) and `typescript` (23 MB), pulled through `vigiles` — and
|
|
357
|
+
paid for by a consumer who only wants the ESLint rules and never loads a hook.
|
|
358
|
+
|
|
359
|
+
`optional: true` is the entry that matches what this section already argues: the consumer brings
|
|
360
|
+
its own `vigiles` _when it uses the hooks_, and npm stops deciding that for them. Both halves
|
|
361
|
+
measured on the 56 MB tree: `eslint-rules/latex-language.mjs` and
|
|
362
|
+
`skills/paper-pipeline/scripts/pipeline-check.mjs` load and run (RC=0), while
|
|
363
|
+
`hooks/paper-edit-guard.hook.mjs` fails with `ERR_MODULE_NOT_FOUND` — which is this contract
|
|
364
|
+
working, not a defect, exactly as argued below.
|
|
365
|
+
|
|
366
|
+
⚠️ The consumer in this project's own base is unaffected: it declares `vigiles` itself
|
|
367
|
+
(`devDependencies: ^27.2.0`), so nothing about its tree changes.
|
|
368
|
+
|
|
369
|
+
### The `.bib` parser is optional for the same reason, and the failure says so out loud
|
|
370
|
+
|
|
371
|
+
`@retorquere/bibtex-parser` is imported at exactly one site
|
|
372
|
+
(`skills/paper-pipeline/scripts/extract-ref-facts.mjs`, and already through a dynamic
|
|
373
|
+
`await import`), and it costs **15 MB of a 56 MB tree**: 9 MB itself, plus
|
|
374
|
+
`wink-eng-lite-web-model` (4 MB, an English NLP model) and `unicode2latex` (2 MB). That is 27%
|
|
375
|
+
of the install for one call that only a consumer extracting bibliography facts ever makes.
|
|
376
|
+
|
|
377
|
+
| | packages | `du -sm node_modules` |
|
|
378
|
+
| ------------------------------------------ | -------: | --------------------: |
|
|
379
|
+
| after the `vigiles` peer was made optional | 164 | 56 MB |
|
|
380
|
+
| parser moved to an optional peer as well | 149 | **39 MB** |
|
|
381
|
+
|
|
382
|
+
🔴 **`optionalDependencies` is the wrong entry and was tried first** — npm _installs_ those and
|
|
383
|
+
only tolerates failure, so the weight stays. What makes a dependency genuinely opt-in is
|
|
384
|
+
`peerDependencies` + `peerDependenciesMeta: { optional: true }`, the same pair used for `vigiles`.
|
|
385
|
+
It stays in `devDependencies` too, because this package's own harnesses parse `.bib`.
|
|
386
|
+
|
|
387
|
+
⚠️ A silent skip here would be the worst outcome: a missing checker and a passing one look
|
|
388
|
+
identical, and "the bibliography was not checked" reads as "the bibliography is fine". So the
|
|
389
|
+
absence throws, and the message carries the cure rather than the diagnosis:
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
parsing .bib requires @retorquere/bibtex-parser — it is declared OPTIONAL because it weighs 15 MB…
|
|
393
|
+
Install: npm i -D @retorquere/bibtex-parser
|
|
394
|
+
Why not our own regex: measured 26.08 — the regex gave 0 entries on both real files…
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
🔴 **Therefore the pin here and the pin in the consumer move TOGETHER, in one pass.** A major
|
|
398
|
+
mismatch means a hook compiled by one version is executed by another: the stamp does not
|
|
399
|
+
verify, the hook does not load, and `PreToolUse` refuses every command. That already happened
|
|
400
|
+
in the consumer on 2026-09-10 (25.1.0 -> 27.1.4) and cost real recovery work.
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
npm ls vigiles # prints `invalid` when what is installed does not satisfy the manifest
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
⚠️ **What that command does NOT tell you, and the boundary matters because the command reads
|
|
407
|
+
like a freshness check.** It compares what is INSTALLED against this repo's MANIFEST. It says
|
|
408
|
+
nothing about the registry. Measured 2026-09-16: manifest `^27.1.4`, installed `27.1.6`,
|
|
409
|
+
published `27.2.0` — exit code **0**, because the range is satisfied. The repo had been one
|
|
410
|
+
minor behind for days and every local check was green.
|
|
411
|
+
|
|
412
|
+
#### Dependabot carries the half `npm ls` cannot — and its two delays STACK
|
|
413
|
+
|
|
414
|
+
That staleness is why `.github/dependabot.yml` exists here. The reasoning is worth keeping
|
|
415
|
+
because the obvious objection to a bot in this org is already recorded and does NOT apply:
|
|
416
|
+
a bot was switched off in a sibling repository for burning Actions minutes — **that
|
|
417
|
+
repository is private**. This one is public, minutes are free, so the objection does not
|
|
418
|
+
travel. If this repo is ever made private, revisit the file along with it.
|
|
419
|
+
|
|
420
|
+
🔴 **A new release does NOT wake the bot.** Two delays add up, and the second one is invisible
|
|
421
|
+
until you read the reference:
|
|
422
|
+
|
|
423
|
+
| | default | what the docs say |
|
|
424
|
+
| ------------------- | ---------: | --------------------------------------------------------------------------------------- |
|
|
425
|
+
| `schedule.interval` | — | the check runs on the schedule and only on the schedule |
|
|
426
|
+
| **`cooldown`** | **3 days** | _"a new version is not considered for a version update until 3 days after its release"_ |
|
|
427
|
+
|
|
428
|
+
With the weekly schedule this file shipped with first, the window was **3–10 days**. It is now
|
|
429
|
+
`daily`, and `vigiles` is listed in `cooldown.exclude`, so for THIS package the window is one
|
|
430
|
+
schedule tick.
|
|
431
|
+
|
|
432
|
+
**Why `vigiles` and nothing else is exempt:** the cooldown guards against a release that gets
|
|
433
|
+
yanked hours later. That is a real risk for a third-party package and an empty one for our own
|
|
434
|
+
— we would be the ones yanking it, and we can ship several versions of it in a single day, so
|
|
435
|
+
a three-day hold would have the bot proposing the version from the day before yesterday.
|
|
436
|
+
|
|
437
|
+
🔴 **And the conclusion is bigger than a cadence knob: for our OWN package no bot schedule is
|
|
438
|
+
the primary path, because no schedule can outrun same-day releases.** The primary path is the
|
|
439
|
+
rule already recorded in the consumer's base — merge a PR in `vigiles`, bump every consumer in
|
|
440
|
+
the same pass. The bot is the backstop for the case that actually bit us: the rule named ONE
|
|
441
|
+
consumer while there were two, and this repo sat forgotten on `^27.1.4`.
|
|
442
|
+
|
|
443
|
+
⚠️ **What the file does not control**, recorded because the sibling repo already lost a day to
|
|
444
|
+
it: `dependabot.yml` configures _version_ updates only. **Security** updates are a separate
|
|
445
|
+
mechanism driven by advisories and a repository SETTING; their cadence cannot be changed from
|
|
446
|
+
this file, and deleting the file would not stop them.
|
|
447
|
+
|
|
448
|
+
Need it now rather than at the next tick: **Insights → Dependency graph → Dependabot → Check
|
|
449
|
+
for updates**.
|
|
450
|
+
|
|
451
|
+
## The guard against a green zero
|
|
452
|
+
|
|
453
|
+
Rule 4 is enforced, not asserted: `scripts/rules-see-files.mjs` loads `eslint.config.mjs`,
|
|
454
|
+
lints the repository, and asks ESLint for the effective config of every linted file. A rule
|
|
455
|
+
enabled for **zero** files is named and the script exits 1.
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
node scripts/rules-see-files.mjs # also part of npm run check
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
It is per RULE, not per glob, and that distinction is the point: a rule can be enabled in one
|
|
462
|
+
block whose glob is empty while a different block is busy, so "some glob matched something" is
|
|
463
|
+
not evidence about the rule you care about. Both halves are tested
|
|
464
|
+
(`scripts/rules-see-files.harness.mjs`) and both directions are mutated
|
|
465
|
+
(`scripts/rules-see-files.mutations.mjs` — under-reporting and over-reporting must die on
|
|
466
|
+
_different_ assertions, or only one half of the guard is really tested).
|
|
467
|
+
|
|
468
|
+
## Mutations — hand-written batteries are deprecated (#52)
|
|
469
|
+
|
|
470
|
+
The `*.mutations.mjs` batteries (string replacements of source lines, run through
|
|
471
|
+
`lib/mutation-driver.mjs`) are being removed. The idea stays — a test must be seen going red
|
|
472
|
+
when the code breaks — but the vehicle is not this one.
|
|
473
|
+
|
|
474
|
+
- **Do not create a new `*.mutations.mjs`**, and **do not add cases to an existing one.**
|
|
475
|
+
- **Record what a test guards as a comment directly above its assertion** (`// Guards: …`).
|
|
476
|
+
- The rule is enforced, not asked for: `scripts/mutation-batteries-frozen.mjs` (part of
|
|
477
|
+
`npm run check` and CI) fails on a battery missing from `scripts/mutation-batteries.frozen.json`,
|
|
478
|
+
on a listed battery with more or fewer cases than recorded, and on a listed file that is gone.
|
|
479
|
+
The list may only shrink — delete a battery or a case, then delete or lower its entry.
|
|
480
|
+
- The intended replacement is a real mutation-testing tool (StrykerJS) or nothing; that is
|
|
481
|
+
decided in #52, not in a pull request that happens to touch a battery.
|
|
482
|
+
|
|
483
|
+
The batteries that remain still run (`node scripts/run-mutations.mjs`) until #52 retires them.
|
|
484
|
+
|
|
485
|
+
## Cost
|
|
486
|
+
|
|
487
|
+
⛽ **This repository is PUBLIC, so its Actions minutes are FREE.** Verified against the API on
|
|
488
|
+
2026-09-17: `"private": false`, `"visibility": "public"`, and three active workflows — `ci`,
|
|
489
|
+
`dependabot auto-merge`, and Dependabot's own updates runner.
|
|
490
|
+
|
|
491
|
+
🔴 **This paragraph said the exact opposite until now, and the correction is the lesson, not the
|
|
492
|
+
fact.** It read «This is a **private** repository … Until then there is no CI here, and that is
|
|
493
|
+
deliberate» — both halves false, and false in the file an agent loads FIRST. The flip to public
|
|
494
|
+
happened on 2026-09-12 and _was_ recorded, at `.github/workflows/ci.yml:8-9`, which is a file
|
|
495
|
+
nobody opens before deciding whether there is any CI to check. Reported as issue #6.
|
|
496
|
+
|
|
497
|
+
⚠️ So the rule this leaves behind is about WHERE a correction lands: a measurement written into
|
|
498
|
+
the artifact it describes is not written down for the reader who needs it. **Status that changes
|
|
499
|
+
what a session DOES belongs in this file**; the workflow header can carry the detail.
|
|
500
|
+
|
|
501
|
+
What stays true, because the reasoning outlives the flip: minutes on a **private** repo come out
|
|
502
|
+
of the account-wide 3000/month shared with every other private repo, and the budget is decided
|
|
503
|
+
**before** the first workflow file, not after the first bill. **If this repository is ever made
|
|
504
|
+
private again, this section and `.github/dependabot.yml` are revisited together** — the bot is
|
|
505
|
+
justified two hundred lines above precisely by these minutes being free.
|
|
506
|
+
|
|
507
|
+
## Testing
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
npm test # vitest over *.test.ts, then the vigiles harnesses
|
|
511
|
+
npx vitest run <file> # one unit test
|
|
512
|
+
npx vigiles test <file> # one harness
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Two kinds of test, told apart by what the file imports.** A `*.harness.*` file tests the agent
|
|
516
|
+
surface and imports `runHook`, `runHarnessTest` or `runEval` from vigiles; everything else is a plain
|
|
517
|
+
unit test, `*.test.ts`, run by vitest — and new tests are TypeScript. Older harnesses that
|
|
518
|
+
import none of the three are frozen in `scripts/harness-api.frozen.json`, which only shrinks;
|
|
519
|
+
`scripts/harness-api.test.ts` parses every harness's imports and holds both halves (#77). vitest
|
|
520
|
+
exits 1 when no file matches, and it transpiles without type-checking, so `npm run check` runs
|
|
521
|
+
`tsc -p tsconfig.test.json` as its own gate.
|
|
522
|
+
|
|
523
|
+
⚠️ **Not `vigiles test .`** — the `.` is read as a FILE, the runner dies with
|
|
524
|
+
`ERR_UNSUPPORTED_DIR_IMPORT`, and it still exits 0. See the measured table below.
|
|
525
|
+
|
|
526
|
+
Skills are tested **through vigiles** — a colocated `<skill>.harness.mjs` beside the skill.
|
|
527
|
+
(This read «Skills, if and when they arrive» until 2026-09-17; there are 24 of them under
|
|
528
|
+
`skills/` carrying a `SKILL.md`, and the README's opening line claimed the repository was
|
|
529
|
+
empty — issue #6.) Not through a bespoke script: a home-grown runner here once printed
|
|
530
|
+
confident, byte-identical "clean" verdicts for three different skills that had never loaded.
|
|
531
|
+
|
|
532
|
+
### Every npm script takes an exclusive lock, and that is not ceremony
|
|
533
|
+
|
|
534
|
+
`npm run *` in this repository goes through `scripts/exclusive.mjs`, which holds
|
|
535
|
+
`.vigiles/exclusive.lock` for the duration. A second gate started while one is running does not
|
|
536
|
+
queue and does not race — it **refuses**, names the holder, and exits 3.
|
|
537
|
+
|
|
538
|
+
🔴 **The reason is that the mutation batteries edit the working tree in place.** That strategy is
|
|
539
|
+
deliberate (see `lib/mutation-driver.mjs` — copying the repo per mutation costs minutes instead
|
|
540
|
+
of seconds), and its one cost is that any parallel reader sees a source file mid-mutation. The
|
|
541
|
+
resulting failure is **false, non-deterministic, and blames the wrong file**: it reports a broken
|
|
542
|
+
assertion, not a mutation, and it reads as "the suite is flaky". That has already cost a wrong
|
|
543
|
+
conclusion here — two runs in a row produced _different_ error messages and the diagnosis "I broke
|
|
544
|
+
round-diff" was incorrect.
|
|
545
|
+
|
|
546
|
+
A prose instruction "don't run them at the same time" existed and did not work: prose does not
|
|
547
|
+
execute, so it does not apply to the person in the other terminal, the agent, or the editor with
|
|
548
|
+
tests on save. Measured live, with the batteries running:
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
$ npm test
|
|
552
|
+
🔴 refused: this repository is busy with a run that EDITS FILES IN PLACE.
|
|
553
|
+
held by: pid 6645, "node scripts/run-mutations.mjs", since 2026-09-17T05:22:28.757Z
|
|
554
|
+
RC=3
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
⚠️ A lock left behind by a process that no longer exists is **taken over** with a message, not
|
|
558
|
+
respected. Otherwise one interrupted run would block the repository forever, and the first cure
|
|
559
|
+
anybody reaches for would be "delete the lock by hand" — i.e. switching the mechanism off.
|
|
560
|
+
|
|
561
|
+
## `npm test` — `--min=1` stays, and here is what it is for
|
|
562
|
+
|
|
563
|
+
The script is `vigiles test --min=1`. It went green on 2026-09-11 when the first harnesses
|
|
564
|
+
landed; before that it correctly exited 1:
|
|
565
|
+
|
|
566
|
+
```
|
|
567
|
+
✗ vigiles test: --min=1 but only 0 test file(s) matched — evals never executed
|
|
568
|
+
(check the paths/globs, or that the run was reached).
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Do **not** "fix" a future red by dropping `--min` — the flag is the only thing standing
|
|
572
|
+
between "every test passed" and "no test ran", which is rule 4 applied to the test runner
|
|
573
|
+
itself.
|
|
574
|
+
|
|
575
|
+
🔴 **Two ways this command lies if written differently, both measured 2026-09-11:**
|
|
576
|
+
|
|
577
|
+
| form | what happens | exit |
|
|
578
|
+
| ---------------------- | --------------------------------------------------------------------------- | ----- |
|
|
579
|
+
| `vigiles test .` | `.` is read as a FILE — `ERR_UNSUPPORTED_DIR_IMPORT`, uncaught, runner dies | **0** |
|
|
580
|
+
| `vigiles test` | `No **/*.harness.{mjs,cjs,js,mts,cts,ts} files found.` | **0** |
|
|
581
|
+
| `vigiles test --min=1` | names the empty match and fails | **1** |
|
|
582
|
+
|
|
583
|
+
The first row is the worse one: the runner crashed with a stack trace and still reported
|
|
584
|
+
success. `package.json` shipped `vigiles test .` from the initial scaffold until this was
|
|
585
|
+
measured — so the repo's own test command had never once executed a test, and said nothing.
|
|
586
|
+
|
|
587
|
+
## Commits
|
|
588
|
+
|
|
589
|
+
Conventional-commit subject, body says what was MEASURED, not what was intended. A number in
|
|
590
|
+
a commit message that no command produced is the thing this repo exists to make impossible.
|
|
591
|
+
|
|
592
|
+
**The subject is also the release.** Every push to `main` runs semantic-release
|
|
593
|
+
(`.github/workflows/release.yml`), and it reads the squash commit, which is the PR title:
|
|
594
|
+
`feat:` → minor, `fix:` or `perf:` → patch, `!` or a `BREAKING CHANGE:` footer → major,
|
|
595
|
+
`docs:` `chore:` `ci:` `test:` `refactor:` → no release. `pr-title.yml` rejects a title without
|
|
596
|
+
a conventional prefix, and for a one-commit PR checks the commit subject too, since GitHub
|
|
597
|
+
squashes to that. npm, the git tag and the GitHub Release always carry the same version. Never
|
|
598
|
+
`npm publish` by hand.
|