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/pdf-facts.test.ts
CHANGED
|
@@ -40,11 +40,11 @@ function page(items: FakeItem[], fonts: Record<string, unknown>) {
|
|
|
40
40
|
}),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
const doc = (pages: ReturnType<typeof page>[]) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const doc = (pages: ReturnType<typeof page>[]) => ({
|
|
44
|
+
numPages: pages.length,
|
|
45
|
+
getPage: (i: number) =>
|
|
46
|
+
Promise.resolve(present(pages[i - 1], `page ${String(i)}`)),
|
|
47
|
+
});
|
|
48
48
|
const item = (
|
|
49
49
|
fontName: string,
|
|
50
50
|
transform: number[],
|
|
@@ -141,3 +141,14 @@ test("a failure that is not an Error is named `Error` with its text", () => {
|
|
|
141
141
|
detail: "Error: boom",
|
|
142
142
|
});
|
|
143
143
|
});
|
|
144
|
+
|
|
145
|
+
test("🔴 a rejection with no value at all is a failure too, not a TypeError out of `readPdf`", () => {
|
|
146
|
+
// Guards: `readPdf` ends in `.catch(failureOf)`, and reading `.name` off `null` threw from inside it.
|
|
147
|
+
assert.deepEqual(
|
|
148
|
+
[failureOf(null), failureOf(undefined)],
|
|
149
|
+
[
|
|
150
|
+
{ ok: false, reason: "unreadable", detail: "Error: null" },
|
|
151
|
+
{ ok: false, reason: "unreadable", detail: "Error: undefined" },
|
|
152
|
+
],
|
|
153
|
+
);
|
|
154
|
+
});
|
package/src/pdf-facts.ts
CHANGED
|
@@ -35,12 +35,67 @@ import type { PageLayout, TextBox } from "./domain/page-layout.ts";
|
|
|
35
35
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
36
36
|
import { fillsFor, isUpright } from "./adapters/pdfjs/fill.ts";
|
|
37
37
|
import { messageOf } from "./domain/text.ts";
|
|
38
|
+
import { fieldOf, isRecord, numbersOf } from "./domain/record.ts";
|
|
38
39
|
|
|
39
40
|
type PdfJs = Awaited<ReturnType<typeof import("unpdf").getResolvedPDFJS>>;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
|
|
42
|
+
/** A text item as this module reads it; pdf.js's `TextItem` is one. */
|
|
43
|
+
interface TextItem {
|
|
44
|
+
readonly str: string;
|
|
45
|
+
readonly fontName: string;
|
|
46
|
+
/** pdf.js types it `any[]`; `numbersOf` reads it. */
|
|
47
|
+
readonly transform: readonly unknown[];
|
|
48
|
+
readonly width: number;
|
|
49
|
+
readonly height: number;
|
|
50
|
+
}
|
|
51
|
+
/** What else pdf.js lists among a page's items: a marked-content boundary, with no text. */
|
|
52
|
+
interface MarkedContent {
|
|
53
|
+
readonly type: string;
|
|
54
|
+
readonly str?: undefined;
|
|
55
|
+
}
|
|
56
|
+
type Item = TextItem | MarkedContent;
|
|
57
|
+
|
|
58
|
+
/** A page's operators, as `fillsFor` walks them; pdf.js's operator list is one. */
|
|
59
|
+
interface OperatorList {
|
|
60
|
+
readonly fnArray: readonly number[];
|
|
61
|
+
readonly argsArray: readonly unknown[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A viewport as this module reads it; pdf.js's `PageViewport` is one. */
|
|
65
|
+
interface Viewport {
|
|
66
|
+
readonly width: number;
|
|
67
|
+
readonly height: number;
|
|
68
|
+
readonly transform: readonly number[];
|
|
69
|
+
convertToViewportPoint(x: number, y: number): readonly unknown[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A page as this module reads it. pdf.js's `PDFPageProxy` is one, and so is a test's fake page: the
|
|
74
|
+
* module depends on these five members, not on the rest of pdf.js's page.
|
|
75
|
+
*/
|
|
76
|
+
interface Page {
|
|
77
|
+
getOperatorList(): Promise<OperatorList>;
|
|
78
|
+
getTextContent(): Promise<{ readonly items: readonly Item[] }>;
|
|
79
|
+
/** pdf.js's `commonObjs`: loaded objects by id, iterable as `[id, object]` pairs. */
|
|
80
|
+
readonly commonObjs: {
|
|
81
|
+
has(id: string): boolean;
|
|
82
|
+
get(id: string): unknown;
|
|
83
|
+
[Symbol.iterator](): Iterator<readonly unknown[]>;
|
|
84
|
+
};
|
|
85
|
+
getViewport(params: { scale: number }): Viewport;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A document as this module reads it; pdf.js's `PDFDocumentProxy` is one. */
|
|
89
|
+
interface Doc {
|
|
90
|
+
readonly numPages: number;
|
|
91
|
+
getPage(pageNumber: number): Promise<Page>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A viewport point, which pdf.js types as `any[]`: the two numbers it holds. */
|
|
95
|
+
const pointOf = (xy: readonly unknown[]): [number, number] => [
|
|
96
|
+
Number(xy[0]),
|
|
97
|
+
Number(xy[1]),
|
|
98
|
+
];
|
|
44
99
|
|
|
45
100
|
/**
|
|
46
101
|
* What `readPdf` measured. `last` is the last page's words; `classifyLastPage` reads it. `layout` is
|
|
@@ -95,8 +150,8 @@ const fail = (reason: PdfReadFailure, detail: string): PdfRead => ({
|
|
|
95
150
|
|
|
96
151
|
/** A font object pdf.js left in `commonObjs`, read into a `RawFont`, or null when it is not one. */
|
|
97
152
|
export function rawFontOf(id: string, obj: unknown): RawFont | null {
|
|
98
|
-
if (
|
|
99
|
-
const o = obj
|
|
153
|
+
if (!isRecord(obj)) return null;
|
|
154
|
+
const o = obj;
|
|
100
155
|
if (!("loadedName" in o) && !("isType3Font" in o)) return null;
|
|
101
156
|
const str = (v: unknown): string | undefined =>
|
|
102
157
|
typeof v === "string" ? v : undefined;
|
|
@@ -111,7 +166,7 @@ export function rawFontOf(id: string, obj: unknown): RawFont | null {
|
|
|
111
166
|
}
|
|
112
167
|
|
|
113
168
|
const isText = (it: Item): it is TextItem =>
|
|
114
|
-
|
|
169
|
+
typeof it.str === "string" && it.str.trim() !== "";
|
|
115
170
|
|
|
116
171
|
/** The font pdf.js resolved for `id`, if it did. `get` throws on an unresolved id; `has` does not. */
|
|
117
172
|
function resolved(page: Page, id: string): unknown {
|
|
@@ -124,9 +179,10 @@ function resolved(page: Page, id: string): unknown {
|
|
|
124
179
|
*/
|
|
125
180
|
function loadedFonts(page: Page): Map<string, RawFont> {
|
|
126
181
|
const out = new Map<string, RawFont>();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
182
|
+
// pdf.js yields `[id, object]` pairs, typed `any[]`.
|
|
183
|
+
for (const entry of page.commonObjs) {
|
|
184
|
+
const f = rawFontOf(String(entry[0]), entry[1]);
|
|
185
|
+
if (f) out.set(f.id, f);
|
|
130
186
|
}
|
|
131
187
|
return out;
|
|
132
188
|
}
|
|
@@ -167,7 +223,7 @@ async function readPage(
|
|
|
167
223
|
* Both are far below any balance tolerance, so the simpler rule stays.
|
|
168
224
|
*/
|
|
169
225
|
function metricsOf(font: unknown): { ascent: number; descent: number } {
|
|
170
|
-
const f = (font
|
|
226
|
+
const f = isRecord(font) ? font : {};
|
|
171
227
|
const ascent = f["ascent"];
|
|
172
228
|
if (typeof ascent !== "number" || !(ascent > 0))
|
|
173
229
|
return { ascent: DEFAULT_ASCENT, descent: DEFAULT_DESCENT };
|
|
@@ -179,8 +235,8 @@ function metricsOf(font: unknown): { ascent: number; descent: number } {
|
|
|
179
235
|
function pageText(page: Page, items: readonly TextItem[]): PageText {
|
|
180
236
|
const vp = page.getViewport({ scale: 1 });
|
|
181
237
|
const runs: TextRun[] = items.map((it) => {
|
|
182
|
-
const [a = 0, b = 0, , d = 0, e = 0, f = 0] = it.transform
|
|
183
|
-
const [x, baseline] = vp.convertToViewportPoint(e, f)
|
|
238
|
+
const [a = 0, b = 0, , d = 0, e = 0, f = 0] = numbersOf(it.transform);
|
|
239
|
+
const [x, baseline] = pointOf(vp.convertToViewportPoint(e, f));
|
|
184
240
|
const size = Math.hypot(a, b) || it.height || Math.abs(d);
|
|
185
241
|
return {
|
|
186
242
|
text: it.str,
|
|
@@ -200,7 +256,7 @@ function pageText(page: Page, items: readonly TextItem[]): PageText {
|
|
|
200
256
|
|
|
201
257
|
/** The font name pdf.js resolved for an item, else its internal id. */
|
|
202
258
|
function fontNameOf(font: unknown, id: string): string {
|
|
203
|
-
const name = (font
|
|
259
|
+
const name = fieldOf(font, "name");
|
|
204
260
|
return typeof name === "string" && name ? name : id;
|
|
205
261
|
}
|
|
206
262
|
|
|
@@ -211,12 +267,12 @@ function fontNameOf(font: unknown, id: string): string {
|
|
|
211
267
|
*/
|
|
212
268
|
function boxOf(
|
|
213
269
|
page: Page,
|
|
214
|
-
vp:
|
|
270
|
+
vp: Viewport,
|
|
215
271
|
it: TextItem,
|
|
216
272
|
lib: PdfJs,
|
|
217
273
|
): Omit<TextBox, "fill"> {
|
|
218
|
-
const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0] = it.transform
|
|
219
|
-
const [x, baseline] = vp.convertToViewportPoint(e, f)
|
|
274
|
+
const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0] = numbersOf(it.transform);
|
|
275
|
+
const [x, baseline] = pointOf(vp.convertToViewportPoint(e, f));
|
|
220
276
|
const size = Math.hypot(c, d) || Math.hypot(a, b) || it.height;
|
|
221
277
|
const font = resolved(page, it.fontName);
|
|
222
278
|
const m = metricsOf(font);
|
|
@@ -229,7 +285,7 @@ function boxOf(
|
|
|
229
285
|
font: fontNameOf(font, it.fontName),
|
|
230
286
|
text: it.str,
|
|
231
287
|
upright: isUpright(
|
|
232
|
-
lib.Util.transform(vp.transform, it.transform)
|
|
288
|
+
numbersOf(lib.Util.transform(vp.transform, it.transform)),
|
|
233
289
|
),
|
|
234
290
|
};
|
|
235
291
|
}
|
|
@@ -238,7 +294,7 @@ function boxOf(
|
|
|
238
294
|
function layoutOf(
|
|
239
295
|
page: Page,
|
|
240
296
|
items: readonly TextItem[],
|
|
241
|
-
ops:
|
|
297
|
+
ops: OperatorList,
|
|
242
298
|
lib: PdfJs,
|
|
243
299
|
): PageLayout {
|
|
244
300
|
const vp = page.getViewport({ scale: 1 });
|
|
@@ -290,10 +346,11 @@ export async function factsOf(doc: Doc, lib: PdfJs): Promise<PdfRead> {
|
|
|
290
346
|
|
|
291
347
|
/** pdf.js's own exception names for the two conditions a caller can act on. */
|
|
292
348
|
export function failureOf(e: unknown): PdfRead {
|
|
293
|
-
const
|
|
294
|
-
const
|
|
349
|
+
const name = fieldOf(e, "name");
|
|
350
|
+
const message = fieldOf(e, "message");
|
|
351
|
+
const detail = `${typeof name === "string" ? name : "Error"}: ${typeof message === "string" ? message : String(e)}`;
|
|
295
352
|
return fail(
|
|
296
|
-
|
|
353
|
+
name === "PasswordException" ? "encrypted" : "unreadable",
|
|
297
354
|
detail,
|
|
298
355
|
);
|
|
299
356
|
}
|
package/src/pdf-geometry.ts
CHANGED
|
@@ -160,6 +160,10 @@ export type FontProgram =
|
|
|
160
160
|
/** A program pdf.js named but this module does not know — kept verbatim, never guessed. */
|
|
161
161
|
export type UnknownProgram = `unknown:${string}`;
|
|
162
162
|
|
|
163
|
+
const isUnknownProgram = (
|
|
164
|
+
p: FontProgram | UnknownProgram,
|
|
165
|
+
): p is UnknownProgram => p.startsWith("unknown:");
|
|
166
|
+
|
|
163
167
|
/**
|
|
164
168
|
* One font the PDF draws text with.
|
|
165
169
|
*
|
|
@@ -296,7 +300,5 @@ export const POPPLER_TYPE: Readonly<Record<FontProgram, string>> = {
|
|
|
296
300
|
/** The type column for any fact, the unknown ones verbatim. */
|
|
297
301
|
export function popplerType(f: FontFact): string {
|
|
298
302
|
if (f.kind === "type3") return POPPLER_TYPE.Type3;
|
|
299
|
-
return f.program.
|
|
300
|
-
? f.program
|
|
301
|
-
: POPPLER_TYPE[f.program as FontProgram];
|
|
303
|
+
return isUnknownProgram(f.program) ? f.program : POPPLER_TYPE[f.program];
|
|
302
304
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What paperlint needs from a LaTeX source, shaped by its rules and `paperlint new` — not by the
|
|
3
|
+
* parser. The one implementation is `src/adapters/latex/` (unified-latex); `src/cli.ts` wires it.
|
|
4
|
+
*/
|
|
5
|
+
import type {
|
|
6
|
+
ClassLine,
|
|
7
|
+
DocumentClass,
|
|
8
|
+
Outline,
|
|
9
|
+
TextRun,
|
|
10
|
+
} from "../domain/tex-document.ts";
|
|
11
|
+
|
|
12
|
+
export interface LatexReader {
|
|
13
|
+
/** The source's `\documentclass`, as one of its states. */
|
|
14
|
+
readonly documentClass: (src: string) => ClassLine;
|
|
15
|
+
/** Its sections, where the back matter begins, and where the document ends. */
|
|
16
|
+
readonly outline: (src: string) => Outline;
|
|
17
|
+
/** The text a reader sees: prose, the author block, footnotes — not comments, keys, math, code. */
|
|
18
|
+
readonly renderedRuns: (src: string) => readonly TextRun[];
|
|
19
|
+
/**
|
|
20
|
+
* A preset's `template` → the class it names: a whole `\documentclass[…]{…}` line, or a bare
|
|
21
|
+
* class name (no option). Null for anything else.
|
|
22
|
+
*/
|
|
23
|
+
readonly template: (text: string) => DocumentClass | null;
|
|
24
|
+
/**
|
|
25
|
+
* `src` with its `\documentclass` set to `want` — unless it already is `want`'s class with every
|
|
26
|
+
* option `want` names (the author's own options stay), or it has none.
|
|
27
|
+
*/
|
|
28
|
+
readonly withDocumentClass: (src: string, want: DocumentClass) => string;
|
|
29
|
+
}
|
|
@@ -2,26 +2,11 @@
|
|
|
2
2
|
* The port `paperlint toolchain` installs and checks an external program through. One port, many
|
|
3
3
|
* programs: banal implements it now, TeX Live in #76.
|
|
4
4
|
*/
|
|
5
|
-
import type { Opaque } from "ts-essentials";
|
|
6
5
|
import type { Result } from "../domain/result.ts";
|
|
7
6
|
import type { Lines } from "../domain/text.ts";
|
|
7
|
+
import type { Ready } from "../domain/ready.ts";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* Present, verified, running. BRANDED: a literal `{ where, fresh, verified }` is not a `Ready`, so
|
|
11
|
-
* only an installer's adapter — one minting function each (banal's is `ready` in
|
|
12
|
-
* `adapters/banal/index.ts`) — can make one, and a ready line cannot be printed without it.
|
|
13
|
-
*/
|
|
14
|
-
export type Ready = Opaque<
|
|
15
|
-
{
|
|
16
|
-
/** Where it is: the program's path or its bin directory. */
|
|
17
|
-
readonly where: string;
|
|
18
|
-
/** This run installed it. */
|
|
19
|
-
readonly fresh: boolean;
|
|
20
|
-
/** What was checked before calling it ready, as a person reads it. */
|
|
21
|
-
readonly verified: string;
|
|
22
|
-
},
|
|
23
|
-
"Ready"
|
|
24
|
-
>;
|
|
9
|
+
export type { Ready };
|
|
25
10
|
|
|
26
11
|
export interface ToolInstaller {
|
|
27
12
|
/** The program as a person reads it: `banal 1.2 (HotCRP f3e4352)`. */
|
package/src/presets.test.ts
CHANGED
|
@@ -10,15 +10,18 @@ import { readdirSync, readFileSync } from "node:fs";
|
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { describe, expect, it } from "vitest";
|
|
12
12
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
13
|
-
import {
|
|
13
|
+
import { presetsDir } from "./package-dirs.ts";
|
|
14
|
+
import { parsePreset } from "./tex-requirements.ts";
|
|
14
15
|
import {
|
|
15
16
|
MAX_PRESET_DEPTH,
|
|
17
|
+
SHIPPED_PREFIX,
|
|
18
|
+
shippedPresets,
|
|
16
19
|
labelOf,
|
|
17
20
|
presetProblemText,
|
|
18
21
|
resolvePreset,
|
|
19
22
|
} from "./presets.ts";
|
|
20
23
|
|
|
21
|
-
const VENUES =
|
|
24
|
+
const VENUES = presetsDir();
|
|
22
25
|
const shipped = Object.fromEntries(
|
|
23
26
|
readdirSync(VENUES)
|
|
24
27
|
.filter((f) => f.endsWith(".jsonc") || f.endsWith(".json"))
|
|
@@ -44,7 +47,11 @@ describe("shipped presets", () => {
|
|
|
44
47
|
expect(r.value.format.columns).toBe(2);
|
|
45
48
|
expect(r.value.format.fontsText).toBe("LinLibertine");
|
|
46
49
|
expect([...r.value.format.kinds.keys()]).toEqual(["short", "full", "demo"]);
|
|
47
|
-
|
|
50
|
+
// The family's template, as its file spells it: the child names none.
|
|
51
|
+
expect(r.value.template).toEqual({
|
|
52
|
+
text: "\\documentclass[sigconf]{acmart}",
|
|
53
|
+
file: join(VENUES, "acm-sigconf.jsonc"),
|
|
54
|
+
});
|
|
48
55
|
expect("acmart" in r.value.tex.packages).toBe(true);
|
|
49
56
|
expect(r.value.label).toBe("agenticdev");
|
|
50
57
|
});
|
|
@@ -70,6 +77,45 @@ describe("shipped presets", () => {
|
|
|
70
77
|
});
|
|
71
78
|
});
|
|
72
79
|
|
|
80
|
+
describe("the naming convention of shipped presets (docs/rules.md)", () => {
|
|
81
|
+
// Each shipped preset, parsed: its file name and what it extends. The families are DERIVED — a
|
|
82
|
+
// shipped preset another shipped preset extends — so a new family is checked the day it ships.
|
|
83
|
+
const presets = shippedPresets(VENUES).map((name) => {
|
|
84
|
+
const file = join(VENUES, `${name}.jsonc`);
|
|
85
|
+
return {
|
|
86
|
+
name,
|
|
87
|
+
extends: parsePreset(readFileSync(file, "utf8"), file, VENUES).extends,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const families = presets.filter((p) =>
|
|
91
|
+
presets.some((q) => q.extends === `${SHIPPED_PREFIX}${p.name}`),
|
|
92
|
+
);
|
|
93
|
+
const publishers = families.map((f) => f.name.slice(0, f.name.indexOf("-")));
|
|
94
|
+
|
|
95
|
+
it("there are families to check (else the checks below see nothing)", () => {
|
|
96
|
+
expect(families.map((f) => f.name).sort()).toEqual([
|
|
97
|
+
"acm-sigconf",
|
|
98
|
+
"ieee-conference",
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it.each(families.map((f) => [f.name]))(
|
|
103
|
+
"the family %s is <publisher>-<template variant>",
|
|
104
|
+
(name) => {
|
|
105
|
+
expect(name).toMatch(/^[a-z0-9]+-[a-z0-9-]+$/);
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
it.each(presets.filter((p) => p.extends !== null).map((p) => [p.name]))(
|
|
110
|
+
"the venue %s carries no publisher prefix — its template is said by `extends`",
|
|
111
|
+
(name) => {
|
|
112
|
+
expect(publishers.filter((pub) => name.startsWith(`${pub}-`))).toEqual(
|
|
113
|
+
[],
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
73
119
|
describe("a project's own preset, by relative path", () => {
|
|
74
120
|
const usenix = JSON.stringify({
|
|
75
121
|
extends: "paperlint:acm-sigconf",
|
|
@@ -149,7 +195,7 @@ describe("what does not resolve, and says why", () => {
|
|
|
149
195
|
"a shipped name with a typo",
|
|
150
196
|
"paperlint:agenticdve",
|
|
151
197
|
"not-found",
|
|
152
|
-
/acm-sigconf, agenticdev, aisec, realm/,
|
|
198
|
+
/acm-sigconf, agenticdev, aidc, aisec, ieee-conference, realm/,
|
|
153
199
|
],
|
|
154
200
|
["the base TeX set", "paperlint:tex-base", "not-found", /agenticdev/],
|
|
155
201
|
[
|
|
@@ -225,6 +271,21 @@ describe("chains that do not resolve", () => {
|
|
|
225
271
|
});
|
|
226
272
|
});
|
|
227
273
|
|
|
274
|
+
describe("a preset's template: kept as its file spells it, with that file", () => {
|
|
275
|
+
it("a child's template replaces its parent's, and names its own file", () => {
|
|
276
|
+
const r = resolve("./own.jsonc", {
|
|
277
|
+
"/work/papers/p/own.jsonc": JSON.stringify({
|
|
278
|
+
extends: "paperlint:aidc",
|
|
279
|
+
template: "\\documentclass[conference]{IEEEtran}",
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
expect(r.ok && r.value.template).toEqual({
|
|
283
|
+
text: "\\documentclass[conference]{IEEEtran}",
|
|
284
|
+
file: "/work/papers/p/own.jsonc",
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
228
289
|
describe("labelOf — display only, never used to resolve", () => {
|
|
229
290
|
it.each([
|
|
230
291
|
["paperlint:agenticdev", "agenticdev"],
|
package/src/presets.ts
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* tex union — a child never removes a package its parent needs
|
|
20
20
|
* format per key, the child wins; `kinds` by kind name, a child's kind replaces that kind
|
|
21
21
|
* rules per rule id, the child wins
|
|
22
|
-
* template the child wins; so does `name`
|
|
22
|
+
* template the child wins; so does `name`, and `required_sections` (the whole list)
|
|
23
|
+
* aliases union, with every `name` — what the venue is called along the chain
|
|
23
24
|
*
|
|
24
25
|
* ── THE LABEL ─────────────────────────────────────────────────────────────────────
|
|
25
26
|
* Messages, the facts file and the build plan need a word for the venue. It is the most derived
|
|
@@ -36,10 +37,11 @@ import {
|
|
|
36
37
|
profileFileOf,
|
|
37
38
|
venueNames,
|
|
38
39
|
type PresetFile,
|
|
40
|
+
type RequiredSection,
|
|
39
41
|
type TexRequirements,
|
|
40
42
|
type VenueFormat,
|
|
41
43
|
} from "./tex-requirements.ts";
|
|
42
|
-
import
|
|
44
|
+
import { callerPath } from "./caller-path.ts";
|
|
43
45
|
import { err, ok, type Result } from "./domain/result.ts";
|
|
44
46
|
import type { Files } from "./ports/files.ts";
|
|
45
47
|
import {
|
|
@@ -61,13 +63,27 @@ export interface PresetDeps {
|
|
|
61
63
|
readonly venuesDir: string;
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/** A preset's `template`, as its file spells it. */
|
|
67
|
+
export interface PresetTemplate {
|
|
68
|
+
readonly text: string;
|
|
69
|
+
readonly file: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
/** A resolved chain, merged. */
|
|
65
73
|
export interface Preset {
|
|
66
74
|
/** The word for this venue in messages. */
|
|
67
75
|
readonly label: string;
|
|
68
76
|
/** Every file of the chain, root first. */
|
|
69
77
|
readonly chain: readonly string[];
|
|
70
|
-
|
|
78
|
+
/**
|
|
79
|
+
* The `\\documentclass` the venue's template uses, as the preset spells it, and the file that
|
|
80
|
+
* spells it; null when no file of the chain names one. Read by the rules and `paperlint new`,
|
|
81
|
+
* through the LaTeX reader.
|
|
82
|
+
*/
|
|
83
|
+
readonly template: PresetTemplate | null;
|
|
84
|
+
/** Every `name` and `aliases` along the chain: what this venue is called in a paper's text. */
|
|
85
|
+
readonly aliases: readonly string[];
|
|
86
|
+
readonly requiredSections: readonly RequiredSection[];
|
|
71
87
|
readonly tex: TexRequirements;
|
|
72
88
|
readonly format: VenueFormat;
|
|
73
89
|
readonly rules: Readonly<Record<string, unknown>>;
|
|
@@ -120,7 +136,7 @@ function readPreset(
|
|
|
120
136
|
): Result<{ file: string; preset: PresetFile }, PresetProblem> {
|
|
121
137
|
const where = fileOf(spec, from, deps.venuesDir);
|
|
122
138
|
if (!where.ok) return where;
|
|
123
|
-
const bytes = deps.files.readBytes(where.value
|
|
139
|
+
const bytes = deps.files.readBytes(callerPath(where.value));
|
|
124
140
|
if (bytes === null)
|
|
125
141
|
return err({ kind: "not-found", spec, file: where.value, shipped: [] });
|
|
126
142
|
try {
|
|
@@ -166,33 +182,58 @@ export function mergeFormat(
|
|
|
166
182
|
parent: VenueFormat,
|
|
167
183
|
child: VenueFormat,
|
|
168
184
|
): VenueFormat {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
// Every field listed: a VenueFormat literal missing one is a compile error.
|
|
186
|
+
const pick = <K extends Exclude<keyof VenueFormat, "kinds">>(
|
|
187
|
+
k: K,
|
|
188
|
+
): VenueFormat[K] => child[k] ?? parent[k];
|
|
189
|
+
return {
|
|
190
|
+
pageWidthIn: pick("pageWidthIn"),
|
|
191
|
+
pageHeightIn: pick("pageHeightIn"),
|
|
192
|
+
columns: pick("columns"),
|
|
193
|
+
bodyPt: pick("bodyPt"),
|
|
194
|
+
bodyPtTol: pick("bodyPtTol"),
|
|
195
|
+
refPtMin: pick("refPtMin"),
|
|
196
|
+
refPtMax: pick("refPtMax"),
|
|
197
|
+
fontsText: pick("fontsText"),
|
|
198
|
+
fontsTitle: pick("fontsTitle"),
|
|
199
|
+
kinds: new Map([...parent.kinds, ...child.kinds]),
|
|
200
|
+
};
|
|
177
201
|
}
|
|
178
202
|
|
|
179
203
|
/** The chain, root first, merged into one preset. Pure. */
|
|
180
204
|
function merged(
|
|
181
205
|
spec: string,
|
|
182
|
-
rootFirst: readonly PresetFile[],
|
|
183
|
-
files: readonly string[],
|
|
206
|
+
rootFirst: readonly { readonly preset: PresetFile; readonly file: string }[],
|
|
184
207
|
): Preset {
|
|
185
|
-
const base
|
|
186
|
-
name:
|
|
187
|
-
template:
|
|
208
|
+
const base: {
|
|
209
|
+
name: string | null;
|
|
210
|
+
template: PresetTemplate | null;
|
|
211
|
+
aliases: readonly string[];
|
|
212
|
+
requiredSections: readonly RequiredSection[];
|
|
213
|
+
tex: TexRequirements;
|
|
214
|
+
format: VenueFormat;
|
|
215
|
+
rules: Readonly<Record<string, unknown>>;
|
|
216
|
+
} = {
|
|
217
|
+
name: null,
|
|
218
|
+
template: null,
|
|
219
|
+
aliases: [],
|
|
220
|
+
requiredSections: [],
|
|
188
221
|
tex: NO_REQUIREMENTS,
|
|
189
222
|
format: NO_FORMAT,
|
|
190
|
-
rules: {}
|
|
223
|
+
rules: {},
|
|
191
224
|
};
|
|
192
225
|
const m = rootFirst.reduce(
|
|
193
|
-
(acc, p) => ({
|
|
226
|
+
(acc, { preset: p, file }) => ({
|
|
194
227
|
name: p.name ?? acc.name,
|
|
195
|
-
template: p.template
|
|
228
|
+
template: p.template === null ? acc.template : { text: p.template, file },
|
|
229
|
+
aliases: [
|
|
230
|
+
...new Set([
|
|
231
|
+
...acc.aliases,
|
|
232
|
+
...(p.name === null ? [] : [p.name]),
|
|
233
|
+
...p.aliases,
|
|
234
|
+
]),
|
|
235
|
+
],
|
|
236
|
+
requiredSections: p.requiredSections ?? acc.requiredSections,
|
|
196
237
|
tex: p.tex ? mergeRequirements(acc.tex, p.tex) : acc.tex,
|
|
197
238
|
format: mergeFormat(acc.format, p.format),
|
|
198
239
|
rules: { ...acc.rules, ...p.rules },
|
|
@@ -201,8 +242,10 @@ function merged(
|
|
|
201
242
|
);
|
|
202
243
|
return {
|
|
203
244
|
label: m.name ?? labelOf(spec),
|
|
204
|
-
chain:
|
|
245
|
+
chain: rootFirst.map((x) => x.file),
|
|
205
246
|
template: m.template,
|
|
247
|
+
aliases: m.aliases,
|
|
248
|
+
requiredSections: m.requiredSections,
|
|
206
249
|
tex: m.tex,
|
|
207
250
|
format: m.format,
|
|
208
251
|
rules: m.rules,
|
|
@@ -224,13 +267,7 @@ export function resolvePreset(
|
|
|
224
267
|
? err({ ...chain.error, shipped: shippedPresets(deps.venuesDir) })
|
|
225
268
|
: chain;
|
|
226
269
|
const rootFirst = [...chain.value].reverse();
|
|
227
|
-
return ok(
|
|
228
|
-
merged(
|
|
229
|
-
spec,
|
|
230
|
-
rootFirst.map((x) => x.preset),
|
|
231
|
-
rootFirst.map((x) => x.file),
|
|
232
|
-
),
|
|
233
|
-
);
|
|
270
|
+
return ok(merged(spec, rootFirst));
|
|
234
271
|
}
|
|
235
272
|
|
|
236
273
|
/** The shipped presets' names (each is spelled `paperlint:<name>`). */
|
package/src/references.test.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { describe, expect, it } from "vitest";
|
|
11
11
|
import { ESLint } from "eslint";
|
|
12
|
+
import { eslintConfig } from "./cli.ts";
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
14
15
|
import { ok } from "./domain/result.ts";
|
|
15
|
-
import
|
|
16
|
+
import { absolutePath } from "./domain/paths.ts";
|
|
16
17
|
import type {
|
|
17
18
|
CheckReferences,
|
|
18
19
|
EntryVerdict,
|
|
@@ -78,9 +79,13 @@ const offline: CheckReferences = (_bib, cache) =>
|
|
|
78
79
|
const ctx = (files: ReturnType<typeof memoryFiles>) => ({
|
|
79
80
|
paperDir: PAPER,
|
|
80
81
|
env: {},
|
|
81
|
-
run: (
|
|
82
|
-
readPdf: (
|
|
83
|
-
measure:
|
|
82
|
+
run: () => ({ status: 0 }),
|
|
83
|
+
readPdf: () => Promise.reject(new Error("the references step reads no PDF")),
|
|
84
|
+
measure: {
|
|
85
|
+
measure: () => {
|
|
86
|
+
throw new Error("the references step measures nothing");
|
|
87
|
+
},
|
|
88
|
+
},
|
|
84
89
|
files,
|
|
85
90
|
});
|
|
86
91
|
|
|
@@ -94,32 +99,26 @@ async function build(tex: string, check: CheckReferences) {
|
|
|
94
99
|
return { files, out };
|
|
95
100
|
}
|
|
96
101
|
|
|
97
|
-
/**
|
|
98
|
-
* The rules as an ESLint plugin. Their context type is the narrow one they read (a custom
|
|
99
|
-
* language's SourceCode), which ESLint's JS-centred plugin type does not describe.
|
|
100
|
-
*/
|
|
101
|
-
const asPlugin = (rules: object): ESLint.Plugin => ({ rules }) as ESLint.Plugin;
|
|
102
|
-
|
|
103
102
|
/** Lint `tex` as the paper's paper.tex against what `files` recorded. */
|
|
104
103
|
async function lint(files: ReturnType<typeof memoryFiles>, tex: string) {
|
|
105
104
|
files.writeAtomic(
|
|
106
|
-
`${PAPER}/paper.tex`
|
|
105
|
+
absolutePath(`${PAPER}/paper.tex`),
|
|
107
106
|
new TextEncoder().encode(tex),
|
|
108
107
|
);
|
|
109
108
|
const eslint = new ESLint({
|
|
110
109
|
cwd: "/work",
|
|
111
110
|
overrideConfigFile: true,
|
|
112
|
-
overrideConfig: [
|
|
111
|
+
overrideConfig: eslintConfig([
|
|
113
112
|
{
|
|
114
113
|
files: ["**/paper.tex"],
|
|
115
114
|
plugins: {
|
|
116
115
|
tex: { languages: { latex: texLanguage } },
|
|
117
|
-
paper:
|
|
116
|
+
paper: { rules: referenceRules({ files }) },
|
|
118
117
|
},
|
|
119
118
|
language: "tex/latex",
|
|
120
119
|
rules: REFERENCE_RULE_LEVELS,
|
|
121
120
|
},
|
|
122
|
-
],
|
|
121
|
+
]),
|
|
123
122
|
});
|
|
124
123
|
const [res] = await eslint.lintText(tex, {
|
|
125
124
|
filePath: join(PAPER, "paper.tex"),
|
|
@@ -186,7 +185,7 @@ function fetching(key: string) {
|
|
|
186
185
|
return { check, seen };
|
|
187
186
|
}
|
|
188
187
|
const cacheText = (files: ReturnType<typeof memoryFiles>): string | null => {
|
|
189
|
-
const b = files.readBytes(lookupCachePath(PAPER)
|
|
188
|
+
const b = files.readBytes(absolutePath(lookupCachePath(PAPER)));
|
|
190
189
|
return b === null ? null : new TextDecoder().decode(b);
|
|
191
190
|
};
|
|
192
191
|
|
|
@@ -363,7 +362,7 @@ const record = (
|
|
|
363
362
|
) => {
|
|
364
363
|
const bib = present(bibliographyOf(files, PAPER), "the bibliography");
|
|
365
364
|
files.writeAtomic(
|
|
366
|
-
referencesPath(PAPER)
|
|
365
|
+
absolutePath(referencesPath(PAPER)),
|
|
367
366
|
new TextEncoder().encode(
|
|
368
367
|
JSON.stringify({
|
|
369
368
|
schema: 1,
|
|
@@ -424,9 +423,16 @@ describe("the reference rules over refs.bib, and on other files", () => {
|
|
|
424
423
|
const files = memoryFiles({});
|
|
425
424
|
const rules = referenceRules({ files });
|
|
426
425
|
for (const rule of Object.values(rules))
|
|
427
|
-
expect(
|
|
428
|
-
{
|
|
429
|
-
|
|
426
|
+
expect(
|
|
427
|
+
rule.create({
|
|
428
|
+
filename: `${PAPER}/notes.tex`,
|
|
429
|
+
cwd: "/work",
|
|
430
|
+
sourceCode: { getLocFromIndex: () => ({ line: 1, column: 0 }) },
|
|
431
|
+
report: () => {
|
|
432
|
+
throw new Error("a file that is not paper.tex is never reported");
|
|
433
|
+
},
|
|
434
|
+
}),
|
|
435
|
+
).toEqual({});
|
|
430
436
|
});
|
|
431
437
|
});
|
|
432
438
|
|