paperlint 3.1.3 → 4.0.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 +12 -7
- package/CLAUDE.md +5 -5
- package/CONTRIBUTING.md +34 -5
- 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 +4 -3
- 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/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/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +2 -1
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/node/process.io.d.ts +5 -7
- package/dist/adapters/node/process.io.d.ts.map +1 -1
- package/dist/adapters/node/process.io.js +4 -3
- 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 +13 -7
- 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 +8 -7
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts.map +1 -1
- package/dist/adapters/references/pool.js +4 -5
- package/dist/adapters/references/pool.js.map +1 -1
- package/dist/adapters/references/reach.io.d.ts.map +1 -1
- package/dist/adapters/references/reach.io.js +2 -1
- package/dist/adapters/references/reach.io.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 +10 -3
- 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 +19 -17
- 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 +93 -46
- 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 +5 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/lookup-cache.d.ts.map +1 -1
- package/dist/domain/lookup-cache.js +10 -8
- package/dist/domain/lookup-cache.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/text.d.ts +12 -0
- package/dist/domain/text.d.ts.map +1 -1
- package/dist/domain/text.js +17 -0
- package/dist/domain/text.js.map +1 -1
- package/dist/engine.d.ts +23 -22
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -3
- package/dist/engine.js.map +1 -1
- package/dist/eslint-rules/bib-reachable-entry.d.ts +22 -0
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +1 -0
- package/dist/eslint-rules/bib-reachable-entry.js +75 -0
- package/dist/eslint-rules/bib-reachable-entry.js.map +1 -0
- package/dist/eslint-rules/install-path-literals.d.ts +80 -0
- package/dist/eslint-rules/install-path-literals.d.ts.map +1 -0
- package/dist/eslint-rules/install-path-literals.js +105 -0
- package/dist/eslint-rules/install-path-literals.js.map +1 -0
- package/dist/eslint-rules/latex-language.d.ts +136 -0
- package/dist/eslint-rules/latex-language.d.ts.map +1 -0
- package/dist/eslint-rules/latex-language.js +639 -0
- package/dist/eslint-rules/latex-language.js.map +1 -0
- package/dist/eslint-rules/paper-research-question.d.ts +31 -0
- package/dist/eslint-rules/paper-research-question.d.ts.map +1 -0
- package/dist/eslint-rules/paper-research-question.js +188 -0
- package/dist/eslint-rules/paper-research-question.js.map +1 -0
- package/dist/eslint-rules/paper-stages.d.ts +83 -0
- package/dist/eslint-rules/paper-stages.d.ts.map +1 -0
- package/dist/eslint-rules/paper-stages.js +358 -0
- package/dist/eslint-rules/paper-stages.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +74 -0
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -0
- package/dist/eslint-rules/paper-typography.js +404 -0
- package/dist/eslint-rules/paper-typography.js.map +1 -0
- package/dist/eslint-rules/papers.d.ts +42 -0
- package/dist/eslint-rules/papers.d.ts.map +1 -0
- package/dist/eslint-rules/papers.js +145 -0
- package/dist/eslint-rules/papers.js.map +1 -0
- package/dist/eslint-rules/pdf-last-page-balance.d.ts +72 -0
- package/dist/eslint-rules/pdf-last-page-balance.d.ts.map +1 -0
- package/dist/eslint-rules/pdf-last-page-balance.js +237 -0
- package/dist/eslint-rules/pdf-last-page-balance.js.map +1 -0
- package/dist/eslint-rules/review-frontmatter.d.ts +57 -0
- package/dist/eslint-rules/review-frontmatter.d.ts.map +1 -0
- package/dist/eslint-rules/review-frontmatter.js +126 -0
- package/dist/eslint-rules/review-frontmatter.js.map +1 -0
- package/dist/eslint-rules/review-frontmatter.schema.json +36 -0
- package/dist/eslint-rules/rule-context.d.ts +55 -0
- package/dist/eslint-rules/rule-context.d.ts.map +1 -0
- package/dist/eslint-rules/rule-context.js +9 -0
- package/dist/eslint-rules/rule-context.js.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.d.ts +25 -0
- package/dist/eslint-rules/sibling-frontmatter.d.ts.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.js +16 -0
- package/dist/eslint-rules/sibling-frontmatter.js.map +1 -0
- package/dist/eslint-rules/sibling-frontmatter.schema.json +14 -0
- package/dist/eslint-rules/temp-root-realpath.d.ts +71 -0
- package/dist/eslint-rules/temp-root-realpath.d.ts.map +1 -0
- package/dist/eslint-rules/temp-root-realpath.js +139 -0
- package/dist/eslint-rules/temp-root-realpath.js.map +1 -0
- package/dist/eslint-rules/tex-build.d.ts +35 -0
- package/dist/eslint-rules/tex-build.d.ts.map +1 -0
- package/dist/eslint-rules/tex-build.js +313 -0
- package/dist/eslint-rules/tex-build.js.map +1 -0
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +31 -27
- package/dist/facts-file.js.map +1 -1
- package/dist/hooks-settings.d.ts +1 -1
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +31 -11
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -9
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +33 -15
- package/dist/init.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -2
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js +2 -2
- package/dist/latex-loop.js.map +1 -1
- package/dist/lib/agent-cli-version.d.ts +65 -0
- package/dist/lib/agent-cli-version.d.ts.map +1 -0
- package/dist/lib/agent-cli-version.js +77 -0
- package/dist/lib/agent-cli-version.js.map +1 -0
- package/dist/lib/check.d.ts +19 -0
- package/dist/lib/check.d.ts.map +1 -0
- package/dist/lib/check.js +52 -0
- package/dist/lib/check.js.map +1 -0
- package/dist/lib/markdown.d.ts +180 -0
- package/dist/lib/markdown.d.ts.map +1 -0
- package/dist/lib/markdown.js +302 -0
- package/dist/lib/markdown.js.map +1 -0
- package/dist/lib/paper-config.d.ts +84 -0
- package/dist/lib/paper-config.d.ts.map +1 -0
- package/dist/lib/paper-config.js +138 -0
- package/dist/lib/paper-config.js.map +1 -0
- package/dist/lib/skill-eval-fixture.d.ts +29 -0
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -0
- package/dist/lib/skill-eval-fixture.js +62 -0
- package/dist/lib/skill-eval-fixture.js.map +1 -0
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +5 -4
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +7 -3
- package/dist/new-paper.js.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/paper-settings.js +8 -7
- 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 +25 -16
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +3 -4
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/ports/tool-installer.d.ts +2 -14
- package/dist/ports/tool-installer.d.ts.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +15 -10
- 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 +15 -12
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +4 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +22 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +21 -16
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +21 -8
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +2 -2
- package/docs/e2e.md +7 -7
- 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 +2 -2
- package/docs/testing.md +11 -8
- package/docs/toolchain.md +1 -1
- package/eslint-rules/{bib-reachable-entry.mjs → bib-reachable-entry.ts} +5 -4
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +18 -10
- package/eslint-rules/latex-language.harness.mjs +2 -2
- package/eslint-rules/latex-language.test.mjs +7 -1
- package/eslint-rules/{latex-language.mjs → latex-language.ts} +297 -109
- package/eslint-rules/paper-research-question.harness.mjs +3 -3
- package/eslint-rules/{paper-research-question.mjs → paper-research-question.ts} +35 -16
- package/eslint-rules/paper-stages.harness.mjs +2 -2
- package/eslint-rules/paper-stages.test.mjs +8 -2
- package/eslint-rules/{paper-stages.mjs → paper-stages.ts} +76 -31
- package/eslint-rules/paper-typography.test.mjs +3 -3
- package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +134 -56
- package/eslint-rules/papers.harness.mjs +2 -2
- package/eslint-rules/{papers.mjs → papers.ts} +7 -4
- package/eslint-rules/pdf-last-page-balance.harness.mjs +3 -3
- package/eslint-rules/{pdf-last-page-balance.mjs → pdf-last-page-balance.ts} +89 -32
- package/eslint-rules/review-frontmatter.test.mjs +2 -2
- package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +38 -16
- package/eslint-rules/rule-context.ts +62 -0
- package/eslint-rules/{sibling-frontmatter.mjs → sibling-frontmatter.ts} +1 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +1 -1
- package/eslint-rules/{temp-root-realpath.mjs → temp-root-realpath.ts} +20 -7
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/{tex-build.mjs → tex-build.ts} +6 -5
- package/eslint.config.mjs +146 -30
- 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 +2 -2
- package/fixtures/real-markdown-paper/{baseline.mjs → baseline.ts} +31 -10
- package/hooks/{hooks.harness.mjs → hooks.harness.ts} +51 -34
- package/hooks/paper-edit-guard.hook.mjs +1 -1
- package/hooks/{paper-edit-guard.test.mjs → paper-edit-guard.test.ts} +3 -7
- package/hooks/paper-skills-nudge.hook.mjs +1 -1
- package/hooks/{paper-skills-nudge.test.mjs → paper-skills-nudge.test.ts} +2 -6
- package/hooks/paper-status-gates.sh +9 -19
- package/hooks/{paper-status-gates.test.mjs → paper-status-gates.test.ts} +2 -6
- package/lib/agent-cli-version.harness.mjs +1 -1
- package/lib/{agent-cli-version.mjs → agent-cli-version.ts} +32 -18
- package/lib/check.test.ts +14 -9
- package/lib/{check.mjs → check.ts} +22 -9
- package/lib/markdown.test.mjs +7 -7
- package/lib/{markdown.mjs → markdown.ts} +68 -29
- package/lib/paper-config.harness.mjs +9 -9
- package/lib/{paper-config.mjs → paper-config.ts} +20 -19
- package/lib/skill-checks.mjs +24 -381
- package/lib/skill-checks.test.mjs +64 -116
- package/lib/skill-corpus.mjs +10 -251
- package/lib/skill-corpus.test.mjs +2 -13
- package/lib/skill-eval-kit.mjs +1 -1
- package/lib/skill-trigger-cases.mjs +1 -1
- package/package.json +42 -10
- package/scripts/{check.harness.mjs → check.harness.ts} +111 -10
- package/scripts/{check.test.mjs → check.test.ts} +13 -10
- package/scripts/{check.mjs → check.ts} +116 -42
- package/scripts/coverage-config.test.ts +80 -38
- package/scripts/eslint-report-guard.test.mjs +1 -1
- package/scripts/harness-api.frozen.json +14 -16
- package/scripts/harness-api.test.ts +8 -5
- package/scripts/layer-legacy-frozen.test.ts +17 -13
- package/scripts/{layer-legacy-frozen.mjs → layer-legacy-frozen.ts} +128 -39
- package/scripts/release-config.test.ts +12 -4
- package/scripts/rules-see-files.test.mjs +1 -1
- package/scripts/test.test.ts +78 -0
- package/scripts/test.ts +87 -0
- 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 +3 -4
- package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +3 -3
- 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.mjs → 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/SKILL.md +2 -28
- package/skills/paper-pipeline/SKILL.md.spec.ts +1 -27
- package/skills/paper-pipeline/description-language.eval.mjs +1 -1
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +2 -2
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- 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/artifact-coverage.mjs +1 -1
- package/skills/paper-pipeline/scripts/{artifact-coverage.test.mjs → artifact-coverage.test.ts} +2 -2
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +6 -104
- package/skills/paper-pipeline/scripts/consumer.mjs +24 -109
- package/skills/paper-pipeline/scripts/consumer.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +1 -1
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +1 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +2 -3
- package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/population-map.mjs +1 -4
- package/skills/paper-pipeline/scripts/population-map.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/{round-diff.harness.mjs → round-diff.harness.ts} +45 -18
- package/skills/paper-pipeline/scripts/round-diff.mjs +1 -1
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +1 -1
- 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/plan-paper-timeline/fixtures/{fake-google-calendar.mjs → fake-google-calendar.ts} +101 -70
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +3 -3
- package/skills/render-paper/SKILL.md +1 -37
- package/skills/render-paper/SKILL.md.spec.ts +0 -36
- package/skills/render-paper/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- 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 +5 -12
- package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
- package/skills/verify-citations/SKILL.md +2 -38
- package/skills/verify-citations/SKILL.md.spec.ts +1 -37
- package/skills/verify-citations/scripts/{bib-authors.cli.test.mjs → bib-authors.cli.test.ts} +14 -4
- package/skills/verify-citations/scripts/bib-authors.d.mts +63 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +1 -4
- package/skills/verify-citations/scripts/{verify-cites.cli.test.mjs → verify-cites.cli.test.ts} +32 -9
- package/skills/verify-citations/scripts/verify-cites.d.mts +58 -0
- package/src/CLAUDE.md +1 -1
- package/src/adapters/banal/failure.ts +9 -10
- package/src/adapters/banal/index.test.ts +5 -8
- package/src/adapters/banal/index.ts +8 -7
- package/src/adapters/banal/install.ts +9 -4
- package/src/adapters/banal/invocation.test.ts +23 -9
- 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 +5 -5
- 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 +8 -6
- package/src/adapters/memory/index.ts +4 -3
- package/src/adapters/node/files.io.ts +2 -2
- package/src/adapters/node/files.test.ts +8 -4
- package/src/adapters/node/process.io.ts +13 -5
- package/src/adapters/node/process.test.ts +5 -5
- package/src/adapters/node/workspace.io.ts +9 -4
- package/src/adapters/node/workspace.test.ts +3 -1
- package/src/adapters/pdfjs/fill.test.ts +2 -2
- package/src/adapters/pdfjs/fill.ts +14 -7
- package/src/adapters/references/index.test.ts +31 -38
- package/src/adapters/references/index.ts +33 -60
- package/src/adapters/references/pool.ts +3 -5
- package/src/adapters/references/reach.io.ts +2 -1
- package/src/build-engine.harness.mjs +1 -1
- package/src/build-engine.test.ts +13 -8
- package/src/build-engine.ts +12 -7
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +24 -24
- package/src/build.ts +34 -26
- package/src/caller-path.ts +13 -0
- package/src/cli.harness.mjs +6 -8
- package/src/cli.test.ts +31 -13
- package/src/cli.ts +117 -58
- package/src/doctor.harness.mjs +3 -3
- package/src/doctor.test.ts +1 -1
- package/src/doctor.ts +17 -7
- package/src/domain/lookup-cache.ts +21 -11
- 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/result.test.ts +2 -2
- package/src/domain/sha256.ts +5 -3
- package/src/domain/text.test.ts +32 -0
- package/src/domain/text.ts +23 -0
- package/src/engine.harness.mjs +2 -2
- package/src/engine.ts +34 -5
- package/src/facts-file.test.ts +9 -7
- package/src/facts-file.ts +45 -29
- package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +56 -20
- package/src/hooks-settings.ts +48 -13
- package/src/init.test.ts +37 -14
- package/src/init.ts +48 -24
- package/src/{latex-log.harness.mjs → latex-log.harness.ts} +14 -14
- package/src/{latex-loop.harness.mjs → latex-loop.harness.ts} +64 -36
- package/src/latex-loop.ts +3 -3
- package/src/{link-skills.harness.mjs → link-skills.harness.ts} +19 -19
- package/src/link-skills.test.ts +3 -3
- package/src/link-skills.ts +5 -4
- package/src/lint-fix.test.ts +4 -6
- package/src/linted-set.test.ts +2 -2
- package/src/new-paper.harness.mjs +9 -25
- package/src/new-paper.test.ts +8 -10
- package/src/new-paper.ts +9 -5
- package/src/paper-files.test.ts +25 -24
- package/src/paper-files.ts +16 -27
- package/src/paper-settings-commands.test.ts +5 -4
- package/src/paper-settings.test.ts +7 -7
- package/src/paper-settings.ts +13 -14
- package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
- package/src/pdf-facts.test.ts +25 -15
- package/src/pdf-facts.ts +81 -23
- package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
- package/src/pdf-geometry.ts +8 -4
- package/src/ports/tool-installer.ts +2 -17
- package/src/presets.ts +31 -16
- package/src/references.test.ts +76 -62
- package/src/references.ts +18 -12
- package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
- package/src/rules-config.ts +46 -14
- package/src/rules-off.test.ts +6 -4
- package/src/{structure.harness.mjs → structure.harness.ts} +26 -15
- package/src/structure.ts +1 -1
- package/src/{tex-requirements.harness.mjs → tex-requirements.harness.ts} +50 -22
- package/src/tex-requirements.test.ts +21 -0
- package/src/tex-requirements.ts +37 -11
- package/src/toolchain.harness.mjs +2 -2
- package/src/toolchain.test.ts +10 -6
- package/src/toolchain.ts +31 -17
- package/src/types.ts +5 -3
- package/src/venue-rules.test.ts +70 -68
- package/src/venue-rules.ts +22 -12
- package/tsconfig.json +6 -3
- package/tsconfig.pkg.json +10 -0
- package/lib/check.d.mts +0 -17
- package/lib/paper-config.d.mts +0 -31
- package/scripts/layer-legacy-frozen.d.mts +0 -36
- package/scripts/rules-are-content-only.harness.mjs +0 -105
- package/scripts/rules-are-content-only.mjs +0 -148
- package/scripts/rules-are-content-only.test.mjs +0 -61
- package/scripts/test.mjs +0 -44
- package/scripts/test.test.mjs +0 -52
- 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.mjs +0 -71
- 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 -753
- 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/bin/{paperlint.test.mjs → paperlint.test.ts} +0 -0
- /package/lib/{skill-eval-fixture.mjs → skill-eval-fixture.ts} +0 -0
- /package/skills/grade-paper-writing/__snapshots__/{prose-lint.test.mjs.snap → prose-lint.test.ts.snap} +0 -0
- /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
package/src/paper-settings.ts
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
ROOT_ONLY_KEYS,
|
|
22
22
|
SETTINGS_KEYS,
|
|
23
23
|
findProjectRoot,
|
|
24
|
-
} from "
|
|
25
|
-
import
|
|
24
|
+
} from "#lib/paper-config";
|
|
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 {
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type Parsed,
|
|
31
31
|
type RuleEntry,
|
|
32
32
|
} from "./rules-config.ts";
|
|
33
|
+
import { messageOf } from "./domain/text.ts";
|
|
33
34
|
|
|
34
35
|
/** What one paper declares. Every absent field is null. */
|
|
35
36
|
export interface PaperSettings {
|
|
@@ -92,11 +93,13 @@ export function parsePaperSettings(
|
|
|
92
93
|
if (!isObject(json)) return err("must be a JSON object");
|
|
93
94
|
const unknown = Object.keys(json).filter((k) => !KNOWN.includes(k));
|
|
94
95
|
if (unknown.length > 0)
|
|
95
|
-
return err(
|
|
96
|
+
return err(
|
|
97
|
+
`unknown key "${String(unknown[0])}" — known keys: ${KNOWN.join(", ")}`,
|
|
98
|
+
);
|
|
96
99
|
const project = Object.keys(json).filter((k) => ROOT_ONLY_KEYS.includes(k));
|
|
97
100
|
if (project.length > 0)
|
|
98
101
|
return err(
|
|
99
|
-
`"${project[0]}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
|
|
102
|
+
`"${String(project[0])}" is a project setting — set it in the root ${CONFIG_FILE}, not in a paper's`,
|
|
100
103
|
);
|
|
101
104
|
const fields = stringFields(json);
|
|
102
105
|
if (!fields.ok) return fields;
|
|
@@ -111,19 +114,17 @@ export function parsePaperSettings(
|
|
|
111
114
|
});
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
const at = (p: string): AbsolutePath => p as AbsolutePath;
|
|
115
|
-
|
|
116
117
|
/** A JSON file through `files`: undefined when absent, the problem when it does not parse. */
|
|
117
118
|
function readJson(
|
|
118
119
|
files: Files,
|
|
119
120
|
path: string,
|
|
120
121
|
): Result<unknown, SettingsProblem> {
|
|
121
|
-
const bytes = files.readBytes(
|
|
122
|
+
const bytes = files.readBytes(callerPath(path));
|
|
122
123
|
if (bytes === null) return ok(undefined);
|
|
123
124
|
try {
|
|
124
125
|
return ok(JSON.parse(new TextDecoder().decode(bytes)));
|
|
125
126
|
} catch (e) {
|
|
126
|
-
return err({ kind: "broken", why: `not JSON (${(e
|
|
127
|
+
return err({ kind: "broken", why: `not JSON (${messageOf(e)})` });
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
|
|
@@ -136,7 +137,9 @@ function rootDefaults(
|
|
|
136
137
|
files: Files,
|
|
137
138
|
paperDir: string,
|
|
138
139
|
): Result<Defaults | null, SettingsProblem> {
|
|
139
|
-
const root = findProjectRoot(dirname(paperDir), (p) =>
|
|
140
|
+
const root = findProjectRoot(dirname(paperDir), (p) =>
|
|
141
|
+
files.isFile(callerPath(p)),
|
|
142
|
+
);
|
|
140
143
|
const json = readJson(files, join(root, CONFIG_FILE));
|
|
141
144
|
if (!json.ok) return json;
|
|
142
145
|
if (!isObject(json.value)) return ok(null);
|
|
@@ -200,9 +203,5 @@ export function paperRules(
|
|
|
200
203
|
if (settings.rules === null || Array.isArray(settings.rules))
|
|
201
204
|
return { ok: true, value: null };
|
|
202
205
|
const where = `${join(paperDir, CONFIG_FILE)} → "rules"`;
|
|
203
|
-
return parseRuleEntries(
|
|
204
|
-
settings.rules as Readonly<Record<string, unknown>>,
|
|
205
|
-
where,
|
|
206
|
-
shipped,
|
|
207
|
-
);
|
|
206
|
+
return parseRuleEntries(settings.rules, where, shipped);
|
|
208
207
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `pdf-facts.ts` — pdf.js reading REAL PDFs, committed under `fixtures/pdf-facts/`.
|
|
3
3
|
*
|
|
4
|
-
* The pure decisions are tested in `pdf-geometry.harness.
|
|
4
|
+
* The pure decisions are tested in `pdf-geometry.harness.ts` with hand-written words. This file is
|
|
5
5
|
* the other half: that the shell asks pdf.js the right questions, with the right options, and maps
|
|
6
6
|
* what comes back. The expected numbers are the ones poppler reported for the same files in the
|
|
7
7
|
* 2026-09-24 measurement (issue #61), so a pass here is agreement with the tool this replaced.
|
|
@@ -11,23 +11,24 @@ import { writeFileSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { dirname, join, resolve } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { createChecker } from "../lib/check.
|
|
14
|
+
import { createChecker } from "../lib/check.ts";
|
|
15
|
+
import type { PdfFacts } from "./pdf-facts.ts";
|
|
15
16
|
|
|
16
17
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const FIX = resolve(HERE, "..", "fixtures", "pdf-facts");
|
|
18
|
-
const { readPdf } = await import(
|
|
19
|
-
const { classifyLastPage } = await import(
|
|
19
|
+
const { readPdf } = await import("./pdf-facts.ts");
|
|
20
|
+
const { classifyLastPage } = await import("./pdf-geometry.ts");
|
|
20
21
|
|
|
21
22
|
const check = createChecker();
|
|
22
23
|
|
|
23
|
-
const read = async (name) => {
|
|
24
|
+
const read = async (name: string): Promise<PdfFacts> => {
|
|
24
25
|
const r = await readPdf(join(FIX, name));
|
|
25
26
|
assert.ok(r.ok, `${name} must read: ${JSON.stringify(r)}`);
|
|
26
27
|
return r.facts;
|
|
27
28
|
};
|
|
28
|
-
const kinds = (facts) =>
|
|
29
|
+
const kinds = (facts: PdfFacts): string[] =>
|
|
29
30
|
facts.fonts.kind === "drawn" ? facts.fonts.list.map((f) => f.kind) : [];
|
|
30
|
-
const named = (facts, name) =>
|
|
31
|
+
const named = (facts: PdfFacts, name: string) =>
|
|
31
32
|
facts.fonts.kind === "drawn"
|
|
32
33
|
? facts.fonts.list.find((f) => f.name === name)
|
|
33
34
|
: undefined;
|
|
@@ -139,7 +140,7 @@ try {
|
|
|
139
140
|
const boxes = f.layout.flatMap((p, i) =>
|
|
140
141
|
p.boxes.map((b) => ({ ...b, page: i + 1 })),
|
|
141
142
|
);
|
|
142
|
-
const chars = (pred) =>
|
|
143
|
+
const chars = (pred: (b: (typeof boxes)[number]) => boolean) =>
|
|
143
144
|
boxes.filter(pred).reduce((sum, b) => sum + b.text.length, 0);
|
|
144
145
|
check(
|
|
145
146
|
"hidden-text: one layout per page, each US letter",
|
package/src/pdf-facts.test.ts
CHANGED
|
@@ -8,6 +8,7 @@ import assert from "node:assert/strict";
|
|
|
8
8
|
import { getResolvedPDFJS } from "unpdf";
|
|
9
9
|
import { test } from "vitest";
|
|
10
10
|
import { factsOf, failureOf, rawFontOf } from "./pdf-facts.ts";
|
|
11
|
+
import { present } from "../test/support.ts";
|
|
11
12
|
|
|
12
13
|
const lib = await getResolvedPDFJS();
|
|
13
14
|
const H = 792;
|
|
@@ -23,8 +24,8 @@ interface FakeItem {
|
|
|
23
24
|
function page(items: FakeItem[], fonts: Record<string, unknown>) {
|
|
24
25
|
const objs = new Map(Object.entries(fonts));
|
|
25
26
|
return {
|
|
26
|
-
getOperatorList:
|
|
27
|
-
getTextContent:
|
|
27
|
+
getOperatorList: () => Promise.resolve({ fnArray: [], argsArray: [] }),
|
|
28
|
+
getTextContent: () => Promise.resolve({ items }),
|
|
28
29
|
// Iterable like pdf.js's `commonObjs`, with its `has`/`get`.
|
|
29
30
|
commonObjs: {
|
|
30
31
|
has: (id: string) => objs.has(id),
|
|
@@ -39,11 +40,11 @@ function page(items: FakeItem[], fonts: Record<string, unknown>) {
|
|
|
39
40
|
}),
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
const doc = (pages: ReturnType<typeof page>[]) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
const doc = (pages: ReturnType<typeof page>[]) => ({
|
|
44
|
+
numPages: pages.length,
|
|
45
|
+
getPage: (i: number) =>
|
|
46
|
+
Promise.resolve(present(pages[i - 1], `page ${String(i)}`)),
|
|
47
|
+
});
|
|
47
48
|
const item = (
|
|
48
49
|
fontName: string,
|
|
49
50
|
transform: number[],
|
|
@@ -89,7 +90,7 @@ test("a document with no pages is `empty`; text drawn with no font is refused",
|
|
|
89
90
|
lib,
|
|
90
91
|
);
|
|
91
92
|
assert.equal(r.ok, false);
|
|
92
|
-
assert.equal(
|
|
93
|
+
assert.equal(r.reason, "zero-fonts-on-text");
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
@@ -117,7 +118,7 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
|
117
118
|
lib,
|
|
118
119
|
);
|
|
119
120
|
assert.equal(r.ok, true);
|
|
120
|
-
const boxes = r.
|
|
121
|
+
const boxes = present(r.facts.layout[0], "a first page").boxes;
|
|
121
122
|
assert.deepEqual(
|
|
122
123
|
boxes.map((b) => [b.font, b.size, b.fill]),
|
|
123
124
|
[
|
|
@@ -127,12 +128,10 @@ test("metrics, sizes and names fall back when pdf.js gives none", async () => {
|
|
|
127
128
|
["gone", 10, { kind: "unknown" }],
|
|
128
129
|
],
|
|
129
130
|
);
|
|
130
|
-
assert.deepEqual(
|
|
131
|
-
|
|
132
|
-
"word",
|
|
133
|
-
|
|
134
|
-
"word",
|
|
135
|
-
]);
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
r.facts.last.words.map((w) => w.text),
|
|
133
|
+
["word", "word", "word", "word"],
|
|
134
|
+
);
|
|
136
135
|
});
|
|
137
136
|
|
|
138
137
|
test("a failure that is not an Error is named `Error` with its text", () => {
|
|
@@ -142,3 +141,14 @@ test("a failure that is not an Error is named `Error` with its text", () => {
|
|
|
142
141
|
detail: "Error: boom",
|
|
143
142
|
});
|
|
144
143
|
});
|
|
144
|
+
|
|
145
|
+
test("🔴 a rejection with no value at all is a failure too, not a TypeError out of `readPdf`", () => {
|
|
146
|
+
// Guards: `readPdf` ends in `.catch(failureOf)`, and reading `.name` off `null` threw from inside it.
|
|
147
|
+
assert.deepEqual(
|
|
148
|
+
[failureOf(null), failureOf(undefined)],
|
|
149
|
+
[
|
|
150
|
+
{ ok: false, reason: "unreadable", detail: "Error: null" },
|
|
151
|
+
{ ok: false, reason: "unreadable", detail: "Error: undefined" },
|
|
152
|
+
],
|
|
153
|
+
);
|
|
154
|
+
});
|
package/src/pdf-facts.ts
CHANGED
|
@@ -34,12 +34,68 @@ import {
|
|
|
34
34
|
import type { PageLayout, TextBox } from "./domain/page-layout.ts";
|
|
35
35
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
36
36
|
import { fillsFor, isUpright } from "./adapters/pdfjs/fill.ts";
|
|
37
|
+
import { messageOf } from "./domain/text.ts";
|
|
38
|
+
import { fieldOf, isRecord, numbersOf } from "./domain/record.ts";
|
|
37
39
|
|
|
38
40
|
type PdfJs = Awaited<ReturnType<typeof import("unpdf").getResolvedPDFJS>>;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
|
|
42
|
+
/** A text item as this module reads it; pdf.js's `TextItem` is one. */
|
|
43
|
+
interface TextItem {
|
|
44
|
+
readonly str: string;
|
|
45
|
+
readonly fontName: string;
|
|
46
|
+
/** pdf.js types it `any[]`; `numbersOf` reads it. */
|
|
47
|
+
readonly transform: readonly unknown[];
|
|
48
|
+
readonly width: number;
|
|
49
|
+
readonly height: number;
|
|
50
|
+
}
|
|
51
|
+
/** What else pdf.js lists among a page's items: a marked-content boundary, with no text. */
|
|
52
|
+
interface MarkedContent {
|
|
53
|
+
readonly type: string;
|
|
54
|
+
readonly str?: undefined;
|
|
55
|
+
}
|
|
56
|
+
type Item = TextItem | MarkedContent;
|
|
57
|
+
|
|
58
|
+
/** A page's operators, as `fillsFor` walks them; pdf.js's operator list is one. */
|
|
59
|
+
interface OperatorList {
|
|
60
|
+
readonly fnArray: readonly number[];
|
|
61
|
+
readonly argsArray: readonly unknown[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A viewport as this module reads it; pdf.js's `PageViewport` is one. */
|
|
65
|
+
interface Viewport {
|
|
66
|
+
readonly width: number;
|
|
67
|
+
readonly height: number;
|
|
68
|
+
readonly transform: readonly number[];
|
|
69
|
+
convertToViewportPoint(x: number, y: number): readonly unknown[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A page as this module reads it. pdf.js's `PDFPageProxy` is one, and so is a test's fake page: the
|
|
74
|
+
* module depends on these five members, not on the rest of pdf.js's page.
|
|
75
|
+
*/
|
|
76
|
+
interface Page {
|
|
77
|
+
getOperatorList(): Promise<OperatorList>;
|
|
78
|
+
getTextContent(): Promise<{ readonly items: readonly Item[] }>;
|
|
79
|
+
/** pdf.js's `commonObjs`: loaded objects by id, iterable as `[id, object]` pairs. */
|
|
80
|
+
readonly commonObjs: {
|
|
81
|
+
has(id: string): boolean;
|
|
82
|
+
get(id: string): unknown;
|
|
83
|
+
[Symbol.iterator](): Iterator<readonly unknown[]>;
|
|
84
|
+
};
|
|
85
|
+
getViewport(params: { scale: number }): Viewport;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A document as this module reads it; pdf.js's `PDFDocumentProxy` is one. */
|
|
89
|
+
interface Doc {
|
|
90
|
+
readonly numPages: number;
|
|
91
|
+
getPage(pageNumber: number): Promise<Page>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A viewport point, which pdf.js types as `any[]`: the two numbers it holds. */
|
|
95
|
+
const pointOf = (xy: readonly unknown[]): [number, number] => [
|
|
96
|
+
Number(xy[0]),
|
|
97
|
+
Number(xy[1]),
|
|
98
|
+
];
|
|
43
99
|
|
|
44
100
|
/**
|
|
45
101
|
* What `readPdf` measured. `last` is the last page's words; `classifyLastPage` reads it. `layout` is
|
|
@@ -94,8 +150,8 @@ const fail = (reason: PdfReadFailure, detail: string): PdfRead => ({
|
|
|
94
150
|
|
|
95
151
|
/** A font object pdf.js left in `commonObjs`, read into a `RawFont`, or null when it is not one. */
|
|
96
152
|
export function rawFontOf(id: string, obj: unknown): RawFont | null {
|
|
97
|
-
if (
|
|
98
|
-
const o = obj
|
|
153
|
+
if (!isRecord(obj)) return null;
|
|
154
|
+
const o = obj;
|
|
99
155
|
if (!("loadedName" in o) && !("isType3Font" in o)) return null;
|
|
100
156
|
const str = (v: unknown): string | undefined =>
|
|
101
157
|
typeof v === "string" ? v : undefined;
|
|
@@ -110,7 +166,7 @@ export function rawFontOf(id: string, obj: unknown): RawFont | null {
|
|
|
110
166
|
}
|
|
111
167
|
|
|
112
168
|
const isText = (it: Item): it is TextItem =>
|
|
113
|
-
|
|
169
|
+
typeof it.str === "string" && it.str.trim() !== "";
|
|
114
170
|
|
|
115
171
|
/** The font pdf.js resolved for `id`, if it did. `get` throws on an unresolved id; `has` does not. */
|
|
116
172
|
function resolved(page: Page, id: string): unknown {
|
|
@@ -123,9 +179,10 @@ function resolved(page: Page, id: string): unknown {
|
|
|
123
179
|
*/
|
|
124
180
|
function loadedFonts(page: Page): Map<string, RawFont> {
|
|
125
181
|
const out = new Map<string, RawFont>();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
182
|
+
// pdf.js yields `[id, object]` pairs, typed `any[]`.
|
|
183
|
+
for (const entry of page.commonObjs) {
|
|
184
|
+
const f = rawFontOf(String(entry[0]), entry[1]);
|
|
185
|
+
if (f) out.set(f.id, f);
|
|
129
186
|
}
|
|
130
187
|
return out;
|
|
131
188
|
}
|
|
@@ -166,7 +223,7 @@ async function readPage(
|
|
|
166
223
|
* Both are far below any balance tolerance, so the simpler rule stays.
|
|
167
224
|
*/
|
|
168
225
|
function metricsOf(font: unknown): { ascent: number; descent: number } {
|
|
169
|
-
const f = (font
|
|
226
|
+
const f = isRecord(font) ? font : {};
|
|
170
227
|
const ascent = f["ascent"];
|
|
171
228
|
if (typeof ascent !== "number" || !(ascent > 0))
|
|
172
229
|
return { ascent: DEFAULT_ASCENT, descent: DEFAULT_DESCENT };
|
|
@@ -178,8 +235,8 @@ function metricsOf(font: unknown): { ascent: number; descent: number } {
|
|
|
178
235
|
function pageText(page: Page, items: readonly TextItem[]): PageText {
|
|
179
236
|
const vp = page.getViewport({ scale: 1 });
|
|
180
237
|
const runs: TextRun[] = items.map((it) => {
|
|
181
|
-
const [a = 0, b = 0, , d = 0, e = 0, f = 0] = it.transform
|
|
182
|
-
const [x, baseline] = vp.convertToViewportPoint(e, f)
|
|
238
|
+
const [a = 0, b = 0, , d = 0, e = 0, f = 0] = numbersOf(it.transform);
|
|
239
|
+
const [x, baseline] = pointOf(vp.convertToViewportPoint(e, f));
|
|
183
240
|
const size = Math.hypot(a, b) || it.height || Math.abs(d);
|
|
184
241
|
return {
|
|
185
242
|
text: it.str,
|
|
@@ -199,7 +256,7 @@ function pageText(page: Page, items: readonly TextItem[]): PageText {
|
|
|
199
256
|
|
|
200
257
|
/** The font name pdf.js resolved for an item, else its internal id. */
|
|
201
258
|
function fontNameOf(font: unknown, id: string): string {
|
|
202
|
-
const name = (font
|
|
259
|
+
const name = fieldOf(font, "name");
|
|
203
260
|
return typeof name === "string" && name ? name : id;
|
|
204
261
|
}
|
|
205
262
|
|
|
@@ -210,12 +267,12 @@ function fontNameOf(font: unknown, id: string): string {
|
|
|
210
267
|
*/
|
|
211
268
|
function boxOf(
|
|
212
269
|
page: Page,
|
|
213
|
-
vp:
|
|
270
|
+
vp: Viewport,
|
|
214
271
|
it: TextItem,
|
|
215
272
|
lib: PdfJs,
|
|
216
273
|
): Omit<TextBox, "fill"> {
|
|
217
|
-
const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0] = it.transform
|
|
218
|
-
const [x, baseline] = vp.convertToViewportPoint(e, f)
|
|
274
|
+
const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0] = numbersOf(it.transform);
|
|
275
|
+
const [x, baseline] = pointOf(vp.convertToViewportPoint(e, f));
|
|
219
276
|
const size = Math.hypot(c, d) || Math.hypot(a, b) || it.height;
|
|
220
277
|
const font = resolved(page, it.fontName);
|
|
221
278
|
const m = metricsOf(font);
|
|
@@ -228,7 +285,7 @@ function boxOf(
|
|
|
228
285
|
font: fontNameOf(font, it.fontName),
|
|
229
286
|
text: it.str,
|
|
230
287
|
upright: isUpright(
|
|
231
|
-
lib.Util.transform(vp.transform, it.transform)
|
|
288
|
+
numbersOf(lib.Util.transform(vp.transform, it.transform)),
|
|
232
289
|
),
|
|
233
290
|
};
|
|
234
291
|
}
|
|
@@ -237,7 +294,7 @@ function boxOf(
|
|
|
237
294
|
function layoutOf(
|
|
238
295
|
page: Page,
|
|
239
296
|
items: readonly TextItem[],
|
|
240
|
-
ops:
|
|
297
|
+
ops: OperatorList,
|
|
241
298
|
lib: PdfJs,
|
|
242
299
|
): PageLayout {
|
|
243
300
|
const vp = page.getViewport({ scale: 1 });
|
|
@@ -289,10 +346,11 @@ export async function factsOf(doc: Doc, lib: PdfJs): Promise<PdfRead> {
|
|
|
289
346
|
|
|
290
347
|
/** pdf.js's own exception names for the two conditions a caller can act on. */
|
|
291
348
|
export function failureOf(e: unknown): PdfRead {
|
|
292
|
-
const
|
|
293
|
-
const
|
|
349
|
+
const name = fieldOf(e, "name");
|
|
350
|
+
const message = fieldOf(e, "message");
|
|
351
|
+
const detail = `${typeof name === "string" ? name : "Error"}: ${typeof message === "string" ? message : String(e)}`;
|
|
294
352
|
return fail(
|
|
295
|
-
|
|
353
|
+
name === "PasswordException" ? "encrypted" : "unreadable",
|
|
296
354
|
detail,
|
|
297
355
|
);
|
|
298
356
|
}
|
|
@@ -303,7 +361,7 @@ export const readPdf: PdfReader = async (path) => {
|
|
|
303
361
|
try {
|
|
304
362
|
data = new Uint8Array(readFileSync(path));
|
|
305
363
|
} catch (e) {
|
|
306
|
-
return fail("unreadable", (e
|
|
364
|
+
return fail("unreadable", messageOf(e));
|
|
307
365
|
}
|
|
308
366
|
const lib = await loadPdfJs();
|
|
309
367
|
const task = lib.getDocument({ data, ...PDFJS_OPTIONS });
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* on 2026-08-29 (the midpoint cut, the review build, years mistaken for line numbers), ported from
|
|
7
7
|
* the harness of the function this replaces.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { createChecker } from "../lib/check.mjs";
|
|
9
|
+
import { createChecker } from "../lib/check.ts";
|
|
10
|
+
import type { RawFont } from "./pdf-geometry.ts";
|
|
12
11
|
|
|
13
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
12
|
const {
|
|
15
13
|
classifyLastPage,
|
|
16
14
|
wordsOf,
|
|
@@ -19,14 +17,23 @@ const {
|
|
|
19
17
|
programOf,
|
|
20
18
|
popplerType,
|
|
21
19
|
STUB_WORDS,
|
|
22
|
-
} = await import(
|
|
20
|
+
} = await import("./pdf-geometry.ts");
|
|
23
21
|
|
|
24
22
|
const check = createChecker();
|
|
25
23
|
|
|
24
|
+
/** A row of stacked words: `n` of them from `top` downwards, `step` apart. */
|
|
25
|
+
interface Row {
|
|
26
|
+
x: number;
|
|
27
|
+
top: number;
|
|
28
|
+
n?: number;
|
|
29
|
+
step?: number;
|
|
30
|
+
text?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
const W = 612; // US letter, points
|
|
27
34
|
|
|
28
35
|
/** Words from rows `{x, top, n, step, text}`: `n` words stacked downwards, each 10 pt tall. */
|
|
29
|
-
const words = (rows) =>
|
|
36
|
+
const words = (rows: readonly Row[]) =>
|
|
30
37
|
rows.flatMap(({ x, top, n: count = 1, step = 10, text = "word" }) =>
|
|
31
38
|
Array.from({ length: count }, (_, i) => ({
|
|
32
39
|
x0: x,
|
|
@@ -36,7 +43,7 @@ const words = (rows) =>
|
|
|
36
43
|
text,
|
|
37
44
|
})),
|
|
38
45
|
);
|
|
39
|
-
const page = (rows, widthPt = W) => ({
|
|
46
|
+
const page = (rows: readonly Row[], widthPt = W) => ({
|
|
40
47
|
widthPt,
|
|
41
48
|
heightPt: 792,
|
|
42
49
|
words: words(rows),
|
|
@@ -164,19 +171,19 @@ check(
|
|
|
164
171
|
check(
|
|
165
172
|
"a run splits at whitespace into words with proportional x",
|
|
166
173
|
ws.length === 2 &&
|
|
167
|
-
ws[0]
|
|
174
|
+
ws[0]?.text === "ab" &&
|
|
168
175
|
ws[0].x0 === 100 &&
|
|
169
176
|
ws[0].x1 === 120 &&
|
|
170
|
-
ws[1]
|
|
177
|
+
ws[1]?.x0 === 140,
|
|
171
178
|
);
|
|
172
179
|
check(
|
|
173
180
|
"a word spans ascent above the baseline to descent below it",
|
|
174
|
-
ws[0]
|
|
181
|
+
ws[0]?.y0 === 192 && ws[0].y1 === 202,
|
|
175
182
|
);
|
|
176
183
|
}
|
|
177
184
|
|
|
178
185
|
// ── fonts ───────────────────────────────────────────────────────────────────────────────
|
|
179
|
-
const font = (over) => ({
|
|
186
|
+
const font = (over: Partial<RawFont>): RawFont => ({
|
|
180
187
|
id: "f1",
|
|
181
188
|
name: "ABCDEF+LinLibertineT",
|
|
182
189
|
type: "Type1",
|
|
@@ -227,7 +234,7 @@ check(
|
|
|
227
234
|
]);
|
|
228
235
|
check(
|
|
229
236
|
"fonts: each font once, in the order pages first drew them",
|
|
230
|
-
r.kind === "drawn" && r.list.length === 2 && r.list[1]
|
|
237
|
+
r.kind === "drawn" && r.list.length === 2 && r.list[1]?.name === "B",
|
|
231
238
|
);
|
|
232
239
|
}
|
|
233
240
|
{
|
package/src/pdf-geometry.ts
CHANGED
|
@@ -89,7 +89,9 @@ function isLineNumber(w: Word, widthPt: number): boolean {
|
|
|
89
89
|
*/
|
|
90
90
|
export function classifyLastPage(p: PageText): LastPage {
|
|
91
91
|
if (!(p.widthPt > 0))
|
|
92
|
-
throw new RangeError(
|
|
92
|
+
throw new RangeError(
|
|
93
|
+
`page width must be positive, got ${String(p.widthPt)}`,
|
|
94
|
+
);
|
|
93
95
|
if (p.words.length < STUB_WORDS)
|
|
94
96
|
return { kind: "stub", words: p.words.length };
|
|
95
97
|
const lineNumbers = p.words.filter((w) => isLineNumber(w, p.widthPt)).length;
|
|
@@ -158,6 +160,10 @@ export type FontProgram =
|
|
|
158
160
|
/** A program pdf.js named but this module does not know — kept verbatim, never guessed. */
|
|
159
161
|
export type UnknownProgram = `unknown:${string}`;
|
|
160
162
|
|
|
163
|
+
const isUnknownProgram = (
|
|
164
|
+
p: FontProgram | UnknownProgram,
|
|
165
|
+
): p is UnknownProgram => p.startsWith("unknown:");
|
|
166
|
+
|
|
161
167
|
/**
|
|
162
168
|
* One font the PDF draws text with.
|
|
163
169
|
*
|
|
@@ -294,7 +300,5 @@ export const POPPLER_TYPE: Readonly<Record<FontProgram, string>> = {
|
|
|
294
300
|
/** The type column for any fact, the unknown ones verbatim. */
|
|
295
301
|
export function popplerType(f: FontFact): string {
|
|
296
302
|
if (f.kind === "type3") return POPPLER_TYPE.Type3;
|
|
297
|
-
return f.program.
|
|
298
|
-
? f.program
|
|
299
|
-
: POPPLER_TYPE[f.program as FontProgram];
|
|
303
|
+
return isUnknownProgram(f.program) ? f.program : POPPLER_TYPE[f.program];
|
|
300
304
|
}
|
|
@@ -2,26 +2,11 @@
|
|
|
2
2
|
* The port `paperlint toolchain` installs and checks an external program through. One port, many
|
|
3
3
|
* programs: banal implements it now, TeX Live in #76.
|
|
4
4
|
*/
|
|
5
|
-
import type { Opaque } from "ts-essentials";
|
|
6
5
|
import type { Result } from "../domain/result.ts";
|
|
7
6
|
import type { Lines } from "../domain/text.ts";
|
|
7
|
+
import type { Ready } from "../domain/ready.ts";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
* Present, verified, running. BRANDED: a literal `{ where, fresh, verified }` is not a `Ready`, so
|
|
11
|
-
* only an installer's adapter — one minting function each (banal's is `ready` in
|
|
12
|
-
* `adapters/banal/index.ts`) — can make one, and a ready line cannot be printed without it.
|
|
13
|
-
*/
|
|
14
|
-
export type Ready = Opaque<
|
|
15
|
-
{
|
|
16
|
-
/** Where it is: the program's path or its bin directory. */
|
|
17
|
-
readonly where: string;
|
|
18
|
-
/** This run installed it. */
|
|
19
|
-
readonly fresh: boolean;
|
|
20
|
-
/** What was checked before calling it ready, as a person reads it. */
|
|
21
|
-
readonly verified: string;
|
|
22
|
-
},
|
|
23
|
-
"Ready"
|
|
24
|
-
>;
|
|
9
|
+
export type { Ready };
|
|
25
10
|
|
|
26
11
|
export interface ToolInstaller {
|
|
27
12
|
/** The program as a person reads it: `banal 1.2 (HotCRP f3e4352)`. */
|
package/src/presets.ts
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
type TexRequirements,
|
|
40
40
|
type VenueFormat,
|
|
41
41
|
} from "./tex-requirements.ts";
|
|
42
|
-
import
|
|
42
|
+
import { callerPath } from "./caller-path.ts";
|
|
43
43
|
import { err, ok, type Result } from "./domain/result.ts";
|
|
44
44
|
import type { Files } from "./ports/files.ts";
|
|
45
45
|
import {
|
|
@@ -47,7 +47,8 @@ import {
|
|
|
47
47
|
type PaperSettings,
|
|
48
48
|
type SettingsProblem,
|
|
49
49
|
} from "./paper-settings.ts";
|
|
50
|
-
import { CONFIG_FILE } from "
|
|
50
|
+
import { CONFIG_FILE } from "#lib/paper-config";
|
|
51
|
+
import { messageOf } from "./domain/text.ts";
|
|
51
52
|
|
|
52
53
|
/** The prefix of a shipped preset's spec. */
|
|
53
54
|
export const SHIPPED_PREFIX = "paperlint:";
|
|
@@ -119,7 +120,7 @@ function readPreset(
|
|
|
119
120
|
): Result<{ file: string; preset: PresetFile }, PresetProblem> {
|
|
120
121
|
const where = fileOf(spec, from, deps.venuesDir);
|
|
121
122
|
if (!where.ok) return where;
|
|
122
|
-
const bytes = deps.files.readBytes(where.value
|
|
123
|
+
const bytes = deps.files.readBytes(callerPath(where.value));
|
|
123
124
|
if (bytes === null)
|
|
124
125
|
return err({ kind: "not-found", spec, file: where.value, shipped: [] });
|
|
125
126
|
try {
|
|
@@ -132,7 +133,7 @@ function readPreset(
|
|
|
132
133
|
return err({
|
|
133
134
|
kind: "broken",
|
|
134
135
|
file: where.value,
|
|
135
|
-
why: (e
|
|
136
|
+
why: messageOf(e).split("\n").join(" "),
|
|
136
137
|
});
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -165,14 +166,22 @@ export function mergeFormat(
|
|
|
165
166
|
parent: VenueFormat,
|
|
166
167
|
child: VenueFormat,
|
|
167
168
|
): VenueFormat {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
// Every field listed: a VenueFormat literal missing one is a compile error.
|
|
170
|
+
const pick = <K extends Exclude<keyof VenueFormat, "kinds">>(
|
|
171
|
+
k: K,
|
|
172
|
+
): VenueFormat[K] => child[k] ?? parent[k];
|
|
173
|
+
return {
|
|
174
|
+
pageWidthIn: pick("pageWidthIn"),
|
|
175
|
+
pageHeightIn: pick("pageHeightIn"),
|
|
176
|
+
columns: pick("columns"),
|
|
177
|
+
bodyPt: pick("bodyPt"),
|
|
178
|
+
bodyPtTol: pick("bodyPtTol"),
|
|
179
|
+
refPtMin: pick("refPtMin"),
|
|
180
|
+
refPtMax: pick("refPtMax"),
|
|
181
|
+
fontsText: pick("fontsText"),
|
|
182
|
+
fontsTitle: pick("fontsTitle"),
|
|
183
|
+
kinds: new Map([...parent.kinds, ...child.kinds]),
|
|
184
|
+
};
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
/** The chain, root first, merged into one preset. Pure. */
|
|
@@ -181,12 +190,18 @@ function merged(
|
|
|
181
190
|
rootFirst: readonly PresetFile[],
|
|
182
191
|
files: readonly string[],
|
|
183
192
|
): Preset {
|
|
184
|
-
const base
|
|
185
|
-
name:
|
|
186
|
-
template:
|
|
193
|
+
const base: {
|
|
194
|
+
name: string | null;
|
|
195
|
+
template: string | null;
|
|
196
|
+
tex: TexRequirements;
|
|
197
|
+
format: VenueFormat;
|
|
198
|
+
rules: Readonly<Record<string, unknown>>;
|
|
199
|
+
} = {
|
|
200
|
+
name: null,
|
|
201
|
+
template: null,
|
|
187
202
|
tex: NO_REQUIREMENTS,
|
|
188
203
|
format: NO_FORMAT,
|
|
189
|
-
rules: {}
|
|
204
|
+
rules: {},
|
|
190
205
|
};
|
|
191
206
|
const m = rootFirst.reduce(
|
|
192
207
|
(acc, p) => ({
|