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,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* consumer.harness.mjs — every rung of `ledgerPath()`, both directions, plus the two path facts
|
|
3
|
+
* the move into a package depends on.
|
|
4
|
+
*
|
|
5
|
+
* `npx vigiles test skills/paper-pipeline/scripts/consumer.harness.mjs`
|
|
6
|
+
*
|
|
7
|
+
* 🔴 WHY THIS FILE IS NOT OPTIONAL. The resolutions in `consumer.mjs` all fail in the SILENT
|
|
8
|
+
* direction when they are wrong: a bad ledger path still accepts appends, a bad main guard still
|
|
9
|
+
* exits 0, a bad root still hashes a missing directory to a stable value. None of them produce an
|
|
10
|
+
* error, and every one of them produces a run that reads as success. So each is asserted here in
|
|
11
|
+
* BOTH directions — fires when it should, silent when it should — because an assertion that only
|
|
12
|
+
* ever sees the good case cannot tell a working resolver from a constant.
|
|
13
|
+
*
|
|
14
|
+
* Assertions run at module top level: `vigiles test` treats "did not throw" as a pass.
|
|
15
|
+
*
|
|
16
|
+
* Parts VIII-X cover the fourth carrier, `scriptsRoot()`. It fails in the same silent direction
|
|
17
|
+
* as the rest and worse: its value is a PREFIX a caller filters prose with, so a wrong one
|
|
18
|
+
* matches nothing and every check built on it reports zero findings.
|
|
19
|
+
*/
|
|
20
|
+
import assert from "node:assert/strict";
|
|
21
|
+
import {
|
|
22
|
+
mkdirSync,
|
|
23
|
+
mkdtempSync,
|
|
24
|
+
readFileSync,
|
|
25
|
+
rmSync,
|
|
26
|
+
symlinkSync,
|
|
27
|
+
writeFileSync,
|
|
28
|
+
realpathSync,
|
|
29
|
+
} from "node:fs";
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { spawnSync } from "node:child_process";
|
|
34
|
+
import {
|
|
35
|
+
CONFIG_KEY,
|
|
36
|
+
DEFAULT_SCRIPTS_ROOT,
|
|
37
|
+
consumerRoot,
|
|
38
|
+
consumerSkillsDir,
|
|
39
|
+
insideNodeModules,
|
|
40
|
+
installedSkills,
|
|
41
|
+
isMain,
|
|
42
|
+
ledgerPath,
|
|
43
|
+
pipelineScripts,
|
|
44
|
+
scriptsRoot,
|
|
45
|
+
PACKAGE_NAME,
|
|
46
|
+
} from "./consumer.mjs";
|
|
47
|
+
|
|
48
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
|
|
50
|
+
// ── THE PACKAGE NAME HAS ONE SOURCE, AND IT MATCHES THE MANIFEST ─────────────────────────────
|
|
51
|
+
// Every path into the installed package is built from PACKAGE_NAME. Two files cannot import it
|
|
52
|
+
// and spell it themselves: the manifest, and the hook wiring (JSON). Both are checked here.
|
|
53
|
+
{
|
|
54
|
+
const root = resolve(HERE, "..", "..", "..");
|
|
55
|
+
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
56
|
+
assert.equal(
|
|
57
|
+
manifest.name,
|
|
58
|
+
PACKAGE_NAME,
|
|
59
|
+
"package.json `name` and PACKAGE_NAME in consumer.mjs must agree",
|
|
60
|
+
);
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
Object.keys(manifest.bin ?? {}),
|
|
63
|
+
[PACKAGE_NAME],
|
|
64
|
+
"the package exposes exactly one command, named like the package",
|
|
65
|
+
);
|
|
66
|
+
const wiring = JSON.parse(
|
|
67
|
+
readFileSync(join(root, "plugin", "hooks", "hooks.json"), "utf8"),
|
|
68
|
+
);
|
|
69
|
+
const commands = Object.values(wiring.hooks ?? {})
|
|
70
|
+
.flat()
|
|
71
|
+
.flatMap((e) => e.hooks ?? [])
|
|
72
|
+
.map((h) => h.command);
|
|
73
|
+
assert.ok(
|
|
74
|
+
commands.length > 0 &&
|
|
75
|
+
commands.every((c) =>
|
|
76
|
+
c.includes(`/node_modules/${PACKAGE_NAME}/bin/rpp.mjs`),
|
|
77
|
+
),
|
|
78
|
+
`every command in plugin/hooks/hooks.json runs node_modules/${PACKAGE_NAME}/bin/rpp.mjs`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const TMP = realpathSync(mkdtempSync(join(tmpdir(), "consumer-harness-")));
|
|
82
|
+
process.on("exit", () => rmSync(TMP, { recursive: true, force: true }));
|
|
83
|
+
|
|
84
|
+
/** A throwaway consumer repository with the given `paperlint` block (or none). */
|
|
85
|
+
function fakeConsumer(block) {
|
|
86
|
+
const root = mkdtempSync(join(TMP, "repo-"));
|
|
87
|
+
writeFileSync(
|
|
88
|
+
join(root, "package.json"),
|
|
89
|
+
JSON.stringify(
|
|
90
|
+
block === undefined ? { name: "x" } : { name: "x", [CONFIG_KEY]: block },
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
return root;
|
|
94
|
+
}
|
|
95
|
+
/** A directory that looks like an installed copy of this package. */
|
|
96
|
+
function installedDir(root) {
|
|
97
|
+
const d = join(root, "node_modules", "paperlint", "skills", "pp", "scripts");
|
|
98
|
+
mkdirSync(d, { recursive: true });
|
|
99
|
+
return d;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── I. RUNG 1 — the environment wins, and it wins over a declaration ─────────────────────────
|
|
103
|
+
{
|
|
104
|
+
const root = fakeConsumer({ ledger: "declared/here.jsonl" });
|
|
105
|
+
const env = { PIPELINE_LEDGER: join(root, "from-env.jsonl") };
|
|
106
|
+
assert.equal(
|
|
107
|
+
ledgerPath(installedDir(root), { env, cwd: root }),
|
|
108
|
+
resolve(join(root, "from-env.jsonl")),
|
|
109
|
+
"PIPELINE_LEDGER did not win. It must outrank everything: harnesses set it BEFORE importing " +
|
|
110
|
+
"the ledger precisely so fixture rows cannot reach real history, and a declaration on disk " +
|
|
111
|
+
"that could override it would put them there.",
|
|
112
|
+
);
|
|
113
|
+
// The other direction: with the variable gone, the same call must move to the next rung.
|
|
114
|
+
assert.equal(
|
|
115
|
+
ledgerPath(installedDir(root), { env: {}, cwd: root }),
|
|
116
|
+
resolve(root, "declared/here.jsonl"),
|
|
117
|
+
"without PIPELINE_LEDGER the declaration must be used — otherwise rung 1 is not a rung, it " +
|
|
118
|
+
"is the only path, and this assertion pair proves nothing about precedence.",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── II. RUNG 2 — the consumer's declaration, resolved against the consumer root ───────────────
|
|
123
|
+
{
|
|
124
|
+
const root = fakeConsumer({ ledger: "docs/pipeline-runs.jsonl" });
|
|
125
|
+
assert.equal(
|
|
126
|
+
ledgerPath(installedDir(root), { env: {}, cwd: root }),
|
|
127
|
+
join(root, "docs/pipeline-runs.jsonl"),
|
|
128
|
+
"a declared relative ledger must resolve against the consumer root, not against the cwd of " +
|
|
129
|
+
"whatever process happened to start",
|
|
130
|
+
);
|
|
131
|
+
// 🔴 `null` is a keystroke, not an absence. `?? DEFAULT` would read it as "nothing declared"
|
|
132
|
+
// and silently use another file — the same distinction `papersRoot()` makes.
|
|
133
|
+
assert.throws(
|
|
134
|
+
() =>
|
|
135
|
+
ledgerPath(installedDir(root), {
|
|
136
|
+
env: {},
|
|
137
|
+
cwd: fakeConsumer({ ledger: null }),
|
|
138
|
+
}),
|
|
139
|
+
/must be a non-empty string/,
|
|
140
|
+
'a `"ledger": null` was accepted. Anything written down must be usable; only ABSENCE may ' +
|
|
141
|
+
"fall through to the next rung.",
|
|
142
|
+
);
|
|
143
|
+
assert.throws(
|
|
144
|
+
() =>
|
|
145
|
+
ledgerPath(installedDir(root), {
|
|
146
|
+
env: {},
|
|
147
|
+
cwd: fakeConsumer({ ledger: "" }),
|
|
148
|
+
}),
|
|
149
|
+
/must be a non-empty string/,
|
|
150
|
+
"an empty declared ledger was accepted; it resolves to the consumer root itself",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── III. RUNG 3 — beside the file, but NEVER inside node_modules ──────────────────────────────
|
|
155
|
+
{
|
|
156
|
+
const root = fakeConsumer(undefined);
|
|
157
|
+
// Developing the package in its own checkout: the default is legitimate.
|
|
158
|
+
const own = mkdtempSync(join(TMP, "checkout-"));
|
|
159
|
+
assert.equal(
|
|
160
|
+
ledgerPath(own, { env: {}, cwd: root }),
|
|
161
|
+
join(own, "runs.jsonl"),
|
|
162
|
+
"outside node_modules and with nothing declared, the ledger belongs beside the module",
|
|
163
|
+
);
|
|
164
|
+
// Installed as a dependency with nothing declared: refusing is the whole point.
|
|
165
|
+
let err;
|
|
166
|
+
try {
|
|
167
|
+
ledgerPath(installedDir(root), { env: {}, cwd: root });
|
|
168
|
+
} catch (e) {
|
|
169
|
+
err = e;
|
|
170
|
+
}
|
|
171
|
+
assert.ok(
|
|
172
|
+
err,
|
|
173
|
+
"🔴 ledgerPath RETURNED A PATH INSIDE node_modules. Appending there SUCCEEDS, so the rows " +
|
|
174
|
+
"look recorded until the next `npm ci` deletes them — silent data loss with no error at " +
|
|
175
|
+
"any point. This must throw.",
|
|
176
|
+
);
|
|
177
|
+
assert.match(
|
|
178
|
+
err.message,
|
|
179
|
+
/"ledger"/,
|
|
180
|
+
"the refusal must name the key to declare. An error that states the problem without the cure " +
|
|
181
|
+
"gets worked around by whoever hits it.",
|
|
182
|
+
);
|
|
183
|
+
assert.match(
|
|
184
|
+
err.message,
|
|
185
|
+
/package\.json/,
|
|
186
|
+
"the refusal must name the file the key goes in",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── IV. `insideNodeModules` is SEGMENT-WISE, not a substring ──────────────────────────────────
|
|
191
|
+
assert.equal(
|
|
192
|
+
insideNodeModules(join("a", "node_modules", "b")),
|
|
193
|
+
true,
|
|
194
|
+
"a real install went undetected",
|
|
195
|
+
);
|
|
196
|
+
assert.equal(
|
|
197
|
+
insideNodeModules(join("a", "my-node_modules-inspector", "b")),
|
|
198
|
+
false,
|
|
199
|
+
"a directory whose NAME merely contains `node_modules` was treated as an install — that " +
|
|
200
|
+
"consumer would be refused its own ledger for a naming coincidence",
|
|
201
|
+
);
|
|
202
|
+
assert.equal(
|
|
203
|
+
insideNodeModules(join("a", "b")),
|
|
204
|
+
false,
|
|
205
|
+
"a plain path was reported as installed",
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// ── V. `consumerRoot` prefers the harness-provided root over the cwd ──────────────────────────
|
|
209
|
+
assert.equal(
|
|
210
|
+
consumerRoot({ env: { CLAUDE_PROJECT_DIR: "/x/y" }, cwd: "/somewhere/else" }),
|
|
211
|
+
"/x/y",
|
|
212
|
+
"CLAUDE_PROJECT_DIR must win: a hook or an editor can start the process anywhere, and the cwd " +
|
|
213
|
+
"then names the wrong repository",
|
|
214
|
+
);
|
|
215
|
+
assert.equal(
|
|
216
|
+
consumerRoot({ env: {}, cwd: "/somewhere/else" }),
|
|
217
|
+
"/somewhere/else",
|
|
218
|
+
"without the variable the cwd is the answer — every documented invocation is typed from the root",
|
|
219
|
+
);
|
|
220
|
+
assert.equal(
|
|
221
|
+
consumerSkillsDir({ env: {}, cwd: "/r" }),
|
|
222
|
+
join("/r", ".claude", "skills"),
|
|
223
|
+
"the skills directory must hang off the consumer root",
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// ── VI. 🔴 `isMain` THROUGH A SYMLINK — the measurement the whole move rests on ────────────────
|
|
227
|
+
// Node resolves the entry point to its REALPATH but leaves `process.argv[1]` as typed. The idiom
|
|
228
|
+
// `import.meta.url === `file://${process.argv[1]}`` is therefore FALSE when a script is reached
|
|
229
|
+
// through a symlink — and false silently: the process exits 0 having run no CLI at all. Nine
|
|
230
|
+
// scripts in this directory are reached exactly that way by a consumer.
|
|
231
|
+
{
|
|
232
|
+
const root = mkdtempSync(join(TMP, "symlink-"));
|
|
233
|
+
const real = join(root, "node_modules", "pkg", "scripts");
|
|
234
|
+
mkdirSync(real, { recursive: true });
|
|
235
|
+
const link = join(root, "linked");
|
|
236
|
+
symlinkSync(real, link, "dir");
|
|
237
|
+
|
|
238
|
+
const probe = join(real, "probe.mjs");
|
|
239
|
+
writeFileSync(
|
|
240
|
+
probe,
|
|
241
|
+
`import { isMain } from ${JSON.stringify(join(HERE, "consumer.mjs"))};\n` +
|
|
242
|
+
"const legacy = import.meta.url === `file://${process.argv[1]}`;\n" +
|
|
243
|
+
"console.log(JSON.stringify({ isMain: isMain(import.meta.url), legacy }));\n",
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const read = (p) => {
|
|
247
|
+
const r = spawnSync(process.execPath, [p], { encoding: "utf8" });
|
|
248
|
+
assert.equal(r.status, 0, `probe failed: ${r.stderr}`);
|
|
249
|
+
return JSON.parse(r.stdout);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const direct = read(probe);
|
|
253
|
+
const viaLink = read(join(link, "probe.mjs"));
|
|
254
|
+
|
|
255
|
+
assert.equal(
|
|
256
|
+
direct.isMain,
|
|
257
|
+
true,
|
|
258
|
+
"isMain was false when the file WAS run directly",
|
|
259
|
+
);
|
|
260
|
+
assert.equal(
|
|
261
|
+
viaLink.isMain,
|
|
262
|
+
true,
|
|
263
|
+
"🔴 isMain is false through a symlink — every CLI in this directory would exit 0 doing " +
|
|
264
|
+
"nothing when a consumer runs it at the path its own skills document.",
|
|
265
|
+
);
|
|
266
|
+
// The other half, and it is what makes the case above worth asserting rather than assuming:
|
|
267
|
+
// the idiom this replaced really does break here. If this ever passes, the measurement that
|
|
268
|
+
// justifies `isMain` has changed and the function can go.
|
|
269
|
+
assert.equal(
|
|
270
|
+
viaLink.legacy,
|
|
271
|
+
false,
|
|
272
|
+
"the legacy `file://${argv[1]}` guard now WORKS through a symlink. Node's behaviour changed; " +
|
|
273
|
+
"re-measure before keeping isMain, because its whole reason to exist was this line.",
|
|
274
|
+
);
|
|
275
|
+
assert.equal(
|
|
276
|
+
direct.legacy,
|
|
277
|
+
true,
|
|
278
|
+
"the legacy guard failed even directly — the probe is wrong",
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── VII. imported, not executed ───────────────────────────────────────────────────────────────
|
|
283
|
+
assert.equal(
|
|
284
|
+
isMain(import.meta.url) && !process.argv[1].endsWith("consumer.harness.mjs"),
|
|
285
|
+
false,
|
|
286
|
+
"isMain claimed a module was the entry point while something else was",
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// ── VIII. THE FOURTH CARRIER — declared wins, absent falls back to the default ────────────────
|
|
290
|
+
// Both directions in one block on purpose: a resolver that returned the declaration for every
|
|
291
|
+
// input and a resolver that returned the default for every input each pass HALF of this pair,
|
|
292
|
+
// and half of a pair is a constant wearing a function's name.
|
|
293
|
+
{
|
|
294
|
+
const declaredRoot = fakeConsumer({ scripts: "tools/pipeline" });
|
|
295
|
+
mkdirSync(join(declaredRoot, "tools", "pipeline"), { recursive: true });
|
|
296
|
+
|
|
297
|
+
// 🔴 RELATIVE, AND `/`-SEPARATED, ASSERTED BEFORE THE EQUALITIES BELOW — and the order is a
|
|
298
|
+
// finding, not a preference. `assert` aborts at the first failure, so with this line placed
|
|
299
|
+
// after them a resolver that returned an absolute path died on "must be returned as written":
|
|
300
|
+
// the mutation run reported RED-but-a-DIFFERENT-case, i.e. this assertion was never reached and
|
|
301
|
+
// nothing showed it can fail. The narrow property goes first; the equalities then cover the rest.
|
|
302
|
+
// The value is compared against text a human typed inside a SKILL.md, so an absolute path makes
|
|
303
|
+
// every `startsWith` false — not an error, an empty loop body.
|
|
304
|
+
assert.equal(
|
|
305
|
+
isAbsolute(scriptsRoot({ env: {}, cwd: declaredRoot })),
|
|
306
|
+
false,
|
|
307
|
+
"scriptsRoot returned an ABSOLUTE path. Callers match it against prose, so every comparison " +
|
|
308
|
+
"would be false and every check built on it would silently examine nothing.",
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
assert.equal(
|
|
312
|
+
scriptsRoot({ env: {}, cwd: declaredRoot }),
|
|
313
|
+
"tools/pipeline",
|
|
314
|
+
"a declared scripts path must be returned as written",
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const defaultRoot = fakeConsumer(undefined);
|
|
318
|
+
mkdirSync(join(defaultRoot, DEFAULT_SCRIPTS_ROOT), { recursive: true });
|
|
319
|
+
assert.equal(
|
|
320
|
+
scriptsRoot({ env: {}, cwd: defaultRoot }),
|
|
321
|
+
DEFAULT_SCRIPTS_ROOT,
|
|
322
|
+
"with nothing declared the customary location must be used — otherwise every consumer that " +
|
|
323
|
+
"keeps the symlink where the prose already looks would have to say so",
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// CLAUDE_PROJECT_DIR outranks the cwd here too, for the reason it does in consumerRoot: a hook
|
|
327
|
+
// or an editor starts the process wherever it likes.
|
|
328
|
+
assert.equal(
|
|
329
|
+
scriptsRoot({
|
|
330
|
+
env: { CLAUDE_PROJECT_DIR: declaredRoot },
|
|
331
|
+
cwd: defaultRoot,
|
|
332
|
+
}),
|
|
333
|
+
"tools/pipeline",
|
|
334
|
+
"the declaration must be read from CLAUDE_PROJECT_DIR when it is set, not from the cwd",
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ── IX. THE FOURTH CARRIER — the two refusals, each checked for its CURE ──────────────────────
|
|
339
|
+
{
|
|
340
|
+
// 1. A value that resolves inside node_modules. This is the tempting wrong fix after the move,
|
|
341
|
+
// and it is how the DEFAULT fails as well: with the process started inside the installed
|
|
342
|
+
// package and CLAUDE_PROJECT_DIR unset, the consumer root is itself under node_modules.
|
|
343
|
+
const root = fakeConsumer({
|
|
344
|
+
scripts: "node_modules/paperlint/skills/pp/scripts",
|
|
345
|
+
});
|
|
346
|
+
mkdirSync(
|
|
347
|
+
join(root, "node_modules", "paperlint", "skills", "pp", "scripts"),
|
|
348
|
+
{
|
|
349
|
+
recursive: true,
|
|
350
|
+
},
|
|
351
|
+
);
|
|
352
|
+
let err;
|
|
353
|
+
try {
|
|
354
|
+
scriptsRoot({ env: {}, cwd: root });
|
|
355
|
+
} catch (e) {
|
|
356
|
+
err = e;
|
|
357
|
+
}
|
|
358
|
+
assert.ok(
|
|
359
|
+
err,
|
|
360
|
+
"🔴 scriptsRoot ACCEPTED a path inside node_modules — and the directory exists, so nothing " +
|
|
361
|
+
"else would have complained. `npm ci` deletes that tree, and a skill's prose would name a " +
|
|
362
|
+
"path the repository does not track.",
|
|
363
|
+
);
|
|
364
|
+
assert.match(
|
|
365
|
+
err.message,
|
|
366
|
+
/node_modules/,
|
|
367
|
+
"the refusal must say what is wrong with the path",
|
|
368
|
+
);
|
|
369
|
+
assert.match(
|
|
370
|
+
err.message,
|
|
371
|
+
/symlink/,
|
|
372
|
+
"the refusal must carry the cure — the symlink is what keeps the documented path working",
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// The same failure reached through the DEFAULT: nothing declared, root under node_modules.
|
|
376
|
+
const installed = installedDir(fakeConsumer(undefined));
|
|
377
|
+
mkdirSync(join(installed, DEFAULT_SCRIPTS_ROOT), { recursive: true });
|
|
378
|
+
assert.throws(
|
|
379
|
+
() => scriptsRoot({ env: {}, cwd: installed }),
|
|
380
|
+
/node_modules/,
|
|
381
|
+
"the default resolved under node_modules and was accepted. A consumer root inside an " +
|
|
382
|
+
"installed package is the one case where the default is wrong, and it is silent.",
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// 2. A value that is not on disk at all — the wrong-prefix case, which produces no findings
|
|
386
|
+
// rather than wrong ones.
|
|
387
|
+
let missing;
|
|
388
|
+
try {
|
|
389
|
+
scriptsRoot({ env: {}, cwd: fakeConsumer({ scripts: "tools/nope" }) });
|
|
390
|
+
} catch (e) {
|
|
391
|
+
missing = e;
|
|
392
|
+
}
|
|
393
|
+
assert.ok(
|
|
394
|
+
missing,
|
|
395
|
+
"🔴 scriptsRoot accepted a path that does not exist. Callers use it as a PREFIX: a wrong one " +
|
|
396
|
+
"matches no instruction, so every check reports zero findings and exits 0 — byte-identical " +
|
|
397
|
+
"to a corpus that was examined and passed.",
|
|
398
|
+
);
|
|
399
|
+
assert.match(
|
|
400
|
+
missing.message,
|
|
401
|
+
/"scripts"/,
|
|
402
|
+
"the refusal must name the key to fix",
|
|
403
|
+
);
|
|
404
|
+
assert.match(
|
|
405
|
+
missing.message,
|
|
406
|
+
/package\.json/,
|
|
407
|
+
"the refusal must name the file the key goes in",
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// And the default's version of that message must say the default was used — otherwise someone
|
|
411
|
+
// who declared nothing goes looking in package.json for a line that is not there.
|
|
412
|
+
assert.throws(
|
|
413
|
+
() => scriptsRoot({ env: {}, cwd: fakeConsumer(undefined) }),
|
|
414
|
+
/Nothing was declared/,
|
|
415
|
+
"a missing default must say it WAS the default; the declared-value message sends the reader " +
|
|
416
|
+
"to a key that does not exist",
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// 3. Anything written down must be usable; only ABSENCE may fall through to the default.
|
|
420
|
+
assert.throws(
|
|
421
|
+
() => scriptsRoot({ env: {}, cwd: fakeConsumer({ scripts: null }) }),
|
|
422
|
+
/must be a non-empty string/,
|
|
423
|
+
'a `"scripts": null` was accepted. `?? DEFAULT` reads an explicit null as an absence, which ' +
|
|
424
|
+
"is a typed keystroke being ignored.",
|
|
425
|
+
);
|
|
426
|
+
assert.throws(
|
|
427
|
+
() => scriptsRoot({ env: {}, cwd: fakeConsumer({ scripts: "" }) }),
|
|
428
|
+
/must be a non-empty string/,
|
|
429
|
+
"an empty scripts path was accepted; it resolves to the consumer root itself, so the prefix " +
|
|
430
|
+
"would match every command in every skill",
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── X. THE SCRIPT NAMES ARE DERIVED, NOT RESTATED ─────────────────────────────────────────────
|
|
435
|
+
// The names belong to this package; a consumer that spelled them out would keep its copy in step
|
|
436
|
+
// by hand. The trailing slash on `prefix` is load-bearing: without it the prefix also matches a
|
|
437
|
+
// SIBLING directory whose name merely starts with the root's.
|
|
438
|
+
{
|
|
439
|
+
const s = pipelineScripts("a/b");
|
|
440
|
+
// The boundary is asserted BEFORE the literal equality for the same reason part VIII reorders:
|
|
441
|
+
// `assert` aborts at the first failure, and a prefix that lost its slash died on "must end in a
|
|
442
|
+
// separator" — leaving the assertion that states WHY the slash matters unreached and unproven.
|
|
443
|
+
assert.equal(
|
|
444
|
+
"a/b-other/ledger.mjs".startsWith(s.prefix),
|
|
445
|
+
false,
|
|
446
|
+
"the prefix matched a sibling directory sharing the root's name — drop the trailing slash " +
|
|
447
|
+
"and every such path is mistaken for a pipeline script",
|
|
448
|
+
);
|
|
449
|
+
assert.equal(
|
|
450
|
+
"a/b/ledger.mjs".startsWith(s.prefix),
|
|
451
|
+
true,
|
|
452
|
+
"the prefix failed on a real member",
|
|
453
|
+
);
|
|
454
|
+
assert.equal(s.prefix, "a/b/", "prefix must end in a separator");
|
|
455
|
+
assert.equal(s.announce, "a/b/announce.mjs");
|
|
456
|
+
assert.equal(s.ledger, "a/b/ledger.mjs");
|
|
457
|
+
|
|
458
|
+
// EXACTLY ONE slash, whatever the consumer typed. `scriptsRoot()` returns the declaration
|
|
459
|
+
// verbatim, so a trailing slash in package.json arrives here intact; `${root}/` would then give
|
|
460
|
+
// `a/b//`, which matches nothing — the silent direction again.
|
|
461
|
+
const typedSlash = pipelineScripts("a/b/");
|
|
462
|
+
assert.equal(
|
|
463
|
+
typedSlash.prefix,
|
|
464
|
+
"a/b/",
|
|
465
|
+
'a declared trailing slash doubled the separator. `"scripts": "tools/pipeline/"` is a normal ' +
|
|
466
|
+
"thing to write, and the doubled prefix matches no instruction at all.",
|
|
467
|
+
);
|
|
468
|
+
assert.equal(
|
|
469
|
+
typedSlash.ledger,
|
|
470
|
+
"a/b/ledger.mjs",
|
|
471
|
+
"the script paths doubled the separator too",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ── XI. WHICH SKILLS ARE INSTALLED — a link counts, a dangling link is REFUSED (rpp#62) ────────
|
|
476
|
+
// `paperlint init` installs every skill as a SYMLINK `.claude/skills/<name> -> …/skills/<name>`. A
|
|
477
|
+
// `Dirent` from `readdirSync(…, { withFileTypes: true })` describes the entry ITSELF and does not
|
|
478
|
+
// follow links, so `e.isDirectory()` is false for every one of them: the eval preflights that
|
|
479
|
+
// asked this question that way saw ZERO installed skills in every consumer and refused to start.
|
|
480
|
+
// The tree below is built on disk, not faked — the property under test is how the filesystem
|
|
481
|
+
// answers, and a stub would test the stub.
|
|
482
|
+
{
|
|
483
|
+
const home = join(TMP, "skills-home");
|
|
484
|
+
const store = join(TMP, "skills-store");
|
|
485
|
+
const skill = (dir, name) => {
|
|
486
|
+
mkdirSync(join(dir, name), { recursive: true });
|
|
487
|
+
writeFileSync(join(dir, name, "SKILL.md"), `---\nname: ${name}\n---\n`);
|
|
488
|
+
};
|
|
489
|
+
mkdirSync(home, { recursive: true });
|
|
490
|
+
skill(home, "real-dir"); // an ordinary directory
|
|
491
|
+
skill(store, "linked"); // the shape `paperlint init` makes: a RELATIVE link
|
|
492
|
+
symlinkSync(
|
|
493
|
+
join("..", "skills-store", "linked"),
|
|
494
|
+
join(home, "linked"),
|
|
495
|
+
"dir",
|
|
496
|
+
);
|
|
497
|
+
mkdirSync(join(home, "not-a-skill")); // a directory without SKILL.md
|
|
498
|
+
mkdirSync(join(store, "linked-not-a-skill"));
|
|
499
|
+
symlinkSync(
|
|
500
|
+
join(store, "linked-not-a-skill"),
|
|
501
|
+
join(home, "linked-not-a-skill"),
|
|
502
|
+
"dir",
|
|
503
|
+
);
|
|
504
|
+
writeFileSync(join(home, "README.md"), "a file, not a skill\n");
|
|
505
|
+
|
|
506
|
+
const names = installedSkills(home);
|
|
507
|
+
assert.ok(
|
|
508
|
+
names.includes("linked"),
|
|
509
|
+
"a SYMLINKED skill was not counted as installed. This is rpp#62: `paperlint init` installs every " +
|
|
510
|
+
"skill as a link, so a reader that does not follow links sees none of them in any consumer.",
|
|
511
|
+
);
|
|
512
|
+
assert.deepEqual(
|
|
513
|
+
names,
|
|
514
|
+
["linked", "real-dir"],
|
|
515
|
+
"installedSkills must return exactly the entries that lead to a directory holding SKILL.md, " +
|
|
516
|
+
"sorted — a directory or link without SKILL.md, or a plain file, is not a skill",
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// 🔴 The dangling link: its target is gone (a skill dropped by an upgrade, a moved store). It is
|
|
520
|
+
// neither skipped — that would read as "not installed", sending the reader after the wrong
|
|
521
|
+
// cause — nor counted, which would hand a caller a skill whose SKILL.md cannot be opened.
|
|
522
|
+
symlinkSync(join("..", "skills-store", "gone"), join(home, "gone"), "dir");
|
|
523
|
+
let err;
|
|
524
|
+
try {
|
|
525
|
+
installedSkills(home);
|
|
526
|
+
} catch (e) {
|
|
527
|
+
err = e;
|
|
528
|
+
}
|
|
529
|
+
assert.ok(
|
|
530
|
+
err,
|
|
531
|
+
"a DANGLING skill link was silently accepted. It must be refused by name — skipped, it reads " +
|
|
532
|
+
"as a skill that was never installed; counted, it is a skill whose SKILL.md cannot be read.",
|
|
533
|
+
);
|
|
534
|
+
assert.equal(err.code, "DANGLING_SKILL_LINK", `wrong error: ${err.message}`);
|
|
535
|
+
assert.deepEqual(
|
|
536
|
+
err.dangling.map((d) => d.name),
|
|
537
|
+
["gone"],
|
|
538
|
+
"the refusal must name exactly the dangling entries",
|
|
539
|
+
);
|
|
540
|
+
assert.match(
|
|
541
|
+
err.message,
|
|
542
|
+
/gone -> \.\.[/\\]skills-store[/\\]gone/,
|
|
543
|
+
"the refusal must say where the dangling link points — that is the cure's first half",
|
|
544
|
+
);
|
|
545
|
+
// The other direction: remove it and the same directory answers again.
|
|
546
|
+
rmSync(join(home, "gone"));
|
|
547
|
+
assert.deepEqual(installedSkills(home), ["linked", "real-dir"]);
|
|
548
|
+
|
|
549
|
+
// A link that cannot be resolved for another reason (a loop) is refused the same way.
|
|
550
|
+
symlinkSync("loop", join(home, "loop"));
|
|
551
|
+
assert.throws(
|
|
552
|
+
() => installedSkills(home),
|
|
553
|
+
(e) => e.code === "DANGLING_SKILL_LINK" && /loop/.test(e.message),
|
|
554
|
+
"a link that cannot be resolved (ELOOP) was not refused",
|
|
555
|
+
);
|
|
556
|
+
rmSync(join(home, "loop"));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
console.log(
|
|
560
|
+
"✓ consumer: three rungs each proved in both directions, node_modules refused, symlinked main guard held, " +
|
|
561
|
+
"scripts root declared/default/refused, installed skills follow links and refuse dangling ones",
|
|
562
|
+
);
|