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
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The venue-conformance rules over `paper.tex` (`src/tex-venue-rules.ts`), on a paper held in
|
|
3
|
+
* memory beside the SHIPPED presets, so a preset edit that breaks a check shows here. Each rule has
|
|
4
|
+
* both halves: the finding a planted defect produces, and the silence of a conforming source.
|
|
5
|
+
*/
|
|
6
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
10
|
+
import { presetsDir } from "./package-dirs.ts";
|
|
11
|
+
import { latexReader } from "./adapters/latex/index.ts";
|
|
12
|
+
import {
|
|
13
|
+
judgeRequiredSections,
|
|
14
|
+
judgeTemplate,
|
|
15
|
+
otherVenues,
|
|
16
|
+
TEX_VENUE_RULE_LEVELS,
|
|
17
|
+
texVenueRules,
|
|
18
|
+
} from "./tex-venue-rules.ts";
|
|
19
|
+
import { resolvePreset } from "./presets.ts";
|
|
20
|
+
import { buildConfig, SHIPPED_RULES } from "./cli.ts";
|
|
21
|
+
|
|
22
|
+
const VENUES = presetsDir();
|
|
23
|
+
const PAPER = "/work/papers/p";
|
|
24
|
+
const shipped = Object.fromEntries(
|
|
25
|
+
readdirSync(VENUES)
|
|
26
|
+
.filter((f) => f.endsWith(".jsonc") || f.endsWith(".json"))
|
|
27
|
+
.map((f) => [join(VENUES, f), readFileSync(join(VENUES, f))]),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
interface Finding {
|
|
31
|
+
readonly rule: string;
|
|
32
|
+
readonly messageId: string;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
readonly line: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The line and 1-based column of an offset, as ESLint's text source code gives them. */
|
|
38
|
+
const locOf = (src: string) => (i: number) => {
|
|
39
|
+
const before = src.slice(0, i).split("\n");
|
|
40
|
+
return { line: before.length, column: (before.at(-1) ?? "").length + 1 };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every venue-conformance rule over one `paper.tex`, the way ESLint runs them: `create`, then
|
|
45
|
+
* `root`. `settings` is the paper's paperlint.json (absent: no file); `extra` more files.
|
|
46
|
+
*/
|
|
47
|
+
function lint(
|
|
48
|
+
tex: string,
|
|
49
|
+
settings: object | undefined,
|
|
50
|
+
{
|
|
51
|
+
filename = `${PAPER}/paper.tex`,
|
|
52
|
+
extra = {},
|
|
53
|
+
raw = true,
|
|
54
|
+
}: {
|
|
55
|
+
filename?: string;
|
|
56
|
+
extra?: Record<string, string>;
|
|
57
|
+
/** false: the source code carries no `raw` (not the `.tex` language's), only `text`. */
|
|
58
|
+
raw?: boolean;
|
|
59
|
+
} = {},
|
|
60
|
+
): Finding[] {
|
|
61
|
+
const files = memoryFiles({
|
|
62
|
+
...shipped,
|
|
63
|
+
...(settings === undefined
|
|
64
|
+
? {}
|
|
65
|
+
: { [`${PAPER}/paperlint.json`]: JSON.stringify(settings) }),
|
|
66
|
+
...extra,
|
|
67
|
+
});
|
|
68
|
+
const out: Finding[] = [];
|
|
69
|
+
for (const [name, rule] of Object.entries(
|
|
70
|
+
texVenueRules({ files, venuesDir: VENUES, latex: latexReader }),
|
|
71
|
+
)) {
|
|
72
|
+
const visitor = rule.create({
|
|
73
|
+
filename,
|
|
74
|
+
sourceCode: {
|
|
75
|
+
text: tex,
|
|
76
|
+
...(raw ? { raw: tex } : {}),
|
|
77
|
+
getLocFromIndex: locOf(tex),
|
|
78
|
+
},
|
|
79
|
+
report: (d) =>
|
|
80
|
+
out.push({
|
|
81
|
+
rule: `tex/${name}`,
|
|
82
|
+
messageId: d.messageId,
|
|
83
|
+
message: Object.entries(d.data ?? {}).reduce(
|
|
84
|
+
(m, [k, v]) => m.replaceAll(`{{${k}}}`, String(v)),
|
|
85
|
+
rule.meta.messages[d.messageId] ?? `(no message ${d.messageId})`,
|
|
86
|
+
),
|
|
87
|
+
line: d.loc.start.line,
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
visitor.root?.();
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ids = (fs: readonly Finding[]) =>
|
|
96
|
+
fs.map((f) => `${f.rule}:${f.messageId}`).sort();
|
|
97
|
+
|
|
98
|
+
const AIDC = { extends: "paperlint:aidc", kind: "regular" };
|
|
99
|
+
/** A paper in `documentclass`; the default body carries AIDC's required closing section. */
|
|
100
|
+
const paper = (
|
|
101
|
+
documentclass: string,
|
|
102
|
+
body = "Text.\n\\section*{LLM Usage Statement}\nNone.",
|
|
103
|
+
) =>
|
|
104
|
+
`% a comment\n${documentclass}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
105
|
+
const OFFICIAL = paper("\\documentclass[conference,compsoc]{IEEEtran}");
|
|
106
|
+
|
|
107
|
+
describe("tex/template — the class and every option the preset names", () => {
|
|
108
|
+
it("the official AIDC template passes", () => {
|
|
109
|
+
expect(lint(OFFICIAL, AIDC)).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("options in another order, with spaces and extra options of the paper's own, pass", () => {
|
|
113
|
+
expect(
|
|
114
|
+
lint(
|
|
115
|
+
paper("\\documentclass[ compsoc,\n conference , a4paper]{IEEEtran}"),
|
|
116
|
+
AIDC,
|
|
117
|
+
),
|
|
118
|
+
).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it.each<[string, string, string[], RegExp]>([
|
|
122
|
+
[
|
|
123
|
+
"[conference]{IEEEtran} — compsoc missing",
|
|
124
|
+
"\\documentclass[conference]{IEEEtran}",
|
|
125
|
+
["tex/template:missingOption"],
|
|
126
|
+
/the class option `compsoc` is missing: aidc requires `\\documentclass\[conference,compsoc\]\{IEEEtran\}`/,
|
|
127
|
+
],
|
|
128
|
+
[
|
|
129
|
+
"{acmart} under an IEEE preset",
|
|
130
|
+
"\\documentclass[sigconf]{acmart}",
|
|
131
|
+
["tex/template:wrongClass"],
|
|
132
|
+
/the class is `acmart`, and aidc requires/,
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
"[journal,compsoc]{IEEEtran} — conference missing",
|
|
136
|
+
"\\documentclass[journal,compsoc]{IEEEtran}",
|
|
137
|
+
["tex/template:missingOption"],
|
|
138
|
+
/`conference` is missing/,
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
"{IEEEtran} with no options — both missing, one finding each",
|
|
142
|
+
"\\documentclass{IEEEtran}",
|
|
143
|
+
["tex/template:missingOption", "tex/template:missingOption"],
|
|
144
|
+
/`conference` is missing/,
|
|
145
|
+
],
|
|
146
|
+
])("%s", (_, cls, want, message) => {
|
|
147
|
+
const fs = lint(paper(cls), AIDC);
|
|
148
|
+
expect(ids(fs)).toEqual(want);
|
|
149
|
+
expect(fs[0]?.message).toMatch(message);
|
|
150
|
+
// At the \documentclass line, where the fix is.
|
|
151
|
+
expect(fs[0]?.line).toBe(2);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("tex/template — the class line's states: missing, empty, commented out", () => {
|
|
156
|
+
it("a paper with no \\documentclass at all says so, at the top", () => {
|
|
157
|
+
const fs = lint(
|
|
158
|
+
"\\begin{document}x\\section*{LLM Usage Statement}\\end{document}\n",
|
|
159
|
+
AIDC,
|
|
160
|
+
);
|
|
161
|
+
expect(ids(fs)).toEqual(["tex/template:noClass"]);
|
|
162
|
+
expect(fs[0]?.line).toBe(1);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("🔴 \\documentclass{} names no class: an empty class, reported on its own line — not a missing one at the top", () => {
|
|
166
|
+
const fs = lint(
|
|
167
|
+
"% header\n\\documentclass{}\n\\begin{document}x\\section*{LLM Usage Statement}\\end{document}\n",
|
|
168
|
+
AIDC,
|
|
169
|
+
);
|
|
170
|
+
expect(ids(fs)).toEqual(["tex/template:emptyClass"]);
|
|
171
|
+
expect(fs[0]?.line).toBe(2);
|
|
172
|
+
expect(fs[0]?.message).toBe(
|
|
173
|
+
"the \\documentclass names no class; aidc requires `\\documentclass[conference,compsoc]{IEEEtran}`",
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("a \\documentclass inside a comment is not the paper's class", () => {
|
|
178
|
+
const fs = lint(
|
|
179
|
+
"% \\documentclass[conference,compsoc]{IEEEtran}\n\\documentclass{article}\n\\section*{LLM Usage Statement}\n",
|
|
180
|
+
AIDC,
|
|
181
|
+
);
|
|
182
|
+
expect(ids(fs)).toEqual(["tex/template:wrongClass"]);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("tex/template — the shipped ACM and ACL templates", () => {
|
|
187
|
+
it("🔴 acmart without sigconf — the manuscript format — fails every ACM preset", () => {
|
|
188
|
+
for (const settings of [
|
|
189
|
+
{ extends: "paperlint:aisec", kind: "research" },
|
|
190
|
+
{ extends: "paperlint:agenticdev", kind: "short" },
|
|
191
|
+
{ extends: "paperlint:acm-sigconf" },
|
|
192
|
+
])
|
|
193
|
+
expect(ids(lint(paper("\\documentclass{acmart}"), settings))).toEqual([
|
|
194
|
+
"tex/template:missingOption",
|
|
195
|
+
]);
|
|
196
|
+
expect(
|
|
197
|
+
lint(paper("\\documentclass[sigconf,review,anonymous]{acmart}"), {
|
|
198
|
+
extends: "paperlint:aisec",
|
|
199
|
+
kind: "research",
|
|
200
|
+
}),
|
|
201
|
+
).toEqual([]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("an ACL paper — article at 11pt, acl.sty as a package — passes realm", () => {
|
|
205
|
+
expect(
|
|
206
|
+
lint(paper("\\documentclass[11pt]{article}\n\\usepackage[review]{acl}"), {
|
|
207
|
+
extends: "paperlint:realm",
|
|
208
|
+
kind: "long",
|
|
209
|
+
}),
|
|
210
|
+
).toEqual([]);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("tex/template — a project's own preset, and nothing to judge", () => {
|
|
215
|
+
it("a preset with no template: nothing to compare, no finding", () => {
|
|
216
|
+
const own = `${PAPER}/own.jsonc`;
|
|
217
|
+
expect(
|
|
218
|
+
lint(
|
|
219
|
+
paper("\\documentclass{whatever}"),
|
|
220
|
+
{ extends: "./own.jsonc" },
|
|
221
|
+
{
|
|
222
|
+
extra: { [own]: JSON.stringify({ tex: { packages: { x: ["x"] } } }) },
|
|
223
|
+
},
|
|
224
|
+
),
|
|
225
|
+
).toEqual([]);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("tex/template — a template the reader cannot read, a bare class name, nothing to judge", () => {
|
|
230
|
+
it("a template the reader cannot read is said once, naming its file — the class is not judged", () => {
|
|
231
|
+
const own = `${PAPER}/own.jsonc`;
|
|
232
|
+
const fs = lint(
|
|
233
|
+
paper("\\documentclass{article}"),
|
|
234
|
+
{ extends: "./own.jsonc" },
|
|
235
|
+
{
|
|
236
|
+
extra: {
|
|
237
|
+
[own]: JSON.stringify({
|
|
238
|
+
template: "\\documentclass[a]{}",
|
|
239
|
+
tex: { packages: { x: ["x"] } },
|
|
240
|
+
}),
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
);
|
|
244
|
+
expect(ids(fs)).toEqual(["tex/template:badTemplate"]);
|
|
245
|
+
expect(fs[0]?.message).toBe(
|
|
246
|
+
`${own}: the preset's "template" "\\\\documentclass[a]{}" is neither a \\documentclass line nor a class name, so the class cannot be checked`,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("a bare class name as the template names the class and no option", () => {
|
|
251
|
+
const own = `${PAPER}/own.jsonc`;
|
|
252
|
+
const extra = {
|
|
253
|
+
[own]: JSON.stringify({
|
|
254
|
+
template: "article",
|
|
255
|
+
tex: { packages: { x: ["x"] } },
|
|
256
|
+
}),
|
|
257
|
+
};
|
|
258
|
+
const settings = { extends: "./own.jsonc" };
|
|
259
|
+
expect(
|
|
260
|
+
lint(paper("\\documentclass[twocolumn]{article}"), settings, { extra }),
|
|
261
|
+
).toEqual([]);
|
|
262
|
+
expect(
|
|
263
|
+
ids(lint(paper("\\documentclass{report}"), settings, { extra })),
|
|
264
|
+
).toEqual(["tex/template:wrongClass"]);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it.each<[string, object | undefined, string]>([
|
|
268
|
+
["no paperlint.json", undefined, `${PAPER}/paper.tex`],
|
|
269
|
+
[
|
|
270
|
+
"a paperlint.json with no extends",
|
|
271
|
+
{ extends: null },
|
|
272
|
+
`${PAPER}/paper.tex`,
|
|
273
|
+
],
|
|
274
|
+
[
|
|
275
|
+
"a preset that does not resolve (pdf/profile says so)",
|
|
276
|
+
{ extends: "paperlint:nope" },
|
|
277
|
+
`${PAPER}/paper.tex`,
|
|
278
|
+
],
|
|
279
|
+
["a file that is not paper.tex", AIDC, `${PAPER}/draft.tex`],
|
|
280
|
+
])("silent on %s", (_, settings, filename) => {
|
|
281
|
+
expect(
|
|
282
|
+
lint(paper("\\documentclass{article}"), settings, { filename }),
|
|
283
|
+
).toEqual([]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("reads `text` when the source code has no `raw`", () => {
|
|
287
|
+
expect(
|
|
288
|
+
ids(lint(paper("\\documentclass{article}"), AIDC, { raw: false })),
|
|
289
|
+
).toEqual(["tex/template:wrongClass"]);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
/** An AIDC paper: the official class, `sections` in order, then `tail` after them. */
|
|
294
|
+
const aidcPaper = (body: string): string =>
|
|
295
|
+
paper("\\documentclass[conference,compsoc]{IEEEtran}", body);
|
|
296
|
+
const BODY = "\\section{Introduction}\nText.\n\\section{Conclusion}\nText.\n";
|
|
297
|
+
const STATEMENT = "\\section*{LLM Usage Statement}\nNo LLMs were used.\n";
|
|
298
|
+
const BIB = "\\bibliographystyle{IEEEtran}\n\\bibliography{refs}\n";
|
|
299
|
+
const APPENDIX = "\\appendix\n\\section{Proofs}\nText.\n";
|
|
300
|
+
const required = (fs: readonly Finding[]) =>
|
|
301
|
+
fs.filter((f) => f.rule === "tex/required-section");
|
|
302
|
+
|
|
303
|
+
describe("tex/required-section — ACSAC's «LLM Usage Statement» under aidc", () => {
|
|
304
|
+
it.each<[string, string]>([
|
|
305
|
+
[
|
|
306
|
+
"after the last body section, before the bibliography",
|
|
307
|
+
BODY + STATEMENT + BIB,
|
|
308
|
+
],
|
|
309
|
+
[
|
|
310
|
+
"after the bibliography — «at the end of the paper»",
|
|
311
|
+
BODY + BIB + STATEMENT,
|
|
312
|
+
],
|
|
313
|
+
["between the body and an appendix", BODY + STATEMENT + APPENDIX + BIB],
|
|
314
|
+
[
|
|
315
|
+
"after the appendix and the bibliography",
|
|
316
|
+
BODY + APPENDIX + BIB + STATEMENT,
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
"unstarred, with its title broken over two lines",
|
|
320
|
+
BODY + "\\section{LLM Usage\n Statement}\nx\n" + BIB,
|
|
321
|
+
],
|
|
322
|
+
[
|
|
323
|
+
"inside a thebibliography paper, after it",
|
|
324
|
+
BODY +
|
|
325
|
+
"\\begin{thebibliography}{1}\\bibitem{a} A.\\end{thebibliography}\n" +
|
|
326
|
+
STATEMENT,
|
|
327
|
+
],
|
|
328
|
+
])("passes %s", (_, body) => {
|
|
329
|
+
expect(lint(aidcPaper(body), AIDC)).toEqual([]);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
describe("tex/required-section — what fails", () => {
|
|
334
|
+
it("🔴 the planted «Use of Generative AI» bold paragraph fails: not a section, not that title", () => {
|
|
335
|
+
const tex = aidcPaper(
|
|
336
|
+
BODY + BIB + "\\paragraph{Use of Generative AI.} We used an LLM.\n",
|
|
337
|
+
);
|
|
338
|
+
const fs = required(lint(tex, AIDC));
|
|
339
|
+
expect(ids(fs)).toEqual(["tex/required-section:missing"]);
|
|
340
|
+
expect(fs[0]?.message).toBe(
|
|
341
|
+
"aidc requires a section titled «LLM Usage Statement» and there is none — add `\\section*{LLM Usage Statement}` (the title exactly; a bold paragraph does not count)",
|
|
342
|
+
);
|
|
343
|
+
// At `\end{document}`, where the section would go.
|
|
344
|
+
expect(fs[0]?.line).toBe(tex.split("\n").indexOf("\\end{document}") + 1);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("🔴 a heading inside a macro definition or a comment environment is not in the PDF: missing", () => {
|
|
348
|
+
const tex = aidcPaper(
|
|
349
|
+
BODY +
|
|
350
|
+
BIB +
|
|
351
|
+
"\\newcommand{\\stmt}{\\section*{LLM Usage Statement}}\n" +
|
|
352
|
+
"\\begin{comment}\n\\section*{LLM Usage Statement}\n\\end{comment}\n",
|
|
353
|
+
);
|
|
354
|
+
expect(ids(required(lint(tex, AIDC)))).toEqual([
|
|
355
|
+
"tex/required-section:missing",
|
|
356
|
+
]);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("a near title is not the title: the venue's words are the contract", () => {
|
|
360
|
+
expect(
|
|
361
|
+
ids(
|
|
362
|
+
required(
|
|
363
|
+
lint(aidcPaper(BODY + "\\section*{LLM usage statement}\nx\n"), AIDC),
|
|
364
|
+
),
|
|
365
|
+
),
|
|
366
|
+
).toEqual(["tex/required-section:missing"]);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("🔴 the same section before the introduction fails, naming a body section after it", () => {
|
|
370
|
+
const tex = aidcPaper(STATEMENT + BODY + BIB);
|
|
371
|
+
const fs = required(lint(tex, AIDC));
|
|
372
|
+
expect(ids(fs)).toEqual(["tex/required-section:notLast"]);
|
|
373
|
+
expect(fs[0]?.message).toMatch(/«Introduction» comes after it/);
|
|
374
|
+
expect(fs[0]?.line).toBe(
|
|
375
|
+
tex.split("\n").indexOf("\\section*{LLM Usage Statement}") + 1,
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe("tex/required-section — the edges", () => {
|
|
381
|
+
it("an unmarked section after the bibliography is back matter, not body", () => {
|
|
382
|
+
// A statement, then the bibliography, then an unmarked section: that section is back matter.
|
|
383
|
+
expect(
|
|
384
|
+
lint(
|
|
385
|
+
aidcPaper(BODY + STATEMENT + BIB + "\\section{Artifact}\nx\n"),
|
|
386
|
+
AIDC,
|
|
387
|
+
),
|
|
388
|
+
).toEqual([]);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("a fragment with no document environment: the missing section points at the top", () => {
|
|
392
|
+
const fs = required(
|
|
393
|
+
lint(
|
|
394
|
+
"\\documentclass[conference,compsoc]{IEEEtran}\n\\section{A}\n",
|
|
395
|
+
AIDC,
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
expect(ids(fs)).toEqual(["tex/required-section:missing"]);
|
|
399
|
+
expect(fs[0]?.line).toBe(1);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("presets that require no section are silent, whatever the paper has", () => {
|
|
403
|
+
expect(
|
|
404
|
+
required(
|
|
405
|
+
lint(paper("\\documentclass[sigconf]{acmart}"), {
|
|
406
|
+
extends: "paperlint:aisec",
|
|
407
|
+
kind: "research",
|
|
408
|
+
}),
|
|
409
|
+
),
|
|
410
|
+
).toEqual([]);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("a position other than last (none): anywhere will do; a child's list replaces its parent's", () => {
|
|
414
|
+
const own = `${PAPER}/own.jsonc`;
|
|
415
|
+
const extra = {
|
|
416
|
+
[own]: JSON.stringify({
|
|
417
|
+
extends: "paperlint:aidc",
|
|
418
|
+
required_sections: [{ title: "Ethics" }],
|
|
419
|
+
}),
|
|
420
|
+
};
|
|
421
|
+
const settings = { extends: "./own.jsonc", kind: "regular" };
|
|
422
|
+
expect(
|
|
423
|
+
lint(aidcPaper("\\section{Ethics}\nx\n" + BODY), settings, { extra }),
|
|
424
|
+
).toEqual([]);
|
|
425
|
+
expect(ids(required(lint(aidcPaper(BODY), settings, { extra })))).toEqual([
|
|
426
|
+
"tex/required-section:missing",
|
|
427
|
+
]);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const INSTITUTION =
|
|
432
|
+
"\\institution{Presented at AISec 2026, co-located with ACM CCS}";
|
|
433
|
+
const leftovers = (fs: readonly Finding[]) =>
|
|
434
|
+
fs.filter((f) => f.rule === "tex/venue-leftover");
|
|
435
|
+
|
|
436
|
+
describe("tex/venue-leftover — another venue named in the text", () => {
|
|
437
|
+
it("🔴 the author-block line fails under aidc: both names, on that line", () => {
|
|
438
|
+
const tex = aidcPaper(`${INSTITUTION}\n${BODY}${STATEMENT}`);
|
|
439
|
+
const fs = leftovers(lint(tex, AIDC));
|
|
440
|
+
expect(fs.map((f) => f.message)).toEqual([
|
|
441
|
+
"«AISec» names aisec, and this paper extends aidc — a leftover from an earlier submission? A reviewer reads it before the abstract. Comments and citation keys are not reported; a sentence that names the other venue on purpose can keep it with a disable directive",
|
|
442
|
+
expect.stringContaining("«ACM CCS» names aisec"),
|
|
443
|
+
]);
|
|
444
|
+
const line = tex.split("\n").indexOf(INSTITUTION) + 1;
|
|
445
|
+
expect(fs.map((f) => f.line)).toEqual([line, line]);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("the same line passes under aisec — it is that venue's own name", () => {
|
|
449
|
+
const tex = paper(
|
|
450
|
+
"\\documentclass[sigconf]{acmart}",
|
|
451
|
+
`${INSTITUTION}\nText.`,
|
|
452
|
+
);
|
|
453
|
+
expect(
|
|
454
|
+
leftovers(lint(tex, { extends: "paperlint:aisec", kind: "research" })),
|
|
455
|
+
).toEqual([]);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("a \\cite key and a comment naming it produce no finding", () => {
|
|
459
|
+
const tex = aidcPaper(
|
|
460
|
+
`As shown~\\cite{aisec2025,AISec} and \\citep{AISec}. % AISec 2026\n${BODY}${STATEMENT}`,
|
|
461
|
+
);
|
|
462
|
+
expect(leftovers(lint(tex, AIDC))).toEqual([]);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("a citation's note in square brackets is printed, so it is reported", () => {
|
|
466
|
+
const tex = aidcPaper(
|
|
467
|
+
`As shown~\\citep[presented at AISec]{aisec2025}.\n${BODY}${STATEMENT}`,
|
|
468
|
+
);
|
|
469
|
+
expect(leftovers(lint(tex, AIDC)).map((f) => f.line)).toEqual([4]);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("the bibliography, math, code and labels are not the text a reader sees as the venue's", () => {
|
|
473
|
+
const tex = aidcPaper(
|
|
474
|
+
`$AISec$ \\label{AISec} \\ref{AISec} \\url{https://AISec.cc} \\begin{verbatim}AISec\\end{verbatim}\n` +
|
|
475
|
+
`${BODY}${STATEMENT}\\begin{thebibliography}{1}\\bibitem{a} In Proc. AISec.\\end{thebibliography}\n`,
|
|
476
|
+
);
|
|
477
|
+
expect(leftovers(lint(tex, AIDC))).toEqual([]);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("tex/venue-leftover — a URL is not text, a definition body is", () => {
|
|
482
|
+
it("\\href: a name in the URL is not reported, a name in the link text is", () => {
|
|
483
|
+
const inUrl = aidcPaper(
|
|
484
|
+
`See \\href{https://example.org/AISec/2026}{the workshop}.\n${BODY}${STATEMENT}`,
|
|
485
|
+
);
|
|
486
|
+
expect(leftovers(lint(inUrl, AIDC))).toEqual([]);
|
|
487
|
+
const inText = aidcPaper(
|
|
488
|
+
`See \\href{https://example.org/}{AISec 2026}.\n${BODY}${STATEMENT}`,
|
|
489
|
+
);
|
|
490
|
+
expect(leftovers(lint(inText, AIDC)).map((f) => f.line)).toEqual([4]);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it.each<[string, string]>([
|
|
494
|
+
["unused", ""],
|
|
495
|
+
["used", "Submitted to \\oldvenue.\n"],
|
|
496
|
+
])(
|
|
497
|
+
"a macro definition naming another venue is reported at the definition, %s",
|
|
498
|
+
(_, use) => {
|
|
499
|
+
const tex = aidcPaper(
|
|
500
|
+
`\\newcommand{\\oldvenue}{AISec}\n${use}${BODY}${STATEMENT}`,
|
|
501
|
+
);
|
|
502
|
+
const fs = leftovers(lint(tex, AIDC));
|
|
503
|
+
expect(fs.map((f) => [f.line, f.message.slice(0, 16)])).toEqual([
|
|
504
|
+
[4, "«AISec» names ai"],
|
|
505
|
+
]);
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
describe("tex/venue-leftover — where names are found, and whose they are", () => {
|
|
511
|
+
it.each<[string, string, string[]]>([
|
|
512
|
+
[
|
|
513
|
+
"the preamble's \\acmConference",
|
|
514
|
+
"\\acmConference[AISec '26]{x}",
|
|
515
|
+
["AISec"],
|
|
516
|
+
],
|
|
517
|
+
["a footnote", "Text.\\footnote{Submitted to REALM.}", ["REALM"]],
|
|
518
|
+
["a tie inside the name", "ACM~CCS", ["ACM CCS"]],
|
|
519
|
+
[
|
|
520
|
+
"whole words only: AISecX and xAIDC are other words",
|
|
521
|
+
"AISecX, EMNLPs and AgenticDevOps.",
|
|
522
|
+
[],
|
|
523
|
+
],
|
|
524
|
+
["case matters: aisec is not AISec", "aisec.cc", []],
|
|
525
|
+
])("%s", (_, text, want) => {
|
|
526
|
+
const tex = aidcPaper(`${text}\n${BODY}${STATEMENT}`);
|
|
527
|
+
expect(
|
|
528
|
+
leftovers(lint(tex, AIDC)).map((f) => /«(.*?)»/u.exec(f.message)?.[1]),
|
|
529
|
+
).toEqual(want);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("a name a venue shares with this paper's own chain is not a leftover", () => {
|
|
533
|
+
const own = `${PAPER}/own.jsonc`;
|
|
534
|
+
const extra = {
|
|
535
|
+
[own]: JSON.stringify({
|
|
536
|
+
extends: "paperlint:aidc",
|
|
537
|
+
name: "AIDC 2027",
|
|
538
|
+
aliases: ["ACM CCS"],
|
|
539
|
+
}),
|
|
540
|
+
};
|
|
541
|
+
const tex = aidcPaper(`ACM CCS and AIDC 2027.\n${BODY}${STATEMENT}`);
|
|
542
|
+
expect(
|
|
543
|
+
leftovers(
|
|
544
|
+
lint(tex, { extends: "./own.jsonc", kind: "regular" }, { extra }),
|
|
545
|
+
),
|
|
546
|
+
).toEqual([]);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("under a family, a venue that extends it is another venue", () => {
|
|
550
|
+
// ieee-conference is on aidc's chain, so under the family aidc is ANOTHER venue.
|
|
551
|
+
const tex = paper(
|
|
552
|
+
"\\documentclass[conference]{IEEEtran}",
|
|
553
|
+
"Accepted at AIDC. Formerly at AISec.",
|
|
554
|
+
);
|
|
555
|
+
expect(
|
|
556
|
+
leftovers(lint(tex, { extends: "paperlint:ieee-conference" }))
|
|
557
|
+
.map((f) => /«(.*?)»/u.exec(f.message)?.[1])
|
|
558
|
+
.sort(),
|
|
559
|
+
).toEqual(["AIDC", "AISec"]);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
describe("the judges, on places the parser did not give", () => {
|
|
564
|
+
const files = memoryFiles(shipped);
|
|
565
|
+
const aidc = resolvePreset("paperlint:aidc", `${PAPER}/paperlint.json`, {
|
|
566
|
+
files,
|
|
567
|
+
venuesDir: VENUES,
|
|
568
|
+
});
|
|
569
|
+
const preset = aidc.ok ? aidc.value : null;
|
|
570
|
+
|
|
571
|
+
it("a class line with no place is reported at the top of the file, not at an invented offset", () => {
|
|
572
|
+
expect(preset).not.toBeNull();
|
|
573
|
+
if (preset === null) return;
|
|
574
|
+
expect(
|
|
575
|
+
judgeTemplate(
|
|
576
|
+
{ kind: "empty", place: { kind: "unplaced" } },
|
|
577
|
+
preset,
|
|
578
|
+
latexReader,
|
|
579
|
+
),
|
|
580
|
+
).toMatchObject([{ messageId: "emptyClass", at: null }]);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("a required section with no place is never out of order; a section with no place is not counted as after it", () => {
|
|
584
|
+
expect(preset).not.toBeNull();
|
|
585
|
+
if (preset === null) return;
|
|
586
|
+
const at = (start: number) => ({
|
|
587
|
+
kind: "at" as const,
|
|
588
|
+
span: { start, end: start + 1 },
|
|
589
|
+
});
|
|
590
|
+
const statement = "LLM Usage Statement";
|
|
591
|
+
expect(
|
|
592
|
+
judgeRequiredSections(
|
|
593
|
+
() => ({
|
|
594
|
+
sections: [
|
|
595
|
+
{ title: statement, place: { kind: "unplaced" } },
|
|
596
|
+
{ title: "Introduction", place: at(50) },
|
|
597
|
+
],
|
|
598
|
+
backMatter: null,
|
|
599
|
+
end: 99,
|
|
600
|
+
}),
|
|
601
|
+
preset,
|
|
602
|
+
),
|
|
603
|
+
).toEqual([]);
|
|
604
|
+
expect(
|
|
605
|
+
judgeRequiredSections(
|
|
606
|
+
() => ({
|
|
607
|
+
sections: [
|
|
608
|
+
{ title: statement, place: at(10) },
|
|
609
|
+
{ title: "Introduction", place: { kind: "unplaced" } },
|
|
610
|
+
],
|
|
611
|
+
backMatter: null,
|
|
612
|
+
end: 99,
|
|
613
|
+
}),
|
|
614
|
+
preset,
|
|
615
|
+
),
|
|
616
|
+
).toEqual([]);
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
describe("otherVenues", () => {
|
|
621
|
+
it("every shipped venue off the paper's chain; one that does not resolve is skipped", () => {
|
|
622
|
+
const files = memoryFiles(
|
|
623
|
+
Object.fromEntries(
|
|
624
|
+
Object.entries(shipped).filter(([f]) => !f.endsWith("realm.jsonc")),
|
|
625
|
+
),
|
|
626
|
+
);
|
|
627
|
+
const deps = { files, venuesDir: VENUES };
|
|
628
|
+
const from = `${PAPER}/paperlint.json`;
|
|
629
|
+
const aidc = resolvePreset("paperlint:aidc", from, deps);
|
|
630
|
+
expect(aidc.ok).toBe(true);
|
|
631
|
+
if (!aidc.ok) return;
|
|
632
|
+
expect(otherVenues(aidc.value, from, deps).map((o) => o.label)).toEqual([
|
|
633
|
+
"acm-sigconf",
|
|
634
|
+
"agenticdev",
|
|
635
|
+
"aisec",
|
|
636
|
+
]);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe("paperlint's own config", () => {
|
|
641
|
+
it.each(Object.entries(TEX_VENUE_RULE_LEVELS))(
|
|
642
|
+
"turns %s on at %s for every paper.tex, and ships it",
|
|
643
|
+
(id, level) => {
|
|
644
|
+
const tex = buildConfig({}, { sentinel: "tex" }).find((b) =>
|
|
645
|
+
b.files?.includes("**/paper.tex"),
|
|
646
|
+
);
|
|
647
|
+
expect(tex?.rules?.[id]).toBe(level);
|
|
648
|
+
expect(SHIPPED_RULES.has(id)).toBe(true);
|
|
649
|
+
},
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
it("the levels: template and required-section are errors, venue-leftover a warning", () => {
|
|
653
|
+
expect(TEX_VENUE_RULE_LEVELS).toEqual({
|
|
654
|
+
"tex/template": "error",
|
|
655
|
+
"tex/required-section": "error",
|
|
656
|
+
"tex/venue-leftover": "warn",
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
});
|