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
|
@@ -25,11 +25,21 @@ import {
|
|
|
25
25
|
writeFileSync,
|
|
26
26
|
} from "node:fs";
|
|
27
27
|
import { tmpdir } from "node:os";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
import { createChecker } from "../lib/check.ts";
|
|
30
|
+
import { isDeepStrictEqual } from "node:util";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import type { Merge, Settings } from "./hooks-settings.ts";
|
|
33
|
+
|
|
34
|
+
/** Settings with a PreToolUse entry — the shape the "wired twice" case edits. */
|
|
35
|
+
const PreToolUseSettings = z.looseObject({
|
|
36
|
+
hooks: z.looseObject({
|
|
37
|
+
PreToolUse: z.array(
|
|
38
|
+
z.looseObject({ hooks: z.array(z.looseObject({ command: z.string() })) }),
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
31
42
|
|
|
32
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
33
43
|
const {
|
|
34
44
|
hookRun,
|
|
35
45
|
shippedWiring,
|
|
@@ -39,16 +49,17 @@ const {
|
|
|
39
49
|
readSettings,
|
|
40
50
|
MANAGED_BY,
|
|
41
51
|
SETTINGS_PATH,
|
|
42
|
-
} = await import(
|
|
52
|
+
} = await import("./hooks-settings.ts");
|
|
43
53
|
const { claudeCodeHookProtocol } = await import("vigiles/claude-code");
|
|
44
|
-
const merge = (e, c, m) =>
|
|
54
|
+
const merge: Merge = (e, c, m) =>
|
|
55
|
+
claudeCodeHookProtocol.mergeRegistrations(e, c, m);
|
|
45
56
|
|
|
46
57
|
const check = createChecker();
|
|
47
58
|
|
|
48
59
|
// ── the contract with vigiles, checked before anything relies on it ──────────────────────
|
|
49
60
|
check(
|
|
50
61
|
"🔴 vigiles/claude-code exports claudeCodeHookProtocol.mergeRegistrations — the merge init calls",
|
|
51
|
-
typeof claudeCodeHookProtocol
|
|
62
|
+
typeof claudeCodeHookProtocol.mergeRegistrations === "function",
|
|
52
63
|
);
|
|
53
64
|
|
|
54
65
|
// ── the one source: plugin/hooks/hooks.json ─────────────────────────────────────────────
|
|
@@ -82,7 +93,7 @@ check(
|
|
|
82
93
|
try {
|
|
83
94
|
shippedWiring(file);
|
|
84
95
|
} catch (e) {
|
|
85
|
-
thrown = e.message;
|
|
96
|
+
thrown = e instanceof Error ? e.message : String(e);
|
|
86
97
|
}
|
|
87
98
|
rmSync(file);
|
|
88
99
|
check(
|
|
@@ -109,7 +120,7 @@ try {
|
|
|
109
120
|
|
|
110
121
|
// ── which spelling runs which hook ─────────────────────────────────────────────────────
|
|
111
122
|
const ours = `node "\${CLAUDE_PROJECT_DIR}/${MANAGED_BY}" hook paper-edit-guard`;
|
|
112
|
-
const cases = [
|
|
123
|
+
const cases: [string, { name: string; ours: boolean } | null][] = [
|
|
113
124
|
[ours, { name: "paper-edit-guard", ours: true }],
|
|
114
125
|
[
|
|
115
126
|
`node "$CLAUDE_PROJECT_DIR/${MANAGED_BY}" hook paper-status-gates`,
|
|
@@ -140,7 +151,7 @@ try {
|
|
|
140
151
|
JSON.stringify(hookRun(cmd)) === JSON.stringify(want),
|
|
141
152
|
);
|
|
142
153
|
|
|
143
|
-
const project = (name, settings) => {
|
|
154
|
+
const project = (name: string, settings?: Settings | string) => {
|
|
144
155
|
const dir = join(work, name);
|
|
145
156
|
mkdirSync(join(dir, ".claude"), { recursive: true });
|
|
146
157
|
if (settings !== undefined)
|
|
@@ -152,14 +163,19 @@ try {
|
|
|
152
163
|
);
|
|
153
164
|
return dir;
|
|
154
165
|
};
|
|
155
|
-
const text = (dir) => readFileSync(join(dir, SETTINGS_PATH), "utf8");
|
|
166
|
+
const text = (dir: string) => readFileSync(join(dir, SETTINGS_PATH), "utf8");
|
|
167
|
+
const settingsIn = (dir: string): Settings => {
|
|
168
|
+
const r = readSettings(dir);
|
|
169
|
+
if (r.status === "unparsable") throw new Error(r.reason);
|
|
170
|
+
return r.settings;
|
|
171
|
+
};
|
|
156
172
|
|
|
157
173
|
// ── a fresh project ────────────────────────────────────────────────────────────────────
|
|
158
174
|
{
|
|
159
175
|
const dir = join(work, "fresh");
|
|
160
176
|
mkdirSync(dir);
|
|
161
177
|
const r = wireHooks(dir, merge, wiring);
|
|
162
|
-
const s =
|
|
178
|
+
const s = settingsIn(dir);
|
|
163
179
|
check(
|
|
164
180
|
"no settings file → it is created with the three hooks",
|
|
165
181
|
r.status === "written" &&
|
|
@@ -189,14 +205,14 @@ try {
|
|
|
189
205
|
},
|
|
190
206
|
});
|
|
191
207
|
wireHooks(dir, merge, wiring);
|
|
192
|
-
const s =
|
|
208
|
+
const s = settingsIn(dir);
|
|
193
209
|
check(
|
|
194
210
|
"🔴 the user's own hook in the SAME matcher survives",
|
|
195
211
|
JSON.stringify(s).includes("node my-own-lint.mjs"),
|
|
196
212
|
);
|
|
197
213
|
check(
|
|
198
214
|
"and every other key is kept (permissions)",
|
|
199
|
-
s
|
|
215
|
+
isDeepStrictEqual(s["permissions"], { allow: ["Bash(ls:*)"] }),
|
|
200
216
|
);
|
|
201
217
|
check(
|
|
202
218
|
"and ours are there, once each",
|
|
@@ -243,11 +259,14 @@ try {
|
|
|
243
259
|
);
|
|
244
260
|
check(
|
|
245
261
|
"the report names the hook and the command it found",
|
|
246
|
-
r.
|
|
262
|
+
r.status === "foreign" &&
|
|
263
|
+
r.found.length === 1 &&
|
|
264
|
+
r.found[0]?.name === "paper-edit-guard",
|
|
247
265
|
);
|
|
248
266
|
check(
|
|
249
267
|
"and names the hooks that are not wired in ANY form",
|
|
250
|
-
r.
|
|
268
|
+
r.status === "foreign" &&
|
|
269
|
+
r.missing.join() === "paper-skills-nudge,paper-status-gates",
|
|
251
270
|
);
|
|
252
271
|
// The control: without the check, vigiles' merge WOULD add a second copy. This is the
|
|
253
272
|
// reason the check exists, measured rather than asserted.
|
|
@@ -257,7 +276,7 @@ try {
|
|
|
257
276
|
);
|
|
258
277
|
check(
|
|
259
278
|
"(control) vigiles' merge alone would wire paper-edit-guard TWICE here",
|
|
260
|
-
c
|
|
279
|
+
c?.ours === 1 && c.other === 1,
|
|
261
280
|
);
|
|
262
281
|
}
|
|
263
282
|
|
|
@@ -276,7 +295,7 @@ try {
|
|
|
276
295
|
}
|
|
277
296
|
|
|
278
297
|
// ── doctor's section ───────────────────────────────────────────────────────────────────
|
|
279
|
-
const doc = (settings) => {
|
|
298
|
+
const doc = (settings?: Settings | string) => {
|
|
280
299
|
const dir = project(`doc-${String(check.count)}`, settings);
|
|
281
300
|
return doctorHooks(dir, wiring).join("\n");
|
|
282
301
|
};
|
|
@@ -291,8 +310,8 @@ try {
|
|
|
291
310
|
"nothing wired → ⚠ not wired, with the remedy",
|
|
292
311
|
/⚠ not wired — none of the 3 hooks/.test(doc({})),
|
|
293
312
|
);
|
|
294
|
-
const twice =
|
|
295
|
-
twice.hooks.PreToolUse[0]
|
|
313
|
+
const twice = PreToolUseSettings.parse(structuredClone(wiredOnce));
|
|
314
|
+
twice.hooks.PreToolUse[0]?.hooks.push({
|
|
296
315
|
type: "command",
|
|
297
316
|
command: "npx paperlint hook paper-edit-guard",
|
|
298
317
|
});
|
package/src/hooks-settings.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
BIN_FILE,
|
|
31
31
|
PACKAGE_NAME,
|
|
32
32
|
} from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
33
|
+
import { messageOf } from "./domain/text.ts";
|
|
33
34
|
|
|
34
35
|
/** The committed, shared settings file — husky's `.husky/` analogue, except git carries it. */
|
|
35
36
|
export const SETTINGS_PATH = join(".claude", "settings.json");
|
|
@@ -133,17 +134,23 @@ export function hookRun(command: string): {
|
|
|
133
134
|
export function commandsIn(
|
|
134
135
|
settings: Settings,
|
|
135
136
|
): { readonly event: string; readonly command: string }[] {
|
|
136
|
-
const hooks = settings["hooks"];
|
|
137
|
-
if (!hooks || typeof hooks !== "object") return [];
|
|
138
137
|
const out: { event: string; command: string }[] = [];
|
|
139
|
-
for (const [event, entries] of Object.entries(hooks
|
|
140
|
-
for (const entry of
|
|
141
|
-
for (const h of
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
for (const [event, entries] of Object.entries(prop(settings["hooks"]) ?? {}))
|
|
139
|
+
for (const entry of listOf(entries))
|
|
140
|
+
for (const h of listOf(prop(entry)?.["hooks"])) {
|
|
141
|
+
const command = prop(h)?.["command"];
|
|
142
|
+
if (typeof command === "string") out.push({ event, command });
|
|
143
|
+
}
|
|
144
144
|
return out;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/** The settings file is the user's: any level may be missing or of the wrong kind. */
|
|
148
|
+
const listOf = (v: unknown): readonly unknown[] => (Array.isArray(v) ? v : []);
|
|
149
|
+
const prop = (v: unknown): Readonly<Record<string, unknown>> | undefined =>
|
|
150
|
+
typeof v === "object" && v !== null
|
|
151
|
+
? Object.fromEntries(Object.entries(v))
|
|
152
|
+
: undefined;
|
|
153
|
+
|
|
147
154
|
export interface Wiring {
|
|
148
155
|
readonly compiled: HooksMap;
|
|
149
156
|
/** Derived from the commands, in order. Never empty — an empty set throws. */
|
|
@@ -195,7 +202,11 @@ export function wiredCounts(
|
|
|
195
202
|
const total = (c: HookCount): number => c.ours + c.other;
|
|
196
203
|
|
|
197
204
|
export type SettingsRead =
|
|
198
|
-
| {
|
|
205
|
+
| {
|
|
206
|
+
readonly status: "absent";
|
|
207
|
+
readonly path: string;
|
|
208
|
+
readonly settings: Settings;
|
|
209
|
+
}
|
|
199
210
|
| {
|
|
200
211
|
readonly status: "read";
|
|
201
212
|
readonly path: string;
|
|
@@ -218,7 +229,7 @@ export function readSettings(root: string): SettingsRead {
|
|
|
218
229
|
return { status: "unparsable", path, reason: "not a JSON object" };
|
|
219
230
|
return { status: "read", path, settings: parsed as Settings, raw };
|
|
220
231
|
} catch (e) {
|
|
221
|
-
return { status: "unparsable", path, reason: (e
|
|
232
|
+
return { status: "unparsable", path, reason: messageOf(e) };
|
|
222
233
|
}
|
|
223
234
|
}
|
|
224
235
|
|
package/src/init.test.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
reportTexLive,
|
|
35
35
|
reportWorkflow,
|
|
36
36
|
} from "./init.ts";
|
|
37
|
-
import { runNode } from "../test/support.
|
|
37
|
+
import { runNode } from "../test/support.ts";
|
|
38
38
|
|
|
39
39
|
const dirs: string[] = [];
|
|
40
40
|
afterEach(() => {
|
|
@@ -81,7 +81,9 @@ describe("paperlint init — a declared papers directory", () => {
|
|
|
81
81
|
expect(code).toBe(0);
|
|
82
82
|
expect(section).toMatch(/✓ papers — declared in paperlint\.json/);
|
|
83
83
|
expect(section).not.toMatch(/candidates|eslint-rules|drafts/);
|
|
84
|
-
const cfg = JSON.parse(
|
|
84
|
+
const cfg: unknown = JSON.parse(
|
|
85
|
+
readFileSync(join(root, "paperlint.json"), "utf8"),
|
|
86
|
+
);
|
|
85
87
|
expect(cfg).toEqual({ papersDir: "papers" });
|
|
86
88
|
});
|
|
87
89
|
|
|
@@ -93,7 +95,7 @@ describe("paperlint init — a declared papers directory", () => {
|
|
|
93
95
|
log: () => {},
|
|
94
96
|
err: () => {},
|
|
95
97
|
interactive: true,
|
|
96
|
-
ask:
|
|
98
|
+
ask: (q) => Promise.resolve((asked.push(q), "n")),
|
|
97
99
|
hooks: false,
|
|
98
100
|
run: (() => ({ status: 0 })) as never,
|
|
99
101
|
link: () => ({ ok: false, error: "not linked in this test" }),
|
|
@@ -165,7 +167,8 @@ const texRun = async (
|
|
|
165
167
|
log: (s: string) => out.push(s),
|
|
166
168
|
err: () => {},
|
|
167
169
|
interactive,
|
|
168
|
-
ask:
|
|
170
|
+
ask: (q) =>
|
|
171
|
+
Promise.resolve((asked.push(q), /TeX Live/.test(q) ? answer : "")),
|
|
169
172
|
hooks: false,
|
|
170
173
|
run: (() => ({ status: 0 })) as never,
|
|
171
174
|
link: () => ({ ok: false as const, error: "not linked in this test" }),
|
|
@@ -301,7 +304,7 @@ describe("paperlint init — how several candidate directories were decided, sai
|
|
|
301
304
|
it("picked by a human", async () => {
|
|
302
305
|
const r = await initRun(several(), {
|
|
303
306
|
interactive: true,
|
|
304
|
-
ask:
|
|
307
|
+
ask: () => Promise.resolve("2"),
|
|
305
308
|
});
|
|
306
309
|
expect(section(r.out)).toEqual([
|
|
307
310
|
" ✓ writing — you picked it out of 2 candidates",
|
|
@@ -319,7 +322,7 @@ describe("paperlint init — how several candidate directories were decided, sai
|
|
|
319
322
|
it("an answer that picks nothing: the first, and said so", async () => {
|
|
320
323
|
const r = await initRun(several(), {
|
|
321
324
|
interactive: true,
|
|
322
|
-
ask:
|
|
325
|
+
ask: () => Promise.resolve("nine"),
|
|
323
326
|
});
|
|
324
327
|
expect(section(r.out)[0]).toBe(
|
|
325
328
|
" ✓ drafts — 2 candidates, no answer was given, so the first one was taken",
|
|
@@ -433,7 +436,7 @@ describe("paperlint init — TeX Live and hooks failures, whole", () => {
|
|
|
433
436
|
it("TeX Live: a failed install says to run it again", async () => {
|
|
434
437
|
const t = await offerTexLive(
|
|
435
438
|
{ installed: () => false, install: () => 1 },
|
|
436
|
-
{ interactive: true, ask:
|
|
439
|
+
{ interactive: true, ask: () => Promise.resolve("yes") },
|
|
437
440
|
);
|
|
438
441
|
expect([t, reportTexLive(t, "")]).toEqual([
|
|
439
442
|
"failed",
|
|
@@ -462,8 +465,8 @@ describe("paperlint init — a first paper", () => {
|
|
|
462
465
|
const made: string[] = [];
|
|
463
466
|
const r = await initRun(tree({}), {
|
|
464
467
|
interactive: true,
|
|
465
|
-
ask:
|
|
466
|
-
createPaper:
|
|
468
|
+
ask: () => Promise.resolve(""),
|
|
469
|
+
createPaper: (_root, name) => Promise.resolve((made.push(name), 0)),
|
|
467
470
|
});
|
|
468
471
|
expect(made).toEqual([]);
|
|
469
472
|
expect(r.out[r.out.indexOf("first paper") + 1]).toBe(
|
package/src/init.ts
CHANGED
|
@@ -64,13 +64,15 @@ import {
|
|
|
64
64
|
nameProblem,
|
|
65
65
|
type PaperFormat,
|
|
66
66
|
} from "./new-paper.ts";
|
|
67
|
-
// The one source for the consumer's config key lives in the
|
|
68
|
-
//
|
|
67
|
+
// The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
|
|
68
|
+
// import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
|
|
69
|
+
// and from dist/ — see CONTRIBUTING.md.
|
|
69
70
|
import {
|
|
70
71
|
CONFIG_FILE,
|
|
71
72
|
DEFAULT_PAPERS_ROOT,
|
|
72
73
|
PAPERS_DIR_FIELD,
|
|
73
|
-
} from "
|
|
74
|
+
} from "#lib/paper-config";
|
|
75
|
+
import { messageOf } from "./domain/text.ts";
|
|
74
76
|
|
|
75
77
|
/** How the papers directory was arrived at. Printed, because a guess must not read as a fact. */
|
|
76
78
|
export type PapersHow =
|
|
@@ -122,13 +124,21 @@ export function interactivity({
|
|
|
122
124
|
return { interactive: true, why: "a terminal on both ends" };
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Whether a standard stream is a terminal. Node types `isTTY` as `boolean`, but on a stream that
|
|
129
|
+
* is NOT a terminal (a pipe, a file) the property is absent — `undefined` — so it is read as the
|
|
130
|
+
* optional it really is.
|
|
131
|
+
*/
|
|
132
|
+
const isTty = (stream: { readonly isTTY?: boolean }): boolean =>
|
|
133
|
+
stream.isTTY === true;
|
|
134
|
+
|
|
125
135
|
/** The mode of THIS process — the one place `process` is read for it. */
|
|
126
136
|
export const processInteractivity = (yes: boolean) =>
|
|
127
137
|
interactivity({
|
|
128
138
|
// eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
|
|
129
|
-
stdinTTY:
|
|
139
|
+
stdinTTY: isTty(process.stdin),
|
|
130
140
|
// eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
|
|
131
|
-
stdoutTTY:
|
|
141
|
+
stdoutTTY: isTty(process.stdout),
|
|
132
142
|
// eslint-disable-next-line no-restricted-globals -- legacy I/O, moves behind a port in #76
|
|
133
143
|
env: process.env,
|
|
134
144
|
yes,
|
|
@@ -222,7 +232,7 @@ export async function declarePapers(
|
|
|
222
232
|
try {
|
|
223
233
|
settings = JSON.parse(raw) as Record<string, unknown>;
|
|
224
234
|
} catch (e) {
|
|
225
|
-
return { status: "unparsable", path, reason: (e
|
|
235
|
+
return { status: "unparsable", path, reason: messageOf(e) };
|
|
226
236
|
}
|
|
227
237
|
const existing = settings[PAPERS_DIR_FIELD];
|
|
228
238
|
if (existing !== undefined)
|
|
@@ -289,7 +299,7 @@ export async function offerWorkflow(
|
|
|
289
299
|
ask?: (q: string) => Promise<string>;
|
|
290
300
|
interactive: boolean;
|
|
291
301
|
/** The running package's version; see `InitOptions.version`. */
|
|
292
|
-
version?: string;
|
|
302
|
+
version?: string | undefined;
|
|
293
303
|
},
|
|
294
304
|
): Promise<WorkflowResult> {
|
|
295
305
|
const path = join(root, WORKFLOW_PATH);
|
|
@@ -317,7 +327,7 @@ export function reportWorkflow(
|
|
|
317
327
|
version,
|
|
318
328
|
papersDir,
|
|
319
329
|
why,
|
|
320
|
-
}: { version?: string; papersDir: string; why: string },
|
|
330
|
+
}: { version?: string | undefined; papersDir: string; why: string },
|
|
321
331
|
): string[] {
|
|
322
332
|
const ref = actionRef(version);
|
|
323
333
|
const out: string[] = [];
|
|
@@ -406,7 +416,7 @@ export async function offerHooks(
|
|
|
406
416
|
hooks: boolean;
|
|
407
417
|
interactive: boolean;
|
|
408
418
|
ask?: (q: string) => Promise<string>;
|
|
409
|
-
merge?: Merge;
|
|
419
|
+
merge?: Merge | undefined;
|
|
410
420
|
},
|
|
411
421
|
): Promise<HooksOutcome> {
|
|
412
422
|
if (!hooks) return { status: "skipped" };
|
|
@@ -438,7 +448,7 @@ export async function offerHooks(
|
|
|
438
448
|
}
|
|
439
449
|
return wireHooks(root, m, shippedWiring());
|
|
440
450
|
} catch (e) {
|
|
441
|
-
return { status: "failed", reason: (e
|
|
451
|
+
return { status: "failed", reason: messageOf(e) };
|
|
442
452
|
}
|
|
443
453
|
}
|
|
444
454
|
|
|
@@ -560,7 +570,7 @@ export interface InitOptions {
|
|
|
560
570
|
/** `false` is `--no-hooks`. */
|
|
561
571
|
hooks?: boolean;
|
|
562
572
|
/** vigiles' merge. Injected only so a test can observe or replace it. */
|
|
563
|
-
merge?: Merge;
|
|
573
|
+
merge?: Merge | undefined;
|
|
564
574
|
/** `--paper <name>`: create this paper, even without a terminal. */
|
|
565
575
|
paper?: string | null;
|
|
566
576
|
/** `--format tex|md` for that paper. */
|
|
@@ -586,7 +596,7 @@ export interface InitOptions {
|
|
|
586
596
|
* The version of the running package, read by the CLI from its own `package.json`. The CI
|
|
587
597
|
* workflow is pinned to its release tag (`actionRef`); absent or unreleased, the placeholder.
|
|
588
598
|
*/
|
|
589
|
-
version?: string;
|
|
599
|
+
version?: string | undefined;
|
|
590
600
|
/**
|
|
591
601
|
* TeX Live for `paperlint build`: whether paperlint's own tree is installed, and how to install
|
|
592
602
|
* it (`paperlint toolchain`). Passed in by the CLI; without them the step only names the command.
|
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
* memory: the two error spellings `-file-line-error` produces, the missing-package form, and a
|
|
6
6
|
* marker that TeX broke at column 79.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { createChecker } from "../lib/check.mjs";
|
|
8
|
+
import { createChecker } from "../lib/check.ts";
|
|
9
|
+
import type { LogMarker } from "./latex-log.ts";
|
|
11
10
|
|
|
12
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
11
|
const {
|
|
14
12
|
unwrapLog,
|
|
15
13
|
logMarkers,
|
|
@@ -18,7 +16,7 @@ const {
|
|
|
18
16
|
bibtexExcerpt,
|
|
19
17
|
auxBib,
|
|
20
18
|
MAX_PRINT_LINE,
|
|
21
|
-
} = await import(
|
|
19
|
+
} = await import("./latex-log.ts");
|
|
22
20
|
|
|
23
21
|
const check = createChecker();
|
|
24
22
|
|
|
@@ -78,13 +76,15 @@ const firstPass = [
|
|
|
78
76
|
const m = logMarkers(firstPass);
|
|
79
77
|
check(
|
|
80
78
|
"every documented marker is recognised",
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
(
|
|
80
|
+
[
|
|
81
|
+
"rerun-requested",
|
|
82
|
+
"labels-changed",
|
|
83
|
+
"rerunfilecheck",
|
|
84
|
+
"undefined-references",
|
|
85
|
+
"undefined-citations",
|
|
86
|
+
] satisfies LogMarker[]
|
|
87
|
+
).every((k) => m.includes(k)),
|
|
88
88
|
);
|
|
89
89
|
check(
|
|
90
90
|
"a clean log carries no markers",
|
|
@@ -96,7 +96,7 @@ check(
|
|
|
96
96
|
// Guards: matching the documented summary line rather than the word 'undefined'.
|
|
97
97
|
check(
|
|
98
98
|
"a single reference warning is not the end-of-run summary",
|
|
99
|
-
!logMarkers(
|
|
99
|
+
!logMarkers(firstPass.slice(0, 1)).includes("undefined-references"),
|
|
100
100
|
);
|
|
101
101
|
|
|
102
102
|
// ── error excerpt ───────────────────────────────────────────────────────────────────────
|
|
@@ -114,7 +114,7 @@ check(
|
|
|
114
114
|
"-file-line-error form: the error line, the l.NNN line and the rest of the source line",
|
|
115
115
|
ex[0] === "./paper.tex:4: Undefined control sequence." &&
|
|
116
116
|
ex[1] === "l.4 \\foo" &&
|
|
117
|
-
ex[2].includes("bar baz"),
|
|
117
|
+
(ex[2] ?? "").includes("bar baz"),
|
|
118
118
|
);
|
|
119
119
|
// Guards: the end of the excerpt — without it the excerpt runs into TeX's memory statistics and
|
|
120
120
|
// buries the line the author needs.
|