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,737 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pipeline-check — the mechanical leg of paper-pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node pipeline-check.mjs <paper-dir> [--json] [--today=YYYY-MM-DD]
|
|
6
|
+
* Exit: always 0 (advisory). Findings go to stdout; a parse failure to stderr.
|
|
7
|
+
*
|
|
8
|
+
* 🔴 2026-08-26 — SIXTEEN CHECKS OUT OF TWENTY-TWO MOVED INTO `eslint-rules/pipeline-status.mjs`.
|
|
9
|
+
* Their input is ONE markdown document, that is, exactly the subject the `@eslint/markdown` engine
|
|
10
|
+
* is made for; here they were held only by the fact that a private `MarkdownIt` stood next to them.
|
|
11
|
+
* What moved:
|
|
12
|
+
* unattributed-row · unreadable-row · duplicate-id → pipeline/row-attribution · row-readable ·
|
|
13
|
+
* duplicate-id (all three `error`)
|
|
14
|
+
* unknown-input → pipeline/unknown-input (`error`)
|
|
15
|
+
* no-verdict → pipeline/verdict-present (`error`)
|
|
16
|
+
* gate-missing-input · gate-stale-input → pipeline/gate-inputs
|
|
17
|
+
* undeclared-input → pipeline/undeclared-input
|
|
18
|
+
* unattributed-verdict · single-family-jury → pipeline/judge-recorded
|
|
19
|
+
* undated-continuous → pipeline/undated-continuous
|
|
20
|
+
* study-before-frame → pipeline/study-before-frame
|
|
21
|
+
* scalar-verdict → pipeline/scalar-verdict
|
|
22
|
+
* weak-accept-unowned → pipeline/weak-accept-unowned
|
|
23
|
+
* ceiling-unplanned → pipeline/ceiling-unplanned
|
|
24
|
+
* unrun-gate → pipeline/unrun-gate
|
|
25
|
+
* Parity was proved BEFORE the deletion, on the real corpus (four live scorecards, 29 findings
|
|
26
|
+
* matched byte for byte) and on 27 fixtures of the "one planted defect at a time" kind. The
|
|
27
|
+
* classification of all 22 and the analysis of the single discrepancy that surfaced along the way
|
|
28
|
+
* are in `the author's private research notes`.
|
|
29
|
+
*
|
|
30
|
+
* WHY A SCRIPT AND NOT A PARAGRAPH. The six remaining checks correspond to failures that actually
|
|
31
|
+
* happened on a real paper, and all six read an input from OUTSIDE this file — which is why they
|
|
32
|
+
* stayed a script (the rule "local to a node goes to the linter, cross-file goes to a script"):
|
|
33
|
+
*
|
|
34
|
+
* stale-continuous verify-citations was numbered like a one-shot step, so a
|
|
35
|
+
* citation added after the last run counted as verified. The
|
|
36
|
+
* project rule "we checked last cycle does not count" existed.
|
|
37
|
+
* INPUT FROM OUTSIDE: the date of the last commit that touched the paper's
|
|
38
|
+
* text (git).
|
|
39
|
+
* submit-access OpenReview needs an ACTIVE profile and moderation runs up to
|
|
40
|
+
* two weeks. Discovered at T−4 days on a finished paper, as the
|
|
41
|
+
* sole item on the critical path.
|
|
42
|
+
* INPUT FROM OUTSIDE: the clock (`--today`). The check is HYBRID — its
|
|
43
|
+
* second branch (there is no deadline in the header at all) needs no clock,
|
|
44
|
+
* but half a check is not moved into the linter: one fact would end up with
|
|
45
|
+
* two homes.
|
|
46
|
+
* repeat-finding the same worst section named pass after pass, never worked.
|
|
47
|
+
* INPUT FROM OUTSIDE: `reviews/*.md` and their mtime.
|
|
48
|
+
* no-cold-read nobody without context has read the current text.
|
|
49
|
+
* stale-cold-read the cold read describes prose that no longer exists.
|
|
50
|
+
* INPUT FROM OUTSIDE: the presence of `reviews/` + the commit date of the
|
|
51
|
+
* text.
|
|
52
|
+
* credential-available a blocker parked on the author that the environment can clear.
|
|
53
|
+
* INPUT FROM OUTSIDE: `process.env`.
|
|
54
|
+
*
|
|
55
|
+
* The checks are deliberately mechanical. Judgement — is the science good, does the
|
|
56
|
+
* arc hold — stays with the skills; this only refuses to let a checkable thing be
|
|
57
|
+
* quietly false.
|
|
58
|
+
*
|
|
59
|
+
* 🔴 ROW IDS ARE WORDS, NOT TWO-LETTER CODES (renamed 2026-08-09). The id is the join key between
|
|
60
|
+
* every paper's scorecard, this checker, `pipeline-edges.mjs` and the skills' prose, and two letters
|
|
61
|
+
* had run out of room. It had already collided twice, both live:
|
|
62
|
+
* - `Cr` meant `cold-read-diff` in one table of `<paper-a>` and `Camera-ready` in another,
|
|
63
|
+
* and `byId` merges rows by id, so the two are one row to everything downstream.
|
|
64
|
+
* - `Id` (research-ideate) is the header word `id`, so the header-row filter below silently ate
|
|
65
|
+
* that row on every scorecard in the repo — a row that parsed as nothing, on every paper, for as
|
|
66
|
+
* long as the template existed.
|
|
67
|
+
* `coldread`/`cameraready` and `idea` cannot collide that way, which is the point: the fix is in the
|
|
68
|
+
* key space, not in a rule telling people to be careful.
|
|
69
|
+
*
|
|
70
|
+
* 🔴 THE SCORECARD IS PARSED AS MARKDOWN, NOT MATCHED WITH REGULAR EXPRESSIONS (rewritten 2026-08-09;
|
|
71
|
+
* since 2026-08-26 the markup for the moved checks is parsed by ESLint itself, and `MarkdownIt`
|
|
72
|
+
* stayed here for the sections the six remaining checks walk over).
|
|
73
|
+
* Three defects were found in one morning and all three had the same cause — a regex guessing at
|
|
74
|
+
* document structure — and all three were SILENT, which is the only kind that survives:
|
|
75
|
+
*
|
|
76
|
+
* 1. the section heading was `/^###\s+([A-Z]+)\s*$/`. The live `<paper-a>` carries
|
|
77
|
+
* `### Гейты, прогнанные 06.08 …`; it did not match, `current` stayed unset, and every row of
|
|
78
|
+
* that table was dropped without a word. Two of them were cold-read rows.
|
|
79
|
+
* 2. the header row was GUESSED with `/^\|\s*id\s*\|/i`. The data row `| Id |` (research-ideate)
|
|
80
|
+
* matched the guess and was eaten on every scorecard in the repo for as long as the template
|
|
81
|
+
* had existed. A markdown table has a real `thead`; there is nothing to guess.
|
|
82
|
+
* 3. nothing checked that ids were unique, so two rows sharing one merged into whichever came last.
|
|
83
|
+
*
|
|
84
|
+
* With an AST a heading is a node whose text is just text — Cyrillic, emoji and trailing prose cost
|
|
85
|
+
* nothing — and `thead`/`tbody` are given, not inferred. What remains genuinely textual (pulling the
|
|
86
|
+
* `Venue:` and `**Readiness verdict:**` fields out of prose, reading a ☑ out of a cell) stays a text
|
|
87
|
+
* match, because those are labelled FIELDS inside text rather than document STRUCTURE. That is the
|
|
88
|
+
* line: infer structure from the parse, read values from the text.
|
|
89
|
+
*
|
|
90
|
+
* TWO CLASSIFICATION DECISIONS, MADE EXPLICIT RATHER THAN LEFT INSIDE A PATTERN:
|
|
91
|
+
*
|
|
92
|
+
* • A heading names a section iff its FIRST Latin-script word is one of SETUP / LOOP / CONTINUOUS /
|
|
93
|
+
* GATES / AFTER. Everything after that word is free — `### GATES (rerun 06.08)` is GATES. A
|
|
94
|
+
* heading with no Latin first word (`### Гейты, прогнанные 06.08`) or a different one names no
|
|
95
|
+
* section and CLOSES the previous one. We deliberately do NOT translate, alias or fuzzy-match
|
|
96
|
+
* headings: guessing that `Гейты` means GATES is the same move that produced defect 2, and
|
|
97
|
+
* silently continuing the previous section would file rows under a section they do not belong to.
|
|
98
|
+
* Rows in such a region are reported by `pipeline/row-attribution` (since 2026-08-26), never dropped.
|
|
99
|
+
* `AFTER` is an ordinary English word, which is exactly why the name must come FIRST — a prose
|
|
100
|
+
* heading like `## What changed AFTER the panel` must not capture the tables under it.
|
|
101
|
+
* • A table belongs to the scorecard iff its `thead`'s first cell is an id column — `id`, `код` or
|
|
102
|
+
* `code`, a short explicit list. This document legitimately contains PROSE tables (the panel
|
|
103
|
+
* must-fix table, the ceiling classification table); they are not scorecard rows and the
|
|
104
|
+
* document's own header row says so. Reading that from `thead` is a structural fact, not the
|
|
105
|
+
* guess defect 2 was. The legacy single-table format keys its first column `#` — a row NUMBER,
|
|
106
|
+
* not an id — and is therefore not adopted; see the diagnostic in `main`.
|
|
107
|
+
*/
|
|
108
|
+
import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
|
|
109
|
+
import { join, resolve } from "node:path";
|
|
110
|
+
import { execFileSync } from "node:child_process";
|
|
111
|
+
import MarkdownIt from "markdown-it";
|
|
112
|
+
import {
|
|
113
|
+
headings as mdHeadings,
|
|
114
|
+
requireMarkdown,
|
|
115
|
+
} from "../../../lib/markdown.mjs";
|
|
116
|
+
import { isMain } from "./consumer.mjs";
|
|
117
|
+
|
|
118
|
+
// Markup is parsed with a parser (`CLAUDE.md`, 2026-08-11). This script already parsed tables
|
|
119
|
+
// through its own `MarkdownIt` (below) — that is, half the file lived by the rule and half did not.
|
|
120
|
+
// Fail rather than degrade: the output goes into a pipeline gate.
|
|
121
|
+
requireMarkdown();
|
|
122
|
+
|
|
123
|
+
const MODERATION_WINDOW_DAYS = 21; // OpenReview says "up to two weeks"; a week of slack.
|
|
124
|
+
const SOURCE_RE = /(\.tex|\/(paper|draft)\.md)$/;
|
|
125
|
+
const DONE = new Set(["☑"]);
|
|
126
|
+
|
|
127
|
+
// ── parse ────────────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
/** Strip bold/emoji decoration so `**access**` and `access` are the same id. */
|
|
130
|
+
const cleanId = (s) => s.replace(/\*\*/g, "").replace(/[^\w]/g, "").trim();
|
|
131
|
+
|
|
132
|
+
const STATUS_RE = /(☑|◐|☐|⚠|n\/a)/;
|
|
133
|
+
const ISO_RE = /(\d{4}-\d{2}-\d{2})/;
|
|
134
|
+
/**
|
|
135
|
+
* Where a scorecard stops being current. The marker is a HEADING, and it is found as a heading
|
|
136
|
+
* NODE in the parsed document — the level is free, the text is what is checked.
|
|
137
|
+
*
|
|
138
|
+
* 🔴 There used to be a second way in, and removing it is the point of this comment. A
|
|
139
|
+
* `HISTORY_MARK_RE` matched the raw line `Всё, что ниже, — история` — a sentence — with `,?`,
|
|
140
|
+
* `\s*` and the truncated stem `истори` bolted on so it would survive the author's mood and the
|
|
141
|
+
* word's declension. Four hedges in one pattern is four admissions that the thing being matched
|
|
142
|
+
* has no fixed form. Measured 2026-09-19: of the four status files the checker reads, ONE carried
|
|
143
|
+
* that sentence; its `<!-- HISTORY -->` alternative had ZERO users and had never had any. The one
|
|
144
|
+
* file now carries a `## History` heading instead, so both branches went.
|
|
145
|
+
*/
|
|
146
|
+
const HISTORY_HEADING_RE = /^(History|Archive)(?![\p{L}\p{N}_])/iu;
|
|
147
|
+
|
|
148
|
+
/** The five kinds of work the template groups rows into. A heading names one or it names none. */
|
|
149
|
+
const KNOWN_SECTIONS = new Set([
|
|
150
|
+
"SETUP",
|
|
151
|
+
"LOOP",
|
|
152
|
+
"CONTINUOUS",
|
|
153
|
+
"GATES",
|
|
154
|
+
"AFTER",
|
|
155
|
+
]);
|
|
156
|
+
/**
|
|
157
|
+
* First-column headers that make a table part of the scorecard. Short and explicit on purpose: an
|
|
158
|
+
* open-ended rule here would be the guess that defect 2 was. `код` is present because the live
|
|
159
|
+
* `<paper-a>` writes its gate-run table that way; `#` is deliberately ABSENT — it is a row
|
|
160
|
+
* number, and the legacy single-table format that uses it has no sections to attribute rows to.
|
|
161
|
+
*/
|
|
162
|
+
const ID_COLUMNS = new Set(["id", "код", "code"]);
|
|
163
|
+
|
|
164
|
+
const md = new MarkdownIt();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The section a heading names, or null.
|
|
168
|
+
* The name must be the heading's first Latin-script word; anything after it is free text.
|
|
169
|
+
*/
|
|
170
|
+
function sectionOf(headingText) {
|
|
171
|
+
const first = (headingText.toUpperCase().match(/[A-Z]+/) ?? [])[0];
|
|
172
|
+
return first && KNOWN_SECTIONS.has(first) ? first : null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Everything up to the history marker. This project appends rather than rewrites, so every
|
|
177
|
+
* scorecard accumulates superseded snapshots: a 2026-08-02 row saying the artifact was unhosted
|
|
178
|
+
* re-triggered the credential check on 2026-08-05, hours after the artifact went up. A history
|
|
179
|
+
* section is evidence, not status.
|
|
180
|
+
*
|
|
181
|
+
* Done by line before the parse rather than by walking the AST, because the marker is a convention
|
|
182
|
+
* about where the document stops being current — not a structural feature markdown knows about.
|
|
183
|
+
*/
|
|
184
|
+
function currentPart(text) {
|
|
185
|
+
const lines = text.split("\n");
|
|
186
|
+
// The line numbers of marker headings come from the parser — for the same reason `headings()`
|
|
187
|
+
// takes them from there: "the line starts with hashes" and "the line is a heading" are not the
|
|
188
|
+
// same thing, and they diverge silently.
|
|
189
|
+
const headingLines = new Set(
|
|
190
|
+
mdHeadings(text)
|
|
191
|
+
.filter((h) => HISTORY_HEADING_RE.test(h.text))
|
|
192
|
+
.map((h) => h.line),
|
|
193
|
+
);
|
|
194
|
+
const cut = lines.findIndex((_l, i) => headingLines.has(i));
|
|
195
|
+
return (cut === -1 ? lines : lines.slice(0, cut)).join("\n");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Index of the token closing the container opened at `open`. */
|
|
199
|
+
function closeOf(tokens, open, openType, closeType) {
|
|
200
|
+
let depth = 0;
|
|
201
|
+
for (let i = open; i < tokens.length; i++) {
|
|
202
|
+
if (tokens[i].type === openType) depth++;
|
|
203
|
+
else if (tokens[i].type === closeType && --depth === 0) return i;
|
|
204
|
+
}
|
|
205
|
+
return tokens.length - 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** `thead` cells and `tbody` rows of one table, straight from the AST — nothing is guessed. */
|
|
209
|
+
function readTable(toks) {
|
|
210
|
+
const head = [];
|
|
211
|
+
const rows = [];
|
|
212
|
+
let inHead = false;
|
|
213
|
+
let row = null;
|
|
214
|
+
for (let i = 0; i < toks.length; i++) {
|
|
215
|
+
const t = toks[i];
|
|
216
|
+
if (t.type === "thead_open") inHead = true;
|
|
217
|
+
else if (t.type === "thead_close") inHead = false;
|
|
218
|
+
else if (t.type === "tr_open") row = [];
|
|
219
|
+
else if (t.type === "tr_close") {
|
|
220
|
+
if (row) (inHead ? head : rows).push(row);
|
|
221
|
+
row = null;
|
|
222
|
+
} else if (t.type === "th_open" || t.type === "td_open") {
|
|
223
|
+
const inline = toks[i + 1];
|
|
224
|
+
row?.push(inline?.type === "inline" ? inline.content.trim() : "");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { head: head[0] ?? [], rows };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Parse PIPELINE-STATUS.md into `{sections: {SETUP: [row], …}, header, verdict}`.
|
|
232
|
+
*
|
|
233
|
+
* 🔴 `parseFindings` MOVED OUT 2026-08-26. The three findings about the PARSE itself
|
|
234
|
+
* (`unattributed-row`, `unreadable-row`, `duplicate-id`) now live as the rules
|
|
235
|
+
* `pipeline/row-attribution`, `pipeline/row-readable`, `pipeline/duplicate-id` in
|
|
236
|
+
* `eslint-rules/pipeline-status.mjs`, and there they also became `error` — they are binary. We DO
|
|
237
|
+
* NOT DUPLICATE them here: two sources of truth about one fact drift apart. A row that cannot be
|
|
238
|
+
* read is still skipped (`continue`), just silently — the linter speaks about it, and to the
|
|
239
|
+
* precision of a line.
|
|
240
|
+
*/
|
|
241
|
+
export function parseStatus(text) {
|
|
242
|
+
const sections = {};
|
|
243
|
+
let verdict = "";
|
|
244
|
+
let header = "";
|
|
245
|
+
let tables = 0;
|
|
246
|
+
let scorecardTables = 0;
|
|
247
|
+
const headings = [];
|
|
248
|
+
|
|
249
|
+
let section = null;
|
|
250
|
+
let heading = "(before the first heading)";
|
|
251
|
+
|
|
252
|
+
const tokens = md.parse(currentPart(text), {});
|
|
253
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
254
|
+
const t = tokens[i];
|
|
255
|
+
|
|
256
|
+
if (t.type === "heading_open") {
|
|
257
|
+
const inline = tokens[i + 1];
|
|
258
|
+
const raw = inline?.type === "inline" ? inline.content : "";
|
|
259
|
+
heading = raw.replace(/\s+/g, " ").trim();
|
|
260
|
+
headings.push(heading);
|
|
261
|
+
section = sectionOf(heading);
|
|
262
|
+
if (section) sections[section] ??= [];
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Labelled fields living inside prose. Last one wins, as it always has: a scorecard that
|
|
267
|
+
// appends keeps older verdict paragraphs above the current one.
|
|
268
|
+
if (t.type === "inline") {
|
|
269
|
+
for (const line of t.content.split("\n")) {
|
|
270
|
+
if (/^Venue:/.test(line)) header = line;
|
|
271
|
+
if (/^\*\*Readiness verdict:\*\*/.test(line)) verdict = line;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (t.type !== "table_open") continue;
|
|
276
|
+
const end = closeOf(tokens, i, "table_open", "table_close");
|
|
277
|
+
const { head, rows } = readTable(tokens.slice(i, end + 1));
|
|
278
|
+
i = end;
|
|
279
|
+
tables++;
|
|
280
|
+
|
|
281
|
+
// Not a scorecard table. The document's own header row says so; see the decision note up top.
|
|
282
|
+
const idCol = (head[0] ?? "").toLowerCase().replace(/[*`\s]/g, "");
|
|
283
|
+
if (!ID_COLUMNS.has(idCol)) continue;
|
|
284
|
+
scorecardTables++;
|
|
285
|
+
|
|
286
|
+
for (const c of rows) {
|
|
287
|
+
const id = cleanId(c[0] ?? "");
|
|
288
|
+
// An unreadable row is skipped silently: the finding about it comes from
|
|
289
|
+
// `pipeline/row-readable`.
|
|
290
|
+
if (!id || c.length < 4) continue;
|
|
291
|
+
const joined = c.join(" | ");
|
|
292
|
+
const row = {
|
|
293
|
+
id,
|
|
294
|
+
name: c[1] ?? "",
|
|
295
|
+
skill: c[2] ?? "",
|
|
296
|
+
// The `Requires` column is no longer read here: all three checks that ate it
|
|
297
|
+
// (`gate-missing-input` · `gate-stale-input` · `unknown-input`) and the check of the
|
|
298
|
+
// declaration itself (`undeclared-input`) moved into `eslint-rules/pipeline-status.mjs`.
|
|
299
|
+
status: (joined.match(STATUS_RE) ?? [""])[0],
|
|
300
|
+
date: (joined.match(ISO_RE) ?? [null])[0],
|
|
301
|
+
raw: joined,
|
|
302
|
+
section,
|
|
303
|
+
heading,
|
|
304
|
+
};
|
|
305
|
+
// A row outside a section does not get into `sections` — no check read it before either;
|
|
306
|
+
// the finding about that comes from `pipeline/row-attribution`.
|
|
307
|
+
if (section) sections[section].push(row);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
sections,
|
|
313
|
+
header,
|
|
314
|
+
verdict,
|
|
315
|
+
shape: { tables, scorecardTables, headings },
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Deadline from the header line, or null. Tolerates markdown bold (`Deadline: **2026-08-06 …**`). */
|
|
320
|
+
const deadlineOf = (header) =>
|
|
321
|
+
(header.match(/Deadline:\s*\**\s*(\d{4}-\d{2}-\d{2})/) ?? [null, null])[1];
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* When did the paper's own text LAST ACTUALLY CHANGE, as an ISO date, or null if no source found.
|
|
325
|
+
*
|
|
326
|
+
* 🔴 TWO DEFECTS FIXED HERE 2026-08-09, both of which made this checker lie about a file nobody
|
|
327
|
+
* had touched. Measured on the live paper the same day: of 13 findings, ONE was false outright
|
|
328
|
+
* (a cold read dated 08-07 reported as older than text last changed 08-06) and FOUR carried a
|
|
329
|
+
* false stated reason. A checker wrong on a third of its output is muted inside a week.
|
|
330
|
+
*
|
|
331
|
+
* 1. THE CLOCK WAS `mtime`, WHICH IS NOT A PROPERTY OF THE TEXT. `git checkout`, `git stash pop`,
|
|
332
|
+
* a merge conflict resolved with `--ours`, or a fresh clone all rewrite it while leaving the
|
|
333
|
+
* bytes identical — and every gate then reports stale. Proven: `paper.md` was byte-identical
|
|
334
|
+
* to the submitted version (same md5, git date 08-06) with an mtime of today, and four
|
|
335
|
+
* continuous checks plus the cold read were called stale. The clock is now the last COMMIT
|
|
336
|
+
* that touched the file; mtime is used only when the file is dirty, where it is honest,
|
|
337
|
+
* because uncommitted edits really did just happen.
|
|
338
|
+
* 2. THE GLOB SWEPT BUILD OUTPUTS. `build/acl_latex.tex` and `build-aclpubcheck/*.tex` match
|
|
339
|
+
* SOURCE_RE, and the walk skipped only dotfiles, node_modules and repro — so REBUILDING THE
|
|
340
|
+
* PDF marked every gate stale. Every build system in the occupancy sweep makes this
|
|
341
|
+
* unrepresentable by forcing outputs to be declared apart from inputs; here the cheap
|
|
342
|
+
* equivalent is to name the generated directories and skip them.
|
|
343
|
+
*/
|
|
344
|
+
const GENERATED_DIRS = new Set([
|
|
345
|
+
"build",
|
|
346
|
+
"build-aclpubcheck",
|
|
347
|
+
"dist",
|
|
348
|
+
"out",
|
|
349
|
+
"artifact-anon",
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
function newestSourceDate(dir) {
|
|
353
|
+
const sources = [];
|
|
354
|
+
const walk = (d, depth) => {
|
|
355
|
+
if (depth > 3) return;
|
|
356
|
+
let entries;
|
|
357
|
+
try {
|
|
358
|
+
entries = readdirSync(d, { withFileTypes: true });
|
|
359
|
+
} catch {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
for (const e of entries) {
|
|
363
|
+
if (
|
|
364
|
+
e.name.startsWith(".") ||
|
|
365
|
+
e.name === "node_modules" ||
|
|
366
|
+
e.name === "repro"
|
|
367
|
+
)
|
|
368
|
+
continue;
|
|
369
|
+
if (e.isDirectory() && GENERATED_DIRS.has(e.name)) continue;
|
|
370
|
+
const p = join(d, e.name);
|
|
371
|
+
if (e.isDirectory()) walk(p, depth + 1);
|
|
372
|
+
else if (SOURCE_RE.test(p)) sources.push(p);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
walk(dir, 0);
|
|
376
|
+
if (!sources.length) return null;
|
|
377
|
+
|
|
378
|
+
let newest = "";
|
|
379
|
+
for (const p of sources) {
|
|
380
|
+
let when; // no initializer: both branches below assign it (2026-08-28)
|
|
381
|
+
try {
|
|
382
|
+
// Dirty file: the edit is real and uncommitted, so mtime is the honest answer.
|
|
383
|
+
const dirty = execFileSync("git", ["status", "--porcelain", "--", p], {
|
|
384
|
+
encoding: "utf8",
|
|
385
|
+
}).trim();
|
|
386
|
+
// 🔴 EMPTY OUTPUT FROM `git log` IS NOT A DATE. The command exits 0 and prints NOTHING when
|
|
387
|
+
// a path has no history: a shallow clone (`actions/checkout` defaults to `fetch-depth: 1`), a
|
|
388
|
+
// file outside git, a file inside `node_modules`. Previously that empty value went on as
|
|
389
|
+
// `when`, the whole `newestSourceDate` returned "", and `stale-continuous` together with
|
|
390
|
+
// `stale-cold-read` silently stopped firing — that is, the staleness check went stale itself
|
|
391
|
+
// without a single word.
|
|
392
|
+
//
|
|
393
|
+
// Measured 14.09: the `fixtures/dirty` fixture lives inside this package, and at the consumer
|
|
394
|
+
// it sits under `node_modules`. Locally `git log` returned 2026-09-12 — the date of the
|
|
395
|
+
// commit THAT DELETED THIS PATH from the consumer's tree, that is, the check rested on a
|
|
396
|
+
// ghost of history. In CI with a shallow clone there is no ghost, the output is empty, and
|
|
397
|
+
// the harness failed on "pipeline-check reports stale-continuous on the dirty fixture" (run
|
|
398
|
+
// 34784079821).
|
|
399
|
+
const logged = dirty
|
|
400
|
+
? ""
|
|
401
|
+
: execFileSync("git", ["log", "-1", "--format=%cs", "--", p], {
|
|
402
|
+
encoding: "utf8",
|
|
403
|
+
}).trim();
|
|
404
|
+
when = logged || new Date(statSync(p).mtimeMs).toISOString().slice(0, 10);
|
|
405
|
+
} catch {
|
|
406
|
+
when = new Date(statSync(p).mtimeMs).toISOString().slice(0, 10); // no git here: fall back
|
|
407
|
+
}
|
|
408
|
+
if (when > newest) newest = when;
|
|
409
|
+
}
|
|
410
|
+
return newest || null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── checks ───────────────────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
const byId = (sections) => {
|
|
416
|
+
const m = new Map();
|
|
417
|
+
for (const rows of Object.values(sections))
|
|
418
|
+
for (const r of rows) m.set(r.id, r);
|
|
419
|
+
return m;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
export function check({ sections, header }, { sourceDate, today, dir }) {
|
|
423
|
+
const out = [];
|
|
424
|
+
const all = byId(sections);
|
|
425
|
+
const add = (kind, msg) => out.push({ kind, msg });
|
|
426
|
+
|
|
427
|
+
// 1. A CONTINUOUS check that passed on text older than the current text.
|
|
428
|
+
for (const r of sections.CONTINUOUS ?? []) {
|
|
429
|
+
if (!DONE.has(r.status)) continue;
|
|
430
|
+
// `undated-continuous` (a ☑ with no date) moved into the rule `pipeline/undated-continuous`.
|
|
431
|
+
// What stays here is exactly the half of the loop that needs an input from OUTSIDE the file —
|
|
432
|
+
// the date of the last commit that touched the paper's text.
|
|
433
|
+
if (!r.date) continue;
|
|
434
|
+
if (sourceDate && r.date < sourceDate) {
|
|
435
|
+
add(
|
|
436
|
+
"stale-continuous",
|
|
437
|
+
`${r.id} (${r.skill}) last ran ${r.date}, the text changed ${sourceDate} — this ☑ is stale, not green`,
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ── MOVED 2026-08-26 into `eslint-rules/pipeline-status.mjs` ─────────────────────────────
|
|
443
|
+
// The five blocks that stood here read ONLY this document and therefore moved into ESLint rules
|
|
444
|
+
// in full, together with their comments and history:
|
|
445
|
+
// 2. a gate with no declared input → `pipeline/gate-inputs` (+ `pipeline/unknown-input`)
|
|
446
|
+
// 2a-bis. who cleared it and is it one family → `pipeline/judge-recorded`
|
|
447
|
+
// 2b. a short Requires cell → `pipeline/undeclared-input`
|
|
448
|
+
// 3. study ☑ while frame ☐ → `pipeline/study-before-frame`
|
|
449
|
+
// They must not be duplicated here: two sources of truth about one fact drift apart.
|
|
450
|
+
|
|
451
|
+
// 4. Can you physically submit, with enough time for someone else's queue?
|
|
452
|
+
const ac = all.get("access");
|
|
453
|
+
const deadline = deadlineOf(header);
|
|
454
|
+
if (ac && !DONE.has(ac.status)) {
|
|
455
|
+
if (deadline && today) {
|
|
456
|
+
const days = Math.round(
|
|
457
|
+
(Date.parse(deadline) - Date.parse(today)) / 86_400_000,
|
|
458
|
+
);
|
|
459
|
+
if (days <= MODERATION_WINDOW_DAYS) {
|
|
460
|
+
add(
|
|
461
|
+
"submit-access",
|
|
462
|
+
`access (can you physically submit?) is ${ac.status || "blank"} with ${days} day(s) to the deadline — profile moderation runs up to two weeks and there is no expedite route without an institutional email`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
add(
|
|
467
|
+
"submit-access",
|
|
468
|
+
`access (can you physically submit?) is ${ac.status || "blank"} — turn it green in the first week, not the last`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ── MOVED 2026-08-26 into `eslint-rules/pipeline-status.mjs` ─────────────────────────────
|
|
474
|
+
// 5. a verdict exists and is not just a number → `pipeline/verdict-present` +
|
|
475
|
+
// `pipeline/scalar-verdict`
|
|
476
|
+
// 5b. Weak Accept with no recorded decision → `pipeline/weak-accept-unowned`
|
|
477
|
+
// 5b. a ceiling with no classification of bars → `pipeline/ceiling-unplanned`
|
|
478
|
+
// 6. ☐ gates → `pipeline/unrun-gate`
|
|
479
|
+
|
|
480
|
+
// 6b. The same section named worst, pass after pass, with nothing done about it.
|
|
481
|
+
//
|
|
482
|
+
// The trigger was the reaction of the person who opened the first page of the built PDF
|
|
483
|
+
// (2026-08-05): the complaint was not about the content but about READABILITY, and it sounded
|
|
484
|
+
// like "what is wrong with our writing rules for papers — they are never called at all". The
|
|
485
|
+
// verbatim wording was removed when the pipeline was extracted; it is in the history of the
|
|
486
|
+
// private knowledge base. The answer to it: the rules WERE called. `grade-paper-writing` ran five times
|
|
487
|
+
// and named the abstract its worst section every single time; the finding was faithfully recorded
|
|
488
|
+
// in the scorecard on all five occasions and was never once the next task. Recording is not
|
|
489
|
+
// owning. A review loop that only appends findings converges on a paper whose defects are all
|
|
490
|
+
// thoroughly documented and none of them fixed — the loop needs a ratchet, and this is it.
|
|
491
|
+
//
|
|
492
|
+
// Deliberately dumb: count how many of the most recent reports name the same worst section. Two
|
|
493
|
+
// in a row is a nudge, three is a process failure, and no amount of prose in a skill file
|
|
494
|
+
// substitutes for the count.
|
|
495
|
+
const worst = dir ? recentWorstSections(dir) : [];
|
|
496
|
+
if (worst.length >= 2 && new Set(worst).size === 1) {
|
|
497
|
+
add(
|
|
498
|
+
"repeat-finding",
|
|
499
|
+
`"${worst[0]}" has been the worst-rated section in the last ${worst.length} writing passes and is still the worst — a finding recorded ${worst.length} times without becoming a task is a process failure, not a known issue. Fix it or record why it is being shipped as is.`,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 6c. Nobody who does not already know the paper has read the current text.
|
|
504
|
+
//
|
|
505
|
+
// This is the check that would have caught every readability defect found by eye on
|
|
506
|
+
// 2026-08-05, and NONE of them were visible to anything else we run. Sentence length, number
|
|
507
|
+
// density, tic frequency — all proxies, all green on sentences that turned out to mean nothing.
|
|
508
|
+
// The failure they cannot see is a sentence compressed until it only parses for someone who
|
|
509
|
+
// already holds the idea, and the author holds the idea by definition.
|
|
510
|
+
//
|
|
511
|
+
// So the gate is not another metric. It is a person-shaped reader with no context, and the only
|
|
512
|
+
// mechanical part is whether one has looked at THIS text: a cold-read report older than the
|
|
513
|
+
// current paper describes prose that no longer exists.
|
|
514
|
+
if (dir) {
|
|
515
|
+
// 🔴 `cold-?read`, not `cold-read`. Measured 2026-08-24: this check reported "nobody without
|
|
516
|
+
// context has read this text" about a paper that had been cold-read TWICE that day, because the
|
|
517
|
+
// report was filed as `2026-08-24-coldread-worksheet.md` and the pattern only knew the hyphen.
|
|
518
|
+
//
|
|
519
|
+
// Not a typo on either side — BOTH spellings are load-bearing in this repo and always will be:
|
|
520
|
+
// the skill is `cold-read-diff` (88 files) while the scorecard row id, in the very file this
|
|
521
|
+
// checker parses, is `coldread` (15 files). A gate keyed to one of two live spellings is
|
|
522
|
+
// guaranteed to miss, and its failure mode is the worst one available: it announces a hole
|
|
523
|
+
// where none exists, which is how a gate stops being read before a real hole walks past it.
|
|
524
|
+
const cr = newestReviewDate(dir, /cold-?read/i);
|
|
525
|
+
if (cr === undefined) {
|
|
526
|
+
// No reviews/ at all: this project does not use the convention. Silence is correct, and the
|
|
527
|
+
// harness asserts it — the first version of this check treated "no folder" and "no report"
|
|
528
|
+
// alike and fired on every clean fixture.
|
|
529
|
+
} else if (cr === null) {
|
|
530
|
+
add(
|
|
531
|
+
"no-cold-read",
|
|
532
|
+
"no cold-read report in reviews/ — nobody without context has read this text, and a compressed sentence only fails for a reader who does not already know the idea",
|
|
533
|
+
);
|
|
534
|
+
} else if (sourceDate && cr < sourceDate) {
|
|
535
|
+
add(
|
|
536
|
+
"stale-cold-read",
|
|
537
|
+
`the cold read ran ${cr}, the text changed ${sourceDate} — it describes prose that no longer exists`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 7. A blocker parked on the author that the environment can already clear.
|
|
543
|
+
//
|
|
544
|
+
// The trigger was a remark by the repository owner (2026-08-05): the agent forgets over and over
|
|
545
|
+
// that it can upload the artifact ITSELF, although it has access to the variable it needs. The
|
|
546
|
+
// verbatim wording was removed when the pipeline was extracted; it is in the history of the
|
|
547
|
+
// private knowledge base.
|
|
548
|
+
//
|
|
549
|
+
// Artifact hosting sat on the human's side of the owner-split for a day and a half; the
|
|
550
|
+
// credential that clears it was in the environment the whole time. The owner-split is the most
|
|
551
|
+
// useful line in a status report and also the easiest to get wrong in the expensive direction —
|
|
552
|
+
// an item wrongly assigned to the human is pure schedule loss, so the check runs against the
|
|
553
|
+
// environment rather than against the agent's memory of what it can reach.
|
|
554
|
+
for (const [envVar, what] of Object.entries(CREDENTIALS)) {
|
|
555
|
+
if (!process.env[envVar]) continue;
|
|
556
|
+
// `done` is checked against the row TEXT, not the row's status box: a multi-axis gate like
|
|
557
|
+
// harden stays ⚠ for reasons that have nothing to do with this credential, and firing on its
|
|
558
|
+
// status would mean nagging about an upload that already happened.
|
|
559
|
+
const row = [...all.values()].find(
|
|
560
|
+
(r) =>
|
|
561
|
+
what.re.test(r.raw ?? "") &&
|
|
562
|
+
!DONE.has(r.status) &&
|
|
563
|
+
!what.done.test(r.raw ?? ""),
|
|
564
|
+
);
|
|
565
|
+
if (row) {
|
|
566
|
+
add(
|
|
567
|
+
"credential-available",
|
|
568
|
+
`${row.id} (${what.label}) is parked as open, but ${envVar} is set in this environment — ${what.action}. Do not report this as waiting on the human.`,
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* ISO date of the newest report in reviews/ whose filename matches.
|
|
578
|
+
* `undefined` means this project has no reviews/ at all and is not using the convention —
|
|
579
|
+
* distinct from `null`, which means it does and nothing has matched. Collapsing the two made the
|
|
580
|
+
* cold-read check fire on every paper without a review folder, including the clean test fixture;
|
|
581
|
+
* the harness caught it on the first run after the check was added.
|
|
582
|
+
*/
|
|
583
|
+
function newestReviewDate(dir, re) {
|
|
584
|
+
const rd = join(dir, "reviews");
|
|
585
|
+
if (!existsSync(rd)) return undefined;
|
|
586
|
+
let newest = 0;
|
|
587
|
+
try {
|
|
588
|
+
for (const f of readdirSync(rd)) {
|
|
589
|
+
if (!re.test(f) || !f.endsWith(".md")) continue;
|
|
590
|
+
newest = Math.max(newest, statSync(join(rd, f)).mtimeMs);
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
return newest ? new Date(newest).toISOString().slice(0, 10) : null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* The worst-rated section from each of the most recent writing passes, newest first.
|
|
600
|
+
*
|
|
601
|
+
* Reads the `WORST-SECTION: <name>` marker that grade-paper-writing is required to put on the
|
|
602
|
+
* first line of its report. A report without the marker is skipped rather than guessed at — a
|
|
603
|
+
* wrong guess here would either invent a repeat or hide one, and both are worse than silence.
|
|
604
|
+
*/
|
|
605
|
+
function recentWorstSections(dir, limit = 4) {
|
|
606
|
+
const rd = join(dir, "reviews");
|
|
607
|
+
if (!existsSync(rd)) return [];
|
|
608
|
+
let files;
|
|
609
|
+
try {
|
|
610
|
+
files = readdirSync(rd).filter(
|
|
611
|
+
(f) => /grade-paper-writing|writing/i.test(f) && f.endsWith(".md"),
|
|
612
|
+
);
|
|
613
|
+
} catch {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
return files
|
|
617
|
+
.map((f) => ({ f, m: statSync(join(rd, f)).mtimeMs }))
|
|
618
|
+
.sort((a, b) => b.m - a.m)
|
|
619
|
+
.slice(0, limit)
|
|
620
|
+
.map(({ f }) => {
|
|
621
|
+
try {
|
|
622
|
+
const m = readFileSync(join(rd, f), "utf8").match(
|
|
623
|
+
/^\s*WORST-SECTION:\s*(.+)$/m,
|
|
624
|
+
);
|
|
625
|
+
return m ? m[1].trim().toLowerCase() : null;
|
|
626
|
+
} catch {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
})
|
|
630
|
+
.filter(Boolean);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Credentials the session may hold, and the blocker each one un-parks. */
|
|
634
|
+
const CREDENTIALS = {
|
|
635
|
+
OSF_TOKEN: {
|
|
636
|
+
label: "artifact hosting",
|
|
637
|
+
re: /artifact|host|upload|anonym\w* (link|url)|osf/i,
|
|
638
|
+
done: /osf\.io\/\w+|захостен|hosted (on|at)\b/i,
|
|
639
|
+
action:
|
|
640
|
+
"upload the anonymised bundle over the OSF API and paste the view-only URL yourself",
|
|
641
|
+
},
|
|
642
|
+
GITHUB_TOKEN: {
|
|
643
|
+
label: "repository work",
|
|
644
|
+
re: /release|tag|github|repo(sitory)? (link|url)/i,
|
|
645
|
+
done: /github\.com\/[\w.-]+\/[\w.-]+\/(releases|tree)\//i,
|
|
646
|
+
action: "do it over the API yourself",
|
|
647
|
+
},
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// ── cli ──────────────────────────────────────────────────────────────────────
|
|
651
|
+
|
|
652
|
+
function main(argv) {
|
|
653
|
+
const args = argv.slice(2);
|
|
654
|
+
const dir = resolve(args.find((a) => !a.startsWith("--")) ?? ".");
|
|
655
|
+
const asJson = args.includes("--json");
|
|
656
|
+
const todayArg = (args.find((a) => a.startsWith("--today=")) ?? "").split(
|
|
657
|
+
"=",
|
|
658
|
+
)[1];
|
|
659
|
+
const today = todayArg || new Date().toISOString().slice(0, 10);
|
|
660
|
+
|
|
661
|
+
const statusPath = join(dir, "PIPELINE-STATUS.md");
|
|
662
|
+
if (!existsSync(statusPath)) return 0; // no scorecard → nothing to say
|
|
663
|
+
|
|
664
|
+
const parsed = parseStatus(readFileSync(statusPath, "utf8"));
|
|
665
|
+
const known = ["SETUP", "LOOP", "CONTINUOUS", "GATES"].filter(
|
|
666
|
+
(s) => parsed.sections[s]?.length,
|
|
667
|
+
);
|
|
668
|
+
if (known.length < 4) {
|
|
669
|
+
// Say WHAT was found, not only what was missing. `<paper-c>` and `<paper-b>` predate the
|
|
670
|
+
// four-section template: one table, first column `#`, no section headings anywhere. Under the
|
|
671
|
+
// old message ("found [none]") that was indistinguishable from a typo in a current scorecard,
|
|
672
|
+
// and the two cases want opposite responses — migrate the format vs fix the heading.
|
|
673
|
+
const { tables, scorecardTables, headings } = parsed.shape;
|
|
674
|
+
const legacy = scorecardTables === 0 && tables > 0;
|
|
675
|
+
console.error(
|
|
676
|
+
`⚠️ ${statusPath}: expected the four sections SETUP/LOOP/CONTINUOUS/GATES, found [${known.join(", ") || "none"}].\n` +
|
|
677
|
+
` Parsed ${tables} table(s), ${scorecardTables} of them with an id column; headings: ${headings.length ? headings.map((h) => `"${h.slice(0, 40)}"`).join(", ") : "none"}.\n` +
|
|
678
|
+
(legacy
|
|
679
|
+
? " No table has an id column — this is the pre-2026-08 single-table format (first column `#`, a row number).\n" +
|
|
680
|
+
" It does NOT parse and will not be made to: `#` cannot key the edges gates declare, and there are no\n" +
|
|
681
|
+
" sections to attribute rows to. Migrate it to the template, or leave it as a submitted paper's record.\n"
|
|
682
|
+
: "") +
|
|
683
|
+
" The template is a format contract — see paper-pipeline/references/pipeline-status-template.md.\n" +
|
|
684
|
+
" Until it parses, these checks are silent, which is the failure mode they exist to prevent.",
|
|
685
|
+
);
|
|
686
|
+
return 0;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const findings = check(parsed, {
|
|
690
|
+
sourceDate: newestSourceDate(dir),
|
|
691
|
+
today,
|
|
692
|
+
dir,
|
|
693
|
+
});
|
|
694
|
+
if (asJson) {
|
|
695
|
+
console.log(JSON.stringify(findings, null, 2));
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// The banner prints even with zero findings, and that is the point.
|
|
700
|
+
//
|
|
701
|
+
// A remark by the repository owner, 2026-08-05: the human has to keep asking for the paper's
|
|
702
|
+
// status and steering the agent by hand. (The verbatim quote was removed on extraction; it is in
|
|
703
|
+
// the history of the knowledge base.)
|
|
704
|
+
// The asking happened because nothing volunteered the state. A checker that is silent when clean
|
|
705
|
+
// trains the session to treat silence as "no state to report", so the state only ever surfaces
|
|
706
|
+
// when a human pulls it — which is exactly the work being complained about. Findings are advisory; the
|
|
707
|
+
// banner is unconditional, so every edit to a paper reprints where the paper stands and what
|
|
708
|
+
// the next move is, without anyone asking.
|
|
709
|
+
const dl = deadlineOf(parsed.header);
|
|
710
|
+
const days = dl
|
|
711
|
+
? Math.round((Date.parse(dl) - Date.parse(today)) / 86_400_000)
|
|
712
|
+
: null;
|
|
713
|
+
const when = dl
|
|
714
|
+
? `${dl} (T${days >= 0 ? "−" : "+"}${Math.abs(days)}d)`
|
|
715
|
+
: "deadline not in the header";
|
|
716
|
+
const name = dir.split("/").filter(Boolean).pop();
|
|
717
|
+
|
|
718
|
+
console.log(`📊 ${name} — ${when} · ${findings.length} open finding(s)`);
|
|
719
|
+
console.log(
|
|
720
|
+
` verdict row: ${parsed.verdict ? parsed.verdict.replace(/^\*\*Readiness verdict:\*\*\s*/, "").slice(0, 160) : "(none)"}`,
|
|
721
|
+
);
|
|
722
|
+
console.log(
|
|
723
|
+
` ➡️ NEXT: ${findings.length ? findings[0].msg.slice(0, 200) : "no mechanical blocker — run paper-status for the measured picture (build page count, stale gates, owner-split) before deciding it is done"}`,
|
|
724
|
+
);
|
|
725
|
+
console.log(
|
|
726
|
+
" (page count comes from repro/build-submission.sh, never from this line)",
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
if (!findings.length) return 0;
|
|
730
|
+
console.log(
|
|
731
|
+
`⚠️ pipeline-check — ${findings.length} finding(s) in ${statusPath}:`,
|
|
732
|
+
);
|
|
733
|
+
for (const f of findings) console.log(` [${f.kind}] ${f.msg}`);
|
|
734
|
+
return 0;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (isMain(import.meta.url)) process.exit(main(process.argv));
|