paperlint 3.1.3 → 4.0.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 +5 -5
- package/CONTRIBUTING.md +34 -5
- package/dist/adapters/banal/failure.d.ts +1 -7
- package/dist/adapters/banal/failure.d.ts.map +1 -1
- package/dist/adapters/banal/failure.js +5 -7
- package/dist/adapters/banal/failure.js.map +1 -1
- package/dist/adapters/banal/index.d.ts.map +1 -1
- package/dist/adapters/banal/index.js +4 -3
- package/dist/adapters/banal/index.js.map +1 -1
- package/dist/adapters/banal/install.d.ts.map +1 -1
- package/dist/adapters/banal/install.js +4 -3
- package/dist/adapters/banal/install.js.map +1 -1
- package/dist/adapters/banal/invocation.d.ts +10 -5
- package/dist/adapters/banal/invocation.d.ts.map +1 -1
- package/dist/adapters/banal/invocation.js +13 -1
- package/dist/adapters/banal/invocation.js.map +1 -1
- package/dist/adapters/banal/locate.d.ts +12 -9
- package/dist/adapters/banal/locate.d.ts.map +1 -1
- package/dist/adapters/banal/locate.js +19 -12
- package/dist/adapters/banal/locate.js.map +1 -1
- package/dist/adapters/banal/run.d.ts +10 -1
- package/dist/adapters/banal/run.d.ts.map +1 -1
- package/dist/adapters/banal/run.js +8 -4
- package/dist/adapters/banal/run.js.map +1 -1
- package/dist/adapters/banal/settings.d.ts +1 -1
- package/dist/adapters/banal/settings.d.ts.map +1 -1
- package/dist/adapters/banal/settings.js +2 -1
- package/dist/adapters/banal/settings.js.map +1 -1
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -2
- package/dist/adapters/memory/index.js.map +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 +5 -7
- 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/node/workspace.io.d.ts.map +1 -1
- package/dist/adapters/node/workspace.io.js +4 -2
- package/dist/adapters/node/workspace.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 +8 -7
- 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.d.ts +1 -1
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +10 -3
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +2 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +19 -17
- package/dist/build.js.map +1 -1
- package/dist/caller-path.d.ts +3 -0
- package/dist/caller-path.d.ts.map +1 -0
- package/dist/caller-path.js +13 -0
- package/dist/caller-path.js.map +1 -0
- package/dist/cli.d.ts +10 -17
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +93 -46
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +12 -3
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +5 -3
- 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/paths.d.ts +8 -2
- package/dist/domain/paths.d.ts.map +1 -1
- package/dist/domain/paths.js +12 -1
- package/dist/domain/paths.js.map +1 -1
- package/dist/domain/ready.d.ts +24 -0
- package/dist/domain/ready.d.ts.map +1 -0
- package/dist/domain/ready.js +11 -0
- package/dist/domain/ready.js.map +1 -0
- package/dist/domain/record.d.ts +8 -0
- package/dist/domain/record.d.ts.map +1 -0
- package/dist/domain/record.js +8 -0
- package/dist/domain/record.js.map +1 -0
- package/dist/domain/sha256.d.ts.map +1 -1
- package/dist/domain/sha256.js +4 -2
- package/dist/domain/sha256.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 +23 -22
- 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 +31 -27
- 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 +31 -11
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -9
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +33 -15
- 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 +138 -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 +7 -3
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +6 -6
- package/dist/paper-files.js.map +1 -1
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +8 -7
- package/dist/paper-settings.js.map +1 -1
- package/dist/pdf-facts.d.ts +51 -1
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +25 -16
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.d.ts.map +1 -1
- package/dist/pdf-geometry.js +3 -4
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/ports/tool-installer.d.ts +2 -14
- package/dist/ports/tool-installer.d.ts.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +15 -10
- package/dist/references.js.map +1 -1
- package/dist/rules-config.d.ts +26 -1
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js +15 -12
- 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 +4 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +22 -5
- 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/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +21 -8
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +2 -2
- 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} +5 -4
- package/eslint-rules/install-path-literals.harness.mjs +1 -1
- package/eslint-rules/{install-path-literals.mjs → install-path-literals.ts} +18 -10
- 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} +35 -16
- 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} +76 -31
- package/eslint-rules/paper-typography.test.mjs +3 -3
- package/eslint-rules/{paper-typography.mjs → paper-typography.ts} +134 -56
- 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} +89 -32
- package/eslint-rules/review-frontmatter.test.mjs +2 -2
- package/eslint-rules/{review-frontmatter.mjs → review-frontmatter.ts} +38 -16
- 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} +20 -7
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/{tex-build.mjs → tex-build.ts} +6 -5
- package/eslint.config.mjs +146 -30
- package/fixtures/install-path-literals/clean.fixture.mjs +1 -1
- package/fixtures/install-path-literals/clean.md +2 -2
- package/fixtures/install-path-literals/defect.fixture.mjs +1 -1
- package/fixtures/install-path-literals/defect.md +2 -2
- 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.sh +9 -19
- 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 +9 -9
- package/lib/{paper-config.mjs → paper-config.ts} +20 -19
- package/lib/skill-checks.mjs +24 -381
- package/lib/skill-checks.test.mjs +64 -116
- package/lib/skill-corpus.mjs +10 -251
- package/lib/skill-corpus.test.mjs +2 -13
- package/lib/skill-eval-kit.mjs +1 -1
- package/lib/skill-trigger-cases.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 -38
- package/scripts/eslint-report-guard.test.mjs +1 -1
- package/scripts/harness-api.frozen.json +14 -16
- 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/analyze-sibling-paper/SKILL.md +1 -39
- package/skills/analyze-sibling-paper/SKILL.md.spec.ts +0 -38
- package/skills/argument-arc/SKILL.md +2 -36
- package/skills/argument-arc/SKILL.md.spec.ts +1 -43
- package/skills/build-benchmark/SKILL.md +1 -41
- package/skills/build-benchmark/SKILL.md.spec.ts +0 -40
- package/skills/camera-ready/SKILL.md +1 -34
- package/skills/camera-ready/SKILL.md.spec.ts +0 -33
- package/skills/cold-read-diff/SKILL.md +1 -35
- package/skills/cold-read-diff/SKILL.md.spec.ts +0 -34
- package/skills/draft-paper/SKILL.md +2 -41
- package/skills/draft-paper/SKILL.md.spec.ts +1 -50
- package/skills/extend-paper/SKILL.md +2 -33
- package/skills/extend-paper/SKILL.md.spec.ts +1 -42
- package/skills/find-venue/SKILL.md +2 -38
- package/skills/find-venue/SKILL.md.spec.ts +1 -47
- package/skills/grade-paper-writing/SKILL.md +3 -38
- package/skills/grade-paper-writing/SKILL.md.spec.ts +2 -47
- package/skills/grade-paper-writing/prose-lint.mjs +3 -4
- package/skills/grade-paper-writing/{prose-lint.test.mjs → prose-lint.test.ts} +3 -3
- package/skills/harden-paper/SKILL.md +1 -39
- package/skills/harden-paper/SKILL.md.spec.ts +0 -38
- package/skills/map-prior-work/SKILL.md +1 -35
- package/skills/map-prior-work/SKILL.md.spec.ts +0 -34
- package/skills/osf-artifact-upload/{osf-artifact-upload.harness.mjs → osf-artifact-upload.harness.ts} +3 -4
- package/skills/paper-adversarial-review/SKILL.md +2 -35
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -43
- package/skills/paper-pipeline/SKILL.md +2 -28
- package/skills/paper-pipeline/SKILL.md.spec.ts +1 -27
- 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 +2 -2
- package/skills/paper-pipeline/paper-pipeline.harness.mjs +25 -40
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
- package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
- package/skills/paper-pipeline/references/acceptance-gate.md +1 -6
- package/skills/paper-pipeline/references/pipeline-status-template.md +8 -35
- package/skills/paper-pipeline/scripts/README.md +30 -225
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +4 -8
- 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.harness.mjs +6 -104
- package/skills/paper-pipeline/scripts/consumer.mjs +24 -109
- 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/generated-code.harness.mjs +2 -3
- package/skills/paper-pipeline/scripts/{generated-code.test.mjs → generated-code.test.ts} +3 -3
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +3 -3
- 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-status/SKILL.md +2 -42
- package/skills/paper-status/SKILL.md.spec.ts +1 -41
- package/skills/pc-panel-review/SKILL.md +2 -46
- package/skills/pc-panel-review/SKILL.md.spec.ts +1 -45
- package/skills/plan-paper-timeline/SKILL.md +2 -36
- package/skills/plan-paper-timeline/SKILL.md.spec.ts +0 -36
- 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/SKILL.md +1 -37
- package/skills/render-paper/SKILL.md.spec.ts +0 -36
- 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/research-ideate/SKILL.md +2 -39
- package/skills/research-ideate/SKILL.md.spec.ts +1 -47
- package/skills/study-accepted-papers/SKILL.md +1 -34
- package/skills/study-accepted-papers/SKILL.md.spec.ts +0 -33
- package/skills/submit-paper/SKILL.md +1 -33
- package/skills/submit-paper/SKILL.md.spec.ts +0 -32
- package/skills/sweep-design-space/SKILL.md +1 -38
- package/skills/sweep-design-space/SKILL.md.spec.ts +0 -37
- package/skills/tighten-paper/SKILL.md +2 -39
- package/skills/tighten-paper/SKILL.md.spec.ts +1 -47
- package/skills/tighten-paper/structure.mjs +5 -12
- package/skills/tighten-paper/{structure.test.mjs → structure.test.ts} +4 -3
- package/skills/verify-citations/SKILL.md +2 -38
- package/skills/verify-citations/SKILL.md.spec.ts +1 -37
- 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/failure.ts +9 -10
- package/src/adapters/banal/index.test.ts +5 -8
- package/src/adapters/banal/index.ts +8 -7
- package/src/adapters/banal/install.ts +9 -4
- package/src/adapters/banal/invocation.test.ts +23 -9
- package/src/adapters/banal/invocation.ts +24 -9
- package/src/adapters/banal/locate.test.ts +2 -2
- package/src/adapters/banal/locate.ts +27 -21
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +5 -5
- package/src/adapters/banal/run.ts +16 -10
- package/src/adapters/banal/settings.ts +2 -2
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/curl/download.test.ts +8 -6
- package/src/adapters/memory/index.ts +4 -3
- package/src/adapters/node/files.io.ts +2 -2
- package/src/adapters/node/files.test.ts +8 -4
- package/src/adapters/node/process.io.ts +13 -5
- package/src/adapters/node/process.test.ts +5 -5
- package/src/adapters/node/workspace.io.ts +9 -4
- 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 +33 -60
- 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 +13 -8
- package/src/build-engine.ts +12 -7
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +24 -24
- package/src/build.ts +34 -26
- package/src/caller-path.ts +13 -0
- package/src/cli.harness.mjs +6 -8
- package/src/cli.test.ts +31 -13
- package/src/cli.ts +117 -58
- package/src/doctor.harness.mjs +3 -3
- package/src/doctor.test.ts +1 -1
- package/src/doctor.ts +17 -7
- package/src/domain/lookup-cache.ts +21 -11
- package/src/domain/paths.test.ts +15 -0
- package/src/domain/paths.ts +20 -1
- package/src/domain/ready.ts +31 -0
- package/src/domain/record.test.ts +20 -0
- package/src/domain/record.ts +12 -0
- package/src/domain/result.test.ts +2 -2
- package/src/domain/sha256.ts +5 -3
- 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 +34 -5
- package/src/facts-file.test.ts +9 -7
- package/src/facts-file.ts +45 -29
- package/src/{hooks-settings.harness.mjs → hooks-settings.harness.ts} +56 -20
- package/src/hooks-settings.ts +48 -13
- package/src/init.test.ts +37 -14
- package/src/init.ts +48 -24
- 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 +9 -25
- package/src/new-paper.test.ts +8 -10
- package/src/new-paper.ts +9 -5
- package/src/paper-files.test.ts +25 -24
- package/src/paper-files.ts +16 -27
- package/src/paper-settings-commands.test.ts +5 -4
- package/src/paper-settings.test.ts +7 -7
- package/src/paper-settings.ts +13 -14
- package/src/{pdf-facts.harness.mjs → pdf-facts.harness.ts} +9 -8
- package/src/pdf-facts.test.ts +25 -15
- package/src/pdf-facts.ts +81 -23
- package/src/{pdf-geometry.harness.mjs → pdf-geometry.harness.ts} +19 -12
- package/src/pdf-geometry.ts +8 -4
- package/src/ports/tool-installer.ts +2 -17
- package/src/presets.ts +31 -16
- package/src/references.test.ts +76 -62
- package/src/references.ts +18 -12
- package/src/{rules-config.harness.mjs → rules-config.harness.ts} +7 -13
- package/src/rules-config.ts +46 -14
- 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.test.ts +21 -0
- package/src/tex-requirements.ts +37 -11
- package/src/toolchain.harness.mjs +2 -2
- package/src/toolchain.test.ts +10 -6
- package/src/toolchain.ts +31 -17
- package/src/types.ts +5 -3
- package/src/venue-rules.test.ts +70 -68
- package/src/venue-rules.ts +22 -12
- 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/skills/paper-pipeline/scripts/announce.mjs +0 -67
- package/skills/paper-pipeline/scripts/announce.test.mjs +0 -102
- package/skills/paper-pipeline/scripts/fixtures/clean/PIPELINE-STATUS.md +0 -51
- package/skills/paper-pipeline/scripts/fixtures/dirty/PIPELINE-STATUS.md +0 -52
- package/skills/paper-pipeline/scripts/fixtures/dirty/paper.md +0 -6
- package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +0 -71
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +0 -302
- package/skills/paper-pipeline/scripts/ledger.mjs +0 -623
- package/skills/paper-pipeline/scripts/ledger.test.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +0 -474
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +0 -328
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +0 -753
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +0 -169
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +0 -73
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +0 -648
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +0 -468
- package/skills/paper-pipeline/scripts/status.mjs +0 -295
- package/skills/paper-pipeline/scripts/status.test.mjs +0 -232
- /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/tighten-paper/__snapshots__/{structure.test.mjs.snap → structure.test.ts.snap} +0 -0
package/src/cli.ts
CHANGED
|
@@ -30,7 +30,8 @@ import { spawnSync } from "node:child_process";
|
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { join, dirname, resolve, relative, basename, sep } from "node:path";
|
|
32
32
|
import markdown from "@eslint/markdown";
|
|
33
|
-
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
// Types come from consumer.d.mts beside it.
|
|
34
35
|
import {
|
|
35
36
|
isMain,
|
|
36
37
|
packageVenuesDir,
|
|
@@ -96,43 +97,39 @@ import {
|
|
|
96
97
|
type PaperFormat,
|
|
97
98
|
type VenueSetting,
|
|
98
99
|
} from "./new-paper.ts";
|
|
99
|
-
// The one source for the consumer's config key lives in the
|
|
100
|
-
//
|
|
100
|
+
// The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
|
|
101
|
+
// import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
|
|
102
|
+
// and from dist/ — see CONTRIBUTING.md.
|
|
101
103
|
import {
|
|
102
104
|
CONFIG_FILE,
|
|
103
105
|
DEFAULT_PAPERS_ROOT,
|
|
104
106
|
PAPERS_DIR_FIELD,
|
|
105
107
|
SETTINGS_KEYS,
|
|
106
108
|
findProjectRoot,
|
|
107
|
-
} from "
|
|
109
|
+
} from "#lib/paper-config";
|
|
108
110
|
import {
|
|
109
111
|
parseRuleBlocks,
|
|
110
112
|
parseRuleEntries,
|
|
111
113
|
shippedRuleIds,
|
|
112
114
|
unknownKeys,
|
|
113
115
|
type Parsed,
|
|
116
|
+
type ConfigBlock,
|
|
114
117
|
type RuleBlock,
|
|
115
118
|
type RuleEntry,
|
|
116
119
|
} from "./rules-config.ts";
|
|
117
120
|
export { init };
|
|
118
121
|
export { nextSteps } from "./init.ts";
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
import
|
|
122
|
-
|
|
123
|
-
import
|
|
124
|
-
|
|
125
|
-
import
|
|
126
|
-
|
|
127
|
-
import
|
|
128
|
-
|
|
129
|
-
import
|
|
130
|
-
// @ts-expect-error — an ESLint rule in .mjs, it has no types
|
|
131
|
-
import reviewFrontmatter from "../eslint-rules/review-frontmatter.mjs";
|
|
132
|
-
// @ts-expect-error — an ESLint rule in .mjs, it has no types
|
|
133
|
-
import siblingFrontmatter from "../eslint-rules/sibling-frontmatter.mjs";
|
|
134
|
-
// @ts-expect-error — an ESLint rule in .mjs, it has no types
|
|
135
|
-
import pdfRules from "../eslint-rules/pdf-last-page-balance.mjs";
|
|
123
|
+
import paperStages from "#eslint-rules/paper-stages";
|
|
124
|
+
import researchQuestion from "#eslint-rules/paper-research-question";
|
|
125
|
+
import typography from "#eslint-rules/paper-typography";
|
|
126
|
+
import texBuild from "#eslint-rules/tex-build";
|
|
127
|
+
import bibReachable from "#eslint-rules/bib-reachable-entry";
|
|
128
|
+
import reviewFrontmatter from "#eslint-rules/review-frontmatter";
|
|
129
|
+
import siblingFrontmatter from "#eslint-rules/sibling-frontmatter";
|
|
130
|
+
import pdfRules from "#eslint-rules/pdf-last-page-balance";
|
|
131
|
+
import { messageOf } from "./domain/text.ts";
|
|
132
|
+
import { isRecord } from "./domain/record.ts";
|
|
136
133
|
|
|
137
134
|
/** The shipped venue presets, read from the package's venues directory — the one list. */
|
|
138
135
|
const SHIPPED_VENUES = (): string[] => shippedPresets(packageVenuesDir());
|
|
@@ -226,7 +223,7 @@ settings — paperlint.json, at two levels, one schema. Both are optional.
|
|
|
226
223
|
export function buildConfig(
|
|
227
224
|
opts: PaperlintConfig = {},
|
|
228
225
|
texLanguage: unknown,
|
|
229
|
-
):
|
|
226
|
+
): ConfigBlock[] {
|
|
230
227
|
const paperRules = { ...researchQuestion.rules, ...typography.rules };
|
|
231
228
|
// The reference rules judge `_build/references.json`, and only on `paper.tex`.
|
|
232
229
|
const texPaperRules = {
|
|
@@ -244,8 +241,7 @@ export function buildConfig(
|
|
|
244
241
|
languageOptions: { frontmatter: "yaml" },
|
|
245
242
|
};
|
|
246
243
|
|
|
247
|
-
|
|
248
|
-
const cfg: any[] = [
|
|
244
|
+
const cfg: ConfigBlock[] = [
|
|
249
245
|
// 🔴 THE PROJECT'S PAPER TEMPLATE IS NOT A PAPER. `paperlint new` reads `<papers>/.template/`, and
|
|
250
246
|
// its files carry every marker a paper does. Flat config does NOT ignore dot-directories by
|
|
251
247
|
// default (only `node_modules/` and `.git/`), so without this block `paperlint lint` would lint the
|
|
@@ -336,6 +332,21 @@ export function buildConfig(
|
|
|
336
332
|
return cfg;
|
|
337
333
|
}
|
|
338
334
|
|
|
335
|
+
/**
|
|
336
|
+
* paperlint's blocks as ESLint's config type — the one place the two meet. ESLint types a plugin's
|
|
337
|
+
* rule as one that runs on ANY language; paperlint's run on the language they were written for, so
|
|
338
|
+
* no static proof that a block fits exists (`ConfigBlock`, rules-config.ts). ESLint checks every block
|
|
339
|
+
* when it loads the config and throws on one it cannot use; what is left to check here is that each
|
|
340
|
+
* is an object.
|
|
341
|
+
*/
|
|
342
|
+
const isEslintBlock = (b: unknown): b is Linter.Config =>
|
|
343
|
+
typeof b === "object" && b !== null;
|
|
344
|
+
|
|
345
|
+
export function eslintConfig(blocks: readonly ConfigBlock[]): Linter.Config[] {
|
|
346
|
+
const loose: readonly unknown[] = blocks;
|
|
347
|
+
return loose.filter(isEslintBlock);
|
|
348
|
+
}
|
|
349
|
+
|
|
339
350
|
/**
|
|
340
351
|
* The rule ids a consumer may name in `rules`: every rule paperlint's own config defines, read off that
|
|
341
352
|
* config rather than listed again. `@eslint/markdown` is a dependency's plugin, not paperlint's.
|
|
@@ -364,9 +375,7 @@ export interface RulePlugin {
|
|
|
364
375
|
export function rulePlugins(texLanguage?: unknown): Record<string, RulePlugin> {
|
|
365
376
|
const out: Record<string, { rules: Record<string, unknown> }> = {};
|
|
366
377
|
for (const block of buildConfig({}, texLanguage ?? { sentinel: "tex" }))
|
|
367
|
-
for (const [name, plugin] of Object.entries(
|
|
368
|
-
(block as { plugins?: Record<string, RulePlugin> }).plugins ?? {},
|
|
369
|
-
))
|
|
378
|
+
for (const [name, plugin] of Object.entries(block.plugins ?? {}))
|
|
370
379
|
if (plugin !== markdown)
|
|
371
380
|
out[name] = { rules: { ...out[name]?.rules, ...plugin.rules } };
|
|
372
381
|
if (out["tex"] && texLanguage !== undefined)
|
|
@@ -389,10 +398,10 @@ export function rulePlugins(texLanguage?: unknown): Record<string, RulePlugin> {
|
|
|
389
398
|
* switched off in THIS run — `paperlint lint` still reports a directive that silences nothing.
|
|
390
399
|
*/
|
|
391
400
|
export function rulesOff(texLanguage?: unknown): Linter.Config[] {
|
|
392
|
-
return [
|
|
401
|
+
return eslintConfig([
|
|
393
402
|
{
|
|
394
403
|
name: "paperlint/rules-off",
|
|
395
|
-
plugins: rulePlugins(texLanguage)
|
|
404
|
+
plugins: rulePlugins(texLanguage),
|
|
396
405
|
rules: Object.fromEntries([...SHIPPED_RULES].map((id) => [id, "off"])),
|
|
397
406
|
},
|
|
398
407
|
{
|
|
@@ -400,12 +409,12 @@ export function rulesOff(texLanguage?: unknown): Linter.Config[] {
|
|
|
400
409
|
files: PAPER_FILE_PATTERNS,
|
|
401
410
|
linterOptions: { reportUnusedDisableDirectives: "off" },
|
|
402
411
|
},
|
|
403
|
-
];
|
|
412
|
+
]);
|
|
404
413
|
}
|
|
405
414
|
|
|
406
415
|
/** Whether a rule entry (`"error"`, `2`, `["warn", {…}]`) turns the rule on. */
|
|
407
416
|
const isOn = (entry: unknown): boolean => {
|
|
408
|
-
const sev = Array.isArray(entry) ? entry[0] : entry;
|
|
417
|
+
const sev: unknown = Array.isArray(entry) ? entry[0] : entry;
|
|
409
418
|
return sev !== undefined && sev !== "off" && sev !== 0;
|
|
410
419
|
};
|
|
411
420
|
|
|
@@ -417,7 +426,7 @@ export const OPTIONAL_RULES: ReadonlySet<string> = new Set(
|
|
|
417
426
|
[...SHIPPED_RULES].filter(
|
|
418
427
|
(id) =>
|
|
419
428
|
!buildConfig({}, { sentinel: "tex language" }).some((b) =>
|
|
420
|
-
isOn(
|
|
429
|
+
isOn(b.rules?.[id]),
|
|
421
430
|
),
|
|
422
431
|
),
|
|
423
432
|
);
|
|
@@ -434,8 +443,13 @@ export const OPTIONAL_RULES: ReadonlySet<string> = new Set(
|
|
|
434
443
|
* papers a and b (#103) — a block that reaches a paper outside this run is not dead. A glob that
|
|
435
444
|
* reaches no paper of the project at all still fails, from a subset as from the whole.
|
|
436
445
|
*/
|
|
446
|
+
/** What `calculateConfigForFile` answers, read for its rules: undefined for a file outside ESLint's cwd or scope. */
|
|
447
|
+
const ComputedConfig = z
|
|
448
|
+
.object({ rules: z.record(z.string(), z.unknown()).optional() })
|
|
449
|
+
.optional();
|
|
450
|
+
|
|
437
451
|
export async function silentOptionalRules(
|
|
438
|
-
eslint: ESLint,
|
|
452
|
+
eslint: Pick<ESLint, "calculateConfigForFile">,
|
|
439
453
|
files: readonly string[],
|
|
440
454
|
opts: PaperlintConfig,
|
|
441
455
|
): Promise<string[]> {
|
|
@@ -449,8 +463,7 @@ export async function silentOptionalRules(
|
|
|
449
463
|
const reached = new Set<string>();
|
|
450
464
|
for (const f of new Set(files.filter((p) => basename(p) === MAIN))) {
|
|
451
465
|
// Undefined for a file outside ESLint's cwd or scope: it reaches nothing.
|
|
452
|
-
const cfg = (await eslint.calculateConfigForFile(f))
|
|
453
|
-
{ rules?: Record<string, unknown> } | undefined;
|
|
466
|
+
const cfg = ComputedConfig.parse(await eslint.calculateConfigForFile(f));
|
|
454
467
|
for (const id of turnedOn) if (isOn(cfg?.rules?.[id])) reached.add(id);
|
|
455
468
|
}
|
|
456
469
|
return [...turnedOn].filter((id) => !reached.has(id));
|
|
@@ -470,7 +483,10 @@ export function paperRuleBlocks(
|
|
|
470
483
|
existsSync(p) && statSync(p).isFile() ? dirname(p) : p,
|
|
471
484
|
);
|
|
472
485
|
const papers = [...new Set(dirs.flatMap((p) => [p, ...papersIn(p)]))];
|
|
473
|
-
const out
|
|
486
|
+
const out: { preset: RuleBlock[]; own: RuleBlock[] } = {
|
|
487
|
+
preset: [],
|
|
488
|
+
own: [],
|
|
489
|
+
};
|
|
474
490
|
for (const dir of papers) {
|
|
475
491
|
const p = paperPreset(dir, PRESET_DEPS);
|
|
476
492
|
if (p.kind === "settings-problem")
|
|
@@ -545,6 +561,20 @@ const PAPER_FILE_PATTERNS: string[] = ownedPatterns(
|
|
|
545
561
|
buildConfig({}, { sentinel: "tex language" }),
|
|
546
562
|
);
|
|
547
563
|
|
|
564
|
+
/** `structure` as `checkStructure` reads it: off, or lists of file names over the defaults. */
|
|
565
|
+
const Names = z.array(z.string()).exactOptional();
|
|
566
|
+
const Structure = z
|
|
567
|
+
.union([
|
|
568
|
+
z.literal(false),
|
|
569
|
+
z.object({
|
|
570
|
+
markers: Names,
|
|
571
|
+
require: Names,
|
|
572
|
+
requireOneOf: z.array(z.array(z.string())).exactOptional(),
|
|
573
|
+
ignore: Names,
|
|
574
|
+
}),
|
|
575
|
+
])
|
|
576
|
+
.optional();
|
|
577
|
+
|
|
548
578
|
/**
|
|
549
579
|
* The root `paperlint.json` after the boundary: an unknown key is refused by name, the paper
|
|
550
580
|
* defaults (`extends`, `kind`, `pdf`) are checked the way a paper's own are, and `rules` becomes
|
|
@@ -555,10 +585,9 @@ export function parseSettings(
|
|
|
555
585
|
where: string,
|
|
556
586
|
baseDir: string,
|
|
557
587
|
): Parsed<PaperlintConfig> {
|
|
558
|
-
if (
|
|
588
|
+
if (!isRecord(json))
|
|
559
589
|
return { ok: false, error: `${where}: must be a JSON object` };
|
|
560
|
-
const raw = json
|
|
561
|
-
const opts = raw as PaperlintConfig;
|
|
590
|
+
const raw = json;
|
|
562
591
|
const unknown = unknownKeys(raw);
|
|
563
592
|
if (unknown.length > 0)
|
|
564
593
|
return {
|
|
@@ -569,9 +598,22 @@ export function parseSettings(
|
|
|
569
598
|
};
|
|
570
599
|
const defaults = stringFields(raw);
|
|
571
600
|
if (!defaults.ok) return { ok: false, error: `${where}: ${defaults.error}` };
|
|
601
|
+
const structure = Structure.safeParse(raw["structure"]);
|
|
602
|
+
if (!structure.success)
|
|
603
|
+
return {
|
|
604
|
+
ok: false,
|
|
605
|
+
error: `${where}: "structure" must be false, or an object whose markers, require and ignore are lists of file names and requireOneOf a list of such lists`,
|
|
606
|
+
};
|
|
572
607
|
const rules = parseRuleBlocks(raw["rules"], where, SHIPPED_RULES, baseDir);
|
|
573
608
|
if (!rules.ok) return rules;
|
|
574
|
-
return {
|
|
609
|
+
return {
|
|
610
|
+
ok: true,
|
|
611
|
+
value: {
|
|
612
|
+
papersDir: raw[PAPERS_DIR_FIELD],
|
|
613
|
+
structure: structure.data,
|
|
614
|
+
rules: rules.value,
|
|
615
|
+
},
|
|
616
|
+
};
|
|
575
617
|
}
|
|
576
618
|
|
|
577
619
|
/**
|
|
@@ -701,8 +743,13 @@ export function ownVersion(
|
|
|
701
743
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
702
744
|
): string | undefined {
|
|
703
745
|
try {
|
|
704
|
-
const
|
|
705
|
-
return typeof
|
|
746
|
+
const manifest: unknown = JSON.parse(readManifest());
|
|
747
|
+
return typeof manifest === "object" &&
|
|
748
|
+
manifest !== null &&
|
|
749
|
+
"version" in manifest &&
|
|
750
|
+
typeof manifest.version === "string"
|
|
751
|
+
? manifest.version
|
|
752
|
+
: undefined;
|
|
706
753
|
} catch {
|
|
707
754
|
return undefined;
|
|
708
755
|
}
|
|
@@ -747,7 +794,7 @@ export function readConfig(
|
|
|
747
794
|
try {
|
|
748
795
|
parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
749
796
|
} catch (e) {
|
|
750
|
-
err(`${configPath} is not valid JSON: ${(e
|
|
797
|
+
err(`${configPath} is not valid JSON: ${messageOf(e)}`);
|
|
751
798
|
return { code: 2 };
|
|
752
799
|
}
|
|
753
800
|
// The discovered config is NAMED out loud. Otherwise a run from someone else's directory picks
|
|
@@ -767,7 +814,7 @@ export function readConfig(
|
|
|
767
814
|
|
|
768
815
|
/** The papers directory field of the settings, read by its one declared name; the default when absent. */
|
|
769
816
|
export function papersDirOf(opts: PaperlintConfig): unknown {
|
|
770
|
-
const declared =
|
|
817
|
+
const declared = opts.papersDir;
|
|
771
818
|
return declared === undefined ? DEFAULT_PAPERS_ROOT : declared;
|
|
772
819
|
}
|
|
773
820
|
|
|
@@ -787,7 +834,9 @@ export const papersRoots = (cfg: {
|
|
|
787
834
|
export function toPaths(papers: unknown): string[] {
|
|
788
835
|
if (typeof papers === "string") return papers.trim() ? [papers.trim()] : [];
|
|
789
836
|
if (Array.isArray(papers))
|
|
790
|
-
return papers.filter(
|
|
837
|
+
return papers.filter(
|
|
838
|
+
(x): x is string => typeof x === "string" && x.trim() !== "",
|
|
839
|
+
);
|
|
791
840
|
return [];
|
|
792
841
|
}
|
|
793
842
|
|
|
@@ -834,7 +883,12 @@ export function runHook(
|
|
|
834
883
|
createRequire(import.meta.url).resolve(spec),
|
|
835
884
|
}: {
|
|
836
885
|
err?: typeof console.error;
|
|
837
|
-
|
|
886
|
+
/** Runs the hook with the terminal's streams: `spawnSync` is one, and only its status is read. */
|
|
887
|
+
run?: (
|
|
888
|
+
file: string,
|
|
889
|
+
args: readonly string[],
|
|
890
|
+
options: { readonly stdio: "inherit" },
|
|
891
|
+
) => { readonly status: number | null };
|
|
838
892
|
resolve?: (spec: string) => string;
|
|
839
893
|
} = {},
|
|
840
894
|
): number {
|
|
@@ -1453,8 +1507,7 @@ export async function run(
|
|
|
1453
1507
|
// failing outright — the LaTeX rules simply have no language to run in.
|
|
1454
1508
|
let texLanguage: unknown = null;
|
|
1455
1509
|
try {
|
|
1456
|
-
|
|
1457
|
-
({ texLanguage } = await import("../eslint-rules/latex-language.mjs"));
|
|
1510
|
+
({ texLanguage } = await import("#eslint-rules/latex-language"));
|
|
1458
1511
|
} catch {
|
|
1459
1512
|
texLanguage = null;
|
|
1460
1513
|
}
|
|
@@ -1462,7 +1515,7 @@ export async function run(
|
|
|
1462
1515
|
const eslint = new ESLint({
|
|
1463
1516
|
cwd: lintRoot(root, paths),
|
|
1464
1517
|
overrideConfigFile: true,
|
|
1465
|
-
overrideConfig: buildConfig(withPapers, texLanguage)
|
|
1518
|
+
overrideConfig: eslintConfig(buildConfig(withPapers, texLanguage)),
|
|
1466
1519
|
fix: a.fix,
|
|
1467
1520
|
});
|
|
1468
1521
|
|
|
@@ -1478,8 +1531,7 @@ export async function run(
|
|
|
1478
1531
|
// reached, which is what the very first run over an empty directory showed: instead of a clear
|
|
1479
1532
|
// message a stack from the depths of eslint-helpers.js flew out. A failure stays a failure, but
|
|
1480
1533
|
// an explicable one.
|
|
1481
|
-
|
|
1482
|
-
let results: any[];
|
|
1534
|
+
let results: ESLint.LintResult[];
|
|
1483
1535
|
try {
|
|
1484
1536
|
results = await eslint.lintFiles(paths);
|
|
1485
1537
|
} catch (e) {
|
|
@@ -1540,12 +1592,20 @@ async function firstUnownedFile(
|
|
|
1540
1592
|
* is outside paperlint's scope (`all-matched-files-ignored` — a papers directory holding only
|
|
1541
1593
|
* vendored scripts). Both mean "nothing was linted", which the caller reports itself.
|
|
1542
1594
|
*/
|
|
1595
|
+
/** What a thrown ESLint error says about itself; anything else thrown says nothing. */
|
|
1596
|
+
const EslintFailure = z
|
|
1597
|
+
.object({
|
|
1598
|
+
messageTemplate: z.string().optional(),
|
|
1599
|
+
message: z.string().optional(),
|
|
1600
|
+
})
|
|
1601
|
+
.catch({});
|
|
1602
|
+
|
|
1543
1603
|
const isEmptySet = (e: unknown): boolean => {
|
|
1544
|
-
const fail = e
|
|
1604
|
+
const fail = EslintFailure.parse(e);
|
|
1545
1605
|
return (
|
|
1546
|
-
fail
|
|
1547
|
-
fail
|
|
1548
|
-
/No files matching/i.test(String(fail
|
|
1606
|
+
fail.messageTemplate === "file-not-found" ||
|
|
1607
|
+
fail.messageTemplate === "all-matched-files-ignored" ||
|
|
1608
|
+
/No files matching/i.test(String(fail.message))
|
|
1549
1609
|
);
|
|
1550
1610
|
};
|
|
1551
1611
|
|
|
@@ -1555,8 +1615,7 @@ const isEmptySet = (e: unknown): boolean => {
|
|
|
1555
1615
|
*/
|
|
1556
1616
|
async function reportLint(
|
|
1557
1617
|
eslint: ESLint,
|
|
1558
|
-
|
|
1559
|
-
results: any[],
|
|
1618
|
+
results: ESLint.LintResult[],
|
|
1560
1619
|
structure: ReturnType<typeof checkStructure>,
|
|
1561
1620
|
{
|
|
1562
1621
|
a,
|
|
@@ -1598,7 +1657,7 @@ async function reportLint(
|
|
|
1598
1657
|
out.trim() ||
|
|
1599
1658
|
(structure.length > 0
|
|
1600
1659
|
? ""
|
|
1601
|
-
: `✓ ${results.length} file(s) checked, no findings`),
|
|
1660
|
+
: `✓ ${String(results.length)} file(s) checked, no findings`),
|
|
1602
1661
|
);
|
|
1603
1662
|
}
|
|
1604
1663
|
if (structure.length > 0 || results.some((r) => r.errorCount > 0)) return 1;
|
|
@@ -1608,7 +1667,7 @@ async function reportLint(
|
|
|
1608
1667
|
const warnings = results.reduce((n, r) => n + r.warningCount, 0);
|
|
1609
1668
|
if (warnings > a.maxWarnings) {
|
|
1610
1669
|
err(
|
|
1611
|
-
`${warnings} warning(s) exceed the --max-warnings limit of ${a.maxWarnings}`,
|
|
1670
|
+
`${String(warnings)} warning(s) exceed the --max-warnings limit of ${String(a.maxWarnings)}`,
|
|
1612
1671
|
);
|
|
1613
1672
|
return 1;
|
|
1614
1673
|
}
|
package/src/doctor.harness.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Run: npx vigiles test src/doctor.harness.mjs
|
|
12
12
|
*/
|
|
13
|
-
import { PAPERS_DIR_FIELD } from "../lib/paper-config.
|
|
13
|
+
import { PAPERS_DIR_FIELD } from "../lib/paper-config.ts";
|
|
14
14
|
import {
|
|
15
15
|
mkdtempSync,
|
|
16
16
|
mkdirSync,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
import { dirname, join } from "node:path";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
|
-
import { createChecker } from "../lib/check.
|
|
24
|
+
import { createChecker } from "../lib/check.ts";
|
|
25
25
|
|
|
26
26
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
27
27
|
const { doctor, detectPapers, PROGRAMS, found } = await import(
|
|
@@ -279,7 +279,7 @@ const runDoctor = (
|
|
|
279
279
|
|
|
280
280
|
// ── VII-bis. SKILLS THAT ARE NOT LINKED ARE NAMED — AND DO NOT FAIL THE RUN ─────────────────
|
|
281
281
|
// Before this section a consumer without `.claude/skills/` links had no `/paper-pipeline`, and
|
|
282
|
-
// nothing anywhere said so. The link state itself is `link-skills.harness.
|
|
282
|
+
// nothing anywhere said so. The link state itself is `link-skills.harness.ts`'s subject; here
|
|
283
283
|
// only doctor's REPORTING of it is judged, so the state is handed in rather than built on disk.
|
|
284
284
|
{
|
|
285
285
|
const dir = consumer({ papersDir: "papers", declared: "papers" });
|
package/src/doctor.test.ts
CHANGED
|
@@ -44,7 +44,7 @@ test("doctor: a settings file silent on papersDir, an occupied entry, an unreada
|
|
|
44
44
|
const code = doctor({
|
|
45
45
|
cwd: root,
|
|
46
46
|
log: (s: string) => lines.push(s),
|
|
47
|
-
run: (
|
|
47
|
+
run: () => ({ status: 0 }),
|
|
48
48
|
cliPapers: join(root, "papers"),
|
|
49
49
|
skillLinks: () => ({
|
|
50
50
|
ok: true,
|
package/src/doctor.ts
CHANGED
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
import { PAPER_MARKERS } from "./build.ts";
|
|
37
37
|
import { linkSkills, SKILLS_HOME, type LinkReport } from "./link-skills.ts";
|
|
38
38
|
import { doctorHooks } from "./hooks-settings.ts";
|
|
39
|
+
import { messageOf } from "./domain/text.ts";
|
|
40
|
+
import { fieldOf } from "./domain/record.ts";
|
|
39
41
|
|
|
40
42
|
export interface Program {
|
|
41
43
|
readonly bin: string;
|
|
@@ -98,7 +100,17 @@ export const PROGRAMS: readonly Program[] = [
|
|
|
98
100
|
},
|
|
99
101
|
];
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Asks the shell whether a program is on PATH. `spawnSync` is one; a fake answers with a status
|
|
105
|
+
* alone, because a status is all `found` reads.
|
|
106
|
+
*/
|
|
107
|
+
export type ProgramProbe = (
|
|
108
|
+
file: string,
|
|
109
|
+
args: readonly string[],
|
|
110
|
+
options: { readonly shell: true; readonly stdio: "ignore" },
|
|
111
|
+
) => { readonly status: number | null };
|
|
112
|
+
|
|
113
|
+
export const found = (bin: string, run: ProgramProbe = spawnSync): boolean =>
|
|
102
114
|
run("command", ["-v", bin], { shell: true, stdio: "ignore" }).status === 0;
|
|
103
115
|
|
|
104
116
|
/** Lists a directory's entries; the disk by default, a test stages an unreadable child. */
|
|
@@ -169,15 +181,13 @@ function declarationVerdict(raw: string | null): {
|
|
|
169
181
|
} catch (e) {
|
|
170
182
|
return {
|
|
171
183
|
lines: [
|
|
172
|
-
` ✗ ${CONFIG_FILE} is not valid JSON: ${(e
|
|
184
|
+
` ✗ ${CONFIG_FILE} is not valid JSON: ${messageOf(e)}`,
|
|
173
185
|
` the edit guard refuses every Bash command until it parses — fix it with Edit or Write`,
|
|
174
186
|
],
|
|
175
187
|
bad: 1,
|
|
176
188
|
};
|
|
177
189
|
}
|
|
178
|
-
const declared = (settings
|
|
179
|
-
PAPERS_DIR_FIELD
|
|
180
|
-
];
|
|
190
|
+
const declared = fieldOf(settings, PAPERS_DIR_FIELD);
|
|
181
191
|
return {
|
|
182
192
|
lines: [
|
|
183
193
|
declared === undefined
|
|
@@ -233,7 +243,7 @@ export interface DoctorOptions {
|
|
|
233
243
|
cwd?: string;
|
|
234
244
|
/** The project root the hooks are anchored to. Claude Code sets this; it defaults to `cwd`. */
|
|
235
245
|
projectDir?: string;
|
|
236
|
-
run?:
|
|
246
|
+
run?: ProgramProbe;
|
|
237
247
|
/** The directory the CLI resolved from its own config, so both sides can be compared. */
|
|
238
248
|
cliPapers?: string | null;
|
|
239
249
|
/** Reads the skill links without writing any. Injected only so a test can fake the install. */
|
|
@@ -334,7 +344,7 @@ export function doctor({
|
|
|
334
344
|
try {
|
|
335
345
|
out.push(...doctorHooks(root));
|
|
336
346
|
} catch (e) {
|
|
337
|
-
out.push(`hooks`, ` ⚠ not checked — ${(e
|
|
347
|
+
out.push(`hooks`, ` ⚠ not checked — ${messageOf(e)}`);
|
|
338
348
|
}
|
|
339
349
|
out.push("");
|
|
340
350
|
log(out.join("\n"));
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* never read as empty.
|
|
32
32
|
*/
|
|
33
33
|
import { err, ok, type Result } from "./result.ts";
|
|
34
|
+
import { messageOf } from "./text.ts";
|
|
34
35
|
|
|
35
36
|
export const LOOKUP_CACHE_SCHEMA = 1;
|
|
36
37
|
|
|
@@ -106,7 +107,7 @@ function sectionOf<T>(
|
|
|
106
107
|
name: string,
|
|
107
108
|
section: unknown,
|
|
108
109
|
known: readonly string[],
|
|
109
|
-
one: (o: Json, at: string) => Result<T, string>,
|
|
110
|
+
one: (o: Json, fetched: string, at: string) => Result<T, string>,
|
|
110
111
|
): Result<Map<string, T>, string> {
|
|
111
112
|
if (!isObject(section)) return err(`\`${name}\`: expected an object`);
|
|
112
113
|
const out = new Map<string, T>();
|
|
@@ -115,18 +116,23 @@ function sectionOf<T>(
|
|
|
115
116
|
if (!isObject(v)) return err(`${at}\`: expected an object`);
|
|
116
117
|
const extra = unknownField(v, known);
|
|
117
118
|
if (extra !== undefined) return err(`${at}\`: unknown field \`${extra}\``);
|
|
118
|
-
|
|
119
|
+
const fetched = v["fetched"];
|
|
120
|
+
if (!isDate(fetched))
|
|
119
121
|
return err(`${at}.fetched\`: expected a date, YYYY-MM-DD`);
|
|
120
|
-
const r = one(v, at);
|
|
122
|
+
const r = one(v, fetched, at);
|
|
121
123
|
if (!r.ok) return r;
|
|
122
124
|
out.set(key, r.value);
|
|
123
125
|
}
|
|
124
126
|
return ok(out);
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
const cachedResponse = (
|
|
129
|
+
const cachedResponse = (
|
|
130
|
+
o: Json,
|
|
131
|
+
fetched: string,
|
|
132
|
+
at: string,
|
|
133
|
+
): Result<CachedResponse, string> =>
|
|
128
134
|
isObject(o["response"]) && o["response"]["transport"] === "ok"
|
|
129
|
-
? ok({ fetched
|
|
135
|
+
? ok({ fetched, response: o["response"] })
|
|
130
136
|
: err(
|
|
131
137
|
`${at}.response\`: expected a successful response ({"transport": "ok", …})`,
|
|
132
138
|
);
|
|
@@ -137,7 +143,11 @@ const isHit = (h: unknown): h is DblpHit =>
|
|
|
137
143
|
Array.isArray(h["authors"]) &&
|
|
138
144
|
h["authors"].every((a) => typeof a === "string");
|
|
139
145
|
|
|
140
|
-
function cachedDblp(
|
|
146
|
+
function cachedDblp(
|
|
147
|
+
o: Json,
|
|
148
|
+
fetched: string,
|
|
149
|
+
at: string,
|
|
150
|
+
): Result<CachedDblp, string> {
|
|
141
151
|
const { title, hits } = o;
|
|
142
152
|
if (typeof title !== "string") return err(`${at}.title\`: expected a string`);
|
|
143
153
|
if (!Array.isArray(hits)) return err(`${at}.hits\`: expected an array`);
|
|
@@ -147,9 +157,9 @@ function cachedDblp(o: Json, at: string): Result<CachedDblp, string> {
|
|
|
147
157
|
`${at}.hits[${String(bad)}]\`: expected {venue, year, type, title: string; authors: string[]}`,
|
|
148
158
|
);
|
|
149
159
|
return ok({
|
|
150
|
-
fetched
|
|
160
|
+
fetched,
|
|
151
161
|
title,
|
|
152
|
-
hits: hits
|
|
162
|
+
hits: hits.filter(isHit),
|
|
153
163
|
});
|
|
154
164
|
}
|
|
155
165
|
|
|
@@ -159,7 +169,7 @@ export function parseLookupCache(text: string): Result<LookupCache, string> {
|
|
|
159
169
|
try {
|
|
160
170
|
json = JSON.parse(text);
|
|
161
171
|
} catch (e) {
|
|
162
|
-
return err(`not JSON: ${(e
|
|
172
|
+
return err(`not JSON: ${messageOf(e)}`);
|
|
163
173
|
}
|
|
164
174
|
if (!isObject(json))
|
|
165
175
|
return err("expected an object with `schema`, `citations` and `dblp`");
|
|
@@ -186,8 +196,8 @@ export function parseLookupCache(text: string): Result<LookupCache, string> {
|
|
|
186
196
|
return ok({ citations: citations.value, dblp: dblp.value });
|
|
187
197
|
}
|
|
188
198
|
|
|
189
|
-
const sorted = <T>(m: ReadonlyMap<string, T>): Record<string, T> =>
|
|
190
|
-
Object.fromEntries([...m.keys()].sort().map((k) => [k, m.get(k)
|
|
199
|
+
const sorted = <T>(m: ReadonlyMap<string, T>): Record<string, T | undefined> =>
|
|
200
|
+
Object.fromEntries([...m.keys()].sort().map((k) => [k, m.get(k)]));
|
|
191
201
|
|
|
192
202
|
/** The cache as the file holds it: keys sorted, so one new answer is a small diff. */
|
|
193
203
|
export const serializeLookupCache = (c: LookupCache): string =>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "vitest";
|
|
3
|
+
import { absolutePath, joinPath } from "./paths.ts";
|
|
4
|
+
|
|
5
|
+
test("absolutePath keeps an absolute path as it is", () => {
|
|
6
|
+
assert.equal(absolutePath("/a/b"), "/a/b");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("absolutePath refuses a relative path instead of letting the process's cwd decide it", () => {
|
|
10
|
+
assert.throws(() => absolutePath("a/b"), /not an absolute path: a\/b/);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("joinPath stays under its absolute base", () => {
|
|
14
|
+
assert.equal(joinPath(absolutePath("/a"), "b", "c"), "/a/b/c");
|
|
15
|
+
});
|
package/src/domain/paths.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
/** Paths as the domain holds them. */
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
2
3
|
import type { Opaque } from "ts-essentials";
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* An absolute path. Minted by `absolutePath` (a checked string) or `joinPath` (an absolute base and
|
|
7
|
+
* segments) only, so nothing holding one has to ask whether it is relative.
|
|
8
|
+
*/
|
|
5
9
|
export type AbsolutePath = Opaque<string, "AbsolutePath">;
|
|
10
|
+
|
|
11
|
+
// The brand's one owner: the predicate IS the check, so no assertion is needed to mint the type.
|
|
12
|
+
const isAbsolutePath = (p: string): p is AbsolutePath => isAbsolute(p);
|
|
13
|
+
|
|
14
|
+
/** A path the caller holds as text, checked where it becomes a value. A relative one throws. */
|
|
15
|
+
export function absolutePath(p: string): AbsolutePath {
|
|
16
|
+
if (!isAbsolutePath(p)) throw new Error(`not an absolute path: ${p}`);
|
|
17
|
+
return p;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `segments` under `base`: absolute because `base` is. */
|
|
21
|
+
export const joinPath = (
|
|
22
|
+
base: AbsolutePath,
|
|
23
|
+
...segments: string[]
|
|
24
|
+
): AbsolutePath => absolutePath(join(base, ...segments));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A program `paperlint toolchain` installed or found: present, verified, running. */
|
|
2
|
+
|
|
3
|
+
// Not exported: a `Ready` carries this property, and the one way to write it is `ready` below.
|
|
4
|
+
const READY = Symbol("Ready");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Present, verified, running. BRANDED: a literal `{ where, fresh, verified }` is not a `Ready`, so
|
|
8
|
+
* only an installer's adapter — one minting call each (banal's is `ready` in
|
|
9
|
+
* `adapters/banal/index.ts`) — can make one, and a ready line cannot be printed without it.
|
|
10
|
+
*/
|
|
11
|
+
export interface Ready {
|
|
12
|
+
/** Where it is: the program's path or its bin directory. */
|
|
13
|
+
readonly where: string;
|
|
14
|
+
/** This run installed it. */
|
|
15
|
+
readonly fresh: boolean;
|
|
16
|
+
/** What was checked before calling it ready, as a person reads it. */
|
|
17
|
+
readonly verified: string;
|
|
18
|
+
readonly [READY]: true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** An installer's verdict that its program is ready. Called by an installer adapter, after its checks. */
|
|
22
|
+
export const ready = (r: {
|
|
23
|
+
readonly where: string;
|
|
24
|
+
readonly fresh: boolean;
|
|
25
|
+
readonly verified: string;
|
|
26
|
+
}): Ready => ({
|
|
27
|
+
where: r.where,
|
|
28
|
+
fresh: r.fresh,
|
|
29
|
+
verified: r.verified,
|
|
30
|
+
[READY]: true,
|
|
31
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "vitest";
|
|
3
|
+
import { fieldOf, isRecord, numbersOf } from "./record.ts";
|
|
4
|
+
|
|
5
|
+
test("isRecord: an object, and not null, an array or a string", () => {
|
|
6
|
+
assert.deepEqual(
|
|
7
|
+
[isRecord({}), isRecord(null), isRecord([]), isRecord("x")],
|
|
8
|
+
[true, false, false, false],
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("fieldOf reads an own or inherited field of a record, and nothing of anything else", () => {
|
|
13
|
+
assert.equal(fieldOf({ a: 1 }, "a"), 1);
|
|
14
|
+
assert.equal(fieldOf(new TypeError("x"), "name"), "TypeError");
|
|
15
|
+
assert.equal(fieldOf("text", "length"), undefined);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("numbersOf keeps numbers as they are", () => {
|
|
19
|
+
assert.deepEqual(numbersOf([1, -0.5, 0]), [1, -0.5, 0]);
|
|
20
|
+
});
|