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,141 @@
|
|
|
1
|
+
/** What the three readers share about unified-latex nodes: places, text, and the walk. */
|
|
2
|
+
import type * as Ast from "@unified-latex/unified-latex-types";
|
|
3
|
+
import { CONTINUE, SKIP, visit } from "@unified-latex/unified-latex-util-visit";
|
|
4
|
+
import type { Place, Span } from "../../domain/tex-document.ts";
|
|
5
|
+
import { KEY_SIGNATURES } from "./parse.ts";
|
|
6
|
+
|
|
7
|
+
export { collapse } from "../../domain/tex-document.ts";
|
|
8
|
+
|
|
9
|
+
/** unified-latex's node types, read-only: nothing here writes to a tree. */
|
|
10
|
+
export type Node = Readonly<Ast.Node>;
|
|
11
|
+
export type Macro = Readonly<Ast.Macro>;
|
|
12
|
+
export type Argument = Readonly<Ast.Argument>;
|
|
13
|
+
|
|
14
|
+
/** Anything the parser may have given a position: offsets of its first and past its last character. */
|
|
15
|
+
interface Positioned {
|
|
16
|
+
readonly position?: {
|
|
17
|
+
readonly start: { readonly offset: number };
|
|
18
|
+
readonly end: { readonly offset: number };
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Where a node stands, or that it has no position. */
|
|
23
|
+
export const placeOf = (n: Positioned): Place =>
|
|
24
|
+
n.position === undefined
|
|
25
|
+
? { kind: "unplaced" }
|
|
26
|
+
: {
|
|
27
|
+
kind: "at",
|
|
28
|
+
span: { start: n.position.start.offset, end: n.position.end.offset },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** The first mandatory (`{…}`) and optional (`[…]`) arguments' contents, and the last mandatory one's. */
|
|
32
|
+
export const mandatory = (m: Macro): readonly Argument[] =>
|
|
33
|
+
(m.args ?? []).filter((a) => a.openMark === "{");
|
|
34
|
+
export const optional = (m: Macro): Argument | undefined =>
|
|
35
|
+
(m.args ?? []).find((a) => a.openMark === "[");
|
|
36
|
+
|
|
37
|
+
/** What a string node reads as: itself, except a tie (`~`), which is a space the reader sees. */
|
|
38
|
+
export const stringText = (content: string): string =>
|
|
39
|
+
content === "~" ? " " : content;
|
|
40
|
+
|
|
41
|
+
/** Which of a macro's mandatory arguments it typesets: one of them, or none at all. */
|
|
42
|
+
type Shown =
|
|
43
|
+
| { readonly kind: "argument"; readonly at: number }
|
|
44
|
+
| { readonly kind: "none" };
|
|
45
|
+
|
|
46
|
+
const LAST: Shown = { kind: "argument", at: -1 };
|
|
47
|
+
const NONE: Shown = { kind: "none" };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Macros that do not typeset their last mandatory argument, as data. `\texorpdfstring{shown}{bookmark}`
|
|
51
|
+
* typesets the first; a declaration (`\color{red}`, `\fontsize{10}{12}`, `\vspace{1em}`,
|
|
52
|
+
* `\setlength{\x}{0pt}`) typesets none. Every other macro typesets its last (`\textcolor{red}{x}`,
|
|
53
|
+
* `\colorbox{yellow}{x}`, `\href{url}{x}`).
|
|
54
|
+
*/
|
|
55
|
+
const SHOWN_ARGUMENT: ReadonlyMap<string, Shown> = new Map<string, Shown>([
|
|
56
|
+
["texorpdfstring", { kind: "argument", at: 0 }],
|
|
57
|
+
...["color", "fontsize", "vspace", "hspace", "setlength", "addtolength"].map(
|
|
58
|
+
(m) => [m, NONE] as const,
|
|
59
|
+
),
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
/** The text a (non-key) macro typesets: see `SHOWN_ARGUMENT`. */
|
|
63
|
+
function shownText(m: Macro): string {
|
|
64
|
+
const shown = SHOWN_ARGUMENT.get(m.content) ?? LAST;
|
|
65
|
+
return shown.kind === "none"
|
|
66
|
+
? ""
|
|
67
|
+
: textOf(mandatory(m).at(shown.at)?.content);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The text of a node list with markup dropped: strings, spaces, what groups hold, and the argument a
|
|
72
|
+
* formatting macro typesets — its last mandatory one (`\textbf{Usage}`, `\textcolor{red}{Usage}`,
|
|
73
|
+
* `\href{url}{Usage}` → `Usage`) unless `SHOWN_ARGUMENT` says otherwise. A key macro's arguments
|
|
74
|
+
* (`\label{…}`, `\cite{…}`) are not text.
|
|
75
|
+
*/
|
|
76
|
+
export const textOf = (nodes: readonly Node[] | undefined): string =>
|
|
77
|
+
(nodes ?? [])
|
|
78
|
+
.map((n) => {
|
|
79
|
+
if (n.type === "string") return stringText(n.content);
|
|
80
|
+
if (n.type === "whitespace" || n.type === "parbreak") return " ";
|
|
81
|
+
if (n.type === "group") return textOf(n.content);
|
|
82
|
+
return n.type === "macro" && !(n.content in KEY_SIGNATURES)
|
|
83
|
+
? shownText(n)
|
|
84
|
+
: "";
|
|
85
|
+
})
|
|
86
|
+
.join("");
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A macro's place with its arguments: from its name to past the closing mark of the last argument
|
|
90
|
+
* that holds text. unified-latex positions the name and the arguments' contents, never the
|
|
91
|
+
* arguments themselves, so an empty trailing argument is not covered.
|
|
92
|
+
*/
|
|
93
|
+
export function macroPlace(m: Macro): Place {
|
|
94
|
+
const name = placeOf(m);
|
|
95
|
+
if (name.kind === "unplaced") return name;
|
|
96
|
+
const end = (m.args ?? []).reduce((e, a) => {
|
|
97
|
+
const last = a.content.at(-1)?.position?.end.offset;
|
|
98
|
+
return last === undefined ? e : last + a.closeMark.length;
|
|
99
|
+
}, name.span.end);
|
|
100
|
+
const span: Span = { start: name.span.start, end };
|
|
101
|
+
return { kind: "at", span };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** What visit reaches: a node, an argument, or a node list (a content array, an argument list). */
|
|
105
|
+
export type Visited = Readonly<Ast.Ast> | readonly (Node | Argument)[];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Everything visit reaches under `tree`, in document order, node lists included; `skip` prunes what
|
|
109
|
+
* it is given (a node or an argument, with the node that holds it) and everything under it. visit
|
|
110
|
+
* reports through a callback and returns nothing, so the one collection point of this adapter is here.
|
|
111
|
+
*/
|
|
112
|
+
export function visited(
|
|
113
|
+
// eslint-disable-next-line functional/prefer-immutable-types -- visit's own parameter type is mutable
|
|
114
|
+
tree: Ast.Ast,
|
|
115
|
+
skip: (v: Visited, parent: Visited | undefined) => boolean,
|
|
116
|
+
): readonly Visited[] {
|
|
117
|
+
// eslint-disable-next-line functional/prefer-immutable-types -- the one accumulator, see above
|
|
118
|
+
const seen: Visited[] = [];
|
|
119
|
+
visit(
|
|
120
|
+
tree,
|
|
121
|
+
(node, info) => {
|
|
122
|
+
// eslint-disable-next-line functional/immutable-data -- visit is callback-only; see above
|
|
123
|
+
seen.push(node);
|
|
124
|
+
return skip(node, info.parents[0]) ? SKIP : CONTINUE;
|
|
125
|
+
},
|
|
126
|
+
{ includeArrays: true },
|
|
127
|
+
);
|
|
128
|
+
return seen;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** A node list visit reached. `Array.isArray` alone does not narrow a readonly array. */
|
|
132
|
+
export const isList = (v: Visited): v is readonly (Node | Argument)[] =>
|
|
133
|
+
Array.isArray(v);
|
|
134
|
+
|
|
135
|
+
/** A macro's argument, as visit reports it. */
|
|
136
|
+
export const isArgument = (v: Visited): v is Argument =>
|
|
137
|
+
!isList(v) && v.type === "argument";
|
|
138
|
+
|
|
139
|
+
/** A node, as opposed to a node list or an argument (which visit also reports). */
|
|
140
|
+
export const isNode = (v: Visited): v is Node =>
|
|
141
|
+
!isList(v) && v.type !== "argument" && v.type !== "root";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** The document's sections, where its back matter starts, and where it ends. */
|
|
2
|
+
import type * as Ast from "@unified-latex/unified-latex-types";
|
|
3
|
+
import { isDefined } from "remeda";
|
|
4
|
+
import type { Heading, Outline } from "../../domain/tex-document.ts";
|
|
5
|
+
import {
|
|
6
|
+
collapse,
|
|
7
|
+
isNode,
|
|
8
|
+
macroPlace,
|
|
9
|
+
mandatory,
|
|
10
|
+
textOf,
|
|
11
|
+
visited,
|
|
12
|
+
type Macro,
|
|
13
|
+
type Node,
|
|
14
|
+
} from "./nodes.ts";
|
|
15
|
+
import { isDefinition, isUnrendered } from "./hidden.ts";
|
|
16
|
+
import type { ParsedTex } from "./parse.ts";
|
|
17
|
+
|
|
18
|
+
/** Macros and the environment that start the back matter: the appendix and the bibliography. */
|
|
19
|
+
const BACK_MATTER_MACROS: ReadonlySet<string> = new Set([
|
|
20
|
+
"appendix",
|
|
21
|
+
"bibliography",
|
|
22
|
+
"printbibliography",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const isBackMatter = (n: Node): boolean =>
|
|
26
|
+
(n.type === "macro" && BACK_MATTER_MACROS.has(n.content)) ||
|
|
27
|
+
(n.type === "environment" && n.env === "thebibliography");
|
|
28
|
+
|
|
29
|
+
const isSection = (n: Node): n is Macro =>
|
|
30
|
+
n.type === "macro" && n.content === "section";
|
|
31
|
+
|
|
32
|
+
const isDocument = (n: Node): n is Readonly<Ast.Environment> =>
|
|
33
|
+
n.type === "environment" && n.env === "document";
|
|
34
|
+
|
|
35
|
+
/** One `\section`/`\section*`: the last mandatory argument is its title. */
|
|
36
|
+
const headingOf = (m: Macro): Heading => ({
|
|
37
|
+
title: collapse(textOf(mandatory(m).at(-1)?.content)),
|
|
38
|
+
place: macroPlace(m),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The body's `\section`s (starred or not) in document order, the first back-matter node, and the
|
|
43
|
+
* end of the document. A source with no `document` environment is read whole, as a fragment. What
|
|
44
|
+
* the PDF never shows is pruned (`isUnrendered`), and so is a macro definition's body
|
|
45
|
+
* (`isDefinition`): a heading in a comment environment or in a definition is not a section.
|
|
46
|
+
*/
|
|
47
|
+
export function outlineOf(t: ParsedTex): Outline {
|
|
48
|
+
const doc = t.root.content.find(isDocument);
|
|
49
|
+
const nodes = visited(
|
|
50
|
+
doc?.content ?? t.root.content,
|
|
51
|
+
(v, parent) => isUnrendered(v, parent) || isDefinition(v),
|
|
52
|
+
).filter(isNode);
|
|
53
|
+
const backMatter =
|
|
54
|
+
nodes
|
|
55
|
+
.filter(isBackMatter)
|
|
56
|
+
.map((n) => n.position?.start.offset)
|
|
57
|
+
.filter(isDefined)[0] ?? null;
|
|
58
|
+
const docEnd = doc?.position?.end.offset;
|
|
59
|
+
return {
|
|
60
|
+
sections: nodes.filter(isSection).map(headingOf),
|
|
61
|
+
backMatter,
|
|
62
|
+
end: docEnd === undefined ? null : docEnd - 1,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A LaTeX source → its unified-latex tree, as one `ParsedTex` that keeps the two together.
|
|
3
|
+
*
|
|
4
|
+
* ── CITATION KEYS ARE ARGUMENTS, NOT TEXT ────────────────────────────────────────
|
|
5
|
+
* unified-latex attaches arguments only to macros it has a signature for. natbib's and biblatex's
|
|
6
|
+
* citation commands have none, so `\citep{aisec2025}` came back with its key as a separate group —
|
|
7
|
+
* readable as prose. `KEY_SIGNATURES` declares them, and the other macros whose arguments name a
|
|
8
|
+
* file, a label or a URL rather than say something, so their arguments are attached and the
|
|
9
|
+
* rendered text skips them structurally. `\href` says something in one argument and names a URL in
|
|
10
|
+
* the other, so it is signed too, and `KEY_ARGUMENT` says which of its arguments is not text.
|
|
11
|
+
*/
|
|
12
|
+
import type * as Ast from "@unified-latex/unified-latex-types";
|
|
13
|
+
import { getParser } from "@unified-latex/unified-latex-util-parse";
|
|
14
|
+
import { once } from "remeda";
|
|
15
|
+
|
|
16
|
+
const signed = (
|
|
17
|
+
names: readonly string[],
|
|
18
|
+
signature: string,
|
|
19
|
+
): readonly (readonly [string, { readonly signature: string }])[] =>
|
|
20
|
+
names.map((m) => [m, { signature }] as const);
|
|
21
|
+
|
|
22
|
+
/** Citation commands: their `{…}` argument is a key, their `[…]` ones are notes the reader sees. */
|
|
23
|
+
export const CITATION_MACROS: ReadonlySet<string> = new Set([
|
|
24
|
+
"cite",
|
|
25
|
+
"citep",
|
|
26
|
+
"citet",
|
|
27
|
+
"citealp",
|
|
28
|
+
"citealt",
|
|
29
|
+
"citeauthor",
|
|
30
|
+
"citeyear",
|
|
31
|
+
"citeyearpar",
|
|
32
|
+
"Citep",
|
|
33
|
+
"Citet",
|
|
34
|
+
"nocite",
|
|
35
|
+
"parencite",
|
|
36
|
+
"textcite",
|
|
37
|
+
"autocite",
|
|
38
|
+
"footcite",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/** Macros whose arguments are keys, labels, files or URLs — never text a reader sees. */
|
|
42
|
+
export const KEY_SIGNATURES: Readonly<
|
|
43
|
+
Record<string, { readonly signature: string }>
|
|
44
|
+
> = Object.fromEntries([
|
|
45
|
+
...signed([...CITATION_MACROS], "o o m"),
|
|
46
|
+
...signed(
|
|
47
|
+
[
|
|
48
|
+
"label",
|
|
49
|
+
"ref",
|
|
50
|
+
"eqref",
|
|
51
|
+
"pageref",
|
|
52
|
+
"autoref",
|
|
53
|
+
"cref",
|
|
54
|
+
"Cref",
|
|
55
|
+
"url",
|
|
56
|
+
"input",
|
|
57
|
+
"include",
|
|
58
|
+
"bibliography",
|
|
59
|
+
"bibliographystyle",
|
|
60
|
+
"addbibresource",
|
|
61
|
+
],
|
|
62
|
+
"m",
|
|
63
|
+
),
|
|
64
|
+
...signed(["includegraphics"], "s o o m"),
|
|
65
|
+
...signed(["usepackage", "RequirePackage", "documentclass"], "o m o"),
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Macros that print some arguments and not others: the index of the one argument that is a key or
|
|
70
|
+
* a URL. A citation (`o o m`) prints its `[…]` notes, not its `{…}` key; `\href` (`m m`) prints its
|
|
71
|
+
* link text, not its URL.
|
|
72
|
+
*/
|
|
73
|
+
export const KEY_ARGUMENT: ReadonlyMap<string, number> = new Map([
|
|
74
|
+
...[...CITATION_MACROS].map((m) => [m, 2] as const),
|
|
75
|
+
["href", 0],
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const LINK_SIGNATURES = Object.fromEntries(signed(["href"], "m m"));
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Declarations unified-latex has no signature for: signed so their arguments (sizes, not text)
|
|
82
|
+
* attach to them instead of standing beside them as groups a reader would seem to see.
|
|
83
|
+
*/
|
|
84
|
+
const DECLARATION_SIGNATURES = Object.fromEntries(signed(["fontsize"], "m m"));
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Macros that define other macros or environments. A heading in a body is no section where it is
|
|
88
|
+
* written, so the outline skips these; the rendered text reads a body as written, since nothing here
|
|
89
|
+
* expands macros. `\def` and its kin get a signature so the name and the body attach to them; a
|
|
90
|
+
* parameter text (`\def\x#1{…}`) is not modelled.
|
|
91
|
+
*/
|
|
92
|
+
export const DEFINITION_MACROS: ReadonlySet<string> = new Set([
|
|
93
|
+
"newcommand",
|
|
94
|
+
"renewcommand",
|
|
95
|
+
"providecommand",
|
|
96
|
+
"DeclareRobustCommand",
|
|
97
|
+
"newenvironment",
|
|
98
|
+
"renewenvironment",
|
|
99
|
+
"def",
|
|
100
|
+
"gdef",
|
|
101
|
+
"edef",
|
|
102
|
+
"xdef",
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const DEF_SIGNATURES = Object.fromEntries(
|
|
106
|
+
signed(["def", "gdef", "edef", "xdef"], "m m"),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Not exported: a `ParsedTex` carries this property, and the one way to write it is `parseLatex`.
|
|
110
|
+
const PARSED = Symbol("ParsedTex");
|
|
111
|
+
|
|
112
|
+
/** A source and ITS tree: only `parseLatex` makes one, so a tree cannot be paired with another source. */
|
|
113
|
+
export interface ParsedTex {
|
|
114
|
+
readonly src: string;
|
|
115
|
+
readonly root: Ast.Root;
|
|
116
|
+
readonly [PARSED]: true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** One parser for the process, made on first use: building one compiles its grammar. */
|
|
120
|
+
const parser = once(() =>
|
|
121
|
+
getParser({
|
|
122
|
+
macros: {
|
|
123
|
+
...KEY_SIGNATURES,
|
|
124
|
+
...LINK_SIGNATURES,
|
|
125
|
+
...DECLARATION_SIGNATURES,
|
|
126
|
+
...DEF_SIGNATURES,
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
/** A LaTeX source → its tree. */
|
|
132
|
+
export const parseLatex = (src: string): ParsedTex => ({
|
|
133
|
+
src,
|
|
134
|
+
root: parser().parse(src),
|
|
135
|
+
[PARSED]: true,
|
|
136
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** The text a reader sees, each character with the source offset it came from. */
|
|
2
|
+
import { hasAtLeast, sortBy } from "remeda";
|
|
3
|
+
import type { Segment, TextRun } from "../../domain/tex-document.ts";
|
|
4
|
+
import { isUnrendered } from "./hidden.ts";
|
|
5
|
+
import {
|
|
6
|
+
isList,
|
|
7
|
+
stringText,
|
|
8
|
+
visited,
|
|
9
|
+
type Argument,
|
|
10
|
+
type Node,
|
|
11
|
+
} from "./nodes.ts";
|
|
12
|
+
import type { ParsedTex } from "./parse.ts";
|
|
13
|
+
|
|
14
|
+
/** A node's rendered text and where it starts, or null when it is not text (it ends a run). */
|
|
15
|
+
function segmentOf(n: Node | Argument): Segment | null {
|
|
16
|
+
const at = n.position?.start.offset;
|
|
17
|
+
if (at === undefined) return null;
|
|
18
|
+
if (n.type === "whitespace" || n.type === "parbreak")
|
|
19
|
+
return { text: " ", at };
|
|
20
|
+
if (n.type !== "string") return null;
|
|
21
|
+
return { text: stringText(n.content), at };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** One node list → its runs: stretches of sibling text, split wherever anything else stands. */
|
|
25
|
+
function runsOf(list: readonly (Node | Argument)[]): readonly TextRun[] {
|
|
26
|
+
const { done, current } = list.reduce<{
|
|
27
|
+
readonly done: readonly (readonly Segment[])[];
|
|
28
|
+
readonly current: readonly Segment[];
|
|
29
|
+
}>(
|
|
30
|
+
(acc, n) => {
|
|
31
|
+
const seg = segmentOf(n);
|
|
32
|
+
return seg === null
|
|
33
|
+
? { done: [...acc.done, acc.current], current: [] }
|
|
34
|
+
: { done: acc.done, current: [...acc.current, seg] };
|
|
35
|
+
},
|
|
36
|
+
{ done: [], current: [] },
|
|
37
|
+
);
|
|
38
|
+
return [...done, current].flatMap((g) =>
|
|
39
|
+
hasAtLeast(g, 1) &&
|
|
40
|
+
g
|
|
41
|
+
.map((x) => x.text)
|
|
42
|
+
.join("")
|
|
43
|
+
.trim() !== ""
|
|
44
|
+
? [{ segments: g }]
|
|
45
|
+
: [],
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Runs of rendered text, in document order: stretches of sibling strings and spaces. Everything
|
|
51
|
+
* else ends a run and is entered — a group's text, a macro's arguments — except what a reader never
|
|
52
|
+
* sees (`isUnrendered`): comments, math, code, the bibliography, keys and URLs. A macro definition's
|
|
53
|
+
* body is read where it is written.
|
|
54
|
+
*/
|
|
55
|
+
export const renderedRuns = (t: ParsedTex): readonly TextRun[] =>
|
|
56
|
+
sortBy(
|
|
57
|
+
visited(t.root, isUnrendered).filter(isList).flatMap(runsOf),
|
|
58
|
+
(r) => r.segments[0].at,
|
|
59
|
+
);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* In-memory adapters for the app layer's tests: every port, no disk, no processes, no network.
|
|
3
3
|
* Each one RECORDS what it was asked, so a test asserts on the calls instead of on side effects.
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
5
|
+
import { absolutePath } from "../../domain/paths.ts";
|
|
6
6
|
import type { Download } from "../../ports/download.ts";
|
|
7
7
|
import type { Files } from "../../ports/files.ts";
|
|
8
8
|
import type { Command, ProcessExit, RunProcess } from "../../ports/process.ts";
|
|
@@ -69,11 +69,12 @@ export interface MemoryWorkspace extends Workspace {
|
|
|
69
69
|
export function memoryWorkspace(dir = "/scratch"): MemoryWorkspace {
|
|
70
70
|
const written: MemoryWorkspace["written"] = [];
|
|
71
71
|
const ended: ("returned" | "threw")[] = [];
|
|
72
|
+
const at = absolutePath(dir);
|
|
72
73
|
const scratch: Scratch = {
|
|
73
|
-
dir:
|
|
74
|
+
dir: at,
|
|
74
75
|
write(name, content, mode) {
|
|
75
76
|
written.push({ name, content, mode });
|
|
76
|
-
return `${dir}/${name}`
|
|
77
|
+
return absolutePath(`${dir}/${name}`);
|
|
77
78
|
},
|
|
78
79
|
};
|
|
79
80
|
return {
|
|
@@ -4,14 +4,16 @@ import { existsSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { afterAll as after, test } from "vitest";
|
|
7
|
-
import
|
|
7
|
+
import { absolutePath, joinPath } from "../../domain/paths.ts";
|
|
8
8
|
import { errorCode, nodeFiles } from "./files.io.ts";
|
|
9
9
|
|
|
10
|
-
const root =
|
|
10
|
+
const root = absolutePath(
|
|
11
|
+
realpathSync(mkdtempSync(join(tmpdir(), "paperlint-files-test-"))),
|
|
12
|
+
);
|
|
11
13
|
after(() => {
|
|
12
14
|
rmSync(root, { recursive: true, force: true });
|
|
13
15
|
});
|
|
14
|
-
const at = (...p: string[]) =>
|
|
16
|
+
const at = (...p: string[]) => joinPath(root, ...p);
|
|
15
17
|
|
|
16
18
|
test("Files: a missing file reads as null and is not a file", () => {
|
|
17
19
|
assert.equal(nodeFiles.readBytes(at("nope")), null);
|
|
@@ -11,8 +11,15 @@ import {
|
|
|
11
11
|
} from "node:child_process";
|
|
12
12
|
import type { Command, ProcessExit, RunProcess } from "../../ports/process.ts";
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Node's `spawnSync` — the one overload this file calls, text in and text out — or a stand-in with its
|
|
16
|
+
* shape (a harness records the calls through one).
|
|
17
|
+
*/
|
|
18
|
+
export type SpawnSync = (
|
|
19
|
+
file: string,
|
|
20
|
+
args: readonly string[],
|
|
21
|
+
options: SpawnSyncOptionsWithStringEncoding,
|
|
22
|
+
) => SpawnSyncReturns<string>;
|
|
16
23
|
|
|
17
24
|
const errno = (r: SpawnSyncReturns<string>): string | undefined =>
|
|
18
25
|
codeOf(r.error);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import assert from "node:assert/strict";
|
|
7
7
|
import { test } from "vitest";
|
|
8
|
-
import
|
|
8
|
+
import { absolutePath } from "../../domain/paths.ts";
|
|
9
9
|
import type { Command } from "../../ports/process.ts";
|
|
10
10
|
import { spawnProcess } from "./process.io.ts";
|
|
11
11
|
|
|
@@ -62,19 +62,19 @@ test("output past maxOutputBytes is a spawn failure, named by Node's own message
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test("cwd is the Command's when it names one", () => {
|
|
65
|
-
const r = run.run({ ...sh("pwd"), cwd: "/"
|
|
65
|
+
const r = run.run({ ...sh("pwd"), cwd: absolutePath("/") });
|
|
66
66
|
assert.deepEqual(r.kind === "exited" && r.stdout, "/\n");
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
test("a spawn that reports no status, no signal and no error reads as exit 1, not success", () => {
|
|
70
|
-
const silent = spawnProcess((
|
|
70
|
+
const silent = spawnProcess(() => ({
|
|
71
71
|
pid: 0,
|
|
72
72
|
output: [],
|
|
73
73
|
stdout: "",
|
|
74
74
|
stderr: "",
|
|
75
75
|
status: null,
|
|
76
76
|
signal: null,
|
|
77
|
-
}))
|
|
77
|
+
}));
|
|
78
78
|
assert.deepEqual(silent.run(sh("true")), {
|
|
79
79
|
kind: "exited",
|
|
80
80
|
status: 1,
|
|
@@ -10,14 +10,18 @@ import {
|
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
absolutePath,
|
|
15
|
+
joinPath,
|
|
16
|
+
type AbsolutePath,
|
|
17
|
+
} from "../../domain/paths.ts";
|
|
14
18
|
import type { NotAPromise, Scratch, Workspace } from "../../ports/workspace.ts";
|
|
15
19
|
|
|
16
20
|
function scratchIn(dir: AbsolutePath): Scratch {
|
|
17
21
|
return {
|
|
18
22
|
dir,
|
|
19
23
|
write(name, content, mode): AbsolutePath {
|
|
20
|
-
const at =
|
|
24
|
+
const at = joinPath(dir, name);
|
|
21
25
|
writeFileSync(at, content);
|
|
22
26
|
if (mode === "exec") chmodSync(at, 0o755);
|
|
23
27
|
return at;
|
|
@@ -29,9 +33,10 @@ function scratchIn(dir: AbsolutePath): Scratch {
|
|
|
29
33
|
export function tmpWorkspace(tmpDir: string): Workspace {
|
|
30
34
|
return {
|
|
31
35
|
within<T>(prefix: string, use: (s: Scratch) => NotAPromise<T>): T {
|
|
32
|
-
|
|
36
|
+
// `realpath` answers with an absolute path, whatever `tmpDir` was.
|
|
37
|
+
const dir = absolutePath(realpathSync(mkdtempSync(join(tmpDir, prefix))));
|
|
33
38
|
try {
|
|
34
|
-
return use(scratchIn(dir
|
|
39
|
+
return use(scratchIn(dir));
|
|
35
40
|
} finally {
|
|
36
41
|
rmSync(dir, { recursive: true, force: true });
|
|
37
42
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fill.ts` — what is recovered from pdf.js's exports for each text item: whether it is upright, and
|
|
3
3
|
* the fill it was drawn in (the operator-list walk). Operator lists here are written by hand; the
|
|
4
|
-
* real-PDF half runs in `pdf-facts.harness.ts` and `test/e2e/banal.ts`.
|
|
4
|
+
* real-PDF half runs in `pdf-facts.harness.ts` and `test/e2e/tex/banal.e2e.ts`.
|
|
5
5
|
*/
|
|
6
6
|
import assert from "node:assert/strict";
|
|
7
7
|
import { test } from "vitest";
|
|
@@ -154,14 +154,22 @@ export function fillsFor<T>(
|
|
|
154
154
|
textOf: (item: T) => string,
|
|
155
155
|
): { readonly item: T; readonly fill: Fill }[] {
|
|
156
156
|
const drawn = drawnCharacters(ops, list);
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
157
|
+
// Each item is searched for after the previous one's match: the offset threads through.
|
|
158
|
+
const { out } = items.reduce<{
|
|
159
|
+
readonly at: number;
|
|
160
|
+
readonly out: readonly { readonly item: T; readonly fill: Fill }[];
|
|
161
|
+
}>(
|
|
162
|
+
(acc, item) => {
|
|
163
|
+
const want = Array.from(squeeze(textOf(item)));
|
|
164
|
+
const hit = want.length === 0 ? null : findRun(drawn, want, acc.at);
|
|
165
|
+
return hit === null
|
|
166
|
+
? { at: acc.at, out: [...acc.out, { item, fill: { kind: "unknown" } }] }
|
|
167
|
+
: {
|
|
168
|
+
at: hit.at + want.length,
|
|
169
|
+
out: [...acc.out, { item, fill: hit.fill }],
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
{ at: 0, out: [] },
|
|
173
|
+
);
|
|
174
|
+
return [...out];
|
|
167
175
|
}
|
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
BibEntry,
|
|
45
45
|
} from "../../../skills/verify-citations/scripts/bib-authors.mjs";
|
|
46
46
|
import { messageOf } from "../../domain/text.ts";
|
|
47
|
+
import { isRecord } from "../../domain/record.ts";
|
|
47
48
|
|
|
48
49
|
const describeAuthors = (f: AuthorFinding): string =>
|
|
49
50
|
[
|
|
@@ -188,12 +189,16 @@ function grown(
|
|
|
188
189
|
): LookupCache {
|
|
189
190
|
const { cache, usable } = loaded;
|
|
190
191
|
const { store, dblp } = run;
|
|
191
|
-
|
|
192
|
+
// verify-cites stores each answer as an object; only such a value is a response the cache can hold.
|
|
193
|
+
const fresh = Object.entries(store).filter(
|
|
194
|
+
(e): e is [string, Readonly<Record<string, unknown>>] =>
|
|
195
|
+
!usable.citations.has(e[0]) && isRecord(e[1]),
|
|
196
|
+
);
|
|
192
197
|
const freshDblp = [...dblp].filter(([k]) => !usable.dblp.has(k));
|
|
193
198
|
if (fresh.length === 0 && freshDblp.length === 0) return cache;
|
|
194
199
|
const dated = fresh.map(([k, response]): [string, CachedResponse] => [
|
|
195
200
|
k,
|
|
196
|
-
{ fetched: today(), response
|
|
201
|
+
{ fetched: today(), response },
|
|
197
202
|
]);
|
|
198
203
|
return {
|
|
199
204
|
citations: new Map([...cache.citations, ...dated]),
|
package/src/build-engine.test.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { dirname, join } from "node:path";
|
|
|
9
9
|
import { test } from "vitest";
|
|
10
10
|
import { useTempDir } from "../test/support.ts";
|
|
11
11
|
import { prepareEngine, refusal } from "./build-engine.ts";
|
|
12
|
+
import type { Runner } from "./engine.ts";
|
|
12
13
|
|
|
13
14
|
const work = useTempDir("paperlint-build-engine-test-");
|
|
14
15
|
const TEX = { packages: { acmart: ["acmart.cls"] }, tools: {} };
|
|
@@ -20,11 +21,12 @@ const texAt = (dir: string): string => {
|
|
|
20
21
|
return dir;
|
|
21
22
|
};
|
|
22
23
|
/** kpsewhich answers per bin directory. */
|
|
23
|
-
const fakeRun =
|
|
24
|
-
(
|
|
24
|
+
const fakeRun =
|
|
25
|
+
(found: Record<string, string>): Runner =>
|
|
26
|
+
(cmd) => ({
|
|
25
27
|
stdout: found[dirname(cmd)] ?? "",
|
|
26
28
|
status: 0,
|
|
27
|
-
})
|
|
29
|
+
});
|
|
28
30
|
|
|
29
31
|
async function go(o: Parameters<typeof prepareEngine>[0]) {
|
|
30
32
|
const out: string[] = [];
|
|
@@ -101,7 +103,10 @@ test("a complete cache is used from an environment with no PATH at all", async (
|
|
|
101
103
|
|
|
102
104
|
test("the refusal line for a decision to use a tree names nothing missing", () => {
|
|
103
105
|
assert.equal(
|
|
104
|
-
refusal(
|
|
106
|
+
refusal(
|
|
107
|
+
{ kind: "use-cache", tree: { label: "cache", bin: "/b", missing: [] } },
|
|
108
|
+
{ system: null },
|
|
109
|
+
),
|
|
105
110
|
"✗ paperlint build: no TeX Live with every package these papers need — run `npx paperlint toolchain` (missing: 0 package(s): )",
|
|
106
111
|
);
|
|
107
112
|
});
|
|
@@ -122,14 +127,14 @@ test("without an ask or an install given: Enter is yes, and the default install
|
|
|
122
127
|
PAPERLINT_CTAN_MIRROR: "https://mirror.invalid/tlnet",
|
|
123
128
|
PATH: join(work, "nothing"),
|
|
124
129
|
},
|
|
125
|
-
run: (
|
|
130
|
+
run: (cmd) => {
|
|
126
131
|
calls.push(cmd.split("/").pop() ?? cmd);
|
|
127
132
|
return {
|
|
128
133
|
status: 22,
|
|
129
134
|
stdout: "",
|
|
130
135
|
stderr: "curl: (6) could not resolve\n",
|
|
131
136
|
};
|
|
132
|
-
}
|
|
137
|
+
},
|
|
133
138
|
});
|
|
134
139
|
assert.deepEqual(r, { ok: false, code: 1 });
|
|
135
140
|
assert.ok(calls.includes("curl"), calls.join(","));
|