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
package/src/venue-rules.test.ts
CHANGED
|
@@ -13,11 +13,7 @@ import { describe, expect, it } from "vitest";
|
|
|
13
13
|
import { memoryFiles } from "./adapters/memory/index.ts";
|
|
14
14
|
import { sha256Hex } from "./domain/sha256.ts";
|
|
15
15
|
import { packageVenuesDir } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
16
|
-
import {
|
|
17
|
-
VENUE_RULE_LEVELS,
|
|
18
|
-
venueRules,
|
|
19
|
-
type VenueRuleModule,
|
|
20
|
-
} from "./venue-rules.ts";
|
|
16
|
+
import { VENUE_RULE_LEVELS, venueRules } from "./venue-rules.ts";
|
|
21
17
|
import { buildConfig, OPTIONAL_RULES, SHIPPED_RULES } from "./cli.ts";
|
|
22
18
|
|
|
23
19
|
const VENUES = packageVenuesDir();
|
|
@@ -34,9 +30,18 @@ const shipped = Object.fromEntries(
|
|
|
34
30
|
);
|
|
35
31
|
|
|
36
32
|
type Json = Record<string, unknown>;
|
|
33
|
+
interface Font {
|
|
34
|
+
name: string;
|
|
35
|
+
type: string;
|
|
36
|
+
embedded: boolean;
|
|
37
|
+
program: string;
|
|
38
|
+
}
|
|
39
|
+
/** The facts file as these tests edit it: free-form, except the fonts they reach into. */
|
|
40
|
+
type Facts = Json & { fonts: Font[] };
|
|
41
|
+
type Patch = (f: Facts) => void;
|
|
37
42
|
|
|
38
43
|
/** The facts of a short agenticdev paper that meets every number in its profile. */
|
|
39
|
-
function goodFacts():
|
|
44
|
+
function goodFacts(): Facts {
|
|
40
45
|
return {
|
|
41
46
|
schema: 2,
|
|
42
47
|
pdf: "paper.pdf",
|
|
@@ -118,10 +123,7 @@ function lint(
|
|
|
118
123
|
const files = memoryFiles(paperFiles(p));
|
|
119
124
|
const rules = venueRules({ files, venuesDir: VENUES });
|
|
120
125
|
const out: Finding[] = [];
|
|
121
|
-
for (const [name, rule] of Object.entries(rules)
|
|
122
|
-
string,
|
|
123
|
-
VenueRuleModule,
|
|
124
|
-
][]) {
|
|
126
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
125
127
|
const visitor = rule.create({
|
|
126
128
|
filename,
|
|
127
129
|
...(cwd === undefined ? {} : { cwd }),
|
|
@@ -147,7 +149,13 @@ function lint(
|
|
|
147
149
|
const DECL = { extends: "paperlint:agenticdev", kind: "short" };
|
|
148
150
|
const ids = (fs: readonly Finding[]) =>
|
|
149
151
|
fs.map((f) => `${f.rule}:${f.messageId}`).sort();
|
|
150
|
-
|
|
152
|
+
/** A patch that changes one field set of the `i`-th font. */
|
|
153
|
+
const font =
|
|
154
|
+
(i: number, change: Partial<Font>): Patch =>
|
|
155
|
+
(f) => {
|
|
156
|
+
f.fonts = f.fonts.map((x, j) => (j === i ? { ...x, ...change } : x));
|
|
157
|
+
};
|
|
158
|
+
const withFacts = (patch: Patch): Facts => {
|
|
151
159
|
const f = goodFacts();
|
|
152
160
|
patch(f);
|
|
153
161
|
return f;
|
|
@@ -260,7 +268,7 @@ describe("pdf/profile — the kind", () => {
|
|
|
260
268
|
// the fonts rule still runs with an unresolved kind
|
|
261
269
|
const fonts = lint({
|
|
262
270
|
venue,
|
|
263
|
-
facts: withFacts((
|
|
271
|
+
facts: withFacts(font(0, { embedded: false })),
|
|
264
272
|
});
|
|
265
273
|
expect(ids(fonts)).toEqual([
|
|
266
274
|
`pdf/fonts:notEmbedded`,
|
|
@@ -338,7 +346,7 @@ describe("pdf/measured — a paper that names a venue and was not measured says
|
|
|
338
346
|
"pages_by_type",
|
|
339
347
|
])
|
|
340
348
|
f[k] = null;
|
|
341
|
-
(
|
|
349
|
+
font(0, { program: "Type3" })(f);
|
|
342
350
|
});
|
|
343
351
|
expect(ids(lint({ venue: DECL, facts: g }))).toEqual([
|
|
344
352
|
"pdf/fonts:type3",
|
|
@@ -348,7 +356,7 @@ describe("pdf/measured — a paper that names a venue and was not measured says
|
|
|
348
356
|
});
|
|
349
357
|
|
|
350
358
|
describe("pdf/fresh — facts about another build are not judged", () => {
|
|
351
|
-
it.each([
|
|
359
|
+
it.each<[string, Paper, string]>([
|
|
352
360
|
[
|
|
353
361
|
"a changed PDF",
|
|
354
362
|
{ pdf: new TextEncoder().encode("another build") },
|
|
@@ -364,55 +372,48 @@ describe("pdf/fresh — facts about another build are not judged", () => {
|
|
|
364
372
|
],
|
|
365
373
|
[
|
|
366
374
|
"a font entry of the wrong shape",
|
|
367
|
-
{ facts:
|
|
375
|
+
{ facts: { ...goodFacts(), fonts: [{ name: 3 }] } },
|
|
368
376
|
"factsBroken",
|
|
369
377
|
],
|
|
370
|
-
]
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
]);
|
|
379
|
-
},
|
|
380
|
-
);
|
|
378
|
+
])("%s: one finding, from pdf/fresh only", (_, p, messageId) => {
|
|
379
|
+
// Everything else planted wrong too — none of it may be reported over stale facts.
|
|
380
|
+
const facts =
|
|
381
|
+
p.facts ?? withFacts((f) => ((f.body_pages = 99), (f.columns = 1)));
|
|
382
|
+
expect(ids(lint({ venue: DECL, ...p, facts }))).toEqual([
|
|
383
|
+
`pdf/fresh:${messageId}`,
|
|
384
|
+
]);
|
|
385
|
+
});
|
|
381
386
|
});
|
|
382
387
|
|
|
383
388
|
describe("pdf/fonts", () => {
|
|
384
|
-
it.each([
|
|
385
|
-
[
|
|
386
|
-
"a Type 3 font",
|
|
387
|
-
(f: Json) => ((f.fonts as Json[])[0]!.program = "Type3"),
|
|
388
|
-
["pdf/fonts:type3"],
|
|
389
|
-
],
|
|
389
|
+
it.each<[string, (f: Facts) => void, string[]]>([
|
|
390
|
+
["a Type 3 font", font(0, { program: "Type3" }), ["pdf/fonts:type3"]],
|
|
390
391
|
[
|
|
391
392
|
"a font not embedded",
|
|
392
|
-
(
|
|
393
|
+
font(1, { embedded: false }),
|
|
393
394
|
["pdf/fonts:notEmbedded"],
|
|
394
395
|
],
|
|
395
396
|
[
|
|
396
397
|
"the text family missing — acmart fell back to Computer Modern",
|
|
397
|
-
(
|
|
398
|
+
font(0, { name: "CMR10" }),
|
|
398
399
|
["pdf/fonts:noFamily"],
|
|
399
400
|
],
|
|
400
401
|
[
|
|
401
402
|
"both families missing: one finding per family",
|
|
402
|
-
(f:
|
|
403
|
+
(f: Facts) =>
|
|
403
404
|
(f.fonts = [
|
|
404
405
|
{ name: "CMR10", type: "Type 1", embedded: true, program: "Type1" },
|
|
405
406
|
]),
|
|
406
407
|
["pdf/fonts:noFamily", "pdf/fonts:noFamily"],
|
|
407
408
|
],
|
|
408
|
-
]
|
|
409
|
+
])("%s", (_, patch, want) => {
|
|
409
410
|
expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
|
|
410
411
|
});
|
|
411
412
|
|
|
412
413
|
it("names the family and the fonts the PDF does have", () => {
|
|
413
414
|
const [f] = lint({
|
|
414
415
|
venue: DECL,
|
|
415
|
-
facts: withFacts((
|
|
416
|
+
facts: withFacts(font(0, { name: "CMR10" })),
|
|
416
417
|
});
|
|
417
418
|
expect(f?.message).toMatch(/LinLibertine/);
|
|
418
419
|
expect(f?.message).toMatch(/CMR10/);
|
|
@@ -420,28 +421,28 @@ describe("pdf/fonts", () => {
|
|
|
420
421
|
});
|
|
421
422
|
|
|
422
423
|
describe("pdf/geometry", () => {
|
|
423
|
-
it.each([
|
|
424
|
+
it.each<[string, (f: Facts) => void, string[]]>([
|
|
424
425
|
[
|
|
425
426
|
"A4 instead of letter",
|
|
426
|
-
(f:
|
|
427
|
+
(f: Facts) => ((f.page_w_in = 8.27), (f.page_h_in = 11.69)),
|
|
427
428
|
["pdf/geometry:dim", "pdf/geometry:dim"],
|
|
428
429
|
],
|
|
429
430
|
[
|
|
430
431
|
"one column instead of two",
|
|
431
|
-
(f:
|
|
432
|
+
(f: Facts) => (f.columns = 1),
|
|
432
433
|
["pdf/geometry:columns"],
|
|
433
434
|
],
|
|
434
435
|
[
|
|
435
436
|
"a page size banal could not read",
|
|
436
|
-
(f:
|
|
437
|
+
(f: Facts) => (f.page_w_in = null),
|
|
437
438
|
["pdf/geometry:dimMissing"],
|
|
438
439
|
],
|
|
439
440
|
[
|
|
440
441
|
"0.03 in off is inside the default tolerance",
|
|
441
|
-
(f:
|
|
442
|
+
(f: Facts) => (f.page_w_in = 8.53),
|
|
442
443
|
[],
|
|
443
444
|
],
|
|
444
|
-
]
|
|
445
|
+
])("%s", (_, patch, want) => {
|
|
445
446
|
expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
|
|
446
447
|
});
|
|
447
448
|
|
|
@@ -455,46 +456,43 @@ describe("pdf/geometry", () => {
|
|
|
455
456
|
});
|
|
456
457
|
|
|
457
458
|
describe("pdf/limits", () => {
|
|
458
|
-
it.each([
|
|
459
|
+
it.each<[string, (f: Facts) => void, string[], RegExp | null]>([
|
|
459
460
|
[
|
|
460
461
|
"one body page over the short-paper limit",
|
|
461
|
-
(f:
|
|
462
|
+
(f: Facts) => (f.body_pages = 6),
|
|
462
463
|
["pdf/limits:pages"],
|
|
463
464
|
/body pages: 6, over the limit 5 for agenticdev\/short/,
|
|
464
465
|
],
|
|
465
466
|
[
|
|
466
467
|
"one reference page over",
|
|
467
|
-
(f:
|
|
468
|
+
(f: Facts) => (f.ref_pages = 3),
|
|
468
469
|
["pdf/limits:pages"],
|
|
469
470
|
/reference pages: 3, over the limit 2/,
|
|
470
471
|
],
|
|
471
|
-
["exactly at the limit", (f:
|
|
472
|
+
["exactly at the limit", (f: Facts) => (f.body_pages = 5), [], null],
|
|
472
473
|
[
|
|
473
474
|
"a reference font below the range",
|
|
474
|
-
(f:
|
|
475
|
+
(f: Facts) => (f.ref_pt = 6),
|
|
475
476
|
["pdf/limits:refPt"],
|
|
476
477
|
/6 pt/,
|
|
477
478
|
],
|
|
478
479
|
[
|
|
479
480
|
"6.8 pt is inside the range once the measuring drift (body_pt_tol) is allowed",
|
|
480
|
-
(f:
|
|
481
|
+
(f: Facts) => (f.ref_pt = 6.8),
|
|
481
482
|
[],
|
|
482
483
|
null,
|
|
483
484
|
],
|
|
484
485
|
[
|
|
485
486
|
"no reference font measured (no bibliography) is not a finding",
|
|
486
|
-
(f:
|
|
487
|
+
(f: Facts) => (f.ref_pt = null),
|
|
487
488
|
[],
|
|
488
489
|
null,
|
|
489
490
|
],
|
|
490
|
-
]
|
|
491
|
-
|
|
492
|
-
(
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (text) expect(fs[0]?.message).toMatch(text);
|
|
496
|
-
},
|
|
497
|
-
);
|
|
491
|
+
])("%s", (_, patch, want, text) => {
|
|
492
|
+
const fs = lint({ venue: DECL, facts: withFacts(patch) });
|
|
493
|
+
expect(ids(fs)).toEqual(want);
|
|
494
|
+
if (text) expect(fs[0]?.message).toMatch(text);
|
|
495
|
+
});
|
|
498
496
|
|
|
499
497
|
it("a longer kind of the same venue passes the same page count", () => {
|
|
500
498
|
const facts = withFacts((f) => (f.body_pages = 9));
|
|
@@ -506,19 +504,19 @@ describe("pdf/limits", () => {
|
|
|
506
504
|
});
|
|
507
505
|
|
|
508
506
|
describe("pdf/body-size", () => {
|
|
509
|
-
it.each([
|
|
507
|
+
it.each<[string, (f: Facts) => void, string[]]>([
|
|
510
508
|
[
|
|
511
509
|
"10.2 pt against 9 ± 0.5",
|
|
512
|
-
(f:
|
|
510
|
+
(f: Facts) => (f.body_pt = 10.2),
|
|
513
511
|
["pdf/body-size:body"],
|
|
514
512
|
],
|
|
515
|
-
["9.4 pt is inside the tolerance", (f:
|
|
513
|
+
["9.4 pt is inside the tolerance", (f: Facts) => (f.body_pt = 9.4), []],
|
|
516
514
|
[
|
|
517
515
|
"no body size measured",
|
|
518
|
-
(f:
|
|
516
|
+
(f: Facts) => (f.body_pt = null),
|
|
519
517
|
["pdf/body-size:bodyMissing"],
|
|
520
518
|
],
|
|
521
|
-
]
|
|
519
|
+
])("%s", (_, patch, want) => {
|
|
522
520
|
expect(ids(lint({ venue: DECL, facts: withFacts(patch) }))).toEqual(want);
|
|
523
521
|
});
|
|
524
522
|
});
|
|
@@ -567,7 +565,7 @@ describe("a project's own preset, and the facts' other spellings", () => {
|
|
|
567
565
|
format: { columns: 2, body_pt: 9, ref_pt_min: 7, ref_pt_max: 8 },
|
|
568
566
|
}),
|
|
569
567
|
};
|
|
570
|
-
const house = (patch: (f:
|
|
568
|
+
const house = (patch: (f: Facts) => void = () => {}) =>
|
|
571
569
|
lint({
|
|
572
570
|
venue: { extends: "./house.jsonc" },
|
|
573
571
|
facts: withFacts(patch),
|
package/src/venue-rules.ts
CHANGED
|
@@ -48,7 +48,7 @@ import type { FlatGeometry } from "./domain/geometry.ts";
|
|
|
48
48
|
import type { AbsolutePath } from "./domain/paths.ts";
|
|
49
49
|
import { sha256Hex } from "./domain/sha256.ts";
|
|
50
50
|
import type { Files } from "./ports/files.ts";
|
|
51
|
-
import { CONFIG_FILE } from "
|
|
51
|
+
import { CONFIG_FILE } from "#lib/paper-config";
|
|
52
52
|
|
|
53
53
|
// ── the verdict's vocabulary ─────────────────────────────────────────────────────────
|
|
54
54
|
|
package/tsconfig.json
CHANGED
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
"sourceMap": true,
|
|
12
12
|
"strict": true,
|
|
13
13
|
"noUncheckedIndexedAccess": true,
|
|
14
|
-
"exactOptionalPropertyTypes":
|
|
14
|
+
"exactOptionalPropertyTypes": true,
|
|
15
15
|
"skipLibCheck": true,
|
|
16
16
|
"verbatimModuleSyntax": false,
|
|
17
17
|
"types": ["node"],
|
|
18
18
|
"allowImportingTsExtensions": true,
|
|
19
|
-
"rewriteRelativeImportExtensions": true
|
|
19
|
+
"rewriteRelativeImportExtensions": true,
|
|
20
|
+
"noImplicitOverride": true,
|
|
21
|
+
"noImplicitReturns": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true
|
|
20
23
|
},
|
|
21
24
|
"include": ["src/**/*.ts"],
|
|
22
|
-
"exclude": ["src/**/*.test.ts"]
|
|
25
|
+
"exclude": ["src/**/*.test.ts", "src/**/*.harness.ts"]
|
|
23
26
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"resolveJsonModule": true
|
|
7
|
+
},
|
|
8
|
+
"include": ["lib/*.ts", "eslint-rules/*.ts", "eslint-rules/*.json"],
|
|
9
|
+
"exclude": ["**/*.test.ts", "**/*.harness.ts", "**/*.d.ts"]
|
|
10
|
+
}
|
package/lib/check.d.mts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// Types for lib/check.mjs — the shared harness assertion helper (see the module).
|
|
2
|
-
|
|
3
|
-
/** A failure detail: text, any value (rendered with `util.inspect`), or a thunk producing one. */
|
|
4
|
-
export type Detail = unknown;
|
|
5
|
-
|
|
6
|
-
export declare function renderDetail(detail: Detail): string;
|
|
7
|
-
export declare function failureMessage(label: string, detail: Detail): string;
|
|
8
|
-
|
|
9
|
-
export interface Check {
|
|
10
|
-
(label: string, cond: unknown, detail?: Detail): void;
|
|
11
|
-
/** How many times this checker has been called. */
|
|
12
|
-
readonly count: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export declare function createChecker(options?: {
|
|
16
|
-
log?: (line: string) => void;
|
|
17
|
-
}): Check;
|
package/lib/paper-config.d.mts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// Types for lib/paper-config.mjs, which `src/*.ts` imports. Without this file TypeScript sees
|
|
2
|
-
// `any` and each import needs a `@ts-expect-error`, which covers only the next line and stops
|
|
3
|
-
// working as soon as a formatter wraps the import across several lines.
|
|
4
|
-
//
|
|
5
|
-
// The field names are typed as `string`, not as their values, so that the value is written
|
|
6
|
-
// once, in paper-config.mjs.
|
|
7
|
-
|
|
8
|
-
/** The tool's name — its settings file is named after it. */
|
|
9
|
-
export declare const CONFIG_KEY: string;
|
|
10
|
-
/** `paperlint.json`: the settings file, at the project root and in each paper. */
|
|
11
|
-
export declare const CONFIG_FILE: string;
|
|
12
|
-
/** The files that make a directory a paper. */
|
|
13
|
-
export declare const PAPER_MARKERS: readonly string[];
|
|
14
|
-
|
|
15
|
-
/** The project root, walking up from `startDir` (see the module). */
|
|
16
|
-
export declare function findProjectRoot(
|
|
17
|
-
startDir: string,
|
|
18
|
-
isFile?: (path: string) => boolean,
|
|
19
|
-
): string;
|
|
20
|
-
|
|
21
|
-
/** The root `paperlint.json` of `root` — undefined when absent, unparsable or not an object. */
|
|
22
|
-
export declare function settingsOf(
|
|
23
|
-
root: string,
|
|
24
|
-
): Readonly<Record<string, unknown>> | undefined;
|
|
25
|
-
export declare const DEFAULT_PAPERS_ROOT: string;
|
|
26
|
-
export declare const PAPERS_DIR_FIELD: string;
|
|
27
|
-
|
|
28
|
-
/** Every key a `paperlint.json` may hold, at either level, mapped to who reads it. */
|
|
29
|
-
export declare const SETTINGS_KEYS: Readonly<Record<string, string>>;
|
|
30
|
-
/** The keys refused in a paper's `paperlint.json`. */
|
|
31
|
-
export declare const ROOT_ONLY_KEYS: readonly string[];
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/** Types for `layer-legacy-frozen.mjs`, for its TypeScript test. */
|
|
2
|
-
export interface Suppressed {
|
|
3
|
-
readonly filePath: string;
|
|
4
|
-
readonly suppressedMessages?: readonly {
|
|
5
|
-
readonly ruleId: string | null;
|
|
6
|
-
readonly line: number;
|
|
7
|
-
readonly suppressions?: readonly { readonly justification?: string }[];
|
|
8
|
-
}[];
|
|
9
|
-
}
|
|
10
|
-
export type Counts = Record<string, Record<string, number>>;
|
|
11
|
-
export declare const FROZEN_FILE: string;
|
|
12
|
-
export declare const LAYER_RULES: readonly string[];
|
|
13
|
-
export declare const NO_LEGACY_UNDER: readonly string[];
|
|
14
|
-
export declare const LEGACY: RegExp;
|
|
15
|
-
export declare function tally(results: readonly Suppressed[]): {
|
|
16
|
-
counts: Counts;
|
|
17
|
-
unexplained: string[];
|
|
18
|
-
};
|
|
19
|
-
export declare function judge(o: {
|
|
20
|
-
counts: Counts;
|
|
21
|
-
unexplained: readonly string[];
|
|
22
|
-
frozen: Counts;
|
|
23
|
-
}): string[];
|
|
24
|
-
export declare function checkFrozen(
|
|
25
|
-
root?: string,
|
|
26
|
-
options?: { lint?: (cwd: string) => Promise<unknown[]> },
|
|
27
|
-
): Promise<{
|
|
28
|
-
problems: string[];
|
|
29
|
-
frozen: Counts;
|
|
30
|
-
counts: Counts;
|
|
31
|
-
}>;
|
|
32
|
-
export declare function main(options?: {
|
|
33
|
-
check?: () => Promise<{ problems: string[]; frozen: Counts; counts: Counts }>;
|
|
34
|
-
log?: (line: string) => void;
|
|
35
|
-
err?: (line: string) => void;
|
|
36
|
-
}): Promise<number>;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Colocated test for `scripts/rules-are-content-only.mjs` — a rule may not ask git.
|
|
3
|
-
* Run: `npx vigiles test scripts/rules-are-content-only.harness.mjs`
|
|
4
|
-
*
|
|
5
|
-
* 🔴 BOTH HALVES. Like its sibling, this guard's success state is silence, so silence alone is
|
|
6
|
-
* no evidence it works:
|
|
7
|
-
* I. QUIET on this repository's real `eslint-rules/`, and the count is asserted non-zero —
|
|
8
|
-
* a guard that scanned nothing is also quiet, and that is the reading to rule out first.
|
|
9
|
-
* II. FIRES on each form the defect takes: static `import`, `require()`, dynamic `import()`,
|
|
10
|
-
* and the `node:` prefix — the prefix matters because a set membership test that lists
|
|
11
|
-
* only the bare specifier passes a file that writes the modern spelling.
|
|
12
|
-
* III. QUIET on sources that merely TALK about the banned module — a comment and a string
|
|
13
|
-
* literal. This is the half a grep cannot have, and the reason the check parses.
|
|
14
|
-
*/
|
|
15
|
-
import { dirname, join } from "node:path";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
-
import { createChecker } from "../lib/check.mjs";
|
|
18
|
-
|
|
19
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
const { rulesAreContentOnly, processImports } = await import(
|
|
21
|
-
join(HERE, "rules-are-content-only.mjs")
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const check = createChecker();
|
|
25
|
-
|
|
26
|
-
// ── I. quiet on the real corpus, and it actually looked ───────────────────────
|
|
27
|
-
{
|
|
28
|
-
const { checked, findings } = rulesAreContentOnly({ cwd: join(HERE, "..") });
|
|
29
|
-
check(
|
|
30
|
-
`scanned something (${checked.length} rule sources)`,
|
|
31
|
-
checked.length > 0,
|
|
32
|
-
);
|
|
33
|
-
check(
|
|
34
|
-
`quiet on the real corpus (${findings.length} findings)`,
|
|
35
|
-
findings.length === 0,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ── II. fires on every form the defect has taken ────────────────────────────
|
|
40
|
-
for (const [label, src] of [
|
|
41
|
-
[
|
|
42
|
-
"execFileSync with a git argv",
|
|
43
|
-
`import { execFileSync } from "node:child_process";
|
|
44
|
-
execFileSync("git", ["cat-file", "-e", sha]);`,
|
|
45
|
-
],
|
|
46
|
-
[
|
|
47
|
-
"a namespaced call",
|
|
48
|
-
`import cp from "node:child_process";
|
|
49
|
-
cp.spawnSync("git", ["rev-parse", ref]);`,
|
|
50
|
-
],
|
|
51
|
-
[
|
|
52
|
-
"execSync with a git shell line",
|
|
53
|
-
`import { execSync } from "child_process";
|
|
54
|
-
execSync("git log --oneline -1");`,
|
|
55
|
-
],
|
|
56
|
-
["exec, async form", `exec("git rev-list --all", cb);`],
|
|
57
|
-
]) {
|
|
58
|
-
check(`FIRES on ${label}`, processImports(src).length === 1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ── III. the FALSE POSITIVE that forced the narrowing, asserted so it cannot return ──
|
|
62
|
-
// 🔴 `paper-texcount.mjs` in the consumer shells out to `texcount` to count words in a .tex.
|
|
63
|
-
// The first version of this check keyed on importing `child_process` at all, so it claimed that
|
|
64
|
-
// rule on its first run — a false positive on an `error` gate, which is worse than a miss
|
|
65
|
-
// because the gate that cannot be cleared gets switched off rather than fixed.
|
|
66
|
-
for (const [label, src] of [
|
|
67
|
-
[
|
|
68
|
-
"a rule spawning texcount",
|
|
69
|
-
`import { execFileSync } from "node:child_process";
|
|
70
|
-
execFileSync("texcount", ["-brief", file]);`,
|
|
71
|
-
],
|
|
72
|
-
[
|
|
73
|
-
"a rule spawning pdflatex",
|
|
74
|
-
`import { spawnSync } from "node:child_process";
|
|
75
|
-
spawnSync("pdflatex", [tex]);`,
|
|
76
|
-
],
|
|
77
|
-
[
|
|
78
|
-
"importing child_process without running git",
|
|
79
|
-
`import { execFileSync } from "node:child_process";
|
|
80
|
-
export default {};`,
|
|
81
|
-
],
|
|
82
|
-
]) {
|
|
83
|
-
check(`quiet on ${label}`, processImports(src).length === 0);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── IV. quiet on sources that only MENTION it — what a grep cannot do ───────
|
|
87
|
-
for (const [label, src] of [
|
|
88
|
-
[
|
|
89
|
-
"a comment naming git",
|
|
90
|
-
`// never reach for git cat-file here\nexport default {};`,
|
|
91
|
-
],
|
|
92
|
-
["a string literal", `export const why = "git cat-file is banned in rules";`],
|
|
93
|
-
[
|
|
94
|
-
"a reported message",
|
|
95
|
-
`export default { meta: { messages: { m: "run git cat-file? no" } } };`,
|
|
96
|
-
],
|
|
97
|
-
[
|
|
98
|
-
"a program merely NAMED git-something",
|
|
99
|
-
`execFileSync("git-lfs-helper", []);`,
|
|
100
|
-
],
|
|
101
|
-
]) {
|
|
102
|
-
check(`quiet on ${label}`, processImports(src).length === 0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
console.log(`rules-are-content-only.harness: ${check.count} assertions passed`);
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A RULE MAY NOT ASK GIT.
|
|
3
|
-
*
|
|
4
|
-
* 🔴 The defect it exists for, measured 2026-09-16. `paper/source` bound a paper's stage to a
|
|
5
|
-
* commit sha and verified it with `git cat-file -e`. Of four shas recorded in the consumer's
|
|
6
|
-
* front matter, ONE still resolved ninety minutes later: a squash-merge had orphaned the branch
|
|
7
|
-
* commits and `gc` collected them. A recorded sha is a pointer to a pointer, and both hops are
|
|
8
|
-
* removed by ROUTINE maintenance — squash, rebase, gc, force-push, filter-repo. Nobody is at
|
|
9
|
-
* fault, so nobody is on the hook to fix it; the rule simply turns red and gets switched off.
|
|
10
|
-
*
|
|
11
|
-
* ── THE STRONGER LEG, AND THE REASON THIS IS A FLOOR CHECK ──────────────────
|
|
12
|
-
* `actions/checkout` defaults to `fetch-depth: 1` — exactly one commit. Measured in the
|
|
13
|
-
* consumer the same day: six checkouts, not one override. So a git-reading rule does not
|
|
14
|
-
* resolve even a LIVE sha under CI, which is the one place it has to hold. Locally it stays
|
|
15
|
-
* green, so the failure is one-directional and in the flattering direction.
|
|
16
|
-
*
|
|
17
|
-
* ── WHAT IT CHECKS, AND WHY NOT A GREP ──────────────────────────────────────
|
|
18
|
-
* A `child_process` spawner called with `git` as the program, read from the AST. Parsed, not
|
|
19
|
-
* grepped, so the words `git cat-file` in this very comment are not a finding — a text guard
|
|
20
|
-
* would have to exempt itself, which is the failure mode that makes text guards unmaintainable.
|
|
21
|
-
*
|
|
22
|
-
* ⚠️ THE BOUND, stated so the check is not mistaken for more than it is. Two things pass: a
|
|
23
|
-
* rule that opens `.git/` with `fs` (indistinguishable at this level from reading a data file),
|
|
24
|
-
* and a rule that assembles the program name at runtime. Both accepted deliberately — see the
|
|
25
|
-
* note on `SPAWNERS` for the false positive that forced the predicate this narrow.
|
|
26
|
-
*
|
|
27
|
-
* Run: `node scripts/rules-are-content-only.mjs` (exit 1 and a named rule on a finding)
|
|
28
|
-
* Tested by: `scripts/rules-are-content-only.harness.mjs` — quiet on the real corpus, firing on
|
|
29
|
-
* a planted rule.
|
|
30
|
-
*/
|
|
31
|
-
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
32
|
-
import { join } from "node:path";
|
|
33
|
-
import { parse } from "espree";
|
|
34
|
-
import { isMain } from "../skills/paper-pipeline/scripts/consumer.mjs";
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 🔴 THE PREDICATE IS "RUNS GIT", NOT "SPAWNS A PROCESS", and the narrowing was forced by a
|
|
38
|
-
* FALSE POSITIVE on the consumer's corpus the hour this check shipped: `paper-texcount.mjs`
|
|
39
|
-
* shells out to `texcount` to count words in a .tex, which is a measuring tool, not history.
|
|
40
|
-
* A false positive on an `error` gate is worse than a miss — the gate that cannot be cleared
|
|
41
|
-
* gets switched off, and this one would have been switched off on its first run.
|
|
42
|
-
*
|
|
43
|
-
* ⚠️ THE COST OF NARROWING, stated rather than hidden: a rule that builds the program name at
|
|
44
|
-
* runtime (`const g = "gi" + "t"`) passes. That is accepted. The defect has taken exactly one
|
|
45
|
-
* form — a literal `git` handed to a `child_process` function — and a check that also caught
|
|
46
|
-
* the contrived form would have to claim every spawn, which is the false positive above.
|
|
47
|
-
*/
|
|
48
|
-
const SPAWNERS = new Set([
|
|
49
|
-
"exec",
|
|
50
|
-
"execSync",
|
|
51
|
-
"execFile",
|
|
52
|
-
"execFileSync",
|
|
53
|
-
"spawn",
|
|
54
|
-
"spawnSync",
|
|
55
|
-
"fork",
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
|
-
/** `"git"` as the program, or `"git …"` as the head of a shell line handed to `exec`. */
|
|
59
|
-
const isGit = (v) =>
|
|
60
|
-
typeof v === "string" && (v === "git" || v.startsWith("git "));
|
|
61
|
-
|
|
62
|
-
/** Rule sources only: a harness plants defects on purpose and asserts about them. */
|
|
63
|
-
const isRuleSource = (f) => f.endsWith(".mjs") && !f.endsWith(".harness.mjs");
|
|
64
|
-
|
|
65
|
-
/** @param {string} src @returns {string[]} the git invocations this source makes */
|
|
66
|
-
export function processImports(src) {
|
|
67
|
-
const ast = parse(src, {
|
|
68
|
-
ecmaVersion: "latest",
|
|
69
|
-
sourceType: "module",
|
|
70
|
-
range: false,
|
|
71
|
-
});
|
|
72
|
-
const found = [];
|
|
73
|
-
const walk = (n) => {
|
|
74
|
-
if (n === null || typeof n !== "object") return;
|
|
75
|
-
if (Array.isArray(n)) return n.forEach(walk);
|
|
76
|
-
// Both spellings of the callee: `execFileSync(…)` after a named import, and
|
|
77
|
-
// `cp.execFileSync(…)` after a namespace import. Listed, not described — the node types a
|
|
78
|
-
// check walks are the only honest statement of its coverage.
|
|
79
|
-
if (n.type === "CallExpression") {
|
|
80
|
-
const name =
|
|
81
|
-
n.callee?.type === "MemberExpression"
|
|
82
|
-
? n.callee.property?.name
|
|
83
|
-
: n.callee?.name;
|
|
84
|
-
if (SPAWNERS.has(String(name)) && isGit(n.arguments?.[0]?.value))
|
|
85
|
-
found.push(String(n.arguments[0].value));
|
|
86
|
-
}
|
|
87
|
-
for (const k of Object.keys(n)) if (k !== "parent") walk(n[k]);
|
|
88
|
-
};
|
|
89
|
-
walk(ast);
|
|
90
|
-
return found;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** @param {{cwd: string}} options */
|
|
94
|
-
export function rulesAreContentOnly({ cwd }) {
|
|
95
|
-
const dir = join(cwd, "eslint-rules");
|
|
96
|
-
const checked = [];
|
|
97
|
-
const findings = [];
|
|
98
|
-
// A repository with no `eslint-rules/` has no rule sources: `checked` stays empty and the gate
|
|
99
|
-
// says so by name. `readdirSync` would throw ENOENT instead — a stack trace, not the refusal.
|
|
100
|
-
const names = existsSync(dir) ? readdirSync(dir) : [];
|
|
101
|
-
for (const f of names.filter(isRuleSource)) {
|
|
102
|
-
const hits = processImports(readFileSync(join(dir, f), "utf8"));
|
|
103
|
-
// 🔴 The counter increments WITH the verdict, not before it: a `checked` that keeps
|
|
104
|
-
// counting while the verdict stops being reached is the failure recorded in the consumer's
|
|
105
|
-
// rules as "a counter that counts what it ignores".
|
|
106
|
-
checked.push(f);
|
|
107
|
-
for (const h of hits) findings.push({ file: f, specifier: h });
|
|
108
|
-
}
|
|
109
|
-
return { checked, findings };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* The gate over `cwd`'s `eslint-rules/`: exit 1 on no rule sources (a silent zero would read as
|
|
114
|
-
* clean) or on any rule asking git; 0 otherwise.
|
|
115
|
-
*
|
|
116
|
-
* 🔴 `cwd` is `process.cwd()`, NOT the package root. A consumer installs this package and runs the
|
|
117
|
-
* script from its own repository, where the rules that matter are ITS `eslint-rules/` — the
|
|
118
|
-
* package's own are already checked by the package's own gate. Anchored to the package root the
|
|
119
|
-
* consumer's gate would re-check the same eight files and report a confident zero about rules it
|
|
120
|
-
* never opened: a counter that counts what it ignores.
|
|
121
|
-
*/
|
|
122
|
-
export function main({
|
|
123
|
-
cwd = process.cwd(),
|
|
124
|
-
log = console.log,
|
|
125
|
-
err = console.error,
|
|
126
|
-
} = {}) {
|
|
127
|
-
const { checked, findings } = rulesAreContentOnly({ cwd });
|
|
128
|
-
if (checked.length === 0) {
|
|
129
|
-
err(
|
|
130
|
-
`${join(cwd, "eslint-rules")}: no rule sources. Either this is not a repository with ` +
|
|
131
|
-
`ESLint rules, or the directory moved — a silent zero here would read as a clean run.`,
|
|
132
|
-
);
|
|
133
|
-
return 1;
|
|
134
|
-
}
|
|
135
|
-
for (const { file, specifier } of findings) {
|
|
136
|
-
err(
|
|
137
|
-
`${file}: imports \`${specifier}\`. A rule may not ask git: history is rewritten by ` +
|
|
138
|
-
`routine maintenance and CI clones one commit deep, so the fact does not survive where ` +
|
|
139
|
-
`the rule has to hold. Put the fact on disk and check it by bytes.`,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
log(
|
|
143
|
-
`rules-are-content-only: ${checked.length} rule sources under ${cwd}, ${findings.length} findings`,
|
|
144
|
-
);
|
|
145
|
-
return findings.length === 0 ? 0 : 1;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (isMain(import.meta.url)) process.exit(main());
|