paperlint 3.1.3 → 3.2.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 +3 -3
- package/CONTRIBUTING.md +34 -5
- package/dist/adapters/banal/index.d.ts.map +1 -1
- package/dist/adapters/banal/index.js +9 -5
- package/dist/adapters/banal/index.js.map +1 -1
- package/dist/adapters/banal/invocation.d.ts +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 +0 -5
- 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/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 +4 -5
- 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.js +2 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +14 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -34
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +3 -2
- 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/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.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 +2 -1
- 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 +14 -9
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +5 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +15 -7
- 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 +142 -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 +2 -2
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +5 -4
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +2 -1
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -2
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +11 -6
- package/dist/references.js.map +1 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +6 -3
- 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.map +1 -1
- package/dist/tex-requirements.js +8 -3
- 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/venue-rules.js +1 -1
- package/dist/venue-rules.js.map +1 -1
- 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} +4 -3
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +16 -8
- 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} +34 -15
- 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} +74 -29
- package/eslint-rules/paper-typography.test.mjs +3 -3
- package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +132 -54
- 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} +88 -31
- package/eslint-rules/review-frontmatter.test.mjs +2 -2
- package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +37 -15
- 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} +19 -6
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/{tex-build.mjs → tex-build.ts} +4 -3
- package/eslint.config.mjs +139 -30
- 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.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 +8 -8
- package/lib/{paper-config.mjs → paper-config.ts} +19 -14
- package/lib/skill-checks.mjs +1 -1
- package/lib/skill-checks.test.mjs +1 -1
- package/lib/skill-corpus.mjs +1 -1
- package/lib/skill-eval-kit.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 -37
- package/scripts/eslint-report-guard.test.mjs +1 -1
- package/scripts/harness-api.frozen.json +14 -15
- 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/grade-paper-writing/prose-lint.mjs +1 -1
- package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +2 -2
- 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 +1 -1
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- package/skills/paper-pipeline/scripts/README.md +1 -1
- package/skills/paper-pipeline/scripts/announce.test.mjs +1 -1
- 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.mjs +1 -5
- 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/fixtures/{scorecard.mjs → scorecard.ts} +36 -7
- package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +1 -4
- 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-pipeline/scripts/run-mechanical.test.mjs +1 -1
- package/skills/paper-pipeline/scripts/status.test.mjs +1 -1
- 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/{extract-pdf-facts.harness.mjs → extract-pdf-facts.harness.ts} +34 -9
- package/skills/render-paper/render-paper.harness.mjs +2 -2
- package/skills/tighten-paper/structure.mjs +1 -1
- package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
- 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/index.test.ts +3 -6
- package/src/adapters/banal/index.ts +7 -4
- package/src/adapters/banal/invocation.test.ts +3 -1
- package/src/adapters/banal/invocation.ts +1 -1
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +3 -3
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/curl/download.test.ts +4 -2
- package/src/adapters/node/files.io.ts +2 -2
- package/src/adapters/node/files.test.ts +3 -1
- package/src/adapters/node/process.io.ts +4 -3
- package/src/adapters/node/process.test.ts +1 -1
- 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 +26 -58
- 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 +2 -2
- package/src/build-engine.ts +2 -2
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +15 -14
- package/src/build.ts +28 -19
- package/src/cli.harness.mjs +6 -6
- package/src/cli.test.ts +13 -6
- package/src/cli.ts +39 -37
- package/src/doctor.harness.mjs +3 -3
- package/src/doctor.ts +3 -2
- package/src/domain/lookup-cache.ts +21 -11
- package/src/domain/result.test.ts +2 -2
- 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 +4 -3
- package/src/facts-file.test.ts +9 -7
- package/src/facts-file.ts +2 -1
- package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +39 -20
- package/src/hooks-settings.ts +20 -9
- package/src/init.test.ts +12 -9
- package/src/init.ts +22 -12
- 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 +2 -2
- package/src/new-paper.test.ts +8 -10
- package/src/new-paper.ts +2 -2
- package/src/paper-files.test.ts +3 -5
- package/src/paper-settings-commands.test.ts +5 -4
- package/src/paper-settings.test.ts +5 -5
- package/src/paper-settings.ts +8 -9
- package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
- package/src/pdf-facts.test.ts +10 -11
- package/src/pdf-facts.ts +2 -1
- package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
- package/src/pdf-geometry.ts +3 -1
- package/src/presets.ts +3 -2
- package/src/references.test.ts +51 -43
- package/src/references.ts +14 -7
- package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
- package/src/rules-config.ts +7 -4
- 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.ts +8 -5
- package/src/toolchain.harness.mjs +2 -2
- package/src/toolchain.test.ts +1 -1
- package/src/toolchain.ts +31 -17
- package/src/venue-rules.test.ts +62 -64
- package/src/venue-rules.ts +1 -1
- 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/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/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +0 -0
- /package/skills/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
|
@@ -6,30 +6,43 @@
|
|
|
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.
|
|
9
|
+
* `test/e2e/build.ts`.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
11
|
+
import { createChecker } from "../lib/check.ts";
|
|
12
|
+
import type {
|
|
13
|
+
BibInput,
|
|
14
|
+
Hashes,
|
|
15
|
+
Observation,
|
|
16
|
+
State,
|
|
17
|
+
Step,
|
|
18
|
+
} from "./latex-loop.ts";
|
|
14
19
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
join(HERE, "latex-loop.ts")
|
|
18
|
-
);
|
|
20
|
+
const { nextStep, summarize, changedFiles, MAX_PASSES } =
|
|
21
|
+
await import("./latex-loop.ts");
|
|
19
22
|
|
|
20
23
|
const check = createChecker();
|
|
21
24
|
|
|
22
25
|
// ── builders ────────────────────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
+
type LatexPass = Extract<Observation, { step: "latex" }>;
|
|
27
|
+
type BibtexPass = Extract<Observation, { step: "bibtex" }>;
|
|
28
|
+
const H = (
|
|
29
|
+
aux: string | null,
|
|
30
|
+
toc: string | null = null,
|
|
31
|
+
out: string | null = null,
|
|
32
|
+
bbl: string | null = null,
|
|
33
|
+
): Hashes => ({ aux, toc, out, bbl });
|
|
34
|
+
const NO_BIB: BibInput = { kind: "none" };
|
|
35
|
+
const bib = (
|
|
36
|
+
citations: readonly string[],
|
|
37
|
+
bibHash: string | null = "b1",
|
|
38
|
+
): BibInput => ({
|
|
26
39
|
kind: "needed",
|
|
27
40
|
citations,
|
|
28
41
|
databases: ["refs"],
|
|
29
42
|
style: "plain",
|
|
30
43
|
bibHash,
|
|
31
44
|
});
|
|
32
|
-
const latex = (o = {}) => ({
|
|
45
|
+
const latex = (o: Partial<LatexPass> = {}): LatexPass => ({
|
|
33
46
|
step: "latex",
|
|
34
47
|
final: false,
|
|
35
48
|
exitCode: 0,
|
|
@@ -40,7 +53,7 @@ const latex = (o = {}) => ({
|
|
|
40
53
|
errorLines: [],
|
|
41
54
|
...o,
|
|
42
55
|
});
|
|
43
|
-
const bibtex = (o = {}) => ({
|
|
56
|
+
const bibtex = (o: Partial<BibtexPass> = {}): BibtexPass => ({
|
|
44
57
|
step: "bibtex",
|
|
45
58
|
exitCode: 0,
|
|
46
59
|
before: H("a1"),
|
|
@@ -49,11 +62,11 @@ const bibtex = (o = {}) => ({
|
|
|
49
62
|
errorLines: [],
|
|
50
63
|
...o,
|
|
51
64
|
});
|
|
52
|
-
const kind = (s) =>
|
|
65
|
+
const kind = (s: Step) =>
|
|
53
66
|
s.kind === "latex" ? `latex${s.final ? "-final" : ""}` : s.kind;
|
|
54
67
|
|
|
55
68
|
// ── 1. nextStep over a State ─────────────────────────────────────────────────────────────
|
|
56
|
-
const S = (o = {}) => ({
|
|
69
|
+
const S = (o: Partial<State> = {}): State => ({
|
|
57
70
|
failed: null,
|
|
58
71
|
bibOutdated: false,
|
|
59
72
|
unsettled: [],
|
|
@@ -62,12 +75,12 @@ const S = (o = {}) => ({
|
|
|
62
75
|
warnings: [],
|
|
63
76
|
...o,
|
|
64
77
|
});
|
|
65
|
-
const exitFail = {
|
|
78
|
+
const exitFail: NonNullable<State["failed"]> = {
|
|
66
79
|
step: "bibtex",
|
|
67
80
|
cause: { kind: "exit", code: 2 },
|
|
68
81
|
lines: ["I couldn't open database file nope.bib"],
|
|
69
82
|
};
|
|
70
|
-
const STATES = [
|
|
83
|
+
const STATES: [string, State, string][] = [
|
|
71
84
|
// Guards: the only moment paper-guards can speak — its error comes on the final pass, so testing
|
|
72
85
|
// 'final' before 'exit code' would ship the undefined reference green.
|
|
73
86
|
[
|
|
@@ -96,7 +109,7 @@ const STATES = [
|
|
|
96
109
|
// Guards: the stop on a document that never settles — without it the loop runs forever; with a
|
|
97
110
|
// silent cap it ships a PDF with stale cross-references.
|
|
98
111
|
[
|
|
99
|
-
`Q3 cap: ${MAX_PASSES} passes and still unsettled — fail`,
|
|
112
|
+
`Q3 cap: ${String(MAX_PASSES)} passes and still unsettled — fail`,
|
|
100
113
|
S({ unsettled: ["paper.aux"], latexPasses: MAX_PASSES }),
|
|
101
114
|
"fail",
|
|
102
115
|
],
|
|
@@ -115,20 +128,29 @@ for (const [label, state, expected] of STATES) {
|
|
|
115
128
|
const q1 = nextStep(S({ failed: exitFail }));
|
|
116
129
|
check(
|
|
117
130
|
"Q1 passes the failure through: the step, the cause and the lines verbatim",
|
|
118
|
-
q1.
|
|
131
|
+
q1.kind === "fail" &&
|
|
132
|
+
q1.step === "bibtex" &&
|
|
133
|
+
q1.cause.kind === "exit" &&
|
|
134
|
+
q1.cause.code === 2 &&
|
|
135
|
+
q1.lines === exitFail.lines,
|
|
119
136
|
);
|
|
120
137
|
const q3cap = nextStep(
|
|
121
138
|
S({ unsettled: ["paper.aux", "labels-changed"], latexPasses: MAX_PASSES }),
|
|
122
139
|
);
|
|
123
140
|
check(
|
|
124
141
|
"the cap's failure is no-convergence and NAMES what stayed unsettled",
|
|
125
|
-
q3cap.
|
|
126
|
-
q3cap.
|
|
142
|
+
q3cap.kind === "fail" &&
|
|
143
|
+
q3cap.cause.kind === "no-convergence" &&
|
|
144
|
+
(q3cap.lines[0] ?? "").includes("paper.aux, labels-changed"),
|
|
127
145
|
);
|
|
128
146
|
check(
|
|
129
147
|
"done carries the state's warnings",
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
(() => {
|
|
149
|
+
const done = nextStep(
|
|
150
|
+
S({ finalDone: true, warnings: ["undefined-citations"] }),
|
|
151
|
+
);
|
|
152
|
+
return done.kind === "done" && done.warnings[0] === "undefined-citations";
|
|
153
|
+
})(),
|
|
132
154
|
);
|
|
133
155
|
|
|
134
156
|
// ── 2. summarize over histories ─────────────────────────────────────────────────────────
|
|
@@ -152,7 +174,10 @@ check(
|
|
|
152
174
|
(() => {
|
|
153
175
|
const f = sum([latex({ exitCode: 1, errorLines: ["l.4 \\foo"] })]).failed;
|
|
154
176
|
return (
|
|
155
|
-
f?.step === "latex" &&
|
|
177
|
+
f?.step === "latex" &&
|
|
178
|
+
f.cause.kind === "exit" &&
|
|
179
|
+
f.cause.code === 1 &&
|
|
180
|
+
f.lines[0] === "l.4 \\foo"
|
|
156
181
|
);
|
|
157
182
|
})(),
|
|
158
183
|
);
|
|
@@ -162,22 +187,22 @@ check(
|
|
|
162
187
|
);
|
|
163
188
|
check(
|
|
164
189
|
"summarize: bibOutdated when the aux names a bibliography bibtex has not run on",
|
|
165
|
-
sum([latex({ bib: bib(["k"]) })]).bibOutdated
|
|
190
|
+
sum([latex({ bib: bib(["k"]) })]).bibOutdated,
|
|
166
191
|
);
|
|
167
192
|
check(
|
|
168
193
|
"summarize: bibOutdated is false once bibtex ran on exactly that input",
|
|
169
|
-
sum([latex({ bib: bib(["k"]) }), bibtex()]).bibOutdated
|
|
194
|
+
!sum([latex({ bib: bib(["k"]) }), bibtex()]).bibOutdated,
|
|
170
195
|
);
|
|
171
196
|
// Guards: the .bib hash as an input — a corrected author or year in refs.bib would keep the old
|
|
172
197
|
// .bbl, and the PDF would print the entry as it was before the fix.
|
|
173
198
|
check(
|
|
174
199
|
"summarize: the .bib content changed since bibtex ran — bibOutdated",
|
|
175
200
|
sum([latex({ bib: bib(["k"]) }), bibtex(), latex({ bib: bib(["k"], "b2") })])
|
|
176
|
-
.bibOutdated
|
|
201
|
+
.bibOutdated,
|
|
177
202
|
);
|
|
178
203
|
check(
|
|
179
204
|
"summarize: no bibliography at all — never bibOutdated",
|
|
180
|
-
sum([latex()]).bibOutdated
|
|
205
|
+
!sum([latex()]).bibOutdated,
|
|
181
206
|
);
|
|
182
207
|
check(
|
|
183
208
|
"summarize: unsettled names every tracked file the last pass changed",
|
|
@@ -232,10 +257,10 @@ check(
|
|
|
232
257
|
);
|
|
233
258
|
|
|
234
259
|
// ── 3. composed: nextStep(summarize(history)) ───────────────────────────────────────────
|
|
235
|
-
const step = (history) => nextStep(summarize(history));
|
|
260
|
+
const step = (history: readonly Observation[]) => nextStep(summarize(history));
|
|
236
261
|
|
|
237
262
|
// ── the histories the loop sees ───────────────────────────────────────────────────────────────────────────
|
|
238
|
-
const TABLE = [
|
|
263
|
+
const TABLE: [string, Observation[], string][] = [
|
|
239
264
|
["nothing ran yet — the first step is a plain pass", [], "latex"],
|
|
240
265
|
[
|
|
241
266
|
"a pass that created the aux — rerun",
|
|
@@ -364,7 +389,8 @@ const latexFail = step([
|
|
|
364
389
|
]);
|
|
365
390
|
check(
|
|
366
391
|
"a latex fail names the step, the exit code and the log excerpt verbatim",
|
|
367
|
-
latexFail.
|
|
392
|
+
latexFail.kind === "fail" &&
|
|
393
|
+
latexFail.step === "latex" &&
|
|
368
394
|
latexFail.cause.kind === "exit" &&
|
|
369
395
|
latexFail.cause.code === 1 &&
|
|
370
396
|
latexFail.lines[1] === "l.4 \\foo",
|
|
@@ -380,14 +406,16 @@ check(
|
|
|
380
406
|
"a bibtex fail names BIBTEX, not latex",
|
|
381
407
|
bibFail.kind === "fail" &&
|
|
382
408
|
bibFail.step === "bibtex" &&
|
|
409
|
+
bibFail.cause.kind === "exit" &&
|
|
383
410
|
bibFail.cause.code === 2,
|
|
384
411
|
);
|
|
385
412
|
|
|
386
413
|
// ── the cap ─────────────────────────────────────────────────────────────────────────────
|
|
387
|
-
const moving = (i
|
|
414
|
+
const moving = (i: number) =>
|
|
415
|
+
latex({ before: H(`a${String(i)}`), after: H(`a${String(i + 1)}`) });
|
|
388
416
|
const underCap = Array.from({ length: MAX_PASSES - 1 }, (_, i) => moving(i));
|
|
389
417
|
check(
|
|
390
|
-
`the cap is ${MAX_PASSES} and it is not hit early: after ${MAX_PASSES - 1} moving passes, one more`,
|
|
418
|
+
`the cap is ${String(MAX_PASSES)} and it is not hit early: after ${String(MAX_PASSES - 1)} moving passes, one more`,
|
|
391
419
|
MAX_PASSES === 5 && kind(step(underCap)) === "latex",
|
|
392
420
|
);
|
|
393
421
|
const atCap = Array.from({ length: MAX_PASSES }, (_, i) => moving(i));
|
|
@@ -398,7 +426,7 @@ check(
|
|
|
398
426
|
);
|
|
399
427
|
check(
|
|
400
428
|
"the non-convergence failure NAMES the file that kept changing",
|
|
401
|
-
capped.kind === "fail" && capped.lines[0].includes("paper.aux"),
|
|
429
|
+
capped.kind === "fail" && (capped.lines[0] ?? "").includes("paper.aux"),
|
|
402
430
|
);
|
|
403
431
|
const cappedMarker = step(
|
|
404
432
|
Array.from({ length: MAX_PASSES }, () =>
|
|
@@ -408,7 +436,7 @@ const cappedMarker = step(
|
|
|
408
436
|
check(
|
|
409
437
|
"a log that keeps asking for a rerun hits the same cap, and names the marker",
|
|
410
438
|
cappedMarker.kind === "fail" &&
|
|
411
|
-
cappedMarker.lines[0].includes("labels-changed"),
|
|
439
|
+
(cappedMarker.lines[0] ?? "").includes("labels-changed"),
|
|
412
440
|
);
|
|
413
441
|
const convergedAtCap = [...atCap.slice(0, -1), latex()];
|
|
414
442
|
check(
|
|
@@ -446,5 +474,5 @@ check(
|
|
|
446
474
|
);
|
|
447
475
|
|
|
448
476
|
console.log(
|
|
449
|
-
`✓ ${String(check.count)} assertions passed — latex-loop: nextStep over State, summarize over histories, and the two composed, including the ${MAX_PASSES}-pass cap`,
|
|
477
|
+
`✓ ${String(check.count)} assertions passed — latex-loop: nextStep over State, summarize over histories, and the two composed, including the ${String(MAX_PASSES)}-pass cap`,
|
|
450
478
|
);
|
package/src/latex-loop.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* PURE. No disk, no process, no clock. The shell in `build.ts` runs a step, records what it saw
|
|
5
5
|
* as an `Observation`, appends it to the history and asks `nextStep(summarize(history))` again.
|
|
6
|
-
* Both halves are table tests (`latex-loop.harness.
|
|
6
|
+
* Both halves are table tests (`latex-loop.harness.ts`), not scenarios that need TeX.
|
|
7
7
|
*
|
|
8
8
|
* ── THE LOOP: `summarize` reduces the history to a `State`, and `nextStep` asks four questions ──
|
|
9
9
|
* 1. did the last program fail? → fail, with its error lines
|
|
@@ -118,7 +118,7 @@ export type Step =
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
/** Non-final pdflatex passes allowed before the build is declared non-converging. */
|
|
121
|
-
export const MAX_PASSES = 5;
|
|
121
|
+
export const MAX_PASSES: number = 5;
|
|
122
122
|
|
|
123
123
|
/** The history, reduced to the four questions `nextStep` asks. */
|
|
124
124
|
export type State = {
|
|
@@ -152,7 +152,7 @@ const noConvergence = (unsettled: readonly string[]): Step => ({
|
|
|
152
152
|
step: "latex",
|
|
153
153
|
cause: { kind: "no-convergence", unsettled },
|
|
154
154
|
lines: [
|
|
155
|
-
`the build does not converge: after ${MAX_PASSES} pdflatex passes, the last one still changed or asked to rerun: ${unsettled.join(", ")}.`,
|
|
155
|
+
`the build does not converge: after ${String(MAX_PASSES)} pdflatex passes, the last one still changed or asked to rerun: ${unsettled.join(", ")}.`,
|
|
156
156
|
`A document whose aux keeps changing on every pass never settles; stopping here instead of`,
|
|
157
157
|
`shipping a PDF with stale cross-references.`,
|
|
158
158
|
],
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* The fixture package keeps its skills where the real one does, under `SHIPPED_SKILLS_DIR` from
|
|
13
13
|
* consumer.mjs — the same constant the linker and the install e2e read.
|
|
14
14
|
*
|
|
15
|
-
* Run: node src/link-skills.harness.
|
|
15
|
+
* Run: node src/link-skills.harness.ts
|
|
16
16
|
*/
|
|
17
17
|
import {
|
|
18
18
|
existsSync,
|
|
@@ -26,17 +26,14 @@ import {
|
|
|
26
26
|
writeFileSync,
|
|
27
27
|
} from "node:fs";
|
|
28
28
|
import { tmpdir } from "node:os";
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
29
|
+
import { isAbsolute, join } from "node:path";
|
|
30
|
+
import { createChecker } from "../lib/check.ts";
|
|
31
|
+
import type { LinkReport } from "./link-skills.ts";
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
const { SHIPPED_SKILLS_DIR: SHIPS, PACKAGE_NAME: PKG } = await import(
|
|
38
|
-
join(HERE, "..", "skills", "paper-pipeline", "scripts", "consumer.mjs")
|
|
39
|
-
);
|
|
33
|
+
const { linkSkills, locatePackage, shippedSkills } =
|
|
34
|
+
await import("./link-skills.ts");
|
|
35
|
+
const { SHIPPED_SKILLS_DIR: SHIPS, PACKAGE_NAME: PKG } =
|
|
36
|
+
await import("../skills/paper-pipeline/scripts/consumer.mjs");
|
|
40
37
|
|
|
41
38
|
const check = createChecker();
|
|
42
39
|
|
|
@@ -46,7 +43,7 @@ const work = realpathSync(
|
|
|
46
43
|
);
|
|
47
44
|
|
|
48
45
|
/** A package as a manager would unpack it: manifest, skills, one non-skill. */
|
|
49
|
-
function writePackage(dir) {
|
|
46
|
+
function writePackage(dir: string): void {
|
|
50
47
|
mkdirSync(dir, { recursive: true });
|
|
51
48
|
writeFileSync(
|
|
52
49
|
join(dir, "package.json"),
|
|
@@ -63,7 +60,7 @@ function writePackage(dir) {
|
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
/** A consumer project with the package installed the npm way or the pnpm way. */
|
|
66
|
-
function consumer(name, manager = "npm") {
|
|
63
|
+
function consumer(name: string, manager = "npm"): string {
|
|
67
64
|
const dir = join(work, name);
|
|
68
65
|
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
69
66
|
writeFileSync(
|
|
@@ -89,8 +86,11 @@ function consumer(name, manager = "npm") {
|
|
|
89
86
|
return dir;
|
|
90
87
|
}
|
|
91
88
|
|
|
92
|
-
const home = (dir) => join(dir, ".claude", "skills");
|
|
93
|
-
|
|
89
|
+
const home = (dir: string) => join(dir, ".claude", "skills");
|
|
90
|
+
/** The report's links; a refusal has none. */
|
|
91
|
+
const linksOf = (report: LinkReport) => (report.ok ? report.links : []);
|
|
92
|
+
const status = (report: LinkReport, name: string) =>
|
|
93
|
+
linksOf(report).find((l) => l.name === name);
|
|
94
94
|
|
|
95
95
|
try {
|
|
96
96
|
// ── I. A FRESH npm CONSUMER: EVERY DECLARED SKILL IS LINKED, AND ONLY SKILLS ───────────
|
|
@@ -99,11 +99,11 @@ try {
|
|
|
99
99
|
const r = linkSkills(dir);
|
|
100
100
|
check(
|
|
101
101
|
"🔴 it links every skill the package ships",
|
|
102
|
-
r.ok
|
|
102
|
+
r.ok && SKILLS.every((s) => status(r, s)?.status === "created"),
|
|
103
103
|
);
|
|
104
104
|
check(
|
|
105
105
|
"and nothing that is not a skill — a directory without SKILL.md is not one",
|
|
106
|
-
r.
|
|
106
|
+
linksOf(r).length === SKILLS.length &&
|
|
107
107
|
!existsSync(join(home(dir), "shared")),
|
|
108
108
|
);
|
|
109
109
|
check(
|
|
@@ -195,7 +195,7 @@ try {
|
|
|
195
195
|
const r = linkSkills(dir);
|
|
196
196
|
check(
|
|
197
197
|
"a project that does not have the package gets an error, not links into somebody's cache",
|
|
198
|
-
r.ok
|
|
198
|
+
!r.ok && /does not resolve from/.test(r.error),
|
|
199
199
|
);
|
|
200
200
|
check("and nothing is created", !existsSync(join(dir, ".claude")));
|
|
201
201
|
}
|
|
@@ -229,5 +229,5 @@ try {
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
console.log(
|
|
232
|
-
`✓ ${check.count} assertions passed — link-skills: the skills are where Claude Code looks`,
|
|
232
|
+
`✓ ${String(check.count)} assertions passed — link-skills: the skills are where Claude Code looks`,
|
|
233
233
|
);
|
package/src/link-skills.test.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { test } from "vitest";
|
|
17
|
-
import { useTempDir, writeTree } from "../test/support.
|
|
17
|
+
import { useTempDir, writeTree } from "../test/support.ts";
|
|
18
18
|
import { linkSkills, locatePackage, type LinkFs } from "./link-skills.ts";
|
|
19
19
|
|
|
20
20
|
const root = useTempDir("paperlint-link-skills-");
|
|
@@ -62,7 +62,7 @@ test("a package without a skills directory, or one holding a dangling link, is n
|
|
|
62
62
|
symlinkSync(join(root, "gone"), join(dangling, "skills", "b"));
|
|
63
63
|
const r = linkSkills(join(root, "p2"), { locate: at(dangling) });
|
|
64
64
|
assert.equal(r.ok, false);
|
|
65
|
-
assert.match(
|
|
65
|
+
assert.match(r.error, /^1 skill link\(s\) in .* lead nowhere:/);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
test("a skills home that cannot be made is named", () => {
|
|
@@ -71,7 +71,7 @@ test("a skills home that cannot be made is named", () => {
|
|
|
71
71
|
});
|
|
72
72
|
const r = linkSkills(project, { locate: at(pkg("home", ["a"])) });
|
|
73
73
|
assert.equal(r.ok, false);
|
|
74
|
-
assert.match(
|
|
74
|
+
assert.match(r.error, /^cannot create .*\.claude\/skills: /);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test("entries that are someone else's: a plain file, one that cannot be resolved, a link that cannot be made", () => {
|
package/src/link-skills.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
PACKAGE_NAME,
|
|
45
45
|
SHIPPED_SKILLS_DIR,
|
|
46
46
|
} from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
47
|
+
import { messageOf, codeOf } from "./domain/text.ts";
|
|
47
48
|
|
|
48
49
|
export { PACKAGE_NAME };
|
|
49
50
|
/** Where Claude Code looks for project skills, relative to the project root. */
|
|
@@ -74,7 +75,7 @@ export function locatePackage(
|
|
|
74
75
|
manifest = resolveManifest(project);
|
|
75
76
|
} catch (e) {
|
|
76
77
|
return {
|
|
77
|
-
error: `${(e
|
|
78
|
+
error: `${codeOf(e) ?? "error"}: ${String(messageOf(e).split("\n")[0])}`,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
const dir = realpathSync(dirname(manifest));
|
|
@@ -108,7 +109,7 @@ export function shippedSkills(
|
|
|
108
109
|
try {
|
|
109
110
|
return { skillsDir, names: installedSkills(skillsDir) };
|
|
110
111
|
} catch (e) {
|
|
111
|
-
return { error: (e
|
|
112
|
+
return { error: messageOf(e) };
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
@@ -207,7 +208,7 @@ function linkOne(
|
|
|
207
208
|
return {
|
|
208
209
|
name,
|
|
209
210
|
status: "foreign",
|
|
210
|
-
reason: `could not create the link: ${(e
|
|
211
|
+
reason: `could not create the link: ${messageOf(e)}`,
|
|
211
212
|
};
|
|
212
213
|
}
|
|
213
214
|
}
|
|
@@ -218,7 +219,7 @@ function makeHome(home: string): string | null {
|
|
|
218
219
|
mkdirSync(home, { recursive: true });
|
|
219
220
|
return null;
|
|
220
221
|
} catch (e) {
|
|
221
|
-
return `cannot create ${home}: ${(e
|
|
222
|
+
return `cannot create ${home}: ${messageOf(e)}`;
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
|
package/src/lint-fix.test.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { tmpdir } from "node:os";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { afterEach, describe, expect, it } from "vitest";
|
|
15
15
|
import { run } from "./cli.ts";
|
|
16
|
+
import { lintReport } from "../test/lint-report.ts";
|
|
16
17
|
|
|
17
18
|
const dirs: string[] = [];
|
|
18
19
|
afterEach(() => {
|
|
@@ -50,8 +51,8 @@ async function lint(root: string, args: string[] = []) {
|
|
|
50
51
|
return { code, out: out.join("\n"), err: err.join("\n") };
|
|
51
52
|
}
|
|
52
53
|
const ruleIds = (stdout: string): string[] =>
|
|
53
|
-
(
|
|
54
|
-
r.messages.map((m) => m.ruleId),
|
|
54
|
+
lintReport(stdout).flatMap((r) =>
|
|
55
|
+
r.messages.map((m) => m.ruleId ?? "(fatal)"),
|
|
55
56
|
);
|
|
56
57
|
|
|
57
58
|
describe("paperlint lint --fix", () => {
|
|
@@ -85,10 +86,7 @@ describe("sibling cards: `read:` is required, from the package's own schema", ()
|
|
|
85
86
|
"papers/a/siblings/jones2024.md": "---\nread: abstract\n---\n# Jones\n",
|
|
86
87
|
},
|
|
87
88
|
);
|
|
88
|
-
const results =
|
|
89
|
-
filePath: string;
|
|
90
|
-
messages: { ruleId: string; severity: number }[];
|
|
91
|
-
}[];
|
|
89
|
+
const results = lintReport((await lint(root)).out);
|
|
92
90
|
const of = (name: string) =>
|
|
93
91
|
results.find((r) => r.filePath.endsWith(name))?.messages ?? null;
|
|
94
92
|
expect(of("smith2025.md")?.map((m) => [m.ruleId, m.severity])).toEqual([
|
package/src/linted-set.test.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { tmpdir } from "node:os";
|
|
|
19
19
|
import { join, relative } from "node:path";
|
|
20
20
|
import { afterEach, describe, expect, it } from "vitest";
|
|
21
21
|
import { run } from "./cli.ts";
|
|
22
|
+
import { lintReport } from "../test/lint-report.ts";
|
|
22
23
|
|
|
23
24
|
const dirs: string[] = [];
|
|
24
25
|
afterEach(() => {
|
|
@@ -91,9 +92,8 @@ async function lintedSet(root: string, args: string[]): Promise<string[]> {
|
|
|
91
92
|
log: (s: string) => out.push(s),
|
|
92
93
|
err: () => {},
|
|
93
94
|
});
|
|
94
|
-
type Result = { filePath: string; messages: { ruleId: string | null }[] };
|
|
95
95
|
return (
|
|
96
|
-
(
|
|
96
|
+
lintReport(out.join("\n"))
|
|
97
97
|
// A structure finding reports a MISSING file in the same shape; it was not linted.
|
|
98
98
|
.filter(
|
|
99
99
|
(r) => !r.messages.some((m) => m.ruleId === "structure/required-file"),
|
|
@@ -30,8 +30,8 @@ import { tmpdir } from "node:os";
|
|
|
30
30
|
import { dirname, join } from "node:path";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
import { load } from "js-yaml";
|
|
33
|
-
import { frontmatterBlock } from "../lib/markdown.
|
|
34
|
-
import { createChecker } from "../lib/check.
|
|
33
|
+
import { frontmatterBlock } from "../lib/markdown.ts";
|
|
34
|
+
import { createChecker } from "../lib/check.ts";
|
|
35
35
|
|
|
36
36
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
37
37
|
const ROOT = dirname(HERE);
|
package/src/new-paper.test.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { parsePaperSettings } from "./paper-settings.ts";
|
|
|
20
20
|
import { chooseVenue, run } from "./cli.ts";
|
|
21
21
|
import { shippedPresets } from "./presets.ts";
|
|
22
22
|
import { packageVenuesDir } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
23
|
+
import { lintReport } from "../test/lint-report.ts";
|
|
24
|
+
import { z } from "zod";
|
|
23
25
|
|
|
24
26
|
const dirs: string[] = [];
|
|
25
27
|
afterEach(() => {
|
|
@@ -31,10 +33,9 @@ const tmp = () => {
|
|
|
31
33
|
return d;
|
|
32
34
|
};
|
|
33
35
|
const settingsOf = (dir: string) =>
|
|
34
|
-
|
|
35
|
-
string,
|
|
36
|
-
|
|
37
|
-
>;
|
|
36
|
+
z
|
|
37
|
+
.record(z.string(), z.unknown())
|
|
38
|
+
.parse(JSON.parse(readFileSync(join(dir, "paperlint.json"), "utf8")));
|
|
38
39
|
|
|
39
40
|
describe("paperlint new — paperlint.json", () => {
|
|
40
41
|
it("is always written, from the package template: no venue chosen yet, and valid", () => {
|
|
@@ -107,11 +108,7 @@ describe("paperlint lint — a paper with no venue preset chosen", () => {
|
|
|
107
108
|
log: (s: string) => out.push(s),
|
|
108
109
|
err: () => {},
|
|
109
110
|
});
|
|
110
|
-
const messages = (
|
|
111
|
-
JSON.parse(out.join("\n")) as {
|
|
112
|
-
messages: { ruleId: string; severity: number; message: string }[];
|
|
113
|
-
}[]
|
|
114
|
-
).flatMap((r) => r.messages);
|
|
111
|
+
const messages = lintReport(out.join("\n")).flatMap((r) => r.messages);
|
|
115
112
|
return { code, messages };
|
|
116
113
|
}
|
|
117
114
|
|
|
@@ -266,7 +263,8 @@ describe("chooseVenue — on a terminal", () => {
|
|
|
266
263
|
paperDir: "/p/papers/demo",
|
|
267
264
|
cwd: "/p",
|
|
268
265
|
interactive: true,
|
|
269
|
-
ask:
|
|
266
|
+
ask: (q: string) =>
|
|
267
|
+
Promise.resolve((asked.push(q), answers.shift() ?? "")),
|
|
270
268
|
},
|
|
271
269
|
};
|
|
272
270
|
};
|
package/src/new-paper.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
/* eslint-enable boundaries/dependencies */
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
import { fileURLToPath } from "node:url";
|
|
28
|
-
import { CONFIG_FILE } from "
|
|
28
|
+
import { CONFIG_FILE } from "#lib/paper-config";
|
|
29
29
|
|
|
30
30
|
export type PaperFormat = "tex" | "md";
|
|
31
31
|
export const FORMATS: readonly PaperFormat[] = ["tex", "md"];
|
|
@@ -50,7 +50,7 @@ export const SOURCE_FILE: Readonly<Record<PaperFormat, string>> = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const isFormat = (v: unknown): v is PaperFormat =>
|
|
53
|
-
FORMATS.
|
|
53
|
+
FORMATS.some((f) => f === v);
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Why a name is refused, or null. `[a-z0-9._-]+` is the charset; a LEADING dot is refused on top
|
package/src/paper-files.test.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
ruleOwners,
|
|
28
28
|
scopeToOwned,
|
|
29
29
|
} from "./paper-files.ts";
|
|
30
|
+
import { lintReport } from "../test/lint-report.ts";
|
|
30
31
|
|
|
31
32
|
const dirs: string[] = [];
|
|
32
33
|
afterEach(() => {
|
|
@@ -67,7 +68,7 @@ async function lint(
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
const linted = (stdout: string): string[] =>
|
|
70
|
-
(
|
|
71
|
+
lintReport(stdout).map((r) => r.filePath);
|
|
71
72
|
|
|
72
73
|
/** Vendored code under a paper: a syntax error, an unknown rule in a directive, plain CommonJS. */
|
|
73
74
|
const VENDORED = {
|
|
@@ -208,10 +209,7 @@ describe("🔴 a generated block never reaches a file paperlint does not lint (#
|
|
|
208
209
|
async (_, settings) => {
|
|
209
210
|
const root = project({ ...SIBLINGS, ...settings });
|
|
210
211
|
const r = await lint(root);
|
|
211
|
-
const results =
|
|
212
|
-
filePath: string;
|
|
213
|
-
messages: { ruleId: string | null; message: string }[];
|
|
214
|
-
}[];
|
|
212
|
+
const results = lintReport(r.out);
|
|
215
213
|
// Guards: the class — no file reaches ESLint's default JavaScript parser.
|
|
216
214
|
expect(
|
|
217
215
|
results.flatMap((x) => x.messages).filter((m) => m.ruleId === null),
|
|
@@ -13,6 +13,7 @@ import { tmpdir } from "node:os";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { afterEach, describe, expect, it } from "vitest";
|
|
15
15
|
import { run, toolchainTex } from "./cli.ts";
|
|
16
|
+
import { lintReport } from "../test/lint-report.ts";
|
|
16
17
|
|
|
17
18
|
const dirs: string[] = [];
|
|
18
19
|
afterEach(() => {
|
|
@@ -55,9 +56,9 @@ async function lint(
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const rulesIn = (stdout: string, paper: string): string[] =>
|
|
58
|
-
(
|
|
59
|
+
lintReport(stdout)
|
|
59
60
|
.filter((r) => r.filePath.endsWith(join(paper, "paper.tex")))
|
|
60
|
-
.flatMap((r) => r.messages.map((m) => m.ruleId));
|
|
61
|
+
.flatMap((r) => r.messages.map((m) => m.ruleId ?? "(fatal)"));
|
|
61
62
|
|
|
62
63
|
describe("🔴 an optional rule that reaches no paper — judged against the PROJECT, not the run (#103)", () => {
|
|
63
64
|
const THIRD = {
|
|
@@ -151,7 +152,7 @@ describe("papersDir — optional, `papers` by default", () => {
|
|
|
151
152
|
const r = await lint(project());
|
|
152
153
|
expect(r.code).toBe(0);
|
|
153
154
|
expect(
|
|
154
|
-
(
|
|
155
|
+
lintReport(r.out).map((f) =>
|
|
155
156
|
f.filePath.slice(f.filePath.indexOf("papers/")),
|
|
156
157
|
),
|
|
157
158
|
).toEqual(
|
|
@@ -178,7 +179,7 @@ describe("papersDir — optional, `papers` by default", () => {
|
|
|
178
179
|
"docs/drafts/c/PIPELINE-STATUS.md": "---\nstages: []\n---\n",
|
|
179
180
|
});
|
|
180
181
|
const r = await lint(root);
|
|
181
|
-
const linted = (
|
|
182
|
+
const linted = lintReport(r.out).map((f) =>
|
|
182
183
|
f.filePath.slice(root.length + 1),
|
|
183
184
|
);
|
|
184
185
|
expect(linted).toContain("docs/drafts/c/paper.tex");
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
7
7
|
import type { AbsolutePath } from "./domain/paths.ts";
|
|
8
|
-
import { findProjectRoot } from "../lib/paper-config.
|
|
8
|
+
import { findProjectRoot } from "../lib/paper-config.ts";
|
|
9
9
|
import {
|
|
10
10
|
paperRules,
|
|
11
11
|
parsePaperSettings,
|
|
@@ -128,12 +128,12 @@ describe("readPaperSettings — a root paperlint.json that cannot be used", () =
|
|
|
128
128
|
readPaperSettings(withFiles({ [`${ROOT}/paperlint.json`]: root }), PAPER);
|
|
129
129
|
const notJson = broken("{ nope");
|
|
130
130
|
expect(!notJson.ok && notJson.error.kind).toBe("broken");
|
|
131
|
+
const why: unknown = expect.stringMatching(
|
|
132
|
+
/^the root paperlint\.json: .*kind/,
|
|
133
|
+
);
|
|
131
134
|
expect(broken('{"kind":3}')).toEqual({
|
|
132
135
|
ok: false,
|
|
133
|
-
error: {
|
|
134
|
-
kind: "broken",
|
|
135
|
-
why: expect.stringMatching(/^the root paperlint\.json: .*kind/),
|
|
136
|
-
},
|
|
136
|
+
error: { kind: "broken", why },
|
|
137
137
|
});
|
|
138
138
|
});
|
|
139
139
|
});
|