paperlint 3.1.2 → 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/skills/verify-citations/scripts/verify-cites.mjs +11 -1
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +35 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +14 -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
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
EMPTY_LOOKUP_CACHE,
|
|
11
11
|
type LookupCache,
|
|
12
12
|
} from "../../domain/lookup-cache.ts";
|
|
13
|
-
// @ts-expect-error — a skill script in .mjs, it has no types
|
|
14
13
|
import * as cites from "../../../skills/verify-citations/scripts/verify-cites.mjs";
|
|
15
14
|
|
|
16
15
|
/** The verdicts of a cold run — an empty cache, the adapter the CLI wires. */
|
|
@@ -46,29 +45,29 @@ const dblp = (title: string, authors: string[]) =>
|
|
|
46
45
|
/** Route by URL prefix; the longest prefix wins. */
|
|
47
46
|
function fakeFetch(routes: Record<string, () => Response>): string[] {
|
|
48
47
|
const calls: string[] = [];
|
|
49
|
-
vi.stubGlobal("fetch",
|
|
48
|
+
vi.stubGlobal("fetch", (url: string) => {
|
|
50
49
|
calls.push(url);
|
|
51
|
-
const hit = Object.
|
|
52
|
-
.filter((p) => url.startsWith(p))
|
|
53
|
-
.sort((a, b) => b.length - a.length)[0];
|
|
54
|
-
|
|
55
|
-
return routes[hit]!();
|
|
50
|
+
const hit = Object.entries(routes)
|
|
51
|
+
.filter(([p]) => url.startsWith(p))
|
|
52
|
+
.sort(([a], [b]) => b.length - a.length)[0];
|
|
53
|
+
return Promise.resolve(hit === undefined ? json(404) : hit[1]());
|
|
56
54
|
});
|
|
57
55
|
return calls;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
/** Await `p` while fake time runs, so DBLP's pauses between requests elapse at once. */
|
|
61
59
|
async function settle<T>(p: Promise<T>): Promise<T> {
|
|
62
|
-
let
|
|
63
|
-
const
|
|
64
|
-
|
|
60
|
+
// A field, not a `let`: the flag is set in a callback, which flow analysis cannot see.
|
|
61
|
+
const state = { done: false };
|
|
62
|
+
const out = p.finally(() => (state.done = true));
|
|
63
|
+
while (!state.done) await vi.advanceTimersByTimeAsync(1000);
|
|
65
64
|
return out;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
test("no service answers: not-checked, with the reason", async () => {
|
|
69
|
-
vi.stubGlobal("fetch",
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
vi.stubGlobal("fetch", () =>
|
|
69
|
+
Promise.reject(new Error("getaddrinfo ENOTFOUND api.crossref.org")),
|
|
70
|
+
);
|
|
72
71
|
assert.deepEqual(await checkOnly("@misc{k, title={T}}"), {
|
|
73
72
|
kind: "not-checked",
|
|
74
73
|
why: "the citation services cannot be reached (getaddrinfo ENOTFOUND api.crossref.org)",
|
|
@@ -104,19 +103,19 @@ test("each entry gets its existence and its authors: confirmed, fabricated, mism
|
|
|
104
103
|
const r = await settle(checkOnly(bib));
|
|
105
104
|
assert.equal(r.kind, "checked");
|
|
106
105
|
const byKey = Object.fromEntries(
|
|
107
|
-
|
|
108
|
-
e.key,
|
|
109
|
-
[e.exists, e.authors, e.why ?? ""],
|
|
110
|
-
]),
|
|
106
|
+
r.entries.map((e) => [e.key, [e.exists, e.authors, e.why ?? ""]]),
|
|
111
107
|
);
|
|
112
108
|
assert.deepEqual(
|
|
113
109
|
{
|
|
114
|
-
good: byKey["good"]
|
|
115
|
-
fake: byKey["fake"]
|
|
116
|
-
drift: byKey["drift"]
|
|
117
|
-
driftWhy: String(byKey["drift"]
|
|
118
|
-
flaky: [
|
|
119
|
-
|
|
110
|
+
good: byKey["good"]?.slice(0, 2),
|
|
111
|
+
fake: byKey["fake"]?.slice(0, 2),
|
|
112
|
+
drift: byKey["drift"]?.slice(0, 2),
|
|
113
|
+
driftWhy: String(byKey["drift"]?.[2]).split("; ").pop(),
|
|
114
|
+
flaky: [
|
|
115
|
+
byKey["flaky"]?.[1],
|
|
116
|
+
String(byKey["flaky"]?.[2]).split("; ").pop(),
|
|
117
|
+
],
|
|
118
|
+
pre: byKey["pre"]?.slice(0, 2),
|
|
120
119
|
},
|
|
121
120
|
{
|
|
122
121
|
good: ["true", "match"],
|
|
@@ -209,20 +208,14 @@ test(
|
|
|
209
208
|
slowFetch(40);
|
|
210
209
|
const bib = manyBib(20);
|
|
211
210
|
const serial: string[] = [];
|
|
212
|
-
for (const c of cites.parseBib(bib)
|
|
213
|
-
serial.push(
|
|
214
|
-
(
|
|
215
|
-
(await cites.verifyCitationLive(c, { cache: {} })) as {
|
|
216
|
-
verdict: string;
|
|
217
|
-
}
|
|
218
|
-
).verdict,
|
|
219
|
-
);
|
|
211
|
+
for (const c of cites.parseBib(bib))
|
|
212
|
+
serial.push((await cites.verifyCitationLive(c, { cache: {} })).verdict);
|
|
220
213
|
const t0 = Date.now();
|
|
221
214
|
const r = await checkOnly(bib);
|
|
222
215
|
const ms = Date.now() - t0;
|
|
223
216
|
assert.equal(r.kind, "checked");
|
|
224
217
|
assert.deepEqual(
|
|
225
|
-
r.
|
|
218
|
+
r.entries.map((e) => e.exists),
|
|
226
219
|
serial,
|
|
227
220
|
);
|
|
228
221
|
assert.ok(ms < (20 * 200) / 2, `${String(ms)} ms for 20 lookups`);
|
|
@@ -249,7 +242,7 @@ const PAPERS = [
|
|
|
249
242
|
"@inproceedings{good, author={Ada Lovelace}, title={Good Paper}, booktitle={ICSE}, doi={10.1/good}}",
|
|
250
243
|
"@inproceedings{other, author={Ada Lovelace}, title={Other Paper}, booktitle={ICSE}}",
|
|
251
244
|
"@misc{pre, author={Ada Lovelace}, title={A Preprint}, journal={arXiv preprint}}",
|
|
252
|
-
];
|
|
245
|
+
] as const;
|
|
253
246
|
/** Every service answers; DBLP knows "Good Paper" and nothing else. */
|
|
254
247
|
const answering = () =>
|
|
255
248
|
fakeFetch({
|
|
@@ -414,7 +407,7 @@ test("a failed lookup is not cached: the next run asks it again", async () => {
|
|
|
414
407
|
"https://api.crossref.org/": () => json(200, { message: { items: [] } }),
|
|
415
408
|
"https://dblp.org/": () => json(500),
|
|
416
409
|
});
|
|
417
|
-
const bib = PAPERS[1]
|
|
410
|
+
const bib = PAPERS[1];
|
|
418
411
|
const r = await settle(cached(bib, EMPTY_LOOKUP_CACHE));
|
|
419
412
|
const failed: LookupCache = r.cache;
|
|
420
413
|
assert.deepEqual(
|
|
@@ -427,10 +420,10 @@ test("a failed lookup is not cached: the next run asks it again", async () => {
|
|
|
427
420
|
});
|
|
428
421
|
|
|
429
422
|
test("offline with an incomplete cache: not-checked, and the cache comes back unchanged", async () => {
|
|
430
|
-
vi.stubGlobal("fetch",
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const r = await cached(PAPERS[0]
|
|
423
|
+
vi.stubGlobal("fetch", () =>
|
|
424
|
+
Promise.reject(new Error("getaddrinfo ENOTFOUND api.crossref.org")),
|
|
425
|
+
);
|
|
426
|
+
const r = await cached(PAPERS[0], EMPTY_LOOKUP_CACHE);
|
|
434
427
|
assert.deepEqual(r, {
|
|
435
428
|
check: {
|
|
436
429
|
kind: "not-checked",
|
|
@@ -35,29 +35,15 @@ import { sleep, todayUtc } from "./clock.io.ts";
|
|
|
35
35
|
|
|
36
36
|
/** Citations looked up at once (#107). */
|
|
37
37
|
const LOOKUPS_IN_FLIGHT = 6;
|
|
38
|
-
// @ts-expect-error — a skill script in .mjs, it has no types
|
|
39
38
|
import * as cites from "../../../skills/verify-citations/scripts/verify-cites.mjs";
|
|
40
|
-
|
|
39
|
+
import type { CiteVerdict } from "../../../skills/verify-citations/scripts/verify-cites.mjs";
|
|
41
40
|
import * as authors from "../../../skills/verify-citations/scripts/bib-authors.mjs";
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
readonly refused?: readonly string[];
|
|
49
|
-
}
|
|
50
|
-
interface AuthorFinding {
|
|
51
|
-
readonly key: string;
|
|
52
|
-
readonly venue: string;
|
|
53
|
-
readonly missing: readonly string[];
|
|
54
|
-
readonly extra: readonly string[];
|
|
55
|
-
readonly orderDiffers: boolean;
|
|
56
|
-
}
|
|
57
|
-
interface KeyWhy {
|
|
58
|
-
readonly key: string;
|
|
59
|
-
readonly why: string;
|
|
60
|
-
}
|
|
41
|
+
import type {
|
|
42
|
+
AuthorBuckets,
|
|
43
|
+
AuthorFinding,
|
|
44
|
+
BibEntry,
|
|
45
|
+
} from "../../../skills/verify-citations/scripts/bib-authors.mjs";
|
|
46
|
+
import { messageOf } from "../../domain/text.ts";
|
|
61
47
|
|
|
62
48
|
const describeAuthors = (f: AuthorFinding): string =>
|
|
63
49
|
[
|
|
@@ -68,13 +54,6 @@ const describeAuthors = (f: AuthorFinding): string =>
|
|
|
68
54
|
.filter(Boolean)
|
|
69
55
|
.join("; ") + ` (DBLP: ${f.venue})`;
|
|
70
56
|
|
|
71
|
-
interface AuthorBuckets {
|
|
72
|
-
readonly findings: readonly AuthorFinding[];
|
|
73
|
-
readonly skipped: readonly KeyWhy[];
|
|
74
|
-
readonly unchecked: readonly KeyWhy[];
|
|
75
|
-
readonly matched: readonly string[];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
57
|
const authorsOf = (key: string, a: AuthorBuckets): EntryVerdict["authors"] =>
|
|
79
58
|
a.findings.some((f) => f.key === key)
|
|
80
59
|
? "mismatch"
|
|
@@ -90,7 +69,7 @@ const authorsOf = (key: string, a: AuthorBuckets): EntryVerdict["authors"] =>
|
|
|
90
69
|
* confirmed work: a refusal after a confirmation changed nothing.
|
|
91
70
|
*/
|
|
92
71
|
const unconfirmed = (
|
|
93
|
-
c:
|
|
72
|
+
c: CiteVerdict | undefined,
|
|
94
73
|
): readonly (string | undefined)[] =>
|
|
95
74
|
c && c.verdict !== "true"
|
|
96
75
|
? [c.reason, ...(c.refused ?? []).map((r) => `not asked: ${r}`)]
|
|
@@ -99,7 +78,7 @@ const unconfirmed = (
|
|
|
99
78
|
/** One entry's verdict from the two checkers' answers. */
|
|
100
79
|
function entryVerdict(
|
|
101
80
|
key: string,
|
|
102
|
-
found: readonly
|
|
81
|
+
found: readonly CiteVerdict[],
|
|
103
82
|
a: AuthorBuckets,
|
|
104
83
|
): EntryVerdict {
|
|
105
84
|
const c = found.find((x) => x.id === key);
|
|
@@ -119,27 +98,21 @@ function entryVerdict(
|
|
|
119
98
|
};
|
|
120
99
|
}
|
|
121
100
|
|
|
122
|
-
interface BibAuthorsEntry {
|
|
123
|
-
readonly key: string;
|
|
124
|
-
readonly title: string;
|
|
125
|
-
readonly author: string;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
101
|
/** Whether bib-authors will ask DBLP about this entry — the filter its `checkAuthors` applies. */
|
|
129
|
-
const asksDblp = (e:
|
|
102
|
+
const asksDblp = (e: BibEntry): boolean =>
|
|
130
103
|
Boolean(e.title && e.author) &&
|
|
131
|
-
|
|
132
|
-
!
|
|
104
|
+
authors.claimsPublished(e) &&
|
|
105
|
+
!authors.truncated(e.author);
|
|
133
106
|
|
|
134
107
|
/** Is every question this bibliography would ask already answered in `cache`? */
|
|
135
108
|
async function fullyCached(
|
|
136
|
-
citations: readonly
|
|
137
|
-
entries: readonly
|
|
109
|
+
citations: readonly cites.Citation[],
|
|
110
|
+
entries: readonly BibEntry[],
|
|
138
111
|
cache: LookupCache,
|
|
139
112
|
store: Readonly<Record<string, unknown>>,
|
|
140
113
|
): Promise<boolean> {
|
|
141
114
|
const asks = await Promise.all(
|
|
142
|
-
citations.map((c) => cites.wouldAsk(c, store)
|
|
115
|
+
citations.map((c) => cites.wouldAsk(c, store)),
|
|
143
116
|
);
|
|
144
117
|
return (
|
|
145
118
|
!asks.includes(true) &&
|
|
@@ -185,11 +158,11 @@ function cachedDblp(cache: LookupCache, today: () => string) {
|
|
|
185
158
|
if (refused !== null)
|
|
186
159
|
throw new Error(`DBLP refused earlier in this run (${refused})`);
|
|
187
160
|
try {
|
|
188
|
-
const hits =
|
|
161
|
+
const hits = await authors.dblpHits(title);
|
|
189
162
|
dblp.set(dblpTitleKey(title), { fetched: today(), title, hits });
|
|
190
163
|
return hits;
|
|
191
164
|
} catch (e) {
|
|
192
|
-
refused = (e
|
|
165
|
+
refused = messageOf(e);
|
|
193
166
|
throw new Error(`DBLP refused earlier in this run (${refused})`);
|
|
194
167
|
}
|
|
195
168
|
},
|
|
@@ -234,10 +207,8 @@ export const referencesChecker =
|
|
|
234
207
|
async (bib, cache) => {
|
|
235
208
|
// Answers past MAX_AGE_DAYS are left out, so the run asks them again (and `grown` re-dates them).
|
|
236
209
|
const usable = freshPart(cache, today());
|
|
237
|
-
const citations =
|
|
238
|
-
|
|
239
|
-
);
|
|
240
|
-
const parsed = authors.parseBib(bib) as BibAuthorsEntry[];
|
|
210
|
+
const citations = cites.parseBib(bib);
|
|
211
|
+
const parsed = authors.parseBib(bib);
|
|
241
212
|
const store: Record<string, unknown> = Object.fromEntries(
|
|
242
213
|
[...usable.citations].map(([k, v]) => [k, v.response]),
|
|
243
214
|
);
|
|
@@ -252,22 +223,19 @@ export const referencesChecker =
|
|
|
252
223
|
// answer.
|
|
253
224
|
// One breaker for the run: a service that refuses is not asked again, not once per citation
|
|
254
225
|
// (#120). Requests already in flight when it starts refusing still land — at most six.
|
|
255
|
-
const breaker = cites.createBreaker()
|
|
226
|
+
const breaker = cites.createBreaker();
|
|
256
227
|
const d = cachedDblp(usable, today);
|
|
257
228
|
const [found, a] = await Promise.all([
|
|
258
|
-
mapLimit(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
cache: store,
|
|
264
|
-
breaker,
|
|
265
|
-
}) as Promise<CiteResult>,
|
|
229
|
+
mapLimit(citations, LOOKUPS_IN_FLIGHT, (c) =>
|
|
230
|
+
cites.verifyCitationLive(c, {
|
|
231
|
+
cache: store,
|
|
232
|
+
breaker,
|
|
233
|
+
}),
|
|
266
234
|
),
|
|
267
235
|
authors.checkAuthors(parsed, {
|
|
268
236
|
lookup: d.lookup,
|
|
269
237
|
pause: d.pause,
|
|
270
|
-
})
|
|
238
|
+
}),
|
|
271
239
|
]);
|
|
272
240
|
const keys = new Set([
|
|
273
241
|
...found.map((c) => c.id),
|
|
@@ -10,12 +10,10 @@ export async function mapLimit<T, R>(
|
|
|
10
10
|
fn: (item: T) => Promise<R>,
|
|
11
11
|
): Promise<R[]> {
|
|
12
12
|
const out: R[] = new Array<R>(items.length);
|
|
13
|
-
|
|
13
|
+
// ONE iterator shared by every worker: each `next()` hands out the next index exactly once.
|
|
14
|
+
const queue = items.entries();
|
|
14
15
|
const worker = async (): Promise<void> => {
|
|
15
|
-
|
|
16
|
-
const i = next++;
|
|
17
|
-
out[i] = await fn(items[i] as T);
|
|
18
|
-
}
|
|
16
|
+
for (const [i, item] of queue) out[i] = await fn(item);
|
|
19
17
|
};
|
|
20
18
|
await Promise.all(
|
|
21
19
|
Array.from({ length: Math.min(limit, items.length) }, worker),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messageOf } from "../../domain/text.ts";
|
|
1
2
|
/** Whether the citation services answer at all — the one request that decides "not checked". */
|
|
2
3
|
export async function unreachable(): Promise<string | null> {
|
|
3
4
|
try {
|
|
@@ -7,6 +8,6 @@ export async function unreachable(): Promise<string | null> {
|
|
|
7
8
|
});
|
|
8
9
|
return null;
|
|
9
10
|
} catch (e) {
|
|
10
|
-
return `the citation services cannot be reached (${(e
|
|
11
|
+
return `the citation services cannot be reached (${messageOf(e)})`;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import { basename, delimiter, dirname, join } from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
|
-
import { createChecker } from "../lib/check.
|
|
22
|
+
import { createChecker } from "../lib/check.ts";
|
|
23
23
|
|
|
24
24
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
const E = await import(join(HERE, "build-engine.ts"));
|
package/src/build-engine.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import assert from "node:assert/strict";
|
|
|
7
7
|
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { test } from "vitest";
|
|
10
|
-
import { useTempDir } from "../test/support.
|
|
10
|
+
import { useTempDir } from "../test/support.ts";
|
|
11
11
|
import { prepareEngine, refusal } from "./build-engine.ts";
|
|
12
12
|
|
|
13
13
|
const work = useTempDir("paperlint-build-engine-test-");
|
|
@@ -34,7 +34,7 @@ async function go(o: Parameters<typeof prepareEngine>[0]) {
|
|
|
34
34
|
err: (l) => err.push(l),
|
|
35
35
|
platform: "linux",
|
|
36
36
|
interactive: true,
|
|
37
|
-
ask:
|
|
37
|
+
ask: () => Promise.resolve("y"),
|
|
38
38
|
...o,
|
|
39
39
|
});
|
|
40
40
|
return { r, out, err };
|
package/src/build-engine.ts
CHANGED
|
@@ -71,7 +71,7 @@ function withDefaults(o: EngineOptions): Resolved {
|
|
|
71
71
|
const r = {
|
|
72
72
|
dryRun: false,
|
|
73
73
|
interactive: false,
|
|
74
|
-
ask:
|
|
74
|
+
ask: () => Promise.resolve(""),
|
|
75
75
|
log: console.log,
|
|
76
76
|
err: console.error,
|
|
77
77
|
// eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
|
|
@@ -130,7 +130,7 @@ export function gatherFacts(o: Resolved): EngineFacts {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
const names = (missing: readonly string[]): string =>
|
|
133
|
-
`${missing.length} package(s): ${missing.join(", ")}`;
|
|
133
|
+
`${String(missing.length)} package(s): ${missing.join(", ")}`;
|
|
134
134
|
|
|
135
135
|
/** The one line a run without a human gets. It names the command and what is missing. */
|
|
136
136
|
export function refusal(d: EngineDecision, facts: EngineFacts): string {
|
package/src/build.harness.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The process runner is replaced by a fake that writes what pdflatex and bibtex would write, so
|
|
5
5
|
* these assertions check the SHELL's decisions: what is run, with which environment, in which
|
|
6
6
|
* order, and what is left on disk. Whether a real pdflatex produces a real PDF is the other half,
|
|
7
|
-
* `test/e2e/build.
|
|
7
|
+
* `test/e2e/build.ts`.
|
|
8
8
|
*
|
|
9
9
|
* 🔴 THE TWO THINGS PINNED DOWN HERE THAT A RETURNED OBJECT CANNOT SHOW: a paper-supplied
|
|
10
10
|
* `build.sh` is never executed (checked by the trace it would leave on disk and by the list of
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { tmpdir } from "node:os";
|
|
23
23
|
import { join, dirname, delimiter } from "node:path";
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
|
-
import { createChecker } from "../lib/check.
|
|
25
|
+
import { createChecker } from "../lib/check.ts";
|
|
26
26
|
|
|
27
27
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
28
28
|
const {
|
package/src/build.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import assert from "node:assert/strict";
|
|
|
8
8
|
import { existsSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { test } from "vitest";
|
|
11
|
-
import { useTempDir, writeTree } from "../test/support.
|
|
11
|
+
import { useTempDir, writeTree } from "../test/support.ts";
|
|
12
12
|
import {
|
|
13
13
|
bibInput,
|
|
14
14
|
buildPaper,
|
|
@@ -62,7 +62,7 @@ function bibtexRun(f: Fake, cwd: string) {
|
|
|
62
62
|
? { status: f.bibtex }
|
|
63
63
|
: { status: f.bibtex, stdout: f.bibtexOut };
|
|
64
64
|
}
|
|
65
|
-
function fakeRun(f: Fake): BuildOptions["run"] {
|
|
65
|
+
function fakeRun(f: Fake): NonNullable<BuildOptions["run"]> {
|
|
66
66
|
let pass = 0;
|
|
67
67
|
return ((bin: string, _args: string[], opts: { cwd: string }) => {
|
|
68
68
|
if (bin === f.missing)
|
|
@@ -103,18 +103,19 @@ const page = (words: number, numbered = 0) => ({
|
|
|
103
103
|
});
|
|
104
104
|
const reader =
|
|
105
105
|
(last = page(80)): PdfReader =>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
() =>
|
|
107
|
+
Promise.resolve({
|
|
108
|
+
ok: true,
|
|
109
|
+
facts: {
|
|
110
|
+
pages: 1,
|
|
111
|
+
fonts: {
|
|
112
|
+
kind: "drawn",
|
|
113
|
+
list: [{ kind: "embedded", name: "CMR10", program: "Type1" }],
|
|
114
|
+
},
|
|
115
|
+
last,
|
|
116
|
+
layout: [],
|
|
113
117
|
},
|
|
114
|
-
|
|
115
|
-
layout: [],
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
+
});
|
|
118
119
|
const quiet = () => {};
|
|
119
120
|
/** buildPaper with fakes; the verdict line is what the command would print for it. */
|
|
120
121
|
async function build(dir: string, f: Fake = {}, over: BuildOptions = {}) {
|
|
@@ -332,7 +333,7 @@ test("paths: a paper that IS the working directory is named by its full path", a
|
|
|
332
333
|
const run = await buildPapers([dir], {
|
|
333
334
|
cwd: dir,
|
|
334
335
|
log: (l) => lines.push(l),
|
|
335
|
-
engine:
|
|
336
|
+
engine: () => Promise.resolve(null),
|
|
336
337
|
});
|
|
337
338
|
assert.deepEqual(
|
|
338
339
|
[run, lines],
|
package/src/build.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { delimiter, join, relative } from "node:path";
|
|
|
45
45
|
// eslint-disable-next-line boundaries/dependencies -- legacy layer, moves behind a port in #76
|
|
46
46
|
import { getParser } from "@unified-latex/unified-latex-util-parse";
|
|
47
47
|
import { packageVenuesDir } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
48
|
-
import { CONFIG_FILE } from "
|
|
48
|
+
import { CONFIG_FILE } from "#lib/paper-config";
|
|
49
49
|
import {
|
|
50
50
|
declaredVenue,
|
|
51
51
|
factsPath,
|
|
@@ -83,6 +83,10 @@ import {
|
|
|
83
83
|
type Step,
|
|
84
84
|
} from "./latex-loop.ts";
|
|
85
85
|
import type { BuildResult, PlanLine } from "./types.ts";
|
|
86
|
+
import { messageOf, printed } from "./domain/text.ts";
|
|
87
|
+
|
|
88
|
+
/** stdin closed, stdout and stderr captured — the tuple type spawnSync's `stdio` wants. */
|
|
89
|
+
const PIPED: ["ignore", "pipe", "pipe"] = ["ignore", "pipe", "pipe"];
|
|
86
90
|
|
|
87
91
|
/** A directory counts as a paper by the same markers as `structure.ts` — one shared dictionary. */
|
|
88
92
|
export const PAPER_MARKERS = [
|
|
@@ -337,7 +341,7 @@ function spawnOptions(ctx: BuildContext) {
|
|
|
337
341
|
return {
|
|
338
342
|
cwd: ctx.paperDir,
|
|
339
343
|
env: ctx.env,
|
|
340
|
-
stdio:
|
|
344
|
+
stdio: PIPED,
|
|
341
345
|
encoding: "latin1" as const,
|
|
342
346
|
maxBuffer: 64 * 1024 * 1024,
|
|
343
347
|
};
|
|
@@ -376,19 +380,23 @@ function latexPass(
|
|
|
376
380
|
exitCode === 0
|
|
377
381
|
? []
|
|
378
382
|
: written === null
|
|
379
|
-
?
|
|
383
|
+
? lastPrinted(r)
|
|
380
384
|
: errorExcerpt(log).map(fromLatin1),
|
|
381
385
|
};
|
|
382
386
|
}
|
|
383
387
|
|
|
388
|
+
/** The two streams of a finished run, as they really arrive (see `printed`). */
|
|
389
|
+
interface Streams {
|
|
390
|
+
readonly stdout: string | undefined;
|
|
391
|
+
readonly stderr: string | undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
384
394
|
/**
|
|
385
395
|
* The last lines pdflatex printed, for the run that wrote no log — it died before opening one (a
|
|
386
396
|
* format it cannot load, a missing TeX Live file). Marked by NO_LOG, which the report keys on.
|
|
387
397
|
*/
|
|
388
|
-
function
|
|
389
|
-
const lines = fromLatin1(
|
|
390
|
-
`${String(r.stdout ?? "")}\n${String(r.stderr ?? "")}`,
|
|
391
|
-
)
|
|
398
|
+
function lastPrinted(r: Streams): string[] {
|
|
399
|
+
const lines = fromLatin1(`${printed(r.stdout)}\n${printed(r.stderr)}`)
|
|
392
400
|
.split("\n")
|
|
393
401
|
.filter((l) => l.trim());
|
|
394
402
|
return [NO_LOG, ...lines.slice(-8).map((l) => ` ${l}`)];
|
|
@@ -410,7 +418,7 @@ function bibtexPass(ctx: BuildContext, opts: SpawnOptions): Observation | null {
|
|
|
410
418
|
after: hashes(ctx.paperDir),
|
|
411
419
|
bib,
|
|
412
420
|
errorLines:
|
|
413
|
-
exitCode === 0 ? [] : bibtexExcerpt(fromLatin1(
|
|
421
|
+
exitCode === 0 ? [] : bibtexExcerpt(fromLatin1(printed(r.stdout))),
|
|
414
422
|
};
|
|
415
423
|
}
|
|
416
424
|
|
|
@@ -443,7 +451,7 @@ export function compile(ctx: BuildContext): {
|
|
|
443
451
|
const NO_PDF = `pdflatex exited 0 but wrote no ${JOB}.pdf — does the document have any pages?`;
|
|
444
452
|
|
|
445
453
|
const plural = (n: number, one: string, many: string): string =>
|
|
446
|
-
`${n} ${n === 1 ? one : many}`;
|
|
454
|
+
`${String(n)} ${n === 1 ? one : many}`;
|
|
447
455
|
|
|
448
456
|
export const compileStep: BuildStep = {
|
|
449
457
|
name: "compile",
|
|
@@ -467,7 +475,7 @@ export const compileStep: BuildStep = {
|
|
|
467
475
|
const bin = end.step === "latex" ? "pdflatex" : "bibtex";
|
|
468
476
|
const where =
|
|
469
477
|
end.cause.kind === "exit"
|
|
470
|
-
? `${bin} exited with ${end.cause.code}`
|
|
478
|
+
? `${bin} exited with ${String(end.cause.code)}`
|
|
471
479
|
: `${bin} did not converge`;
|
|
472
480
|
const log = join(
|
|
473
481
|
ctx.paperDir,
|
|
@@ -669,13 +677,14 @@ function baseDefaults({
|
|
|
669
677
|
}
|
|
670
678
|
|
|
671
679
|
/** Without a checker the record says so — never a pass. The CLI wires the real one. */
|
|
672
|
-
const notWired: CheckReferences =
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
680
|
+
const notWired: CheckReferences = (_bib, cache) =>
|
|
681
|
+
Promise.resolve({
|
|
682
|
+
check: {
|
|
683
|
+
kind: "not-checked",
|
|
684
|
+
why: "no reference checker was wired into this build",
|
|
685
|
+
},
|
|
686
|
+
cache,
|
|
687
|
+
});
|
|
679
688
|
|
|
680
689
|
/** banal as the measurer, wired from the build's environment: the one piece of root work left here (#76). */
|
|
681
690
|
function defaultMeasurer(
|
|
@@ -762,7 +771,7 @@ export async function buildPaper(
|
|
|
762
771
|
dir,
|
|
763
772
|
status: "failed",
|
|
764
773
|
plan: [],
|
|
765
|
-
failure: { step: "facts", lines: [(e
|
|
774
|
+
failure: { step: "facts", lines: [messageOf(e)] },
|
|
766
775
|
};
|
|
767
776
|
}
|
|
768
777
|
for (const s of facts.ignoredScripts)
|
|
@@ -853,7 +862,7 @@ export function formatResult(r: BuildResult): string {
|
|
|
853
862
|
function formatFailure(r: BuildResult): string {
|
|
854
863
|
const [head, ...rest] = r.failure?.lines ?? ["failed"];
|
|
855
864
|
return [
|
|
856
|
-
` ✗ ${r.failure?.step ?? "build"}: ${head}`,
|
|
865
|
+
` ✗ ${r.failure?.step ?? "build"}: ${String(head)}`,
|
|
857
866
|
...rest.map((l) => ` ${l}`),
|
|
858
867
|
` ${PDF_REMOVED}`,
|
|
859
868
|
].join("\n");
|
package/src/cli.harness.mjs
CHANGED
|
@@ -28,8 +28,8 @@ import { spawnSync } from "node:child_process";
|
|
|
28
28
|
import { tmpdir } from "node:os";
|
|
29
29
|
import { join, dirname } from "node:path";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
|
-
import { PAPERS_DIR_FIELD, findProjectRoot } from "../lib/paper-config.
|
|
32
|
-
import { createChecker } from "../lib/check.
|
|
31
|
+
import { PAPERS_DIR_FIELD, findProjectRoot } from "../lib/paper-config.ts";
|
|
32
|
+
import { createChecker } from "../lib/check.ts";
|
|
33
33
|
|
|
34
34
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
35
35
|
const { run, parseArgs, buildConfig, nextSteps, toPaths, runHook } =
|
|
@@ -635,7 +635,7 @@ check(
|
|
|
635
635
|
|
|
636
636
|
// ── 7-bis. THE SKILLS: WHAT WAS LINKED, AND WHAT WAS LEFT ALONE, BY NAME ────────────
|
|
637
637
|
//
|
|
638
|
-
// The links themselves are `link-skills.harness.
|
|
638
|
+
// The links themselves are `link-skills.harness.ts`'s subject. Here: init REPORTS them, and
|
|
639
639
|
// a name it refused to take does not turn a working install red. The state is handed in so
|
|
640
640
|
// this block does not depend on a package being installed next to the temp project.
|
|
641
641
|
{
|
|
@@ -967,7 +967,7 @@ check(
|
|
|
967
967
|
|
|
968
968
|
// ── STRUCTURE GETS THROUGH TO THE COMMAND ──────────────────────────────────────────────
|
|
969
969
|
//
|
|
970
|
-
// `structure.mjs` is checked separately and in full (`structure.harness.
|
|
970
|
+
// `structure.mjs` is checked separately and in full (`structure.harness.ts`). Here — exactly
|
|
971
971
|
// one fact that harness cannot know: that the module is WIRED IN. A correct module forgotten in
|
|
972
972
|
// `run()` gives zero findings and looks like a clean corpus.
|
|
973
973
|
{
|
|
@@ -1148,7 +1148,7 @@ check(
|
|
|
1148
1148
|
// pnpm: node_modules/vigiles/dist/cli.js DOES NOT
|
|
1149
1149
|
// The old wiring addressed the runtime from the project root and did not resolve under pnpm,
|
|
1150
1150
|
// and `|| exit 2` on PreToolUse(Bash) turned that into a block on ANY command. The end-to-end
|
|
1151
|
-
// half (both installs, real processes) lives in `test/e2e/install.
|
|
1151
|
+
// half (both installs, real processes) lives in `test/e2e/install.ts`; here — the verdicts.
|
|
1152
1152
|
{
|
|
1153
1153
|
const calls = [];
|
|
1154
1154
|
const fake = (code) => (bin, args, opts) => {
|
|
@@ -1258,7 +1258,7 @@ console.log(
|
|
|
1258
1258
|
|
|
1259
1259
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1260
1260
|
// What is left after init is typed in THE SAME TERMINAL. The two `/plugin` lines are gone: the
|
|
1261
|
-
// hooks are written into `.claude/settings.json` by init itself (hooks-settings.harness.
|
|
1261
|
+
// hooks are written into `.claude/settings.json` by init itself (hooks-settings.harness.ts).
|
|
1262
1262
|
|
|
1263
1263
|
{
|
|
1264
1264
|
const steps = nextSteps("papers");
|