paperlint 3.1.2 → 3.2.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 +3 -3
- package/CONTRIBUTING.md +34 -5
- package/dist/adapters/banal/index.d.ts.map +1 -1
- package/dist/adapters/banal/index.js +9 -5
- package/dist/adapters/banal/index.js.map +1 -1
- package/dist/adapters/banal/invocation.d.ts +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 +0 -5
- 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/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 +4 -5
- 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.js +2 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +14 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -34
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +3 -2
- 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/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.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 +2 -1
- 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 +14 -9
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +5 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +15 -7
- 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 +142 -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 +2 -2
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +5 -4
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +2 -1
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -2
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +11 -6
- package/dist/references.js.map +1 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +6 -3
- 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.map +1 -1
- package/dist/tex-requirements.js +8 -3
- 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/venue-rules.js +1 -1
- package/dist/venue-rules.js.map +1 -1
- 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} +4 -3
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +16 -8
- 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} +34 -15
- 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} +74 -29
- package/eslint-rules/paper-typography.test.mjs +3 -3
- package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +132 -54
- 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} +88 -31
- package/eslint-rules/review-frontmatter.test.mjs +2 -2
- package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +37 -15
- 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} +19 -6
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/{tex-build.mjs → tex-build.ts} +4 -3
- package/eslint.config.mjs +139 -30
- 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.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 +8 -8
- package/lib/{paper-config.mjs → paper-config.ts} +19 -14
- package/lib/skill-checks.mjs +1 -1
- package/lib/skill-checks.test.mjs +1 -1
- package/lib/skill-corpus.mjs +1 -1
- package/lib/skill-eval-kit.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 -37
- package/scripts/eslint-report-guard.test.mjs +1 -1
- package/scripts/harness-api.frozen.json +14 -15
- 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/grade-paper-writing/prose-lint.mjs +1 -1
- package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +2 -2
- 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 +1 -1
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- package/skills/paper-pipeline/scripts/README.md +1 -1
- package/skills/paper-pipeline/scripts/announce.test.mjs +1 -1
- 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.mjs +1 -5
- 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/fixtures/{scorecard.mjs → scorecard.ts} +36 -7
- package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +1 -4
- 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-pipeline/scripts/run-mechanical.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/status.test.mjs +1 -1
- 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/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- package/skills/tighten-paper/structure.mjs +1 -1
- package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
- 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/skills/verify-citations/scripts/verify-cites.mjs +11 -1
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +35 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +14 -0
- package/src/CLAUDE.md +1 -1
- package/src/adapters/banal/index.test.ts +3 -6
- package/src/adapters/banal/index.ts +7 -4
- package/src/adapters/banal/invocation.test.ts +3 -1
- package/src/adapters/banal/invocation.ts +1 -1
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +3 -3
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/curl/download.test.ts +4 -2
- package/src/adapters/node/files.io.ts +2 -2
- package/src/adapters/node/files.test.ts +3 -1
- package/src/adapters/node/process.io.ts +4 -3
- package/src/adapters/node/process.test.ts +1 -1
- 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 +26 -58
- 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 +2 -2
- package/src/build-engine.ts +2 -2
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +15 -14
- package/src/build.ts +28 -19
- package/src/cli.harness.mjs +6 -6
- package/src/cli.test.ts +13 -6
- package/src/cli.ts +39 -37
- package/src/doctor.harness.mjs +3 -3
- package/src/doctor.ts +3 -2
- package/src/domain/lookup-cache.ts +21 -11
- package/src/domain/result.test.ts +2 -2
- 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 +4 -3
- package/src/facts-file.test.ts +9 -7
- package/src/facts-file.ts +2 -1
- package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +39 -20
- package/src/hooks-settings.ts +20 -9
- package/src/init.test.ts +12 -9
- package/src/init.ts +22 -12
- 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 +2 -2
- package/src/new-paper.test.ts +8 -10
- package/src/new-paper.ts +2 -2
- package/src/paper-files.test.ts +3 -5
- package/src/paper-settings-commands.test.ts +5 -4
- package/src/paper-settings.test.ts +5 -5
- package/src/paper-settings.ts +8 -9
- package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
- package/src/pdf-facts.test.ts +10 -11
- package/src/pdf-facts.ts +2 -1
- package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
- package/src/pdf-geometry.ts +3 -1
- package/src/presets.ts +3 -2
- package/src/references.test.ts +51 -43
- package/src/references.ts +14 -7
- package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
- package/src/rules-config.ts +7 -4
- 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.ts +8 -5
- package/src/toolchain.harness.mjs +2 -2
- package/src/toolchain.test.ts +1 -1
- package/src/toolchain.ts +31 -17
- package/src/venue-rules.test.ts +62 -64
- package/src/venue-rules.ts +1 -1
- 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/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/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +0 -0
- /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
|
@@ -35,6 +35,23 @@
|
|
|
35
35
|
import { existsSync, readFileSync } from "node:fs";
|
|
36
36
|
import { join, dirname } from "node:path";
|
|
37
37
|
import { load } from "js-yaml";
|
|
38
|
+
import { z } from "zod";
|
|
39
|
+
import type { RuleContext } from "./rule-context.ts";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The two scorecard fields this rule reads, parsed rather than trusted: a field of the wrong shape
|
|
43
|
+
* reads as absent, the way `data?.stages` on a string or a number always did.
|
|
44
|
+
*/
|
|
45
|
+
const Scorecard = z
|
|
46
|
+
.looseObject({
|
|
47
|
+
stages: z.array(z.unknown()).catch([]),
|
|
48
|
+
researchQuestion: z.string().catch(""),
|
|
49
|
+
})
|
|
50
|
+
.catch({ stages: [], researchQuestion: "" });
|
|
51
|
+
const StageEntry = z.looseObject({ stage: z.unknown().optional() }).catch({});
|
|
52
|
+
const RuleOptions = z
|
|
53
|
+
.object({ statusFile: z.string().default("PIPELINE-STATUS.md") })
|
|
54
|
+
.default({ statusFile: "PIPELINE-STATUS.md" });
|
|
38
55
|
|
|
39
56
|
/**
|
|
40
57
|
* 🔴 THE REGEX IS GONE, AND THE REASON IS NOT "regexes are bad" — IT IS THAT IT ANSWERED THE
|
|
@@ -76,18 +93,21 @@ import { load } from "js-yaml";
|
|
|
76
93
|
*/
|
|
77
94
|
|
|
78
95
|
/** Whitespace is the only thing normalised: a sentence wrapped across lines is the same sentence. */
|
|
79
|
-
const flatten = (t) => t.replace(/\s+/g, " ").trim().toLowerCase();
|
|
96
|
+
const flatten = (t: string) => t.replace(/\s+/g, " ").trim().toLowerCase();
|
|
80
97
|
|
|
81
98
|
/**
|
|
82
99
|
* Both facts come out of ONE parse of the scorecard's front matter: the stages (has this paper
|
|
83
100
|
* shipped?) and the declared question. Reading them separately would invite the two to be taken
|
|
84
101
|
* from different revisions of the same file.
|
|
85
102
|
*/
|
|
86
|
-
function scorecard(
|
|
103
|
+
function scorecard(
|
|
104
|
+
dir: string,
|
|
105
|
+
statusName: string,
|
|
106
|
+
): { stages: unknown[]; question: string } {
|
|
87
107
|
const p = join(dir, statusName);
|
|
88
108
|
const none = { stages: [], question: "" };
|
|
89
109
|
if (!existsSync(p)) return none;
|
|
90
|
-
let text;
|
|
110
|
+
let text: string;
|
|
91
111
|
try {
|
|
92
112
|
text = readFileSync(p, "utf-8");
|
|
93
113
|
} catch {
|
|
@@ -95,18 +115,16 @@ function scorecard(dir, statusName) {
|
|
|
95
115
|
}
|
|
96
116
|
const m = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
97
117
|
if (!m) return none;
|
|
98
|
-
|
|
118
|
+
const [, front = ""] = m;
|
|
119
|
+
let data: unknown;
|
|
99
120
|
try {
|
|
100
|
-
data = load(
|
|
121
|
+
data = load(front);
|
|
101
122
|
} catch {
|
|
102
123
|
return none; // the unreadable YAML is already reported by `paper/stages`, on its own file
|
|
103
124
|
}
|
|
104
|
-
const raw = data
|
|
105
|
-
const stages =
|
|
106
|
-
|
|
107
|
-
: [];
|
|
108
|
-
const q = data?.researchQuestion;
|
|
109
|
-
return { stages, question: typeof q === "string" ? q : "" };
|
|
125
|
+
const { stages: raw, researchQuestion: question } = Scorecard.parse(data);
|
|
126
|
+
const stages = raw.map((r) => StageEntry.parse(r).stage).filter(Boolean);
|
|
127
|
+
return { stages, question };
|
|
110
128
|
}
|
|
111
129
|
|
|
112
130
|
export default {
|
|
@@ -137,14 +155,15 @@ export default {
|
|
|
137
155
|
"the scorecard declares a research question the paper does not contain. Looked for «{{needle}}» with whitespace collapsed, and the source has no such run of text. Either the paper dropped it or the declaration drifted from what was written — and which of the two it is, only you know",
|
|
138
156
|
},
|
|
139
157
|
},
|
|
140
|
-
create(context) {
|
|
141
|
-
const statusName =
|
|
142
|
-
context.options
|
|
158
|
+
create(context: RuleContext) {
|
|
159
|
+
const { statusFile: statusName } = RuleOptions.parse(
|
|
160
|
+
context.options[0],
|
|
161
|
+
);
|
|
143
162
|
return {
|
|
144
163
|
// `root:exit` exists for both markdown and the `tex/latex` language — the same place
|
|
145
164
|
// `paper/typography` lives, and for the same reason: a paper in this corpus can be
|
|
146
165
|
// either.
|
|
147
|
-
"root:exit"(node) {
|
|
166
|
+
"root:exit"(node: object) {
|
|
148
167
|
const raw = context.sourceCode.raw ?? context.sourceCode.text;
|
|
149
168
|
if (typeof raw !== "string") return;
|
|
150
169
|
const { stages, question } = scorecard(
|
|
@@ -15,8 +15,8 @@ import { join, dirname } from "node:path";
|
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { Linter } from "eslint";
|
|
17
17
|
import markdown from "@eslint/markdown";
|
|
18
|
-
import stages from "./paper-stages.
|
|
19
|
-
import { createChecker } from "../lib/check.
|
|
18
|
+
import stages from "./paper-stages.ts";
|
|
19
|
+
import { createChecker } from "../lib/check.ts";
|
|
20
20
|
|
|
21
21
|
const FIX = join(
|
|
22
22
|
dirname(fileURLToPath(import.meta.url)),
|
|
@@ -8,8 +8,8 @@ import { join } from "node:path";
|
|
|
8
8
|
import markdown from "@eslint/markdown";
|
|
9
9
|
import { Linter } from "eslint";
|
|
10
10
|
import { test } from "vitest";
|
|
11
|
-
import { useTempDir, writeTree } from "../test/support.
|
|
12
|
-
import stages from "./paper-stages.
|
|
11
|
+
import { useTempDir, writeTree } from "../test/support.ts";
|
|
12
|
+
import stages from "./paper-stages.ts";
|
|
13
13
|
|
|
14
14
|
const root = useTempDir("paper-stages-");
|
|
15
15
|
// Flat config matches `files` against paths under the linter's cwd; the temp papers live outside
|
|
@@ -93,6 +93,12 @@ test("each entry: an unknown or missing stage, missing fields, a date that is no
|
|
|
93
93
|
);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
test("a stage written as a mapping is named as written, not as [object Object]", () => {
|
|
97
|
+
assert.deepEqual(lint("mapping", "stages:\n - stage: {name: submitted}"), [
|
|
98
|
+
'unknown stage «{"name":"submitted"}» — the vocabulary is: submitted · camera-ready · arxiv',
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
96
102
|
test("paper/source skips a `stages` that is not a list, and entries whose stage is unknown or missing", () => {
|
|
97
103
|
assert.deepEqual(
|
|
98
104
|
lint("src-scalar", "stages: submitted", { rule: "source" }),
|
|
@@ -48,30 +48,69 @@
|
|
|
48
48
|
*/
|
|
49
49
|
import { existsSync, statSync, readdirSync } from "node:fs";
|
|
50
50
|
import { join, dirname } from "node:path";
|
|
51
|
+
import assert from "node:assert/strict";
|
|
51
52
|
import { load } from "js-yaml";
|
|
53
|
+
import { z } from "zod";
|
|
54
|
+
import type { RuleContext } from "./rule-context.ts";
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
/** The frontmatter's `stages` field, whatever it holds; anything that is not a mapping has none. */
|
|
57
|
+
const Frontmatter = z.looseObject({ stages: z.unknown().optional() }).catch({});
|
|
58
|
+
const StageList = z.array(z.unknown());
|
|
59
|
+
/** One `stages` entry. A field of the wrong shape is read as it is written, then judged below. */
|
|
60
|
+
const StageRecord = z
|
|
61
|
+
.looseObject({
|
|
62
|
+
stage: z.unknown().optional(),
|
|
63
|
+
date: z.unknown().optional(),
|
|
64
|
+
pdf: z.unknown().optional(),
|
|
65
|
+
bytes: z.unknown().optional(),
|
|
66
|
+
source: z.unknown().optional(),
|
|
67
|
+
sourceBytes: z.unknown().optional(),
|
|
68
|
+
sourceLost: z.unknown().optional(),
|
|
69
|
+
})
|
|
70
|
+
.catch({});
|
|
71
|
+
const REQUIRED = ["date", "pdf", "bytes"] satisfies readonly (
|
|
72
|
+
"date" | "pdf" | "bytes"
|
|
73
|
+
)[];
|
|
74
|
+
|
|
75
|
+
/** A stage the frontmatter declares, with the fields every check below needs. */
|
|
76
|
+
interface Declared {
|
|
77
|
+
readonly stage: string;
|
|
78
|
+
readonly date: string;
|
|
79
|
+
readonly pdf: string;
|
|
80
|
+
readonly bytes: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const STAGES: readonly string[] = ["submitted", "camera-ready", "arxiv"];
|
|
54
84
|
|
|
55
85
|
/**
|
|
56
86
|
* A YAML date without quotes parses to a `Date`, not a string — js-yaml honours YAML 1.1
|
|
57
87
|
* timestamps. Comparing `Date === "2026-08-29"` is silently false, so every date is
|
|
58
88
|
* normalised before it is compared with one taken from a filename.
|
|
59
89
|
*/
|
|
60
|
-
|
|
90
|
+
/**
|
|
91
|
+
* A YAML value as the text a message shows: a scalar as written, nothing as "", and a mapping or
|
|
92
|
+
* list as its JSON (as a number is) — `String()` would print `[object Object]` for a mapping.
|
|
93
|
+
*/
|
|
94
|
+
const scalarText = (v: unknown): string =>
|
|
95
|
+
typeof v === "string" ? v : v === undefined ? "" : JSON.stringify(v);
|
|
96
|
+
|
|
97
|
+
function isoDate(v: unknown): string {
|
|
61
98
|
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
62
99
|
if (typeof v === "string") return /^\d{4}-\d{2}-\d{2}/.exec(v)?.[0] ?? "";
|
|
63
100
|
return "";
|
|
64
101
|
}
|
|
65
102
|
|
|
66
103
|
/** Frozen versions on disk, as `{name, date, stage}`. */
|
|
67
|
-
function frozenPdfs(
|
|
68
|
-
|
|
104
|
+
function frozenPdfs(
|
|
105
|
+
versionsDir: string,
|
|
106
|
+
): { name: string; date: string; stage: string }[] {
|
|
107
|
+
let names: string[];
|
|
69
108
|
try {
|
|
70
109
|
names = readdirSync(versionsDir);
|
|
71
110
|
} catch {
|
|
72
111
|
return [];
|
|
73
112
|
}
|
|
74
|
-
const out = [];
|
|
113
|
+
const out: { name: string; date: string; stage: string }[] = [];
|
|
75
114
|
for (const name of names) {
|
|
76
115
|
if (!name.endsWith(".pdf")) continue;
|
|
77
116
|
// ⚠️ A name carrying `STALE` is a version WITHDRAWN on purpose — one such file records
|
|
@@ -80,8 +119,9 @@ function frozenPdfs(versionsDir) {
|
|
|
80
119
|
if (name.includes("STALE")) continue;
|
|
81
120
|
const m = /^(\d{4}-\d{2}-\d{2})-(.+)\.pdf$/.exec(name);
|
|
82
121
|
if (!m) continue;
|
|
83
|
-
|
|
84
|
-
|
|
122
|
+
const [, date = "", stage = ""] = m;
|
|
123
|
+
if (!STAGES.includes(stage)) continue;
|
|
124
|
+
out.push({ name, date, stage });
|
|
85
125
|
}
|
|
86
126
|
return out;
|
|
87
127
|
}
|
|
@@ -112,26 +152,29 @@ export default {
|
|
|
112
152
|
"`versions/{{file}}` is frozen, but no «{{stage}}» stage on {{date}} is declared in `stages` — the artefact ran ahead of the declaration",
|
|
113
153
|
},
|
|
114
154
|
},
|
|
115
|
-
create(context) {
|
|
155
|
+
create(context: RuleContext) {
|
|
116
156
|
const dir = dirname(context.filename);
|
|
117
|
-
let declared = null; // null = there was no frontmatter at all
|
|
157
|
+
let declared: Declared[] | null = null; // null = there was no frontmatter at all
|
|
118
158
|
|
|
119
159
|
return {
|
|
120
|
-
yaml(node) {
|
|
121
|
-
let data;
|
|
160
|
+
yaml(node: { readonly value?: string }) {
|
|
161
|
+
let data: unknown;
|
|
122
162
|
try {
|
|
123
163
|
data = load(node.value ?? "");
|
|
124
164
|
} catch (e) {
|
|
165
|
+
// js-yaml throws its own `YAMLException`, an `Error`, and nothing else.
|
|
166
|
+
assert.ok(e instanceof Error);
|
|
125
167
|
context.report({
|
|
126
168
|
node,
|
|
127
169
|
messageId: "badYaml",
|
|
128
|
-
data: { reason:
|
|
170
|
+
data: { reason: e.message },
|
|
129
171
|
});
|
|
130
172
|
return;
|
|
131
173
|
}
|
|
132
|
-
const raw = data
|
|
174
|
+
const { stages: raw } = Frontmatter.parse(data);
|
|
133
175
|
if (raw === undefined) return;
|
|
134
|
-
|
|
176
|
+
const list = StageList.safeParse(raw);
|
|
177
|
+
if (!list.success) {
|
|
135
178
|
context.report({
|
|
136
179
|
node,
|
|
137
180
|
messageId: "notAList",
|
|
@@ -140,8 +183,9 @@ export default {
|
|
|
140
183
|
return;
|
|
141
184
|
}
|
|
142
185
|
declared = [];
|
|
143
|
-
for (const
|
|
144
|
-
const
|
|
186
|
+
for (const entry of list.data) {
|
|
187
|
+
const rec = StageRecord.parse(entry);
|
|
188
|
+
const stage = scalarText(rec.stage);
|
|
145
189
|
if (!STAGES.includes(stage)) {
|
|
146
190
|
context.report({
|
|
147
191
|
node,
|
|
@@ -151,7 +195,7 @@ export default {
|
|
|
151
195
|
continue;
|
|
152
196
|
}
|
|
153
197
|
let complete = true;
|
|
154
|
-
for (const key of
|
|
198
|
+
for (const key of REQUIRED) {
|
|
155
199
|
if (rec[key] === undefined) {
|
|
156
200
|
context.report({
|
|
157
201
|
node,
|
|
@@ -207,7 +251,7 @@ export default {
|
|
|
207
251
|
// ── direction two: bytes owe their declaration ────────────────────────────────
|
|
208
252
|
// 🔴 On `root:exit` rather than inside `yaml`, because the case worth catching is
|
|
209
253
|
// precisely the one with NO frontmatter at all — where the `yaml` visitor never runs.
|
|
210
|
-
"root:exit"(node) {
|
|
254
|
+
"root:exit"(node: object) {
|
|
211
255
|
const records = declared ?? [];
|
|
212
256
|
for (const f of frozenPdfs(join(dir, "versions"))) {
|
|
213
257
|
if (records.some((r) => r.stage === f.stage && r.date === f.date))
|
|
@@ -273,26 +317,27 @@ export default {
|
|
|
273
317
|
"stage «{{stage}}» ({{date}}): the source is declared LOST. There is nothing left to match a build against a reviewer's line — if a copy turns up, put it in versions/ and clear the flag",
|
|
274
318
|
},
|
|
275
319
|
},
|
|
276
|
-
create(context) {
|
|
320
|
+
create(context: RuleContext) {
|
|
277
321
|
const dir = dirname(context.filename);
|
|
278
322
|
return {
|
|
279
|
-
yaml(node) {
|
|
280
|
-
let data;
|
|
323
|
+
yaml(node: { readonly value?: string }) {
|
|
324
|
+
let data: unknown;
|
|
281
325
|
try {
|
|
282
326
|
data = load(node.value ?? "");
|
|
283
327
|
} catch {
|
|
284
328
|
return; // `paper/stages` has already reported the unreadable YAML
|
|
285
329
|
}
|
|
286
|
-
const
|
|
287
|
-
if (!
|
|
288
|
-
for (const
|
|
289
|
-
const
|
|
330
|
+
const list = StageList.safeParse(Frontmatter.parse(data).stages);
|
|
331
|
+
if (!list.success) return;
|
|
332
|
+
for (const entry of list.data) {
|
|
333
|
+
const rec = StageRecord.parse(entry);
|
|
334
|
+
const stage = scalarText(rec.stage);
|
|
290
335
|
if (!STAGES.includes(stage)) continue;
|
|
291
|
-
const date = isoDate(rec
|
|
336
|
+
const date = isoDate(rec.date);
|
|
292
337
|
|
|
293
338
|
// Acknowledging the loss is a RECORD, not an exemption: the rule keeps speaking,
|
|
294
339
|
// because the state stays defective, just unfixable today.
|
|
295
|
-
if (rec
|
|
340
|
+
if (rec.sourceLost === true) {
|
|
296
341
|
context.report({
|
|
297
342
|
node,
|
|
298
343
|
messageId: "lostAcknowledged",
|
|
@@ -300,7 +345,7 @@ export default {
|
|
|
300
345
|
});
|
|
301
346
|
continue;
|
|
302
347
|
}
|
|
303
|
-
const src =
|
|
348
|
+
const src = scalarText(rec.source);
|
|
304
349
|
if (src === "") {
|
|
305
350
|
context.report({
|
|
306
351
|
node,
|
|
@@ -319,7 +364,7 @@ export default {
|
|
|
319
364
|
continue;
|
|
320
365
|
}
|
|
321
366
|
const got = statSync(abs).size;
|
|
322
|
-
const want = Number(rec
|
|
367
|
+
const want = Number(rec.sourceBytes);
|
|
323
368
|
if (Number.isFinite(want) && got !== want)
|
|
324
369
|
context.report({
|
|
325
370
|
node,
|
|
@@ -15,9 +15,9 @@ import markdown from "@eslint/markdown";
|
|
|
15
15
|
import { readFileSync } from "node:fs";
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
|
-
import { texLanguage } from "./latex-language.
|
|
19
|
-
import typography, { texVisibleRuns } from "./paper-typography.
|
|
20
|
-
import bib from "./bib-reachable-entry.
|
|
18
|
+
import { texLanguage } from "./latex-language.ts";
|
|
19
|
+
import typography, { texVisibleRuns } from "./paper-typography.ts";
|
|
20
|
+
import bib from "./bib-reachable-entry.ts";
|
|
21
21
|
|
|
22
22
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const FIX = join(HERE, "..", "fixtures", "paper-typography");
|