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
package/src/build-engine.ts
CHANGED
|
@@ -62,10 +62,12 @@ type Resolved = Required<Omit<EngineOptions, "install">> & {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
/** The options a caller actually set — `undefined` must still get its default, so no plain spread. */
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
65
|
+
function defined<T extends object>(o: T): Partial<T> {
|
|
66
|
+
const out: Partial<T> = {};
|
|
67
|
+
// `for…in` hands out `o`'s keys typed as its keys; an options literal has no inherited ones.
|
|
68
|
+
for (const k in o) if (o[k] !== undefined) out[k] = o[k];
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
69
71
|
|
|
70
72
|
function withDefaults(o: EngineOptions): Resolved {
|
|
71
73
|
const r = {
|
|
@@ -133,7 +135,10 @@ const names = (missing: readonly string[]): string =>
|
|
|
133
135
|
`${String(missing.length)} package(s): ${missing.join(", ")}`;
|
|
134
136
|
|
|
135
137
|
/** The one line a run without a human gets. It names the command and what is missing. */
|
|
136
|
-
export function refusal(
|
|
138
|
+
export function refusal(
|
|
139
|
+
d: EngineDecision,
|
|
140
|
+
facts: Pick<EngineFacts, "system">,
|
|
141
|
+
): string {
|
|
137
142
|
const missing = d.kind === "refuse" || d.kind === "ask" ? d.missing : [];
|
|
138
143
|
const sys =
|
|
139
144
|
facts.system && facts.system.missing.length
|
package/src/build.harness.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The process runner is replaced by a fake that writes what pdflatex and bibtex would write, so
|
|
5
5
|
* these assertions check the SHELL's decisions: what is run, with which environment, in which
|
|
6
6
|
* order, and what is left on disk. Whether a real pdflatex produces a real PDF is the other half,
|
|
7
|
-
* `test/e2e/build.ts`.
|
|
7
|
+
* `test/e2e/tex/build.e2e.ts`.
|
|
8
8
|
*
|
|
9
9
|
* 🔴 THE TWO THINGS PINNED DOWN HERE THAT A RETURNED OBJECT CANNOT SHOW: a paper-supplied
|
|
10
10
|
* `build.sh` is never executed (checked by the trace it would leave on disk and by the list of
|
|
@@ -40,9 +40,7 @@ const {
|
|
|
40
40
|
STEPS,
|
|
41
41
|
IGNORED_SCRIPTS,
|
|
42
42
|
} = await import(join(HERE, "build.ts"));
|
|
43
|
-
const {
|
|
44
|
-
join(HERE, "..", "skills", "paper-pipeline", "scripts", "consumer.mjs")
|
|
45
|
-
);
|
|
43
|
+
const { texInputsDir } = await import(join(HERE, "package-dirs.ts"));
|
|
46
44
|
|
|
47
45
|
const check = createChecker();
|
|
48
46
|
|
|
@@ -137,12 +135,12 @@ const fakeRead = async (pdf) => {
|
|
|
137
135
|
const CLEAN_TEX = "\\documentclass{article}\\begin{document}x\\end{document}";
|
|
138
136
|
|
|
139
137
|
try {
|
|
140
|
-
// ── inputs: paperlint's own
|
|
138
|
+
// ── inputs: paperlint's own TeX inputs directory, no configuration ────────────────────────────
|
|
141
139
|
// First: the facts below resolve a paper's venue preset in this same directory, so a wrong one
|
|
142
140
|
// must be named HERE, not as a missing venue label two checks later.
|
|
143
|
-
const venues =
|
|
141
|
+
const venues = texInputsDir();
|
|
144
142
|
check(
|
|
145
|
-
"the
|
|
143
|
+
"the TeX inputs directory is paperlint's own and holds paper-guards.tex",
|
|
146
144
|
existsSync(join(venues, "paper-guards.tex")),
|
|
147
145
|
);
|
|
148
146
|
check(
|
package/src/build.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import assert from "node:assert/strict";
|
|
|
8
8
|
import { existsSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { test } from "vitest";
|
|
11
|
-
import { useTempDir, writeTree } from "../test/support.ts";
|
|
11
|
+
import { present, useTempDir, writeTree } from "../test/support.ts";
|
|
12
12
|
import {
|
|
13
13
|
bibInput,
|
|
14
14
|
buildPaper,
|
|
@@ -64,16 +64,16 @@ function bibtexRun(f: Fake, cwd: string) {
|
|
|
64
64
|
}
|
|
65
65
|
function fakeRun(f: Fake): NonNullable<BuildOptions["run"]> {
|
|
66
66
|
let pass = 0;
|
|
67
|
-
return (
|
|
67
|
+
return (bin, _args, opts) => {
|
|
68
68
|
if (bin === f.missing)
|
|
69
69
|
return {
|
|
70
70
|
error: Object.assign(new Error("spawn ENOENT"), { code: "ENOENT" }),
|
|
71
71
|
status: null,
|
|
72
72
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
73
|
+
// The build always runs a pass in the paper's directory.
|
|
74
|
+
const cwd = present(opts.cwd, "the pass's cwd");
|
|
75
|
+
return bin === "pdflatex" ? latexPass(f, cwd, ++pass) : bibtexRun(f, cwd);
|
|
76
|
+
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const NO_GEOMETRY: Geometry = {
|
|
@@ -146,11 +146,10 @@ test("documentclass: an empty name is no class; a macro inside the name contribu
|
|
|
146
146
|
// `\documentclass` came back bare has no name to read, and is no class.
|
|
147
147
|
assert.equal(
|
|
148
148
|
parseDocumentclass("\\documentclass", {
|
|
149
|
-
parse: () =>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}) as never,
|
|
149
|
+
parse: () => ({
|
|
150
|
+
type: "root",
|
|
151
|
+
content: [{ type: "macro", content: "documentclass" }],
|
|
152
|
+
}),
|
|
154
153
|
}),
|
|
155
154
|
null,
|
|
156
155
|
);
|
package/src/build.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* The decision of the LaTeX loop is `latex-loop.ts`, reading the log is `latex-log.ts`; both are
|
|
35
35
|
* pure. This file is the shell: it runs processes through the injected `run` (the existing port —
|
|
36
36
|
* `spawnSync` by default) and reads the files a pass left behind. Where paperlint's own files live is
|
|
37
|
-
* answered by `
|
|
37
|
+
* answered by `package-dirs.ts`, the one module allowed to know it (rule 10).
|
|
38
38
|
*/
|
|
39
39
|
// eslint-disable-next-line boundaries/dependencies -- legacy I/O, moves behind a port in #76
|
|
40
40
|
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
@@ -44,7 +44,7 @@ import { createHash } from "node:crypto";
|
|
|
44
44
|
import { delimiter, join, relative } from "node:path";
|
|
45
45
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
46
46
|
import { getParser } from "@unified-latex/unified-latex-util-parse";
|
|
47
|
-
import {
|
|
47
|
+
import { texInputsDir } from "./package-dirs.ts";
|
|
48
48
|
import { CONFIG_FILE } from "#lib/paper-config";
|
|
49
49
|
import {
|
|
50
50
|
declaredVenue,
|
|
@@ -61,7 +61,8 @@ import { banalMeasurer, parseBanalSettings } from "./adapters/banal/index.ts";
|
|
|
61
61
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
62
62
|
import { hostDirs, nodeAdapters, nodeFiles } from "./adapters/node/index.ts";
|
|
63
63
|
import { whyNoGeometry } from "./domain/geometry.ts";
|
|
64
|
-
import
|
|
64
|
+
import { callerPath } from "./caller-path.ts";
|
|
65
|
+
import type { Runner } from "./engine.ts";
|
|
65
66
|
import type { Files } from "./ports/files.ts";
|
|
66
67
|
import type { MeasureGeometry } from "./ports/measure-geometry.ts";
|
|
67
68
|
import type { CheckReferences } from "./ports/check-references.ts";
|
|
@@ -76,7 +77,6 @@ import {
|
|
|
76
77
|
import {
|
|
77
78
|
nextStep,
|
|
78
79
|
summarize,
|
|
79
|
-
TRACKED,
|
|
80
80
|
type BibInput,
|
|
81
81
|
type Hashes,
|
|
82
82
|
type Observation,
|
|
@@ -128,7 +128,7 @@ export interface PaperFacts {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/** A process runner with `spawnSync`'s shape — the port the harness replaces. */
|
|
131
|
-
export type Runner
|
|
131
|
+
export type { Runner };
|
|
132
132
|
|
|
133
133
|
/** What a step's `run` receives. `env` is the environment accumulated by the steps before it. */
|
|
134
134
|
export interface BuildContext {
|
|
@@ -268,11 +268,11 @@ export const inputsStep: BuildStep = {
|
|
|
268
268
|
required: false,
|
|
269
269
|
applies: () => ({
|
|
270
270
|
yes: true,
|
|
271
|
-
why: `TEXINPUTS += ${
|
|
271
|
+
why: `TEXINPUTS += ${texInputsDir()}`,
|
|
272
272
|
}),
|
|
273
273
|
run: (ctx) => ({
|
|
274
274
|
ok: true,
|
|
275
|
-
env: withTexInputs(ctx.env, [
|
|
275
|
+
env: withTexInputs(ctx.env, [texInputsDir()]),
|
|
276
276
|
}),
|
|
277
277
|
};
|
|
278
278
|
|
|
@@ -286,9 +286,8 @@ function hashes(paperDir: string): Hashes {
|
|
|
286
286
|
const p = join(paperDir, `${JOB}.${ext}`);
|
|
287
287
|
return existsSync(p) ? sha(readFileSync(p)) : null;
|
|
288
288
|
};
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return out as Hashes;
|
|
289
|
+
// A literal of the mapped type: a new tracked extension without a hash here is a compile error.
|
|
290
|
+
return { aux: h("aux"), toc: h("toc"), out: h("out"), bbl: h("bbl") };
|
|
292
291
|
}
|
|
293
292
|
|
|
294
293
|
const readOr = (path: string, enc: BufferEncoding): string | null =>
|
|
@@ -387,8 +386,8 @@ function latexPass(
|
|
|
387
386
|
|
|
388
387
|
/** The two streams of a finished run, as they really arrive (see `printed`). */
|
|
389
388
|
interface Streams {
|
|
390
|
-
readonly stdout
|
|
391
|
-
readonly stderr
|
|
389
|
+
readonly stdout?: string | undefined;
|
|
390
|
+
readonly stderr?: string | undefined;
|
|
392
391
|
}
|
|
393
392
|
|
|
394
393
|
/**
|
|
@@ -694,7 +693,7 @@ function defaultMeasurer(
|
|
|
694
693
|
return banalMeasurer(
|
|
695
694
|
nodeAdapters({ tmpDir: dirs.tmp }),
|
|
696
695
|
parseBanalSettings(b.env, dirs),
|
|
697
|
-
b.projectRoot
|
|
696
|
+
callerPath(b.projectRoot),
|
|
698
697
|
);
|
|
699
698
|
}
|
|
700
699
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A path as a caller gave it, made absolute where it enters a `Files` call.
|
|
3
|
+
*
|
|
4
|
+
* The app's use cases take paper directories and files as strings, and some callers pass them as
|
|
5
|
+
* typed on a command line (`skills/render-paper/extract-pdf-facts.mjs <paper.pdf>`). A relative one
|
|
6
|
+
* has always meant the process's cwd — `node:fs` resolved it there. Resolving it HERE, the same way,
|
|
7
|
+
* makes the `AbsolutePath` brand a checked fact rather than an assertion: the value handed to the port
|
|
8
|
+
* is absolute, and nothing downstream has to wonder.
|
|
9
|
+
*/
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { absolutePath, type AbsolutePath } from "./domain/paths.ts";
|
|
12
|
+
|
|
13
|
+
export const callerPath = (p: string): AbsolutePath => absolutePath(resolve(p));
|
package/src/cli.harness.mjs
CHANGED
|
@@ -1148,7 +1148,7 @@ check(
|
|
|
1148
1148
|
// pnpm: node_modules/vigiles/dist/cli.js DOES NOT
|
|
1149
1149
|
// The old wiring addressed the runtime from the project root and did not resolve under pnpm,
|
|
1150
1150
|
// and `|| exit 2` on PreToolUse(Bash) turned that into a block on ANY command. The end-to-end
|
|
1151
|
-
// half (both installs, real processes) lives in `test/e2e/install.ts`; here — the verdicts.
|
|
1151
|
+
// half (both installs, real processes) lives in `test/e2e/install/install.e2e.ts`; here — the verdicts.
|
|
1152
1152
|
{
|
|
1153
1153
|
const calls = [];
|
|
1154
1154
|
const fake = (code) => (bin, args, opts) => {
|
|
@@ -1711,8 +1711,6 @@ console.log(
|
|
|
1711
1711
|
typo.out.includes('paperlint.json: unknown key "typographyDept"'),
|
|
1712
1712
|
);
|
|
1713
1713
|
settings({
|
|
1714
|
-
ledger: "x.jsonl",
|
|
1715
|
-
citeChecks: "c",
|
|
1716
1714
|
timezone: "UTC",
|
|
1717
1715
|
contactEmail: "a@b.c",
|
|
1718
1716
|
triggerCases: "t.mjs",
|
package/src/cli.test.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync } from "node:fs";
|
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { test } from "vitest";
|
|
12
12
|
import { runNode, useTempDir, writeTree } from "../test/support.ts";
|
|
13
|
+
import type { runToolchain } from "./toolchain.ts";
|
|
13
14
|
import {
|
|
14
15
|
chooseVenue,
|
|
15
16
|
initTexLive,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
silentOptionalRules,
|
|
23
24
|
toolchainTex,
|
|
24
25
|
} from "./cli.ts";
|
|
26
|
+
import { texInputsDir } from "./package-dirs.ts";
|
|
25
27
|
|
|
26
28
|
const root = useTempDir("cli-test-");
|
|
27
29
|
|
|
@@ -57,6 +59,18 @@ test("parseArgs: the boolean flags of build and toolchain, and a value flag with
|
|
|
57
59
|
);
|
|
58
60
|
});
|
|
59
61
|
|
|
62
|
+
test("🔴 parseSettings refuses a mistyped `structure` instead of letting `lint` crash on it", () => {
|
|
63
|
+
// Guards: `markers: 5` used to pass the boundary, and `checkStructure` then threw
|
|
64
|
+
// "rules.markers.some is not a function" from inside `paperlint lint`.
|
|
65
|
+
for (const structure of [{ markers: 5 }, "yes", { requireOneOf: ["a"] }]) {
|
|
66
|
+
const r = parseSettings({ structure }, "paperlint.json", "/r");
|
|
67
|
+
assert.equal(r.ok, false, JSON.stringify(structure));
|
|
68
|
+
assert.match(r.error, /^paperlint\.json: "structure" must be false/);
|
|
69
|
+
}
|
|
70
|
+
const off = parseSettings({ structure: false }, "paperlint.json", "/r");
|
|
71
|
+
assert.equal(off.ok && off.value.structure, false);
|
|
72
|
+
});
|
|
73
|
+
|
|
60
74
|
test("parseSettings refuses a non-object, names one unknown key or several, and a mistyped default", () => {
|
|
61
75
|
const error = (json: unknown) => {
|
|
62
76
|
const r = parseSettings(json, "paperlint.json", "/r");
|
|
@@ -146,7 +160,7 @@ test("runHook: a runtime with no cli.js beside it, and a hook killed by a signal
|
|
|
146
160
|
});
|
|
147
161
|
const killed = runHook("paper-edit-guard", {
|
|
148
162
|
err: (m: string) => messages.push(m),
|
|
149
|
-
run: (
|
|
163
|
+
run: () => ({ status: null, signal: "SIGKILL" }),
|
|
150
164
|
});
|
|
151
165
|
assert.deepEqual(
|
|
152
166
|
{ noCli, killed, messages: messages.map((m) => m.split(" from ")[0]) },
|
|
@@ -235,14 +249,7 @@ writeTree(noTexDir, {
|
|
|
235
249
|
"papers/tex/paper.tex":
|
|
236
250
|
"\\documentclass{article}\\begin{document}x\\end{document}\n",
|
|
237
251
|
});
|
|
238
|
-
const venues =
|
|
239
|
-
import.meta.dirname,
|
|
240
|
-
"..",
|
|
241
|
-
"skills",
|
|
242
|
-
"submit-paper",
|
|
243
|
-
"references",
|
|
244
|
-
"venues",
|
|
245
|
-
);
|
|
252
|
+
const venues = texInputsDir();
|
|
246
253
|
|
|
247
254
|
test("build: a markdown-only paper is refused", async () => {
|
|
248
255
|
const md = await withoutTex(noTexDir, () =>
|
|
@@ -363,7 +370,8 @@ test("a paper's paperlint.json that does not parse stops the lint with its path"
|
|
|
363
370
|
});
|
|
364
371
|
|
|
365
372
|
test("silentOptionalRules: a config with no rules turns nothing on", async () => {
|
|
366
|
-
|
|
373
|
+
const eslint = { calculateConfigForFile: () => Promise.resolve(undefined) };
|
|
374
|
+
assert.deepEqual(await silentOptionalRules(eslint, [], {}), []);
|
|
367
375
|
});
|
|
368
376
|
|
|
369
377
|
test("init with no path and --format md sets up the current directory", async () => {
|
|
@@ -538,8 +546,8 @@ test("ownVersion: the manifest's version; an unreadable manifest or a non-string
|
|
|
538
546
|
|
|
539
547
|
test("init's TeX Live install runs the toolchain over the project's papers, never a check", () => {
|
|
540
548
|
const dir = writeTree(join(root, "init-tex"), { "package.json": "{}" });
|
|
541
|
-
const calls:
|
|
542
|
-
const fake = (
|
|
549
|
+
const calls: Parameters<typeof runToolchain>[0][] = [];
|
|
550
|
+
const fake: typeof runToolchain = (o) => (calls.push(o), 0);
|
|
543
551
|
const log = () => {};
|
|
544
552
|
const here = initTexLive(
|
|
545
553
|
parseArgs(["init"]),
|
|
@@ -553,10 +561,7 @@ test("init's TeX Live install runs the toolchain over the project's papers, neve
|
|
|
553
561
|
);
|
|
554
562
|
assert.deepEqual([here.install(), there.install()], [0, 0]);
|
|
555
563
|
assert.deepEqual(
|
|
556
|
-
calls.map((c) =>
|
|
557
|
-
const o = c as { check: boolean; tex: unknown; banal: unknown };
|
|
558
|
-
return [o.check, o.tex, typeof o.banal];
|
|
559
|
-
}),
|
|
564
|
+
calls.map((c) => [c.check, c.tex, typeof c.banal]),
|
|
560
565
|
[
|
|
561
566
|
[false, toolchainTex(dir), "object"],
|
|
562
567
|
[false, toolchainTex(join(dir, "sub")), "object"],
|
package/src/cli.ts
CHANGED
|
@@ -30,11 +30,10 @@ import { spawnSync } from "node:child_process";
|
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { join, dirname, resolve, relative, basename, sep } from "node:path";
|
|
32
32
|
import markdown from "@eslint/markdown";
|
|
33
|
+
import { z } from "zod";
|
|
33
34
|
// Types come from consumer.d.mts beside it.
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
packageVenuesDir,
|
|
37
|
-
} from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
35
|
+
import { isMain } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
36
|
+
import { presetsDir } from "./package-dirs.ts";
|
|
38
37
|
export { isMain };
|
|
39
38
|
import type { Args, PaperlintConfig, ConfigRead } from "./types.ts";
|
|
40
39
|
import {
|
|
@@ -47,8 +46,10 @@ import { prepareEngine } from "./build-engine.ts";
|
|
|
47
46
|
import { cacheRoot, cachedTree, runToolchain } from "./toolchain.ts";
|
|
48
47
|
import { banalInstaller, parseBanalSettings } from "./adapters/banal/index.ts";
|
|
49
48
|
import { curlDownload } from "./adapters/curl/index.ts";
|
|
49
|
+
import { latexReader } from "./adapters/latex/index.ts";
|
|
50
50
|
import { hostDirs, nodeAdapters, nodeFiles } from "./adapters/node/index.ts";
|
|
51
51
|
import { VENUE_RULE_LEVELS, venueRules } from "./venue-rules.ts";
|
|
52
|
+
import { TEX_VENUE_RULE_LEVELS, texVenueRules } from "./tex-venue-rules.ts";
|
|
52
53
|
import {
|
|
53
54
|
paperRules,
|
|
54
55
|
stringFields,
|
|
@@ -71,6 +72,7 @@ import {
|
|
|
71
72
|
shippedPresets,
|
|
72
73
|
SHIPPED_PREFIX,
|
|
73
74
|
type PaperPreset,
|
|
75
|
+
type PresetTemplate,
|
|
74
76
|
} from "./presets.ts";
|
|
75
77
|
import type { ToolInstaller } from "./ports/tool-installer.ts";
|
|
76
78
|
import {
|
|
@@ -112,6 +114,7 @@ import {
|
|
|
112
114
|
shippedRuleIds,
|
|
113
115
|
unknownKeys,
|
|
114
116
|
type Parsed,
|
|
117
|
+
type ConfigBlock,
|
|
115
118
|
type RuleBlock,
|
|
116
119
|
type RuleEntry,
|
|
117
120
|
} from "./rules-config.ts";
|
|
@@ -127,9 +130,10 @@ import reviewFrontmatter from "#eslint-rules/review-frontmatter";
|
|
|
127
130
|
import siblingFrontmatter from "#eslint-rules/sibling-frontmatter";
|
|
128
131
|
import pdfRules from "#eslint-rules/pdf-last-page-balance";
|
|
129
132
|
import { messageOf } from "./domain/text.ts";
|
|
133
|
+
import { isRecord } from "./domain/record.ts";
|
|
130
134
|
|
|
131
135
|
/** The shipped venue presets, read from the package's venues directory — the one list. */
|
|
132
|
-
const SHIPPED_VENUES = (): string[] => shippedPresets(
|
|
136
|
+
const SHIPPED_VENUES = (): string[] => shippedPresets(presetsDir());
|
|
133
137
|
|
|
134
138
|
const USAGE = `paperlint — machine-checkable gates for a paper kept in git
|
|
135
139
|
|
|
@@ -220,7 +224,7 @@ settings — paperlint.json, at two levels, one schema. Both are optional.
|
|
|
220
224
|
export function buildConfig(
|
|
221
225
|
opts: PaperlintConfig = {},
|
|
222
226
|
texLanguage: unknown,
|
|
223
|
-
):
|
|
227
|
+
): ConfigBlock[] {
|
|
224
228
|
const paperRules = { ...researchQuestion.rules, ...typography.rules };
|
|
225
229
|
// The reference rules judge `_build/references.json`, and only on `paper.tex`.
|
|
226
230
|
const texPaperRules = {
|
|
@@ -238,7 +242,7 @@ export function buildConfig(
|
|
|
238
242
|
languageOptions: { frontmatter: "yaml" },
|
|
239
243
|
};
|
|
240
244
|
|
|
241
|
-
const cfg:
|
|
245
|
+
const cfg: ConfigBlock[] = [
|
|
242
246
|
// 🔴 THE PROJECT'S PAPER TEMPLATE IS NOT A PAPER. `paperlint new` reads `<papers>/.template/`, and
|
|
243
247
|
// its files carry every marker a paper does. Flat config does NOT ignore dot-directories by
|
|
244
248
|
// default (only `node_modules/` and `.git/`), so without this block `paperlint lint` would lint the
|
|
@@ -254,7 +258,7 @@ export function buildConfig(
|
|
|
254
258
|
pdf: {
|
|
255
259
|
rules: {
|
|
256
260
|
...pdfRules.rules,
|
|
257
|
-
...venueRules({ files: nodeFiles, venuesDir:
|
|
261
|
+
...venueRules({ files: nodeFiles, venuesDir: presetsDir() }),
|
|
258
262
|
},
|
|
259
263
|
},
|
|
260
264
|
},
|
|
@@ -299,7 +303,17 @@ export function buildConfig(
|
|
|
299
303
|
cfg.push({
|
|
300
304
|
files: ["**/paper.tex"],
|
|
301
305
|
plugins: {
|
|
302
|
-
tex: {
|
|
306
|
+
tex: {
|
|
307
|
+
languages: { latex: texLanguage },
|
|
308
|
+
rules: {
|
|
309
|
+
...texBuild,
|
|
310
|
+
...texVenueRules({
|
|
311
|
+
files: nodeFiles,
|
|
312
|
+
venuesDir: presetsDir(),
|
|
313
|
+
latex: latexReader,
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
},
|
|
303
317
|
paper: { rules: texPaperRules },
|
|
304
318
|
bib: bibReachable,
|
|
305
319
|
},
|
|
@@ -313,6 +327,7 @@ export function buildConfig(
|
|
|
313
327
|
"tex/acm-frontmatter-override": "error",
|
|
314
328
|
// Silent for a paper whose paperlint.json names no venue (src/venue-rules.ts).
|
|
315
329
|
...VENUE_RULE_LEVELS,
|
|
330
|
+
...TEX_VENUE_RULE_LEVELS,
|
|
316
331
|
},
|
|
317
332
|
});
|
|
318
333
|
// 🔴 ONLY THE FILES THESE BLOCKS CLAIM ARE LINTED (src/paper-files.ts). Without this block ESLint's
|
|
@@ -329,6 +344,21 @@ export function buildConfig(
|
|
|
329
344
|
return cfg;
|
|
330
345
|
}
|
|
331
346
|
|
|
347
|
+
/**
|
|
348
|
+
* paperlint's blocks as ESLint's config type — the one place the two meet. ESLint types a plugin's
|
|
349
|
+
* rule as one that runs on ANY language; paperlint's run on the language they were written for, so
|
|
350
|
+
* no static proof that a block fits exists (`ConfigBlock`, rules-config.ts). ESLint checks every block
|
|
351
|
+
* when it loads the config and throws on one it cannot use; what is left to check here is that each
|
|
352
|
+
* is an object.
|
|
353
|
+
*/
|
|
354
|
+
const isEslintBlock = (b: unknown): b is Linter.Config =>
|
|
355
|
+
typeof b === "object" && b !== null;
|
|
356
|
+
|
|
357
|
+
export function eslintConfig(blocks: readonly ConfigBlock[]): Linter.Config[] {
|
|
358
|
+
const loose: readonly unknown[] = blocks;
|
|
359
|
+
return loose.filter(isEslintBlock);
|
|
360
|
+
}
|
|
361
|
+
|
|
332
362
|
/**
|
|
333
363
|
* The rule ids a consumer may name in `rules`: every rule paperlint's own config defines, read off that
|
|
334
364
|
* config rather than listed again. `@eslint/markdown` is a dependency's plugin, not paperlint's.
|
|
@@ -357,9 +387,7 @@ export interface RulePlugin {
|
|
|
357
387
|
export function rulePlugins(texLanguage?: unknown): Record<string, RulePlugin> {
|
|
358
388
|
const out: Record<string, { rules: Record<string, unknown> }> = {};
|
|
359
389
|
for (const block of buildConfig({}, texLanguage ?? { sentinel: "tex" }))
|
|
360
|
-
for (const [name, plugin] of Object.entries(
|
|
361
|
-
(block as { plugins?: Record<string, RulePlugin> }).plugins ?? {},
|
|
362
|
-
))
|
|
390
|
+
for (const [name, plugin] of Object.entries(block.plugins ?? {}))
|
|
363
391
|
if (plugin !== markdown)
|
|
364
392
|
out[name] = { rules: { ...out[name]?.rules, ...plugin.rules } };
|
|
365
393
|
if (out["tex"] && texLanguage !== undefined)
|
|
@@ -382,12 +410,10 @@ export function rulePlugins(texLanguage?: unknown): Record<string, RulePlugin> {
|
|
|
382
410
|
* switched off in THIS run — `paperlint lint` still reports a directive that silences nothing.
|
|
383
411
|
*/
|
|
384
412
|
export function rulesOff(texLanguage?: unknown): Linter.Config[] {
|
|
385
|
-
return [
|
|
413
|
+
return eslintConfig([
|
|
386
414
|
{
|
|
387
415
|
name: "paperlint/rules-off",
|
|
388
|
-
plugins: rulePlugins(texLanguage)
|
|
389
|
-
Linter.Config["plugins"]
|
|
390
|
-
>,
|
|
416
|
+
plugins: rulePlugins(texLanguage),
|
|
391
417
|
rules: Object.fromEntries([...SHIPPED_RULES].map((id) => [id, "off"])),
|
|
392
418
|
},
|
|
393
419
|
{
|
|
@@ -395,7 +421,7 @@ export function rulesOff(texLanguage?: unknown): Linter.Config[] {
|
|
|
395
421
|
files: PAPER_FILE_PATTERNS,
|
|
396
422
|
linterOptions: { reportUnusedDisableDirectives: "off" },
|
|
397
423
|
},
|
|
398
|
-
];
|
|
424
|
+
]);
|
|
399
425
|
}
|
|
400
426
|
|
|
401
427
|
/** Whether a rule entry (`"error"`, `2`, `["warn", {…}]`) turns the rule on. */
|
|
@@ -412,7 +438,7 @@ export const OPTIONAL_RULES: ReadonlySet<string> = new Set(
|
|
|
412
438
|
[...SHIPPED_RULES].filter(
|
|
413
439
|
(id) =>
|
|
414
440
|
!buildConfig({}, { sentinel: "tex language" }).some((b) =>
|
|
415
|
-
isOn(
|
|
441
|
+
isOn(b.rules?.[id]),
|
|
416
442
|
),
|
|
417
443
|
),
|
|
418
444
|
);
|
|
@@ -429,8 +455,13 @@ export const OPTIONAL_RULES: ReadonlySet<string> = new Set(
|
|
|
429
455
|
* papers a and b (#103) — a block that reaches a paper outside this run is not dead. A glob that
|
|
430
456
|
* reaches no paper of the project at all still fails, from a subset as from the whole.
|
|
431
457
|
*/
|
|
458
|
+
/** What `calculateConfigForFile` answers, read for its rules: undefined for a file outside ESLint's cwd or scope. */
|
|
459
|
+
const ComputedConfig = z
|
|
460
|
+
.object({ rules: z.record(z.string(), z.unknown()).optional() })
|
|
461
|
+
.optional();
|
|
462
|
+
|
|
432
463
|
export async function silentOptionalRules(
|
|
433
|
-
eslint: ESLint,
|
|
464
|
+
eslint: Pick<ESLint, "calculateConfigForFile">,
|
|
434
465
|
files: readonly string[],
|
|
435
466
|
opts: PaperlintConfig,
|
|
436
467
|
): Promise<string[]> {
|
|
@@ -444,8 +475,7 @@ export async function silentOptionalRules(
|
|
|
444
475
|
const reached = new Set<string>();
|
|
445
476
|
for (const f of new Set(files.filter((p) => basename(p) === MAIN))) {
|
|
446
477
|
// Undefined for a file outside ESLint's cwd or scope: it reaches nothing.
|
|
447
|
-
const cfg = (await eslint.calculateConfigForFile(f))
|
|
448
|
-
{ rules?: Record<string, unknown> } | undefined;
|
|
478
|
+
const cfg = ComputedConfig.parse(await eslint.calculateConfigForFile(f));
|
|
449
479
|
for (const id of turnedOn) if (isOn(cfg?.rules?.[id])) reached.add(id);
|
|
450
480
|
}
|
|
451
481
|
return [...turnedOn].filter((id) => !reached.has(id));
|
|
@@ -543,6 +573,20 @@ const PAPER_FILE_PATTERNS: string[] = ownedPatterns(
|
|
|
543
573
|
buildConfig({}, { sentinel: "tex language" }),
|
|
544
574
|
);
|
|
545
575
|
|
|
576
|
+
/** `structure` as `checkStructure` reads it: off, or lists of file names over the defaults. */
|
|
577
|
+
const Names = z.array(z.string()).exactOptional();
|
|
578
|
+
const Structure = z
|
|
579
|
+
.union([
|
|
580
|
+
z.literal(false),
|
|
581
|
+
z.object({
|
|
582
|
+
markers: Names,
|
|
583
|
+
require: Names,
|
|
584
|
+
requireOneOf: z.array(z.array(z.string())).exactOptional(),
|
|
585
|
+
ignore: Names,
|
|
586
|
+
}),
|
|
587
|
+
])
|
|
588
|
+
.optional();
|
|
589
|
+
|
|
546
590
|
/**
|
|
547
591
|
* The root `paperlint.json` after the boundary: an unknown key is refused by name, the paper
|
|
548
592
|
* defaults (`extends`, `kind`, `pdf`) are checked the way a paper's own are, and `rules` becomes
|
|
@@ -553,10 +597,9 @@ export function parseSettings(
|
|
|
553
597
|
where: string,
|
|
554
598
|
baseDir: string,
|
|
555
599
|
): Parsed<PaperlintConfig> {
|
|
556
|
-
if (
|
|
600
|
+
if (!isRecord(json))
|
|
557
601
|
return { ok: false, error: `${where}: must be a JSON object` };
|
|
558
|
-
const raw = json
|
|
559
|
-
const opts = raw as PaperlintConfig;
|
|
602
|
+
const raw = json;
|
|
560
603
|
const unknown = unknownKeys(raw);
|
|
561
604
|
if (unknown.length > 0)
|
|
562
605
|
return {
|
|
@@ -567,9 +610,22 @@ export function parseSettings(
|
|
|
567
610
|
};
|
|
568
611
|
const defaults = stringFields(raw);
|
|
569
612
|
if (!defaults.ok) return { ok: false, error: `${where}: ${defaults.error}` };
|
|
613
|
+
const structure = Structure.safeParse(raw["structure"]);
|
|
614
|
+
if (!structure.success)
|
|
615
|
+
return {
|
|
616
|
+
ok: false,
|
|
617
|
+
error: `${where}: "structure" must be false, or an object whose markers, require and ignore are lists of file names and requireOneOf a list of such lists`,
|
|
618
|
+
};
|
|
570
619
|
const rules = parseRuleBlocks(raw["rules"], where, SHIPPED_RULES, baseDir);
|
|
571
620
|
if (!rules.ok) return rules;
|
|
572
|
-
return {
|
|
621
|
+
return {
|
|
622
|
+
ok: true,
|
|
623
|
+
value: {
|
|
624
|
+
papersDir: raw[PAPERS_DIR_FIELD],
|
|
625
|
+
structure: structure.data,
|
|
626
|
+
rules: rules.value,
|
|
627
|
+
},
|
|
628
|
+
};
|
|
573
629
|
}
|
|
574
630
|
|
|
575
631
|
/**
|
|
@@ -770,7 +826,7 @@ export function readConfig(
|
|
|
770
826
|
|
|
771
827
|
/** The papers directory field of the settings, read by its one declared name; the default when absent. */
|
|
772
828
|
export function papersDirOf(opts: PaperlintConfig): unknown {
|
|
773
|
-
const declared =
|
|
829
|
+
const declared = opts.papersDir;
|
|
774
830
|
return declared === undefined ? DEFAULT_PAPERS_ROOT : declared;
|
|
775
831
|
}
|
|
776
832
|
|
|
@@ -839,7 +895,12 @@ export function runHook(
|
|
|
839
895
|
createRequire(import.meta.url).resolve(spec),
|
|
840
896
|
}: {
|
|
841
897
|
err?: typeof console.error;
|
|
842
|
-
|
|
898
|
+
/** Runs the hook with the terminal's streams: `spawnSync` is one, and only its status is read. */
|
|
899
|
+
run?: (
|
|
900
|
+
file: string,
|
|
901
|
+
args: readonly string[],
|
|
902
|
+
options: { readonly stdio: "inherit" },
|
|
903
|
+
) => { readonly status: number | null };
|
|
843
904
|
resolve?: (spec: string) => string;
|
|
844
905
|
} = {},
|
|
845
906
|
): number {
|
|
@@ -988,12 +1049,25 @@ export async function chooseVenue(
|
|
|
988
1049
|
value: {
|
|
989
1050
|
extends: spec.value,
|
|
990
1051
|
kind: kind !== null && kinds.includes(kind) ? kind : null,
|
|
1052
|
+
setClass: classSetter(r.value.template),
|
|
991
1053
|
label,
|
|
992
1054
|
kinds,
|
|
993
1055
|
},
|
|
994
1056
|
};
|
|
995
1057
|
}
|
|
996
1058
|
|
|
1059
|
+
/**
|
|
1060
|
+
* What `paperlint new` does to the template's paper.tex for a venue: set it in the class the
|
|
1061
|
+
* preset's template names. Null when the preset names no template, or one the reader cannot read
|
|
1062
|
+
* (then `tex/template` says so on the new paper).
|
|
1063
|
+
*/
|
|
1064
|
+
function classSetter(
|
|
1065
|
+
t: PresetTemplate | null,
|
|
1066
|
+
): ((paperTex: string) => string) | null {
|
|
1067
|
+
const cls = t === null ? null : latexReader.template(t.text);
|
|
1068
|
+
return cls === null ? null : (src) => latexReader.withDocumentClass(src, cls);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
997
1071
|
const bad = (error: string): { ok: false; error: string } => ({
|
|
998
1072
|
ok: false,
|
|
999
1073
|
error,
|
|
@@ -1168,7 +1242,7 @@ async function runBuild(
|
|
|
1168
1242
|
}
|
|
1169
1243
|
|
|
1170
1244
|
/** The presets' deps, wired to the disk and the package's own venues directory. */
|
|
1171
|
-
const PRESET_DEPS = { files: nodeFiles, venuesDir:
|
|
1245
|
+
const PRESET_DEPS = { files: nodeFiles, venuesDir: presetsDir() };
|
|
1172
1246
|
|
|
1173
1247
|
/**
|
|
1174
1248
|
* What one paper needs from TeX Live: the base set plus its preset chain's `tex`. A paper whose
|
|
@@ -1466,7 +1540,7 @@ export async function run(
|
|
|
1466
1540
|
const eslint = new ESLint({
|
|
1467
1541
|
cwd: lintRoot(root, paths),
|
|
1468
1542
|
overrideConfigFile: true,
|
|
1469
|
-
overrideConfig: buildConfig(withPapers, texLanguage)
|
|
1543
|
+
overrideConfig: eslintConfig(buildConfig(withPapers, texLanguage)),
|
|
1470
1544
|
fix: a.fix,
|
|
1471
1545
|
});
|
|
1472
1546
|
|
|
@@ -1543,12 +1617,20 @@ async function firstUnownedFile(
|
|
|
1543
1617
|
* is outside paperlint's scope (`all-matched-files-ignored` — a papers directory holding only
|
|
1544
1618
|
* vendored scripts). Both mean "nothing was linted", which the caller reports itself.
|
|
1545
1619
|
*/
|
|
1620
|
+
/** What a thrown ESLint error says about itself; anything else thrown says nothing. */
|
|
1621
|
+
const EslintFailure = z
|
|
1622
|
+
.object({
|
|
1623
|
+
messageTemplate: z.string().optional(),
|
|
1624
|
+
message: z.string().optional(),
|
|
1625
|
+
})
|
|
1626
|
+
.catch({});
|
|
1627
|
+
|
|
1546
1628
|
const isEmptySet = (e: unknown): boolean => {
|
|
1547
|
-
const fail = e
|
|
1629
|
+
const fail = EslintFailure.parse(e);
|
|
1548
1630
|
return (
|
|
1549
|
-
fail
|
|
1550
|
-
fail
|
|
1551
|
-
/No files matching/i.test(String(fail
|
|
1631
|
+
fail.messageTemplate === "file-not-found" ||
|
|
1632
|
+
fail.messageTemplate === "all-matched-files-ignored" ||
|
|
1633
|
+
/No files matching/i.test(String(fail.message))
|
|
1552
1634
|
);
|
|
1553
1635
|
};
|
|
1554
1636
|
|