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,576 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* round-diff — a review round may only change what it declared, and the WHOLE paper is weighed
|
|
4
|
+
* every round.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node round-diff.mjs <paper-dir> [--json] [--since=<git-rev>]
|
|
7
|
+
* Exit: always 0 (advisory). Findings on stdout; a census and a SILENT ABOUT block always.
|
|
8
|
+
*
|
|
9
|
+
* ── THE FAILURE ──────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
* `references/review-ratchet.md`, from a real session: "one session, seven overclaim fixes plus one
|
|
11
|
+
* panel round added roughly a page of pure hedge density." Review only ever ADDS. Every round
|
|
12
|
+
* deposits a hedge, a caveat, a clarification, one more citation — each one locally defensible,
|
|
13
|
+
* none of them authorised by anybody, and the paper that survives four rounds reads like armour.
|
|
14
|
+
* Nothing in this pipeline was watching the aggregate. `tighten-paper` is asked to pay the debt back
|
|
15
|
+
* afterwards, which is a cleanup pass, not a gate: it runs when someone remembers, and what it
|
|
16
|
+
* measures is the paper's state, never the round's spend.
|
|
17
|
+
*
|
|
18
|
+
* Two mechanisms from ARIS (`wanshuiyin/Auto-claude-code-research-in-sleep`, MIT, HEAD 2a23847, read
|
|
19
|
+
* 2026-08-09), adapted to a markdown source:
|
|
20
|
+
*
|
|
21
|
+
* - `auto-paper-improvement-loop`'s EDIT WHITELIST — `forbidden_operations`, of which theirs are
|
|
22
|
+
* `new_cite`, `new_bibitem`, `new_theorem_env` and `numerical_claim` ("blocks adding numbers /
|
|
23
|
+
* percentages / metrics not present in original"). Their argument for making it a parameter is
|
|
24
|
+
* the whole point of doing it this way: routing the constraint "through a first-class parameter
|
|
25
|
+
* (rather than relying on the LLM to 'remember' not to do them) makes the constraint enforceable,
|
|
26
|
+
* auditable … and visible to the user at each round's checkpoint."
|
|
27
|
+
* - `resubmit-pipeline:412` — "**Per-round diff gate is mandatory.** Multi-round drift is the
|
|
28
|
+
* highest-risk failure mode … (a small softening at round 1 + another small softening at round 2
|
|
29
|
+
* can compound into a meaningful framing change)."
|
|
30
|
+
*
|
|
31
|
+
* ── WHY IT IS NOT A DIFF CHECKER ─────────────────────────────────────────────────────────────
|
|
32
|
+
* 🔴 The house failure of 2026-08-05, recorded in `papers/CLAUDE.md`: four defects found by eye, all
|
|
33
|
+
* four covered by an existing tool, all four silent BY CONSTRUCTION because every tool asked a local
|
|
34
|
+
* question. The `carries:` convention had coverage that was "a function of the history of edits" —
|
|
35
|
+
* a pass that annotates only what it touched leaves holes exactly where it touched nothing, and the
|
|
36
|
+
* hole grows silently at the rate the pass skips things.
|
|
37
|
+
*
|
|
38
|
+
* So the load-bearing findings here — `ratchet`, `ratchet-cumulative`, `hedge-mass`,
|
|
39
|
+
* `overbroad-scope` — are computed over the ENTIRE document at both revisions and do not care what
|
|
40
|
+
* the round touched. `ratchet-cumulative` does not even look at this round: it weighs today's body
|
|
41
|
+
* against the base of the FIRST round and against the sum of every budget ever declared, which is
|
|
42
|
+
* the compounding case ARIS names and the case a per-round-only check cannot see by construction.
|
|
43
|
+
* Only `undeclared-section` is diff-shaped, and even it enumerates every section in the document
|
|
44
|
+
* rather than reading a patch.
|
|
45
|
+
*
|
|
46
|
+
* ── WHAT AUTHORISES ──────────────────────────────────────────────────────────────────────────
|
|
47
|
+
* A round is a manifest at `<paper-dir>/rounds/<anything>.md` with a frontmatter block:
|
|
48
|
+
*
|
|
49
|
+
* ---
|
|
50
|
+
* round: 3
|
|
51
|
+
* opened: 2026-08-10
|
|
52
|
+
* base: 4f1c2ab # git rev this round starts from (required)
|
|
53
|
+
* touches: # headings this round may change (required)
|
|
54
|
+
* - "4.4"
|
|
55
|
+
* - Limitations
|
|
56
|
+
* allows: [new-citation] # otherwise-forbidden operations (optional)
|
|
57
|
+
* budget: +60 # net BODY words this round may add (optional, default 0)
|
|
58
|
+
* hedge-budget: +0.4 # hedges per 1000 body words it may add (optional, default 0)
|
|
59
|
+
* closed: 2026-08-11 # presence means the round is over (optional)
|
|
60
|
+
* ---
|
|
61
|
+
*
|
|
62
|
+
* The manifest is written BEFORE the round, which is the entire mechanism: a budget agreed after
|
|
63
|
+
* seeing the spend is not a budget. Nothing here reads prose to decide anything — every verdict is a
|
|
64
|
+
* string comparison, a set difference or a counter against a declared number.
|
|
65
|
+
*/
|
|
66
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
67
|
+
import { spawnSync } from "node:child_process";
|
|
68
|
+
import { join, resolve, relative } from "node:path";
|
|
69
|
+
import { stripFrontmatter, frontmatterBlock } from "../../../lib/markdown.mjs";
|
|
70
|
+
import { isMain } from "./consumer.mjs";
|
|
71
|
+
|
|
72
|
+
// ── the lexicon ──────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
//
|
|
74
|
+
// Hedges are counted, never judged. A rising count is a fact and the fact is the finding; whether a
|
|
75
|
+
// given hedge earned its place is Type B and belongs to a reader (see references/acceptance-gate.md).
|
|
76
|
+
// Kept deliberately small and unambiguous: every entry is a word that qualifies a claim rather than
|
|
77
|
+
// making one. `Limitations` sections legitimately carry these, which is why the metric is a DENSITY
|
|
78
|
+
// over the whole body and why the round declares a budget instead of the checker guessing one.
|
|
79
|
+
const HEDGES = [
|
|
80
|
+
"may",
|
|
81
|
+
"might",
|
|
82
|
+
"could",
|
|
83
|
+
"would appear",
|
|
84
|
+
"appears to",
|
|
85
|
+
"appear to",
|
|
86
|
+
"seems to",
|
|
87
|
+
"seem to",
|
|
88
|
+
"suggests",
|
|
89
|
+
"suggest that",
|
|
90
|
+
"arguably",
|
|
91
|
+
"presumably",
|
|
92
|
+
"possibly",
|
|
93
|
+
"perhaps",
|
|
94
|
+
"somewhat",
|
|
95
|
+
"relatively",
|
|
96
|
+
"largely",
|
|
97
|
+
"broadly",
|
|
98
|
+
"generally",
|
|
99
|
+
"typically",
|
|
100
|
+
"usually",
|
|
101
|
+
"often",
|
|
102
|
+
"in some cases",
|
|
103
|
+
"to some extent",
|
|
104
|
+
"not necessarily",
|
|
105
|
+
"we believe",
|
|
106
|
+
"we think",
|
|
107
|
+
"it is possible",
|
|
108
|
+
"it may be",
|
|
109
|
+
"tends to",
|
|
110
|
+
"tend to",
|
|
111
|
+
"roughly",
|
|
112
|
+
"approximately",
|
|
113
|
+
"more or less",
|
|
114
|
+
"at least in part",
|
|
115
|
+
"partly",
|
|
116
|
+
];
|
|
117
|
+
const HEDGE_RE = new RegExp(
|
|
118
|
+
`\\b(?:${HEDGES.map((h) => h.replace(/ /g, "\\s+")).join("|")})\\b`,
|
|
119
|
+
"gi",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
/** Operations a round must declare before it may perform them. Mirrors ARIS `forbidden_operations`. */
|
|
123
|
+
const OPS = ["new-citation", "new-number"];
|
|
124
|
+
|
|
125
|
+
// ── reading a paper ──────────────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
/** HTML comments are metadata (TIGHTEN verdicts, carries: notes, figure provenance), not prose.
|
|
128
|
+
* They churn constantly and none of it is text a reader sees, so every measurement here strips
|
|
129
|
+
* them first — otherwise a round that only rewrote its own bookkeeping would light the board up. */
|
|
130
|
+
const stripComments = (s) => s.replace(/<!--[\s\S]*?-->/g, " ");
|
|
131
|
+
|
|
132
|
+
/** Split into sections keyed by heading line. A section runs to the next heading of ANY level. */
|
|
133
|
+
export function sections(md) {
|
|
134
|
+
const text = stripComments(stripFrontmatter(md));
|
|
135
|
+
const out = [];
|
|
136
|
+
let cur = null;
|
|
137
|
+
for (const line of text.split("\n")) {
|
|
138
|
+
const m = /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
|
|
139
|
+
if (m) {
|
|
140
|
+
cur = { level: m[1].length, heading: m[2], body: [] };
|
|
141
|
+
out.push(cur);
|
|
142
|
+
} else if (cur) cur.body.push(line);
|
|
143
|
+
}
|
|
144
|
+
return out.map((s) => ({ ...s, text: s.body.join("\n").trim() }));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Everything before `## References` is BODY; References and the appendices after it are not.
|
|
148
|
+
* This is the same boundary `paper-lint`'s appendix ratio uses, and it matters for the ratchet: moving
|
|
149
|
+
* a passage into an appendix is the sanctioned way to pay for added body text (`papers/CLAUDE.md`
|
|
150
|
+
* rule 4, "never pay by shaving a neighbouring sentence"), so an appendix must not count as growth. */
|
|
151
|
+
export function split(secs) {
|
|
152
|
+
const i = secs.findIndex((s) => /^references\b/i.test(s.heading));
|
|
153
|
+
return i === -1
|
|
154
|
+
? { body: secs, appendix: [] }
|
|
155
|
+
: { body: secs.slice(0, i), appendix: secs.slice(i) };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const words = (s) => (s.match(/\S+/g) ?? []).length;
|
|
159
|
+
const hedges = (s) => (s.match(HEDGE_RE) ?? []).length;
|
|
160
|
+
|
|
161
|
+
export function census(md) {
|
|
162
|
+
const { body, appendix } = split(sections(md));
|
|
163
|
+
const bodyText = body.map((s) => s.text).join("\n");
|
|
164
|
+
const bw = words(bodyText);
|
|
165
|
+
return {
|
|
166
|
+
sections: body.length + appendix.length,
|
|
167
|
+
bodyWords: bw,
|
|
168
|
+
appendixWords: words(appendix.map((s) => s.text).join("\n")),
|
|
169
|
+
hedges: hedges(bodyText),
|
|
170
|
+
hedgeDensity: bw ? (hedges(bodyText) * 1000) / bw : 0,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Citation markers `[12]` in prose, plus numbered entries in the References list. */
|
|
175
|
+
export function citations(md) {
|
|
176
|
+
const { body, appendix } = split(sections(md));
|
|
177
|
+
const inProse = [
|
|
178
|
+
...stripComments(body.map((s) => s.text).join("\n")).matchAll(
|
|
179
|
+
/\[(\d{1,3})\]/g,
|
|
180
|
+
),
|
|
181
|
+
].map((m) => m[1]);
|
|
182
|
+
const refs = appendix
|
|
183
|
+
.filter((s) => /^references\b/i.test(s.heading))
|
|
184
|
+
.flatMap((s) =>
|
|
185
|
+
[...s.text.matchAll(/^\s*(\d{1,3})\.\s+\S/gm)].map((m) => m[1]),
|
|
186
|
+
);
|
|
187
|
+
return new Set([...inProse, ...refs]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Raw numeric literals in BODY prose — the things ARIS calls `numerical_claim`.
|
|
192
|
+
*
|
|
193
|
+
* Excluded on purpose, and each exclusion is a place this check is deliberately blind:
|
|
194
|
+
* - HTML comments (stripped upstream) — bookkeeping, not claims;
|
|
195
|
+
* - `{{registry.key}}` placeholders — a bound quantity has its own gate (`repro/paper_numbers.py`)
|
|
196
|
+
* which is strictly stronger, being rung 1: a wrong value has no representation there;
|
|
197
|
+
* - `[12]` citation markers — those are the citation check's business;
|
|
198
|
+
* - the leading number of a heading (`### 4.4 …`) — structure, not a claim.
|
|
199
|
+
*/
|
|
200
|
+
export function numbers(md) {
|
|
201
|
+
const { body } = split(sections(md));
|
|
202
|
+
const out = new Set();
|
|
203
|
+
for (const s of body) {
|
|
204
|
+
const t = s.text
|
|
205
|
+
.replace(/\{\{[^}]*\}\}/g, " ")
|
|
206
|
+
.replace(/\[\d{1,3}\]/g, " ");
|
|
207
|
+
for (const m of t.matchAll(/(?<![\w.])\d+(?:[.,]\d+)*\s*%?/g))
|
|
208
|
+
out.add(m[0].replace(/\s+/g, ""));
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── the manifest ─────────────────────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A frontmatter reader for exactly the keys above: scalars, inline `[a, b]` lists, and `- ` lists.
|
|
217
|
+
*
|
|
218
|
+
* Not a YAML parser, and it says so where it fails rather than guessing — a manifest that does not
|
|
219
|
+
* parse is reported as `unreadable-manifest`, never skipped. Four SKILL.md files in this repo
|
|
220
|
+
* shipped frontmatter that no real YAML parser accepts and were read as having no fields at all;
|
|
221
|
+
* silence on a malformed declaration is how a gate becomes decorative.
|
|
222
|
+
*/
|
|
223
|
+
export function parseManifest(src, path) {
|
|
224
|
+
const block = frontmatterBlock(src);
|
|
225
|
+
if (block === null) return { error: "no frontmatter block", path };
|
|
226
|
+
const out = { path, touches: [], allows: [] };
|
|
227
|
+
let key = null;
|
|
228
|
+
for (const raw of block.split("\n")) {
|
|
229
|
+
const line = raw.replace(/\s+#.*$/, "");
|
|
230
|
+
if (!line.trim()) continue;
|
|
231
|
+
const item = /^\s*-\s+(.*\S)\s*$/.exec(line);
|
|
232
|
+
if (item && key) {
|
|
233
|
+
out[key].push(unquote(item[1]));
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const kv = /^([a-zA-Z][\w-]*)\s*:\s*(.*)$/.exec(line);
|
|
237
|
+
if (!kv) continue;
|
|
238
|
+
const [, k, v] = kv;
|
|
239
|
+
if (v.trim() === "") {
|
|
240
|
+
key = k;
|
|
241
|
+
out[k] = out[k] ?? [];
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
key = null;
|
|
245
|
+
const inline = /^\[(.*)\]$/.exec(v.trim());
|
|
246
|
+
out[k] = inline
|
|
247
|
+
? inline[1]
|
|
248
|
+
.split(",")
|
|
249
|
+
.map((x) => unquote(x.trim()))
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
: unquote(v.trim());
|
|
252
|
+
}
|
|
253
|
+
if (!out.base)
|
|
254
|
+
return {
|
|
255
|
+
error:
|
|
256
|
+
"no `base:` — a round with no revision to compare against cannot be checked",
|
|
257
|
+
path,
|
|
258
|
+
};
|
|
259
|
+
if (!Array.isArray(out.touches) || out.touches.length === 0) {
|
|
260
|
+
return {
|
|
261
|
+
error:
|
|
262
|
+
"no `touches:` list — a round that declares nothing authorises nothing",
|
|
263
|
+
path,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
const unquote = (s) => s.replace(/^["']|["']$/g, "");
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Does `entry` cover `heading`? Number prefixes nest (`4` covers `4.1`); text matches on substring.
|
|
272
|
+
*
|
|
273
|
+
* 🔴 A NUMERIC ENTRY ADDRESSES THE SECTION NUMBER AND NOTHING ELSE, and that clause is not tidiness.
|
|
274
|
+
* The first version fell through to the substring rule for numeric entries too, so `touches: ["2"]`
|
|
275
|
+
* matched "Section 12", "2026", and every heading containing the character 2 — a one-character
|
|
276
|
+
* declaration authorising most of the paper, with `overbroad-scope` silent because the LIST was
|
|
277
|
+
* short. Found on 2026-08-10 by mutation, not by reading: disabling the nesting rule changed no
|
|
278
|
+
* verdict, because the substring fallback was quietly doing its job for it.
|
|
279
|
+
*/
|
|
280
|
+
export function covers(entry, heading) {
|
|
281
|
+
if (entry === "*") return true;
|
|
282
|
+
const e = String(entry).trim().toLowerCase();
|
|
283
|
+
const h = heading.trim().toLowerCase();
|
|
284
|
+
const num = /^(\d+(?:\.\d+)*)\.?\s/.exec(h)?.[1];
|
|
285
|
+
if (/^\d+(\.\d+)*$/.test(e))
|
|
286
|
+
return !!num && (num === e || num.startsWith(e + "."));
|
|
287
|
+
return h.includes(e);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ── git ──────────────────────────────────────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
function git(root, ...args) {
|
|
293
|
+
const r = spawnSync("git", args, {
|
|
294
|
+
cwd: root,
|
|
295
|
+
encoding: "utf8",
|
|
296
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
ok: r.status === 0,
|
|
300
|
+
out: r.stdout ?? "",
|
|
301
|
+
err: (r.stderr ?? "").trim(),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── the check ────────────────────────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
export function check(dir, { since } = {}) {
|
|
308
|
+
const findings = [];
|
|
309
|
+
const add = (kind, msg) => findings.push({ kind, msg });
|
|
310
|
+
const paperPath = join(dir, "paper.md");
|
|
311
|
+
if (!existsSync(paperPath))
|
|
312
|
+
return { findings, census: null, notes: [`no paper.md in ${dir}`] };
|
|
313
|
+
|
|
314
|
+
const now = readFileSync(paperPath, "utf8");
|
|
315
|
+
const nowC = census(now);
|
|
316
|
+
const repo = git(dir, "rev-parse", "--show-toplevel").out.trim();
|
|
317
|
+
const rel = repo ? relative(repo, resolve(paperPath)) : null;
|
|
318
|
+
const at = (rev) => {
|
|
319
|
+
if (!repo) return { ok: false, err: "not a git repository" };
|
|
320
|
+
const r = git(repo, "show", `${rev}:${rel}`);
|
|
321
|
+
return r.ok
|
|
322
|
+
? { ok: true, src: r.out }
|
|
323
|
+
: { ok: false, err: r.err || `git show ${rev}:${rel} failed` };
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// ── which round authorises the current state ──
|
|
327
|
+
const roundsDir = join(dir, "rounds");
|
|
328
|
+
const files = existsSync(roundsDir)
|
|
329
|
+
? readdirSync(roundsDir)
|
|
330
|
+
.filter((f) => f.endsWith(".md") && f !== "README.md")
|
|
331
|
+
.sort()
|
|
332
|
+
: [];
|
|
333
|
+
|
|
334
|
+
const manifests = [];
|
|
335
|
+
for (const f of files) {
|
|
336
|
+
const p = join(roundsDir, f);
|
|
337
|
+
const man = parseManifest(readFileSync(p, "utf8"), p);
|
|
338
|
+
if (man.error) {
|
|
339
|
+
add("unreadable-manifest", `${relOf(dir, p)}: ${man.error}`);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
manifests.push(man);
|
|
343
|
+
}
|
|
344
|
+
manifests.sort((a, b) => Number(a.round ?? 0) - Number(b.round ?? 0));
|
|
345
|
+
const open = manifests.filter((m) => !m.closed);
|
|
346
|
+
|
|
347
|
+
if (!since && manifests.length === 0) {
|
|
348
|
+
add(
|
|
349
|
+
"no-round-ledger",
|
|
350
|
+
`no readable round manifest under ${relOf(dir, roundsDir)} — review rounds on this paper are unrecorded, so the ratchet (${nowC.bodyWords} body words, ${nowC.hedgeDensity.toFixed(1)} hedges per 1000) is measured against nothing. Open a round before the next review pass.`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (open.length > 1) {
|
|
354
|
+
add(
|
|
355
|
+
"two-open-rounds",
|
|
356
|
+
`${open.length} manifests have no \`closed:\` (${open.map((m) => `round ${m.round}`).join(", ")}) — with two open rounds every budget below is checked against the wrong base.`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (!since && manifests.length > 0 && open.length === 0) {
|
|
360
|
+
const dirty =
|
|
361
|
+
repo && git(repo, "diff", "--quiet", "HEAD", "--", rel).ok === false;
|
|
362
|
+
if (dirty) {
|
|
363
|
+
add(
|
|
364
|
+
"no-open-round",
|
|
365
|
+
`paper.md differs from HEAD and every round manifest is closed — these edits are authorised by nothing. This is the state that deposited a page of hedging in one session.`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ── pick the base ──
|
|
371
|
+
const active =
|
|
372
|
+
open[open.length - 1] ?? manifests[manifests.length - 1] ?? null;
|
|
373
|
+
const baseRev = since ?? active?.base;
|
|
374
|
+
if (!baseRev) return { findings, census: nowC, notes: [], active: null };
|
|
375
|
+
|
|
376
|
+
const got = at(baseRev);
|
|
377
|
+
if (!got.ok) {
|
|
378
|
+
add(
|
|
379
|
+
"unresolvable-base",
|
|
380
|
+
`cannot read paper.md at \`${baseRev}\`: ${got.err}. A round whose base does not resolve is a round with no gate, and it must not read as a clean run.`,
|
|
381
|
+
);
|
|
382
|
+
return { findings, census: nowC, notes: [], active };
|
|
383
|
+
}
|
|
384
|
+
const base = got.src;
|
|
385
|
+
const baseC = census(base);
|
|
386
|
+
|
|
387
|
+
// ── 1. whole-document: this round's spend ──
|
|
388
|
+
const budget = num(active?.budget, 0);
|
|
389
|
+
const grew = nowC.bodyWords - baseC.bodyWords;
|
|
390
|
+
if (grew > budget) {
|
|
391
|
+
add(
|
|
392
|
+
"ratchet",
|
|
393
|
+
`the body grew ${sign(grew)} words against a declared budget of ${sign(budget)} (${baseC.bodyWords} → ${nowC.bodyWords}); the appendix moved ${sign(nowC.appendixWords - baseC.appendixWords)}. Pay it back with tighten-paper or raise the budget in the manifest before the round, not after.`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ── 2. whole-document, and blind to this round: the compounding case ──
|
|
398
|
+
//
|
|
399
|
+
// ARIS: "a small softening at round 1 + another small softening at round 2 can compound into a
|
|
400
|
+
// meaningful framing change". Every round can sit inside its own budget while the paper drifts a
|
|
401
|
+
// page. This weighs today against the FIRST round's base and against every budget ever declared,
|
|
402
|
+
// so it is the one finding a per-round check cannot produce by construction.
|
|
403
|
+
if (!since && manifests.length > 1) {
|
|
404
|
+
const first = manifests[0];
|
|
405
|
+
const g0 = at(first.base);
|
|
406
|
+
if (g0.ok) {
|
|
407
|
+
const c0 = census(g0.src);
|
|
408
|
+
const total = manifests.reduce((a, m) => a + num(m.budget, 0), 0);
|
|
409
|
+
const cum = nowC.bodyWords - c0.bodyWords;
|
|
410
|
+
if (cum > total) {
|
|
411
|
+
add(
|
|
412
|
+
"ratchet-cumulative",
|
|
413
|
+
`across ${manifests.length} rounds the body grew ${sign(cum)} words against ${sign(total)} of declared budget (${c0.bodyWords} → ${nowC.bodyWords}, from round ${first.round}'s base \`${first.base}\`). Every individual round may have been inside its own budget; this is the drift they compound into.`,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
add(
|
|
418
|
+
"unresolvable-base",
|
|
419
|
+
`round ${first.round}'s base \`${first.base}\` does not resolve (${g0.err}), so cumulative drift across ${manifests.length} rounds is UNMEASURED — not zero.`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ── 3. whole-document: hedge mass ──
|
|
425
|
+
const hb = num(active?.["hedge-budget"], 0);
|
|
426
|
+
const dh = nowC.hedgeDensity - baseC.hedgeDensity;
|
|
427
|
+
if (dh > hb + 1e-9) {
|
|
428
|
+
add(
|
|
429
|
+
"hedge-mass",
|
|
430
|
+
`hedge density rose ${dh.toFixed(2)} per 1000 body words against a budget of ${hb.toFixed(2)} (${baseC.hedgeDensity.toFixed(2)} → ${nowC.hedgeDensity.toFixed(2)}; ${baseC.hedges} → ${nowC.hedges} hedges). The ratchet fix order is TIGHTEN → CUT → Threats → inline hedge; if the round reached step four, say so in \`hedge-budget\`.`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── 4. whole-document: is the declaration a declaration ──
|
|
435
|
+
if (active && !since) {
|
|
436
|
+
const all = [
|
|
437
|
+
...split(sections(now)).body,
|
|
438
|
+
...split(sections(now)).appendix,
|
|
439
|
+
];
|
|
440
|
+
const covered = all.filter((s) =>
|
|
441
|
+
active.touches.some((t) => covers(t, s.heading)),
|
|
442
|
+
);
|
|
443
|
+
if (all.length && covered.length * 2 > all.length) {
|
|
444
|
+
add(
|
|
445
|
+
"overbroad-scope",
|
|
446
|
+
`\`touches:\` covers ${covered.length} of ${all.length} sections — a round that may change most of the paper has declared nothing. Split it into rounds, or state plainly that this is a rewrite and not a review round.`,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ── 5. per-section: what changed outside the declaration ──
|
|
452
|
+
if (active && !since) {
|
|
453
|
+
const before = new Map(sections(base).map((s) => [s.heading, s.text]));
|
|
454
|
+
for (const s of sections(now)) {
|
|
455
|
+
const was = before.get(s.heading);
|
|
456
|
+
if (was === s.text) continue;
|
|
457
|
+
if (active.touches.some((t) => covers(t, s.heading))) continue;
|
|
458
|
+
const how =
|
|
459
|
+
was === undefined
|
|
460
|
+
? "is new"
|
|
461
|
+
: `changed (${sign(words(s.text) - words(was))} words)`;
|
|
462
|
+
add(
|
|
463
|
+
"undeclared-section",
|
|
464
|
+
`"${s.heading}" ${how}, and no entry in \`touches:\` covers it (declared: ${active.touches.join(", ")}).`,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
for (const h of before.keys()) {
|
|
468
|
+
if (sections(now).some((s) => s.heading === h)) continue;
|
|
469
|
+
if (active.touches.some((t) => covers(t, h))) continue;
|
|
470
|
+
add(
|
|
471
|
+
"undeclared-section",
|
|
472
|
+
`"${h}" was removed, and no entry in \`touches:\` covers it (declared: ${active.touches.join(", ")}).`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ── 6. forbidden operations ──
|
|
478
|
+
const allows = new Set(
|
|
479
|
+
(active?.allows ?? []).map((a) => String(a).trim().toLowerCase()),
|
|
480
|
+
);
|
|
481
|
+
for (const op of allows) {
|
|
482
|
+
if (!OPS.includes(op))
|
|
483
|
+
add(
|
|
484
|
+
"unknown-op",
|
|
485
|
+
`\`allows: ${op}\` is not an operation this gate knows (${OPS.join(", ")}) — a permission nothing reads is not a permission.`,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!allows.has("new-citation")) {
|
|
490
|
+
const wasCites = citations(base);
|
|
491
|
+
const added = [...citations(now)].filter((c) => !wasCites.has(c));
|
|
492
|
+
if (added.length) {
|
|
493
|
+
add(
|
|
494
|
+
"unauthorised-citation",
|
|
495
|
+
`${added.length} citation(s) appeared this round (${added.sort((a, b) => a - b).join(", ")}) and the manifest does not list \`new-citation\`. Every added cite must clear verify-citations before it is in the text; a round that adds one without declaring it skips that queue.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (!allows.has("new-number")) {
|
|
500
|
+
const wasNums = numbers(base);
|
|
501
|
+
const added = [...numbers(now)].filter((n) => !wasNums.has(n));
|
|
502
|
+
if (added.length) {
|
|
503
|
+
const show =
|
|
504
|
+
added.slice(0, 8).join(", ") + (added.length > 8 ? `, …` : "");
|
|
505
|
+
add(
|
|
506
|
+
"unauthorised-number",
|
|
507
|
+
`${added.length} numeric literal(s) new to the body this round (${show}) and the manifest does not list \`new-number\`. A number that entered during a review round has no provenance row and no registry key — this is the shape of the 2026-08-05 defect, arriving under cover of a hedge fix.`,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
findings,
|
|
514
|
+
census: nowC,
|
|
515
|
+
base: baseC,
|
|
516
|
+
active,
|
|
517
|
+
baseRev,
|
|
518
|
+
rounds: manifests.length,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const num = (v, d) =>
|
|
523
|
+
v === undefined || v === null || v === "" || isNaN(Number(v)) ? d : Number(v);
|
|
524
|
+
const sign = (n) => (n > 0 ? `+${n}` : String(n));
|
|
525
|
+
const relOf = (dir, p) => relative(resolve(dir, ".."), p) || p;
|
|
526
|
+
|
|
527
|
+
// ── CLI ──────────────────────────────────────────────────────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
function main(argv) {
|
|
530
|
+
const args = argv.slice(2);
|
|
531
|
+
const dir = resolve(args.find((a) => !a.startsWith("--")) ?? ".");
|
|
532
|
+
const asJson = args.includes("--json");
|
|
533
|
+
const since =
|
|
534
|
+
(args.find((a) => a.startsWith("--since=")) ?? "").split("=")[1] ||
|
|
535
|
+
undefined;
|
|
536
|
+
|
|
537
|
+
const r = check(dir, { since });
|
|
538
|
+
if (asJson) {
|
|
539
|
+
console.log(JSON.stringify(r.findings, null, 2));
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (r.census) {
|
|
544
|
+
const c = r.census,
|
|
545
|
+
b = r.base;
|
|
546
|
+
console.log(
|
|
547
|
+
`📏 round-diff — ${dir.split("/").filter(Boolean).pop()}${r.baseRev ? ` against ${r.baseRev}` : ""}`,
|
|
548
|
+
);
|
|
549
|
+
console.log(
|
|
550
|
+
` body ${c.bodyWords}w${b ? ` (${sign(c.bodyWords - b.bodyWords)})` : ""} · appendix ${c.appendixWords}w${b ? ` (${sign(c.appendixWords - b.appendixWords)})` : ""} · hedges ${c.hedgeDensity.toFixed(2)}/1000${b ? ` (${sign(+(c.hedgeDensity - b.hedgeDensity).toFixed(2))})` : ""} · ${c.sections} sections · ${r.rounds ?? 0} round(s) on file`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
for (const n of r.notes ?? []) console.log(` ${n}`);
|
|
554
|
+
|
|
555
|
+
// Always printed, findings or none. A check that speaks only when it fires teaches the reader to
|
|
556
|
+
// hear silence as coverage, and this one has four blind spots big enough to name.
|
|
557
|
+
console.log(
|
|
558
|
+
` SILENT ABOUT: a claim whose strength changed at constant length (that is the claims diff);`,
|
|
559
|
+
);
|
|
560
|
+
console.log(
|
|
561
|
+
` edits made and reverted inside one round; whether a hedge deserved its place;`,
|
|
562
|
+
);
|
|
563
|
+
console.log(
|
|
564
|
+
` every file other than paper.md; and prose inside HTML comments, which is stripped.`,
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
if (!r.findings.length) {
|
|
568
|
+
console.log(` ✅ no finding`);
|
|
569
|
+
return 0;
|
|
570
|
+
}
|
|
571
|
+
console.log(`⚠️ round-diff — ${r.findings.length} finding(s):`);
|
|
572
|
+
for (const f of r.findings) console.log(` [${f.kind}] ${f.msg}`);
|
|
573
|
+
return 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (isMain(import.meta.url)) process.exit(main(process.argv));
|