paperlint 3.2.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +16 -23
- package/CLAUDE.md +33 -13
- package/CONTRIBUTING.md +23 -7
- package/README.md +11 -8
- package/dist/adapters/banal/failure.d.ts +1 -7
- package/dist/adapters/banal/failure.d.ts.map +1 -1
- package/dist/adapters/banal/failure.js +5 -7
- package/dist/adapters/banal/failure.js.map +1 -1
- package/dist/adapters/banal/index.d.ts.map +1 -1
- package/dist/adapters/banal/index.js +8 -11
- package/dist/adapters/banal/index.js.map +1 -1
- package/dist/adapters/banal/install.d.ts.map +1 -1
- package/dist/adapters/banal/install.js +4 -3
- package/dist/adapters/banal/install.js.map +1 -1
- package/dist/adapters/banal/invocation.d.ts +10 -5
- package/dist/adapters/banal/invocation.d.ts.map +1 -1
- package/dist/adapters/banal/invocation.js +13 -1
- package/dist/adapters/banal/invocation.js.map +1 -1
- package/dist/adapters/banal/locate.d.ts +12 -9
- package/dist/adapters/banal/locate.d.ts.map +1 -1
- package/dist/adapters/banal/locate.js +19 -12
- package/dist/adapters/banal/locate.js.map +1 -1
- package/dist/adapters/banal/run.d.ts +10 -1
- package/dist/adapters/banal/run.d.ts.map +1 -1
- package/dist/adapters/banal/run.js +8 -4
- package/dist/adapters/banal/run.js.map +1 -1
- package/dist/adapters/banal/settings.d.ts +1 -1
- package/dist/adapters/banal/settings.d.ts.map +1 -1
- package/dist/adapters/banal/settings.js +2 -1
- package/dist/adapters/banal/settings.js.map +1 -1
- package/dist/adapters/latex/document-class.d.ts +22 -0
- package/dist/adapters/latex/document-class.d.ts.map +1 -0
- package/dist/adapters/latex/document-class.js +102 -0
- package/dist/adapters/latex/document-class.js.map +1 -0
- package/dist/adapters/latex/hidden.d.ts +16 -0
- package/dist/adapters/latex/hidden.d.ts.map +1 -0
- package/dist/adapters/latex/hidden.js +49 -0
- package/dist/adapters/latex/hidden.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +29 -0
- package/dist/adapters/latex/index.d.ts.map +1 -0
- package/dist/adapters/latex/index.js +18 -0
- package/dist/adapters/latex/index.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts +54 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -0
- package/dist/adapters/latex/nodes.js +93 -0
- package/dist/adapters/latex/nodes.js.map +1 -0
- package/dist/adapters/latex/outline.d.ts +10 -0
- package/dist/adapters/latex/outline.d.ts.map +1 -0
- package/dist/adapters/latex/outline.js +39 -0
- package/dist/adapters/latex/outline.js.map +1 -0
- package/dist/adapters/latex/parse.d.ts +42 -0
- package/dist/adapters/latex/parse.d.ts.map +1 -0
- package/dist/adapters/latex/parse.js +94 -0
- package/dist/adapters/latex/parse.js.map +1 -0
- package/dist/adapters/latex/rendered.d.ts +10 -0
- package/dist/adapters/latex/rendered.d.ts.map +1 -0
- package/dist/adapters/latex/rendered.js +39 -0
- package/dist/adapters/latex/rendered.js.map +1 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -2
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/process.io.d.ts +6 -3
- package/dist/adapters/node/process.io.d.ts.map +1 -1
- package/dist/adapters/node/process.io.js.map +1 -1
- package/dist/adapters/node/workspace.io.d.ts.map +1 -1
- package/dist/adapters/node/workspace.io.js +4 -2
- package/dist/adapters/node/workspace.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +12 -12
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +4 -2
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/build-engine.d.ts +1 -1
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +8 -1
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +2 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +9 -10
- package/dist/build.js.map +1 -1
- package/dist/caller-path.d.ts +3 -0
- package/dist/caller-path.d.ts.map +1 -0
- package/dist/caller-path.js +13 -0
- package/dist/caller-path.js.map +1 -0
- package/dist/cli.d.ts +10 -17
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +91 -17
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +12 -3
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +2 -1
- package/dist/doctor.js.map +1 -1
- package/dist/domain/paths.d.ts +8 -2
- package/dist/domain/paths.d.ts.map +1 -1
- package/dist/domain/paths.js +12 -1
- package/dist/domain/paths.js.map +1 -1
- package/dist/domain/ready.d.ts +24 -0
- package/dist/domain/ready.d.ts.map +1 -0
- package/dist/domain/ready.js +11 -0
- package/dist/domain/ready.js.map +1 -0
- package/dist/domain/record.d.ts +8 -0
- package/dist/domain/record.d.ts.map +1 -0
- package/dist/domain/record.js +8 -0
- package/dist/domain/record.js.map +1 -0
- package/dist/domain/sha256.d.ts.map +1 -1
- package/dist/domain/sha256.js +4 -2
- package/dist/domain/sha256.js.map +1 -1
- package/dist/domain/tex-document.d.ts +84 -0
- package/dist/domain/tex-document.d.ts.map +1 -0
- package/dist/domain/tex-document.js +43 -0
- package/dist/domain/tex-document.js.map +1 -0
- package/dist/engine.d.ts +23 -22
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js.map +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +1 -1
- package/dist/eslint-rules/bib-reachable-entry.js.map +1 -1
- package/dist/eslint-rules/install-path-literals.d.ts +2 -2
- package/dist/eslint-rules/install-path-literals.js.map +1 -1
- package/dist/eslint-rules/paper-research-question.d.ts +1 -1
- package/dist/eslint-rules/paper-research-question.js.map +1 -1
- package/dist/eslint-rules/paper-stages.d.ts +2 -2
- package/dist/eslint-rules/paper-stages.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +6 -6
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +1 -1
- package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -1
- package/dist/eslint-rules/review-frontmatter.d.ts +2 -2
- package/dist/eslint-rules/review-frontmatter.js.map +1 -1
- package/dist/eslint-rules/sibling-frontmatter.d.ts +1 -1
- package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -1
- package/dist/eslint-rules/temp-root-realpath.d.ts +1 -1
- package/dist/eslint-rules/temp-root-realpath.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts +2 -2
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +31 -28
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +17 -2
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +3 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +18 -8
- package/dist/init.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +1 -5
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/new-paper.d.ts +5 -0
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +8 -1
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts +5 -0
- package/dist/package-dirs.d.ts.map +1 -0
- package/dist/package-dirs.js +14 -0
- package/dist/package-dirs.js.map +1 -0
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +6 -6
- package/dist/paper-files.js.map +1 -1
- package/dist/paper-settings.js +3 -3
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-facts.d.ts +51 -1
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +23 -15
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +2 -3
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/ports/latex.d.ts +24 -0
- package/dist/ports/latex.d.ts.map +1 -0
- package/dist/ports/latex.js +2 -0
- package/dist/ports/latex.js.map +1 -0
- package/dist/ports/tool-installer.d.ts +2 -14
- package/dist/ports/tool-installer.d.ts.map +1 -1
- package/dist/presets.d.ts +15 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +35 -10
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +4 -4
- package/dist/references.js.map +1 -1
- package/dist/rules-config.d.ts +26 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +9 -9
- package/dist/rules-config.js.map +1 -1
- package/dist/tex-requirements.d.ts +15 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +28 -10
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +83 -0
- package/dist/tex-venue-rules.d.ts.map +1 -0
- package/dist/tex-venue-rules.js +254 -0
- package/dist/tex-venue-rules.js.map +1 -0
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +20 -7
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +5 -4
- package/docs/e2e.md +69 -40
- package/docs/install.md +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/package-location.md +5 -5
- package/docs/prior-art/repro/README.md +1 -1
- package/docs/prior-art/test-tooling.md +21 -10
- package/docs/rules/tex/required-section.md +73 -0
- package/docs/rules/tex/template.md +62 -0
- package/docs/rules/tex/venue-leftover.md +71 -0
- package/docs/rules.md +70 -48
- package/docs/testing.md +17 -4
- package/docs/texlive-install-decision.md +1 -1
- package/docs/toolchain.md +2 -2
- package/eslint-rules/bib-reachable-entry.ts +1 -1
- package/eslint-rules/install-path-literals.ts +2 -2
- package/eslint-rules/paper-research-question.ts +1 -1
- package/eslint-rules/paper-stages.ts +2 -2
- package/eslint-rules/paper-typography.ts +2 -2
- package/eslint-rules/pdf-last-page-balance.ts +1 -1
- package/eslint-rules/review-frontmatter.ts +1 -1
- package/eslint-rules/temp-root-realpath.ts +1 -1
- package/eslint-rules/tex-build.ts +2 -2
- package/eslint.config.mjs +162 -15
- package/fixtures/build-e2e/acmart/expect.json +26 -0
- package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/aidc/expect.json +14 -0
- package/fixtures/build-e2e/aidc/paper.tex +46 -0
- package/fixtures/build-e2e/aidc/paperlint.json +1 -0
- package/fixtures/build-e2e/broken/expect.json +11 -0
- package/fixtures/build-e2e/cite/expect.json +27 -0
- package/fixtures/build-e2e/empty/expect.json +12 -0
- package/fixtures/build-e2e/fallback/expect.json +53 -0
- package/fixtures/build-e2e/guards/expect.json +6 -0
- package/fixtures/build-e2e/no-source/expect.json +10 -0
- package/fixtures/build-e2e/unbalanced/expect.json +60 -0
- package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
- package/fixtures/install-path-literals/clean.md +2 -2
- package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
- package/fixtures/install-path-literals/defect.md +2 -2
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.ts +1 -1
- package/fixtures/render-paper/build-clean.sh +3 -5
- package/hooks/paper-status-gates.sh +9 -19
- package/lib/paper-config.harness.mjs +1 -1
- package/lib/paper-config.ts +1 -5
- package/lib/skill-checks.mjs +24 -381
- package/lib/skill-checks.test.mjs +63 -115
- package/lib/skill-corpus.mjs +10 -251
- package/lib/skill-corpus.test.mjs +2 -13
- package/lib/skill-trigger-cases.mjs +1 -1
- package/package.json +13 -2
- package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
- package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
- package/presets/aidc.jsonc +73 -0
- package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
- package/presets/ieee-conference.jsonc +66 -0
- package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
- package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
- package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
- package/scripts/check.harness.ts +64 -10
- package/scripts/check.test.ts +60 -13
- package/scripts/check.ts +83 -30
- package/scripts/coverage-config.test.ts +0 -1
- package/scripts/harness-api.frozen.json +0 -1
- package/scripts/test.test.ts +1 -1
- package/scripts/test.ts +1 -1
- package/skills/analyze-sibling-paper/SKILL.md +1 -39
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
- package/skills/argument-arc/SKILL.md +2 -36
- package/skills/argument-arc/SKILL.md.spec.ts +1 -43
- package/skills/build-benchmark/SKILL.md +1 -41
- package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
- package/skills/camera-ready/SKILL.md +1 -34
- package/skills/camera-ready/SKILL.md.spec.ts +0 -33
- package/skills/cold-read-diff/SKILL.md +1 -35
- package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
- package/skills/draft-paper/SKILL.md +2 -41
- package/skills/draft-paper/SKILL.md.spec.ts +1 -50
- package/skills/extend-paper/SKILL.md +2 -33
- package/skills/extend-paper/SKILL.md.spec.ts +1 -42
- package/skills/find-venue/SKILL.md +2 -38
- package/skills/find-venue/SKILL.md.spec.ts +1 -47
- package/skills/grade-paper-writing/SKILL.md +3 -38
- package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
- package/skills/grade-paper-writing/prose-lint.mjs +2 -3
- package/skills/grade-paper-writing/prose-lint.test.ts +1 -1
- package/skills/harden-paper/SKILL.md +1 -39
- package/skills/harden-paper/SKILL.md.spec.ts +0 -38
- package/skills/map-prior-work/SKILL.md +1 -35
- package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
- package/skills/osf-artifact-upload/osf-artifact-upload.harness.ts +3 -4
- package/skills/paper-adversarial-review/SKILL.md +2 -35
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
- package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
- package/skills/paper-pipeline/SKILL.md +3 -29
- package/skills/paper-pipeline/SKILL.md.spec.ts +2 -28
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +1 -1
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
- package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
- package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
- package/skills/paper-pipeline/scripts/README.md +30 -225
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
- package/skills/paper-pipeline/scripts/consumer.mjs +24 -119
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -42
- package/skills/paper-status/SKILL.md.spec.ts +1 -41
- package/skills/pc-panel-review/SKILL.md +2 -46
- package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
- package/skills/plan-paper-timeline/SKILL.md +2 -36
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
- package/skills/render-paper/SKILL.md +2 -38
- package/skills/render-paper/SKILL.md.spec.ts +1 -37
- package/skills/render-paper/check-render.sh +16 -63
- package/skills/render-paper/render-paper.harness.mjs +17 -59
- package/skills/research-ideate/SKILL.md +2 -39
- package/skills/research-ideate/SKILL.md.spec.ts +1 -47
- package/skills/study-accepted-papers/SKILL.md +1 -34
- package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
- package/skills/submit-paper/SKILL.md +1 -33
- package/skills/submit-paper/SKILL.md.spec.ts +0 -32
- package/skills/sweep-design-space/SKILL.md +1 -38
- package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
- package/skills/tighten-paper/SKILL.md +2 -39
- package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
- package/skills/tighten-paper/structure.mjs +4 -11
- package/skills/verify-citations/SKILL.md +2 -38
- package/skills/verify-citations/SKILL.md.spec.ts +1 -37
- package/src/adapters/banal/failure.ts +9 -10
- package/src/adapters/banal/index.test.ts +3 -3
- package/src/adapters/banal/index.ts +8 -10
- package/src/adapters/banal/install.ts +9 -4
- package/src/adapters/banal/invocation.test.ts +20 -8
- package/src/adapters/banal/invocation.ts +24 -9
- package/src/adapters/banal/locate.test.ts +2 -2
- package/src/adapters/banal/locate.ts +27 -21
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +3 -3
- package/src/adapters/banal/run.ts +16 -10
- package/src/adapters/banal/settings.ts +2 -2
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/curl/download.test.ts +4 -4
- package/src/adapters/latex/document-class.ts +147 -0
- package/src/adapters/latex/hidden.ts +70 -0
- package/src/adapters/latex/index.test.ts +361 -0
- package/src/adapters/latex/index.ts +55 -0
- package/src/adapters/latex/nodes.ts +141 -0
- package/src/adapters/latex/outline.ts +64 -0
- package/src/adapters/latex/parse.ts +136 -0
- package/src/adapters/latex/rendered.ts +59 -0
- package/src/adapters/memory/index.ts +4 -3
- package/src/adapters/node/files.test.ts +5 -3
- package/src/adapters/node/process.io.ts +9 -2
- package/src/adapters/node/process.test.ts +4 -4
- package/src/adapters/node/workspace.io.ts +9 -4
- package/src/adapters/pdfjs/fill.test.ts +1 -1
- package/src/adapters/pdfjs/fill.ts +18 -10
- package/src/adapters/references/index.ts +7 -2
- package/src/build-engine.test.ts +11 -6
- package/src/build-engine.ts +10 -5
- package/src/build.harness.mjs +5 -7
- package/src/build.test.ts +10 -11
- package/src/build.ts +12 -13
- package/src/caller-path.ts +13 -0
- package/src/cli.harness.mjs +1 -3
- package/src/cli.test.ts +21 -16
- package/src/cli.ts +115 -33
- package/src/doctor.test.ts +1 -1
- package/src/doctor.ts +14 -5
- package/src/domain/paths.test.ts +15 -0
- package/src/domain/paths.ts +20 -1
- package/src/domain/ready.ts +31 -0
- package/src/domain/record.test.ts +20 -0
- package/src/domain/record.ts +12 -0
- package/src/domain/sha256.ts +5 -3
- package/src/domain/tex-document.test.ts +80 -0
- package/src/domain/tex-document.ts +110 -0
- package/src/engine.harness.mjs +1 -1
- package/src/engine.ts +30 -2
- package/src/facts-file.test.ts +4 -7
- package/src/facts-file.ts +45 -30
- package/src/hooks-settings.harness.ts +17 -0
- package/src/hooks-settings.ts +28 -4
- package/src/init.test.ts +25 -5
- package/src/init.ts +26 -12
- package/src/latex-loop.harness.ts +1 -1
- package/src/new-paper.harness.mjs +7 -23
- package/src/new-paper.test.ts +28 -3
- package/src/new-paper.ts +15 -3
- package/src/package-dirs.ts +17 -0
- package/src/paper-files.test.ts +22 -19
- package/src/paper-files.ts +16 -27
- package/src/paper-settings-commands.test.ts +6 -1
- package/src/paper-settings.test.ts +2 -2
- package/src/paper-settings.ts +5 -5
- package/src/pdf-facts.test.ts +16 -5
- package/src/pdf-facts.ts +79 -22
- package/src/pdf-geometry.ts +5 -3
- package/src/ports/latex.ts +29 -0
- package/src/ports/tool-installer.ts +2 -17
- package/src/presets.test.ts +65 -4
- package/src/presets.ts +65 -28
- package/src/references.test.ts +25 -19
- package/src/references.ts +4 -5
- package/src/rule-cases.test.ts +474 -0
- package/src/rule-docs.test.ts +159 -0
- package/src/rules-config.ts +40 -11
- package/src/tex-requirements.harness.ts +13 -23
- package/src/tex-requirements.test.ts +21 -0
- package/src/tex-requirements.ts +64 -14
- package/src/tex-venue-rules.test.ts +659 -0
- package/src/tex-venue-rules.ts +429 -0
- package/src/toolchain.harness.mjs +1 -1
- package/src/toolchain.test.ts +9 -5
- package/src/toolchain.ts +2 -2
- package/src/types.ts +5 -3
- package/src/venue-rules.test.ts +104 -8
- package/src/venue-rules.ts +22 -12
- package/templates/paper/paper.tex +2 -1
- package/skills/paper-pipeline/scripts/announce.mjs +0 -67
- package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
- package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
- package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
- package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
- package/skills/paper-pipeline/scripts/fixtures/scorecard.ts +0 -100
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
- package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
- package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -750
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
- package/skills/paper-pipeline/scripts/status.mjs +0 -295
- package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
- /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
- /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
package/src/hooks-settings.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
PACKAGE_NAME,
|
|
32
32
|
} from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
33
33
|
import { messageOf } from "./domain/text.ts";
|
|
34
|
+
import { fieldOf, isRecord } from "./domain/record.ts";
|
|
34
35
|
|
|
35
36
|
/** The committed, shared settings file — husky's `.husky/` analogue, except git carries it. */
|
|
36
37
|
export const SETTINGS_PATH = join(".claude", "settings.json");
|
|
@@ -151,6 +152,25 @@ const prop = (v: unknown): Readonly<Record<string, unknown>> | undefined =>
|
|
|
151
152
|
? Object.fromEntries(Object.entries(v))
|
|
152
153
|
: undefined;
|
|
153
154
|
|
|
155
|
+
const isHookCommand = (v: unknown): v is HookCommand =>
|
|
156
|
+
typeof fieldOf(v, "type") === "string" &&
|
|
157
|
+
typeof fieldOf(v, "command") === "string";
|
|
158
|
+
|
|
159
|
+
function isHookEntry(v: unknown): v is HookEntry {
|
|
160
|
+
const matcher = fieldOf(v, "matcher");
|
|
161
|
+
const hooks = fieldOf(v, "hooks");
|
|
162
|
+
return (
|
|
163
|
+
["undefined", "string"].includes(typeof matcher) &&
|
|
164
|
+
Array.isArray(hooks) &&
|
|
165
|
+
hooks.every(isHookCommand)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** A `hooks` map of the shape Claude Code reads: event → entries → commands. */
|
|
170
|
+
const isHooksMap = (v: unknown): v is HooksMap =>
|
|
171
|
+
isRecord(v) &&
|
|
172
|
+
Object.values(v).every((es) => Array.isArray(es) && es.every(isHookEntry));
|
|
173
|
+
|
|
154
174
|
export interface Wiring {
|
|
155
175
|
readonly compiled: HooksMap;
|
|
156
176
|
/** Derived from the commands, in order. Never empty — an empty set throws. */
|
|
@@ -162,8 +182,12 @@ export interface Wiring {
|
|
|
162
182
|
* check that silently wired nothing would report "✓ wired" over an empty set.
|
|
163
183
|
*/
|
|
164
184
|
export function shippedWiring(file: string = WIRING_FILE): Wiring {
|
|
165
|
-
const json = JSON.parse(readFileSync(file, "utf8"))
|
|
166
|
-
const compiled = json
|
|
185
|
+
const json: unknown = JSON.parse(readFileSync(file, "utf8"));
|
|
186
|
+
const compiled = fieldOf(json, "hooks") ?? {};
|
|
187
|
+
if (!isHooksMap(compiled))
|
|
188
|
+
throw new Error(
|
|
189
|
+
`${file}: "hooks" is not a map of event → [{ matcher?, hooks: [{ type, command }] }]`,
|
|
190
|
+
);
|
|
167
191
|
const names = commandsIn({ hooks: compiled })
|
|
168
192
|
.map((c) => hookRun(c.command)?.name)
|
|
169
193
|
.filter((n): n is string => typeof n === "string");
|
|
@@ -225,9 +249,9 @@ export function readSettings(root: string): SettingsRead {
|
|
|
225
249
|
const raw = readFileSync(path, "utf8");
|
|
226
250
|
try {
|
|
227
251
|
const parsed: unknown = JSON.parse(raw);
|
|
228
|
-
if (!
|
|
252
|
+
if (!isRecord(parsed))
|
|
229
253
|
return { status: "unparsable", path, reason: "not a JSON object" };
|
|
230
|
-
return { status: "read", path, settings: parsed
|
|
254
|
+
return { status: "read", path, settings: parsed, raw };
|
|
231
255
|
} catch (e) {
|
|
232
256
|
return { status: "unparsable", path, reason: messageOf(e) };
|
|
233
257
|
}
|
package/src/init.test.ts
CHANGED
|
@@ -70,7 +70,7 @@ describe("paperlint init — a declared papers directory", () => {
|
|
|
70
70
|
err: (s: string) => out.push(s),
|
|
71
71
|
interactive: false,
|
|
72
72
|
hooks: false,
|
|
73
|
-
run: (
|
|
73
|
+
run: () => ({ status: 0 }),
|
|
74
74
|
link: () => ({ ok: false, error: "not linked in this test" }),
|
|
75
75
|
});
|
|
76
76
|
const text = out.join("\n");
|
|
@@ -97,7 +97,7 @@ describe("paperlint init — a declared papers directory", () => {
|
|
|
97
97
|
interactive: true,
|
|
98
98
|
ask: (q) => Promise.resolve((asked.push(q), "n")),
|
|
99
99
|
hooks: false,
|
|
100
|
-
run: (
|
|
100
|
+
run: () => ({ status: 0 }),
|
|
101
101
|
link: () => ({ ok: false, error: "not linked in this test" }),
|
|
102
102
|
});
|
|
103
103
|
expect(asked.filter((q) => /papers roots/.test(q))).toEqual([]);
|
|
@@ -116,7 +116,7 @@ describe("paperlint init — writes paperlint.json only when something differs f
|
|
|
116
116
|
err: () => {},
|
|
117
117
|
interactive: false,
|
|
118
118
|
hooks: false,
|
|
119
|
-
run: (
|
|
119
|
+
run: () => ({ status: 0 }),
|
|
120
120
|
link: () => ({ ok: false as const, error: "not linked in this test" }),
|
|
121
121
|
};
|
|
122
122
|
const fresh = (files: Record<string, string>): string => {
|
|
@@ -170,7 +170,7 @@ const texRun = async (
|
|
|
170
170
|
ask: (q) =>
|
|
171
171
|
Promise.resolve((asked.push(q), /TeX Live/.test(q) ? answer : "")),
|
|
172
172
|
hooks: false,
|
|
173
|
-
run: (
|
|
173
|
+
run: () => ({ status: 0 }),
|
|
174
174
|
link: () => ({ ok: false as const, error: "not linked in this test" }),
|
|
175
175
|
tex,
|
|
176
176
|
});
|
|
@@ -249,7 +249,7 @@ async function initRun(
|
|
|
249
249
|
err: (s: string) => err.push(s),
|
|
250
250
|
interactive: false,
|
|
251
251
|
hooks: false,
|
|
252
|
-
run: (
|
|
252
|
+
run: () => ({ status: 0 }),
|
|
253
253
|
link: () => ({ ok: false, error: "not linked in this test" }),
|
|
254
254
|
...opts,
|
|
255
255
|
});
|
|
@@ -280,6 +280,26 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
280
280
|
]);
|
|
281
281
|
});
|
|
282
282
|
|
|
283
|
+
it("🔴 a paperlint.json holding JSON that is not an object is refused, not read as settings", async () => {
|
|
284
|
+
// Guards: `null` threw a TypeError reading the papers field; `[1]` was spread into a settings
|
|
285
|
+
// object and the consumer's file rewritten as `{ "0": 1, "papersDir": … }`.
|
|
286
|
+
for (const json of ["null\n", "[1]\n"]) {
|
|
287
|
+
const root = tree({
|
|
288
|
+
"paperlint.json": json,
|
|
289
|
+
"drafts/a/paper.md": "# a\n",
|
|
290
|
+
});
|
|
291
|
+
const decl = await declarePapers(root, () =>
|
|
292
|
+
choosePapers(root, { interactive: false }),
|
|
293
|
+
);
|
|
294
|
+
expect(decl).toEqual({
|
|
295
|
+
status: "unparsable",
|
|
296
|
+
path: join(root, "paperlint.json"),
|
|
297
|
+
reason: "not a JSON object",
|
|
298
|
+
});
|
|
299
|
+
expect(readFileSync(join(root, "paperlint.json"), "utf8")).toBe(json);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
283
303
|
it("a file without a trailing newline keeps having none", async () => {
|
|
284
304
|
const root = tree({
|
|
285
305
|
"paperlint.json": '{"x":1}',
|
package/src/init.ts
CHANGED
|
@@ -46,7 +46,13 @@ import {
|
|
|
46
46
|
// eslint-disable-next-line boundaries/dependencies -- legacy I/O, moves behind a port in #76
|
|
47
47
|
import { spawnSync } from "node:child_process";
|
|
48
48
|
import { dirname, join, relative, resolve } from "node:path";
|
|
49
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
doctor,
|
|
51
|
+
detectPapers,
|
|
52
|
+
found,
|
|
53
|
+
PROGRAMS,
|
|
54
|
+
type ProgramProbe,
|
|
55
|
+
} from "./doctor.ts";
|
|
50
56
|
import { PAPER_MARKERS, papersIn } from "./build.ts";
|
|
51
57
|
import { linkSkills, SKILLS_HOME, type LinkReport } from "./link-skills.ts";
|
|
52
58
|
import { actionRef } from "./action-ref.ts";
|
|
@@ -73,6 +79,7 @@ import {
|
|
|
73
79
|
PAPERS_DIR_FIELD,
|
|
74
80
|
} from "#lib/paper-config";
|
|
75
81
|
import { messageOf } from "./domain/text.ts";
|
|
82
|
+
import { isRecord } from "./domain/record.ts";
|
|
76
83
|
|
|
77
84
|
/** How the papers directory was arrived at. Printed, because a guess must not read as a fact. */
|
|
78
85
|
export type PapersHow =
|
|
@@ -227,13 +234,17 @@ export async function declarePapers(
|
|
|
227
234
|
): Promise<DeclarationResult> {
|
|
228
235
|
const path = join(root, CONFIG_FILE);
|
|
229
236
|
const raw = existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
230
|
-
let settings: Record<string, unknown
|
|
237
|
+
let settings: Readonly<Record<string, unknown>> = {};
|
|
231
238
|
if (raw !== null) {
|
|
239
|
+
let parsed: unknown;
|
|
232
240
|
try {
|
|
233
|
-
|
|
241
|
+
parsed = JSON.parse(raw);
|
|
234
242
|
} catch (e) {
|
|
235
243
|
return { status: "unparsable", path, reason: messageOf(e) };
|
|
236
244
|
}
|
|
245
|
+
if (!isRecord(parsed))
|
|
246
|
+
return { status: "unparsable", path, reason: "not a JSON object" };
|
|
247
|
+
settings = parsed;
|
|
237
248
|
const existing = settings[PAPERS_DIR_FIELD];
|
|
238
249
|
if (existing !== undefined)
|
|
239
250
|
return { status: "kept", path, papers: existing };
|
|
@@ -362,7 +373,7 @@ export function reportWorkflow(
|
|
|
362
373
|
* passing checker produce the same silence.
|
|
363
374
|
*/
|
|
364
375
|
export function missingPrograms(
|
|
365
|
-
run = spawnSync,
|
|
376
|
+
run: ProgramProbe = spawnSync,
|
|
366
377
|
): readonly (typeof PROGRAMS)[number][] {
|
|
367
378
|
return PROGRAMS.filter((p) => !found(p.bin, run));
|
|
368
379
|
}
|
|
@@ -584,7 +595,7 @@ export interface InitOptions {
|
|
|
584
595
|
name: string,
|
|
585
596
|
format: PaperFormat,
|
|
586
597
|
) => Promise<number>;
|
|
587
|
-
run?:
|
|
598
|
+
run?: ProgramProbe;
|
|
588
599
|
/**
|
|
589
600
|
* What `paperlint lint` would resolve from the declaration, asked of the CLI's OWN reader. A second
|
|
590
601
|
* implementation here would be a second source of truth — the very defect `doctor` reports.
|
|
@@ -749,21 +760,28 @@ export async function init(
|
|
|
749
760
|
for (const line of papersLines(decl, why)) log(line);
|
|
750
761
|
log(``);
|
|
751
762
|
log(`settings`);
|
|
752
|
-
|
|
763
|
+
// Every step below uses the DECLARED directory. A kept declaration outranks what init
|
|
764
|
+
// measured or guessed: otherwise the first paper and the workflow would land in the
|
|
765
|
+
// guessed directory while lint and the hooks keep reading the declared one.
|
|
766
|
+
let papersDir: string;
|
|
767
|
+
if (decl.status === "written") {
|
|
768
|
+
papersDir = decl.papers;
|
|
753
769
|
log(
|
|
754
770
|
` ✓ ${here(decl.path)} → { "${PAPERS_DIR_FIELD}": ${JSON.stringify(decl.papers)} }`,
|
|
755
771
|
);
|
|
756
|
-
else if (decl.status === "default")
|
|
772
|
+
} else if (decl.status === "default") {
|
|
773
|
+
papersDir = decl.papers;
|
|
757
774
|
log(
|
|
758
775
|
` ✓ nothing to write — "${decl.papers}" is the default, so no ${CONFIG_FILE} is needed`,
|
|
759
776
|
);
|
|
760
|
-
else if (decl.status === "kept") {
|
|
777
|
+
} else if (decl.status === "kept") {
|
|
761
778
|
if (typeof decl.papers !== "string") {
|
|
762
779
|
err(
|
|
763
780
|
` ✗ ${here(decl.path)} declares ${PAPERS_DIR_FIELD} = ${JSON.stringify(decl.papers)} — it must be a directory path (a string)`,
|
|
764
781
|
);
|
|
765
782
|
return 2;
|
|
766
783
|
}
|
|
784
|
+
papersDir = decl.papers;
|
|
767
785
|
log(
|
|
768
786
|
` ✓ ${here(decl.path)} already declares ${PAPERS_DIR_FIELD} = ${JSON.stringify(decl.papers)} — kept, nothing overwritten`,
|
|
769
787
|
);
|
|
@@ -777,10 +795,6 @@ export async function init(
|
|
|
777
795
|
);
|
|
778
796
|
return 2;
|
|
779
797
|
}
|
|
780
|
-
// Every step below uses the DECLARED directory. A kept declaration outranks what init
|
|
781
|
-
// measured or guessed: otherwise the first paper and the workflow would land in the
|
|
782
|
-
// guessed directory while lint and the hooks keep reading the declared one.
|
|
783
|
-
const papersDir = decl.papers as string;
|
|
784
798
|
|
|
785
799
|
// ── 3. the skills, linked where Claude Code looks for them ─────────────────────────────
|
|
786
800
|
for (const line of reportSkillLinks(link(root), here)) log(line);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* A defect in `nextStep` fails in table 1, one in `summarize` in table 2, before table 3 runs.
|
|
7
7
|
*
|
|
8
8
|
* The decision is pure, so none of this needs TeX. The real-pdflatex half lives in
|
|
9
|
-
* `test/e2e/build.ts`.
|
|
9
|
+
* `test/e2e/tex/build.e2e.ts`.
|
|
10
10
|
*/
|
|
11
11
|
import { createChecker } from "../lib/check.ts";
|
|
12
12
|
import type {
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* from a missing file into a created one;
|
|
8
8
|
* - the scorecard carries `researchQuestion` and NO `stages` — a new paper has shipped nothing,
|
|
9
9
|
* and a placeholder stage would be a red `paper/stages` on the very first run;
|
|
10
|
-
* - the scorecard
|
|
11
|
-
*
|
|
12
|
-
* skills' checker reads;
|
|
10
|
+
* - the scorecard carries the `**Readiness verdict:**` line the status hook surfaces on every
|
|
11
|
+
* paper edit — without it the hook reports that the paper cannot say whether it is ready;
|
|
13
12
|
* - it never overwrites, and on an existing folder adds only what is missing;
|
|
14
13
|
* - the project's `<papers>/.template/` wins over the package, file by file.
|
|
15
14
|
*
|
|
@@ -25,7 +24,6 @@ import {
|
|
|
25
24
|
rmSync,
|
|
26
25
|
writeFileSync,
|
|
27
26
|
} from "node:fs";
|
|
28
|
-
import { spawnSync } from "node:child_process";
|
|
29
27
|
import { tmpdir } from "node:os";
|
|
30
28
|
import { dirname, join } from "node:path";
|
|
31
29
|
import { fileURLToPath } from "node:url";
|
|
@@ -124,26 +122,12 @@ try {
|
|
|
124
122
|
/1 problem \(0 errors, 1 warning\)/.test(l.text),
|
|
125
123
|
);
|
|
126
124
|
|
|
127
|
-
//
|
|
128
|
-
const pc = spawnSync(
|
|
129
|
-
process.execPath,
|
|
130
|
-
[
|
|
131
|
-
join(ROOT, "skills", "paper-pipeline", "scripts", "pipeline-check.mjs"),
|
|
132
|
-
join(papers, "demo"),
|
|
133
|
-
"--json",
|
|
134
|
-
"--today=2026-09-23",
|
|
135
|
-
],
|
|
136
|
-
{ cwd: work, encoding: "utf8" },
|
|
137
|
-
);
|
|
138
|
-
let parsed = null;
|
|
139
|
-
try {
|
|
140
|
-
parsed = JSON.parse(pc.stdout);
|
|
141
|
-
} catch {
|
|
142
|
-
parsed = null;
|
|
143
|
-
}
|
|
125
|
+
// What the status hook reads on every paper edit: the verdict line.
|
|
144
126
|
check(
|
|
145
|
-
"the scorecard
|
|
146
|
-
|
|
127
|
+
"the scorecard carries the `**Readiness verdict:**` line the status hook surfaces",
|
|
128
|
+
/^\*\*Readiness verdict:\*\*/m.test(
|
|
129
|
+
readFileSync(join(papers, "demo", STATUS_FILE), "utf8"),
|
|
130
|
+
),
|
|
147
131
|
);
|
|
148
132
|
|
|
149
133
|
// ── never overwrite; on an existing folder only the missing files ─────────────────────
|
package/src/new-paper.test.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { newPaper, OVERRIDE_DIR, reportNewPaper } from "./new-paper.ts";
|
|
|
19
19
|
import { parsePaperSettings } from "./paper-settings.ts";
|
|
20
20
|
import { chooseVenue, run } from "./cli.ts";
|
|
21
21
|
import { shippedPresets } from "./presets.ts";
|
|
22
|
-
import {
|
|
22
|
+
import { presetsDir } from "./package-dirs.ts";
|
|
23
23
|
import { lintReport } from "../test/lint-report.ts";
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
|
|
@@ -177,6 +177,31 @@ describe("paperlint new --venue / --kind", () => {
|
|
|
177
177
|
expect(r.code).toBe(0);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
+
it("🔴 the new paper.tex is set in the venue's class, so tex/template passes on what new wrote; the required section is named", async () => {
|
|
181
|
+
const r = await newIn(["--venue", "aidc", "--kind", "regular"]);
|
|
182
|
+
const tex = readFileSync(join(r.dir, "paper.tex"), "utf8");
|
|
183
|
+
expect(tex).toContain("\\documentclass[conference,compsoc]{IEEEtran}\n");
|
|
184
|
+
expect(tex).not.toContain("{article}");
|
|
185
|
+
expect(r.out).not.toMatch(/tex\/template/);
|
|
186
|
+
// What the stub still lacks is what AIDC requires of the text, and lint says so right away.
|
|
187
|
+
expect(r.out).toMatch(/tex\/required-section/);
|
|
188
|
+
expect(r.code).toBe(1);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("a preset that names no template, or one no reader can read, leaves the template's class as it is", async () => {
|
|
192
|
+
for (const template of [undefined, "\\documentclass[a]{}"]) {
|
|
193
|
+
const r = await newIn(["--venue", "./venues/bare.jsonc"], {
|
|
194
|
+
"venues/bare.jsonc": JSON.stringify({
|
|
195
|
+
...(template === undefined ? {} : { template }),
|
|
196
|
+
tex: { packages: { x: ["x.sty"] } },
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
expect(readFileSync(join(r.dir, "paper.tex"), "utf8")).toContain(
|
|
200
|
+
"\\documentclass{article}",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
180
205
|
it("🔴 a path is relative to where you run it, and written relative to the paper's file", async () => {
|
|
181
206
|
const r = await newIn(
|
|
182
207
|
["--venue", "./venues/my-workshop.jsonc", "--kind", "short"],
|
|
@@ -197,7 +222,7 @@ describe("paperlint new --venue / --kind", () => {
|
|
|
197
222
|
const r = await newIn(["--venue", "icse"]);
|
|
198
223
|
expect(r.code).toBe(2);
|
|
199
224
|
// Guards: the list is read from the shipped presets, the same list lint prints.
|
|
200
|
-
expect(r.err).toContain(shippedPresets(
|
|
225
|
+
expect(r.err).toContain(shippedPresets(presetsDir()).join(", "));
|
|
201
226
|
expect(r.err).toMatch(/--venue icse: no such venue preset/);
|
|
202
227
|
expect(existsSync(r.dir)).toBe(false);
|
|
203
228
|
});
|
|
@@ -277,7 +302,7 @@ describe("chooseVenue — on a terminal", () => {
|
|
|
277
302
|
value: { extends: "paperlint:agenticdev", kind: "full" },
|
|
278
303
|
});
|
|
279
304
|
expect(t.asked[0]).toContain(
|
|
280
|
-
[...shippedPresets(
|
|
305
|
+
[...shippedPresets(presetsDir()), "none"].join(" / "),
|
|
281
306
|
);
|
|
282
307
|
expect(t.asked[1]).toMatch(/kind: short \/ full \/ demo \/ later/);
|
|
283
308
|
});
|
package/src/new-paper.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
28
|
import { CONFIG_FILE } from "#lib/paper-config";
|
|
29
|
+
import { isRecord } from "./domain/record.ts";
|
|
29
30
|
|
|
30
31
|
export type PaperFormat = "tex" | "md";
|
|
31
32
|
export const FORMATS: readonly PaperFormat[] = ["tex", "md"];
|
|
@@ -103,6 +104,11 @@ export interface VenueSetting {
|
|
|
103
104
|
/** `paperlint:<name>`, or a path relative to the paper's `paperlint.json`. */
|
|
104
105
|
readonly extends: string;
|
|
105
106
|
readonly kind: string | null;
|
|
107
|
+
/**
|
|
108
|
+
* The new `paper.tex` set in the class the preset's template names, so `tex/template` passes on
|
|
109
|
+
* the paper `new` just wrote. Absent or null: the template's class is kept.
|
|
110
|
+
*/
|
|
111
|
+
readonly setClass?: ((paperTex: string) => string) | null;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
/**
|
|
@@ -115,15 +121,18 @@ function withVenue(
|
|
|
115
121
|
src: string,
|
|
116
122
|
venue: VenueSetting,
|
|
117
123
|
): { ok: true; text: string } | { ok: false; reason: string } {
|
|
118
|
-
let obj:
|
|
124
|
+
let obj: unknown;
|
|
119
125
|
try {
|
|
120
|
-
obj = JSON.parse(text)
|
|
126
|
+
obj = JSON.parse(text);
|
|
121
127
|
} catch {
|
|
128
|
+
obj = null;
|
|
129
|
+
}
|
|
130
|
+
// Not JSON, or JSON that is not an object: there is no field to write "extends" into.
|
|
131
|
+
if (!isRecord(obj))
|
|
122
132
|
return {
|
|
123
133
|
ok: false,
|
|
124
134
|
reason: `--venue cannot be written into ${src}: it is not plain JSON — set "extends" by hand`,
|
|
125
135
|
};
|
|
126
|
-
}
|
|
127
136
|
const out: Record<string, unknown> = { ...obj, extends: venue.extends };
|
|
128
137
|
delete out["$comment"];
|
|
129
138
|
if (venue.kind !== null) out["kind"] = venue.kind;
|
|
@@ -160,6 +169,9 @@ function fromTemplate(
|
|
|
160
169
|
if (!existsSync(src))
|
|
161
170
|
return { ok: false, reason: `no template for ${file}: ${src} is missing` };
|
|
162
171
|
const text = readFileSync(src, "utf8").split("{{name}}").join(name);
|
|
172
|
+
const setClass = venue?.setClass ?? null;
|
|
173
|
+
if (file === SOURCE_FILE.tex && setClass !== null)
|
|
174
|
+
return { ok: true, value: { file, text: setClass(text), from } };
|
|
163
175
|
if (file !== CONFIG_FILE || venue === null)
|
|
164
176
|
return { ok: true, value: { file, text, from } };
|
|
165
177
|
const edited = withVenue(text, src, venue);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the package's own data lives, wherever the package is installed (its checkout,
|
|
3
|
+
* `node_modules`, a plugin cache). This file sits one level below the package root both as a source
|
|
4
|
+
* (`src/`) and as a build (`dist/`), so `..` is the root in either.
|
|
5
|
+
*
|
|
6
|
+
* presets/ the venue presets (`paperlint:<name>`) and their JSON Schema
|
|
7
|
+
* presets/tex/ the LaTeX inputs `paperlint build` puts on TEXINPUTS
|
|
8
|
+
*/
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
/** The shipped venue presets and `venue-profile.schema.json`. */
|
|
12
|
+
export const presetsDir = (): string =>
|
|
13
|
+
fileURLToPath(new URL("../presets", import.meta.url));
|
|
14
|
+
|
|
15
|
+
/** The LaTeX inputs a paper `\input`s by name (`paper-guards`, a venue's numbers). */
|
|
16
|
+
export const texInputsDir = (): string =>
|
|
17
|
+
fileURLToPath(new URL("../presets/tex", import.meta.url));
|
package/src/paper-files.test.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 74 errors — parse errors and "Definition for rule … was not found" — none of them on a paper
|
|
9
9
|
* file, and the run failed. Nothing in the settings could turn that off.
|
|
10
10
|
*/
|
|
11
|
-
import { ESLint
|
|
11
|
+
import { ESLint } from "eslint";
|
|
12
12
|
import {
|
|
13
13
|
mkdirSync,
|
|
14
14
|
mkdtempSync,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { afterEach, describe, expect, it } from "vitest";
|
|
22
|
-
import { buildConfig, run } from "./cli.ts";
|
|
22
|
+
import { buildConfig, eslintConfig, run } from "./cli.ts";
|
|
23
23
|
import {
|
|
24
24
|
narrowToOwners,
|
|
25
25
|
ownedPatterns,
|
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
scopeToOwned,
|
|
29
29
|
} from "./paper-files.ts";
|
|
30
30
|
import { lintReport } from "../test/lint-report.ts";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
|
|
33
|
+
/** What `calculateConfigForFile` answers for a file inside the config's scope, read for its rules. */
|
|
34
|
+
const Computed = z.object({ rules: z.record(z.string(), z.unknown()) });
|
|
31
35
|
|
|
32
36
|
const dirs: string[] = [];
|
|
33
37
|
afterEach(() => {
|
|
@@ -232,25 +236,24 @@ describe("`rules` blocks — where the rule lands", () => {
|
|
|
232
236
|
const eslint = new ESLint({
|
|
233
237
|
cwd: root,
|
|
234
238
|
overrideConfigFile: true,
|
|
235
|
-
overrideConfig:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
overrideConfig: eslintConfig(
|
|
240
|
+
buildConfig(
|
|
241
|
+
{
|
|
242
|
+
rules: [
|
|
243
|
+
{
|
|
244
|
+
basePath: root,
|
|
245
|
+
files: ["papers/**"],
|
|
246
|
+
rules: { "paper/source": "warn", "paper/section-word": "off" },
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
null,
|
|
251
|
+
),
|
|
252
|
+
),
|
|
247
253
|
});
|
|
248
254
|
const on = async (file: string) =>
|
|
249
|
-
(
|
|
250
|
-
|
|
251
|
-
rules: Record<string, unknown>;
|
|
252
|
-
}
|
|
253
|
-
).rules;
|
|
255
|
+
Computed.parse(await eslint.calculateConfigForFile(join(root, file)))
|
|
256
|
+
.rules;
|
|
254
257
|
const status = await on("papers/a/PIPELINE-STATUS.md");
|
|
255
258
|
expect(status["paper/source"]).toEqual([1]);
|
|
256
259
|
expect(status["paper/section-word"]).toBeUndefined();
|
package/src/paper-files.ts
CHANGED
|
@@ -40,16 +40,7 @@
|
|
|
40
40
|
* - later global-ignore patterns win, across blocks, in config order;
|
|
41
41
|
* - `files: [["a", "b"]]` matches a file only when BOTH patterns match.
|
|
42
42
|
*/
|
|
43
|
-
import type { RuleBlock, RuleEntry } from "./rules-config.ts";
|
|
44
|
-
|
|
45
|
-
/** What this module reads of a config block. */
|
|
46
|
-
interface Block {
|
|
47
|
-
readonly files?: readonly unknown[];
|
|
48
|
-
readonly ignores?: readonly unknown[];
|
|
49
|
-
readonly plugins?: Readonly<
|
|
50
|
-
Record<string, { readonly rules?: Readonly<Record<string, unknown>> }>
|
|
51
|
-
>;
|
|
52
|
-
}
|
|
43
|
+
import type { ConfigBlock, RuleBlock, RuleEntry } from "./rules-config.ts";
|
|
53
44
|
|
|
54
45
|
/**
|
|
55
46
|
* A set of files paperlint lints: an own block's globs and the exceptions that block makes. A file is
|
|
@@ -99,20 +90,18 @@ const merged = (scopes: readonly OwnedScope[]): OwnedScope[] => {
|
|
|
99
90
|
* THE FILES PAPERLINT LINTS: every own block that names `files`, with its `ignores`. A block without
|
|
100
91
|
* `files` (a global ignore, or the block registering `pdf` for every file) claims nothing.
|
|
101
92
|
*/
|
|
102
|
-
export function ownedScopes(own: readonly
|
|
93
|
+
export function ownedScopes(own: readonly ConfigBlock[]): OwnedScope[] {
|
|
103
94
|
return merged(
|
|
104
|
-
own
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
b.files
|
|
108
|
-
|
|
109
|
-
: [{ files: globsOf(b.files), ignores: stringsOf(b.ignores) }],
|
|
110
|
-
),
|
|
95
|
+
own.flatMap((b) =>
|
|
96
|
+
b.files === undefined
|
|
97
|
+
? []
|
|
98
|
+
: [{ files: globsOf(b.files), ignores: stringsOf(b.ignores) }],
|
|
99
|
+
),
|
|
111
100
|
);
|
|
112
101
|
}
|
|
113
102
|
|
|
114
103
|
/** Every glob paperlint's own blocks lint, once, in config order. */
|
|
115
|
-
export function ownedPatterns(own: readonly
|
|
104
|
+
export function ownedPatterns(own: readonly ConfigBlock[]): string[] {
|
|
116
105
|
return [...new Set(ownedScopes(own).flatMap((s) => s.files))];
|
|
117
106
|
}
|
|
118
107
|
|
|
@@ -134,7 +123,7 @@ export function scopeToOwned(owned: readonly string[]): {
|
|
|
134
123
|
}
|
|
135
124
|
|
|
136
125
|
/** Every `<plugin>/<rule>` a block registers. */
|
|
137
|
-
const ruleIdsOf = (b:
|
|
126
|
+
const ruleIdsOf = (b: ConfigBlock): string[] =>
|
|
138
127
|
Object.entries(b.plugins ?? {}).flatMap(([name, plugin]) =>
|
|
139
128
|
Object.keys(plugin.rules ?? {}).map((rule) => `${name}/${rule}`),
|
|
140
129
|
);
|
|
@@ -145,12 +134,11 @@ const ruleIdsOf = (b: Block): string[] =>
|
|
|
145
134
|
* one file more.
|
|
146
135
|
*/
|
|
147
136
|
export function ruleOwners(
|
|
148
|
-
own: readonly
|
|
137
|
+
own: readonly ConfigBlock[],
|
|
149
138
|
): Map<string, readonly OwnedScope[]> {
|
|
150
139
|
const all = ownedScopes(own);
|
|
151
140
|
const found = new Map<string, OwnedScope[]>();
|
|
152
|
-
for (const
|
|
153
|
-
const b = raw as Block;
|
|
141
|
+
for (const b of own) {
|
|
154
142
|
const scopes =
|
|
155
143
|
b.files === undefined
|
|
156
144
|
? all
|
|
@@ -171,17 +159,18 @@ export function narrowToOwners(
|
|
|
171
159
|
block: RuleBlock,
|
|
172
160
|
owners: ReadonlyMap<string, readonly OwnedScope[]>,
|
|
173
161
|
): ScopedBlock[] {
|
|
162
|
+
// Keyed by the owner's JSON, so rules with equal owners share one group of blocks.
|
|
174
163
|
const groups = new Map<string, Record<string, RuleEntry>>();
|
|
164
|
+
const ownerOf = new Map<string, readonly OwnedScope[]>();
|
|
175
165
|
for (const [id, entry] of Object.entries(block.rules)) {
|
|
176
166
|
const owner = owners.get(id);
|
|
177
167
|
if (owner === undefined) continue;
|
|
178
168
|
const key = JSON.stringify(owner);
|
|
179
169
|
groups.set(key, { ...groups.get(key), [id]: entry });
|
|
170
|
+
ownerOf.set(key, owner);
|
|
180
171
|
}
|
|
181
|
-
return [...
|
|
182
|
-
|
|
183
|
-
within(block, scope, rules),
|
|
184
|
-
),
|
|
172
|
+
return [...ownerOf].flatMap(([key, owner]) =>
|
|
173
|
+
owner.map((scope) => within(block, scope, { ...groups.get(key) })),
|
|
185
174
|
);
|
|
186
175
|
}
|
|
187
176
|
|
|
@@ -232,6 +232,9 @@ describe("paperlint lint — the venue preset's rules", () => {
|
|
|
232
232
|
extends: "paperlint:agenticdev",
|
|
233
233
|
kind: "short",
|
|
234
234
|
}),
|
|
235
|
+
// In agenticdev's class, or tex/template would be the finding.
|
|
236
|
+
"papers/a/paper.tex":
|
|
237
|
+
"\\documentclass[sigconf]{acmart}\n\\begin{document}x\\end{document}\n",
|
|
235
238
|
});
|
|
236
239
|
const r = await lint(root);
|
|
237
240
|
expect(r.code).toBe(0);
|
|
@@ -282,7 +285,9 @@ describe("paperlint lint — the paper over its preset, and the project's own pr
|
|
|
282
285
|
});
|
|
283
286
|
const r = await lint(root);
|
|
284
287
|
expect(r.code).toBe(1);
|
|
285
|
-
expect(r.out).toMatch(
|
|
288
|
+
expect(r.out).toMatch(
|
|
289
|
+
/acm-sigconf, agenticdev, aidc, aisec, ieee-conference, realm/,
|
|
290
|
+
);
|
|
286
291
|
});
|
|
287
292
|
});
|
|
288
293
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
7
|
-
import
|
|
7
|
+
import { absolutePath } from "./domain/paths.ts";
|
|
8
8
|
import { findProjectRoot } from "../lib/paper-config.ts";
|
|
9
9
|
import {
|
|
10
10
|
paperRules,
|
|
@@ -215,7 +215,7 @@ describe("findProjectRoot — where the root paperlint.json is looked for", () =
|
|
|
215
215
|
[`${PAPER}/paperlint.json`]: '{"kind":"research"}',
|
|
216
216
|
[`${ROOT}/paperlint.json`]: '{"extends":"paperlint:aisec"}',
|
|
217
217
|
});
|
|
218
|
-
const isFile = (p: string) => files.isFile(p
|
|
218
|
+
const isFile = (p: string) => files.isFile(absolutePath(p));
|
|
219
219
|
expect(findProjectRoot(PAPER, isFile)).toBe(ROOT);
|
|
220
220
|
// No root file anywhere: the package.json directory.
|
|
221
221
|
expect(findProjectRoot(PAPER, (p) => p === `${ROOT}/package.json`)).toBe(
|
package/src/paper-settings.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
SETTINGS_KEYS,
|
|
23
23
|
findProjectRoot,
|
|
24
24
|
} from "#lib/paper-config";
|
|
25
|
-
import
|
|
25
|
+
import { callerPath } from "./caller-path.ts";
|
|
26
26
|
import { err, ok, type Result } from "./domain/result.ts";
|
|
27
27
|
import type { Files } from "./ports/files.ts";
|
|
28
28
|
import {
|
|
@@ -114,14 +114,12 @@ export function parsePaperSettings(
|
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
const at = (p: string): AbsolutePath => p as AbsolutePath;
|
|
118
|
-
|
|
119
117
|
/** A JSON file through `files`: undefined when absent, the problem when it does not parse. */
|
|
120
118
|
function readJson(
|
|
121
119
|
files: Files,
|
|
122
120
|
path: string,
|
|
123
121
|
): Result<unknown, SettingsProblem> {
|
|
124
|
-
const bytes = files.readBytes(
|
|
122
|
+
const bytes = files.readBytes(callerPath(path));
|
|
125
123
|
if (bytes === null) return ok(undefined);
|
|
126
124
|
try {
|
|
127
125
|
return ok(JSON.parse(new TextDecoder().decode(bytes)));
|
|
@@ -139,7 +137,9 @@ function rootDefaults(
|
|
|
139
137
|
files: Files,
|
|
140
138
|
paperDir: string,
|
|
141
139
|
): Result<Defaults | null, SettingsProblem> {
|
|
142
|
-
const root = findProjectRoot(dirname(paperDir), (p) =>
|
|
140
|
+
const root = findProjectRoot(dirname(paperDir), (p) =>
|
|
141
|
+
files.isFile(callerPath(p)),
|
|
142
|
+
);
|
|
143
143
|
const json = readJson(files, join(root, CONFIG_FILE));
|
|
144
144
|
if (!json.ok) return json;
|
|
145
145
|
if (!isObject(json.value)) return ok(null);
|