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
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colocated test for `scripts/mutation-batteries-frozen.mjs` — the shrink-only guard (#52).
|
|
3
|
+
* Run: `npx vigiles test scripts/mutation-batteries-frozen.harness.mjs`
|
|
4
|
+
*
|
|
5
|
+
* Both halves:
|
|
6
|
+
* I. QUIET on this repository — every battery on disk is listed, every listed one exists,
|
|
7
|
+
* and every count matches. Plus a non-vacuity row: the walk found batteries at all.
|
|
8
|
+
* II. RED on a planted tree — a new battery, a grown one, a shrunk one, a stale list entry
|
|
9
|
+
* and an uncountable table are each named; an unchanged battery beside them is not.
|
|
10
|
+
* III. The case counter on each table shape the repository actually uses.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ Assertions at the TOP LEVEL: `vigiles test` imports the file and counts "did not throw"
|
|
13
|
+
* as a pass.
|
|
14
|
+
*/
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import {
|
|
17
|
+
mkdirSync,
|
|
18
|
+
mkdtempSync,
|
|
19
|
+
realpathSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
symlinkSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { dirname, join } from "node:path";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
|
|
28
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const ROOT = dirname(HERE);
|
|
30
|
+
const { checkFrozen, countCases, findBatteries, FROZEN_FILE } = await import(
|
|
31
|
+
join(HERE, "mutation-batteries-frozen.mjs")
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
let n = 0;
|
|
35
|
+
const check = (label, cond, detail = "") => {
|
|
36
|
+
n++;
|
|
37
|
+
assert.ok(cond, detail ? `${label}\n${detail}` : label);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ── I. quiet on this repository ─────────────────────────────────────────────────────────
|
|
41
|
+
{
|
|
42
|
+
const { problems, found, frozen } = checkFrozen(ROOT);
|
|
43
|
+
check(
|
|
44
|
+
"the walk finds batteries in this repository — otherwise the quiet below proves nothing",
|
|
45
|
+
found.length > 0,
|
|
46
|
+
);
|
|
47
|
+
check(
|
|
48
|
+
"this repository passes: nothing new, nothing grown, nothing stale",
|
|
49
|
+
problems.length === 0,
|
|
50
|
+
problems.join("\n"),
|
|
51
|
+
);
|
|
52
|
+
check(
|
|
53
|
+
"the frozen list names exactly the batteries on disk",
|
|
54
|
+
JSON.stringify(Object.keys(frozen).sort()) === JSON.stringify(found),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── II. red on a planted tree ───────────────────────────────────────────────────────────
|
|
59
|
+
const driverBattery = (k) =>
|
|
60
|
+
`import { runMutations } from "./lib/mutation-driver.mjs";\n` +
|
|
61
|
+
`process.exit(runMutations({ root: ".", cases: [${Array.from({ length: k }, (_, i) => `{ name: "c${String(i)}" }`).join(", ")}] }));\n`;
|
|
62
|
+
|
|
63
|
+
const TMP = realpathSync(mkdtempSync(join(tmpdir(), "rpp-frozen-h-")));
|
|
64
|
+
process.on("exit", () => rmSync(TMP, { recursive: true, force: true }));
|
|
65
|
+
mkdirSync(join(TMP, "scripts"), { recursive: true });
|
|
66
|
+
mkdirSync(join(TMP, "src"), { recursive: true });
|
|
67
|
+
mkdirSync(join(TMP, "node_modules", "dep"), { recursive: true });
|
|
68
|
+
writeFileSync(join(TMP, "src", "same.mutations.mjs"), driverBattery(3));
|
|
69
|
+
writeFileSync(join(TMP, "src", "grown.mutations.mjs"), driverBattery(4));
|
|
70
|
+
writeFileSync(join(TMP, "src", "shrunk.mutations.mjs"), driverBattery(1));
|
|
71
|
+
writeFileSync(join(TMP, "src", "planted.mutations.mjs"), driverBattery(1));
|
|
72
|
+
writeFileSync(
|
|
73
|
+
join(TMP, "src", "opaque.mutations.mjs"),
|
|
74
|
+
`const ALL = [{}, {}];\nrunMutations({ cases: ALL.filter(Boolean) });\n`,
|
|
75
|
+
);
|
|
76
|
+
// Not the repository's own files: a dependency, and a symlinked view of `src/`.
|
|
77
|
+
writeFileSync(
|
|
78
|
+
join(TMP, "node_modules", "dep", "theirs.mutations.mjs"),
|
|
79
|
+
driverBattery(9),
|
|
80
|
+
);
|
|
81
|
+
symlinkSync(join(TMP, "src"), join(TMP, "view"), "dir");
|
|
82
|
+
writeFileSync(
|
|
83
|
+
join(TMP, FROZEN_FILE),
|
|
84
|
+
JSON.stringify({
|
|
85
|
+
batteries: {
|
|
86
|
+
"src/same.mutations.mjs": { cases: 3 },
|
|
87
|
+
"src/grown.mutations.mjs": { cases: 2 },
|
|
88
|
+
"src/shrunk.mutations.mjs": { cases: 2 },
|
|
89
|
+
"src/opaque.mutations.mjs": { cases: 2 },
|
|
90
|
+
"src/gone.mutations.mjs": { cases: 5 },
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
{
|
|
95
|
+
const { problems, found } = checkFrozen(TMP);
|
|
96
|
+
const about = (file) => problems.filter((p) => p.startsWith(`${file}:`));
|
|
97
|
+
const all = problems.join("\n");
|
|
98
|
+
check(
|
|
99
|
+
"the walk skips node_modules and does not follow symlinks — each battery once",
|
|
100
|
+
JSON.stringify(found) ===
|
|
101
|
+
JSON.stringify([
|
|
102
|
+
"src/grown.mutations.mjs",
|
|
103
|
+
"src/opaque.mutations.mjs",
|
|
104
|
+
"src/planted.mutations.mjs",
|
|
105
|
+
"src/same.mutations.mjs",
|
|
106
|
+
"src/shrunk.mutations.mjs",
|
|
107
|
+
]),
|
|
108
|
+
JSON.stringify(found),
|
|
109
|
+
);
|
|
110
|
+
check(
|
|
111
|
+
"🔴 a NEW battery fails, pointing at #52",
|
|
112
|
+
about("src/planted.mutations.mjs").some((p) =>
|
|
113
|
+
p.includes(
|
|
114
|
+
"new hand-written mutation batteries are not accepted, see #52",
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
all,
|
|
118
|
+
);
|
|
119
|
+
check(
|
|
120
|
+
"🔴 a battery that GREW fails, naming both counts",
|
|
121
|
+
about("src/grown.mutations.mjs").some(
|
|
122
|
+
(p) => p.includes("4 cases, frozen at 2") && p.includes("#52"),
|
|
123
|
+
),
|
|
124
|
+
all,
|
|
125
|
+
);
|
|
126
|
+
check(
|
|
127
|
+
"🔴 a listed file that no longer exists fails: remove it from the frozen list",
|
|
128
|
+
about("src/gone.mutations.mjs").some(
|
|
129
|
+
(p) =>
|
|
130
|
+
p.includes("remove it from") &&
|
|
131
|
+
p.includes("the list must shrink with the code"),
|
|
132
|
+
),
|
|
133
|
+
all,
|
|
134
|
+
);
|
|
135
|
+
check(
|
|
136
|
+
"a battery that SHRANK fails until its recorded count is lowered to the new one",
|
|
137
|
+
about("src/shrunk.mutations.mjs").some((p) =>
|
|
138
|
+
p.includes('lower "cases" to 1'),
|
|
139
|
+
),
|
|
140
|
+
all,
|
|
141
|
+
);
|
|
142
|
+
check(
|
|
143
|
+
"a table the counter cannot read is a failure, never a guessed number",
|
|
144
|
+
about("src/opaque.mutations.mjs").some((p) =>
|
|
145
|
+
p.includes("cannot be counted"),
|
|
146
|
+
),
|
|
147
|
+
all,
|
|
148
|
+
);
|
|
149
|
+
check(
|
|
150
|
+
"control: an unchanged battery beside them is not reported",
|
|
151
|
+
about("src/same.mutations.mjs").length === 0,
|
|
152
|
+
all,
|
|
153
|
+
);
|
|
154
|
+
check(
|
|
155
|
+
"exactly the five planted defects, nothing else",
|
|
156
|
+
problems.length === 5,
|
|
157
|
+
all,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── III. the counter, on each table shape in use ────────────────────────────────────────
|
|
162
|
+
const cases = (src) => countCases(src, "x.mutations.mjs");
|
|
163
|
+
check("inline `cases: [...]` literal", cases(driverBattery(3)).count === 3);
|
|
164
|
+
check(
|
|
165
|
+
"`cases: TABLE.map(...)` counts TABLE",
|
|
166
|
+
cases(
|
|
167
|
+
`const T = [[1], [2], [3], [4]];\nrunMutations({ cases: T.map(([a]) => ({ a })) });`,
|
|
168
|
+
).count === 4,
|
|
169
|
+
);
|
|
170
|
+
check(
|
|
171
|
+
"`const CASES = A.map(...)` then `cases: CASES`, and the shorthand `{ cases }`",
|
|
172
|
+
cases(
|
|
173
|
+
`const A = [1, 2];\nconst CASES = A.map((x) => x);\nrunMutations({ cases: CASES });`,
|
|
174
|
+
).count === 2 &&
|
|
175
|
+
cases(`const cases = [1, 2, 3];\nrunMutations({ root, cases });`).count ===
|
|
176
|
+
3,
|
|
177
|
+
);
|
|
178
|
+
check(
|
|
179
|
+
"a spread of another const table adds its length",
|
|
180
|
+
cases(`const A = [1, 2];\nrunMutations({ cases: [...A, 3] });`).count === 3,
|
|
181
|
+
);
|
|
182
|
+
check(
|
|
183
|
+
"a self-contained battery: the loop over M counts, the results loop over `rows = []` does not",
|
|
184
|
+
cases(
|
|
185
|
+
`const M = [["a"], ["b"], ["c"]];\nconst rows = [];\nfor (const [x] of M) rows.push(x);\nfor (const r of rows) console.log(r);`,
|
|
186
|
+
).count === 3,
|
|
187
|
+
);
|
|
188
|
+
check(
|
|
189
|
+
"a file with no table is an error, not zero",
|
|
190
|
+
"error" in cases(`console.log("hi");`),
|
|
191
|
+
);
|
|
192
|
+
check(
|
|
193
|
+
"two driver calls are ambiguous, not summed",
|
|
194
|
+
"error" in
|
|
195
|
+
cases(`runMutations({ cases: [1] });\nrunMutations({ cases: [2] });`),
|
|
196
|
+
);
|
|
197
|
+
check(
|
|
198
|
+
"findBatteries on this repository matches only `*.mutations.mjs` names",
|
|
199
|
+
findBatteries(ROOT).every((f) => f.endsWith(".mutations.mjs")),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
console.log(
|
|
203
|
+
`✓ ${String(n)} assertions passed — mutation-batteries-frozen: quiet here, red on a new, grown, shrunk, stale or uncountable battery`,
|
|
204
|
+
);
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mutation-batteries-frozen.mjs — the hand-written mutation batteries may only SHRINK.
|
|
4
|
+
*
|
|
5
|
+
* Issue #52 retires the `*.mutations.mjs` pattern: string replacements of source lines, run
|
|
6
|
+
* through `lib/mutation-driver.mjs`. The idea behind it stays (a test must be seen going red
|
|
7
|
+
* when the code breaks); the vehicle is being replaced by a real mutation-testing tool, or by
|
|
8
|
+
* nothing — that is decided in #52, not here. Until the last battery is gone, this check holds
|
|
9
|
+
* the line so the pattern cannot grow back while it is being removed:
|
|
10
|
+
*
|
|
11
|
+
* 1. a `*.mutations.mjs` on disk that is not in the frozen list fails — a NEW battery;
|
|
12
|
+
* 2. a listed file that no longer exists fails — the list must shrink with the code;
|
|
13
|
+
* 3. a listed battery whose case table grew fails — no new cases in an old battery;
|
|
14
|
+
* 4. a listed battery whose case table shrank fails until the recorded count is lowered —
|
|
15
|
+
* otherwise a removed case leaves room for a different one to be added later.
|
|
16
|
+
*
|
|
17
|
+
* The frozen list and the counts live in `mutation-batteries.frozen.json`, next to this file.
|
|
18
|
+
*
|
|
19
|
+
* ── HOW CASES ARE COUNTED, AND WHY NOT BY IMPORTING ───────────────────────────────────────
|
|
20
|
+
* A battery runs on import and ends with `process.exit`, so it cannot be imported to read its
|
|
21
|
+
* table. The file is PARSED instead (TypeScript's parser, already a dependency) and the case
|
|
22
|
+
* table is found structurally:
|
|
23
|
+
*
|
|
24
|
+
* - batteries on the shared driver pass `cases` to `runMutations({ ... })`;
|
|
25
|
+
* - the older self-contained batteries loop `for (const [...] of M)` over a top-level table.
|
|
26
|
+
*
|
|
27
|
+
* The table expression is then resolved to an array literal: an identifier goes to its
|
|
28
|
+
* top-level `const` initializer, `X.map(...)` goes to `X` (map keeps the length), and a spread
|
|
29
|
+
* `...X` inside a literal adds X's count. Anything else — `.filter`, a function call, a table
|
|
30
|
+
* built in a loop — is NOT guessed at: the battery is reported as uncountable, which fails the
|
|
31
|
+
* check. A count that silently stood for something else would be the green zero this repo
|
|
32
|
+
* keeps guarding against.
|
|
33
|
+
*
|
|
34
|
+
* Run: `node scripts/mutation-batteries-frozen.mjs` (also part of `npm run check`)
|
|
35
|
+
*/
|
|
36
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
37
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
38
|
+
import { fileURLToPath } from "node:url";
|
|
39
|
+
import ts from "typescript";
|
|
40
|
+
|
|
41
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
42
|
+
export const FROZEN_FILE = join("scripts", "mutation-batteries.frozen.json");
|
|
43
|
+
|
|
44
|
+
// Build output and dependencies are not the repository's own files. Symlinks are not followed:
|
|
45
|
+
// `.claude/skills/<name>` points back into `skills/`, and following it would list every skill
|
|
46
|
+
// battery twice under two spellings.
|
|
47
|
+
const SKIP = new Set(["node_modules", ".git", "dist", "_build"]);
|
|
48
|
+
|
|
49
|
+
/** Every `*.mutations.mjs` under `root`, as sorted POSIX-style repo-relative paths. */
|
|
50
|
+
export function findBatteries(root) {
|
|
51
|
+
const found = [];
|
|
52
|
+
const walk = (dir) => {
|
|
53
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
if (e.isSymbolicLink() || SKIP.has(e.name)) continue;
|
|
55
|
+
const p = join(dir, e.name);
|
|
56
|
+
if (e.isDirectory()) walk(p);
|
|
57
|
+
else if (e.isFile() && e.name.endsWith(".mutations.mjs"))
|
|
58
|
+
found.push(relative(root, p).split(sep).join("/"));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
walk(root);
|
|
62
|
+
return found.sort();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The number of cases in a battery's table, read from its source without running it.
|
|
67
|
+
* Returns `{ count }` or `{ error }` — never a guess.
|
|
68
|
+
*/
|
|
69
|
+
export function countCases(source, fileName = "battery.mjs") {
|
|
70
|
+
const sf = ts.createSourceFile(
|
|
71
|
+
fileName,
|
|
72
|
+
source,
|
|
73
|
+
ts.ScriptTarget.Latest,
|
|
74
|
+
true,
|
|
75
|
+
ts.ScriptKind.JS,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Top-level `const NAME = <init>` — the only bindings a table name is resolved through.
|
|
79
|
+
const consts = new Map();
|
|
80
|
+
for (const st of sf.statements) {
|
|
81
|
+
if (!ts.isVariableStatement(st)) continue;
|
|
82
|
+
if (!(st.declarationList.flags & ts.NodeFlags.Const)) continue;
|
|
83
|
+
for (const d of st.declarationList.declarations)
|
|
84
|
+
if (ts.isIdentifier(d.name) && d.initializer)
|
|
85
|
+
consts.set(d.name.text, d.initializer);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tables = [];
|
|
89
|
+
const visit = (node) => {
|
|
90
|
+
if (
|
|
91
|
+
ts.isCallExpression(node) &&
|
|
92
|
+
ts.isIdentifier(node.expression) &&
|
|
93
|
+
node.expression.text === "runMutations"
|
|
94
|
+
) {
|
|
95
|
+
const arg = node.arguments[0];
|
|
96
|
+
const prop =
|
|
97
|
+
arg && ts.isObjectLiteralExpression(arg)
|
|
98
|
+
? arg.properties.find((p) => p.name?.getText(sf) === "cases")
|
|
99
|
+
: undefined;
|
|
100
|
+
if (prop && ts.isPropertyAssignment(prop)) tables.push(prop.initializer);
|
|
101
|
+
else if (prop && ts.isShorthandPropertyAssignment(prop))
|
|
102
|
+
tables.push(prop.name);
|
|
103
|
+
else tables.push(null);
|
|
104
|
+
}
|
|
105
|
+
ts.forEachChild(node, visit);
|
|
106
|
+
};
|
|
107
|
+
visit(sf);
|
|
108
|
+
|
|
109
|
+
const size = (expr, seen = new Set()) => {
|
|
110
|
+
if (!expr) return null;
|
|
111
|
+
while (
|
|
112
|
+
ts.isParenthesizedExpression(expr) ||
|
|
113
|
+
ts.isAsExpression?.(expr) ||
|
|
114
|
+
ts.isSatisfiesExpression?.(expr)
|
|
115
|
+
)
|
|
116
|
+
expr = expr.expression;
|
|
117
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
118
|
+
let n = 0;
|
|
119
|
+
for (const el of expr.elements) {
|
|
120
|
+
if (ts.isSpreadElement(el)) {
|
|
121
|
+
const inner = size(el.expression, seen);
|
|
122
|
+
if (inner === null) return null;
|
|
123
|
+
n += inner;
|
|
124
|
+
} else if (ts.isOmittedExpression(el)) return null;
|
|
125
|
+
else n += 1;
|
|
126
|
+
}
|
|
127
|
+
return n;
|
|
128
|
+
}
|
|
129
|
+
if (ts.isIdentifier(expr)) {
|
|
130
|
+
if (seen.has(expr.text) || !consts.has(expr.text)) return null;
|
|
131
|
+
seen.add(expr.text);
|
|
132
|
+
return size(consts.get(expr.text), seen);
|
|
133
|
+
}
|
|
134
|
+
if (
|
|
135
|
+
ts.isCallExpression(expr) &&
|
|
136
|
+
ts.isPropertyAccessExpression(expr.expression) &&
|
|
137
|
+
expr.expression.name.text === "map"
|
|
138
|
+
)
|
|
139
|
+
return size(expr.expression.expression, seen);
|
|
140
|
+
return null;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// The self-contained batteries: a top-level `for (... of TABLE)`. They also loop over a
|
|
144
|
+
// results accumulator (`const rows = []; for (const r of rows) ...`), which is empty in the
|
|
145
|
+
// source — so a candidate counts only when its table is non-empty.
|
|
146
|
+
if (tables.length === 0)
|
|
147
|
+
for (const st of sf.statements)
|
|
148
|
+
if (ts.isForOfStatement(st) && (size(st.expression) ?? 0) > 0)
|
|
149
|
+
tables.push(st.expression);
|
|
150
|
+
|
|
151
|
+
if (tables.length !== 1)
|
|
152
|
+
return {
|
|
153
|
+
error:
|
|
154
|
+
tables.length === 0
|
|
155
|
+
? "no case table found (neither `runMutations({ cases })` nor a top-level `for ... of` over a non-empty table)"
|
|
156
|
+
: `${String(tables.length)} candidate case tables — which one counts is ambiguous`,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const count = size(tables[0]);
|
|
160
|
+
return count === null
|
|
161
|
+
? {
|
|
162
|
+
error: `the case table (${tables[0]?.getText(sf).slice(0, 60) ?? "?"}) is not an array literal this check can count`,
|
|
163
|
+
}
|
|
164
|
+
: { count };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Compare what is on disk with the frozen list. Pure: every input is passed in.
|
|
169
|
+
* `found` — paths from findBatteries; `frozen` — `{ path: { cases } }`;
|
|
170
|
+
* `counted` — `{ path: { count } | { error } }` for the paths that exist.
|
|
171
|
+
*/
|
|
172
|
+
export function judge({ found, frozen, counted }) {
|
|
173
|
+
const problems = [];
|
|
174
|
+
const listed = new Set(Object.keys(frozen));
|
|
175
|
+
for (const f of found)
|
|
176
|
+
if (!listed.has(f))
|
|
177
|
+
problems.push(
|
|
178
|
+
`${f}: new hand-written mutation batteries are not accepted, see #52. ` +
|
|
179
|
+
`Record what a test guards as a comment above its assertion instead.`,
|
|
180
|
+
);
|
|
181
|
+
const onDisk = new Set(found);
|
|
182
|
+
for (const f of listed)
|
|
183
|
+
if (!onDisk.has(f))
|
|
184
|
+
problems.push(
|
|
185
|
+
`${f}: no longer exists — remove it from ${FROZEN_FILE} (the list must shrink with the code).`,
|
|
186
|
+
);
|
|
187
|
+
for (const f of found) {
|
|
188
|
+
if (!listed.has(f)) continue;
|
|
189
|
+
const want = frozen[f].cases;
|
|
190
|
+
const got = counted[f];
|
|
191
|
+
if (!got || "error" in got) {
|
|
192
|
+
problems.push(
|
|
193
|
+
`${f}: cases cannot be counted — ${got?.error ?? "not read"}. ` +
|
|
194
|
+
`The table must stay an array literal (optionally through a const or .map) so it can only shrink.`,
|
|
195
|
+
);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (got.count > want)
|
|
199
|
+
problems.push(
|
|
200
|
+
`${f}: ${String(got.count)} cases, frozen at ${String(want)} — ` +
|
|
201
|
+
`no new cases in existing batteries, see #52.`,
|
|
202
|
+
);
|
|
203
|
+
else if (got.count < want)
|
|
204
|
+
problems.push(
|
|
205
|
+
`${f}: ${String(got.count)} cases, frozen at ${String(want)} — ` +
|
|
206
|
+
`lower "cases" to ${String(got.count)} in ${FROZEN_FILE} (the list must shrink with the code).`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return problems;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Run the whole check against `root`. Returns the problems found. */
|
|
213
|
+
export function checkFrozen(root = ROOT) {
|
|
214
|
+
const data = JSON.parse(readFileSync(join(root, FROZEN_FILE), "utf8"));
|
|
215
|
+
const frozen = data.batteries ?? {};
|
|
216
|
+
const found = findBatteries(root);
|
|
217
|
+
const counted = {};
|
|
218
|
+
for (const f of found) {
|
|
219
|
+
const p = join(root, f);
|
|
220
|
+
if (existsSync(p)) counted[f] = countCases(readFileSync(p, "utf8"), f);
|
|
221
|
+
}
|
|
222
|
+
return { problems: judge({ found, frozen, counted }), found, frozen };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
226
|
+
const { problems, found, frozen } = checkFrozen(ROOT);
|
|
227
|
+
if (problems.length) {
|
|
228
|
+
console.error(
|
|
229
|
+
`🔴 mutation batteries are frozen and may only shrink (#52) — ${String(problems.length)} problem(s):`,
|
|
230
|
+
);
|
|
231
|
+
for (const p of problems) console.error(` ${p}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
const cases = Object.values(frozen).reduce((s, v) => s + v.cases, 0);
|
|
235
|
+
console.log(
|
|
236
|
+
`✓ ${String(found.length)} frozen mutation batteries, ${String(cases)} cases, none new, none grown (#52)`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"issue": "https://github.com/zernie/research-paper-pipeline/issues/52",
|
|
3
|
+
"why": "Hand-written *.mutations.mjs batteries are being removed (#52). This list may only shrink: no new batteries, no new cases in existing ones. Delete a battery or a case, then delete its entry or lower its count here. Checked by scripts/mutation-batteries-frozen.mjs, part of npm run check.",
|
|
4
|
+
"batteries": {
|
|
5
|
+
"action.mutations.mjs": {
|
|
6
|
+
"cases": 6
|
|
7
|
+
},
|
|
8
|
+
"eslint-rules/doc-fields.mutations.mjs": {
|
|
9
|
+
"cases": 6
|
|
10
|
+
},
|
|
11
|
+
"eslint-rules/install-path-literals.mutations.mjs": {
|
|
12
|
+
"cases": 3
|
|
13
|
+
},
|
|
14
|
+
"eslint-rules/latex-language.mutations.mjs": {
|
|
15
|
+
"cases": 12
|
|
16
|
+
},
|
|
17
|
+
"eslint-rules/paper-research-question.mutations.mjs": {
|
|
18
|
+
"cases": 6
|
|
19
|
+
},
|
|
20
|
+
"eslint-rules/paper-stages.mutations.mjs": {
|
|
21
|
+
"cases": 9
|
|
22
|
+
},
|
|
23
|
+
"eslint-rules/paper-typography.mutations.mjs": {
|
|
24
|
+
"cases": 6
|
|
25
|
+
},
|
|
26
|
+
"eslint-rules/papers.mutations.mjs": {
|
|
27
|
+
"cases": 10
|
|
28
|
+
},
|
|
29
|
+
"eslint-rules/review-findings-cause.mutations.mjs": {
|
|
30
|
+
"cases": 4
|
|
31
|
+
},
|
|
32
|
+
"eslint-rules/temp-root-realpath.mutations.mjs": {
|
|
33
|
+
"cases": 5
|
|
34
|
+
},
|
|
35
|
+
"eslint-rules/tex-build.mutations.mjs": {
|
|
36
|
+
"cases": 21
|
|
37
|
+
},
|
|
38
|
+
"fixtures/real-markdown-paper/real-markdown-paper.mutations.mjs": {
|
|
39
|
+
"cases": 4
|
|
40
|
+
},
|
|
41
|
+
"hooks/hooks.mutations.mjs": {
|
|
42
|
+
"cases": 23
|
|
43
|
+
},
|
|
44
|
+
"lib/agent-cli-version.mutations.mjs": {
|
|
45
|
+
"cases": 6
|
|
46
|
+
},
|
|
47
|
+
"lib/mutation-driver.mutations.mjs": {
|
|
48
|
+
"cases": 3
|
|
49
|
+
},
|
|
50
|
+
"lib/paper-config.mutations.mjs": {
|
|
51
|
+
"cases": 7
|
|
52
|
+
},
|
|
53
|
+
"lib/skill-trigger-cases.mutations.mjs": {
|
|
54
|
+
"cases": 3
|
|
55
|
+
},
|
|
56
|
+
"scripts/check.mutations.mjs": {
|
|
57
|
+
"cases": 5
|
|
58
|
+
},
|
|
59
|
+
"scripts/rules-are-content-only.mutations.mjs": {
|
|
60
|
+
"cases": 3
|
|
61
|
+
},
|
|
62
|
+
"scripts/rules-see-files.mutations.mjs": {
|
|
63
|
+
"cases": 2
|
|
64
|
+
},
|
|
65
|
+
"skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs": {
|
|
66
|
+
"cases": 22
|
|
67
|
+
},
|
|
68
|
+
"skills/paper-pipeline/scripts/consumer.mutations.mjs": {
|
|
69
|
+
"cases": 14
|
|
70
|
+
},
|
|
71
|
+
"skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs": {
|
|
72
|
+
"cases": 1
|
|
73
|
+
},
|
|
74
|
+
"skills/paper-pipeline/scripts/generated-code.mutations.mjs": {
|
|
75
|
+
"cases": 25
|
|
76
|
+
},
|
|
77
|
+
"skills/paper-pipeline/scripts/pipeline-check.mutations.mjs": {
|
|
78
|
+
"cases": 1
|
|
79
|
+
},
|
|
80
|
+
"skills/paper-pipeline/scripts/population-map.mutations.mjs": {
|
|
81
|
+
"cases": 2
|
|
82
|
+
},
|
|
83
|
+
"skills/paper-pipeline/scripts/provenance.mutations.mjs": {
|
|
84
|
+
"cases": 2
|
|
85
|
+
},
|
|
86
|
+
"skills/paper-pipeline/scripts/round-diff.mutations.mjs": {
|
|
87
|
+
"cases": 28
|
|
88
|
+
},
|
|
89
|
+
"skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs": {
|
|
90
|
+
"cases": 2
|
|
91
|
+
},
|
|
92
|
+
"skills/skill-contract.mutations.mjs": {
|
|
93
|
+
"cases": 22
|
|
94
|
+
},
|
|
95
|
+
"src/build.mutations.mjs": {
|
|
96
|
+
"cases": 11
|
|
97
|
+
},
|
|
98
|
+
"src/cli.mutations.mjs": {
|
|
99
|
+
"cases": 28
|
|
100
|
+
},
|
|
101
|
+
"src/doctor.mutations.mjs": {
|
|
102
|
+
"cases": 10
|
|
103
|
+
},
|
|
104
|
+
"src/hooks-settings.mutations.mjs": {
|
|
105
|
+
"cases": 7
|
|
106
|
+
},
|
|
107
|
+
"src/link-skills.mutations.mjs": {
|
|
108
|
+
"cases": 6
|
|
109
|
+
},
|
|
110
|
+
"src/new-paper.mutations.mjs": {
|
|
111
|
+
"cases": 4
|
|
112
|
+
},
|
|
113
|
+
"src/structure.mutations.mjs": {
|
|
114
|
+
"cases": 4
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The release pipeline, rendered locally from the real `.releaserc.json` with the installed
|
|
3
|
+
* toolchain — the same plugins, the same preset, the same versions `npx --no-install
|
|
4
|
+
* semantic-release` runs in release.yml.
|
|
5
|
+
*
|
|
6
|
+
* 🔴 WHY THIS EXISTS. In zernie/vigiles, from v16.0.0 to v31.0.0, every GitHub release shipped a
|
|
7
|
+
* header and nothing else (zernie/vigiles#282). `conventional-changelog-conventionalcommits` had
|
|
8
|
+
* moved to 10, which renders only through `conventional-changelog-writer@9`, while
|
|
9
|
+
* `@semantic-release/release-notes-generator` 14 still loads writer 8. The release went out with
|
|
10
|
+
* an empty body, and nothing checked the notes. This package copies that setup, so it copies the
|
|
11
|
+
* check: a dependency bump that breaks either half of the preset's job turns this red before it
|
|
12
|
+
* releases.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, expect, test } from "vitest";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
import { analyzeCommits } from "@semantic-release/commit-analyzer";
|
|
17
|
+
import { generateNotes } from "@semantic-release/release-notes-generator";
|
|
18
|
+
|
|
19
|
+
type PluginEntry = string | [string, Record<string, unknown>];
|
|
20
|
+
const releaserc = JSON.parse(
|
|
21
|
+
readFileSync(new URL("../.releaserc.json", import.meta.url), "utf8"),
|
|
22
|
+
) as { plugins: PluginEntry[] };
|
|
23
|
+
|
|
24
|
+
/** The options `.releaserc.json` gives one plugin, exactly as semantic-release would pass them. */
|
|
25
|
+
function optionsFor(name: string): Record<string, unknown> {
|
|
26
|
+
const entry = releaserc.plugins.find((p) =>
|
|
27
|
+
Array.isArray(p) ? p[0] === name : p === name,
|
|
28
|
+
);
|
|
29
|
+
if (entry === undefined) throw new Error(`${name} is not in .releaserc.json`);
|
|
30
|
+
return Array.isArray(entry) ? entry[1] : {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const breaking = {
|
|
34
|
+
hash: "1111111111111111111111111111111111111111",
|
|
35
|
+
message:
|
|
36
|
+
"feat!: drop the markdown profile (#90)\n\n" +
|
|
37
|
+
"BREAKING CHANGE: the `markdown` venue profile is removed.",
|
|
38
|
+
};
|
|
39
|
+
/** `!` alone, no footer. The default (angular) preset ignores it; conventionalcommits makes it major. */
|
|
40
|
+
const bangOnly = {
|
|
41
|
+
hash: "4444444444444444444444444444444444444444",
|
|
42
|
+
message: "feat!: rename the lint command",
|
|
43
|
+
};
|
|
44
|
+
const feature = {
|
|
45
|
+
hash: "2222222222222222222222222222222222222222",
|
|
46
|
+
message: "feat(cli): add a flag (#1)",
|
|
47
|
+
};
|
|
48
|
+
const fix = {
|
|
49
|
+
hash: "3333333333333333333333333333333333333333",
|
|
50
|
+
message: "fix(build): resolve the paper from the config root (#2)",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const context = {
|
|
54
|
+
cwd: process.cwd(),
|
|
55
|
+
options: {
|
|
56
|
+
repositoryUrl: "https://github.com/zernie/research-paper-pipeline",
|
|
57
|
+
},
|
|
58
|
+
lastRelease: { gitTag: "v1.0.0", version: "1.0.0" },
|
|
59
|
+
nextRelease: { gitTag: "v2.0.0", version: "2.0.0", type: "major" },
|
|
60
|
+
logger: { log() {}, error() {} },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe("release config (.releaserc.json) with the installed toolchain", () => {
|
|
64
|
+
test("`feat!:` is a major (with or without a footer), `feat:` a minor, `fix:` a patch", async () => {
|
|
65
|
+
const analyzer = optionsFor("@semantic-release/commit-analyzer");
|
|
66
|
+
expect(
|
|
67
|
+
await analyzeCommits(analyzer, { ...context, commits: [breaking] }),
|
|
68
|
+
).toBe("major");
|
|
69
|
+
expect(
|
|
70
|
+
await analyzeCommits(analyzer, { ...context, commits: [bangOnly] }),
|
|
71
|
+
).toBe("major");
|
|
72
|
+
expect(
|
|
73
|
+
await analyzeCommits(analyzer, { ...context, commits: [feature] }),
|
|
74
|
+
).toBe("minor");
|
|
75
|
+
expect(await analyzeCommits(analyzer, { ...context, commits: [fix] })).toBe(
|
|
76
|
+
"patch",
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("the notes carry the breaking change, the feature and the fix — not just a header", async () => {
|
|
81
|
+
const notes: string = await generateNotes(
|
|
82
|
+
optionsFor("@semantic-release/release-notes-generator"),
|
|
83
|
+
{ ...context, commits: [breaking, feature, fix] },
|
|
84
|
+
);
|
|
85
|
+
expect(notes).toContain("BREAKING CHANGES");
|
|
86
|
+
expect(notes).toContain("the `markdown` venue profile is removed.");
|
|
87
|
+
expect(notes).toContain("add a flag");
|
|
88
|
+
expect(notes).toContain("resolve the paper from the config root");
|
|
89
|
+
});
|
|
90
|
+
});
|