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,286 @@
|
|
|
1
|
+
// ledger.selftest.mjs — plant each failure this thing exists to catch, and watch it catch them.
|
|
2
|
+
//
|
|
3
|
+
// The project's rule, written after five tools reported success while doing nothing:
|
|
4
|
+
// "a checker that has never failed does not count as working". So this file does not check that
|
|
5
|
+
// the ledger runs. It checks that it says NO when it should, and YES when it should — both
|
|
6
|
+
// directions, for every property the design claims.
|
|
7
|
+
//
|
|
8
|
+
// node .claude/skills/paper-pipeline/scripts/ledger.selftest.mjs
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
appendFileSync,
|
|
16
|
+
existsSync,
|
|
17
|
+
realpathSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { consumerSkillsDir } from "./consumer.mjs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), "ledger-selftest-")));
|
|
24
|
+
const paper = join(tmp, "a-paper");
|
|
25
|
+
|
|
26
|
+
// 🔴 Point the ledger at a throwaway file BEFORE importing it. The first version wrote to the real
|
|
27
|
+
// ledger and restored it in a `finally`, which leaks fixture rows into permanent history the moment
|
|
28
|
+
// the run dies between the two — and it did, on the day this shipped. Isolation, not cleanup.
|
|
29
|
+
process.env.PIPELINE_LEDGER = join(tmp, "runs.jsonl");
|
|
30
|
+
|
|
31
|
+
let failures = 0;
|
|
32
|
+
const check = (name, actual, expected) => {
|
|
33
|
+
const ok = actual === expected;
|
|
34
|
+
if (!ok) failures++;
|
|
35
|
+
console.log(
|
|
36
|
+
` ${ok ? "ok " : "FAIL"} ${name}${ok ? "" : ` — expected ${expected}, got ${actual}`}`,
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// `skillHash` was dropped from the destructuring on 2026-08-28 — no check below was reading it.
|
|
42
|
+
const { record, status } = await import("./ledger.mjs");
|
|
43
|
+
const mk = (t) => {
|
|
44
|
+
writeFileSync(join(paper, "paper.md"), t);
|
|
45
|
+
};
|
|
46
|
+
const { mkdirSync } = await import("node:fs");
|
|
47
|
+
mkdirSync(paper, { recursive: true });
|
|
48
|
+
|
|
49
|
+
const gate = "cold-read-diff"; // a real skill, so skillHash resolves
|
|
50
|
+
const st = () => status(paper, { gates: [gate] })[0];
|
|
51
|
+
|
|
52
|
+
// ── 1. NEVER-RUN is a state, not an absence ──────────────────────────────────────────────
|
|
53
|
+
mk("version one");
|
|
54
|
+
check("a gate nobody has run reads NEVER-RUN", st().state, "NEVER-RUN");
|
|
55
|
+
|
|
56
|
+
// ── 2. a fresh run is fresh ──────────────────────────────────────────────────────────────
|
|
57
|
+
// The clean run is an ABSTENTION, not a pass: there is no constructor asserting that nothing
|
|
58
|
+
// was wrong (see ledger.mjs). `no-witness` says the check ran and produced no finding.
|
|
59
|
+
const ran = () =>
|
|
60
|
+
record({ skill: gate, paper, kind: "ABSTAINED", reason: "no-witness" });
|
|
61
|
+
ran();
|
|
62
|
+
check("a run against the current bytes reads FRESH", st().state, "FRESH");
|
|
63
|
+
|
|
64
|
+
// ── 3. 🔴 THE PLANTED ERROR THIS FILE EXISTS FOR: the paper changes underneath a green gate.
|
|
65
|
+
// This is the exact defect that shipped — a ☑ describing a document that no longer
|
|
66
|
+
// exists. If this assertion ever goes green-on-stale, the ledger is decorative.
|
|
67
|
+
mk("version two, rewritten after the gate ran");
|
|
68
|
+
check(
|
|
69
|
+
"after the paper changes, the same verdict reads STALE-PAPER",
|
|
70
|
+
st().state,
|
|
71
|
+
"STALE-PAPER",
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// ── 4. and the property `make` does not have: the CHECKER changed ────────────────────────
|
|
75
|
+
mk("version one"); // put the paper back so only the skill differs
|
|
76
|
+
ran();
|
|
77
|
+
check("back on the original bytes it is FRESH again", st().state, "FRESH");
|
|
78
|
+
|
|
79
|
+
// 🔴 `../skills/…` until 2026-08-15, when this file moved from `.claude/pipeline/` into the
|
|
80
|
+
// skill it serves. A SIXTH form of hidden reference, after literal paths, `join()` segments,
|
|
81
|
+
// relative imports, path-shaped regexes and `..`-depth constants: `new URL(rel, import.meta.url)`
|
|
82
|
+
// is not an import, so neither a path search nor an import resolver sees it. It broke in CI —
|
|
83
|
+
// ENOENT on `.claude/skills/paper-pipeline/skills/cold-read-diff/SKILL.md`, a path assembled from
|
|
84
|
+
// the new location and the old relative segment.
|
|
85
|
+
//
|
|
86
|
+
// 🔴 AND A SEVENTH FORM, 2026-09-12: the segment was right and the ANCHOR moved. Once the
|
|
87
|
+
// mechanism ships as a package, `import.meta.url` is inside `node_modules`, so the same two `..`
|
|
88
|
+
// now name a skill directory of the PACKAGE — which holds no consumer skill. The subject here
|
|
89
|
+
// was never "a file near me"; it is "the SKILL.md whose bytes `skillHash(gate)` folds into the
|
|
90
|
+
// key", and that file belongs to the repository under test. So it is resolved the way
|
|
91
|
+
// `ledger.mjs` resolves `SKILLS`, from the consumer root, and the two cannot drift apart.
|
|
92
|
+
const skillFile = join(consumerSkillsDir(), gate, "SKILL.md");
|
|
93
|
+
// Loud, not skipped. This assertion is the only one covering property 2 of the design ("the
|
|
94
|
+
// checker's own source is part of the key"); skipping it when the file is absent would leave the
|
|
95
|
+
// selftest printing success while proving one property fewer — the green-over-emptiness this
|
|
96
|
+
// whole file exists to forbid.
|
|
97
|
+
if (!existsSync(skillFile))
|
|
98
|
+
throw new Error(
|
|
99
|
+
`ledger.selftest: ${skillFile} does not exist.\n` +
|
|
100
|
+
`This selftest runs against a CONSUMER of the pipeline — a repository that has the ` +
|
|
101
|
+
`pipeline's skills installed under .claude/skills/ — not against this package on its own.`,
|
|
102
|
+
);
|
|
103
|
+
const skillBefore = readFileSync(skillFile);
|
|
104
|
+
try {
|
|
105
|
+
appendFileSync(
|
|
106
|
+
skillFile,
|
|
107
|
+
"\n<!-- selftest: transient edit, removed below -->\n",
|
|
108
|
+
);
|
|
109
|
+
check(
|
|
110
|
+
"editing the SKILL itself makes its old verdict STALE-SKILL",
|
|
111
|
+
st().state,
|
|
112
|
+
"STALE-SKILL",
|
|
113
|
+
);
|
|
114
|
+
} finally {
|
|
115
|
+
writeFileSync(skillFile, skillBefore); // never leave a real skill modified
|
|
116
|
+
}
|
|
117
|
+
check("restoring the skill restores FRESH", st().state, "FRESH");
|
|
118
|
+
|
|
119
|
+
// ── 5. the mute-check signal, both ways ──────────────────────────────────────────────────
|
|
120
|
+
check(
|
|
121
|
+
"a check that has only ever abstained is flagged as never having found anything",
|
|
122
|
+
st().everFound,
|
|
123
|
+
false,
|
|
124
|
+
);
|
|
125
|
+
writeFileSync(join(paper, "report.md"), "---\nfindings: 3\n---\n");
|
|
126
|
+
record({
|
|
127
|
+
skill: gate,
|
|
128
|
+
paper,
|
|
129
|
+
kind: "FINDING",
|
|
130
|
+
findings: 3,
|
|
131
|
+
report: "report.md",
|
|
132
|
+
});
|
|
133
|
+
check("one finding clears the flag", st().everFound, true);
|
|
134
|
+
|
|
135
|
+
// ── 6. a retired verdict is refused rather than silently stored ──────────────────────────
|
|
136
|
+
// 🔴 THE POINT OF THE WHOLE REFACTOR, asserted at its narrowest: `PASS` cannot be written.
|
|
137
|
+
// Not "is discouraged" — the constructor does not exist, and reaching for it is an error that
|
|
138
|
+
// names what to do instead.
|
|
139
|
+
let msg = "";
|
|
140
|
+
try {
|
|
141
|
+
record({ skill: gate, paper, kind: "PASS" });
|
|
142
|
+
} catch (e) {
|
|
143
|
+
msg = e.message;
|
|
144
|
+
}
|
|
145
|
+
check("recording PASS throws", msg.includes("not a verdict any more"), true);
|
|
146
|
+
check(
|
|
147
|
+
"...and the error says what to record instead",
|
|
148
|
+
msg.includes("no-witness"),
|
|
149
|
+
true,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
let threw = false;
|
|
153
|
+
try {
|
|
154
|
+
record({ skill: gate, paper, kind: "probably fine" });
|
|
155
|
+
} catch {
|
|
156
|
+
threw = true;
|
|
157
|
+
}
|
|
158
|
+
check("an unrecognised kind throws instead of being written", threw, true);
|
|
159
|
+
|
|
160
|
+
threw = false;
|
|
161
|
+
try {
|
|
162
|
+
record({ skill: gate, paper, kind: "ABSTAINED", reason: "it seemed fine" });
|
|
163
|
+
} catch {
|
|
164
|
+
threw = true;
|
|
165
|
+
}
|
|
166
|
+
check("an abstention with a free-text reason throws", threw, true);
|
|
167
|
+
|
|
168
|
+
// ── 7. ONE CHECK, ONE ROW: two checks under one skill do not overwrite each other ────────
|
|
169
|
+
// The defect: `status()` takes the last row per key, and the key used to be the skill. A clean
|
|
170
|
+
// run of the second check erased a finding of the first from every derived view.
|
|
171
|
+
writeFileSync(join(paper, "prov.md"), "---\nfindings: 5\n---\n");
|
|
172
|
+
record({
|
|
173
|
+
skill: "build-benchmark",
|
|
174
|
+
check: "check-provenance",
|
|
175
|
+
paper,
|
|
176
|
+
kind: "FINDING",
|
|
177
|
+
findings: 5,
|
|
178
|
+
report: "prov.md",
|
|
179
|
+
});
|
|
180
|
+
record({
|
|
181
|
+
skill: "build-benchmark",
|
|
182
|
+
check: "arm-permutation",
|
|
183
|
+
paper,
|
|
184
|
+
kind: "ABSTAINED",
|
|
185
|
+
reason: "no-witness",
|
|
186
|
+
});
|
|
187
|
+
const both = status(paper, {
|
|
188
|
+
gates: [
|
|
189
|
+
"build-benchmark/check-provenance",
|
|
190
|
+
"build-benchmark/arm-permutation",
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
check(
|
|
194
|
+
"the finding survives a clean run of a SIBLING check",
|
|
195
|
+
both[0].findings,
|
|
196
|
+
5,
|
|
197
|
+
);
|
|
198
|
+
check("and the sibling keeps its own row", both[1].findings, 0);
|
|
199
|
+
check("the sibling did not inherit the finding", both[1].everFound, false);
|
|
200
|
+
|
|
201
|
+
// ── 8. ORDER IN THE FILE IS NOT ORDER IN TIME ────────────────────────────────────────────
|
|
202
|
+
// The defect (external review, P1, 2026-09-15): `status()` took `runs[runs.length - 1]`, so the
|
|
203
|
+
// verdict was decided by POSITION. The consumer declares `merge=union` for this ledger, and
|
|
204
|
+
// union concatenates "ours, then theirs" without ordering anything — so a merge that lands an
|
|
205
|
+
// older row after a newer one hid the finding and reported a clean state.
|
|
206
|
+
//
|
|
207
|
+
// The fixture reproduces exactly that shape: both rows are REAL (written by `record`), only
|
|
208
|
+
// their order in the file is swapped, which is all a union merge does. The older row's stamp is
|
|
209
|
+
// set explicitly rather than slept for, so the test is deterministic and costs no wall clock.
|
|
210
|
+
const LEDGER_FILE = process.env.PIPELINE_LEDGER;
|
|
211
|
+
writeFileSync(join(paper, "merge.md"), "---\nfindings: 3\n---\n");
|
|
212
|
+
record({
|
|
213
|
+
skill: gate,
|
|
214
|
+
check: "merge-order",
|
|
215
|
+
paper,
|
|
216
|
+
kind: "ABSTAINED",
|
|
217
|
+
reason: "no-witness",
|
|
218
|
+
});
|
|
219
|
+
record({
|
|
220
|
+
skill: gate,
|
|
221
|
+
check: "merge-order",
|
|
222
|
+
paper,
|
|
223
|
+
kind: "FINDING",
|
|
224
|
+
findings: 3,
|
|
225
|
+
report: "merge.md",
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
{
|
|
229
|
+
const all = readFileSync(LEDGER_FILE, "utf8").split("\n").filter(Boolean);
|
|
230
|
+
const [abstained, finding] = all.slice(-2).map((l) => JSON.parse(l));
|
|
231
|
+
abstained.ts = "2026-01-01T00:00:00.000Z"; // deliberately older, with no dependence on a timer
|
|
232
|
+
writeFileSync(
|
|
233
|
+
LEDGER_FILE,
|
|
234
|
+
[
|
|
235
|
+
...all.slice(0, -2),
|
|
236
|
+
JSON.stringify(finding),
|
|
237
|
+
JSON.stringify(abstained),
|
|
238
|
+
].join("\n") + "\n",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const merged = status(paper, { gates: [`${gate}/merge-order`] })[0];
|
|
243
|
+
check(
|
|
244
|
+
"an older row sitting AFTER a newer one does not hide the finding",
|
|
245
|
+
merged.findings,
|
|
246
|
+
3,
|
|
247
|
+
);
|
|
248
|
+
check(
|
|
249
|
+
"...and the verdict does not come from the stale abstention",
|
|
250
|
+
merged.abstained,
|
|
251
|
+
null,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// The other half: EQUAL stamps must keep the old behaviour — file order decides. Two runs inside
|
|
255
|
+
// one second carry no other information, and for a single writer file order is the true order.
|
|
256
|
+
{
|
|
257
|
+
const all = readFileSync(LEDGER_FILE, "utf8").split("\n").filter(Boolean);
|
|
258
|
+
const [finding, abstained] = all.slice(-2).map((l) => JSON.parse(l));
|
|
259
|
+
const sameTs = finding.ts;
|
|
260
|
+
writeFileSync(
|
|
261
|
+
LEDGER_FILE,
|
|
262
|
+
[
|
|
263
|
+
...all.slice(0, -2),
|
|
264
|
+
JSON.stringify({ ...finding, ts: sameTs }),
|
|
265
|
+
JSON.stringify({ ...abstained, ts: sameTs }),
|
|
266
|
+
].join("\n") + "\n",
|
|
267
|
+
);
|
|
268
|
+
const tie = status(paper, { gates: [`${gate}/merge-order`] })[0];
|
|
269
|
+
check(
|
|
270
|
+
"with equal stamps the later row in the file still wins",
|
|
271
|
+
tie.findings,
|
|
272
|
+
0,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
} finally {
|
|
276
|
+
// The ledger lives inside `tmp`, so removing the fixture removes it too. Nothing to restore,
|
|
277
|
+
// which is the point — there is no window in which real history is at risk.
|
|
278
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log(
|
|
282
|
+
failures === 0
|
|
283
|
+
? "\n all planted errors were caught\n"
|
|
284
|
+
: `\n 🔴 ${failures} assertion(s) failed — the ledger is not doing what it claims\n`,
|
|
285
|
+
);
|
|
286
|
+
process.exit(failures === 0 ? 0 : 1);
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pipeline-check.harness.mjs — one planted defect at a time, on an otherwise-clean scorecard.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 Since 2026-08-26 HERE ARE SIX CHECKS, NOT TWENTY-TWO. Sixteen, whose input is the document itself,
|
|
5
|
+
* moved to `eslint-rules/pipeline-status.mjs`, and their cases — to `eslint-rules/pipeline-status.harness.mjs`.
|
|
6
|
+
* `npx vigiles test .claude/skills/paper-pipeline/scripts/pipeline-check.harness.mjs`.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS ALONGSIDE THE FIXTURE TEST IN hooks.harness.mjs. That one runs the checker over
|
|
9
|
+
* `scripts/fixtures/dirty/`, a scorecard carrying every defect at once, and asserts each kind
|
|
10
|
+
* appears somewhere in the output. It cannot answer the question that matters when a check breaks:
|
|
11
|
+
* WHICH defect produced WHICH finding. A checker that reports `stale-continuous` for the wrong
|
|
12
|
+
* reason — say, because the date parser returns null and every row looks undated — passes that test
|
|
13
|
+
* forever. So each case below plants exactly ONE defect on a scorecard that is otherwise silent, and
|
|
14
|
+
* asserts the FULL SET of kinds, not merely that the expected one is present. Collateral findings
|
|
15
|
+
* are written into the expectation on purpose: they are part of the contract, and an unlisted one is
|
|
16
|
+
* a change in behaviour nobody asked for.
|
|
17
|
+
*
|
|
18
|
+
* 🔴 Assertions run at MODULE TOP LEVEL. `vigiles test` imports the file and treats "did not throw"
|
|
19
|
+
* as a pass — an earlier harness here exported a `tests` object, nothing ran, and the runner printed
|
|
20
|
+
* ✓ on a file whose only assertion was `assert.equal(1, 2)`.
|
|
21
|
+
*
|
|
22
|
+
* 🔴 The subprocess environment is SCRUBBED of OSF_TOKEN and GITHUB_TOKEN. The `credential-available`
|
|
23
|
+
* check fires when the session holds a credential that could clear a parked blocker, so with either
|
|
24
|
+
* token exported this suite would pass on a laptop and fail in CI, or the reverse. A test whose
|
|
25
|
+
* verdict depends on who is running it is not a test.
|
|
26
|
+
*
|
|
27
|
+
* Fixtures are throwaways in a temp dir; no real scorecard is read or written.
|
|
28
|
+
*/
|
|
29
|
+
import assert from "node:assert/strict";
|
|
30
|
+
import { parseStatus } from "./pipeline-check.mjs";
|
|
31
|
+
import { execFileSync } from "node:child_process";
|
|
32
|
+
import {
|
|
33
|
+
mkdtempSync,
|
|
34
|
+
mkdirSync,
|
|
35
|
+
writeFileSync,
|
|
36
|
+
rmSync,
|
|
37
|
+
realpathSync,
|
|
38
|
+
} from "node:fs";
|
|
39
|
+
import { tmpdir } from "node:os";
|
|
40
|
+
import { join, dirname } from "node:path";
|
|
41
|
+
import { fileURLToPath } from "node:url";
|
|
42
|
+
import { consumerRoot } from "./consumer.mjs";
|
|
43
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
|
|
45
|
+
const ROOT = consumerRoot();
|
|
46
|
+
const SCRIPT = join(HERE, "pipeline-check.mjs");
|
|
47
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), "pipecheck-harness-")));
|
|
48
|
+
|
|
49
|
+
// The paper source is written NOW, so `newestSourceDate` returns today and the staleness comparison
|
|
50
|
+
// has something real to compare against. Every clean row therefore carries today's date: hard-coding
|
|
51
|
+
// a date would make this suite start failing on its own the following morning.
|
|
52
|
+
const TODAY = new Date().toISOString().slice(0, 10);
|
|
53
|
+
const FAR = "2027-12-31";
|
|
54
|
+
|
|
55
|
+
const ENV = { ...process.env };
|
|
56
|
+
delete ENV.OSF_TOKEN;
|
|
57
|
+
delete ENV.GITHUB_TOKEN;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A scorecard with the four sections the template requires, all green. `over` replaces individual
|
|
61
|
+
* cells: `{ cites: { date: "2020-01-01" } }`, `{ verdict: "…" }`, `{ deadline: "2026-08-18" }`,
|
|
62
|
+
* `{ tail: "…" }` for anything appended after the last section, `{ preamble: "…" }` for anything
|
|
63
|
+
* inserted BEFORE the first heading (the only way to plant a row under no heading at all).
|
|
64
|
+
*/
|
|
65
|
+
function scorecard(over = {}) {
|
|
66
|
+
const r = (id, status = "☑", date = TODAY) => ({
|
|
67
|
+
id,
|
|
68
|
+
status,
|
|
69
|
+
date,
|
|
70
|
+
...(over[id] ?? {}),
|
|
71
|
+
});
|
|
72
|
+
const row = (o, ...mid) =>
|
|
73
|
+
`| ${o.id} | work | ${o.skill ?? "skill"} | ${mid.length ? mid.join(" | ") + " | " : ""}${o.status} | ${o.date} | ${o.result ?? "result"} | — |`;
|
|
74
|
+
// The GATES `Requires` cell, canonical by default so the baseline is silent — `{ structure: {
|
|
75
|
+
// requires: "render" } }` plants a dropped edge. `harden` deliberately declares four of its five
|
|
76
|
+
// canonical inputs: the fifth (`priordelta`) has no row in this fixture, and the clean case asserts that the checker stays
|
|
77
|
+
// quiet about an edge there is nowhere to point at.
|
|
78
|
+
const req = (id, dflt) => over[id]?.requires ?? dflt;
|
|
79
|
+
const verdict =
|
|
80
|
+
over.verdict ??
|
|
81
|
+
"Submit-ready — every gate green, artifact reproduces clean, nothing blocking.";
|
|
82
|
+
return [
|
|
83
|
+
"# PIPELINE-STATUS — harness fixture",
|
|
84
|
+
`Venue: Fixture Workshop · Deadline: ${over.deadline ?? FAR} · Blind: double · State: drafting`,
|
|
85
|
+
"",
|
|
86
|
+
`**Readiness verdict:** ${verdict}`,
|
|
87
|
+
"",
|
|
88
|
+
over.preamble ?? "",
|
|
89
|
+
"",
|
|
90
|
+
"### SETUP",
|
|
91
|
+
"| id | Work | Skill | Status | Date | Result | Open |",
|
|
92
|
+
"|----|------|-------|--------|------|--------|------|",
|
|
93
|
+
row(r("idea")),
|
|
94
|
+
row(r("access")),
|
|
95
|
+
row(r("frame")),
|
|
96
|
+
"",
|
|
97
|
+
"### LOOP",
|
|
98
|
+
"| id | Work | Skill | Status | Date | Result | Open |",
|
|
99
|
+
"|----|------|-------|--------|------|--------|------|",
|
|
100
|
+
row(r("study")),
|
|
101
|
+
row(r("draft")),
|
|
102
|
+
row(r("arc")),
|
|
103
|
+
"",
|
|
104
|
+
"### CONTINUOUS",
|
|
105
|
+
"| id | Trigger | Skill | Status | Date | Result | Open |",
|
|
106
|
+
"|----|---------|-------|--------|------|--------|------|",
|
|
107
|
+
row(r("cites")),
|
|
108
|
+
row(r("render")),
|
|
109
|
+
"",
|
|
110
|
+
"### GATES",
|
|
111
|
+
"| id | Gate | Skill | Requires | Status | Date | Result | Open |",
|
|
112
|
+
"|----|------|-------|----------|--------|------|--------|------|",
|
|
113
|
+
row(r("structure"), req("structure", "render, arc")),
|
|
114
|
+
row(r("writing"), req("writing", "draft, arc, structure")),
|
|
115
|
+
row(r("panel"), req("panel", "structure, writing")),
|
|
116
|
+
row(r("harden"), req("harden", "panel, structure, writing, cites")),
|
|
117
|
+
"",
|
|
118
|
+
over.tail ?? "",
|
|
119
|
+
].join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Write a fixture and return the findings pipeline-check reports, verbatim. */
|
|
123
|
+
function findings(name, over = {}, today = TODAY) {
|
|
124
|
+
const d = join(tmp, name);
|
|
125
|
+
mkdirSync(d, { recursive: true });
|
|
126
|
+
writeFileSync(join(d, "PIPELINE-STATUS.md"), scorecard(over));
|
|
127
|
+
writeFileSync(join(d, "paper.md"), "## 1. Heading\n\nProse.\n");
|
|
128
|
+
const out = execFileSync("node", [SCRIPT, d, "--json", `--today=${today}`], {
|
|
129
|
+
cwd: ROOT,
|
|
130
|
+
encoding: "utf8",
|
|
131
|
+
env: ENV,
|
|
132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
+
});
|
|
134
|
+
return JSON.parse(out);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** …and just the kinds, sorted. */
|
|
138
|
+
const kinds = (name, over = {}, today = TODAY) =>
|
|
139
|
+
findings(name, over, today)
|
|
140
|
+
.map((f) => f.kind)
|
|
141
|
+
.sort();
|
|
142
|
+
|
|
143
|
+
/** A SETUP-shaped (7-column) table head. */
|
|
144
|
+
const PLAIN_HEAD = [
|
|
145
|
+
"| id | Work | Skill | Status | Date | Result | Open |",
|
|
146
|
+
"|----|------|-------|--------|------|--------|------|",
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
/** Assert the EXACT set of kinds, so a check firing for the wrong reason cannot hide. */
|
|
150
|
+
const expect = (name, over, want, today = TODAY) =>
|
|
151
|
+
assert.deepEqual(
|
|
152
|
+
kinds(name, over, today),
|
|
153
|
+
[...want].sort(),
|
|
154
|
+
`${name}: wrong finding set`,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// ═══════════════════════════════════════════════════════════════════════════════════════
|
|
158
|
+
// 🔴 2026-08-26 — SIXTEEN CASES MOVED OUT ALONG WITH THEIR CHECKS.
|
|
159
|
+
//
|
|
160
|
+
// Checks whose input is ONE markdown document moved to `eslint-rules/pipeline-status.mjs`,
|
|
161
|
+
// and their cases — to the colocated `eslint-rules/pipeline-status.harness.mjs`. They were NOT
|
|
162
|
+
// left behind even "just in case": a test running a check that is not in the file is always green
|
|
163
|
+
// and therefore lies worse than its absence.
|
|
164
|
+
//
|
|
165
|
+
// undated-continuous · gate-missing-input · gate-stale-input · unknown-input ·
|
|
166
|
+
// unattributed-verdict · single-family-jury (+ mixed-family / non-judging / widened) ·
|
|
167
|
+
// scalar-verdict (+ owned) · study-before-frame · undeclared-input (+ superset) ·
|
|
168
|
+
// unrun-gate · no-verdict · weak-accept-unowned · ceiling-unplanned ·
|
|
169
|
+
// unattributed-row · unreadable-row · duplicate-id
|
|
170
|
+
//
|
|
171
|
+
// Six checks remain, and all six read input FROM OUTSIDE the file: git, clock, `reviews/`,
|
|
172
|
+
// `process.env`. Exactly those are checked below.
|
|
173
|
+
//
|
|
174
|
+
// ⚠️ TWO CASES ABOUT PARSING WERE REWRITTEN, NOT DELETED, and this is more important than keeping them.
|
|
175
|
+
// "Heading with trailing text" and "line `| Id |`" — parser regressions, found live
|
|
176
|
+
// on 2026-08-09, and before they were observed through `unrun-gate`, which no longer exists here. If they
|
|
177
|
+
// had simply been left expecting `[]`, they would have turned green BY CONSTRUCTION: empty set
|
|
178
|
+
// of findings appears both when the parser is right and when it silently ate the table. So both
|
|
179
|
+
// were rewritten to be observed through `stale-continuous` — the check that stayed here:
|
|
180
|
+
// parser did not see section ⇒ no line ⇒ no finding ⇒ assertion is red.
|
|
181
|
+
// ═══════════════════════════════════════════════════════════════════════════════════════
|
|
182
|
+
|
|
183
|
+
// ── 0. the baseline says nothing ───────────────────────────────────────────────────────
|
|
184
|
+
// Asserted first and asserted hard. Every case below is "the baseline plus one thing", so a noisy
|
|
185
|
+
// baseline would make all of them meaningless — and a checker that fires on a clean scorecard is
|
|
186
|
+
// muted within a day, which is worse than one that misses.
|
|
187
|
+
expect("clean", {}, []);
|
|
188
|
+
|
|
189
|
+
// ── 1. stale-continuous — a ☑ about text that has since been rewritten ─────────────────
|
|
190
|
+
// verify-citations was numbered like a one-shot step, so a citation added after the last run counted
|
|
191
|
+
// as verified. The project rule "we checked last cycle does not count" was already written down.
|
|
192
|
+
// INPUT FROM OUTSIDE THE FILE: the date of the last commit that touched the paper's text — that is why the check stayed.
|
|
193
|
+
expect("stale-continuous", { cites: { date: "2020-01-01" } }, [
|
|
194
|
+
"stale-continuous",
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
// ── 2. submit-access — no portal account inside somebody else's moderation window ──────
|
|
198
|
+
// Found at T−4 days on a finished paper, as the sole item on the critical path: OpenReview profiles
|
|
199
|
+
// are moderated for up to two weeks and there is no expedite route without an institutional email.
|
|
200
|
+
// INPUT FROM OUTSIDE THE FILE: the clock.
|
|
201
|
+
expect(
|
|
202
|
+
"submit-access",
|
|
203
|
+
{ access: { status: "☐" }, deadline: "2026-09-01" },
|
|
204
|
+
["submit-access"],
|
|
205
|
+
"2026-08-20",
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// ── 3. …and a far deadline with the same blank row is NOT urgent ───────────────────────
|
|
209
|
+
expect("submit-access-not-yet", { access: { status: "☐" } }, []);
|
|
210
|
+
|
|
211
|
+
// ── 4. a history section is evidence, not status ──────────────────────────────────────
|
|
212
|
+
// A superseded 2026-08-02 row saying the artifact was unhosted re-triggered the credential check on
|
|
213
|
+
// 2026-08-05, hours after the artifact went up. This project appends rather than rewrites, so every
|
|
214
|
+
// scorecard accumulates old rows, and parsing them makes every past state live again.
|
|
215
|
+
// Observed through `stale-continuous`: a row in history is dated 2020, and if the history
|
|
216
|
+
// slice did not work, it would have given it.
|
|
217
|
+
expect(
|
|
218
|
+
"history-is-not-status",
|
|
219
|
+
{
|
|
220
|
+
tail: [
|
|
221
|
+
"## History",
|
|
222
|
+
"",
|
|
223
|
+
"### CONTINUOUS",
|
|
224
|
+
...PLAIN_HEAD,
|
|
225
|
+
"| cites | any cite | verify-citations | ☑ | 2020-01-01 | old | — |",
|
|
226
|
+
].join("\n"),
|
|
227
|
+
},
|
|
228
|
+
[],
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// ── 5. a heading may carry trailing text — the RELAXATION, still observable ────────────
|
|
232
|
+
// The old pattern was `/^###\s+([A-Z]+)\s*$/`: an all-caps Latin word and NOTHING after it. Papers
|
|
233
|
+
// annotate their headings (`### GATES (re-run 06.08)`), so the pattern failed on the real document
|
|
234
|
+
// and the section simply stayed unset — SILENTLY. Planted here as a CONTINUOUS row with an old date:
|
|
235
|
+
// the finding exists only if the annotated heading was recognised as a section.
|
|
236
|
+
expect(
|
|
237
|
+
"heading-with-trailing-text",
|
|
238
|
+
{
|
|
239
|
+
tail: [
|
|
240
|
+
"### CONTINUOUS (re-run 2026-08-09, after the cut)",
|
|
241
|
+
...PLAIN_HEAD,
|
|
242
|
+
"| refs | any ref moved | verify-refs | ☑ | 2020-01-01 | ok | — |",
|
|
243
|
+
].join("\n"),
|
|
244
|
+
},
|
|
245
|
+
["stale-continuous"],
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// ── 6. the row `| Id |` is a ROW, and the header is whatever `thead` says it is ────────
|
|
249
|
+
// The old parser guessed the header with `/^\|\s*id\s*\|/i`, so the DATA row `| Id | …` — the
|
|
250
|
+
// research-ideate row, before the words-not-codes rename — matched the guess and was eaten on every
|
|
251
|
+
// scorecard in the repo, for as long as the template had existed. A row that parses as nothing, on
|
|
252
|
+
// every paper, forever. Planted as a stale CONTINUOUS row so its absence is visible: if the row were
|
|
253
|
+
// still being swallowed, the finding set would be EMPTY.
|
|
254
|
+
{
|
|
255
|
+
const f = findings("header-lookalike-row", {
|
|
256
|
+
tail: [
|
|
257
|
+
"### CONTINUOUS",
|
|
258
|
+
...PLAIN_HEAD,
|
|
259
|
+
"| Id | Header-lookalike | research-ideate | ☑ | 2020-01-01 | — | — |",
|
|
260
|
+
].join("\n"),
|
|
261
|
+
});
|
|
262
|
+
assert.deepEqual(
|
|
263
|
+
f.map((x) => x.kind),
|
|
264
|
+
["stale-continuous"],
|
|
265
|
+
"header-lookalike-row: wrong finding set",
|
|
266
|
+
);
|
|
267
|
+
assert.match(
|
|
268
|
+
f[0].msg,
|
|
269
|
+
/\bId\b/,
|
|
270
|
+
"header-lookalike-row: the swallowed row is not named in the finding",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── 17. the cold-read report's filename: BOTH live spellings must count ───────────────────────────
|
|
275
|
+
// 2026-08-24: this gate announced "nobody without context has read this text" about a paper that had
|
|
276
|
+
// been cold-read TWICE that day. The report was filed as `…-coldread-worksheet.md`; the pattern knew
|
|
277
|
+
// only `cold-read`. Neither spelling is a typo — the skill is `cold-read-diff` while the scorecard
|
|
278
|
+
// row id, in the very file this checker parses, is `coldread` — so a gate keyed to one of two live
|
|
279
|
+
// spellings is guaranteed to miss. Its failure mode is the expensive one: a hole announced where
|
|
280
|
+
// none exists is how a gate stops being read, and then a real hole walks past it.
|
|
281
|
+
for (const fname of [
|
|
282
|
+
"2026-08-24-cold-read-report.md",
|
|
283
|
+
"2026-08-24-coldread-worksheet.md",
|
|
284
|
+
]) {
|
|
285
|
+
const name = "coldread-" + fname.replace(/[^a-z]/gi, "");
|
|
286
|
+
const d = join(tmp, name);
|
|
287
|
+
mkdirSync(join(d, "reviews"), { recursive: true });
|
|
288
|
+
writeFileSync(join(d, "PIPELINE-STATUS.md"), scorecard());
|
|
289
|
+
writeFileSync(join(d, "paper.md"), "## 1. Heading\n\nProse.\n");
|
|
290
|
+
writeFileSync(join(d, "reviews", fname), "# a reader with no context\n");
|
|
291
|
+
const out = execFileSync("node", [SCRIPT, d, "--json", `--today=${TODAY}`], {
|
|
292
|
+
cwd: ROOT,
|
|
293
|
+
encoding: "utf8",
|
|
294
|
+
env: ENV,
|
|
295
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
296
|
+
});
|
|
297
|
+
const ks = JSON.parse(out).map((f) => f.kind);
|
|
298
|
+
assert.ok(
|
|
299
|
+
!ks.includes("no-cold-read"),
|
|
300
|
+
`a real cold-read report named ${fname} was reported as missing — the pattern does not know this spelling`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
// …and the half that proves the check still works: reviews/ exists and holds nothing cold-read.
|
|
304
|
+
{
|
|
305
|
+
const d = join(tmp, "coldread-absent");
|
|
306
|
+
mkdirSync(join(d, "reviews"), { recursive: true });
|
|
307
|
+
writeFileSync(join(d, "PIPELINE-STATUS.md"), scorecard());
|
|
308
|
+
writeFileSync(join(d, "paper.md"), "## 1. Heading\n\nProse.\n");
|
|
309
|
+
writeFileSync(
|
|
310
|
+
join(d, "reviews", "2026-08-24-hotcrp-reviews.md"),
|
|
311
|
+
"# not a cold read\n",
|
|
312
|
+
);
|
|
313
|
+
const out = execFileSync("node", [SCRIPT, d, "--json", `--today=${TODAY}`], {
|
|
314
|
+
cwd: ROOT,
|
|
315
|
+
encoding: "utf8",
|
|
316
|
+
env: ENV,
|
|
317
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
318
|
+
});
|
|
319
|
+
assert.ok(
|
|
320
|
+
JSON.parse(out)
|
|
321
|
+
.map((f) => f.kind)
|
|
322
|
+
.includes("no-cold-read"),
|
|
323
|
+
"reviews/ with no cold-read report was NOT reported — widening the pattern silenced the gate",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
328
|
+
console.log(
|
|
329
|
+
"✓ pipeline-check: 6 cases + two spellings of the cold-read report — one planted defect at a time, on six checks whose input is OUTSIDE the file (git · clock · reviews/ · env). The other sixteen moved to eslint-rules/pipeline-status.harness.mjs",
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// ── THE HISTORY CUT ──────────────────────────────────────────────────────────
|
|
333
|
+
// Added 2026-09-19, and the reason it was added is the finding: `currentPart` had NO test at
|
|
334
|
+
// all. The word "History" appeared nowhere in this file, so the function that decides which
|
|
335
|
+
// half of a scorecard counts as current was running unwatched.
|
|
336
|
+
//
|
|
337
|
+
// 🔴 That is also why the cleanup it guards was unsafe until now. `HISTORY_MARK_RE` — a regex
|
|
338
|
+
// over the raw sentence «Всё, что ниже, — история» — was deleted in favour of the heading path
|
|
339
|
+
// alone, and neither the old branch nor the new one could be shown to work. Two attempts at a
|
|
340
|
+
// discriminator through the CLI returned the same answer for both inputs, which says nothing
|
|
341
|
+
// about the subject: same answer for different inputs means the probe is measuring itself.
|
|
342
|
+
// Going through the exported parser instead separates them in one call.
|
|
343
|
+
//
|
|
344
|
+
// Both halves: the buried row is gone under a marker heading, and PRESENT under one that is not
|
|
345
|
+
// a marker. Without the second, a parser that dropped every second section would pass.
|
|
346
|
+
{
|
|
347
|
+
const body = (heading) => `# Scorecard
|
|
348
|
+
|
|
349
|
+
### CONTINUOUS
|
|
350
|
+
| id | Trigger | Skill | Status | Date | Result | Open |
|
|
351
|
+
|----|---------|-------|--------|------|--------|------|
|
|
352
|
+
| live | x | s | ☑ | 2026-09-01 | current | — |
|
|
353
|
+
|
|
354
|
+
## ${heading}
|
|
355
|
+
|
|
356
|
+
### CONTINUOUS
|
|
357
|
+
| id | Trigger | Skill | Status | Date | Result | Open |
|
|
358
|
+
|----|---------|-------|--------|------|--------|------|
|
|
359
|
+
| buried | x | s | ☑ | 2001-01-01 | superseded snapshot | — |
|
|
360
|
+
`;
|
|
361
|
+
const ids = (t) =>
|
|
362
|
+
Object.values(parseStatus(t).sections)
|
|
363
|
+
.flat()
|
|
364
|
+
.map((r) => r.id);
|
|
365
|
+
|
|
366
|
+
for (const marker of [
|
|
367
|
+
"History",
|
|
368
|
+
"Archive",
|
|
369
|
+
"history",
|
|
370
|
+
"Archive of superseded rows",
|
|
371
|
+
]) {
|
|
372
|
+
const got = ids(body(marker));
|
|
373
|
+
assert.deepEqual(
|
|
374
|
+
got,
|
|
375
|
+
["live"],
|
|
376
|
+
`a row under the «${marker}» marker is still counted as current — a superseded snapshot ` +
|
|
377
|
+
`reads as today's status, which is the exact failure the cut exists to prevent: ${JSON.stringify(got)}`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// The other half. A heading that merely mentions neither word must NOT cut, or the check
|
|
382
|
+
// silently stops seeing whole sections and reports a clean scorecard it never read.
|
|
383
|
+
const kept = ids(body("Notes"));
|
|
384
|
+
assert.deepEqual(
|
|
385
|
+
kept,
|
|
386
|
+
["live", "buried"],
|
|
387
|
+
`an ordinary heading cut the document — sections after it vanish unnoticed: ${JSON.stringify(kept)}`,
|
|
388
|
+
);
|
|
389
|
+
}
|