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,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mutation-driver.mjs — the shared engine behind every `*.mutations.mjs` in this repo.
|
|
3
|
+
*
|
|
4
|
+
* NOT IN vigiles, AND THAT IS A DECISION WITH A DATE. On 2026-08-14 this engine was extracted into
|
|
5
|
+
* vigiles as `runMutations` (`vigiles/testing`, shipped in 15.3.0). The owner killed it the next
|
|
6
|
+
* day with one question: «how is this vigiles' responsibility, it's just nodejs?» — and he is
|
|
7
|
+
* right. Measured against the rest of
|
|
8
|
+
* that package: `runHook` knows the Claude Code hook protocol, `measureTriggerRate` knows what a
|
|
9
|
+
* skill is, `skillContract` reads `allowed-tools` out of frontmatter. This file knows none of that.
|
|
10
|
+
* It replaces a substring, spawns `node`, and reads stdout; lift it into any Node repo and it works
|
|
11
|
+
* unchanged. Confirming measurement: no test in vigiles used it — the product did not dogfood its
|
|
12
|
+
* own published API.
|
|
13
|
+
*
|
|
14
|
+
* The rule "mechanism → vigiles" was applied too bluntly, and this is the correction worth keeping:
|
|
15
|
+
* what fixed the ten drifted copies was CONSOLIDATION, not RELOCATION. One copy instead of ten
|
|
16
|
+
* buys every protection listed below. Moving that copy into a product for harness authors was a
|
|
17
|
+
* second, separate step, and it was never justified — it only bought a newcomer the question the
|
|
18
|
+
* owner asked. `checkProductCodeInKb` flags this file on sight, which is correct and stays visible: the
|
|
19
|
+
* heuristic is right that this is mechanism-shaped, and the answer is «yes, and it was rejected
|
|
20
|
+
* upstream on 2026-08-15», not «no».
|
|
21
|
+
*
|
|
22
|
+
* 🔴 2026-09-12 — IT NOW LIVES IN A PACKAGE ANYWAY, AND THAT DOES NOT REOPEN THE DECISION
|
|
23
|
+
* ABOVE. The rejected move was into vigiles: a product for people writing agent harnesses, where
|
|
24
|
+
* a generic string-replacing test driver is off-topic and, measured, was not used by a single one
|
|
25
|
+
* of its own tests. This package is the opposite case — it IS the batteries' own repository, they
|
|
26
|
+
* are its tests, and it cannot run them without the engine. So the destination differs on the
|
|
27
|
+
* exact axis the rejection turned on: «does the package this lives in actually use it». Do not
|
|
28
|
+
* read this file's presence here as the vigiles decision being reversed; it was not re-argued.
|
|
29
|
+
*
|
|
30
|
+
* The one shape that WOULD belong in vigiles is a different function that does not exist yet:
|
|
31
|
+
* mutate the HOOK — remove one branch of a guard — and require the assembled harness test to
|
|
32
|
+
* notice. Only vigiles knows what a hook is. Backlog, not this file.
|
|
33
|
+
*
|
|
34
|
+
* WHAT A MUTATION RUN IS. Plant one defect in a checker, run that checker's harness, and require
|
|
35
|
+
* the harness to go red AND to name the case that owns the defect. A harness that stays green has
|
|
36
|
+
* an assertion that cannot fail; a harness that reddens with someone else's message has two
|
|
37
|
+
* defects sharing one assertion, which means neither is really watched.
|
|
38
|
+
*
|
|
39
|
+
* WHY THIS FILE EXISTS (2026-08-14). Ten mutation files, 1799 lines, ran the same twenty-five-line
|
|
40
|
+
* driver ten times — and they had DRIFTED, so the copies were not equivalent. Counted against the
|
|
41
|
+
* committed versions, not estimated:
|
|
42
|
+
*
|
|
43
|
+
* - NO-OP GUARD (a replacement equal to the original leaves a green harness that proves nothing):
|
|
44
|
+
* present in 4 of 10 — `ledger`, `round-diff`, `textidote`, `uncited-refs`. SIX lacked it.
|
|
45
|
+
* - RETRY (a non-kill is re-run once before it is believed, because a row killed as named came
|
|
46
|
+
* back DIFFERENT in a later full run and reproduced clean when replayed alone): present in
|
|
47
|
+
* 1 of 10 — `ledger`. NINE lacked it.
|
|
48
|
+
* - `skills.mutations.mjs` alone mutated a COPY of the tree; the other nine rewrite the real
|
|
49
|
+
* source and restore it in a `finally`.
|
|
50
|
+
* - Two files pointed at `.claude/pipeline/skills.harness.mjs`, deleted by the 2026-08-11
|
|
51
|
+
* colocation (`ledger`, six cases; and `skills` itself). `vigiles test` on a path matching
|
|
52
|
+
* nothing exits 0, so those cases reported SURVIVED forever — a mutation runner producing
|
|
53
|
+
* confident wrong answers about mutation runs.
|
|
54
|
+
*
|
|
55
|
+
* Every protection above was written AFTER it caught something, in one file, and never travelled
|
|
56
|
+
* to the other nine. That is the argument for one engine, and it is stronger than "less code".
|
|
57
|
+
*
|
|
58
|
+
* So the split is not cosmetic DRY: nine files GAIN two protections they never had, and the dead
|
|
59
|
+
* path becomes expressible once instead of three times.
|
|
60
|
+
*
|
|
61
|
+
* WHAT STAYS IN THE CALLER. The CASES TABLE — which defect, which bytes, which message. That is
|
|
62
|
+
* data about OUR checkers and belongs next to them (root CLAUDE.md: "mechanism — in vigiles,
|
|
63
|
+
* data — in mine"; the same line separates mechanism from data inside this repo). This file knows
|
|
64
|
+
* nothing about papers, skills or ledgers.
|
|
65
|
+
*
|
|
66
|
+
* WHAT DOES NOT USE THIS ENGINE, AND WHY. `skills.mutations.mjs` mutates a COPY of `.claude` in a
|
|
67
|
+
* temp root and runs the harnesses there with `CLAUDE_PROJECT_DIR` repointed. That is the right
|
|
68
|
+
* strategy for it — its cases break the SKILL CORPUS itself, so a copy is cheap (one directory) and
|
|
69
|
+
* the real corpus is never touched. It is the only one CI runs, and it stays as it is. Two
|
|
70
|
+
* strategies is a smell only when they differ by accident; here the difference is the subject.
|
|
71
|
+
*
|
|
72
|
+
* 🔴 THE IN-PLACE STRATEGY IS PRESERVED, NOT "FIXED". Nine callers rewrite a real file, and moving
|
|
73
|
+
* them to a temp copy is not a refactor: their harnesses resolve every path from the repo root, so
|
|
74
|
+
* a copy means copying the whole repo per mutation — minutes instead of seconds. What IS fixed is
|
|
75
|
+
* the failure mode: the restore now also runs on SIGINT/SIGTERM, and a run refuses to start when
|
|
76
|
+
* any target file is already dirty, so an interrupted run cannot leave a neutered checker behind
|
|
77
|
+
* and then pass.
|
|
78
|
+
*/
|
|
79
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
80
|
+
import { spawnSync } from "node:child_process";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {object} MutationCase
|
|
84
|
+
* @property {string} name Short id, printed in the table.
|
|
85
|
+
* @property {string} disables What the mutation takes away, in words — the column a reader scans.
|
|
86
|
+
* @property {[string, string, string][]} edits `[absolutePath, find, replace]`. A LIST because some
|
|
87
|
+
* defects are only expressible as more than one edit: an acquittal reaching the ledger needs both
|
|
88
|
+
* the retired-name guard and the vocabulary check removed, and either alone throws for an
|
|
89
|
+
* unrelated reason.
|
|
90
|
+
* @property {string} harness Absolute path to the harness that must go red.
|
|
91
|
+
* @property {string} expect Substring the harness's complaint must contain.
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/** Runs a harness file. `node` for a plain harness, `npx vigiles test` when the caller says so. */
|
|
95
|
+
const runners = {
|
|
96
|
+
node: (harness, root) =>
|
|
97
|
+
spawnSync("node", [harness], { cwd: root, encoding: "utf8" }),
|
|
98
|
+
vigiles: (harness, root) =>
|
|
99
|
+
spawnSync("npx", ["vigiles", "test", harness], {
|
|
100
|
+
cwd: root,
|
|
101
|
+
encoding: "utf8",
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* vigiles' own protocol constant: `skip()` ends the process with this code. Its docs state it —
|
|
107
|
+
* `harness-assert.d.ts`: «Exit 77 is the runner's `SKIP_EXIT_CODE` (run-scripts.ts)» — but the
|
|
108
|
+
* package exports `skip` and not the number, so it is NAMED here instead of appearing as a bare
|
|
109
|
+
* literal in a comparison.
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ If a future vigiles changes it, this degrades GRACEFULLY rather than silently: an unknown
|
|
112
|
+
* non-zero code reads as "red", so the battery reports UNJUDGEABLE instead of claiming a
|
|
113
|
+
* survivor. Wrong, but loudly wrong — which is the direction this whole file errs in.
|
|
114
|
+
*/
|
|
115
|
+
const VIGILES_SKIP_EXIT = 77;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 🔴 A SKIPPED HARNESS IS A THIRD OUTCOME, AND CONFLATING IT WITH "GREEN" PRODUCES A CONFIDENT
|
|
119
|
+
* WRONG DIAGNOSIS — the failure this file already documents twice, arriving by a new door.
|
|
120
|
+
*
|
|
121
|
+
* MEASURED 2026-09-15, chain complete, every link observed rather than argued:
|
|
122
|
+
*
|
|
123
|
+
* the harness itself, `claude` CLI absent exit 77 "SKIPPED: … no run can be observed"
|
|
124
|
+
* `npx vigiles test <harness>` exit 0 "0 passed, 1 skipped."
|
|
125
|
+
* the driver reads 0 ⇒ "🔴 SURVIVED"
|
|
126
|
+
*
|
|
127
|
+
* So `plan-paper-timeline.effects.mutations.mjs` reported TWO survivors in CI and none locally,
|
|
128
|
+
* and the difference was not the code under test — it was that CI has no `claude` binary. The
|
|
129
|
+
* reader is sent to rewrite a working assertion, which is strictly worse than a red with no
|
|
130
|
+
* explanation.
|
|
131
|
+
*
|
|
132
|
+
* WHY THE FLAG AND NOT A SUBSTRING. `--no-skip` exists precisely to turn "skipped" into a
|
|
133
|
+
* failure, so asking the tool with it is asking the THING; matching "SKIPPED" in the output
|
|
134
|
+
* would be matching its shadow, and the shadow has spellings. The plain run then disambiguates:
|
|
135
|
+
* a harness that is green without the flag and red with it SKIPPED; one that is red both ways was
|
|
136
|
+
* already red, which the caller below already handles.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ The extra probe runs ONLY for a harness that is not plainly green, so the common path costs
|
|
139
|
+
* exactly what it cost before.
|
|
140
|
+
*
|
|
141
|
+
* @returns {"green"|"red"|"skip"}
|
|
142
|
+
*/
|
|
143
|
+
function probeHarness(runner, harness, root) {
|
|
144
|
+
// The node runner needs no probe: a skipping harness exits with vigiles' own skip code, and
|
|
145
|
+
// that code IS the signal. It is only the `vigiles` wrapper that swallows it into a 0.
|
|
146
|
+
if (runner === "node") {
|
|
147
|
+
const r = runners.node(harness, root);
|
|
148
|
+
return r.status === 0
|
|
149
|
+
? "green"
|
|
150
|
+
: r.status === VIGILES_SKIP_EXIT
|
|
151
|
+
? "skip"
|
|
152
|
+
: "red";
|
|
153
|
+
}
|
|
154
|
+
const strict = spawnSync("npx", ["vigiles", "test", harness, "--no-skip"], {
|
|
155
|
+
cwd: root,
|
|
156
|
+
encoding: "utf8",
|
|
157
|
+
});
|
|
158
|
+
if (strict.status === 0) return "green";
|
|
159
|
+
return runners.vigiles(harness, root).status === 0 ? "skip" : "red";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {object} o
|
|
164
|
+
* @param {string} o.root Repo root; every path in `edits` and `harness` is absolute.
|
|
165
|
+
* @param {MutationCase[]} o.cases
|
|
166
|
+
* @param {"node"|"vigiles"} [o.runner] How to invoke a harness. Default `node`.
|
|
167
|
+
* @param {string[]} [o.cleanHarnesses] Run after restoring to prove the restore worked. Defaults
|
|
168
|
+
* to the distinct harnesses named by the cases.
|
|
169
|
+
* @returns {number} process exit code — 0 only if every mutation was killed by its OWN case.
|
|
170
|
+
*/
|
|
171
|
+
export function runMutations({ root, cases, runner = "node", cleanHarnesses }) {
|
|
172
|
+
const run = runners[runner];
|
|
173
|
+
if (!run)
|
|
174
|
+
throw new Error(
|
|
175
|
+
`unknown runner ${JSON.stringify(runner)}; expected node or vigiles`,
|
|
176
|
+
);
|
|
177
|
+
if (!cases.length)
|
|
178
|
+
throw new Error(
|
|
179
|
+
"no mutation cases were passed — a runner with nothing to run reports success",
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const targets = [...new Set(cases.flatMap((c) => c.edits.map(([f]) => f)))];
|
|
183
|
+
const harnesses = [...new Set(cases.map((c) => c.harness))];
|
|
184
|
+
|
|
185
|
+
// COVERAGE MODE — report which harnesses this battery can kill, then stop without touching a
|
|
186
|
+
// file. Read by `scripts/run-mutations.mjs` to answer «is every harness in the repo killable by
|
|
187
|
+
// SOMETHING», which it previously answered by FILENAME (`x.harness.mjs` ⇄ `x.mutations.mjs`).
|
|
188
|
+
//
|
|
189
|
+
// 🔴 THE FILENAME WAS A PROXY, AND IT WAS WRONG ON THIS CORPUS. `ledger.mutations.mjs` is the
|
|
190
|
+
// battery for `gates.harness.mjs` — it plants defects in the ledger and requires the GATES
|
|
191
|
+
// harness to notice — and no naming convention expresses that. Under the old rule it read as
|
|
192
|
+
// two problems at once: a battery covering nothing and a harness covered by nobody. Pairing is
|
|
193
|
+
// now derived from the `harness` field of the cases that actually run, i.e. from the same data
|
|
194
|
+
// structure that does the work, so it cannot disagree with what the battery does.
|
|
195
|
+
//
|
|
196
|
+
// ⚠️ An exemption list was the other available answer and is the one NOT taken: a guard with a
|
|
197
|
+
// list of things it agrees not to look at is decoration. Nothing here is exempt — a harness is
|
|
198
|
+
// covered only when some battery names it, and a battery that names a harness it does not
|
|
199
|
+
// really exercise is caught by that case reporting SURVIVED.
|
|
200
|
+
if (process.env.MUTATIONS_REPORT_COVERAGE) {
|
|
201
|
+
for (const h of harnesses) console.log(`MUTATION-COVERS\t${h}`);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 🔴 A harness path that resolves to nothing is the defect that hid for three days: `vigiles
|
|
206
|
+
// test` prints "No files found" and exits 0, so every case naming it reports SURVIVED and the
|
|
207
|
+
// reader goes looking for a broken assertion that was never reached. Checked BEFORE any file is
|
|
208
|
+
// touched, so the message arrives with a clean working tree.
|
|
209
|
+
const missing = harnesses.filter((h) => !existsSync(h));
|
|
210
|
+
if (missing.length) {
|
|
211
|
+
console.error(
|
|
212
|
+
`refusing to run: harness file(s) do not exist:\n ${missing.join("\n ")}`,
|
|
213
|
+
);
|
|
214
|
+
return 2;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const git = (...a) =>
|
|
218
|
+
spawnSync("git", a, { cwd: root, encoding: "utf8" }).stdout ?? "";
|
|
219
|
+
const dirty = targets.filter((f) => git("status", "--porcelain", f).trim());
|
|
220
|
+
if (dirty.length) {
|
|
221
|
+
console.error(
|
|
222
|
+
`refusing to run: this script rewrites the file(s) below and they have uncommitted changes.\n ${dirty.join("\n ")}\nCommit first (do NOT stash — the stash is repository-global and shared with any other agent).`,
|
|
223
|
+
);
|
|
224
|
+
return 2;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 🔴 BASELINE FIRST, and it is not an optimisation. The old "did the restore work" check ran the
|
|
228
|
+
// harnesses at the END and called any red one a FAILED RESTORE. `gates.harness.mjs` is red today
|
|
229
|
+
// on purpose — its own message says "these are OPEN FINDINGS about the gates themselves, not
|
|
230
|
+
// harness bugs" — so every ledger run printed "🔴 the restore failed" about a working restore and
|
|
231
|
+
// exited 1. A confident wrong diagnosis, produced by the tool whose whole job is catching those.
|
|
232
|
+
// Knowing which harnesses were ALREADY red makes the end-of-run claim mean what it says.
|
|
233
|
+
// Three outcomes, not two — see `probeHarness`. A harness that SKIPS here can never testify
|
|
234
|
+
// about a mutation, so the battery is refused BEFORE a single file is touched, exactly like the
|
|
235
|
+
// missing-path guard above: the message arrives with a clean working tree.
|
|
236
|
+
const probes = new Map(
|
|
237
|
+
harnesses.map((h) => [h, probeHarness(runner, h, root)]),
|
|
238
|
+
);
|
|
239
|
+
const skipped = harnesses.filter((h) => probes.get(h) === "skip");
|
|
240
|
+
if (skipped.length) {
|
|
241
|
+
console.error(
|
|
242
|
+
`refusing to run: harness(es) SKIP on clean source, so this battery can prove nothing:\n ` +
|
|
243
|
+
skipped.join("\n ") +
|
|
244
|
+
`\nA skipped harness exits 0 through \`vigiles test\`, which this driver would read as ` +
|
|
245
|
+
`"the mutation survived" — a claim about the TEST that is really a claim about the ` +
|
|
246
|
+
`ENVIRONMENT. Install the capability the harness names (it says so on its skip line), ` +
|
|
247
|
+
`or run the battery where that capability exists.`,
|
|
248
|
+
);
|
|
249
|
+
return 2;
|
|
250
|
+
}
|
|
251
|
+
const redBefore = new Set(harnesses.filter((h) => probes.get(h) === "red"));
|
|
252
|
+
if (redBefore.size) {
|
|
253
|
+
console.log(
|
|
254
|
+
`ℹ️ already red before any mutation: ${[...redBefore].map(base).join(", ")} — excluded from the restore check, and any case naming one is reported as UNJUDGEABLE.`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const originals = new Map(targets.map((f) => [f, readFileSync(f, "utf8")]));
|
|
259
|
+
const restore = () => {
|
|
260
|
+
for (const [f, text] of originals) writeFileSync(f, text);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Restoring only in `finally` leaves a neutered checker on the disk when the run is killed —
|
|
264
|
+
// and the next run then measures a repo that is already broken.
|
|
265
|
+
const onSignal = (sig) => {
|
|
266
|
+
restore();
|
|
267
|
+
process.exit(sig === "SIGINT" ? 130 : 143);
|
|
268
|
+
};
|
|
269
|
+
process.on("SIGINT", onSignal);
|
|
270
|
+
process.on("SIGTERM", onSignal);
|
|
271
|
+
|
|
272
|
+
const rows = [];
|
|
273
|
+
try {
|
|
274
|
+
for (const c of cases) {
|
|
275
|
+
let problem = null;
|
|
276
|
+
for (const [file, find, replace] of c.edits) {
|
|
277
|
+
const src = readFileSync(file, "utf8");
|
|
278
|
+
const n = src.split(find).length - 1;
|
|
279
|
+
if (n !== 1) {
|
|
280
|
+
problem =
|
|
281
|
+
n === 0
|
|
282
|
+
? `NOT FOUND in ${base(file)}`
|
|
283
|
+
: `AMBIGUOUS (${n}) in ${base(file)}`;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
const next = src.replace(find, replace);
|
|
287
|
+
// The mutation-that-does-not-mutate guard, confirmed against the BYTES rather than the
|
|
288
|
+
// intent. Nine of the ten files lacked it; a replacement equal to the original leaves a
|
|
289
|
+
// green harness that proves nothing and reads exactly like a killed mutation.
|
|
290
|
+
if (next === src) {
|
|
291
|
+
problem = "NO-OP (replacement equals original)";
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
writeFileSync(file, next);
|
|
295
|
+
}
|
|
296
|
+
if (problem) {
|
|
297
|
+
rows.push([c.name, problem, c.disables]);
|
|
298
|
+
restore();
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// A non-kill is retried ONCE before it is believed. Observed on the ledger table: a row
|
|
303
|
+
// killed as named came back DIFFERENT in a later full run and then reproduced as a clean
|
|
304
|
+
// kill when replayed alone. Reporting a flake as a survivor sends the next reader to rewrite
|
|
305
|
+
// a working assertion — a worse outcome than one extra run. A row that fails BOTH times is
|
|
306
|
+
// a real finding and is printed without hedging.
|
|
307
|
+
const attempt = () => {
|
|
308
|
+
const r = run(c.harness, root);
|
|
309
|
+
const out = (r.stdout || "") + (r.stderr || "");
|
|
310
|
+
if (r.status === 0) return "🔴 SURVIVED";
|
|
311
|
+
if (out.includes(c.expect)) return "RED (expected case)";
|
|
312
|
+
// When the harness was ALREADY red, "red" carries no information — but the MESSAGE still
|
|
313
|
+
// does, because node's assert aborts at the first failure, so a message that appears was
|
|
314
|
+
// genuinely reached. Absent it, the two causes are indistinguishable and saying "different
|
|
315
|
+
// case" would be a guess dressed as a verdict.
|
|
316
|
+
return redBefore.has(c.harness)
|
|
317
|
+
? `🔴 UNJUDGEABLE (harness red before the run; "${c.expect}" never printed)`
|
|
318
|
+
: `RED (but a DIFFERENT case: expected "${c.expect}")`;
|
|
319
|
+
};
|
|
320
|
+
let verdict = attempt();
|
|
321
|
+
if (verdict !== "RED (expected case)") {
|
|
322
|
+
const second = attempt();
|
|
323
|
+
if (second === "RED (expected case)")
|
|
324
|
+
verdict = "RED (expected case, on retry)";
|
|
325
|
+
else verdict = second;
|
|
326
|
+
}
|
|
327
|
+
rows.push([c.name, verdict, c.disables]);
|
|
328
|
+
restore();
|
|
329
|
+
}
|
|
330
|
+
} finally {
|
|
331
|
+
restore();
|
|
332
|
+
process.off("SIGINT", onSignal);
|
|
333
|
+
process.off("SIGTERM", onSignal);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const width = Math.max(...rows.map((r) => r[0].length));
|
|
337
|
+
for (const [name, verdict, disables] of rows)
|
|
338
|
+
console.log(`${name.padEnd(width)} ${verdict.padEnd(38)} ${disables}`);
|
|
339
|
+
|
|
340
|
+
// The restore is asserted, not assumed: every harness the run touched must be green again.
|
|
341
|
+
// Only harnesses that were GREEN before can testify about the restore. One that was already red
|
|
342
|
+
// proves nothing either way, and counting it was how this line came to lie.
|
|
343
|
+
const cleanSet = (cleanHarnesses ?? harnesses).filter(
|
|
344
|
+
(h) => !redBefore.has(h),
|
|
345
|
+
);
|
|
346
|
+
const stillRed = cleanSet.filter((h) => run(h, root).status !== 0);
|
|
347
|
+
console.log(
|
|
348
|
+
`\nrestored source: ${
|
|
349
|
+
cleanSet.length === 0
|
|
350
|
+
? "NOT CHECKED — every harness here was already red before the run"
|
|
351
|
+
: stillRed.length === 0
|
|
352
|
+
? "harness GREEN"
|
|
353
|
+
: `🔴 RED — the restore failed (${stillRed.map(base).join(", ")})`
|
|
354
|
+
}`,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// 🔴 `RED (expected`, not `RED`. Nine of the ten files counted a mutation killed by SOMEONE
|
|
358
|
+
// ELSE'S assertion as a kill; only `ledger` required the named one. Taking the weak rule while
|
|
359
|
+
// printing "each at its own assertion" below would have been a claim the mechanism did not back —
|
|
360
|
+
// exactly the defect this whole directory exists to catch. Measured cost of the strict rule on
|
|
361
|
+
// today's corpus: zero rows change verdict, because every row already came back as its own case.
|
|
362
|
+
// 🔴 A MUTATION THAT NEVER LANDED IS NOT A SURVIVOR, and until 2026-09-17 this line said it
|
|
363
|
+
// was: every row that did not start with `RED (expected` went into `survived`, including
|
|
364
|
+
// `NOT FOUND`, `AMBIGUOUS` and `NO-OP` — cases where the file was never edited at all. The two
|
|
365
|
+
// verdicts send a reader to opposite places: a survivor means STRENGTHEN THE TEST, an unusable
|
|
366
|
+
// case means FIX THE MUTATION. Observed cost: a stale `find` string (the rule had been
|
|
367
|
+
// rewritten) was reported as "1 mutation survived", and the next step would have been rewriting
|
|
368
|
+
// a harness that was fine. The docstring two hundred lines above warns about exactly this
|
|
369
|
+
// confusion — the summary contradicted it.
|
|
370
|
+
const unusable = rows.filter(
|
|
371
|
+
(r) =>
|
|
372
|
+
r[1].startsWith("NOT FOUND") ||
|
|
373
|
+
r[1].startsWith("AMBIGUOUS") ||
|
|
374
|
+
r[1].startsWith("NO-OP"),
|
|
375
|
+
);
|
|
376
|
+
const survived = rows.filter(
|
|
377
|
+
(r) => !r[1].startsWith("RED (expected") && !unusable.includes(r),
|
|
378
|
+
);
|
|
379
|
+
if (unusable.length)
|
|
380
|
+
console.log(
|
|
381
|
+
`🔴 ${unusable.length} mutation(s) UNUSABLE — the patch never landed, so nothing was ` +
|
|
382
|
+
`measured about the test: ${unusable.map((r) => `${r[0]} [${r[1]}]`).join(", ")}`,
|
|
383
|
+
);
|
|
384
|
+
console.log(
|
|
385
|
+
survived.length
|
|
386
|
+
? `🔴 ${survived.length} mutation(s) survived: ${survived.map((r) => r[0]).join(", ")}`
|
|
387
|
+
: // "each at its own assertion" is not decoration — a kill only counts when the harness went
|
|
388
|
+
// red with the message the case named. A mutation killed by SOMEONE ELSE'S assertion means
|
|
389
|
+
// two defects share one assertion and neither is really watched, and it lands in `survived`.
|
|
390
|
+
unusable.length
|
|
391
|
+
? `(${rows.length - unusable.length} of ${rows.length} mutations killed; the rest were never applied)`
|
|
392
|
+
: `✓ all ${rows.length} mutations killed by the harness, each at its own assertion`,
|
|
393
|
+
);
|
|
394
|
+
return survived.length || unusable.length || stillRed.length ? 1 : 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const base = (p) => p.split("/").pop();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Battery for the mutation engine itself — the one it didn't have.
|
|
3
|
+
*
|
|
4
|
+
* Both cases target ONE property, added 2026-09-15: a skipped harness is a third outcome,
|
|
5
|
+
* and a battery whose harness skips must REFUSE to judge. The first removes the refusal
|
|
6
|
+
* itself, the second removes the ability to recognize a skip. Different causes, one
|
|
7
|
+
* observable effect, and that effect already cost a false "two mutations survived"
|
|
8
|
+
* diagnosis in CI (run 34966606186).
|
|
9
|
+
*/
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { runMutations } from "./mutation-driver.mjs";
|
|
13
|
+
|
|
14
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const ROOT = resolve(HERE, "..");
|
|
16
|
+
const DRIVER = join(HERE, "mutation-driver.mjs");
|
|
17
|
+
const HARNESS = join(HERE, "mutation-driver.harness.mjs");
|
|
18
|
+
|
|
19
|
+
process.exit(
|
|
20
|
+
runMutations({
|
|
21
|
+
root: ROOT,
|
|
22
|
+
runner: "node",
|
|
23
|
+
cases: [
|
|
24
|
+
{
|
|
25
|
+
// Revert to how it was before 09-17: anything not killed is declared survived — and
|
|
26
|
+
// "the patch never landed" becomes indistinguishable from "the test is weak". The
|
|
27
|
+
// mutation is caught ONLY by the harness's third outcome; the first two pass through
|
|
28
|
+
// it unchanged.
|
|
29
|
+
name: '"never landed" collapses back into "survived"',
|
|
30
|
+
harness: HARNESS,
|
|
31
|
+
expect: "the verdict must be named unusable, not survived",
|
|
32
|
+
disables:
|
|
33
|
+
"the distinction between two verdicts that send the reader in opposite directions: " +
|
|
34
|
+
"survived — strengthen the test, unusable — fix the mutation. Conflating them sends " +
|
|
35
|
+
"the engine to rewrite a working harness",
|
|
36
|
+
edits: [
|
|
37
|
+
[
|
|
38
|
+
DRIVER,
|
|
39
|
+
"if (unusable.length)\n console.log(",
|
|
40
|
+
"if (false)\n console.log(",
|
|
41
|
+
],
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "the refusal for a skipping harness is removed",
|
|
46
|
+
harness: HARNESS,
|
|
47
|
+
expect: "a skip must REFUSE",
|
|
48
|
+
disables:
|
|
49
|
+
"the refusal itself — the driver goes back to judging by a harness that never ran",
|
|
50
|
+
edits: [[DRIVER, " if (skipped.length) {", " if (false) {"]],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "the skip exit code stops being recognized",
|
|
54
|
+
harness: HARNESS,
|
|
55
|
+
expect: "a skip must REFUSE",
|
|
56
|
+
disables:
|
|
57
|
+
'recognizing the protocol code — a skip starts reading as "red"',
|
|
58
|
+
edits: [
|
|
59
|
+
[
|
|
60
|
+
DRIVER,
|
|
61
|
+
"const VIGILES_SKIP_EXIT = 77;",
|
|
62
|
+
"const VIGILES_SKIP_EXIT = 76;",
|
|
63
|
+
],
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Types for lib/paper-config.mjs, which `src/*.ts` imports. Without this file TypeScript sees
|
|
2
|
+
// `any` and each import needs a `@ts-expect-error`, which covers only the next line and stops
|
|
3
|
+
// working as soon as a formatter wraps the import across several lines.
|
|
4
|
+
//
|
|
5
|
+
// The field names are typed as `string`, not as their values, so that the value is written
|
|
6
|
+
// once, in paper-config.mjs.
|
|
7
|
+
|
|
8
|
+
export declare const CONFIG_KEY: string;
|
|
9
|
+
export declare const LEGACY_CONFIG_KEY: string;
|
|
10
|
+
export declare const LEGACY_KEY_MESSAGE: string;
|
|
11
|
+
|
|
12
|
+
/** Where a parsed package.json keeps the settings: the new key, else the old one. */
|
|
13
|
+
export declare function declaredSettings(pkg: unknown): {
|
|
14
|
+
readonly settings: unknown;
|
|
15
|
+
readonly legacy: boolean;
|
|
16
|
+
readonly conflict: string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** The settings object alone — undefined when absent or on a conflict. */
|
|
20
|
+
export declare function settingsOf(
|
|
21
|
+
pkg: unknown,
|
|
22
|
+
): Readonly<Record<string, unknown>> | undefined;
|
|
23
|
+
export declare const DEFAULT_PAPERS_ROOT: string;
|
|
24
|
+
export declare const PAPERS_DIR_FIELD: string;
|
|
25
|
+
export declare const OLD_PAPERS_DIR_FIELD: string;
|
|
26
|
+
|
|
27
|
+
/** The error text for settings that still use the old field name, or `null` when they do not. */
|
|
28
|
+
export declare function renamedFieldMessage(
|
|
29
|
+
settings: unknown,
|
|
30
|
+
where?: string,
|
|
31
|
+
): string | null;
|
|
32
|
+
|
|
33
|
+
/** Every key the settings object may hold, mapped to who reads it. */
|
|
34
|
+
export declare const SETTINGS_KEYS: Readonly<Record<string, string>>;
|