paperlint 3.2.0 → 4.1.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/workflows/ci.yml +16 -23
- package/CLAUDE.md +33 -13
- package/CONTRIBUTING.md +23 -7
- package/README.md +11 -8
- package/dist/adapters/banal/failure.d.ts +1 -7
- package/dist/adapters/banal/failure.d.ts.map +1 -1
- package/dist/adapters/banal/failure.js +5 -7
- package/dist/adapters/banal/failure.js.map +1 -1
- package/dist/adapters/banal/index.d.ts.map +1 -1
- package/dist/adapters/banal/index.js +8 -11
- package/dist/adapters/banal/index.js.map +1 -1
- package/dist/adapters/banal/install.d.ts.map +1 -1
- package/dist/adapters/banal/install.js +4 -3
- package/dist/adapters/banal/install.js.map +1 -1
- package/dist/adapters/banal/invocation.d.ts +10 -5
- package/dist/adapters/banal/invocation.d.ts.map +1 -1
- package/dist/adapters/banal/invocation.js +13 -1
- package/dist/adapters/banal/invocation.js.map +1 -1
- package/dist/adapters/banal/locate.d.ts +12 -9
- package/dist/adapters/banal/locate.d.ts.map +1 -1
- package/dist/adapters/banal/locate.js +19 -12
- package/dist/adapters/banal/locate.js.map +1 -1
- package/dist/adapters/banal/run.d.ts +10 -1
- package/dist/adapters/banal/run.d.ts.map +1 -1
- package/dist/adapters/banal/run.js +8 -4
- package/dist/adapters/banal/run.js.map +1 -1
- package/dist/adapters/banal/settings.d.ts +1 -1
- package/dist/adapters/banal/settings.d.ts.map +1 -1
- package/dist/adapters/banal/settings.js +2 -1
- package/dist/adapters/banal/settings.js.map +1 -1
- package/dist/adapters/latex/document-class.d.ts +22 -0
- package/dist/adapters/latex/document-class.d.ts.map +1 -0
- package/dist/adapters/latex/document-class.js +102 -0
- package/dist/adapters/latex/document-class.js.map +1 -0
- package/dist/adapters/latex/hidden.d.ts +16 -0
- package/dist/adapters/latex/hidden.d.ts.map +1 -0
- package/dist/adapters/latex/hidden.js +49 -0
- package/dist/adapters/latex/hidden.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +29 -0
- package/dist/adapters/latex/index.d.ts.map +1 -0
- package/dist/adapters/latex/index.js +18 -0
- package/dist/adapters/latex/index.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts +54 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -0
- package/dist/adapters/latex/nodes.js +93 -0
- package/dist/adapters/latex/nodes.js.map +1 -0
- package/dist/adapters/latex/outline.d.ts +10 -0
- package/dist/adapters/latex/outline.d.ts.map +1 -0
- package/dist/adapters/latex/outline.js +39 -0
- package/dist/adapters/latex/outline.js.map +1 -0
- package/dist/adapters/latex/parse.d.ts +42 -0
- package/dist/adapters/latex/parse.d.ts.map +1 -0
- package/dist/adapters/latex/parse.js +94 -0
- package/dist/adapters/latex/parse.js.map +1 -0
- package/dist/adapters/latex/rendered.d.ts +10 -0
- package/dist/adapters/latex/rendered.d.ts.map +1 -0
- package/dist/adapters/latex/rendered.js +39 -0
- package/dist/adapters/latex/rendered.js.map +1 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -2
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/process.io.d.ts +6 -3
- package/dist/adapters/node/process.io.d.ts.map +1 -1
- package/dist/adapters/node/process.io.js.map +1 -1
- package/dist/adapters/node/workspace.io.d.ts.map +1 -1
- package/dist/adapters/node/workspace.io.js +4 -2
- package/dist/adapters/node/workspace.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +12 -12
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +4 -2
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/build-engine.d.ts +1 -1
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +8 -1
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +2 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +9 -10
- package/dist/build.js.map +1 -1
- package/dist/caller-path.d.ts +3 -0
- package/dist/caller-path.d.ts.map +1 -0
- package/dist/caller-path.js +13 -0
- package/dist/caller-path.js.map +1 -0
- package/dist/cli.d.ts +10 -17
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +91 -17
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +12 -3
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +2 -1
- package/dist/doctor.js.map +1 -1
- package/dist/domain/paths.d.ts +8 -2
- package/dist/domain/paths.d.ts.map +1 -1
- package/dist/domain/paths.js +12 -1
- package/dist/domain/paths.js.map +1 -1
- package/dist/domain/ready.d.ts +24 -0
- package/dist/domain/ready.d.ts.map +1 -0
- package/dist/domain/ready.js +11 -0
- package/dist/domain/ready.js.map +1 -0
- package/dist/domain/record.d.ts +8 -0
- package/dist/domain/record.d.ts.map +1 -0
- package/dist/domain/record.js +8 -0
- package/dist/domain/record.js.map +1 -0
- package/dist/domain/sha256.d.ts.map +1 -1
- package/dist/domain/sha256.js +4 -2
- package/dist/domain/sha256.js.map +1 -1
- package/dist/domain/tex-document.d.ts +84 -0
- package/dist/domain/tex-document.d.ts.map +1 -0
- package/dist/domain/tex-document.js +43 -0
- package/dist/domain/tex-document.js.map +1 -0
- package/dist/engine.d.ts +23 -22
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js.map +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +1 -1
- package/dist/eslint-rules/bib-reachable-entry.js.map +1 -1
- package/dist/eslint-rules/install-path-literals.d.ts +2 -2
- package/dist/eslint-rules/install-path-literals.js.map +1 -1
- package/dist/eslint-rules/paper-research-question.d.ts +1 -1
- package/dist/eslint-rules/paper-research-question.js.map +1 -1
- package/dist/eslint-rules/paper-stages.d.ts +2 -2
- package/dist/eslint-rules/paper-stages.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +6 -6
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -1
- package/dist/eslint-rules/review-frontmatter.d.ts +2 -2
- package/dist/eslint-rules/review-frontmatter.js.map +1 -1
- package/dist/eslint-rules/sibling-frontmatter.d.ts +1 -1
- package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -1
- package/dist/eslint-rules/temp-root-realpath.d.ts +1 -1
- package/dist/eslint-rules/temp-root-realpath.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts +2 -2
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +31 -28
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +17 -2
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +3 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +18 -8
- package/dist/init.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +1 -5
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/new-paper.d.ts +5 -0
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +8 -1
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts +5 -0
- package/dist/package-dirs.d.ts.map +1 -0
- package/dist/package-dirs.js +14 -0
- package/dist/package-dirs.js.map +1 -0
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +6 -6
- package/dist/paper-files.js.map +1 -1
- package/dist/paper-settings.js +3 -3
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-facts.d.ts +51 -1
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +23 -15
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +2 -3
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/ports/latex.d.ts +24 -0
- package/dist/ports/latex.d.ts.map +1 -0
- package/dist/ports/latex.js +2 -0
- package/dist/ports/latex.js.map +1 -0
- package/dist/ports/tool-installer.d.ts +2 -14
- package/dist/ports/tool-installer.d.ts.map +1 -1
- package/dist/presets.d.ts +15 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +35 -10
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +4 -4
- package/dist/references.js.map +1 -1
- package/dist/rules-config.d.ts +26 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +9 -9
- package/dist/rules-config.js.map +1 -1
- package/dist/tex-requirements.d.ts +15 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +28 -10
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +83 -0
- package/dist/tex-venue-rules.d.ts.map +1 -0
- package/dist/tex-venue-rules.js +254 -0
- package/dist/tex-venue-rules.js.map +1 -0
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +20 -7
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +5 -4
- package/docs/e2e.md +69 -40
- package/docs/install.md +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/package-location.md +5 -5
- package/docs/prior-art/repro/README.md +1 -1
- package/docs/prior-art/test-tooling.md +21 -10
- package/docs/rules/tex/required-section.md +73 -0
- package/docs/rules/tex/template.md +62 -0
- package/docs/rules/tex/venue-leftover.md +71 -0
- package/docs/rules.md +70 -48
- package/docs/testing.md +17 -4
- package/docs/texlive-install-decision.md +1 -1
- package/docs/toolchain.md +2 -2
- package/eslint-rules/bib-reachable-entry.ts +1 -1
- package/eslint-rules/install-path-literals.ts +2 -2
- package/eslint-rules/paper-research-question.ts +1 -1
- package/eslint-rules/paper-stages.ts +2 -2
- package/eslint-rules/paper-typography.ts +2 -2
- package/eslint-rules/pdf-last-page-balance.ts +1 -1
- package/eslint-rules/review-frontmatter.ts +1 -1
- package/eslint-rules/temp-root-realpath.ts +1 -1
- package/eslint-rules/tex-build.ts +2 -2
- package/eslint.config.mjs +162 -15
- package/fixtures/build-e2e/acmart/expect.json +26 -0
- package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/aidc/expect.json +14 -0
- package/fixtures/build-e2e/aidc/paper.tex +46 -0
- package/fixtures/build-e2e/aidc/paperlint.json +1 -0
- package/fixtures/build-e2e/broken/expect.json +11 -0
- package/fixtures/build-e2e/cite/expect.json +27 -0
- package/fixtures/build-e2e/empty/expect.json +12 -0
- package/fixtures/build-e2e/fallback/expect.json +53 -0
- package/fixtures/build-e2e/guards/expect.json +6 -0
- package/fixtures/build-e2e/no-source/expect.json +10 -0
- package/fixtures/build-e2e/unbalanced/expect.json +60 -0
- package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
- package/fixtures/install-path-literals/clean.md +2 -2
- package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
- package/fixtures/install-path-literals/defect.md +2 -2
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.ts +1 -1
- package/fixtures/render-paper/build-clean.sh +3 -5
- package/hooks/paper-status-gates.sh +9 -19
- package/lib/paper-config.harness.mjs +1 -1
- package/lib/paper-config.ts +1 -5
- package/lib/skill-checks.mjs +24 -381
- package/lib/skill-checks.test.mjs +63 -115
- package/lib/skill-corpus.mjs +10 -251
- package/lib/skill-corpus.test.mjs +2 -13
- package/lib/skill-trigger-cases.mjs +1 -1
- package/package.json +13 -2
- package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
- package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
- package/presets/aidc.jsonc +73 -0
- package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
- package/presets/ieee-conference.jsonc +66 -0
- package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
- package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
- package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
- package/scripts/check.harness.ts +64 -10
- package/scripts/check.test.ts +60 -13
- package/scripts/check.ts +83 -30
- package/scripts/coverage-config.test.ts +0 -1
- package/scripts/harness-api.frozen.json +0 -1
- package/scripts/test.test.ts +1 -1
- package/scripts/test.ts +1 -1
- package/skills/analyze-sibling-paper/SKILL.md +1 -39
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
- package/skills/argument-arc/SKILL.md +2 -36
- package/skills/argument-arc/SKILL.md.spec.ts +1 -43
- package/skills/build-benchmark/SKILL.md +1 -41
- package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
- package/skills/camera-ready/SKILL.md +1 -34
- package/skills/camera-ready/SKILL.md.spec.ts +0 -33
- package/skills/cold-read-diff/SKILL.md +1 -35
- package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
- package/skills/draft-paper/SKILL.md +2 -41
- package/skills/draft-paper/SKILL.md.spec.ts +1 -50
- package/skills/extend-paper/SKILL.md +2 -33
- package/skills/extend-paper/SKILL.md.spec.ts +1 -42
- package/skills/find-venue/SKILL.md +2 -38
- package/skills/find-venue/SKILL.md.spec.ts +1 -47
- package/skills/grade-paper-writing/SKILL.md +3 -38
- package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
- package/skills/grade-paper-writing/prose-lint.mjs +2 -3
- package/skills/grade-paper-writing/prose-lint.test.ts +1 -1
- package/skills/harden-paper/SKILL.md +1 -39
- package/skills/harden-paper/SKILL.md.spec.ts +0 -38
- package/skills/map-prior-work/SKILL.md +1 -35
- package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
- package/skills/osf-artifact-upload/osf-artifact-upload.harness.ts +3 -4
- package/skills/paper-adversarial-review/SKILL.md +2 -35
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
- package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
- package/skills/paper-pipeline/SKILL.md +3 -29
- package/skills/paper-pipeline/SKILL.md.spec.ts +2 -28
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1 -1
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
- package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
- package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
- package/skills/paper-pipeline/scripts/README.md +30 -225
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
- package/skills/paper-pipeline/scripts/consumer.mjs +24 -119
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -42
- package/skills/paper-status/SKILL.md.spec.ts +1 -41
- package/skills/pc-panel-review/SKILL.md +2 -46
- package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
- package/skills/plan-paper-timeline/SKILL.md +2 -36
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
- package/skills/render-paper/SKILL.md +2 -38
- package/skills/render-paper/SKILL.md.spec.ts +1 -37
- package/skills/render-paper/check-render.sh +16 -63
- package/skills/render-paper/render-paper.harness.mjs +17 -59
- package/skills/research-ideate/SKILL.md +2 -39
- package/skills/research-ideate/SKILL.md.spec.ts +1 -47
- package/skills/study-accepted-papers/SKILL.md +1 -34
- package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
- package/skills/submit-paper/SKILL.md +1 -33
- package/skills/submit-paper/SKILL.md.spec.ts +0 -32
- package/skills/sweep-design-space/SKILL.md +1 -38
- package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
- package/skills/tighten-paper/SKILL.md +2 -39
- package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
- package/skills/tighten-paper/structure.mjs +4 -11
- package/skills/verify-citations/SKILL.md +2 -38
- package/skills/verify-citations/SKILL.md.spec.ts +1 -37
- package/src/adapters/banal/failure.ts +9 -10
- package/src/adapters/banal/index.test.ts +3 -3
- package/src/adapters/banal/index.ts +8 -10
- package/src/adapters/banal/install.ts +9 -4
- package/src/adapters/banal/invocation.test.ts +20 -8
- package/src/adapters/banal/invocation.ts +24 -9
- package/src/adapters/banal/locate.test.ts +2 -2
- package/src/adapters/banal/locate.ts +27 -21
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +3 -3
- package/src/adapters/banal/run.ts +16 -10
- package/src/adapters/banal/settings.ts +2 -2
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/curl/download.test.ts +4 -4
- package/src/adapters/latex/document-class.ts +147 -0
- package/src/adapters/latex/hidden.ts +70 -0
- package/src/adapters/latex/index.test.ts +361 -0
- package/src/adapters/latex/index.ts +55 -0
- package/src/adapters/latex/nodes.ts +141 -0
- package/src/adapters/latex/outline.ts +64 -0
- package/src/adapters/latex/parse.ts +136 -0
- package/src/adapters/latex/rendered.ts +59 -0
- package/src/adapters/memory/index.ts +4 -3
- package/src/adapters/node/files.test.ts +5 -3
- package/src/adapters/node/process.io.ts +9 -2
- package/src/adapters/node/process.test.ts +4 -4
- package/src/adapters/node/workspace.io.ts +9 -4
- package/src/adapters/pdfjs/fill.test.ts +1 -1
- package/src/adapters/pdfjs/fill.ts +18 -10
- package/src/adapters/references/index.ts +7 -2
- package/src/build-engine.test.ts +11 -6
- package/src/build-engine.ts +10 -5
- package/src/build.harness.mjs +5 -7
- package/src/build.test.ts +10 -11
- package/src/build.ts +12 -13
- package/src/caller-path.ts +13 -0
- package/src/cli.harness.mjs +1 -3
- package/src/cli.test.ts +21 -16
- package/src/cli.ts +115 -33
- package/src/doctor.test.ts +1 -1
- package/src/doctor.ts +14 -5
- package/src/domain/paths.test.ts +15 -0
- package/src/domain/paths.ts +20 -1
- package/src/domain/ready.ts +31 -0
- package/src/domain/record.test.ts +20 -0
- package/src/domain/record.ts +12 -0
- package/src/domain/sha256.ts +5 -3
- package/src/domain/tex-document.test.ts +80 -0
- package/src/domain/tex-document.ts +110 -0
- package/src/engine.harness.mjs +1 -1
- package/src/engine.ts +30 -2
- package/src/facts-file.test.ts +4 -7
- package/src/facts-file.ts +45 -30
- package/src/hooks-settings.harness.ts +17 -0
- package/src/hooks-settings.ts +28 -4
- package/src/init.test.ts +25 -5
- package/src/init.ts +26 -12
- package/src/latex-loop.harness.ts +1 -1
- package/src/new-paper.harness.mjs +7 -23
- package/src/new-paper.test.ts +28 -3
- package/src/new-paper.ts +15 -3
- package/src/package-dirs.ts +17 -0
- package/src/paper-files.test.ts +22 -19
- package/src/paper-files.ts +16 -27
- package/src/paper-settings-commands.test.ts +6 -1
- package/src/paper-settings.test.ts +2 -2
- package/src/paper-settings.ts +5 -5
- package/src/pdf-facts.test.ts +16 -5
- package/src/pdf-facts.ts +79 -22
- package/src/pdf-geometry.ts +5 -3
- package/src/ports/latex.ts +29 -0
- package/src/ports/tool-installer.ts +2 -17
- package/src/presets.test.ts +65 -4
- package/src/presets.ts +65 -28
- package/src/references.test.ts +25 -19
- package/src/references.ts +4 -5
- package/src/rule-cases.test.ts +474 -0
- package/src/rule-docs.test.ts +159 -0
- package/src/rules-config.ts +40 -11
- package/src/tex-requirements.harness.ts +13 -23
- package/src/tex-requirements.test.ts +21 -0
- package/src/tex-requirements.ts +64 -14
- package/src/tex-venue-rules.test.ts +659 -0
- package/src/tex-venue-rules.ts +429 -0
- package/src/toolchain.harness.mjs +1 -1
- package/src/toolchain.test.ts +9 -5
- package/src/toolchain.ts +2 -2
- package/src/types.ts +5 -3
- package/src/venue-rules.test.ts +104 -8
- package/src/venue-rules.ts +22 -12
- package/templates/paper/paper.tex +2 -1
- package/skills/paper-pipeline/scripts/announce.mjs +0 -67
- package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
- package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
- package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
- package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
- package/skills/paper-pipeline/scripts/fixtures/scorecard.ts +0 -100
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
- package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
- package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -750
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
- package/skills/paper-pipeline/scripts/status.mjs +0 -295
- package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
- /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
- /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# paper-status-gates.sh — NOT A HOOK. This is a TOOL invoked by `paper-status-gates.hook.mjs`,
|
|
3
3
|
# which owns the event routing and decides WHICH paper (if any) an edit concerned.
|
|
4
4
|
#
|
|
5
|
-
# --surface <paper-dir> print the readiness verdict
|
|
5
|
+
# --surface <paper-dir> print the readiness verdict and the unrun gates
|
|
6
6
|
#
|
|
7
7
|
# 🔴 WHAT LEFT THIS FILE AND WHY IT MATTERS. It used to read its payload with
|
|
8
8
|
# `INPUT="${CLAUDE_TOOL_INPUT:-}"` plus a `timeout 1 cat` fallback and then grep that blob for a
|
|
@@ -28,22 +28,21 @@ case "$pdir_name" in
|
|
|
28
28
|
esac
|
|
29
29
|
|
|
30
30
|
# ── WHERE THE CONSUMER'S THINGS ARE — READ, NOT ASSUMED ─────────────────────
|
|
31
|
-
#
|
|
32
|
-
# project's root paperlint.json (optional — absent means
|
|
31
|
+
# The papers path comes from the one declaration every other carrier of this package reads, the
|
|
32
|
+
# project's root paperlint.json (optional — absent means the default):
|
|
33
33
|
#
|
|
34
|
-
# { "papersDir": "docs/papers"
|
|
34
|
+
# { "papersDir": "docs/papers" }
|
|
35
35
|
#
|
|
36
36
|
# ⚠️ `node -p` RATHER THAN grep/sed ON THE FILE. A JSON value is not a line of text: it can
|
|
37
37
|
# be quoted, escaped, or spread across lines, and a pattern that gets it right today gets it
|
|
38
38
|
# wrong on the first reformat — silently, by producing a root that matches nothing. Node is
|
|
39
|
-
# already a hard dependency of the
|
|
39
|
+
# already a hard dependency of the hook that runs this script, so there is no new requirement.
|
|
40
40
|
ROOT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
41
41
|
read_key() { # $1 = key, $2 = default
|
|
42
42
|
node -p "((c) => c['$1'] ?? '$2')((() => { try { return require('$ROOT_DIR/paperlint.json') } catch { return {} } })())" \
|
|
43
43
|
2>/dev/null || echo "$2"
|
|
44
44
|
}
|
|
45
45
|
papers_root="$(read_key papersDir papers)"
|
|
46
|
-
scripts_root="$(read_key scripts .claude/skills/paper-pipeline/scripts)"
|
|
47
46
|
|
|
48
47
|
# 🔴 AN UNUSABLE ROOT EXITS, IT DOES NOT BUILD A PATH FROM IT. `node -p` prints `undefined` for a
|
|
49
48
|
# key whose value is literally `null` (`?? ` only catches null/undefined AFTER the object lookup,
|
|
@@ -69,18 +68,9 @@ else
|
|
|
69
68
|
echo " whether this paper is submittable, which is the one question it exists to answer." >&2
|
|
70
69
|
fi
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
if [ -
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# only a percentage, unrun gates. Advisory, always exit 0.
|
|
77
|
-
node "$CHECK" "$ROOT_DIR/$pdir" >&2 || true
|
|
78
|
-
else
|
|
79
|
-
# Fallback for a checkout without the skill tree — the old crude surface.
|
|
80
|
-
gates=$(grep -E '☐' "$STATUS" 2>/dev/null | grep -iE 'study-accepted|pc-panel|harden|verify.?citations' || true)
|
|
81
|
-
if [ -n "$gates" ]; then
|
|
82
|
-
echo "⚠️ UNRUN GATES in $pdir/PIPELINE-STATUS.md (☐ — run them, don't 'anti-churn'-skip):" >&2
|
|
83
|
-
echo "$gates" | sed -E 's/\|.*//; s/^\| */ /' >&2
|
|
84
|
-
fi
|
|
71
|
+
gates=$(grep -E '☐' "$STATUS" 2>/dev/null | grep -iE 'study-accepted|pc-panel|harden|verify.?citations' || true)
|
|
72
|
+
if [ -n "$gates" ]; then
|
|
73
|
+
echo "⚠️ UNRUN GATES in $pdir/PIPELINE-STATUS.md (☐ — run them, don't 'anti-churn'-skip):" >&2
|
|
74
|
+
echo "$gates" | sed -E 's/\|.*//; s/^\| */ /' >&2
|
|
85
75
|
fi
|
|
86
76
|
exit 0
|
|
@@ -253,7 +253,7 @@ const loaded = await Promise.all(
|
|
|
253
253
|
);
|
|
254
254
|
check(
|
|
255
255
|
"and the scan found the readers it must find — the skill scripts' keys and PaperlintConfig's",
|
|
256
|
-
["
|
|
256
|
+
["scripts", "timezone", "triggerCases", "papersDir", "rules"].every((k) =>
|
|
257
257
|
readKeys.has(k),
|
|
258
258
|
),
|
|
259
259
|
);
|
package/lib/paper-config.ts
CHANGED
|
@@ -138,11 +138,9 @@ export const SETTINGS_KEYS: Readonly<Record<string, string>> = Object.freeze({
|
|
|
138
138
|
"the venue preset: paperlint:<name> or ./path.jsonc — the pdf/ venue rules, paperlint build",
|
|
139
139
|
kind: "the kind of paper, whose page limit applies — pdf/limits",
|
|
140
140
|
pdf: "where the built PDF is, relative to the paper, when it is not paper.pdf — the facts",
|
|
141
|
-
ledger: "the run ledger — skills/paper-pipeline/scripts/consumer.mjs",
|
|
142
141
|
scripts: "how skill prose names the scripts — consumer.mjs",
|
|
143
|
-
timezone: "
|
|
142
|
+
timezone: "the zone deadline anchors are written in — consumer.mjs",
|
|
144
143
|
contactEmail: "the polite-pool address for citation lookups — consumer.mjs",
|
|
145
|
-
citeChecks: "the consumer's citation checkers — run-mechanical.mjs",
|
|
146
144
|
triggerCases:
|
|
147
145
|
"the consumer's own skill trigger cases — lib/skill-trigger-cases.mjs",
|
|
148
146
|
$comment: "a note for humans (JSON Schema's own comment keyword); ignored",
|
|
@@ -152,10 +150,8 @@ export const SETTINGS_KEYS: Readonly<Record<string, string>> = Object.freeze({
|
|
|
152
150
|
export const ROOT_ONLY_KEYS: readonly string[] = Object.freeze([
|
|
153
151
|
"papersDir",
|
|
154
152
|
"structure",
|
|
155
|
-
"ledger",
|
|
156
153
|
"scripts",
|
|
157
154
|
"timezone",
|
|
158
155
|
"contactEmail",
|
|
159
|
-
"citeChecks",
|
|
160
156
|
"triggerCases",
|
|
161
157
|
]);
|
package/lib/skill-checks.mjs
CHANGED
|
@@ -9,11 +9,9 @@
|
|
|
9
9
|
* a result, and the coupling was an environment variable. The owner asked three times why the
|
|
10
10
|
* file survived; the answer was that I had wrapped it instead of splitting it.
|
|
11
11
|
*
|
|
12
|
-
* WHAT IS CHECKED HERE
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* vocabulary. Anything about the SET — name uniqueness, the gate table, the exclusion list — is
|
|
16
|
-
* `pipeline-corpus.harness.mjs`, because a per-skill run structurally cannot see it.
|
|
12
|
+
* WHAT IS CHECKED HERE. Anything decidable from one SKILL.md: strict-YAML frontmatter, the
|
|
13
|
+
* declared tool contract, every script path it instructs, and whether it is PERMITTED to run what
|
|
14
|
+
* it instructs.
|
|
17
15
|
*
|
|
18
16
|
* 🔴 KNOWN-RED ASSERTIONS ARE DEFERRED, NOT WEAKENED, and the deferral list is now PER CALL.
|
|
19
17
|
* A throw would abort the rest, making one open finding silently skip every check after it —
|
|
@@ -21,108 +19,29 @@
|
|
|
21
19
|
* a full sweep could attribute one skill's failures to another's line in the report.
|
|
22
20
|
*/
|
|
23
21
|
import assert from "node:assert/strict";
|
|
24
|
-
import { existsSync, readFileSync
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
25
23
|
import { join } from "node:path";
|
|
26
24
|
import { recordCheck } from "vigiles";
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
// `skill-corpus.mjs` on the import below, so by the time anything is parsed here the parser
|
|
30
|
-
// either exists or the process does not.
|
|
31
|
-
import { headings as mdHeadings } from "#lib/markdown";
|
|
26
|
+
import { SKILLS_DIR, ROOT, load, commands, permits } from "./skill-corpus.mjs";
|
|
32
27
|
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// list); the file's own header says that is `pipeline-corpus.harness.mjs`'s job, and it was
|
|
37
|
-
// verified by grep — all six really are used there, no logic was lost. `frontmatter` and
|
|
38
|
-
// `toolList` are called INSIDE `load()` in `skill-corpus.mjs`, and the direct import was a
|
|
39
|
-
// second path to the same data. `allBold` is the raw material for `namedKinds`/`namedRetired`,
|
|
40
|
-
// which are imported right beside it.
|
|
28
|
+
// Where the pipeline's own scripts live is asked of the package, not spelled here. The resolver
|
|
29
|
+
// refuses a root that is not on disk, because every use below is a FILTER: a wrong prefix would
|
|
30
|
+
// match nothing and report a clean skill that was never examined.
|
|
41
31
|
import {
|
|
42
|
-
SKILLS,
|
|
43
|
-
SKILLS_DIR,
|
|
44
|
-
RETIRED,
|
|
45
|
-
ABSTENTIONS,
|
|
46
|
-
record,
|
|
47
|
-
ROOT,
|
|
48
|
-
LEDGER_ONLY_BASH,
|
|
49
|
-
recordBlock,
|
|
50
|
-
namedKinds,
|
|
51
|
-
namedRetired,
|
|
52
|
-
load,
|
|
53
|
-
commands,
|
|
54
|
-
permits,
|
|
55
|
-
definedKinds,
|
|
56
|
-
namedReasons,
|
|
57
|
-
ADMISSION,
|
|
58
|
-
tmp,
|
|
59
|
-
} from "./skill-corpus.mjs";
|
|
60
|
-
|
|
61
|
-
// ═════════════════════════════════════════════════════════════════════════════════════════════
|
|
62
|
-
// WHERE THE PIPELINE'S SCRIPTS ARE — ASKED, NOT SPELLED (2026-09-12)
|
|
63
|
-
//
|
|
64
|
-
// Until today this file carried the string `.claude/skills/paper-pipeline/scripts/` in SEVEN
|
|
65
|
-
// places: two announce/record templates, two `indexOf` ordering probes, one `startsWith` filter
|
|
66
|
-
// and two regular expressions. Every one of them is this consumer's own layout, and a mechanism
|
|
67
|
-
// that spells one consumer's layout has exactly one possible user — the thing the extraction
|
|
68
|
-
// exists to undo. Nine colocated harnesses of wave ① import THIS module and nothing else from
|
|
69
|
-
// `lib/`, so until the seven literals left, none of them could be built anywhere but here.
|
|
70
|
-
//
|
|
71
|
-
// The value now comes from the package, as the fourth carrier beside `papersRoot()` (where the
|
|
72
|
-
// papers are), `ledgerPath()` (where the journal is) and `citeChecks` (where the consumer's own
|
|
73
|
-
// citation checkers are). One declaration in `paperlint.json`, default
|
|
74
|
-
// `.claude/skills/paper-pipeline/scripts`, and the resolver REFUSES a root that is not on disk
|
|
75
|
-
// or that resolves inside `node_modules`.
|
|
76
|
-
//
|
|
77
|
-
// 🔴 WHY THE REFUSAL IS THE POINT AND NOT DEFENSIVENESS. Six of the seven uses are FILTERS —
|
|
78
|
-
// `startsWith`, `indexOf`, `RegExp.test`. A wrong prefix does not make them wrong; it makes them
|
|
79
|
-
// match nothing, so every loop body below goes unentered and `checkSkill` reports a clean skill
|
|
80
|
-
// it never examined. That failure is byte-identical to success, and a throw while this module is
|
|
81
|
-
// still LOADING is the only place it can be made visible.
|
|
82
|
-
//
|
|
83
|
-
// `{ env: {}, cwd: ROOT }` rather than the defaults: `ROOT` above already resolved
|
|
84
|
-
// `CLAUDE_PROJECT_DIR`, and letting the resolver consult it again would make the two disagree on
|
|
85
|
-
// exactly one input (an empty-string variable — `??` keeps it, `||` does not).
|
|
86
|
-
import {
|
|
87
|
-
installedSkills,
|
|
88
32
|
pipelineScripts,
|
|
89
33
|
scriptsRoot,
|
|
90
34
|
} from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
91
35
|
|
|
92
36
|
const PIPELINE = pipelineScripts(scriptsRoot({ env: {}, cwd: ROOT }));
|
|
93
37
|
|
|
94
|
-
/**
|
|
95
|
-
* A literal path, embedded in a RegExp. Not a parser dodge — the subject here IS a string (a
|
|
96
|
-
* path, which has no grammar to ask), and this is the standard way to put one inside a pattern.
|
|
97
|
-
*/
|
|
98
|
-
const reEscape = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
99
|
-
|
|
100
|
-
/** `Bash(node <pipeline scripts>/<name>.mjs:*)` — the narrowed entry the ledger-only skills get. */
|
|
101
|
-
const LEDGER_BASH_RE = new RegExp(
|
|
102
|
-
`^Bash\\(node ${reEscape(PIPELINE.prefix)}[a-z-]+\\.mjs:\\*\\)$`,
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* A command that is one of the two ledger calls.
|
|
107
|
-
*
|
|
108
|
-
* ⚠️ DELIBERATELY NOT ANCHORED AT THE START, because the literal it replaced was not either. The
|
|
109
|
-
* anchored form would be tighter and is probably what anyone would write today, but tightening it
|
|
110
|
-
* in the same change that parameterises it would mean a `before = after` comparison could no
|
|
111
|
-
* longer tell a successful parameterisation from a changed rule.
|
|
112
|
-
*/
|
|
113
|
-
const LEDGER_SCRIPT_RE = new RegExp(
|
|
114
|
-
`${reEscape(PIPELINE.prefix)}(announce|ledger)\\.mjs$`,
|
|
115
|
-
);
|
|
116
|
-
|
|
117
38
|
/**
|
|
118
39
|
* Assert everything single-skill about `name`. Throws with EVERY finding, not the first.
|
|
119
40
|
* Returns the number of skills checked (1) so a caller can report what it actually did.
|
|
120
41
|
*/
|
|
121
42
|
export async function checkSkill(name) {
|
|
122
|
-
if (!
|
|
123
|
-
throw new Error(
|
|
124
|
-
`"${name}" is not a wired pipeline skill. Known: ${SKILLS.join(", ")}`,
|
|
125
|
-
);
|
|
43
|
+
if (!existsSync(join(SKILLS_DIR, name, "SKILL.md")))
|
|
44
|
+
throw new Error(`"${name}" has no SKILL.md under ${SKILLS_DIR}`);
|
|
126
45
|
const deferred = [];
|
|
127
46
|
const soft = (fn) => {
|
|
128
47
|
try {
|
|
@@ -132,7 +51,6 @@ export async function checkSkill(name) {
|
|
|
132
51
|
}
|
|
133
52
|
};
|
|
134
53
|
const skills = [load(name)];
|
|
135
|
-
const skillDirs = new Set(installedSkills(SKILLS_DIR));
|
|
136
54
|
|
|
137
55
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
138
56
|
// 1. GONE, AND THAT IS THE RESULT (2026-08-19). This slot held a strict-YAML assertion whose whole
|
|
@@ -150,19 +68,15 @@ export async function checkSkill(name) {
|
|
|
150
68
|
|
|
151
69
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
152
70
|
// 2-4. Identity and contract fields, read from the strict parse — there is no other kind now, and
|
|
153
|
-
// nothing above can fail while leaving these reachable. `name` is what
|
|
154
|
-
//
|
|
155
|
-
// with its directory means every verdict this skill records hashes against a directory that is not
|
|
156
|
-
// its own, and staleness detection quietly stops working for it.
|
|
71
|
+
// nothing above can fail while leaving these reachable. `name` is what Claude Code lists and
|
|
72
|
+
// matches the skill by, so a name that disagrees with its directory is two identities for one skill.
|
|
157
73
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
158
74
|
for (const s of skills) {
|
|
159
75
|
soft(() =>
|
|
160
76
|
assert.equal(
|
|
161
77
|
s.fm.name,
|
|
162
78
|
s.name,
|
|
163
|
-
`${s.name}: frontmatter name is ${JSON.stringify(s.fm.name)} but the directory is "${s.name}"
|
|
164
|
-
`skillHash() resolves by DIRECTORY and the ledger stores whatever the SKILL.md text passes — ` +
|
|
165
|
-
`disagreement here splits one skill's history across two keys and neither is ever fresh.`,
|
|
79
|
+
`${s.name}: frontmatter name is ${JSON.stringify(s.fm.name)} but the directory is "${s.name}".`,
|
|
166
80
|
),
|
|
167
81
|
);
|
|
168
82
|
|
|
@@ -186,92 +100,7 @@ export async function checkSkill(name) {
|
|
|
186
100
|
}
|
|
187
101
|
|
|
188
102
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
189
|
-
//
|
|
190
|
-
// The README calls these the whole wiring contract. Each is checked three ways — the block exists,
|
|
191
|
-
// the script it names exists on disk, and the SKILL NAME IT PASSES IS ITS OWN. The third is the
|
|
192
|
-
// copy-paste defect: these blocks differ from their siblings' by one token, and `announce.mjs`
|
|
193
|
-
// validates nothing, so a stale token records a real verdict under another skill's name.
|
|
194
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
195
|
-
for (const s of skills) {
|
|
196
|
-
const cmds = commands(s.src);
|
|
197
|
-
|
|
198
|
-
// 2026-08-11: "is there a `## Run me` block" is a question about a HEADING, therefore about
|
|
199
|
-
// level and text, not about the number of hashes. The former `/^#{1,3} Run me\s*$/m` also
|
|
200
|
-
// counted a `## Run me` line inside a ```-block as a heading: a skill SHOWING someone else's
|
|
201
|
-
// block in an example passed the check without having one of its own. Silence is a soft
|
|
202
|
-
// assertion's success state, so there was nothing to notice this with.
|
|
203
|
-
soft(() =>
|
|
204
|
-
assert.ok(
|
|
205
|
-
mdHeadings(s.src).some((h) => h.depth <= 3 && h.text === "Run me"),
|
|
206
|
-
`${s.name}: no "## Run me" block. Without it the skill leaves no trace of having started, and ` +
|
|
207
|
-
`an advisory pass that leaves no trace cannot be observed failing — silence is both its error ` +
|
|
208
|
-
`state and its normal state. Three hooks in this repo died exactly that way.`,
|
|
209
|
-
),
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
for (const [label, script, want] of [
|
|
213
|
-
["announce", PIPELINE.announce, "Run me"],
|
|
214
|
-
["record", PIPELINE.ledger, "Record the verdict"],
|
|
215
|
-
]) {
|
|
216
|
-
// For the ledger, only a `record` call is the wired call: `ledger.mjs status <dir>` is a
|
|
217
|
-
// read, files under nothing, and until 2026-09-27 was misreported as "files under status".
|
|
218
|
-
const hits = cmds.filter(
|
|
219
|
-
(c) =>
|
|
220
|
-
c.script === script && (label !== "record" || c.args[0] === "record"),
|
|
221
|
-
);
|
|
222
|
-
soft(() =>
|
|
223
|
-
assert.ok(
|
|
224
|
-
hits.length > 0,
|
|
225
|
-
`${s.name}: the "${want}" block instructs no \`node ${script}\` command. The block may be present ` +
|
|
226
|
-
`and empty — the README's contract is the CALL, not the heading.`,
|
|
227
|
-
),
|
|
228
|
-
);
|
|
229
|
-
for (const h of hits) {
|
|
230
|
-
soft(() =>
|
|
231
|
-
assert.ok(
|
|
232
|
-
existsSync(join(ROOT, h.script)),
|
|
233
|
-
`${s.name}: instructs \`${h.cmd}\` and ${h.script} DOES NOT EXIST. A skill naming a path that ` +
|
|
234
|
-
`is not there is a dead instruction: the model reads it, does something adjacent, and reports ` +
|
|
235
|
-
`success. Prose cannot notice this; nothing else in this repo was looking.`,
|
|
236
|
-
),
|
|
237
|
-
);
|
|
238
|
-
// arg[0] for announce is the skill; for ledger.mjs it is the literal `record`, then the skill.
|
|
239
|
-
const passed = label === "announce" ? h.args[0] : h.args[1];
|
|
240
|
-
soft(() =>
|
|
241
|
-
assert.equal(
|
|
242
|
-
passed,
|
|
243
|
-
s.name,
|
|
244
|
-
`${s.name}: its ${label} command files under ${JSON.stringify(passed)}, not "${s.name}" — ` +
|
|
245
|
-
`\`${h.cmd}\`. These blocks are copy-paste templates differing in one token and announce.mjs ` +
|
|
246
|
-
`validates nothing, so this records a real run under a SIBLING'S identity: the wrong gate ` +
|
|
247
|
-
`reads FRESH in status.mjs and the right one reads NEVER-RUN.`,
|
|
248
|
-
),
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
soft(() => {
|
|
254
|
-
const a = s.src.indexOf(PIPELINE.announce);
|
|
255
|
-
const r = s.src.indexOf(`${PIPELINE.ledger} record`);
|
|
256
|
-
if (a >= 0 && r >= 0)
|
|
257
|
-
assert.ok(
|
|
258
|
-
a < r,
|
|
259
|
-
`${s.name}: the ledger record call appears BEFORE the announce call. The contract is announce ` +
|
|
260
|
-
`first / record last; a verdict written before the run started is a verdict about nothing.`,
|
|
261
|
-
);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
soft(() =>
|
|
265
|
-
assert.ok(
|
|
266
|
-
recordBlock(s.src),
|
|
267
|
-
`${s.name}: no "Record the verdict" heading. A run with an announce row and no verdict row is ` +
|
|
268
|
-
`indistinguishable from a skill that started and died.`,
|
|
269
|
-
),
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
274
|
-
// 7. EVERY script path the skill instructs resolves — not only the two ledger calls.
|
|
103
|
+
// 7. EVERY script path the skill instructs resolves.
|
|
275
104
|
//
|
|
276
105
|
// Scoped to paths that are unambiguously resolvable: `.claude/…` is repo-root-relative and
|
|
277
106
|
// `scripts/…` is skill-relative (both conventions are used consistently across the corpus). Paths
|
|
@@ -298,209 +127,27 @@ export async function checkSkill(name) {
|
|
|
298
127
|
|
|
299
128
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
300
129
|
// 8. A skill is PERMITTED TO RUN what it instructs. The inverse-direction risk of narrowing: over-
|
|
301
|
-
// narrow an allowed-tools entry and the skill's own
|
|
302
|
-
//
|
|
303
|
-
//
|
|
130
|
+
// narrow an allowed-tools entry and the skill's own instruction is denied at runtime, which looks
|
|
131
|
+
// like the skill silently declining a step — the same invisible failure as a dead hook, arrived at
|
|
132
|
+
// from the opposite direction. Nothing else checks the two halves against each other.
|
|
304
133
|
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
305
134
|
for (const s of skills) {
|
|
306
135
|
for (const c of commands(s.src)) {
|
|
307
|
-
// The
|
|
308
|
-
// directory was dissolved and `ledger.mjs`/`announce.mjs` went home to the skill they serve.
|
|
136
|
+
// The pipeline's own scripts; any other command is the consumer's business.
|
|
309
137
|
if (!c.script.startsWith(PIPELINE.prefix)) continue;
|
|
310
138
|
soft(() =>
|
|
311
139
|
assert.ok(
|
|
312
140
|
s.tools.some((t) => permits(t, c.cmd)),
|
|
313
141
|
`${s.name}: instructs \`${c.cmd}\` but its allowed-tools [${s.tools.join(", ")}] permit no such ` +
|
|
314
|
-
`Bash command. The instruction is denied at runtime and the
|
|
315
|
-
`record — invisible, because a gate that never wrote a row looks exactly like one nobody ran.`,
|
|
142
|
+
`Bash command. The instruction is denied at runtime and the step silently does not happen.`,
|
|
316
143
|
),
|
|
317
144
|
);
|
|
318
145
|
}
|
|
319
146
|
}
|
|
320
147
|
|
|
321
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
322
|
-
// 9. The nine ledger-only skills keep their Bash NARROWED.
|
|
323
|
-
//
|
|
324
|
-
// A plain `Bash` on one of these is the regression that took this repo's Safety score from 70 to 0
|
|
325
|
-
// in the audit's own arithmetic: Read + Bash + WebFetch is the lethal trifecta, and these nine are
|
|
326
|
-
// analysis skills that read the knowledge base. `argument-arc` grading a paper does not need a
|
|
327
|
-
// shell; it needs to append one line to a JSONL. Construction beats prose — with the narrow entry
|
|
328
|
-
// the capability is ABSENT, and absence cannot be talked around by injected text in a fetched CFP.
|
|
329
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
330
|
-
for (const s of skills.filter((s) => LEDGER_ONLY_BASH.has(s.name))) {
|
|
331
|
-
const bash = s.tools.filter((t) => t === "Bash" || t.startsWith("Bash("));
|
|
332
|
-
soft(() =>
|
|
333
|
-
assert.ok(
|
|
334
|
-
bash.length > 0,
|
|
335
|
-
`${s.name}: listed as ledger-only-Bash but declares no Bash entry at all — it cannot run its own ` +
|
|
336
|
-
`announce/record calls. Either add the narrow entries or remove it from LEDGER_ONLY_BASH here.`,
|
|
337
|
-
),
|
|
338
|
-
);
|
|
339
|
-
for (const b of bash) {
|
|
340
|
-
soft(() =>
|
|
341
|
-
assert.match(
|
|
342
|
-
b,
|
|
343
|
-
LEDGER_BASH_RE,
|
|
344
|
-
`${s.name}: Bash entry ${JSON.stringify(b)} is not narrowed to the ledger. This skill gained Bash ` +
|
|
345
|
-
`ONLY to append a run row; a bare \`Bash\` hands a knowledge-base reader a shell and completes ` +
|
|
346
|
-
`the lethal trifecta. See ALLOWED-TOOLS-AUDIT-2026-08-03.md §2 for why this one was narrowed.`,
|
|
347
|
-
),
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
353
|
-
// 10. THE VOCABULARY, TURNED AROUND ON 2026-08-10.
|
|
354
|
-
//
|
|
355
|
-
// WHAT THIS USED TO ASSERT, and why it had to change rather than be deleted. The old text was:
|
|
356
|
-
// "the vocabulary contains a REACHABLE NEGATIVE — or the text admits it does not", because a gate
|
|
357
|
-
// documented as PASS-only cannot say no and no amount of running would reveal it. With `PASS`
|
|
358
|
-
// deleted from the ledger, the literal form of that assertion is VACUOUS: `FINDING` and
|
|
359
|
-
// `ABSTAINED` are the only constructors, so every conforming block names a negative by
|
|
360
|
-
// construction and the check would pass on every file forever while reading as though it still
|
|
361
|
-
// guarded something. A vacuous assertion is worse than a missing one — it is a green light with
|
|
362
|
-
// nothing behind it, which is this directory's whole subject.
|
|
363
|
-
//
|
|
364
|
-
// So the property is carried over rather than the wording. It was never really "names a negative";
|
|
365
|
-
// it was **"this check is capable of reporting that something is wrong."** Under the new
|
|
366
|
-
// vocabulary that is: the block names FINDING, the evidence-carrying constructor. A block that
|
|
367
|
-
// documents only how to abstain describes a check that cannot fail — the same defect, wearing the
|
|
368
|
-
// new words.
|
|
369
|
-
//
|
|
370
|
-
// 10b is genuinely new and is the migration guard: a block still instructing a RETIRED constructor
|
|
371
|
-
// tells the model to run a command that now exits 2. Prose survives refactors that code does not,
|
|
372
|
-
// and 22 files were rewritten by hand today.
|
|
373
|
-
//
|
|
374
|
-
// 10c requires a named abstention REASON, because `ABSTAINED` with no reason is the deleted `PASS`
|
|
375
|
-
// with a longer name — the closed reason vocabulary is the entire mechanism separating "ran and
|
|
376
|
-
// found nothing" from "could not run".
|
|
377
|
-
//
|
|
378
|
-
// The escape hatch on 10a is deliberate and is the honest case, not a loophole: `draft-paper`
|
|
379
|
-
// GENERATES, will abstain nearly always, and its text says so in status.mjs's own terms. That
|
|
380
|
-
// admission is load-bearing documentation — delete it and an explained limitation becomes an
|
|
381
|
-
// unexplained never-finding check — so it is asserted directly, not merely permitted.
|
|
382
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
383
|
-
for (const s of skills) {
|
|
384
|
-
const blk = recordBlock(s.src);
|
|
385
|
-
if (!blk) continue;
|
|
386
|
-
|
|
387
|
-
soft(() =>
|
|
388
|
-
assert.ok(
|
|
389
|
-
namedKinds(blk).includes("FINDING") || ADMISSION.test(blk),
|
|
390
|
-
`${s.name}: its record block names [${namedKinds(blk).join(", ") || "nothing"}] and not FINDING, ` +
|
|
391
|
-
`and does not admit that it cannot produce one. FINDING is the only constructor that carries ` +
|
|
392
|
-
`evidence; a block documenting only how to ABSTAIN describes a check that can never report that ` +
|
|
393
|
-
`anything is wrong. Either name the finding this skill can return, or state plainly that it ` +
|
|
394
|
-
`generates rather than judges — as draft-paper does.`,
|
|
395
|
-
),
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
soft(() =>
|
|
399
|
-
assert.deepEqual(
|
|
400
|
-
namedRetired(blk),
|
|
401
|
-
[],
|
|
402
|
-
`${s.name}: its record block still instructs the RETIRED constructor(s) ` +
|
|
403
|
-
`[${namedRetired(blk).join(", ")}]. ${[...RETIRED.keys()]
|
|
404
|
-
.filter((k) => namedRetired(blk).includes(k))
|
|
405
|
-
.map((k) => `${k} ${RETIRED.get(k)}`)
|
|
406
|
-
.join(" ")} ` +
|
|
407
|
-
`A model following this block runs a command that exits 2 at the last step of finished work.`,
|
|
408
|
-
),
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
soft(() =>
|
|
412
|
-
assert.ok(
|
|
413
|
-
namedReasons(blk).length > 0,
|
|
414
|
-
`${s.name}: its record block names no abstention reason from [${[...ABSTENTIONS.keys()].join(", ")}]. ` +
|
|
415
|
-
`An ABSTAINED with an unnamed reason is the deleted PASS with more characters: the closed reason ` +
|
|
416
|
-
`set is the only thing separating "ran and found nothing" from "could not run", and a block that ` +
|
|
417
|
-
`does not say which one applies leaves the model to invent a string record() will refuse.`,
|
|
418
|
-
),
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
// Guarded for one-skill mode: this assertion is ABOUT draft-paper, so it only runs
|
|
422
|
-
// when draft-paper is in scope. Silently skipping it in a full sweep would be the
|
|
423
|
-
// vacuity this file hunts, so the guard is the membership test, not a try/catch.
|
|
424
|
-
const draftPaper = skills.find((s) => s.name === "draft-paper");
|
|
425
|
-
if (draftPaper)
|
|
426
|
-
soft(() =>
|
|
427
|
-
assert.match(
|
|
428
|
-
recordBlock(draftPaper.src),
|
|
429
|
-
ADMISSION,
|
|
430
|
-
`draft-paper: the honest admission is gone from its record block. It is the one skill in the ` +
|
|
431
|
-
`pipeline that will almost always abstain, and the paragraph saying so — in status.mjs's own ` +
|
|
432
|
-
`terms, "a generator is not a gate" — is what keeps that flag readable as a known limitation ` +
|
|
433
|
-
`rather than an unexplained never-finding check someone later "fixes" by faking one.`,
|
|
434
|
-
),
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
438
|
-
// 11. No skill instructs something `record()` will throw on — proven by CALLING IT, not by reading
|
|
439
|
-
// the vocabulary list. `record()` validates its argument and throws; a skill telling the model to
|
|
440
|
-
// record `GREEN`, `OK` or `PASS` produces a crash at the last step of a completed pass, after the
|
|
441
|
-
// work is done.
|
|
442
|
-
//
|
|
443
|
-
// Each constructor is called in its VALID shape, because the shape is part of the contract: a
|
|
444
|
-
// FINDING needs a count and an existing report, an ABSTENTION needs a reason from the closed set.
|
|
445
|
-
// Calling `record` with a bare kind would prove only that the kind string is spelled right.
|
|
446
|
-
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
447
|
-
{
|
|
448
|
-
writeFileSync(
|
|
449
|
-
join(tmp, "fixture-report.md"),
|
|
450
|
-
"---\nfindings: 1\n---\n\nharness fixture\n",
|
|
451
|
-
);
|
|
452
|
-
for (const s of skills) {
|
|
453
|
-
const blk = recordBlock(s.src);
|
|
454
|
-
if (!blk) continue;
|
|
455
|
-
for (const v of definedKinds(blk)) {
|
|
456
|
-
soft(() =>
|
|
457
|
-
record(
|
|
458
|
-
v === "ABSTAINED"
|
|
459
|
-
? {
|
|
460
|
-
skill: s.name,
|
|
461
|
-
paper: tmp,
|
|
462
|
-
kind: v,
|
|
463
|
-
reason: namedReasons(blk)[0] ?? null,
|
|
464
|
-
note: "harness fixture",
|
|
465
|
-
}
|
|
466
|
-
: {
|
|
467
|
-
skill: s.name,
|
|
468
|
-
paper: tmp,
|
|
469
|
-
kind: v,
|
|
470
|
-
findings: 1,
|
|
471
|
-
report: "fixture-report.md",
|
|
472
|
-
note: "harness fixture",
|
|
473
|
-
},
|
|
474
|
-
),
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
// Every skill name any of these files hands to announce.mjs or ledger.mjs must resolve to a real
|
|
480
|
-
// skill, including names inside worked examples. announce.mjs does NOT validate: an unknown name
|
|
481
|
-
// hashes to `????????` and the row is written anyway.
|
|
482
|
-
for (const s of skills) {
|
|
483
|
-
for (const c of commands(s.src)) {
|
|
484
|
-
if (!LEDGER_SCRIPT_RE.test(c.script)) continue;
|
|
485
|
-
const passed = c.script.endsWith("announce.mjs")
|
|
486
|
-
? c.args[0]
|
|
487
|
-
: c.args[0] === "record"
|
|
488
|
-
? c.args[1]
|
|
489
|
-
: null;
|
|
490
|
-
if (!passed || passed.startsWith("<")) continue;
|
|
491
|
-
soft(() =>
|
|
492
|
-
assert.ok(
|
|
493
|
-
skillDirs.has(passed),
|
|
494
|
-
`${s.name}: instructs \`${c.cmd}\`, naming skill "${passed}", which has no directory under ` +
|
|
495
|
-
`.claude/skills/. announce.mjs validates nothing — it hashes the unknown name to "????????" ` +
|
|
496
|
-
`and writes the row, so the typo is permanent and silent in the ledger.`,
|
|
497
|
-
),
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
148
|
if (deferred.length) {
|
|
502
149
|
const e = new Error(
|
|
503
|
-
`${deferred.length} assertion(s) failed for
|
|
150
|
+
`${deferred.length} assertion(s) failed for skill "${name}":\n\n` +
|
|
504
151
|
deferred.map((d, i) => ` [${i + 1}] ${d.message}`).join("\n\n"),
|
|
505
152
|
);
|
|
506
153
|
e.stack = e.message;
|
|
@@ -509,20 +156,16 @@ export async function checkSkill(name) {
|
|
|
509
156
|
// Reported so `vigiles test` can tell a run that verified this skill from a file that merely
|
|
510
157
|
// sits beside it — the distinction colocation cannot make (see vigiles check-count.ts).
|
|
511
158
|
recordCheck();
|
|
512
|
-
console.log(
|
|
513
|
-
`✓ ${name}: frontmatter, wiring, script paths, tool contract, verdict vocabulary`,
|
|
514
|
-
);
|
|
159
|
+
console.log(`✓ ${name}: frontmatter, script paths, tool contract`);
|
|
515
160
|
return 1;
|
|
516
161
|
}
|
|
517
162
|
|
|
518
163
|
// ═════════════════════════════════════════════════════════════════════════════════════════════════
|
|
519
164
|
// checkProseSkill — for skills WITHOUT the pipeline (2026-08-27)
|
|
520
165
|
//
|
|
521
|
-
// WHY A SEPARATE FUNCTION
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
// getting three red assertions about the absence of what it never promised. A check that is red by
|
|
525
|
-
// construction lives one day, after which it gets silenced — that has already happened in this repo.
|
|
166
|
+
// WHY A SEPARATE FUNCTION. A prose skill only writes text, so Bash, Agent and Task are banned
|
|
167
|
+
// unless an exemption names a reason; a pipeline skill runs its scripts through narrowed Bash
|
|
168
|
+
// entries and is checked instead for whether those scripts exist and are permitted.
|
|
526
169
|
//
|
|
527
170
|
// 🔴 BOTH HALVES ARE BUILT IN, NOT WRITTEN BY HAND. The `CLAUDE.md` rule requires two halves of
|
|
528
171
|
// every check: it fires on a planted defect AND it stays silent on clean input. The "fires" half is
|