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,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generated-code.harness.mjs β the linter that reads the analysis scripts BEFORE they produce a
|
|
3
|
+
* number, handed scripts that carry each defect it claims to catch.
|
|
4
|
+
* `npx vigiles test .claude/skills/paper-pipeline/scripts/generated-code.harness.mjs`.
|
|
5
|
+
*
|
|
6
|
+
* WHY. `repro/` is full of scripts a model wrote, and until now nothing asked whether any of them
|
|
7
|
+
* would run twice the same way or run at all on another machine. Ported from
|
|
8
|
+
* `check_generated_code.py` in MedSci Skills (arXiv:2606.09500v4, tag v3.8.0): no random seed, a
|
|
9
|
+
* hard-coded absolute path, an in-place overwrite of the script's own input.
|
|
10
|
+
*
|
|
11
|
+
* π΄ The three rules are cheap enough that the interesting failure mode is not missing a defect but
|
|
12
|
+
* FIRING ON CORRECT CODE β a checker that cries wolf is muted within a day, and a muted checker is
|
|
13
|
+
* indistinguishable from one that was never written. So every rule here is asserted in BOTH
|
|
14
|
+
* directions, and the quiet cases are the ones that were hardest to get right: `--seed` in an
|
|
15
|
+
* argparse call, a write-only script, a variable reused across two unrelated loops.
|
|
16
|
+
*
|
|
17
|
+
* The ignore set gets its own assertions too. It is the part of this design that can silently
|
|
18
|
+
* neuter the whole check, so: it prints on a clean run, it prints every reason, an allowance is
|
|
19
|
+
* scoped to one KIND rather than to a file, and a row naming nothing is reported.
|
|
20
|
+
*
|
|
21
|
+
* π΄ Assertions run at MODULE TOP LEVEL β `vigiles test` imports the file and treats "did not
|
|
22
|
+
* throw" as a pass, so an exported test object would report β having run nothing.
|
|
23
|
+
*
|
|
24
|
+
* Every fixture is a throwaway in a temp dir; no real script is read or written.
|
|
25
|
+
*/
|
|
26
|
+
import assert from "node:assert/strict";
|
|
27
|
+
import { execFileSync } from "node:child_process";
|
|
28
|
+
import {
|
|
29
|
+
mkdtempSync,
|
|
30
|
+
mkdirSync,
|
|
31
|
+
writeFileSync,
|
|
32
|
+
rmSync,
|
|
33
|
+
realpathSync,
|
|
34
|
+
} from "node:fs";
|
|
35
|
+
import { tmpdir } from "node:os";
|
|
36
|
+
import { join, dirname } from "node:path";
|
|
37
|
+
import { fileURLToPath } from "node:url";
|
|
38
|
+
import { consumerRoot } from "./consumer.mjs";
|
|
39
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
|
|
41
|
+
const ROOT = consumerRoot();
|
|
42
|
+
const SCRIPT = join(HERE, "generated-code.mjs");
|
|
43
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), "genrated-code-harness-")));
|
|
44
|
+
let n = 0;
|
|
45
|
+
|
|
46
|
+
function run(dir, flagsOnly = true) {
|
|
47
|
+
const argv = flagsOnly ? [SCRIPT, dir, "--flags-only"] : [SCRIPT, dir];
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
code: 0,
|
|
51
|
+
out: execFileSync("node", argv, {
|
|
52
|
+
cwd: ROOT,
|
|
53
|
+
encoding: "utf8",
|
|
54
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
55
|
+
}),
|
|
56
|
+
};
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return { code: e.status ?? 1, out: (e.stdout || "") + (e.stderr || "") };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `scripts` is {relative-path-under-repro: source}; `allow` is the grandfather file's content.
|
|
64
|
+
* `at` is where the repro root sits relative to the paper dir β `"repro"` for every paper but one.
|
|
65
|
+
* `<paper-e>` keeps it at `typed-shell/repro`, and that is not a curiosity: pointing this
|
|
66
|
+
* check at that paper used to print nothing at all. `at: null` builds a paper with NO repro root.
|
|
67
|
+
*/
|
|
68
|
+
function fixture({ scripts = {}, allow = null, bundle = {}, at = "repro" }) {
|
|
69
|
+
const d = join(tmp, `f${++n}`);
|
|
70
|
+
mkdirSync(d, { recursive: true });
|
|
71
|
+
writeFileSync(join(d, "paper.md"), "## 1. x\n\nprose\n");
|
|
72
|
+
if (at === null) return d;
|
|
73
|
+
mkdirSync(join(d, at), { recursive: true });
|
|
74
|
+
for (const [p, src] of Object.entries(scripts)) {
|
|
75
|
+
mkdirSync(join(d, at, dirname(p)), { recursive: true });
|
|
76
|
+
writeFileSync(join(d, at, p), src);
|
|
77
|
+
}
|
|
78
|
+
for (const [p, src] of Object.entries(bundle)) {
|
|
79
|
+
mkdirSync(join(d, at, "artifact-anon", dirname(p)), { recursive: true });
|
|
80
|
+
writeFileSync(join(d, at, "artifact-anon", p), src);
|
|
81
|
+
}
|
|
82
|
+
if (allow !== null)
|
|
83
|
+
writeFileSync(join(d, at, "generated-code-grandfathered.txt"), allow);
|
|
84
|
+
return d;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ββ 1. NO_SEED fires βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
88
|
+
{
|
|
89
|
+
const r = run(
|
|
90
|
+
fixture({
|
|
91
|
+
scripts: {
|
|
92
|
+
"sample.py":
|
|
93
|
+
"import random\nrows = random.sample(all_rows, 30)\nprint(rows)\n",
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
assert.match(
|
|
98
|
+
r.out,
|
|
99
|
+
/NO_SEED\s+repro\/sample\.py β draws randomness \(`random\.sample`\) and never seeds it/,
|
|
100
|
+
"a sampling script with no seed passed: the paper's number came from one draw of a die nobody can\n" +
|
|
101
|
+
"re-roll, and nothing said so:\n" +
|
|
102
|
+
r.out,
|
|
103
|
+
);
|
|
104
|
+
assert.equal(r.code, 1, "it reported NO_SEED and still exited 0");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ββ 2. NO_SEED stays quiet when the script seeds βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
108
|
+
// Two spellings, both live in this repository. The `--seed` one is a REGRESSION TEST: the first
|
|
109
|
+
// draft of the seed pattern opened with `\b`, which never matches before a hyphen preceded by a
|
|
110
|
+
// quote, so `"--seed"` inside argparse was invisible and a correctly-seeded script was accused.
|
|
111
|
+
{
|
|
112
|
+
for (const [name, src] of [
|
|
113
|
+
[
|
|
114
|
+
"explicit",
|
|
115
|
+
"import random\nrandom.seed(7)\nrows = random.sample(all_rows, 30)\n",
|
|
116
|
+
],
|
|
117
|
+
// π΄ THIS FIXTURE WAS VACUOUS IN ITS FIRST FORM and the mutations file caught it. It drew with
|
|
118
|
+
// `rnd.sample(...)` off a local `random.Random(...)`, which the DRAWS pattern does not recognise
|
|
119
|
+
// as a draw at all β so the case passed because NOTHING was detected, not because the seed was.
|
|
120
|
+
// Neutering the `--seed` alternation changed no verdict. It now draws with `np.random.choice`,
|
|
121
|
+
// which DRAWS does recognise, and is seeded ONLY through the command-line flag, so the argparse
|
|
122
|
+
// alternation is the single thing standing between this script and a finding.
|
|
123
|
+
[
|
|
124
|
+
"argparse",
|
|
125
|
+
'import argparse, numpy as np\nap = argparse.ArgumentParser()\nap.add_argument("--seed", type=int, default=7)\nrng = np.random.default_rng(ap.parse_args().seed)\nrows = np.random.choice(all_rows, 30)\n',
|
|
126
|
+
],
|
|
127
|
+
]) {
|
|
128
|
+
const r = run(fixture({ scripts: { "s.py": src } }));
|
|
129
|
+
assert.equal(
|
|
130
|
+
r.code,
|
|
131
|
+
0,
|
|
132
|
+
`a properly seeded script (${name}) was reported as unseeded β this is the false positive\n` +
|
|
133
|
+
"that gets the whole check switched off:\n" +
|
|
134
|
+
r.out,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ββ 3. ABS_PATH fires, and names the path ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
140
|
+
{
|
|
141
|
+
const r = run(
|
|
142
|
+
fixture({
|
|
143
|
+
scripts: {
|
|
144
|
+
"wc.py":
|
|
145
|
+
'text = open("/home/example/papers/paper.md").read()\nprint(len(text.split()))\n',
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
assert.match(
|
|
150
|
+
r.out,
|
|
151
|
+
/ABS_PATH\s+repro\/wc\.py β hard-codes 1 absolute path\(s\), e\.g\. `\/home\/example\/papers\/paper\.md`/,
|
|
152
|
+
"a script that only runs on its author's laptop shipped in the reproduction directory unremarked:\n" +
|
|
153
|
+
r.out,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ββ 4. ABS_PATH stays quiet on the two strings that look like paths and are not βββββββββββββββββ
|
|
158
|
+
// Both guards are lifted from `check-anon.sh` cat. 5, which learned them the hard way: without
|
|
159
|
+
// case-sensitivity an Express route `/users/export` matches `/Users/`; without the left anchor a
|
|
160
|
+
// third party's RELATIVE `src/pages/workspace/AGENTS.md` matches `/workspace/`.
|
|
161
|
+
{
|
|
162
|
+
const r = run(
|
|
163
|
+
fixture({
|
|
164
|
+
scripts: {
|
|
165
|
+
"routes.js":
|
|
166
|
+
'app.get("/users/export", h);\nconst p = "src/pages/workspace/AGENTS.md";\n',
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
assert.equal(
|
|
171
|
+
r.code,
|
|
172
|
+
0,
|
|
173
|
+
"a lowercase route and a relative third-party path were reported as absolute local paths:\n" +
|
|
174
|
+
r.out,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ββ 5. IN_PLACE fires on a literal path read then overwritten ββββββββββββββββββββββββββββββββββ
|
|
179
|
+
{
|
|
180
|
+
const r = run(
|
|
181
|
+
fixture({
|
|
182
|
+
scripts: {
|
|
183
|
+
"clean.py":
|
|
184
|
+
'rows = open("data/census.tsv").read().splitlines()\nout = [r for r in rows if r]\nopen("data/census.tsv", "w").write("\\n".join(out))\n',
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
assert.match(
|
|
189
|
+
r.out,
|
|
190
|
+
/IN_PLACE\s+repro\/clean\.py β reads and then overwrites `data\/census\.tsv`/,
|
|
191
|
+
"a script that clobbers its own input passed: after one run the original is gone and a re-run\n" +
|
|
192
|
+
"computes on the last run's output:\n" +
|
|
193
|
+
r.out,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ββ 6. IN_PLACE fires on argv, the classic form ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
198
|
+
{
|
|
199
|
+
const r = run(
|
|
200
|
+
fixture({
|
|
201
|
+
scripts: {
|
|
202
|
+
"fix.py":
|
|
203
|
+
'import sys\ns = open(sys.argv[1]).read()\nopen(sys.argv[1], "w").write(s.replace("a", "b"))\n',
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
assert.match(
|
|
208
|
+
r.out,
|
|
209
|
+
/IN_PLACE\s+repro\/fix\.py β reads and then overwrites `sys\.argv\[1\]`/,
|
|
210
|
+
"the argv form of an in-place rewrite was not caught:\n" + r.out,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ββ 7. IN_PLACE stays quiet on read-here-write-there, and on a reused variable name βββββββββββββ
|
|
215
|
+
// π΄ The second fixture is the one that decided the rule's shape. Matching the OPERAND EXPRESSION
|
|
216
|
+
// instead of the literal produced seventeen hits on the live repository, essentially all of them a
|
|
217
|
+
// variable called `file` or `p` reused across two unrelated loops. The rule is textual and literal
|
|
218
|
+
// on purpose, and the recall that costs is written into the check's own blind-spot list.
|
|
219
|
+
{
|
|
220
|
+
const r = run(
|
|
221
|
+
fixture({
|
|
222
|
+
scripts: {
|
|
223
|
+
"stats.py":
|
|
224
|
+
'rows = open("data/rows.tsv").read()\nopen("data/summary.tsv", "w").write(rows.upper())\n',
|
|
225
|
+
"loops.mjs":
|
|
226
|
+
'for (const file of inputs) read(readFileSync(file));\nfor (const file of outputs) writeFileSync(file, "x");\n',
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
assert.equal(
|
|
231
|
+
r.code,
|
|
232
|
+
0,
|
|
233
|
+
"a script that reads one file and writes another was reported as clobbering its input,\n" +
|
|
234
|
+
"or a variable name reused across two loops was mistaken for dataflow:\n" +
|
|
235
|
+
r.out,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ββ 8. the bundle is NOT scanned β the boundary with check-anon.sh, asserted ββββββββββββββββββββ
|
|
240
|
+
// π΄ The two checks grep the same four prefixes and would be a duplicate if they read the same
|
|
241
|
+
// tree. They do not: check-anon reads the SHIPPED bundle and asks whether it leaks a machine
|
|
242
|
+
// layout; this reads the WORKING tree and asks whether the script is portable. If this assertion
|
|
243
|
+
// ever fails, one of the two checks should be deleted rather than both kept.
|
|
244
|
+
{
|
|
245
|
+
// Silence asserted through --flags-only rather than through the header's count, so that this case
|
|
246
|
+
// tests the EXCLUSION and not the count. Sharing the count assertion with case 11 made a mutation
|
|
247
|
+
// of the header land here instead, which reads as an off-target kill and hides which property
|
|
248
|
+
// actually failed.
|
|
249
|
+
const r = run(
|
|
250
|
+
fixture({ bundle: { "leak.py": 'open("/home/example/secret.tsv")\n' } }),
|
|
251
|
+
false,
|
|
252
|
+
);
|
|
253
|
+
assert.doesNotMatch(
|
|
254
|
+
r.out,
|
|
255
|
+
/ABS_PATH/,
|
|
256
|
+
"generated-code reported an absolute path inside the RELEASED bundle. That is check-anon.sh cat. 5's\n" +
|
|
257
|
+
"question, and two checks answering it is one check too many:\n" +
|
|
258
|
+
r.out,
|
|
259
|
+
);
|
|
260
|
+
assert.match(
|
|
261
|
+
r.out,
|
|
262
|
+
/the released bundle β `check-anon\.sh` cat\. 5 owns it/,
|
|
263
|
+
"the bundle was skipped and the ledger did not say so, which makes the boundary invisible:\n" +
|
|
264
|
+
r.out,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ββ 9. an allow row is scoped to ONE KIND, not to the file βββββββββββββββββββββββββββββββββββββ
|
|
269
|
+
// π΄ The design point. Waving through a hard-coded path in a script must not also wave through a
|
|
270
|
+
// missing seed in that same script; that is how one justified exemption becomes a file nobody
|
|
271
|
+
// checks. The fixture carries BOTH defects and allows only one.
|
|
272
|
+
{
|
|
273
|
+
const src =
|
|
274
|
+
'import random\nrows = random.sample(open("/home/example/x.tsv").read().split(), 3)\n';
|
|
275
|
+
const r = run(
|
|
276
|
+
fixture({
|
|
277
|
+
scripts: { "both.py": src },
|
|
278
|
+
allow: "both.py\tABS_PATH\tit is this checker's own pattern\n",
|
|
279
|
+
}),
|
|
280
|
+
false,
|
|
281
|
+
);
|
|
282
|
+
assert.doesNotMatch(
|
|
283
|
+
r.out,
|
|
284
|
+
/ABS_PATH\s+repro\/both\.py/,
|
|
285
|
+
"the allowed kind still fired:\n" + r.out,
|
|
286
|
+
);
|
|
287
|
+
assert.match(
|
|
288
|
+
r.out,
|
|
289
|
+
/NO_SEED\s+repro\/both\.py/,
|
|
290
|
+
"allowing ABS_PATH in a file also silenced its unseeded sampling. An allowance scoped to a FILE is\n" +
|
|
291
|
+
"how an ignore list stops being an ignore list and starts being a hole:\n" +
|
|
292
|
+
r.out,
|
|
293
|
+
);
|
|
294
|
+
assert.match(
|
|
295
|
+
r.out,
|
|
296
|
+
/both\.py Β· ABS_PATH\s+β\s+it is this checker's own pattern/,
|
|
297
|
+
"the allowance was applied and its reason was not printed β an invisible ignore set is the failure\n" +
|
|
298
|
+
"this whole design is arranged around:\n" +
|
|
299
|
+
r.out,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ββ 10. a row naming nothing on disk is reported ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
304
|
+
{
|
|
305
|
+
const r = run(
|
|
306
|
+
fixture({
|
|
307
|
+
scripts: { "ok.py": "print(1)\n" },
|
|
308
|
+
allow: "deleted.py\tNO_SEED\tgone\n",
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
assert.match(
|
|
312
|
+
r.out,
|
|
313
|
+
/STALE_ALLOW\s+the allow row `deleted\.py Β· NO_SEED` names no script under repro\//,
|
|
314
|
+
"a row for a script that no longer exists sat unreported, so the list never shrinks:\n" +
|
|
315
|
+
r.out,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ββ 11. the ledger prints on a CLEAN run, and the header states the count βββββββββββββββββββββββ
|
|
320
|
+
// The count matters beyond tidiness: `run-mechanical.mjs` reads this check in `flags` mode, where
|
|
321
|
+
// it prefers a stated count and otherwise COUNTS OUTPUT LINES. The ledger is ~20 lines that are not
|
|
322
|
+
// findings, so without the stated count a clean paper would be recorded as twenty findings.
|
|
323
|
+
{
|
|
324
|
+
const r = run(
|
|
325
|
+
fixture({
|
|
326
|
+
scripts: {
|
|
327
|
+
"ok.py": "import random\nrandom.seed(1)\nprint(random.random())\n",
|
|
328
|
+
},
|
|
329
|
+
}),
|
|
330
|
+
false,
|
|
331
|
+
);
|
|
332
|
+
assert.equal(r.code, 0, "the clean fixture is not clean:\n" + r.out);
|
|
333
|
+
assert.match(
|
|
334
|
+
r.out,
|
|
335
|
+
/β\s*0\s+finding\(s\)/,
|
|
336
|
+
"a clean run does not state `β 0 finding(s)`, so run-mechanical counts the ledger's lines and records\n" +
|
|
337
|
+
"a clean paper as a pile of findings:\n" +
|
|
338
|
+
r.out,
|
|
339
|
+
);
|
|
340
|
+
assert.match(
|
|
341
|
+
r.out,
|
|
342
|
+
/what this check ignored, in full \(1 scripts scanned under repro\/\)/,
|
|
343
|
+
"no ignore ledger on a clean run β silence then reads as coverage:\n" +
|
|
344
|
+
r.out,
|
|
345
|
+
);
|
|
346
|
+
assert.match(
|
|
347
|
+
r.out,
|
|
348
|
+
/\(nothing is being waved through\)/,
|
|
349
|
+
"an empty allow file printed nothing, which is indistinguishable from not reading it:\n" +
|
|
350
|
+
r.out,
|
|
351
|
+
);
|
|
352
|
+
// β¦and --flags-only stays silent, because a hook that speaks when nothing is wrong gets removed.
|
|
353
|
+
const q = run(fixture({ scripts: { "ok.py": "print(1)\n" } }));
|
|
354
|
+
assert.equal(
|
|
355
|
+
q.out.trim(),
|
|
356
|
+
"",
|
|
357
|
+
"--flags-only printed something about a clean tree:\n" + q.out,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ββ 12. KNOWN GAP, asserted so it stays visible ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
362
|
+
// π΄ A path held in a variable defeats IN_PLACE. This is the price paid for precision in case 7 and
|
|
363
|
+
// it is written into the check's own output; asserting the CURRENT behaviour so that when the rule
|
|
364
|
+
// learns dataflow, this flips and must be updated.
|
|
365
|
+
{
|
|
366
|
+
const r = run(
|
|
367
|
+
fixture({
|
|
368
|
+
scripts: {
|
|
369
|
+
"var.py":
|
|
370
|
+
'p = "data/rows.tsv"\ns = open(p).read()\nopen(p, "w").write(s)\n',
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
assert.equal(
|
|
375
|
+
r.code,
|
|
376
|
+
0,
|
|
377
|
+
"IN_PLACE now follows a path through a variable β good; update this assertion and case 7",
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ββ 13. a repro/ ONE LEVEL DOWN is found β the input that was invisible ββββββββββββββββββββββββ
|
|
382
|
+
// π΄ The defect this case exists for, measured 2026-08-26. `<paper-e>` keeps its
|
|
383
|
+
// reproduction code at `typed-shell/repro/`. Run against the paper directory, this check used to
|
|
384
|
+
// print NOTHING and exit 0 β which reads as a clean paper β while eighteen ABS_PATH findings sat
|
|
385
|
+
// one level below, including a reproduction script hard-coding a path into ANOTHER paper's
|
|
386
|
+
// directory. A guard that tests for a filename rather than for the job; the same class the
|
|
387
|
+
// `numbers` CI job documents about itself and the same class as the skipped `artifact/reproduce.py`.
|
|
388
|
+
{
|
|
389
|
+
// Fixture is neutral ON PURPOSE: `/home/example/x.tsv` and the label `typed-shell/repro` stood here β
|
|
390
|
+
// the owner's home directory and our paper's name in test data. The check does not depend on this:
|
|
391
|
+
// it needs ANY absolute path.
|
|
392
|
+
const r = run(
|
|
393
|
+
fixture({
|
|
394
|
+
at: "some-paper/repro",
|
|
395
|
+
scripts: { "wc.py": 'open("/home/example/x.tsv").read()\n' },
|
|
396
|
+
}),
|
|
397
|
+
);
|
|
398
|
+
assert.match(
|
|
399
|
+
r.out,
|
|
400
|
+
/ABS_PATH\s+some-paper\/repro\/wc\.py/,
|
|
401
|
+
"a repro/ one level below the paper directory was not scanned. That is not a missed finding, it is a\n" +
|
|
402
|
+
"SILENT missed directory β the run looks exactly like a clean paper:\n" +
|
|
403
|
+
r.out,
|
|
404
|
+
);
|
|
405
|
+
assert.equal(
|
|
406
|
+
r.code,
|
|
407
|
+
1,
|
|
408
|
+
"the nested tree was scanned, findings were reported, and it still exited 0",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ββ 14. no repro/ ANYWHERE says so, instead of exiting in silence ββββββββββββββββββββββββββββββ
|
|
413
|
+
// The half that matters more. Silence has to mean "scanned and clean"; if it can also mean "did not
|
|
414
|
+
// look", then every clean report in this pipeline is worth nothing. `--flags-only` still stays
|
|
415
|
+
// quiet, because a hook that speaks when it has nothing to say is removed within a day.
|
|
416
|
+
{
|
|
417
|
+
const r = run(fixture({ at: null }), false);
|
|
418
|
+
assert.equal(r.code, 0, "a paper with no repro/ is not an error:\n" + r.out);
|
|
419
|
+
assert.match(
|
|
420
|
+
r.out,
|
|
421
|
+
/NOTHING WAS SCANNED: no repro\/ here or one level below/,
|
|
422
|
+
"a paper with no repro/ produced no output at all, so 'nothing to check' and 'checked, all clean' are\n" +
|
|
423
|
+
"the same observation:\n" +
|
|
424
|
+
r.out,
|
|
425
|
+
);
|
|
426
|
+
const q = run(fixture({ at: null }));
|
|
427
|
+
assert.equal(
|
|
428
|
+
q.out.trim(),
|
|
429
|
+
"",
|
|
430
|
+
"--flags-only spoke about a paper it had nothing to say about:\n" + q.out,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ββ 15. a SECOND repro/ root is named in the ledger rather than silently dropped ββββββββββββββββ
|
|
435
|
+
// The allow file is scoped to one root, so this check reads one. That is a defensible scope and an
|
|
436
|
+
// indefensible silence: an unread tree has to be a name you can count.
|
|
437
|
+
{
|
|
438
|
+
const d = fixture({ scripts: { "ok.py": "print(1)\n" } });
|
|
439
|
+
mkdirSync(join(d, "sub", "repro"), { recursive: true });
|
|
440
|
+
writeFileSync(
|
|
441
|
+
join(d, "sub", "repro", "leak.py"),
|
|
442
|
+
'open("/home/example/x.tsv")\n',
|
|
443
|
+
);
|
|
444
|
+
const r = run(d, false);
|
|
445
|
+
assert.match(
|
|
446
|
+
r.out,
|
|
447
|
+
/1 tree\(s\) not entered β a SECOND repro\/ root/,
|
|
448
|
+
"a second repro/ root under the paper directory was dropped without being named, which is the ignore\n" +
|
|
449
|
+
"set becoming invisible again:\n" +
|
|
450
|
+
r.out,
|
|
451
|
+
);
|
|
452
|
+
assert.match(
|
|
453
|
+
r.out,
|
|
454
|
+
/sub\/repro/,
|
|
455
|
+
"the second root was counted but not named:\n" + r.out,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
460
|
+
console.log(
|
|
461
|
+
"β generated-code: unseeded randomness, a hard-coded absolute path and an in-place overwrite (literal and argv) " +
|
|
462
|
+
"each fire; a seeded script, a lowercase route, a relative third-party path, a read-here-write-there script and a reused " +
|
|
463
|
+
"variable name all stay quiet; the released bundle is left to check-anon; allowances are per-kind, printed, and reported " +
|
|
464
|
+
"when stale; the ledger prints on a clean run and the header states its count; a repro/ one level down is found, " +
|
|
465
|
+
"a paper with none says so instead of exiting silent, and a second root is named; 1 known gap recorded",
|
|
466
|
+
);
|