paperlint 3.1.3 → 4.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/workflows/ci.yml +12 -7
- package/CLAUDE.md +5 -5
- package/CONTRIBUTING.md +34 -5
- 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 +4 -3
- 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/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/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +2 -1
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/node/process.io.d.ts +5 -7
- package/dist/adapters/node/process.io.d.ts.map +1 -1
- package/dist/adapters/node/process.io.js +4 -3
- 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 +13 -7
- 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 +8 -7
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts.map +1 -1
- package/dist/adapters/references/pool.js +4 -5
- package/dist/adapters/references/pool.js.map +1 -1
- package/dist/adapters/references/reach.io.d.ts.map +1 -1
- package/dist/adapters/references/reach.io.js +2 -1
- package/dist/adapters/references/reach.io.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 +10 -3
- 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 +19 -17
- 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 +93 -46
- 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 +5 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/lookup-cache.d.ts.map +1 -1
- package/dist/domain/lookup-cache.js +10 -8
- package/dist/domain/lookup-cache.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/text.d.ts +12 -0
- package/dist/domain/text.d.ts.map +1 -1
- package/dist/domain/text.js +17 -0
- package/dist/domain/text.js.map +1 -1
- package/dist/engine.d.ts +23 -22
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -3
- package/dist/engine.js.map +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +22 -0
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +1 -0
- package/dist/eslint-rules/bib-reachable-entry.js +75 -0
- package/dist/eslint-rules/bib-reachable-entry.js.map +1 -0
- package/dist/eslint-rules/install-path-literals.d.ts +80 -0
- package/dist/eslint-rules/install-path-literals.d.ts.map +1 -0
- package/dist/eslint-rules/install-path-literals.js +105 -0
- package/dist/eslint-rules/install-path-literals.js.map +1 -0
- package/dist/eslint-rules/latex-language.d.ts +136 -0
- package/dist/eslint-rules/latex-language.d.ts.map +1 -0
- package/dist/eslint-rules/latex-language.js +639 -0
- package/dist/eslint-rules/latex-language.js.map +1 -0
- package/dist/eslint-rules/paper-research-question.d.ts +31 -0
- package/dist/eslint-rules/paper-research-question.d.ts.map +1 -0
- package/dist/eslint-rules/paper-research-question.js +188 -0
- package/dist/eslint-rules/paper-research-question.js.map +1 -0
- package/dist/eslint-rules/paper-stages.d.ts +83 -0
- package/dist/eslint-rules/paper-stages.d.ts.map +1 -0
- package/dist/eslint-rules/paper-stages.js +358 -0
- package/dist/eslint-rules/paper-stages.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +74 -0
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -0
- package/dist/eslint-rules/paper-typography.js +404 -0
- package/dist/eslint-rules/paper-typography.js.map +1 -0
- package/dist/eslint-rules/papers.d.ts +42 -0
- package/dist/eslint-rules/papers.d.ts.map +1 -0
- package/dist/eslint-rules/papers.js +145 -0
- package/dist/eslint-rules/papers.js.map +1 -0
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +72 -0
- package/dist/eslint-rules/pdf-last-page-balance.d.ts.map +1 -0
- package/dist/eslint-rules/pdf-last-page-balance.js +237 -0
- package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -0
- package/dist/eslint-rules/review-frontmatter.d.ts +57 -0
- package/dist/eslint-rules/review-frontmatter.d.ts.map +1 -0
- package/dist/eslint-rules/review-frontmatter.js +126 -0
- package/dist/eslint-rules/review-frontmatter.js.map +1 -0
- package/dist/eslint-rules/review-frontmatter.schema.json +36 -0
- package/dist/eslint-rules/rule-context.d.ts +55 -0
- package/dist/eslint-rules/rule-context.d.ts.map +1 -0
- package/dist/eslint-rules/rule-context.js +9 -0
- package/dist/eslint-rules/rule-context.js.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.d.ts +25 -0
- package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.js +16 -0
- package/dist/eslint-rules/sibling-frontmatter.js.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.schema.json +14 -0
- package/dist/eslint-rules/temp-root-realpath.d.ts +71 -0
- package/dist/eslint-rules/temp-root-realpath.d.ts.map +1 -0
- package/dist/eslint-rules/temp-root-realpath.js +139 -0
- package/dist/eslint-rules/temp-root-realpath.js.map +1 -0
- package/dist/eslint-rules/tex-build.d.ts +35 -0
- package/dist/eslint-rules/tex-build.d.ts.map +1 -0
- package/dist/eslint-rules/tex-build.js +313 -0
- package/dist/eslint-rules/tex-build.js.map +1 -0
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +31 -27
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts +1 -1
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +31 -11
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -9
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +33 -15
- package/dist/init.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -2
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js +2 -2
- package/dist/latex-loop.js.map +1 -1
- package/dist/lib/agent-cli-version.d.ts +65 -0
- package/dist/lib/agent-cli-version.d.ts.map +1 -0
- package/dist/lib/agent-cli-version.js +77 -0
- package/dist/lib/agent-cli-version.js.map +1 -0
- package/dist/lib/check.d.ts +19 -0
- package/dist/lib/check.d.ts.map +1 -0
- package/dist/lib/check.js +52 -0
- package/dist/lib/check.js.map +1 -0
- package/dist/lib/markdown.d.ts +180 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/dist/lib/markdown.js +302 -0
- package/dist/lib/markdown.js.map +1 -0
- package/dist/lib/paper-config.d.ts +84 -0
- package/dist/lib/paper-config.d.ts.map +1 -0
- package/dist/lib/paper-config.js +138 -0
- package/dist/lib/paper-config.js.map +1 -0
- package/dist/lib/skill-eval-fixture.d.ts +29 -0
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -0
- package/dist/lib/skill-eval-fixture.js +62 -0
- package/dist/lib/skill-eval-fixture.js.map +1 -0
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +5 -4
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +7 -3
- package/dist/new-paper.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/paper-settings.js +8 -7
- 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 +25 -16
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +3 -4
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/ports/tool-installer.d.ts +2 -14
- package/dist/ports/tool-installer.d.ts.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +15 -10
- 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 +15 -12
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +4 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +22 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +21 -16
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +21 -8
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +2 -2
- package/docs/e2e.md +7 -7
- 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 +2 -2
- package/docs/testing.md +11 -8
- package/docs/toolchain.md +1 -1
- package/eslint-rules/{bib-reachable-entry.mjs → bib-reachable-entry.ts} +5 -4
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +18 -10
- package/eslint-rules/latex-language.harness.mjs +2 -2
- package/eslint-rules/latex-language.test.mjs +7 -1
- package/eslint-rules/{latex-language.mjs → latex-language.ts} +297 -109
- package/eslint-rules/paper-research-question.harness.mjs +3 -3
- package/eslint-rules/{paper-research-question.mjs → paper-research-question.ts} +35 -16
- package/eslint-rules/paper-stages.harness.mjs +2 -2
- package/eslint-rules/paper-stages.test.mjs +8 -2
- package/eslint-rules/{paper-stages.mjs → paper-stages.ts} +76 -31
- package/eslint-rules/paper-typography.test.mjs +3 -3
- package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +134 -56
- package/eslint-rules/papers.harness.mjs +2 -2
- package/eslint-rules/{papers.mjs → papers.ts} +7 -4
- package/eslint-rules/pdf-last-page-balance.harness.mjs +3 -3
- package/eslint-rules/{pdf-last-page-balance.mjs → pdf-last-page-balance.ts} +89 -32
- package/eslint-rules/review-frontmatter.test.mjs +2 -2
- package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +38 -16
- package/eslint-rules/rule-context.ts +62 -0
- package/eslint-rules/{sibling-frontmatter.mjs → sibling-frontmatter.ts} +1 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +1 -1
- package/eslint-rules/{temp-root-realpath.mjs → temp-root-realpath.ts} +20 -7
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/{tex-build.mjs → tex-build.ts} +6 -5
- package/eslint.config.mjs +146 -30
- 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 +2 -2
- package/fixtures/real-markdown-paper/{baseline.mjs → baseline.ts} +31 -10
- package/hooks/{hooks.harness.mjs → hooks.harness.ts} +51 -34
- package/hooks/paper-edit-guard.hook.mjs +1 -1
- package/hooks/{paper-edit-guard.test.mjs → paper-edit-guard.test.ts} +3 -7
- package/hooks/paper-skills-nudge.hook.mjs +1 -1
- package/hooks/{paper-skills-nudge.test.mjs → paper-skills-nudge.test.ts} +2 -6
- package/hooks/paper-status-gates.sh +9 -19
- package/hooks/{paper-status-gates.test.mjs → paper-status-gates.test.ts} +2 -6
- package/lib/agent-cli-version.harness.mjs +1 -1
- package/lib/{agent-cli-version.mjs → agent-cli-version.ts} +32 -18
- package/lib/check.test.ts +14 -9
- package/lib/{check.mjs → check.ts} +22 -9
- package/lib/markdown.test.mjs +7 -7
- package/lib/{markdown.mjs → markdown.ts} +68 -29
- package/lib/paper-config.harness.mjs +9 -9
- package/lib/{paper-config.mjs → paper-config.ts} +20 -19
- package/lib/skill-checks.mjs +24 -381
- package/lib/skill-checks.test.mjs +64 -116
- package/lib/skill-corpus.mjs +10 -251
- package/lib/skill-corpus.test.mjs +2 -13
- package/lib/skill-eval-kit.mjs +1 -1
- package/lib/skill-trigger-cases.mjs +1 -1
- package/package.json +42 -10
- package/scripts/{check.harness.mjs → check.harness.ts} +111 -10
- package/scripts/{check.test.mjs → check.test.ts} +13 -10
- package/scripts/{check.mjs → check.ts} +116 -42
- package/scripts/coverage-config.test.ts +80 -38
- package/scripts/eslint-report-guard.test.mjs +1 -1
- package/scripts/harness-api.frozen.json +14 -16
- package/scripts/harness-api.test.ts +8 -5
- package/scripts/layer-legacy-frozen.test.ts +17 -13
- package/scripts/{layer-legacy-frozen.mjs → layer-legacy-frozen.ts} +128 -39
- package/scripts/release-config.test.ts +12 -4
- package/scripts/rules-see-files.test.mjs +1 -1
- package/scripts/test.test.ts +78 -0
- package/scripts/test.ts +87 -0
- 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 +3 -4
- package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +3 -3
- 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.mjs → 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/SKILL.md +2 -28
- package/skills/paper-pipeline/SKILL.md.spec.ts +1 -27
- package/skills/paper-pipeline/description-language.eval.mjs +1 -1
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +2 -2
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- 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/artifact-coverage.mjs +1 -1
- package/skills/paper-pipeline/scripts/{artifact-coverage.test.mjs → artifact-coverage.test.ts} +2 -2
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
- package/skills/paper-pipeline/scripts/consumer.mjs +24 -109
- package/skills/paper-pipeline/scripts/consumer.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +1 -1
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +1 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
- package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/population-map.mjs +1 -4
- package/skills/paper-pipeline/scripts/population-map.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/{round-diff.harness.mjs → round-diff.harness.ts} +45 -18
- package/skills/paper-pipeline/scripts/round-diff.mjs +1 -1
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +1 -1
- 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/plan-paper-timeline/fixtures/{fake-google-calendar.mjs → fake-google-calendar.ts} +101 -70
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +3 -3
- package/skills/render-paper/SKILL.md +1 -37
- package/skills/render-paper/SKILL.md.spec.ts +0 -36
- package/skills/render-paper/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- 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 +5 -12
- package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
- package/skills/verify-citations/SKILL.md +2 -38
- package/skills/verify-citations/SKILL.md.spec.ts +1 -37
- package/skills/verify-citations/scripts/{bib-authors.cli.test.mjs → bib-authors.cli.test.ts} +14 -4
- package/skills/verify-citations/scripts/bib-authors.d.mts +63 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +1 -4
- package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts} +32 -9
- package/skills/verify-citations/scripts/verify-cites.d.mts +58 -0
- package/src/CLAUDE.md +1 -1
- package/src/adapters/banal/failure.ts +9 -10
- package/src/adapters/banal/index.test.ts +5 -8
- package/src/adapters/banal/index.ts +8 -7
- package/src/adapters/banal/install.ts +9 -4
- package/src/adapters/banal/invocation.test.ts +23 -9
- 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 +5 -5
- 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 +8 -6
- package/src/adapters/memory/index.ts +4 -3
- package/src/adapters/node/files.io.ts +2 -2
- package/src/adapters/node/files.test.ts +8 -4
- package/src/adapters/node/process.io.ts +13 -5
- package/src/adapters/node/process.test.ts +5 -5
- package/src/adapters/node/workspace.io.ts +9 -4
- package/src/adapters/node/workspace.test.ts +3 -1
- package/src/adapters/pdfjs/fill.test.ts +2 -2
- package/src/adapters/pdfjs/fill.ts +14 -7
- package/src/adapters/references/index.test.ts +31 -38
- package/src/adapters/references/index.ts +33 -60
- package/src/adapters/references/pool.ts +3 -5
- package/src/adapters/references/reach.io.ts +2 -1
- package/src/build-engine.harness.mjs +1 -1
- package/src/build-engine.test.ts +13 -8
- package/src/build-engine.ts +12 -7
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +24 -24
- package/src/build.ts +34 -26
- package/src/caller-path.ts +13 -0
- package/src/cli.harness.mjs +6 -8
- package/src/cli.test.ts +31 -13
- package/src/cli.ts +117 -58
- package/src/doctor.harness.mjs +3 -3
- package/src/doctor.test.ts +1 -1
- package/src/doctor.ts +17 -7
- package/src/domain/lookup-cache.ts +21 -11
- 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/result.test.ts +2 -2
- package/src/domain/sha256.ts +5 -3
- package/src/domain/text.test.ts +32 -0
- package/src/domain/text.ts +23 -0
- package/src/engine.harness.mjs +2 -2
- package/src/engine.ts +34 -5
- package/src/facts-file.test.ts +9 -7
- package/src/facts-file.ts +45 -29
- package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +56 -20
- package/src/hooks-settings.ts +48 -13
- package/src/init.test.ts +37 -14
- package/src/init.ts +48 -24
- package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
- package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
- package/src/latex-loop.ts +3 -3
- package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
- package/src/link-skills.test.ts +3 -3
- package/src/link-skills.ts +5 -4
- package/src/lint-fix.test.ts +4 -6
- package/src/linted-set.test.ts +2 -2
- package/src/new-paper.harness.mjs +9 -25
- package/src/new-paper.test.ts +8 -10
- package/src/new-paper.ts +9 -5
- package/src/paper-files.test.ts +25 -24
- package/src/paper-files.ts +16 -27
- package/src/paper-settings-commands.test.ts +5 -4
- package/src/paper-settings.test.ts +7 -7
- package/src/paper-settings.ts +13 -14
- package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
- package/src/pdf-facts.test.ts +25 -15
- package/src/pdf-facts.ts +81 -23
- package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
- package/src/pdf-geometry.ts +8 -4
- package/src/ports/tool-installer.ts +2 -17
- package/src/presets.ts +31 -16
- package/src/references.test.ts +76 -62
- package/src/references.ts +18 -12
- package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
- package/src/rules-config.ts +46 -14
- package/src/rules-off.test.ts +6 -4
- package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
- package/src/structure.ts +1 -1
- package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
- package/src/tex-requirements.test.ts +21 -0
- package/src/tex-requirements.ts +37 -11
- package/src/toolchain.harness.mjs +2 -2
- package/src/toolchain.test.ts +10 -6
- package/src/toolchain.ts +31 -17
- package/src/types.ts +5 -3
- package/src/venue-rules.test.ts +70 -68
- package/src/venue-rules.ts +22 -12
- package/tsconfig.json +6 -3
- package/tsconfig.pkg.json +10 -0
- package/lib/check.d.mts +0 -17
- package/lib/paper-config.d.mts +0 -31
- package/scripts/layer-legacy-frozen.d.mts +0 -36
- package/scripts/rules-are-content-only.harness.mjs +0 -105
- package/scripts/rules-are-content-only.mjs +0 -148
- package/scripts/rules-are-content-only.test.mjs +0 -61
- package/scripts/test.mjs +0 -44
- package/scripts/test.test.mjs +0 -52
- 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.mjs +0 -71
- 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 -753
- 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/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
- /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
- /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
- /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperlint",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"prepare": "tsc",
|
|
10
|
-
"test": "node scripts/test.
|
|
11
|
-
"coverage": "NODE_OPTIONS=\"--import=./test/coverage-src.
|
|
8
|
+
"build": "tsc -p tsconfig.pkg.json && tsc",
|
|
9
|
+
"prepare": "tsc -p tsconfig.pkg.json && tsc",
|
|
10
|
+
"test": "node scripts/test.ts",
|
|
11
|
+
"coverage": "NODE_OPTIONS=\"--import=./test/coverage-src.ts $NODE_OPTIONS\" c8 --check-coverage npm test --",
|
|
12
12
|
"lint": "eslint .",
|
|
13
|
-
"check": "node scripts/check.
|
|
14
|
-
"test:e2e": "node test/e2e/install.
|
|
13
|
+
"check": "node scripts/check.ts",
|
|
14
|
+
"test:e2e": "node test/e2e/install.ts && node test/e2e/build.ts",
|
|
15
15
|
"fmt": "prettier --write .",
|
|
16
16
|
"fmt:check": "prettier --check ."
|
|
17
17
|
},
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"js-yaml": "^4.1.0",
|
|
25
25
|
"markdown-it": "^14.3.0",
|
|
26
26
|
"ts-essentials": "^10.2.1",
|
|
27
|
-
"typescript": "^
|
|
27
|
+
"typescript": "^6.0.3",
|
|
28
28
|
"unpdf": "^1.8.1",
|
|
29
29
|
"vigiles": "^31.0.1",
|
|
30
30
|
"zod": "^4.6.5"
|
|
@@ -33,9 +33,12 @@
|
|
|
33
33
|
"@retorquere/bibtex-parser": "^10.0.1",
|
|
34
34
|
"@semantic-release/commit-analyzer": "13.0.1",
|
|
35
35
|
"@semantic-release/release-notes-generator": "14.1.1",
|
|
36
|
+
"@types/js-yaml": "^4.0.9",
|
|
37
|
+
"@types/markdown-it": "^14.2.0",
|
|
36
38
|
"@types/node": "^26.6.1",
|
|
37
39
|
"c8": "^12.0.0",
|
|
38
40
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
41
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
39
42
|
"eslint-plugin-boundaries": "^7.2.0",
|
|
40
43
|
"eslint-plugin-n": "^18.3.0",
|
|
41
44
|
"prettier": "^3.8.1",
|
|
@@ -72,6 +75,22 @@
|
|
|
72
75
|
"bin": {
|
|
73
76
|
"paperlint": "bin/paperlint.mjs"
|
|
74
77
|
},
|
|
78
|
+
"exports": {
|
|
79
|
+
"./eslint-rules/*.mjs": {
|
|
80
|
+
"types": "./dist/eslint-rules/*.d.ts",
|
|
81
|
+
"default": "./dist/eslint-rules/*.js"
|
|
82
|
+
},
|
|
83
|
+
"./lib/skill-checks.mjs": "./lib/skill-checks.mjs",
|
|
84
|
+
"./lib/skill-corpus.mjs": "./lib/skill-corpus.mjs",
|
|
85
|
+
"./lib/skill-eval-kit.mjs": "./lib/skill-eval-kit.mjs",
|
|
86
|
+
"./lib/skill-trigger-cases.mjs": "./lib/skill-trigger-cases.mjs",
|
|
87
|
+
"./lib/trigger-ledger.mjs": "./lib/trigger-ledger.mjs",
|
|
88
|
+
"./lib/*.mjs": {
|
|
89
|
+
"types": "./dist/lib/*.d.ts",
|
|
90
|
+
"default": "./dist/lib/*.js"
|
|
91
|
+
},
|
|
92
|
+
"./*": "./*"
|
|
93
|
+
},
|
|
75
94
|
"files": [
|
|
76
95
|
".github",
|
|
77
96
|
"CLAUDE.md",
|
|
@@ -93,6 +112,19 @@
|
|
|
93
112
|
"skills",
|
|
94
113
|
"src",
|
|
95
114
|
"templates",
|
|
96
|
-
"tsconfig.json"
|
|
97
|
-
|
|
115
|
+
"tsconfig.json",
|
|
116
|
+
"tsconfig.pkg.json"
|
|
117
|
+
],
|
|
118
|
+
"imports": {
|
|
119
|
+
"#lib/*": {
|
|
120
|
+
"paperlint-source": "./lib/*.ts",
|
|
121
|
+
"types": "./dist/lib/*.d.ts",
|
|
122
|
+
"default": "./dist/lib/*.js"
|
|
123
|
+
},
|
|
124
|
+
"#eslint-rules/*": {
|
|
125
|
+
"paperlint-source": "./eslint-rules/*.ts",
|
|
126
|
+
"types": "./dist/eslint-rules/*.d.ts",
|
|
127
|
+
"default": "./dist/eslint-rules/*.js"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
98
130
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* and a suite-breaking change was pushed. Writing the subset down in a doc would have produced
|
|
8
8
|
* the same outcome one release later — the list would simply have been wrong instead of absent.
|
|
9
9
|
*
|
|
10
|
-
* So the assertions below do not check that `check.
|
|
10
|
+
* So the assertions below do not check that `check.ts` contains the right strings. They pull
|
|
11
11
|
* the job names OUT OF `.github/workflows/ci.yml` and require every one to be accounted for.
|
|
12
12
|
* Add a job to CI and this goes red the same day, naming the job nobody covered.
|
|
13
13
|
*
|
|
@@ -18,13 +18,50 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
18
18
|
import { dirname, join } from "node:path";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { load } from "js-yaml";
|
|
21
|
-
import {
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { createChecker } from "../lib/check.ts";
|
|
23
|
+
import type { Gate } from "./check.ts";
|
|
24
|
+
|
|
25
|
+
/** A workflow file, as far as these checks read it. `on` is a plain key: js-yaml reads YAML 1.2. */
|
|
26
|
+
const Step = z.looseObject({
|
|
27
|
+
name: z.string().optional(),
|
|
28
|
+
run: z.string().optional(),
|
|
29
|
+
uses: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
const Workflow = z.looseObject({
|
|
32
|
+
on: z
|
|
33
|
+
.looseObject({
|
|
34
|
+
pull_request: z
|
|
35
|
+
.looseObject({ types: z.array(z.string()).optional() })
|
|
36
|
+
.nullish(),
|
|
37
|
+
})
|
|
38
|
+
.optional(),
|
|
39
|
+
jobs: z
|
|
40
|
+
.record(
|
|
41
|
+
z.string(),
|
|
42
|
+
z.looseObject({
|
|
43
|
+
steps: z.array(Step).optional(),
|
|
44
|
+
strategy: z
|
|
45
|
+
.looseObject({
|
|
46
|
+
matrix: z
|
|
47
|
+
.looseObject({ os: z.array(z.string()).optional() })
|
|
48
|
+
.optional(),
|
|
49
|
+
})
|
|
50
|
+
.optional(),
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
.optional(),
|
|
54
|
+
});
|
|
55
|
+
type Workflow = z.infer<typeof Workflow>;
|
|
56
|
+
const PackageJson = z.looseObject({
|
|
57
|
+
scripts: z.record(z.string(), z.string()).optional(),
|
|
58
|
+
});
|
|
22
59
|
|
|
23
60
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
24
61
|
const ROOT = dirname(HERE);
|
|
25
62
|
|
|
26
63
|
const { GATES, NOT_COVERED, outcome, SKIP_EXIT, commandOf } =
|
|
27
|
-
await import("./check.
|
|
64
|
+
await import("./check.ts");
|
|
28
65
|
|
|
29
66
|
const check = createChecker();
|
|
30
67
|
|
|
@@ -34,7 +71,7 @@ const check = createChecker();
|
|
|
34
71
|
// command red for anyone without pnpm or TeX, and it would be ignored.
|
|
35
72
|
check("exit 0 is a pass", outcome(0) === "pass");
|
|
36
73
|
check(
|
|
37
|
-
`exit ${SKIP_EXIT} is a skip — neither a pass nor a failure`,
|
|
74
|
+
`exit ${String(SKIP_EXIT)} is a skip — neither a pass nor a failure`,
|
|
38
75
|
outcome(SKIP_EXIT) === "skip",
|
|
39
76
|
);
|
|
40
77
|
check("the skip code is the one vigiles' runner uses (77)", SKIP_EXIT === 77);
|
|
@@ -54,7 +91,10 @@ const WF_DIR = join(ROOT, ".github", "workflows");
|
|
|
54
91
|
const workflows = Object.fromEntries(
|
|
55
92
|
readdirSync(WF_DIR)
|
|
56
93
|
.filter((f) => f.endsWith(".yml"))
|
|
57
|
-
.map((f) => [
|
|
94
|
+
.map((f) => [
|
|
95
|
+
f,
|
|
96
|
+
Workflow.parse(load(readFileSync(join(WF_DIR, f), "utf-8"))),
|
|
97
|
+
]),
|
|
58
98
|
);
|
|
59
99
|
const ciJobs = Object.values(workflows).flatMap((wf) =>
|
|
60
100
|
Object.keys(wf.jobs ?? {}),
|
|
@@ -67,7 +107,8 @@ check(
|
|
|
67
107
|
// ── macOS: a cell of the TeX job, on every push ─────────────────────────────────────────
|
|
68
108
|
// install-tl-unx must work there, and issue #9 appears there alone. The trigger used to be a
|
|
69
109
|
// separate once-per-PR workflow; the repository is public, so the minutes are free.
|
|
70
|
-
const prTypes = (wf
|
|
110
|
+
const prTypes = (wf: Workflow | undefined): string[] =>
|
|
111
|
+
wf?.on?.pull_request?.types ?? [];
|
|
71
112
|
const buildOs =
|
|
72
113
|
workflows["ci.yml"]?.jobs?.["build-e2e"]?.strategy?.matrix?.os ?? [];
|
|
73
114
|
check(
|
|
@@ -114,7 +155,9 @@ for (const job of Object.keys(NOT_COVERED)) {
|
|
|
114
155
|
// A gate whose script or file was renamed fails at the moment someone runs it — which is
|
|
115
156
|
// exactly the moment they are trusting it. Catch it here instead.
|
|
116
157
|
const scripts =
|
|
117
|
-
|
|
158
|
+
PackageJson.parse(
|
|
159
|
+
JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8")),
|
|
160
|
+
).scripts ?? {};
|
|
118
161
|
for (const g of GATES) {
|
|
119
162
|
check(
|
|
120
163
|
`gate «${g.name}» says how to run it in exactly one way (script or run)`,
|
|
@@ -130,7 +173,7 @@ for (const g of GATES) {
|
|
|
130
173
|
// A `run` gate names files and programs directly. Every file it names must be on disk, and
|
|
131
174
|
// every program other than `node` must be installed in node_modules/.bin.
|
|
132
175
|
const argv = commandOf(g);
|
|
133
|
-
const files = argv.filter((a) => /\.
|
|
176
|
+
const files = argv.filter((a) => /\.[cm]?[jt]s$/.test(a));
|
|
134
177
|
const programs = [argv[0]];
|
|
135
178
|
// Guards: a gate runs SOMETHING — a script of ours, or an installed program such as `tsc`.
|
|
136
179
|
check(
|
|
@@ -159,6 +202,64 @@ for (const g of GATES.filter((g) => g.job === null)) {
|
|
|
159
202
|
);
|
|
160
203
|
}
|
|
161
204
|
|
|
205
|
+
// ── HALF FIVE: the gate's command RUNS in the job it names ─────────────────────────────────
|
|
206
|
+
// Half two proves the job exists; it does not prove the job runs the gate. `the tests type-check`
|
|
207
|
+
// claimed `gates` for weeks while no step of that job ran tsc — the claim was the only coverage.
|
|
208
|
+
// So each step's `run` is split into commands (lines, `&&`, `||`, `;`) and each command into
|
|
209
|
+
// tokens, and the gate's own command has to be one of them: `npm run <script>` (flags and extra
|
|
210
|
+
// arguments allowed) for a script gate, its argv (an `npx` in front and extra arguments allowed)
|
|
211
|
+
// for a run gate. Tokens, not a substring: `npm run lint` must not be found inside
|
|
212
|
+
// `npm run lint:skills`, nor a path inside a comment.
|
|
213
|
+
const commandsIn = (run: string | undefined): string[][] =>
|
|
214
|
+
(run ?? "")
|
|
215
|
+
.split(/\n|&&|\|\||;/)
|
|
216
|
+
.map((c) => c.trim().split(/\s+/).filter(Boolean))
|
|
217
|
+
.filter((t) => t.length > 0);
|
|
218
|
+
const stepsOf = (job: string) =>
|
|
219
|
+
Object.values(workflows).flatMap((wf) => wf.jobs?.[job]?.steps ?? []);
|
|
220
|
+
const startsWith = (tokens: readonly string[], prefix: readonly string[]) =>
|
|
221
|
+
prefix.every((p, i) => tokens[i] === p);
|
|
222
|
+
const runsGate = (tokens: readonly string[], g: Gate): boolean => {
|
|
223
|
+
if (g.script)
|
|
224
|
+
return (
|
|
225
|
+
tokens[0] === "npm" &&
|
|
226
|
+
tokens[1] === "run" &&
|
|
227
|
+
tokens.slice(2).find((t) => !t.startsWith("-")) === g.script
|
|
228
|
+
);
|
|
229
|
+
const argv = commandOf(g);
|
|
230
|
+
return (
|
|
231
|
+
startsWith(tokens, argv) ||
|
|
232
|
+
(tokens[0] === "npx" && startsWith(tokens.slice(1), argv))
|
|
233
|
+
);
|
|
234
|
+
};
|
|
235
|
+
for (const g of GATES) {
|
|
236
|
+
if (g.job === null) continue;
|
|
237
|
+
const steps = stepsOf(g.job);
|
|
238
|
+
const { inCi } = g;
|
|
239
|
+
if (inCi) {
|
|
240
|
+
// A gate CI runs as a side effect of another step, through an npm lifecycle script: the step
|
|
241
|
+
// it names must exist in the job and install the package (npm runs lifecycle scripts there),
|
|
242
|
+
// and the lifecycle script must be the gate's own command, so the two cannot drift apart.
|
|
243
|
+
const step = steps.find((s) => s.name === inCi.step);
|
|
244
|
+
const tokens = commandsIn(step?.run)[0] ?? [];
|
|
245
|
+
check(
|
|
246
|
+
`gate «${g.name}» runs in CI job «${g.job}» through step «${inCi.step}», whose ${inCi.lifecycle} script is the gate's command`,
|
|
247
|
+
step !== undefined &&
|
|
248
|
+
tokens[0] === "npm" &&
|
|
249
|
+
["ci", "install"].includes(tokens[1] ?? "") &&
|
|
250
|
+
typeof scripts[g.script] === "string" &&
|
|
251
|
+
scripts[inCi.lifecycle] === scripts[g.script],
|
|
252
|
+
JSON.stringify({ run: step?.run, lifecycle: scripts[inCi.lifecycle] }),
|
|
253
|
+
);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
check(
|
|
257
|
+
`gate «${g.name}» runs its command (${commandOf(g).join(" ")}) in a step of CI job «${g.job}»`,
|
|
258
|
+
steps.some((s) => commandsIn(s.run).some((t) => runsGate(t, g))),
|
|
259
|
+
steps.map((s) => s.run ?? `(uses ${String(s.uses)})`).join(" | "),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
162
263
|
// ── and the tail is not optional ───────────────────────────────────────────────────────────
|
|
163
264
|
check(
|
|
164
265
|
"NOT_COVERED is non-empty — if it ever is, either CI shrank or someone silenced the tail",
|
|
@@ -166,6 +267,6 @@ check(
|
|
|
166
267
|
);
|
|
167
268
|
|
|
168
269
|
console.log(
|
|
169
|
-
`✓ ${String(check.count)} assertions passed — npm run check: ${GATES.length} gates, ` +
|
|
170
|
-
`${ciJobs.length} CI job(s) all accounted for`,
|
|
270
|
+
`✓ ${String(check.count)} assertions passed — npm run check: ${String(GATES.length)} gates, ` +
|
|
271
|
+
`${String(ciJobs.length)} CI job(s) all accounted for`,
|
|
171
272
|
);
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* check.
|
|
2
|
+
* check.ts's runGates — the one command run before a push — on every outcome, with an injected
|
|
3
3
|
* spawn: a pass, a declared skip (77) and a failure, and THE TAIL that names what it does not cover.
|
|
4
4
|
*/
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { test } from "vitest";
|
|
9
|
-
import { GATES, NOT_COVERED, runGates } from "./check.
|
|
9
|
+
import { GATES, NOT_COVERED, runGates, type Gate } from "./check.ts";
|
|
10
10
|
|
|
11
11
|
const capture = () => {
|
|
12
|
-
const out = [];
|
|
13
|
-
const io = {
|
|
12
|
+
const out: string[] = [];
|
|
13
|
+
const io = {
|
|
14
|
+
log: (s: string) => out.push(s),
|
|
15
|
+
err: (s: string) => out.push(`E ${s}`),
|
|
16
|
+
};
|
|
14
17
|
return { out, io };
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
test("runGates: a pass, a declared skip (77) and a failure, then the tail naming what is not covered", () => {
|
|
18
|
-
const gates = [
|
|
21
|
+
const gates: Gate[] = [
|
|
19
22
|
{ name: "passes", job: "gates", run: ["true"] },
|
|
20
23
|
{ name: "skips", job: "gates", run: ["skipper", "--x"] },
|
|
21
24
|
{
|
|
@@ -25,12 +28,12 @@ test("runGates: a pass, a declared skip (77) and a failure, then the tail naming
|
|
|
25
28
|
run: ["false"],
|
|
26
29
|
},
|
|
27
30
|
];
|
|
28
|
-
const status = { true: 0, skipper: 77, false: 3 };
|
|
29
|
-
const written = [];
|
|
31
|
+
const status: Record<string, number> = { true: 0, skipper: 77, false: 3 };
|
|
32
|
+
const written: string[] = [];
|
|
30
33
|
const { out, io } = capture();
|
|
31
34
|
const code = runGates({
|
|
32
35
|
gates,
|
|
33
|
-
spawn: (cmd) => ({ status: status[cmd] }),
|
|
36
|
+
spawn: (cmd) => ({ status: status[cmd] ?? null }),
|
|
34
37
|
write: (s) => written.push(s),
|
|
35
38
|
...io,
|
|
36
39
|
});
|
|
@@ -79,7 +82,7 @@ test("runGates: all passing, with a skip, says how many passed and that the skip
|
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
test("run as a program with every gate passing: the plain verdict, exit 0", () => {
|
|
82
|
-
// spawnSync is replaced before check.
|
|
85
|
+
// spawnSync is replaced before check.ts loads, so the real file runs end to end — every gate
|
|
83
86
|
// "passes" in microseconds instead of the eleven real ones running.
|
|
84
87
|
const allPass = [
|
|
85
88
|
'import cp from "node:child_process";',
|
|
@@ -92,7 +95,7 @@ test("run as a program with every gate passing: the plain verdict, exit 0", () =
|
|
|
92
95
|
[
|
|
93
96
|
"--import",
|
|
94
97
|
`data:text/javascript,${encodeURIComponent(allPass)}`,
|
|
95
|
-
fileURLToPath(new URL("./check.
|
|
98
|
+
fileURLToPath(new URL("./check.ts", import.meta.url)),
|
|
96
99
|
],
|
|
97
100
|
{ encoding: "utf8" },
|
|
98
101
|
);
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* boundary reads as complete, which is the same class as a counter that counts what it ignores.
|
|
20
20
|
*
|
|
21
21
|
* ── WHAT KEEPS THE LIST FROM GOING STALE ───────────────────────────────────────────────────
|
|
22
|
-
* `check.harness.
|
|
22
|
+
* `check.harness.ts` sits beside it and pulls the job names OUT OF THE WORKFLOW ITSELF,
|
|
23
23
|
* requiring every one to be either covered here or named in `NOT_COVERED` with a reason. Add a
|
|
24
24
|
* job to CI and the harness goes red the same day — the list cannot quietly fall behind.
|
|
25
25
|
*
|
|
@@ -53,11 +53,63 @@ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
53
53
|
* Programs installed by npm (`vigiles`, `eslint`) are found because `node_modules/.bin` is put
|
|
54
54
|
* first on PATH below, the same way `npm run` does it.
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* How CI runs a gate that has no step of its own: the step that runs it as a side effect, and the
|
|
58
|
+
* npm lifecycle script through which it does. `check.harness.ts` requires that step in the job
|
|
59
|
+
* and that lifecycle script to be the gate's own command, so the two cannot drift apart.
|
|
60
|
+
*/
|
|
61
|
+
export interface RunInCi {
|
|
62
|
+
readonly step: string;
|
|
63
|
+
readonly lifecycle: "prepare";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A gate runs either an npm script or a command of its own — never both. Only an npm script can be
|
|
68
|
+
* run by CI through a lifecycle script (`inCi`); every other gate has a step that runs its command.
|
|
69
|
+
*/
|
|
70
|
+
type GateCommand =
|
|
71
|
+
| {
|
|
72
|
+
readonly script: string;
|
|
73
|
+
readonly run?: undefined;
|
|
74
|
+
readonly inCi?: RunInCi;
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
readonly run: readonly [string, ...string[]];
|
|
78
|
+
readonly script?: undefined;
|
|
79
|
+
readonly inCi?: undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A gate reproduces a CI job, or runs only here and says why (`job: null` needs a `reason`, or it
|
|
84
|
+
* becomes the quiet way to drop something out of CI).
|
|
85
|
+
*/
|
|
86
|
+
export type Gate = { readonly name: string } & GateCommand &
|
|
87
|
+
(
|
|
88
|
+
| { readonly job: string; readonly reason?: undefined }
|
|
89
|
+
| { readonly job: null; readonly reason: string }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/** What `runGates` needs from a finished process: its exit status, `null` when a signal ended it. */
|
|
93
|
+
export type Spawn = (
|
|
94
|
+
cmd: string,
|
|
95
|
+
args: readonly string[],
|
|
96
|
+
options: {
|
|
97
|
+
cwd: string;
|
|
98
|
+
stdio: "inherit";
|
|
99
|
+
encoding: "utf8";
|
|
100
|
+
env: NodeJS.ProcessEnv;
|
|
101
|
+
},
|
|
102
|
+
) => { readonly status: number | null };
|
|
103
|
+
|
|
104
|
+
export type Outcome = "pass" | "skip" | "fail";
|
|
105
|
+
|
|
106
|
+
export const GATES: readonly Gate[] = [
|
|
57
107
|
{
|
|
58
108
|
name: "the package compiles",
|
|
59
109
|
job: "gates",
|
|
60
110
|
script: "build",
|
|
111
|
+
// CI has no `npm run build` step: `npm ci` runs `prepare`, which is `tsc` as well.
|
|
112
|
+
inCi: { step: "npm ci", lifecycle: "prepare" },
|
|
61
113
|
// First because everything below imports from `dist/`. A stale build makes every later
|
|
62
114
|
// failure a lie about its own cause.
|
|
63
115
|
},
|
|
@@ -86,17 +138,6 @@ export const GATES = [
|
|
|
86
138
|
job: "gates",
|
|
87
139
|
run: ["node", "scripts/rules-see-files.mjs"],
|
|
88
140
|
},
|
|
89
|
-
{
|
|
90
|
-
name: "rules read content, not the filesystem",
|
|
91
|
-
job: null,
|
|
92
|
-
// 🔴 Deliberately not in CI, and this is the one asymmetry worth stating rather than
|
|
93
|
-
// hiding: the check is about a property of the rules' source, which cannot change between
|
|
94
|
-
// a developer's tree and the runner's. Running it twice buys nothing; NOT running it
|
|
95
|
-
// locally buys a defect that reaches review.
|
|
96
|
-
reason:
|
|
97
|
-
"source-only property — identical in every environment, so CI adds nothing",
|
|
98
|
-
run: ["node", "scripts/rules-are-content-only.mjs"],
|
|
99
|
-
},
|
|
100
141
|
{
|
|
101
142
|
name: "every test, under coverage — vitest, then the vigiles harnesses, with c8's thresholds (npm run coverage)",
|
|
102
143
|
job: "gates",
|
|
@@ -114,40 +155,42 @@ export const GATES = [
|
|
|
114
155
|
{
|
|
115
156
|
name: "legacy layer exemptions are frozen — none new, none grown (#76)",
|
|
116
157
|
job: "gates",
|
|
117
|
-
run: ["node", "scripts/layer-legacy-frozen.
|
|
158
|
+
run: ["node", "scripts/layer-legacy-frozen.ts"],
|
|
118
159
|
},
|
|
119
160
|
{
|
|
120
161
|
name: "install e2e — pack, install under npm and pnpm, run the binary",
|
|
121
162
|
job: "gates",
|
|
122
|
-
run: ["node", "test/e2e/install.
|
|
163
|
+
run: ["node", "test/e2e/install.ts"],
|
|
123
164
|
},
|
|
124
165
|
{
|
|
125
166
|
name: "build e2e — a real pdflatex, and the PDF's fonts are measured",
|
|
126
167
|
job: "build-e2e",
|
|
127
|
-
run: ["node", "test/e2e/build.
|
|
168
|
+
run: ["node", "test/e2e/build.ts"],
|
|
128
169
|
},
|
|
129
170
|
{
|
|
130
171
|
name: "banal e2e — the real banal on pdf.js-written XML gives banal-on-pdftohtml's numbers",
|
|
131
172
|
job: "build-e2e",
|
|
132
|
-
run: ["node", "test/e2e/banal.
|
|
173
|
+
run: ["node", "test/e2e/banal.ts"],
|
|
133
174
|
},
|
|
134
175
|
{
|
|
135
176
|
name: "toolchain e2e — real TeX Live into $PAPERLINT_TEXLIVE_DIR, then a build with only it on PATH",
|
|
136
177
|
job: "build-e2e",
|
|
137
|
-
run: ["node", "test/e2e/toolchain.
|
|
178
|
+
run: ["node", "test/e2e/toolchain.ts"],
|
|
138
179
|
},
|
|
139
180
|
];
|
|
140
181
|
|
|
141
182
|
/** The command line a gate runs, as an argument list. */
|
|
142
|
-
export function commandOf(gate) {
|
|
143
|
-
return gate.script
|
|
183
|
+
export function commandOf(gate: Gate): readonly [string, ...string[]] {
|
|
184
|
+
return gate.script !== undefined
|
|
185
|
+
? ["npm", "run", "-s", gate.script]
|
|
186
|
+
: gate.run;
|
|
144
187
|
}
|
|
145
188
|
|
|
146
189
|
/**
|
|
147
190
|
* CI jobs this command does NOT reproduce. Each needs a reason a reader can check, because an
|
|
148
191
|
* unexplained gap is indistinguishable from an oversight — which is what the harness enforces.
|
|
149
192
|
*/
|
|
150
|
-
export const NOT_COVERED = {
|
|
193
|
+
export const NOT_COVERED: Readonly<Record<string, string>> = {
|
|
151
194
|
// build-e2e's macOS cell is the one part of a covered job no local command can stand in for:
|
|
152
195
|
// it exists because issue #9 appeared on macOS alone (/var is a symlink to /private/var).
|
|
153
196
|
merge:
|
|
@@ -169,31 +212,30 @@ export const NOT_COVERED = {
|
|
|
169
212
|
* this loop counted that as a pass, and `skipped` below was declared and never filled: a machine
|
|
170
213
|
* without pnpm or TeX printed "all gates passed" for runs that never happened.
|
|
171
214
|
*/
|
|
172
|
-
export const SKIP_EXIT = 77;
|
|
173
|
-
export function outcome(status) {
|
|
215
|
+
export const SKIP_EXIT: number = 77;
|
|
216
|
+
export function outcome(status: number | null): Outcome {
|
|
174
217
|
if (status === 0) return "pass";
|
|
175
218
|
if (status === SKIP_EXIT) return "skip";
|
|
176
219
|
return "fail";
|
|
177
220
|
}
|
|
178
221
|
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
} = {}) {
|
|
222
|
+
/** The gates that did not pass, each as the line the verdict prints, split by outcome. */
|
|
223
|
+
interface NotPassed {
|
|
224
|
+
failed: string[];
|
|
225
|
+
skipped: string[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Run each gate in order; `write` announces it before it starts. */
|
|
229
|
+
function runEach(
|
|
230
|
+
gates: readonly Gate[],
|
|
231
|
+
spawn: Spawn,
|
|
232
|
+
write: (text: string) => unknown,
|
|
233
|
+
): NotPassed {
|
|
192
234
|
const BIN_FIRST_PATH = [join(ROOT, "node_modules", ".bin"), process.env.PATH]
|
|
193
235
|
.filter(Boolean)
|
|
194
236
|
.join(delimiter);
|
|
195
|
-
const failed = [];
|
|
196
|
-
const skipped = [];
|
|
237
|
+
const failed: string[] = [];
|
|
238
|
+
const skipped: string[] = [];
|
|
197
239
|
|
|
198
240
|
for (const g of gates) {
|
|
199
241
|
write(`── ${g.name}\n`);
|
|
@@ -210,10 +252,19 @@ export function runGates({
|
|
|
210
252
|
const o = outcome(r.status);
|
|
211
253
|
if (o === "pass") continue;
|
|
212
254
|
const shown = commandOf(g).join(" ");
|
|
213
|
-
if (o === "skip")
|
|
214
|
-
|
|
255
|
+
if (o === "skip")
|
|
256
|
+
skipped.push(`${g.name} (${shown} → ${String(SKIP_EXIT)})`);
|
|
257
|
+
else failed.push(`${g.name} (${shown} → ${String(r.status)})`);
|
|
215
258
|
}
|
|
259
|
+
return { failed, skipped };
|
|
260
|
+
}
|
|
216
261
|
|
|
262
|
+
/** The verdict, then THE TAIL. */
|
|
263
|
+
function report(
|
|
264
|
+
gates: readonly Gate[],
|
|
265
|
+
{ failed, skipped }: NotPassed,
|
|
266
|
+
{ log, err }: { log: (line: string) => void; err: (line: string) => void },
|
|
267
|
+
): void {
|
|
217
268
|
log("");
|
|
218
269
|
if (failed.length) {
|
|
219
270
|
err("🔴 gates failed:");
|
|
@@ -221,7 +272,7 @@ export function runGates({
|
|
|
221
272
|
} else {
|
|
222
273
|
const passed = gates.length - skipped.length;
|
|
223
274
|
log(
|
|
224
|
-
`✓ ${passed} gate(s) passed${skipped.length ? `, ${skipped.length} SKIPPED — not run, not passed` : ""}`,
|
|
275
|
+
`✓ ${String(passed)} gate(s) passed${skipped.length ? `, ${String(skipped.length)} SKIPPED — not run, not passed` : ""}`,
|
|
225
276
|
);
|
|
226
277
|
}
|
|
227
278
|
for (const s of skipped) log(`⏳ skipped: ${s}`);
|
|
@@ -234,7 +285,30 @@ export function runGates({
|
|
|
234
285
|
for (const g of gates.filter((x) => x.job === null)) {
|
|
235
286
|
log(` (and «${g.name}» runs ONLY here — ${g.reason})`);
|
|
236
287
|
}
|
|
237
|
-
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Run every gate in order and print the verdict and THE TAIL. `spawn`, `log`, `err` and `write`
|
|
292
|
+
* are injected so a test can drive every outcome without running eleven real gates.
|
|
293
|
+
*
|
|
294
|
+
* @returns the exit code: 1 when a gate failed, else 0 (a skip is not a failure, and says so).
|
|
295
|
+
*/
|
|
296
|
+
export function runGates({
|
|
297
|
+
gates = GATES,
|
|
298
|
+
spawn = spawnSync,
|
|
299
|
+
log = console.log,
|
|
300
|
+
err = console.error,
|
|
301
|
+
write = (s: string) => process.stdout.write(s),
|
|
302
|
+
}: {
|
|
303
|
+
gates?: readonly Gate[];
|
|
304
|
+
spawn?: Spawn;
|
|
305
|
+
log?: (line: string) => void;
|
|
306
|
+
err?: (line: string) => void;
|
|
307
|
+
write?: (text: string) => unknown;
|
|
308
|
+
} = {}): number {
|
|
309
|
+
const notPassed = runEach(gates, spawn, write);
|
|
310
|
+
report(gates, notPassed, { log, err });
|
|
311
|
+
return notPassed.failed.length ? 1 : 0;
|
|
238
312
|
}
|
|
239
313
|
|
|
240
314
|
if (isMain(import.meta.url)) process.exit(runGates());
|