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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* hooks.harness.
|
|
2
|
+
* hooks.harness.ts — the three shipped hooks, each asserted in BOTH halves.
|
|
3
3
|
*
|
|
4
4
|
* 🔴 WHY "BOTH HALVES" IS THE ORGANISING RULE HERE AND NOT A STYLE. Two of these three are
|
|
5
5
|
* ADVISORY: silence is their success state. So "it printed nothing" and "it is dead" look
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* `checkHookImports`, over the shipped files directly. That is STRICTER than compiling a twin
|
|
21
21
|
* `.ts` source would be: a twin can drift from its build, and there is no twin here to drift.
|
|
22
22
|
*
|
|
23
|
-
* Run: `npx vigiles test hooks/hooks.harness.
|
|
23
|
+
* Run: `npx vigiles test hooks/hooks.harness.ts`
|
|
24
24
|
*/
|
|
25
|
-
import { runHook } from "vigiles";
|
|
25
|
+
import { runHook, type HookInput, type HookRunResult } from "vigiles";
|
|
26
26
|
import { checkHookImports } from "vigiles/hook";
|
|
27
27
|
import {
|
|
28
28
|
mkdtempSync,
|
|
@@ -37,8 +37,9 @@ import {
|
|
|
37
37
|
import { tmpdir } from "node:os";
|
|
38
38
|
import { dirname, join, resolve } from "node:path";
|
|
39
39
|
import { fileURLToPath } from "node:url";
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
40
|
+
import { z } from "zod";
|
|
41
|
+
import { PAPERS_DIR_FIELD } from "../lib/paper-config.ts";
|
|
42
|
+
import { createChecker } from "../lib/check.ts";
|
|
42
43
|
|
|
43
44
|
const HOOKS = dirname(fileURLToPath(import.meta.url));
|
|
44
45
|
const ROOT = resolve(HOOKS, "..");
|
|
@@ -64,7 +65,13 @@ const check = createChecker();
|
|
|
64
65
|
* installed rather than adjacent. The `node_modules/<pkg>` entry is a symlink to this very
|
|
65
66
|
* checkout, which is what `npm install` of a local package does anyway.
|
|
66
67
|
*/
|
|
67
|
-
const consumer = (
|
|
68
|
+
const consumer = (
|
|
69
|
+
block: unknown,
|
|
70
|
+
{
|
|
71
|
+
papers = "docs/papers",
|
|
72
|
+
paper = "alpha",
|
|
73
|
+
}: { papers?: string | null; paper?: string } = {},
|
|
74
|
+
) => {
|
|
68
75
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-hooks-")));
|
|
69
76
|
const nm = join(dir, "node_modules");
|
|
70
77
|
mkdirSync(nm, { recursive: true });
|
|
@@ -84,24 +91,28 @@ const consumer = (block, { papers = "docs/papers", paper = "alpha" } = {}) => {
|
|
|
84
91
|
};
|
|
85
92
|
|
|
86
93
|
/** `run-program <hook>` exactly as a consumer's settings block invokes it. */
|
|
87
|
-
const program = (name) =>
|
|
94
|
+
const program = (name: string) =>
|
|
88
95
|
`node ${JSON.stringify(CLI)} hook-runtime run-program ${JSON.stringify(join(HOOKS, name + ".hook.mjs"))}`;
|
|
89
96
|
|
|
90
|
-
const onBash = (command) => ({
|
|
97
|
+
const onBash = (command: string): HookInput => ({
|
|
91
98
|
hook_event_name: "PreToolUse",
|
|
92
99
|
tool_name: "Bash",
|
|
93
100
|
tool_input: { command },
|
|
94
101
|
});
|
|
95
|
-
const onEdit = (file_path) => ({
|
|
102
|
+
const onEdit = (file_path: string): HookInput => ({
|
|
96
103
|
hook_event_name: "PostToolUse",
|
|
97
104
|
tool_name: "Edit",
|
|
98
105
|
tool_input: { file_path },
|
|
99
106
|
tool_response: {},
|
|
100
107
|
});
|
|
101
|
-
const STOP
|
|
108
|
+
const STOP: HookInput = {
|
|
109
|
+
hook_event_name: "Stop",
|
|
110
|
+
tool_name: "",
|
|
111
|
+
tool_input: {},
|
|
112
|
+
};
|
|
102
113
|
|
|
103
114
|
/** Run a hook AS a consumer: cwd and `CLAUDE_PROJECT_DIR` both point at the fixture repo. */
|
|
104
|
-
const at = (dir, name, input) =>
|
|
115
|
+
const at = (dir: string, name: string, input: HookInput): HookRunResult =>
|
|
105
116
|
runHook(program(name), input, { cwd: dir, env: { CLAUDE_PROJECT_DIR: dir } });
|
|
106
117
|
|
|
107
118
|
/**
|
|
@@ -116,23 +127,29 @@ const at = (dir, name, input) =>
|
|
|
116
127
|
* live case: a session `cd`s into a subtree, and vigiles runs every provider «via execSync in
|
|
117
128
|
* the hook's cwd».
|
|
118
129
|
*/
|
|
119
|
-
const adrift = (dir, name, input) =>
|
|
130
|
+
const adrift = (dir: string, name: string, input: HookInput): HookRunResult =>
|
|
120
131
|
runHook(program(name), input, {
|
|
121
132
|
cwd: realpathSync(mkdtempSync(join(tmpdir(), "drift-"))),
|
|
122
133
|
env: { CLAUDE_PROJECT_DIR: dir },
|
|
123
134
|
});
|
|
124
135
|
|
|
125
136
|
/** An injected notice, as the model would receive it — not the returned reaction. */
|
|
126
|
-
const
|
|
137
|
+
const Injected = z.object({
|
|
138
|
+
hookSpecificOutput: z.object({ additionalContext: z.string().nullish() }),
|
|
139
|
+
});
|
|
140
|
+
const injected = (r: HookRunResult): string => {
|
|
127
141
|
try {
|
|
128
|
-
return
|
|
142
|
+
return (
|
|
143
|
+
Injected.parse(JSON.parse(r.stdout)).hookSpecificOutput
|
|
144
|
+
.additionalContext ?? ""
|
|
145
|
+
);
|
|
129
146
|
} catch {
|
|
130
147
|
return "";
|
|
131
148
|
}
|
|
132
149
|
};
|
|
133
150
|
|
|
134
|
-
const tmps = [];
|
|
135
|
-
const fixture = (...args) => {
|
|
151
|
+
const tmps: string[] = [];
|
|
152
|
+
const fixture = (...args: Parameters<typeof consumer>) => {
|
|
136
153
|
const d = consumer(...args);
|
|
137
154
|
tmps.push(d);
|
|
138
155
|
return d;
|
|
@@ -191,10 +208,10 @@ try {
|
|
|
191
208
|
const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
|
|
192
209
|
const P = "docs/papers/alpha/paper.md";
|
|
193
210
|
const T = "docs/papers/alpha/paper.tex";
|
|
194
|
-
const deny = (label, cmd) => {
|
|
211
|
+
const deny = (label: string, cmd: string) => {
|
|
195
212
|
const r = at(dir, "paper-edit-guard", onBash(cmd));
|
|
196
213
|
check(
|
|
197
|
-
`guard BLOCKS ${label} (rc=${r.exitCode})`,
|
|
214
|
+
`guard BLOCKS ${label} (rc=${String(r.exitCode)})`,
|
|
198
215
|
r.exitCode === 2 && r.blocked,
|
|
199
216
|
);
|
|
200
217
|
check(
|
|
@@ -202,10 +219,10 @@ try {
|
|
|
202
219
|
/Use Edit or Write/.test(r.stderr),
|
|
203
220
|
);
|
|
204
221
|
};
|
|
205
|
-
const allow = (label, cmd) => {
|
|
222
|
+
const allow = (label: string, cmd: string) => {
|
|
206
223
|
const r = at(dir, "paper-edit-guard", onBash(cmd));
|
|
207
224
|
check(
|
|
208
|
-
`guard ALLOWS ${label} (rc=${r.exitCode}, ${r.stderr.length}b err)`,
|
|
225
|
+
`guard ALLOWS ${label} (rc=${String(r.exitCode)}, ${String(r.stderr.length)}b err)`,
|
|
209
226
|
r.exitCode === 0 && !r.blocked,
|
|
210
227
|
);
|
|
211
228
|
};
|
|
@@ -292,7 +309,7 @@ try {
|
|
|
292
309
|
check("drift · guard still blocks a paper write", write.exitCode === 2);
|
|
293
310
|
const idle = adrift(dir, "paper-edit-guard", onBash("echo hi"));
|
|
294
311
|
check(
|
|
295
|
-
`drift · guard does NOT deny an unrelated command (rc=${idle.exitCode})`,
|
|
312
|
+
`drift · guard does NOT deny an unrelated command (rc=${String(idle.exitCode)})`,
|
|
296
313
|
idle.exitCode === 0,
|
|
297
314
|
);
|
|
298
315
|
const guarded = adrift(
|
|
@@ -318,7 +335,7 @@ try {
|
|
|
318
335
|
onEdit(`${dir}/writing/drafts/alpha/PIPELINE-STATUS.md`),
|
|
319
336
|
);
|
|
320
337
|
check(
|
|
321
|
-
`drift · the status-gates hook still runs (rc=${gates.exitCode})`,
|
|
338
|
+
`drift · the status-gates hook still runs (rc=${String(gates.exitCode)})`,
|
|
322
339
|
gates.exitCode === 0,
|
|
323
340
|
);
|
|
324
341
|
}
|
|
@@ -381,7 +398,7 @@ try {
|
|
|
381
398
|
const dir = fixture({ [PAPERS_DIR_FIELD]: null });
|
|
382
399
|
const r = at(dir, "paper-edit-guard", harmless);
|
|
383
400
|
check(
|
|
384
|
-
`carrier: "${PAPERS_DIR_FIELD}": null REFUSES (rc=${r.exitCode})`,
|
|
401
|
+
`carrier: "${PAPERS_DIR_FIELD}": null REFUSES (rc=${String(r.exitCode)})`,
|
|
385
402
|
r.exitCode === 2,
|
|
386
403
|
);
|
|
387
404
|
check(
|
|
@@ -394,7 +411,7 @@ try {
|
|
|
394
411
|
const dir = fixture({ [PAPERS_DIR_FIELD]: "" });
|
|
395
412
|
const r = at(dir, "paper-edit-guard", harmless);
|
|
396
413
|
check(
|
|
397
|
-
`carrier: "${PAPERS_DIR_FIELD}": "" REFUSES (rc=${r.exitCode})`,
|
|
414
|
+
`carrier: "${PAPERS_DIR_FIELD}": "" REFUSES (rc=${String(r.exitCode)})`,
|
|
398
415
|
r.exitCode === 2,
|
|
399
416
|
);
|
|
400
417
|
check(
|
|
@@ -414,7 +431,7 @@ try {
|
|
|
414
431
|
);
|
|
415
432
|
const r = at(dir, "paper-edit-guard", harmless);
|
|
416
433
|
check(
|
|
417
|
-
`carrier: an unparseable paperlint.json REFUSES (rc=${r.exitCode})`,
|
|
434
|
+
`carrier: an unparseable paperlint.json REFUSES (rc=${String(r.exitCode)})`,
|
|
418
435
|
r.exitCode === 2,
|
|
419
436
|
);
|
|
420
437
|
check(
|
|
@@ -442,11 +459,11 @@ try {
|
|
|
442
459
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
443
460
|
{
|
|
444
461
|
const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
|
|
445
|
-
const lands = (label, p) => {
|
|
462
|
+
const lands = (label: string, p: string) => {
|
|
446
463
|
const r = at(dir, "paper-skills-nudge", onEdit(p));
|
|
447
464
|
const ctx = injected(r);
|
|
448
465
|
check(
|
|
449
|
-
`nudge LANDS on ${label} (${r.stdout.length}b stdout)`,
|
|
466
|
+
`nudge LANDS on ${label} (${String(r.stdout.length)}b stdout)`,
|
|
450
467
|
ctx.length > 0,
|
|
451
468
|
);
|
|
452
469
|
check(
|
|
@@ -455,10 +472,10 @@ try {
|
|
|
455
472
|
);
|
|
456
473
|
check(`nudge on ${label} exits 0`, r.exitCode === 0);
|
|
457
474
|
};
|
|
458
|
-
const silent = (label, p) => {
|
|
475
|
+
const silent = (label: string, p: string) => {
|
|
459
476
|
const r = at(dir, "paper-skills-nudge", onEdit(p));
|
|
460
477
|
check(
|
|
461
|
-
`nudge SILENT on ${label} (${r.stdout.length}b stdout, ${r.stderr.length}b stderr)`,
|
|
478
|
+
`nudge SILENT on ${label} (${String(r.stdout.length)}b stdout, ${String(r.stderr.length)}b stderr)`,
|
|
462
479
|
r.stdout.length === 0 && r.stderr.length === 0 && r.exitCode === 0,
|
|
463
480
|
);
|
|
464
481
|
};
|
|
@@ -505,15 +522,15 @@ try {
|
|
|
505
522
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
506
523
|
{
|
|
507
524
|
const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
|
|
508
|
-
const fires = (label, p) => {
|
|
525
|
+
const fires = (label: string, p: string) => {
|
|
509
526
|
const r = at(dir, "paper-status-gates", onEdit(p));
|
|
510
527
|
check(
|
|
511
|
-
`gates FIRES on ${label} (${r.stderr.length}b stderr)`,
|
|
528
|
+
`gates FIRES on ${label} (${String(r.stderr.length)}b stderr)`,
|
|
512
529
|
/fixture verdict line/.test(r.stderr),
|
|
513
530
|
);
|
|
514
531
|
check(`gates on ${label} exits 0`, r.exitCode === 0);
|
|
515
532
|
};
|
|
516
|
-
const silent = (label, p) => {
|
|
533
|
+
const silent = (label: string, p: string) => {
|
|
517
534
|
const r = at(dir, "paper-status-gates", onEdit(p));
|
|
518
535
|
check(
|
|
519
536
|
`gates SILENT on ${label}`,
|
|
@@ -593,7 +610,7 @@ try {
|
|
|
593
610
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
594
611
|
{
|
|
595
612
|
const dir = fixture({ [PAPERS_DIR_FIELD]: "docs/papers" });
|
|
596
|
-
const sh = (args) =>
|
|
613
|
+
const sh = (args: string) =>
|
|
597
614
|
runHook(
|
|
598
615
|
`bash ${JSON.stringify(join(HOOKS, "paper-status-gates.sh"))} ${args}`,
|
|
599
616
|
STOP,
|
|
@@ -643,7 +660,7 @@ try {
|
|
|
643
660
|
);
|
|
644
661
|
}
|
|
645
662
|
|
|
646
|
-
console.log(`hooks.harness: ${check.count} assertions passed`);
|
|
663
|
+
console.log(`hooks.harness: ${String(check.count)} assertions passed`);
|
|
647
664
|
} finally {
|
|
648
665
|
for (const d of tmps) rmSync(d, { recursive: true, force: true });
|
|
649
666
|
}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* install reverting it.
|
|
39
39
|
*
|
|
40
40
|
* The capability surface is therefore asserted HERE, on the artifact that actually executes:
|
|
41
|
-
* `hooks.harness.
|
|
41
|
+
* `hooks.harness.ts` runs `checkHookImports` over every shipped `.hook.mjs`. That is a stricter
|
|
42
42
|
* check than compiling a twin `.ts` source would be, because a twin can drift from its build and
|
|
43
43
|
* this cannot — there is only one file.
|
|
44
44
|
*
|
|
@@ -7,12 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
9
|
import { test } from "vitest";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
onBash,
|
|
13
|
-
runShippedHook,
|
|
14
|
-
} from "../test/hook-consumer.mjs";
|
|
15
|
-
import { useTempDir } from "../test/support.mjs";
|
|
10
|
+
import { hookConsumer, onBash, runShippedHook } from "../test/hook-consumer.ts";
|
|
11
|
+
import { useTempDir } from "../test/support.ts";
|
|
16
12
|
|
|
17
13
|
const dir = hookConsumer(useTempDir("paper-edit-guard-"), {
|
|
18
14
|
"papers/alpha/PIPELINE-STATUS.md": "# S\n",
|
|
@@ -22,7 +18,7 @@ const GT = String.fromCharCode(62);
|
|
|
22
18
|
const P = "papers/alpha/paper.md";
|
|
23
19
|
|
|
24
20
|
/** The guard's exit code for one Bash command: 2 denies, 0 lets it through. */
|
|
25
|
-
const guard = (command) =>
|
|
21
|
+
const guard = (command: string): number =>
|
|
26
22
|
runShippedHook("paper-edit-guard", onBash(command), dir).exitCode;
|
|
27
23
|
|
|
28
24
|
test("a `>` inside quotes, after an escaped quote, or escaped itself is not a redirection", () => {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* load at all, and a consumer-side thin spec that imports this logic is refused by
|
|
29
29
|
* `vigiles compile` and can therefore never be re-stamped. The full reasoning, with both
|
|
30
30
|
* measurements, is in that file's header; the capability surface of this one is asserted by
|
|
31
|
-
* `hooks.harness.
|
|
31
|
+
* `hooks.harness.ts` running `checkHookImports` over the shipped artifact.
|
|
32
32
|
*/
|
|
33
33
|
import {
|
|
34
34
|
experimental_defineReact,
|
|
@@ -5,12 +5,8 @@
|
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { test } from "vitest";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
onEdit,
|
|
11
|
-
runShippedHook,
|
|
12
|
-
} from "../test/hook-consumer.mjs";
|
|
13
|
-
import { useTempDir } from "../test/support.mjs";
|
|
8
|
+
import { hookConsumer, onEdit, runShippedHook } from "../test/hook-consumer.ts";
|
|
9
|
+
import { useTempDir } from "../test/support.ts";
|
|
14
10
|
|
|
15
11
|
const root = useTempDir("paper-skills-nudge-");
|
|
16
12
|
|
|
@@ -5,12 +5,8 @@
|
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { test } from "vitest";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
onEdit,
|
|
11
|
-
runShippedHook,
|
|
12
|
-
} from "../test/hook-consumer.mjs";
|
|
13
|
-
import { useTempDir } from "../test/support.mjs";
|
|
8
|
+
import { hookConsumer, onEdit, runShippedHook } from "../test/hook-consumer.ts";
|
|
9
|
+
import { useTempDir } from "../test/support.ts";
|
|
14
10
|
|
|
15
11
|
const root = useTempDir("paper-status-gates-");
|
|
16
12
|
const STATUS = "**Readiness verdict:** ✅ fixture verdict line\n";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import assert from "node:assert/strict";
|
|
10
10
|
import { recordCheck } from "vigiles";
|
|
11
|
-
import { observeAgentCli, CHARACTERIZED_CLI } from "./agent-cli-version.
|
|
11
|
+
import { observeAgentCli, CHARACTERIZED_CLI } from "./agent-cli-version.ts";
|
|
12
12
|
|
|
13
13
|
/** A spawner that records how it was called, so the CALL SHAPE is assertable, not just the result. */
|
|
14
14
|
const spawner = (result) => {
|
|
@@ -39,28 +39,42 @@ export const CHARACTERIZED_CLI = "2.1.227";
|
|
|
39
39
|
/** What `claude --version` prints: `2.1.273 (Claude Code)`. */
|
|
40
40
|
const VERSION_IN = /\b(\d+\.\d+\.\d+)\b/;
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
42
|
+
/** What one `--version` probe of the agent CLI observed. */
|
|
43
|
+
export interface CliObservation {
|
|
44
|
+
/** The binary answered `--version` with status 0. */
|
|
45
|
+
present: boolean;
|
|
46
|
+
/** The full line it printed, trimmed. */
|
|
47
|
+
raw: string;
|
|
48
|
+
/** The dotted version parsed out of it. */
|
|
49
|
+
version: string;
|
|
50
|
+
/** The version the assertions were written against. */
|
|
51
|
+
characterized: string;
|
|
52
|
+
/** Observed and characterized differ. */
|
|
53
|
+
drifted: boolean;
|
|
54
|
+
/** One line naming BOTH numbers, for the report and for failures. */
|
|
55
|
+
note: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The one process call the probe makes — injected, so every case is stageable. */
|
|
59
|
+
export type SpawnVersion = (
|
|
60
|
+
program: string,
|
|
61
|
+
args: readonly string[],
|
|
62
|
+
options: { encoding: "utf8" },
|
|
63
|
+
) => {
|
|
64
|
+
readonly status: number | null;
|
|
65
|
+
readonly stdout?: string | null;
|
|
66
|
+
readonly stderr?: string | null;
|
|
67
|
+
};
|
|
51
68
|
|
|
52
|
-
/**
|
|
53
|
-
* @param {object} deps
|
|
54
|
-
* @param {Function} deps.spawnSync injected, so every case below is stageable
|
|
55
|
-
* @param {string} [deps.program]
|
|
56
|
-
* @param {string} [deps.characterized]
|
|
57
|
-
* @returns {CliObservation}
|
|
58
|
-
*/
|
|
59
69
|
export function observeAgentCli({
|
|
60
70
|
spawnSync,
|
|
61
71
|
program = "claude",
|
|
62
72
|
characterized = CHARACTERIZED_CLI,
|
|
63
|
-
}
|
|
73
|
+
}: {
|
|
74
|
+
spawnSync: SpawnVersion;
|
|
75
|
+
program?: string;
|
|
76
|
+
characterized?: string;
|
|
77
|
+
}): CliObservation {
|
|
64
78
|
// 🔴 `encoding: "utf8"` IS THE FIX. With `stdio: "ignore"` — the shape this replaces — the
|
|
65
79
|
// child's output goes nowhere and `probe.stdout` comes back null, so everything downstream
|
|
66
80
|
// would be describing a string that was never read.
|
|
@@ -85,7 +99,7 @@ export function observeAgentCli({
|
|
|
85
99
|
if (version === "")
|
|
86
100
|
throw new Error(
|
|
87
101
|
`\`${program} --version\` exited 0 but printed no version on stdout (got ${JSON.stringify(raw)}` +
|
|
88
|
-
`${probe.stderr ? `, stderr ${JSON.stringify(
|
|
102
|
+
`${probe.stderr ? `, stderr ${JSON.stringify(probe.stderr.trim().slice(0, 200))}` : ""}). ` +
|
|
89
103
|
`A presence probe is not an observation: this is the \`stdio: "ignore"\` shape that issue #7 ` +
|
|
90
104
|
`is about, and a version nobody read must not be reported as one.`,
|
|
91
105
|
);
|
package/lib/check.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { test } from "vitest";
|
|
3
|
-
import { createChecker, failureMessage, renderDetail } from "./check.
|
|
3
|
+
import { createChecker, failureMessage, renderDetail } from "./check.ts";
|
|
4
4
|
|
|
5
5
|
test("renderDetail: a string verbatim, nothing for empty, a value inspected, a thunk called", () => {
|
|
6
6
|
assert.deepEqual(
|
|
@@ -32,7 +32,7 @@ test("createChecker: counts every call, stays silent on a pass", () => {
|
|
|
32
32
|
const printed: string[] = [];
|
|
33
33
|
const check = createChecker({ log: (s) => printed.push(s) });
|
|
34
34
|
check("a", true);
|
|
35
|
-
check("b",
|
|
35
|
+
check("b", true, "unused detail");
|
|
36
36
|
assert.deepEqual({ count: check.count, printed }, { count: 2, printed: [] });
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -40,10 +40,15 @@ test("createChecker: a failure throws AND prints the label with its detail, and
|
|
|
40
40
|
const printed: string[] = [];
|
|
41
41
|
const check = createChecker({ log: (s) => printed.push(s) });
|
|
42
42
|
// Guards: the detail reaches the reader — the drift this helper replaced dropped it.
|
|
43
|
-
assert.throws(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
assert.throws(
|
|
44
|
+
() => {
|
|
45
|
+
check("the label", false, { got: 3 });
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "AssertionError",
|
|
49
|
+
message: "the label — { got: 3 }",
|
|
50
|
+
},
|
|
51
|
+
);
|
|
47
52
|
assert.deepEqual(
|
|
48
53
|
{ count: check.count, printed },
|
|
49
54
|
{ count: 1, printed: ["✗ the label — { got: 3 }"] },
|
|
@@ -57,11 +62,11 @@ test("createChecker: a detail thunk is only evaluated on failure", () => {
|
|
|
57
62
|
calls++;
|
|
58
63
|
return "x";
|
|
59
64
|
});
|
|
60
|
-
assert.throws(() =>
|
|
65
|
+
assert.throws(() => {
|
|
61
66
|
check("fail", false, () => {
|
|
62
67
|
calls++;
|
|
63
68
|
return "x";
|
|
64
|
-
})
|
|
65
|
-
);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
66
71
|
assert.equal(calls, 1);
|
|
67
72
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The one assertion helper every
|
|
2
|
+
* The one assertion helper every harness uses.
|
|
3
3
|
*
|
|
4
4
|
* Until #52 each harness defined its own `check()`, and the copies had drifted: some printed a
|
|
5
5
|
* detail on failure and some dropped it, some joined it with " — " and some with a newline, and two
|
|
@@ -17,15 +17,27 @@ import assert from "node:assert/strict";
|
|
|
17
17
|
import { inspect } from "node:util";
|
|
18
18
|
import { recordCheck } from "vigiles";
|
|
19
19
|
|
|
20
|
+
/** A failure detail: text, any value (rendered with `util.inspect`), or a thunk producing one. */
|
|
21
|
+
export type Detail = unknown;
|
|
22
|
+
|
|
23
|
+
/** `check(label, cond, detail?)`, and how many times it has been called. */
|
|
24
|
+
export interface Check {
|
|
25
|
+
(label: string, cond: unknown, detail?: Detail): void;
|
|
26
|
+
readonly count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A detail given as a function, so it is only built on failure. */
|
|
30
|
+
const isThunk = (d: Detail): d is () => unknown => typeof d === "function";
|
|
31
|
+
|
|
20
32
|
/** A failure detail as text: strings verbatim, other values through `util.inspect`, a thunk called. */
|
|
21
|
-
export function renderDetail(detail) {
|
|
22
|
-
const value =
|
|
33
|
+
export function renderDetail(detail: Detail): string {
|
|
34
|
+
const value: unknown = isThunk(detail) ? detail() : detail;
|
|
23
35
|
if (value === undefined || value === null || value === "") return "";
|
|
24
36
|
return typeof value === "string" ? value : inspect(value, { depth: 6 });
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
/** The label, then the detail: on the same line when it is one line, below it when it is many. */
|
|
28
|
-
export function failureMessage(label, detail) {
|
|
40
|
+
export function failureMessage(label: string, detail: Detail): string {
|
|
29
41
|
const shown = renderDetail(detail);
|
|
30
42
|
if (!shown) return label;
|
|
31
43
|
return shown.includes("\n") ? `${label}\n${shown}` : `${label} — ${shown}`;
|
|
@@ -35,16 +47,17 @@ export function failureMessage(label, detail) {
|
|
|
35
47
|
* A fresh `check(label, cond, detail?)`. `check.count` is how many times it has been called.
|
|
36
48
|
* `log` is where a failure is printed — stderr unless this module's own test injects one.
|
|
37
49
|
*/
|
|
38
|
-
export function createChecker({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
export function createChecker({
|
|
51
|
+
log = console.error,
|
|
52
|
+
}: { log?: (line: string) => void } = {}): Check {
|
|
53
|
+
const check = (label: string, cond: unknown, detail?: Detail): void => {
|
|
54
|
+
check.count++;
|
|
42
55
|
recordCheck();
|
|
43
56
|
if (cond) return;
|
|
44
57
|
const message = failureMessage(label, detail);
|
|
45
58
|
log(`✗ ${message}`);
|
|
46
59
|
assert.fail(message);
|
|
47
60
|
};
|
|
48
|
-
|
|
61
|
+
check.count = 0;
|
|
49
62
|
return check;
|
|
50
63
|
}
|
package/lib/markdown.test.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { copyFileSync, mkdirSync } from "node:fs";
|
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { test } from "vitest";
|
|
13
|
-
import { runNode, useTempDir, writeTree } from "../test/support.
|
|
13
|
+
import { runNode, useTempDir, writeTree } from "../test/support.ts";
|
|
14
14
|
import {
|
|
15
15
|
blankFrontmatter,
|
|
16
16
|
fences,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
stripFences,
|
|
23
23
|
stripFrontmatter,
|
|
24
24
|
tables,
|
|
25
|
-
} from "./markdown.
|
|
25
|
+
} from "./markdown.ts";
|
|
26
26
|
|
|
27
27
|
const DOC = [
|
|
28
28
|
"---", // 0
|
|
@@ -157,13 +157,13 @@ writeTree(root, {
|
|
|
157
157
|
"import { register } from 'node:module';\n" +
|
|
158
158
|
"register('data:text/javascript,' + encodeURIComponent(\"export async function resolve(s, c, next) { if (s === 'markdown-it') throw Object.assign(new Error('gone'), { code: 'ERR_MODULE_NOT_FOUND' }); return next(s, c); }\"));\n",
|
|
159
159
|
"probe.mjs":
|
|
160
|
-
`const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.
|
|
160
|
+
`const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"))});\n` +
|
|
161
161
|
"const t = '# H\\n\\n```\\nx\\n```\\n';\n" +
|
|
162
162
|
"console.log(JSON.stringify([m.MD_AVAILABLE(), m.headings(t), m.fences(t), m.stripFences(t), m.paragraphs(t), m.tables(t)]));\n" +
|
|
163
163
|
"m.requireMarkdown();\n" +
|
|
164
164
|
"console.log('not reached');\n",
|
|
165
165
|
"probe-plugin.mjs":
|
|
166
|
-
`const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.
|
|
166
|
+
`const m = await import(${JSON.stringify(join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"))});\n` +
|
|
167
167
|
'm.requireMarkdown({ from: "file:///home/u/.claude/plugins/cache/paperlint/lib/markdown.mjs" });\n',
|
|
168
168
|
});
|
|
169
169
|
|
|
@@ -223,12 +223,12 @@ test("a copy running from a plugin cache without markdown-it is told to install
|
|
|
223
223
|
const lib = join(root, ".claude", "plugins", "paperlint", "lib");
|
|
224
224
|
mkdirSync(lib, { recursive: true });
|
|
225
225
|
copyFileSync(
|
|
226
|
-
join(dirname(fileURLToPath(import.meta.url)), "markdown.
|
|
227
|
-
join(lib, "markdown.
|
|
226
|
+
join(dirname(fileURLToPath(import.meta.url)), "markdown.ts"),
|
|
227
|
+
join(lib, "markdown.ts"),
|
|
228
228
|
);
|
|
229
229
|
writeTree(lib, {
|
|
230
230
|
"probe.mjs":
|
|
231
|
-
'const m = await import("./markdown.
|
|
231
|
+
'const m = await import("./markdown.ts");\nm.requireMarkdown();\n',
|
|
232
232
|
});
|
|
233
233
|
const r = runNode(join(lib, "probe.mjs"), []);
|
|
234
234
|
assert.deepEqual(
|