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,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npm run check` — ONE command you run on yourself before pushing.
|
|
4
|
+
*
|
|
5
|
+
* ── WHY IT EXISTS, AND IT IS A MEASURED FAILURE, NOT A TIDINESS IDEA ───────────────────────
|
|
6
|
+
* On 2026-09-19 a change to `paper/research-question` was pushed that broke the test suite.
|
|
7
|
+
* The gates were run afterwards — `lint`, `check:readme`, `check:globs`, `check:content-only`,
|
|
8
|
+
* and the rule's own mutation battery, all green — but `npm test` and `test:install` were not,
|
|
9
|
+
* because there were eleven separate scripts and no way to run them except from memory. Five
|
|
10
|
+
* callers across two files had been left behind by the change, and the suite said so; nobody
|
|
11
|
+
* asked it.
|
|
12
|
+
*
|
|
13
|
+
* A prose reminder would not have helped. The same class is on record from vigiles: three
|
|
14
|
+
* agents were handed a list of five gate commands, all three reported "gates 5/5 green", and
|
|
15
|
+
* not one ran the harness tests — they were not on the list.
|
|
16
|
+
*
|
|
17
|
+
* 🔴 SO THE LOAD-BEARING PART HERE IS NOT THE LIST OF GATES — IT IS THE TAIL. This script
|
|
18
|
+
* prints which CI jobs it does NOT reproduce, and why. A check that is silent about its own
|
|
19
|
+
* boundary reads as complete, which is the same class as a counter that counts what it ignores.
|
|
20
|
+
*
|
|
21
|
+
* ── WHAT KEEPS THE LIST FROM GOING STALE ───────────────────────────────────────────────────
|
|
22
|
+
* `check.harness.mjs` sits beside it and pulls the job names OUT OF THE WORKFLOW ITSELF,
|
|
23
|
+
* requiring every one to be either covered here or named in `NOT_COVERED` with a reason. Add a
|
|
24
|
+
* job to CI and the harness goes red the same day — the list cannot quietly fall behind.
|
|
25
|
+
*
|
|
26
|
+
* ── WHY IT DOES NOT REPLACE THE CI JOBS ────────────────────────────────────────────────────
|
|
27
|
+
* The jobs solve a different problem: a different environment (TeX Live exists only in
|
|
28
|
+
* `build-e2e`, and macOS only in its second matrix cell) and a named culprit per bond. One command cannot do that and should
|
|
29
|
+
* not try. This is about a human being able to check themselves BEFORE the push.
|
|
30
|
+
*
|
|
31
|
+
* ── NO `--fast` FLAG, ON PURPOSE ───────────────────────────────────────────────────────────
|
|
32
|
+
* A subset flag re-creates the exact failure above: the cheap half gets run and reported as
|
|
33
|
+
* "the gates". The slow steps instead SKIP LOUDLY when their prerequisite is genuinely absent
|
|
34
|
+
* (no TeX, no network), and the tail names every skip. "I ran check" then means one thing.
|
|
35
|
+
*/
|
|
36
|
+
import { spawnSync } from "node:child_process";
|
|
37
|
+
import { existsSync } from "node:fs";
|
|
38
|
+
import { delimiter, dirname, join } from "node:path";
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
40
|
+
|
|
41
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every local gate, in the order a human wants them: cheapest and most likely to fail first,
|
|
45
|
+
* so a typo does not cost eight minutes. `job` names the CI job this step reproduces, and the
|
|
46
|
+
* harness checks those names against the real workflow.
|
|
47
|
+
*
|
|
48
|
+
* Each gate says how it runs in one of two ways:
|
|
49
|
+
* - `script` — an npm script that people also run by hand (`build`, `lint`, `fmt:check`, `test`);
|
|
50
|
+
* - `run` — the command itself, for checks that have no npm script of their own. Keeping these
|
|
51
|
+
* out of package.json keeps its script list short; this file is the one place they are listed.
|
|
52
|
+
* Programs installed by npm (`vigiles`, `eslint`) are found because `node_modules/.bin` is put
|
|
53
|
+
* first on PATH below, the same way `npm run` does it.
|
|
54
|
+
*/
|
|
55
|
+
const locked = (...cmd) => ["node", "scripts/exclusive.mjs", ...cmd];
|
|
56
|
+
|
|
57
|
+
export const GATES = [
|
|
58
|
+
{
|
|
59
|
+
name: "the package compiles",
|
|
60
|
+
job: "gates",
|
|
61
|
+
script: "build",
|
|
62
|
+
// First because everything below imports from `dist/`. A stale build makes every later
|
|
63
|
+
// failure a lie about its own cause.
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "rules and scripts lint",
|
|
67
|
+
job: "gates",
|
|
68
|
+
script: "lint",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "skills lint, and the vigiles marks in README.md and CLAUDE.md",
|
|
72
|
+
job: "gates",
|
|
73
|
+
run: locked("vigiles", "lint", ".", "README.md"),
|
|
74
|
+
// README.md is passed to `vigiles lint` by name: it is not an instruction file, so vigiles
|
|
75
|
+
// would not open it on its own. The marks tie the `paperlint init` and `paperlint new` sections to the
|
|
76
|
+
// functions that implement them, and the lint fails when either function is renamed.
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "formatting",
|
|
80
|
+
job: "gates",
|
|
81
|
+
script: "fmt:check",
|
|
82
|
+
// Prettier defaults, the same config as vigiles. What it must not touch — compiled
|
|
83
|
+
// SKILL.md, fixture data, raw runs — is listed in `.prettierignore` with the reason.
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "every declared rule is enabled for a file on disk",
|
|
87
|
+
job: "gates",
|
|
88
|
+
run: locked("node", "scripts/rules-see-files.mjs"),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "rules read content, not the filesystem",
|
|
92
|
+
job: null,
|
|
93
|
+
// 🔴 Deliberately not in CI, and this is the one asymmetry worth stating rather than
|
|
94
|
+
// hiding: the check is about a property of the rules' source, which cannot change between
|
|
95
|
+
// a developer's tree and the runner's. Running it twice buys nothing; NOT running it
|
|
96
|
+
// locally buys a defect that reaches review.
|
|
97
|
+
reason:
|
|
98
|
+
"source-only property — identical in every environment, so CI adds nothing",
|
|
99
|
+
run: locked("node", "scripts/rules-are-content-only.mjs"),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "every test — vitest over *.test.ts, then the vigiles harnesses (npm test)",
|
|
103
|
+
job: "gates",
|
|
104
|
+
script: "test",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
// vitest transpiles without type-checking, so the tests' types are checked here.
|
|
108
|
+
name: "the tests type-check",
|
|
109
|
+
job: "gates",
|
|
110
|
+
run: ["tsc", "-p", "tsconfig.test.json"],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "mutation batteries are frozen — none new, none grown (#52)",
|
|
114
|
+
job: "gates",
|
|
115
|
+
run: locked("node", "scripts/mutation-batteries-frozen.mjs"),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "legacy layer exemptions are frozen — none new, none grown (#76)",
|
|
119
|
+
job: "gates",
|
|
120
|
+
run: locked("node", "scripts/layer-legacy-frozen.mjs"),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "mutation batteries — every guard is killed by its own assertion",
|
|
124
|
+
job: "gates",
|
|
125
|
+
run: locked("node", "scripts/run-mutations.mjs"),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "install e2e — pack, install under npm and pnpm, run the binary",
|
|
129
|
+
job: "gates",
|
|
130
|
+
run: locked("node", "test/e2e/install.mjs"),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "build e2e — a real pdflatex, and the PDF's fonts are measured",
|
|
134
|
+
job: "build-e2e",
|
|
135
|
+
run: locked("node", "test/e2e/build.mjs"),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "banal e2e — the real banal on pdf.js-written XML gives banal-on-pdftohtml's numbers",
|
|
139
|
+
job: "build-e2e",
|
|
140
|
+
run: locked("node", "test/e2e/banal.mjs"),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "toolchain e2e — real TeX Live into $RPP_TEXLIVE_DIR, then a build with only it on PATH",
|
|
144
|
+
job: "build-e2e",
|
|
145
|
+
run: locked("node", "test/e2e/toolchain.mjs"),
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
/** The command line a gate runs, as an argument list. */
|
|
150
|
+
export function commandOf(gate) {
|
|
151
|
+
return gate.script ? ["npm", "run", "-s", gate.script] : gate.run;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* CI jobs this command does NOT reproduce. Each needs a reason a reader can check, because an
|
|
156
|
+
* unexplained gap is indistinguishable from an oversight — which is what the harness enforces.
|
|
157
|
+
*/
|
|
158
|
+
export const NOT_COVERED = {
|
|
159
|
+
// build-e2e's macOS cell is the one part of a covered job no local command can stand in for:
|
|
160
|
+
// it exists because issue #9 appeared on macOS alone (/var is a symlink to /private/var).
|
|
161
|
+
merge:
|
|
162
|
+
"dependabot-automerge.yml merges the bot's own pull requests once CI is green. It checks " +
|
|
163
|
+
"nothing itself; there is no local equivalent because it acts on GitHub, not on the tree.",
|
|
164
|
+
release:
|
|
165
|
+
"release.yml publishes to npm and tags a GitHub release; running it locally would publish. " +
|
|
166
|
+
"What can break it silently, the release notes, is rendered by scripts/release-config.test.ts in npm test.",
|
|
167
|
+
validate:
|
|
168
|
+
"pr-title.yml checks the pull request's TITLE (the squash commit semantic-release reads), " +
|
|
169
|
+
"which exists only on GitHub, not in the tree.",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Exit 77 means "declared skip" — the same code vigiles' runner uses (`SKIP_EXIT_CODE`), and the
|
|
174
|
+
* one lib/mutation-driver.mjs already recognizes. The e2e steps exit 77 when a tool they need is
|
|
175
|
+
* absent and --strict is off. Anything else nonzero is a failure.
|
|
176
|
+
*
|
|
177
|
+
* 🔴 A SKIP IS A THIRD OUTCOME. Until the Codex review on #45 the e2e steps exited 0 on a skip,
|
|
178
|
+
* this loop counted that as a pass, and `skipped` below was declared and never filled: a machine
|
|
179
|
+
* without pnpm or TeX printed "all gates passed" for runs that never happened.
|
|
180
|
+
*/
|
|
181
|
+
export const SKIP_EXIT = 77;
|
|
182
|
+
export function outcome(status) {
|
|
183
|
+
if (status === 0) return "pass";
|
|
184
|
+
if (status === SKIP_EXIT) return "skip";
|
|
185
|
+
return "fail";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
189
|
+
const BIN_FIRST_PATH = [join(ROOT, "node_modules", ".bin"), process.env.PATH]
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.join(delimiter);
|
|
192
|
+
const failed = [];
|
|
193
|
+
const skipped = [];
|
|
194
|
+
|
|
195
|
+
for (const g of GATES) {
|
|
196
|
+
process.stdout.write(`── ${g.name}\n`);
|
|
197
|
+
const [cmd, ...args] = commandOf(g);
|
|
198
|
+
const r = spawnSync(cmd, args, {
|
|
199
|
+
cwd: ROOT,
|
|
200
|
+
stdio: "inherit",
|
|
201
|
+
encoding: "utf8",
|
|
202
|
+
env: { ...process.env, PATH: BIN_FIRST_PATH },
|
|
203
|
+
});
|
|
204
|
+
// 🔴 The exit code is read from the command itself, never from a pipe. `cmd | tail && …`
|
|
205
|
+
// reports the FILTER's status, which is almost always zero — that is how a red harness
|
|
206
|
+
// shipped on 2026-09-16.
|
|
207
|
+
const o = outcome(r.status);
|
|
208
|
+
if (o === "pass") continue;
|
|
209
|
+
const shown = commandOf(g).join(" ");
|
|
210
|
+
if (o === "skip") skipped.push(`${g.name} (${shown} → ${SKIP_EXIT})`);
|
|
211
|
+
else failed.push(`${g.name} (${shown} → ${r.status})`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log("");
|
|
215
|
+
if (failed.length) {
|
|
216
|
+
console.error("🔴 gates failed:");
|
|
217
|
+
for (const f of failed) console.error(` ${f}`);
|
|
218
|
+
} else {
|
|
219
|
+
const passed = GATES.length - skipped.length;
|
|
220
|
+
console.log(
|
|
221
|
+
`✓ ${passed} gate(s) passed${skipped.length ? `, ${skipped.length} SKIPPED — not run, not passed` : ""}`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
if (skipped.length) for (const s of skipped) console.log(`⏳ skipped: ${s}`);
|
|
225
|
+
|
|
226
|
+
// THE TAIL. Not decoration — the reason this file exists rather than a list in a doc.
|
|
227
|
+
console.log("\nWhat this command does NOT cover:");
|
|
228
|
+
for (const [job, why] of Object.entries(NOT_COVERED)) {
|
|
229
|
+
console.log(` CI job «${job}» — ${why}`);
|
|
230
|
+
}
|
|
231
|
+
const localOnly = GATES.filter((g) => g.job === null);
|
|
232
|
+
for (const g of localOnly) {
|
|
233
|
+
console.log(` (and «${g.name}» runs ONLY here — ${g.reason})`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
process.exit(failed.length ? 1 : 0);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export { ROOT, existsSync, join };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Battery for `npm run check` — and the thing under test is NOT "does it run every gate".
|
|
3
|
+
* That part is visible: you watch it happen. What is invisible, and therefore what this kills,
|
|
4
|
+
* is whether the harness beside it can still tell when the gate list has fallen behind CI.
|
|
5
|
+
*
|
|
6
|
+
* 🔴 The failure mode this whole file exists against, measured 2026-09-19: a list of gates kept
|
|
7
|
+
* by hand becomes a fossil the day after it is written. `check.harness.mjs` is the mechanism that
|
|
8
|
+
* stops that — it reads job names out of the workflow rather than trusting a copy — and its
|
|
9
|
+
* success state is silence. "It passed" and "it cannot fail" look identical from outside.
|
|
10
|
+
*
|
|
11
|
+
* Each case removes ONE property and names the assertion that must notice.
|
|
12
|
+
*/
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { runMutations } from "../lib/mutation-driver.mjs";
|
|
16
|
+
|
|
17
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const ROOT = resolve(HERE, "..");
|
|
19
|
+
const CHECK = join(HERE, "check.mjs");
|
|
20
|
+
const HARNESS = join(HERE, "check.harness.mjs");
|
|
21
|
+
|
|
22
|
+
process.exit(
|
|
23
|
+
runMutations({
|
|
24
|
+
root: ROOT,
|
|
25
|
+
runner: "node",
|
|
26
|
+
cases: [
|
|
27
|
+
{
|
|
28
|
+
name: "a CI job stops being accounted for — the tail goes quiet about its own boundary",
|
|
29
|
+
harness: HARNESS,
|
|
30
|
+
// No job name here: the harness reads every workflow in directory order, so which job
|
|
31
|
+
// fails first depends on file names, not on what this mutation proves.
|
|
32
|
+
expect:
|
|
33
|
+
"is either reproduced by a gate or named in NOT_COVERED with a reason",
|
|
34
|
+
disables:
|
|
35
|
+
"the reason this command is not just a shell alias. Emptying NOT_COVERED removes the " +
|
|
36
|
+
"one sentence that tells a reader what the green does NOT mean — and a check silent " +
|
|
37
|
+
"about its boundary reads as complete, which is the counter-that-counts-what-it-ignores " +
|
|
38
|
+
"class this repository keeps re-finding",
|
|
39
|
+
edits: [
|
|
40
|
+
[
|
|
41
|
+
CHECK,
|
|
42
|
+
"export const NOT_COVERED = {",
|
|
43
|
+
"export const NOT_COVERED = { }; const _unused = {",
|
|
44
|
+
],
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "a gate names a CI job that does not exist — coverage on paper, none in fact",
|
|
49
|
+
harness: HARNESS,
|
|
50
|
+
expect: "names a job that really exists in a workflow",
|
|
51
|
+
disables:
|
|
52
|
+
"the link between the list and the workflow. A dead job name is WORSE than a missing " +
|
|
53
|
+
"one: it reads as coverage and delivers nothing, and nothing else in the repository " +
|
|
54
|
+
"would ever contradict it",
|
|
55
|
+
// 🔴 The anchor is a gate whose job is ALSO claimed by other gates (`gates`), and that
|
|
56
|
+
// is not incidental. Renaming a solely-claimed job — `build-e2e` — kills the FIRST
|
|
57
|
+
// assertion instead, because that job becomes uncovered before anyone asks whether the
|
|
58
|
+
// new name exists. Measured: the driver reported "RED but a DIFFERENT case". Two
|
|
59
|
+
// assertions overlapping is fine; a battery that cannot tell them apart is not.
|
|
60
|
+
edits: [
|
|
61
|
+
[
|
|
62
|
+
CHECK,
|
|
63
|
+
'job: "gates",\n run: locked("vigiles", "lint"',
|
|
64
|
+
'job: "gates-renamed-in-ci",\n run: locked("vigiles", "lint"',
|
|
65
|
+
],
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "a gate points at an npm script nobody can run",
|
|
70
|
+
harness: HARNESS,
|
|
71
|
+
expect: "maps to a script that exists",
|
|
72
|
+
disables:
|
|
73
|
+
"the guarantee that every listed gate is runnable. A renamed script fails at the exact " +
|
|
74
|
+
"moment someone is trusting the list — which is the worst possible moment to find out",
|
|
75
|
+
edits: [[CHECK, 'script: "fmt:check"', 'script: "fmt:check-renamed"']],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "a gate runs a file that is not on disk",
|
|
79
|
+
harness: HARNESS,
|
|
80
|
+
expect: "runs files that exist",
|
|
81
|
+
disables:
|
|
82
|
+
"the same guarantee for gates that name their command directly instead of an npm " +
|
|
83
|
+
"script. A moved or renamed file would only show up when someone runs the whole list",
|
|
84
|
+
edits: [
|
|
85
|
+
[
|
|
86
|
+
CHECK,
|
|
87
|
+
'"scripts/rules-are-content-only.mjs"',
|
|
88
|
+
'"scripts/rules-are-content-only-renamed.mjs"',
|
|
89
|
+
],
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "a gate drops out of CI silently, with no reason given",
|
|
94
|
+
harness: HARNESS,
|
|
95
|
+
expect: "carries a reason",
|
|
96
|
+
disables:
|
|
97
|
+
"the cost of removing something from CI. Without this, `job: null` is the quiet way to " +
|
|
98
|
+
"take a check out of the pipeline: the local run still shows it green and nothing says " +
|
|
99
|
+
"it stopped being enforced anywhere else",
|
|
100
|
+
edits: [
|
|
101
|
+
[
|
|
102
|
+
CHECK,
|
|
103
|
+
'reason:\n "source-only property',
|
|
104
|
+
'reasonRemoved:\n "source-only property',
|
|
105
|
+
],
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE GREEN-ZERO GUARD over an ESLint JSON report.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 WHY THIS EXISTS. `eslint` prints findings and exits 0 when there are none — and "no findings"
|
|
5
|
+
* is byte-identical to "not one rule was handed a single file". A rule whose glob matched nothing is
|
|
6
|
+
* never invoked, and a rule that is never invoked physically cannot report that. A CI job built on
|
|
7
|
+
* the exit code alone therefore passes loudest exactly when it measured nothing.
|
|
8
|
+
*
|
|
9
|
+
* WHAT IT DOES AND DOES NOT CLAIM. It answers one question that needs no knowledge of the caller's
|
|
10
|
+
* config: *was anything linted at all*. It does NOT audit per-rule coverage — a run can lint a
|
|
11
|
+
* thousand files while one rule still sees none. That is `scripts/rules-see-files.mjs`, which reads this
|
|
12
|
+
* package's own config and cannot read yours.
|
|
13
|
+
*
|
|
14
|
+
* 🔴 IT LIVES IN A FILE, NOT INSIDE `action.yml`. An inline `node -e` blob in a composite step is
|
|
15
|
+
* unreachable from a test: the only way to exercise it is to run the whole action on a runner. This
|
|
16
|
+
* file is called by the action and by `action.harness.mjs` with the same arguments, so the harness
|
|
17
|
+
* proves the BEHAVIOUR rather than the presence of a string in YAML.
|
|
18
|
+
*
|
|
19
|
+
* Usage: node scripts/eslint-report-guard.mjs <report.json> <eslint-rc> [paths] [config]
|
|
20
|
+
* Exit: 1 when nothing was measured (unparsable report, or zero files linted)
|
|
21
|
+
* otherwise ESLint's own return code, passed through untouched
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
|
|
25
|
+
/** @returns {{ code: number, lines: string[] }} — never throws, so the caller decides how to exit. */
|
|
26
|
+
export function guard(
|
|
27
|
+
reportPath,
|
|
28
|
+
eslintRc,
|
|
29
|
+
{ paths = ".", config = "eslint.config.mjs" } = {},
|
|
30
|
+
) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
let res;
|
|
33
|
+
try {
|
|
34
|
+
res = JSON.parse(readFileSync(reportPath, "utf8"));
|
|
35
|
+
} catch {
|
|
36
|
+
lines.push(
|
|
37
|
+
`::error::ESLint wrote no parsable report to ${reportPath} (rc=${eslintRc}). Nothing was measured.`,
|
|
38
|
+
);
|
|
39
|
+
return { code: 1, lines };
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(res)) {
|
|
42
|
+
lines.push(
|
|
43
|
+
`::error::The report at ${reportPath} is not an ESLint result array. Nothing was measured.`,
|
|
44
|
+
);
|
|
45
|
+
return { code: 1, lines };
|
|
46
|
+
}
|
|
47
|
+
// THE GUARD. Not "were there findings" — "was anything linted at all".
|
|
48
|
+
if (res.length === 0) {
|
|
49
|
+
lines.push(
|
|
50
|
+
"::error::ESLint linted ZERO files. In the exit code this is indistinguishable from a clean " +
|
|
51
|
+
`run, and it means the paths or the config matched nothing. paths=${paths} config=${config}`,
|
|
52
|
+
);
|
|
53
|
+
return { code: 1, lines };
|
|
54
|
+
}
|
|
55
|
+
const msgs = res.flatMap((f) => f.messages ?? []);
|
|
56
|
+
const errors = msgs.filter((m) => m.severity === 2);
|
|
57
|
+
for (const f of res)
|
|
58
|
+
for (const m of f.messages ?? [])
|
|
59
|
+
lines.push(
|
|
60
|
+
`${m.severity === 2 ? "error" : "warning"} ${f.filePath}:${m.line}:${m.column} ` +
|
|
61
|
+
`${m.ruleId ?? "(no rule)"} ${m.message}`,
|
|
62
|
+
);
|
|
63
|
+
lines.push(
|
|
64
|
+
`\nlinted ${res.length} file(s) · ${msgs.length} finding(s) · ${errors.length} error(s) · ` +
|
|
65
|
+
`${msgs.length - errors.length} warning(s)`,
|
|
66
|
+
);
|
|
67
|
+
return { code: eslintRc, lines };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Guarded by realpath, not by `import.meta.url === \`file://${process.argv[1]}\``: through a symlink
|
|
71
|
+
// `import.meta.url` is the REALPATH while `argv[1]` is the link, so that idiom compares false and the
|
|
72
|
+
// CLI silently does nothing. Consumers reach this package through a symlinked tree.
|
|
73
|
+
if (
|
|
74
|
+
process.argv[1] &&
|
|
75
|
+
(await import("node:fs")).realpathSync(process.argv[1]) ===
|
|
76
|
+
(await import("node:url")).fileURLToPath(import.meta.url)
|
|
77
|
+
) {
|
|
78
|
+
const [report, rc, paths, config] = process.argv.slice(2);
|
|
79
|
+
const { code, lines } = guard(report, Number(rc), { paths, config });
|
|
80
|
+
for (const l of lines) console.log(l);
|
|
81
|
+
process.exit(code);
|
|
82
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MUTUAL EXCLUSION FOR RUNS THAT TOUCH THE SAME FILES.
|
|
4
|
+
*
|
|
5
|
+
* ── WHY ──────────────────────────────────────────────────────────────────────────────────────
|
|
6
|
+
* The mutation batteries (`scripts/run-mutations.mjs`) edit the sources IN PLACE — a deliberate strategy, explained in the header of
|
|
7
|
+
* `lib/mutation-driver.mjs`: nine cases rewrite the real file, and copying the whole repository
|
|
8
|
+
* per mutation would mean minutes instead of seconds.
|
|
9
|
+
*
|
|
10
|
+
* The strategy has one cost: while the battery runs, ANY parallel `npm test` reads a file in its
|
|
11
|
+
* mutilated state and fails — with a message about an assertion, not about a mutation. The
|
|
12
|
+
* failure is FALSE, NON-DETERMINISTIC (it depends on which second you caught) and reads as "the
|
|
13
|
+
* tests are flaky", i.e. it leads in exactly the direction where there is nothing to fix. This
|
|
14
|
+
* has already cost an investigation: two runs in a row gave DIFFERENT error messages, and the
|
|
15
|
+
* conclusion "I broke round-diff" was wrong.
|
|
16
|
+
*
|
|
17
|
+
* ── WHY A LOCK AND NOT AN INSTRUCTION "DON'T RUN THEM AT THE SAME TIME" ───────────────────────
|
|
18
|
+
* The instruction already existed — as prose, in the consumer's CLAUDE.md. Prose does not
|
|
19
|
+
* execute, so it is not checked and does not apply to anyone who has not read it: the person in
|
|
20
|
+
* the other terminal, the agent, the editor with tests on save. A lock refuses LOUDLY and names
|
|
21
|
+
* the reason at the moment it matters.
|
|
22
|
+
*
|
|
23
|
+
* ── HOW ─────────────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
* `mkdirSync` is atomic: either we created the directory or it already existed — there is no
|
|
25
|
+
* race, unlike "check existsSync, then write". Inside it lie the holder's pid and command; if the
|
|
26
|
+
* process with that pid is dead, the lock is taken over with an explicit message, otherwise an
|
|
27
|
+
* interrupted run would block the repository forever, and the first cure would be "delete the
|
|
28
|
+
* lock by hand", i.e. switching the mechanism off.
|
|
29
|
+
*
|
|
30
|
+
* The lock is released in `finally` AND on signals — for exactly the same reason the mutation
|
|
31
|
+
* driver restores the sources on SIGINT/SIGTERM.
|
|
32
|
+
*/
|
|
33
|
+
import {
|
|
34
|
+
mkdirSync,
|
|
35
|
+
writeFileSync,
|
|
36
|
+
readFileSync,
|
|
37
|
+
rmSync,
|
|
38
|
+
existsSync,
|
|
39
|
+
} from "node:fs";
|
|
40
|
+
import { spawnSync } from "node:child_process";
|
|
41
|
+
import { dirname, join } from "node:path";
|
|
42
|
+
import { fileURLToPath } from "node:url";
|
|
43
|
+
|
|
44
|
+
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
45
|
+
const LOCK = join(ROOT, ".vigiles", "exclusive.lock");
|
|
46
|
+
const INFO = join(LOCK, "holder.json");
|
|
47
|
+
|
|
48
|
+
const argv = process.argv.slice(2);
|
|
49
|
+
if (argv.length === 0) {
|
|
50
|
+
console.error("usage: node scripts/exclusive.mjs <command> [args…]");
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The PARENT is created recursively; only the lock itself must be a non-recursive mkdir, because
|
|
55
|
+
// that is the atomic step. Nothing else guarantees `.vigiles/` exists: once
|
|
56
|
+
// `.vigiles/coverage.json` stopped being tracked, a fresh checkout has no such directory, and the
|
|
57
|
+
// lock's mkdir failed with ENOENT before any gate ran (CI on e389916).
|
|
58
|
+
mkdirSync(dirname(LOCK), { recursive: true });
|
|
59
|
+
|
|
60
|
+
/** Is the process alive. `kill(pid, 0)` sends nothing — it only checks existence and rights. */
|
|
61
|
+
function alive(pid) {
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 0);
|
|
64
|
+
return true;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return e.code === "EPERM"; // exists but belongs to someone else — counted as alive
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function readHolder() {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(readFileSync(INFO, "utf8"));
|
|
73
|
+
} catch {
|
|
74
|
+
return null; // the lock exists, its description is unreadable — resolved below as "dead"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function acquire() {
|
|
79
|
+
try {
|
|
80
|
+
mkdirSync(LOCK, { recursive: false });
|
|
81
|
+
} catch (e) {
|
|
82
|
+
if (e.code !== "EEXIST") throw e;
|
|
83
|
+
const holder = readHolder();
|
|
84
|
+
if (holder && alive(holder.pid)) {
|
|
85
|
+
console.error(
|
|
86
|
+
`🔴 refused: this repository is busy with a run that EDITS FILES IN PLACE.\n` +
|
|
87
|
+
` held by: pid ${holder.pid}, "${holder.cmd}", since ${holder.at}\n` +
|
|
88
|
+
` A parallel run would produce FALSE failures — a test would read the source in its\n` +
|
|
89
|
+
` mutilated state and complain about an assertion, not a mutation. Wait for it to finish.`,
|
|
90
|
+
);
|
|
91
|
+
process.exit(3);
|
|
92
|
+
}
|
|
93
|
+
console.error(
|
|
94
|
+
`⚠️ the lock was left behind by a run that no longer exists` +
|
|
95
|
+
(holder
|
|
96
|
+
? ` (pid ${holder.pid}, "${holder.cmd}")`
|
|
97
|
+
: " (description unreadable)") +
|
|
98
|
+
` — taking it over.`,
|
|
99
|
+
);
|
|
100
|
+
rmSync(LOCK, { recursive: true, force: true });
|
|
101
|
+
mkdirSync(LOCK, { recursive: false });
|
|
102
|
+
}
|
|
103
|
+
writeFileSync(
|
|
104
|
+
INFO,
|
|
105
|
+
JSON.stringify(
|
|
106
|
+
{ pid: process.pid, cmd: argv.join(" "), at: new Date().toISOString() },
|
|
107
|
+
null,
|
|
108
|
+
2,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const release = () => {
|
|
114
|
+
if (existsSync(LOCK)) rmSync(LOCK, { recursive: true, force: true });
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
acquire();
|
|
118
|
+
for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
119
|
+
process.on(sig, () => {
|
|
120
|
+
release();
|
|
121
|
+
process.exit(sig === "SIGINT" ? 130 : 143);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 🔴 `process.on("exit")`, NOT `finally`, AND THIS IS NOT STYLE. The first edition released the
|
|
126
|
+
// lock in `finally` — and it STAYED ON DISK: `process.exit()` terminates the process immediately,
|
|
127
|
+
// the stack is not unwound, `finally` does not run. Caught by a run (the lock outlived a
|
|
128
|
+
// successful launch), not by reading. The cost of the defect would have been exactly the inverse
|
|
129
|
+
// of the intent: every run would leave the repository "busy", and the first cure would be "delete
|
|
130
|
+
// the lock by hand", i.e. switching the mechanism off.
|
|
131
|
+
process.on("exit", release);
|
|
132
|
+
|
|
133
|
+
const r = spawnSync(argv[0], argv.slice(1), { stdio: "inherit", shell: false });
|
|
134
|
+
if (r.error) {
|
|
135
|
+
console.error(`🔴 failed to launch "${argv.join(" ")}": ${r.error.message}`);
|
|
136
|
+
process.exit(127);
|
|
137
|
+
}
|
|
138
|
+
process.exit(r.status ?? 1);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"issue": "https://github.com/zernie/research-paper-pipeline/issues/77",
|
|
3
|
+
"why": "A *.harness.* file tests the agent surface and imports runHook, runHarnessTest or runEval from vigiles. Plain unit tests are *.test.ts, run by vitest. These are the harness files that existed on main on 2026-09-25 and import none of the three. The list only shrinks: rename a file to *.test.ts (issue #77), or make it a real harness, then delete its entry. Checked by scripts/harness-api.test.ts, part of npm test.",
|
|
4
|
+
"harnesses": [
|
|
5
|
+
"action.harness.mjs",
|
|
6
|
+
"eslint-rules/doc-fields.harness.mjs",
|
|
7
|
+
"eslint-rules/install-path-literals.harness.mjs",
|
|
8
|
+
"eslint-rules/latex-language.harness.mjs",
|
|
9
|
+
"eslint-rules/paper-research-question.harness.mjs",
|
|
10
|
+
"eslint-rules/paper-stages.harness.mjs",
|
|
11
|
+
"eslint-rules/paper-typography.harness.mjs",
|
|
12
|
+
"eslint-rules/papers.harness.mjs",
|
|
13
|
+
"eslint-rules/pdf-last-page-balance.harness.mjs",
|
|
14
|
+
"eslint-rules/review-findings-cause.harness.mjs",
|
|
15
|
+
"eslint-rules/temp-root-realpath.harness.mjs",
|
|
16
|
+
"eslint-rules/tex-build.harness.mjs",
|
|
17
|
+
"fixtures/real-markdown-paper/real-markdown-paper.harness.mjs",
|
|
18
|
+
"lib/agent-cli-version.harness.mjs",
|
|
19
|
+
"lib/mutation-driver.harness.mjs",
|
|
20
|
+
"lib/paper-config.harness.mjs",
|
|
21
|
+
"lib/skill-trigger-cases.harness.mjs",
|
|
22
|
+
"scripts/check.harness.mjs",
|
|
23
|
+
"scripts/mutation-batteries-frozen.harness.mjs",
|
|
24
|
+
"scripts/rules-are-content-only.harness.mjs",
|
|
25
|
+
"scripts/rules-see-files.harness.mjs",
|
|
26
|
+
"skills/analyze-sibling-paper/analyze-sibling-paper.harness.mjs",
|
|
27
|
+
"skills/argument-arc/argument-arc.harness.mjs",
|
|
28
|
+
"skills/build-benchmark/build-benchmark.harness.mjs",
|
|
29
|
+
"skills/camera-ready/camera-ready.harness.mjs",
|
|
30
|
+
"skills/cold-read-diff/cold-read-diff.harness.mjs",
|
|
31
|
+
"skills/draft-paper/draft-paper.harness.mjs",
|
|
32
|
+
"skills/extend-paper/extend-paper.harness.mjs",
|
|
33
|
+
"skills/find-venue/find-venue.harness.mjs",
|
|
34
|
+
"skills/grade-paper-writing/grade-paper-writing.harness.mjs",
|
|
35
|
+
"skills/harden-paper/harden-paper.harness.mjs",
|
|
36
|
+
"skills/map-prior-work/map-prior-work.harness.mjs",
|
|
37
|
+
"skills/osf-artifact-upload/osf-artifact-upload.harness.mjs",
|
|
38
|
+
"skills/paper-adversarial-review/paper-adversarial-review.harness.mjs",
|
|
39
|
+
"skills/paper-pipeline/paper-pipeline.harness.mjs",
|
|
40
|
+
"skills/paper-pipeline/scripts/artifact-coverage.harness.mjs",
|
|
41
|
+
"skills/paper-pipeline/scripts/consumer.harness.mjs",
|
|
42
|
+
"skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs",
|
|
43
|
+
"skills/paper-pipeline/scripts/generated-code.harness.mjs",
|
|
44
|
+
"skills/paper-pipeline/scripts/pipeline-check.harness.mjs",
|
|
45
|
+
"skills/paper-pipeline/scripts/population-map.harness.mjs",
|
|
46
|
+
"skills/paper-pipeline/scripts/provenance.harness.mjs",
|
|
47
|
+
"skills/paper-pipeline/scripts/round-diff.harness.mjs",
|
|
48
|
+
"skills/paper-status/paper-status.harness.mjs",
|
|
49
|
+
"skills/pc-panel-review/pc-panel-review.harness.mjs",
|
|
50
|
+
"skills/plan-paper-timeline/plan-paper-timeline.harness.mjs",
|
|
51
|
+
"skills/render-paper/extract-pdf-facts.harness.mjs",
|
|
52
|
+
"skills/render-paper/render-paper.harness.mjs",
|
|
53
|
+
"skills/research-ideate/research-ideate.harness.mjs",
|
|
54
|
+
"skills/study-accepted-papers/study-accepted-papers.harness.mjs",
|
|
55
|
+
"skills/submit-paper/submit-paper.harness.mjs",
|
|
56
|
+
"skills/sweep-design-space/sweep-design-space.harness.mjs",
|
|
57
|
+
"skills/tighten-paper/tighten-paper.harness.mjs",
|
|
58
|
+
"skills/verify-citations/verify-citations.harness.mjs",
|
|
59
|
+
"src/build-engine.harness.mjs",
|
|
60
|
+
"src/build.harness.mjs",
|
|
61
|
+
"src/cli.harness.mjs",
|
|
62
|
+
"src/doctor.harness.mjs",
|
|
63
|
+
"src/engine.harness.mjs",
|
|
64
|
+
"src/hooks-settings.harness.mjs",
|
|
65
|
+
"src/latex-log.harness.mjs",
|
|
66
|
+
"src/latex-loop.harness.mjs",
|
|
67
|
+
"src/link-skills.harness.mjs",
|
|
68
|
+
"src/new-paper.harness.mjs",
|
|
69
|
+
"src/pdf-facts.harness.mjs",
|
|
70
|
+
"src/pdf-geometry.harness.mjs",
|
|
71
|
+
"src/rules-config.harness.mjs",
|
|
72
|
+
"src/structure.harness.mjs",
|
|
73
|
+
"src/tex-requirements.harness.mjs",
|
|
74
|
+
"src/toolchain.harness.mjs"
|
|
75
|
+
]
|
|
76
|
+
}
|