paperlint 3.0.0 → 3.1.1
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 +28 -41
- package/.github/workflows/dependabot-automerge.yml +1 -1
- package/CLAUDE.md +51 -56
- package/CONTRIBUTING.md +30 -18
- package/README.md +179 -180
- package/bin/paperlint.test.mjs +45 -0
- package/dist/adapters/node/files.io.d.ts +2 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +3 -2
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts +15 -4
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +31 -21
- 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 +14 -5
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts +8 -0
- package/dist/adapters/references/pool.d.ts.map +1 -0
- package/dist/adapters/references/pool.js +19 -0
- package/dist/adapters/references/pool.js.map +1 -0
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +6 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +10 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +29 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +93 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +296 -80
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +27 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +4 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/result.d.ts +0 -2
- package/dist/domain/result.d.ts.map +1 -1
- package/dist/domain/result.js +0 -4
- package/dist/domain/result.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -1
- package/dist/hooks-settings.d.ts +8 -5
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +32 -32
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +4 -4
- package/dist/init.js.map +1 -1
- package/dist/latex-log.d.ts +0 -2
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +7 -13
- package/dist/latex-log.js.map +1 -1
- package/dist/link-skills.d.ts +15 -2
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +36 -20
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts +8 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +50 -15
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +30 -8
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +59 -30
- package/dist/paper-files.js.map +1 -1
- package/dist/pdf-facts.d.ts +11 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +16 -18
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -1
- package/dist/presets.js.map +1 -1
- package/dist/references.js +2 -2
- package/dist/references.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +8 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +6 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +8 -3
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +26 -10
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +12 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +48 -4
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/test-tooling.md +7 -30
- package/docs/rules.md +29 -25
- package/docs/skills.md +80 -0
- package/eslint-rules/install-path-literals.harness.mjs +17 -1
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.mjs +12 -9
- package/eslint-rules/latex-language.test.mjs +200 -0
- package/eslint-rules/paper-research-question.harness.mjs +73 -7
- package/eslint-rules/paper-stages.harness.mjs +9 -14
- package/eslint-rules/paper-stages.test.mjs +135 -0
- package/eslint-rules/paper-typography.mjs +56 -9
- package/eslint-rules/paper-typography.test.mjs +186 -1
- package/eslint-rules/pdf-last-page-balance.harness.mjs +63 -7
- package/eslint-rules/review-frontmatter.mjs +2 -1
- package/eslint-rules/review-frontmatter.schema.json +1 -1
- package/eslint-rules/review-frontmatter.test.mjs +37 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +49 -0
- package/eslint-rules/tex-build.mjs +13 -13
- package/eslint.config.mjs +6 -2
- package/fixtures/toolchain-mirror/catalog.txt +1 -0
- package/fixtures/toolchain-mirror/install-tl +4 -1
- package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
- package/hooks/hooks.harness.mjs +6 -11
- package/hooks/paper-edit-guard.hook.mjs +2 -2
- package/hooks/paper-edit-guard.test.mjs +61 -0
- package/hooks/paper-skills-nudge.hook.mjs +2 -2
- package/hooks/paper-skills-nudge.test.mjs +38 -0
- package/hooks/paper-status-gates.test.mjs +42 -0
- package/lib/agent-cli-version.harness.mjs +7 -0
- package/lib/check.d.mts +17 -0
- package/lib/check.mjs +50 -0
- package/lib/check.test.ts +67 -0
- package/lib/markdown.mjs +16 -69
- package/lib/markdown.test.mjs +246 -0
- package/lib/paper-config.harness.mjs +28 -9
- package/lib/paper-config.mjs +7 -2
- package/lib/skill-checks.mjs +18 -46
- package/lib/skill-checks.test.mjs +297 -0
- package/lib/skill-corpus.mjs +11 -13
- package/lib/skill-corpus.test.mjs +54 -0
- package/lib/skill-trigger-cases.harness.mjs +57 -4
- package/package.json +6 -5
- package/scripts/check.harness.mjs +4 -10
- package/scripts/check.mjs +45 -42
- package/scripts/check.test.mjs +104 -0
- package/scripts/coverage-config.test.ts +406 -0
- package/scripts/eslint-report-guard.test.mjs +48 -0
- package/scripts/harness-api.frozen.json +0 -2
- package/scripts/layer-legacy-frozen.d.mts +9 -1
- package/scripts/layer-legacy-frozen.mjs +28 -11
- package/scripts/layer-legacy-frozen.test.ts +143 -1
- package/scripts/rules-are-content-only.harness.mjs +3 -11
- package/scripts/rules-are-content-only.mjs +29 -19
- package/scripts/rules-are-content-only.test.mjs +61 -0
- package/scripts/rules-see-files.mjs +11 -9
- package/scripts/rules-see-files.test.mjs +71 -0
- package/scripts/test.mjs +44 -0
- package/scripts/test.test.mjs +52 -0
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.mjs.snap +57 -0
- package/skills/grade-paper-writing/prose-lint.mjs +31 -12
- package/skills/grade-paper-writing/prose-lint.test.mjs +153 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +3 -3
- package/skills/paper-pipeline/scripts/README.md +13 -132
- package/skills/paper-pipeline/scripts/announce.test.mjs +102 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.test.mjs +86 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +8 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/consumer.mjs +13 -25
- package/skills/paper-pipeline/scripts/consumer.test.mjs +113 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +9 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +380 -0
- package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +71 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +4 -4
- package/skills/paper-pipeline/scripts/generated-code.mjs +3 -2
- package/skills/paper-pipeline/scripts/generated-code.test.mjs +62 -0
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +302 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +1 -1
- package/skills/paper-pipeline/scripts/{ledger.selftest.mjs → ledger.test.mjs} +44 -76
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +474 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -63
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +37 -21
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +73 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +4 -47
- package/skills/paper-pipeline/scripts/population-map.mjs +12 -2
- package/skills/paper-pipeline/scripts/population-map.test.mjs +154 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +26 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +1 -2
- package/skills/paper-pipeline/scripts/round-diff.mjs +13 -6
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +159 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +30 -6
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +468 -0
- package/skills/paper-pipeline/scripts/status.mjs +2 -2
- package/skills/paper-pipeline/scripts/status.test.mjs +232 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +2 -2
- package/skills/render-paper/extract-pdf-facts.harness.mjs +56 -7
- package/skills/tighten-paper/__snapshots__/structure.test.mjs.snap +133 -0
- package/skills/tighten-paper/structure.test.mjs +116 -0
- package/skills/verify-citations/SKILL.md +5 -5
- package/skills/verify-citations/SKILL.md.spec.ts +4 -4
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +235 -0
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +274 -0
- package/skills/verify-citations/scripts/bib-authors.cli.test.mjs +178 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +2 -1
- package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +96 -96
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +287 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +149 -5
- package/src/adapters/banal/failure.test.ts +70 -1
- package/src/adapters/banal/index.test.ts +16 -0
- package/src/adapters/banal/output.test.ts +5 -0
- package/src/adapters/curl/download.test.ts +43 -1
- package/src/adapters/memory/index.test.ts +25 -0
- package/src/adapters/node/files.io.ts +3 -2
- package/src/adapters/node/files.test.ts +13 -1
- package/src/adapters/node/process.test.ts +38 -0
- package/src/adapters/pdfjs/fill.test.ts +34 -0
- package/src/adapters/pdfjs/fill.ts +44 -25
- package/src/adapters/references/index.test.ts +228 -0
- package/src/adapters/references/index.ts +21 -6
- package/src/adapters/references/pool.test.ts +31 -0
- package/src/adapters/references/pool.ts +24 -0
- package/src/build-engine.harness.mjs +3 -7
- package/src/build-engine.test.ts +137 -0
- package/src/build-engine.ts +6 -1
- package/src/build.harness.mjs +51 -11
- package/src/build.test.ts +362 -0
- package/src/build.ts +40 -14
- package/src/cli.harness.mjs +12 -19
- package/src/cli.test.ts +584 -0
- package/src/cli.ts +409 -94
- package/src/doctor.harness.mjs +3 -8
- package/src/doctor.test.ts +64 -0
- package/src/doctor.ts +12 -4
- package/src/domain/result.test.ts +4 -0
- package/src/domain/result.ts +0 -5
- package/src/engine.harness.mjs +77 -7
- package/src/engine.ts +35 -0
- package/src/facts-file.test.ts +110 -0
- package/src/hooks-settings.harness.mjs +43 -13
- package/src/hooks-settings.ts +41 -35
- package/src/init.test.ts +308 -5
- package/src/init.ts +4 -4
- package/src/latex-log.harness.mjs +32 -7
- package/src/latex-log.ts +6 -13
- package/src/latex-loop.harness.mjs +8 -7
- package/src/link-skills.harness.mjs +3 -8
- package/src/link-skills.test.ts +105 -0
- package/src/link-skills.ts +75 -21
- package/src/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +5 -7
- package/src/new-paper.test.ts +203 -2
- package/src/new-paper.ts +84 -17
- package/src/paper-files.test.ts +122 -5
- package/src/paper-files.ts +99 -39
- package/src/paper-settings-commands.test.ts +41 -5
- package/src/paper-settings.test.ts +20 -0
- package/src/parse-args.test.ts +48 -0
- package/src/pdf-facts.harness.mjs +3 -6
- package/src/pdf-facts.test.ts +144 -0
- package/src/pdf-facts.ts +17 -19
- package/src/pdf-geometry.harness.mjs +3 -7
- package/src/pdf-geometry.ts +1 -1
- package/src/presets.test.ts +2 -0
- package/src/presets.ts +7 -1
- package/src/references.test.ts +97 -0
- package/src/references.ts +2 -2
- package/src/rules-config.harness.mjs +10 -8
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +5 -9
- package/src/structure.ts +1 -1
- package/src/tex-requirements.harness.mjs +34 -7
- package/src/tex-requirements.ts +12 -6
- package/src/toolchain.harness.mjs +44 -13
- package/src/toolchain.test.ts +280 -0
- package/src/toolchain.ts +37 -12
- package/src/types.ts +6 -0
- package/src/venue-rules.test.ts +63 -0
- package/src/venue-rules.ts +14 -12
- package/templates/paper/paperlint.json +1 -1
- package/action.mutations.mjs +0 -162
- package/eslint-rules/install-path-literals.mutations.mjs +0 -62
- package/eslint-rules/latex-language.mutations.mjs +0 -196
- package/eslint-rules/paper-research-question.mutations.mjs +0 -127
- package/eslint-rules/paper-stages.mutations.mjs +0 -81
- package/eslint-rules/papers.mutations.mjs +0 -186
- package/eslint-rules/temp-root-realpath.mutations.mjs +0 -99
- package/eslint-rules/tex-build.mutations.mjs +0 -258
- package/hooks/hooks.mutations.mjs +0 -337
- package/lib/agent-cli-version.mutations.mjs +0 -109
- package/lib/mutation-driver.harness.mjs +0 -227
- package/lib/mutation-driver.mjs +0 -397
- package/lib/mutation-driver.mutations.mjs +0 -68
- package/lib/paper-config.mutations.mjs +0 -143
- package/lib/skill-trigger-cases.mutations.mjs +0 -65
- package/scripts/check.mutations.mjs +0 -110
- package/scripts/exclusive.mjs +0 -138
- package/scripts/mutation-batteries-frozen.harness.mjs +0 -204
- package/scripts/mutation-batteries-frozen.mjs +0 -238
- package/scripts/mutation-batteries.frozen.json +0 -108
- package/scripts/rules-are-content-only.mutations.mjs +0 -81
- package/scripts/rules-see-files.mutations.mjs +0 -131
- package/scripts/run-mutations.mjs +0 -100
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +0 -218
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +0 -190
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +0 -65
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +0 -122
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +0 -59
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +0 -276
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +0 -65
- package/skills/skill-contract.mutations.mjs +0 -179
- package/src/build.mutations.mjs +0 -196
- package/src/cli.mutations.mjs +0 -414
- package/src/doctor.mutations.mjs +0 -175
- package/src/hooks-settings.mutations.mjs +0 -99
- package/src/link-skills.mutations.mjs +0 -140
- package/src/new-paper.mutations.mjs +0 -79
- package/src/structure.mutations.mjs +0 -83
package/.github/workflows/ci.yml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ── WHY ONE JOB ─────────────────────────────────────────────────────────────
|
|
4
4
|
# Parallelism does not save billable minutes, it ADDS them: every job pays for its own
|
|
5
5
|
# setup + checkout + install (~20-30 s). Splitting is worth it for signal isolation on a big
|
|
6
|
-
# matrix; here the whole suite is
|
|
6
|
+
# matrix; here the whole suite is ~96 seconds, so a second job would cost more than it isolates.
|
|
7
7
|
#
|
|
8
8
|
# ⛽ THIS REPOSITORY IS PUBLIC, so its minutes are FREE — they do not touch the account's
|
|
9
9
|
# 3000/month quota (verified against the API 2026-09-12: `"private": false`). The header said
|
|
@@ -11,23 +11,24 @@
|
|
|
11
11
|
# rather than guessed. Keep measuring them anyway: the bond exists to stop a HANG, and a hang
|
|
12
12
|
# costs wall-clock and a red check whether or not anyone is billed.
|
|
13
13
|
#
|
|
14
|
-
# ── THE MEASUREMENT THE BONDS COME FROM (local, 2026-09-
|
|
15
|
-
# npm
|
|
16
|
-
# npm
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
14
|
+
# ── THE MEASUREMENT THE BONDS COME FROM (local, 4 cores, 2026-09-26) ─────────
|
|
15
|
+
# npm run build 2 s
|
|
16
|
+
# npm test -- --no-skip (69 harnesses) 41 s ← vitest + every harness
|
|
17
|
+
# npm run lint 6 s
|
|
18
|
+
# npm run fmt:check 5 s
|
|
19
|
+
# vigiles lint . README.md 6 s
|
|
20
|
+
# rules-see-files.mjs 5 s
|
|
21
|
+
# layer-legacy-frozen.mjs 3 s
|
|
22
|
+
# install e2e --strict (npm + pnpm) 28 s
|
|
20
23
|
# ───────────────────────────────────────────
|
|
21
|
-
# total work ~
|
|
24
|
+
# total work ~96 s, plus checkout + install + the claude binary
|
|
22
25
|
#
|
|
23
|
-
# 🔴 THE PREVIOUS
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# as a broken suite rather than as a bond. Re-measure when the counts move again; do not cite the
|
|
28
|
-
# table above without re-running it.
|
|
26
|
+
# 🔴 THE PREVIOUS TABLE (2026-09-12) had one more row, `run-mutations.mjs`, at 242 s of ~255 s, and
|
|
27
|
+
# by 2026-09-23 it was 8m39s of a 10m02s job (#52). The batteries were removed in #52, which is
|
|
28
|
+
# what took the job from `timeout-minutes: 20` to 10. Re-measure when the counts move again; do not
|
|
29
|
+
# cite the table above without re-running it.
|
|
29
30
|
#
|
|
30
|
-
# Expect ~
|
|
31
|
+
# Expect ~3 minutes warm. `timeout-minutes: 10` is ~3×, and `npm ci` carries its OWN bond
|
|
31
32
|
# because a job-level bond cannot tell "the registry hung" from "the suite is slow" and so hands
|
|
32
33
|
# a hang the whole job.
|
|
33
34
|
name: ci
|
|
@@ -62,7 +63,7 @@ jobs:
|
|
|
62
63
|
# there is no pull_request object, and a bare `.draft` would be missing and kill every check there.
|
|
63
64
|
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
|
|
64
65
|
runs-on: ubuntu-latest
|
|
65
|
-
timeout-minutes:
|
|
66
|
+
timeout-minutes: 10
|
|
66
67
|
steps:
|
|
67
68
|
- uses: actions/checkout@v7
|
|
68
69
|
|
|
@@ -88,14 +89,10 @@ jobs:
|
|
|
88
89
|
# i.e., claimed a protection that does not exist. Protection against this case comes from `--no-skip` below.
|
|
89
90
|
# 🔴 THE `claude` CLI IS A TEST DEPENDENCY HERE, and its absence was NOT harmless.
|
|
90
91
|
# `plan-paper-timeline.effects.harness.mjs` spawns the real agent binary; without it the
|
|
91
|
-
# harness SKIPS,
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# The driver now REFUSES a battery whose harness skips (lib/mutation-driver.mjs), so the
|
|
96
|
-
# wrong diagnosis is gone either way. This step is the other half: it restores the evidence
|
|
97
|
-
# instead of settling for an honest "no evidence". No API key — the tier drives a scripted
|
|
98
|
-
# stand-in, which is why it costs seconds and $0.
|
|
92
|
+
# harness SKIPS, and a skip is not evidence. (Measured 2026-09-15 on run 34966606186, when the
|
|
93
|
+
# since-removed mutation batteries read that skip as two "survivors" in CI and none locally.)
|
|
94
|
+
# This step restores the evidence instead of settling for an honest "no evidence". No API
|
|
95
|
+
# key — the tier drives a scripted stand-in, which is why it costs seconds and $0.
|
|
99
96
|
# 🔴 DELIBERATELY UNPINNED, and since issue #7 that is finally honest. The tier exists to
|
|
100
97
|
# notice upstream moving, so a pin would defeat it — but until the version was OBSERVED
|
|
101
98
|
# rather than probed for, an unpinned install meant the assertions characterized whatever
|
|
@@ -110,14 +107,16 @@ jobs:
|
|
|
110
107
|
# that failed to LOAD. In other words, one flag closes both doors at once.
|
|
111
108
|
#
|
|
112
109
|
# ⚠️ The step order was incorrect and made the install above nearly pointless: `npm test`
|
|
113
|
-
# ran BEFORE it, so the effects tier in this job was always skipped
|
|
114
|
-
# reached only the mutation batteries. The comment about "its absence was NOT harmless"
|
|
110
|
+
# ran BEFORE it, so the effects tier in this job was always skipped. The comment about "its absence was NOT harmless"
|
|
115
111
|
# described exactly this harm yet did not protect against it itself.
|
|
116
112
|
#
|
|
117
113
|
# ⚠️ And the flag belongs IN THE STEP, not in `package.json`: the macOS cell of build-e2e runs the same `npm test`
|
|
118
114
|
# WITHOUT installing the binary, where skips are legitimate, and a global flag would falsely color it.
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
#
|
|
116
|
+
# `npm run coverage` is `npm test` under c8 with the thresholds in .c8rc.json: the same tests,
|
|
117
|
+
# one run, and a drop below the coverage floor fails this step by name.
|
|
118
|
+
- name: npm test, under coverage
|
|
119
|
+
run: npm run coverage -- --no-skip
|
|
121
120
|
|
|
122
121
|
# The rules themselves, over this repository's own fixtures. Two of the four fixtures are
|
|
123
122
|
# DEFECTIVE ON PURPOSE, so a healthy checkout reports warnings here and still exits 0 —
|
|
@@ -161,22 +160,11 @@ jobs:
|
|
|
161
160
|
- name: every rule sees a file
|
|
162
161
|
run: node scripts/rules-see-files.mjs
|
|
163
162
|
|
|
164
|
-
# Hand-written mutation batteries are being removed (#52): the frozen list may only
|
|
165
|
-
# shrink — no new battery, no new case in an existing one.
|
|
166
|
-
- name: mutation batteries are frozen
|
|
167
|
-
run: node scripts/mutation-batteries-frozen.mjs
|
|
168
|
-
|
|
169
163
|
# The legacy exemptions from src/'s layer rules (#76) may only shrink: counted from ESLint's
|
|
170
164
|
# own suppressedMessages, so a new `-- legacy …` directive anywhere fails here.
|
|
171
165
|
- name: legacy layer exemptions are frozen
|
|
172
166
|
run: node scripts/layer-legacy-frozen.mjs
|
|
173
167
|
|
|
174
|
-
# The batteries that remain still run until #52 replaces or removes them. The runner
|
|
175
|
-
# DISCOVERS batteries rather than listing them and errors on finding zero — see
|
|
176
|
-
# scripts/run-mutations.mjs for why (the hand list it replaced had gone stale, silently).
|
|
177
|
-
- name: mutation batteries
|
|
178
|
-
run: node scripts/run-mutations.mjs
|
|
179
|
-
|
|
180
168
|
# pnpm is INSTALLED here rather than hoped for. The runner image may or may not carry it,
|
|
181
169
|
# and the npm/pnpm difference is the entire reason the install e2e exists — so leaving it
|
|
182
170
|
# to the image means the half that matters most is covered by luck. `corepack` ships with
|
|
@@ -244,8 +232,7 @@ jobs:
|
|
|
244
232
|
: "DIVERGE — this is the platform this job exists for");
|
|
245
233
|
'
|
|
246
234
|
|
|
247
|
-
# Issue #9: `npm test` was red ONLY on macOS.
|
|
248
|
-
# they would verify on macOS exactly what the gates job verifies on Linux.
|
|
235
|
+
# Issue #9: `npm test` was red ONLY on macOS.
|
|
249
236
|
- name: npm test (macOS)
|
|
250
237
|
if: runner.os == 'macOS'
|
|
251
238
|
run: npm test
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
#
|
|
17
17
|
# 🔴 WHY WE MERGE ANY BUMP, INCLUDING MAJOR, not just patch/minor.
|
|
18
18
|
# Semver is a PUBLISHER'S CLAIM about their release; `ci` is a MEASUREMENT on our corpus (full harness suite,
|
|
19
|
-
#
|
|
19
|
+
# gates for "rules see their files" and "rules don't touch git"). A major bump that breaks us
|
|
20
20
|
# reds the run and does not merge; a minor one that breaks us does the same.
|
|
21
21
|
# A version-number filter adds a manual step where measurement already exists, and would miss
|
|
22
22
|
# exactly the class for which version filters are typically used.
|
package/CLAUDE.md
CHANGED
|
@@ -35,7 +35,6 @@ assumed. Two things that sound like limits and are not:
|
|
|
35
35
|
| ----------------------- | -----------------------------------------------------------------------------------------: |
|
|
36
36
|
| ESLint rules | **5** — `latex-language` · `tex-build` · `papers` · `review-findings-cause` · `doc-fields` |
|
|
37
37
|
| harnesses | **57** |
|
|
38
|
-
| mutation batteries | **34** |
|
|
39
38
|
| skills | **24** |
|
|
40
39
|
| hooks (runnable `.mjs`) | **5** |
|
|
41
40
|
| repo-wide scripts | 5 |
|
|
@@ -75,8 +74,8 @@ nodes.
|
|
|
75
74
|
|
|
76
75
|
**3. Every check needs BOTH halves, or it is not tested.** It must FIRE on a planted defect
|
|
77
76
|
and stay QUIET on a clean fixture. A check that has only been seen quiet is
|
|
78
|
-
indistinguishable from a dead one — silence is its success state. Prove the fire half
|
|
79
|
-
|
|
77
|
+
indistinguishable from a dead one — silence is its success state. Prove the fire half by
|
|
78
|
+
seeing the test go RED before the fix lands (§ Testing — how a test is written here).
|
|
80
79
|
|
|
81
80
|
**4. `exit 0` with empty output is NOT "clean".** A rule whose glob matched no files reports
|
|
82
81
|
exactly like a rule that passed. Any rule shipped here must be loud when its input set is
|
|
@@ -461,26 +460,8 @@ node scripts/rules-see-files.mjs # also part of npm run check
|
|
|
461
460
|
It is per RULE, not per glob, and that distinction is the point: a rule can be enabled in one
|
|
462
461
|
block whose glob is empty while a different block is busy, so "some glob matched something" is
|
|
463
462
|
not evidence about the rule you care about. Both halves are tested
|
|
464
|
-
(`scripts/rules-see-files.harness.mjs`) and
|
|
465
|
-
|
|
466
|
-
_different_ assertions, or only one half of the guard is really tested).
|
|
467
|
-
|
|
468
|
-
## Mutations — hand-written batteries are deprecated (#52)
|
|
469
|
-
|
|
470
|
-
The `*.mutations.mjs` batteries (string replacements of source lines, run through
|
|
471
|
-
`lib/mutation-driver.mjs`) are being removed. The idea stays — a test must be seen going red
|
|
472
|
-
when the code breaks — but the vehicle is not this one.
|
|
473
|
-
|
|
474
|
-
- **Do not create a new `*.mutations.mjs`**, and **do not add cases to an existing one.**
|
|
475
|
-
- **Record what a test guards as a comment directly above its assertion** (`// Guards: …`).
|
|
476
|
-
- The rule is enforced, not asked for: `scripts/mutation-batteries-frozen.mjs` (part of
|
|
477
|
-
`npm run check` and CI) fails on a battery missing from `scripts/mutation-batteries.frozen.json`,
|
|
478
|
-
on a listed battery with more or fewer cases than recorded, and on a listed file that is gone.
|
|
479
|
-
The list may only shrink — delete a battery or a case, then delete or lower its entry.
|
|
480
|
-
- The intended replacement is a real mutation-testing tool (StrykerJS) or nothing; that is
|
|
481
|
-
decided in #52, not in a pull request that happens to touch a battery.
|
|
482
|
-
|
|
483
|
-
The batteries that remain still run (`node scripts/run-mutations.mjs`) until #52 retires them.
|
|
463
|
+
(`scripts/rules-see-files.harness.mjs`), under-reporting and over-reporting each by an
|
|
464
|
+
assertion of its own.
|
|
484
465
|
|
|
485
466
|
## Cost
|
|
486
467
|
|
|
@@ -508,22 +489,26 @@ justified two hundred lines above precisely by these minutes being free.
|
|
|
508
489
|
|
|
509
490
|
```bash
|
|
510
491
|
npm test # vitest over *.test.ts, then the vigiles harnesses
|
|
492
|
+
npm run coverage # the same run under c8; fails below the thresholds in .c8rc.json
|
|
511
493
|
npx vitest run <file> # one unit test
|
|
512
494
|
npx vigiles test <file> # one harness
|
|
513
495
|
```
|
|
514
496
|
|
|
515
497
|
**Two kinds of test, told apart by what the file imports.** A `*.harness.*` file tests the agent
|
|
516
498
|
surface and imports `runHook`, `runHarnessTest` or `runEval` from vigiles; everything else is a plain
|
|
517
|
-
unit test
|
|
499
|
+
unit test run by vitest, beside the module it tests: `<module>.test.ts` for a `.ts` module, and
|
|
500
|
+
`<module>.test.mjs` for a `.mjs` one (TypeScript has no types to check a JS module against, and
|
|
501
|
+
`allowJs` was measured to break a dozen existing `@ts-expect-error` imports). Older harnesses that
|
|
518
502
|
import none of the three are frozen in `scripts/harness-api.frozen.json`, which only shrinks;
|
|
519
503
|
`scripts/harness-api.test.ts` parses every harness's imports and holds both halves (#77). vitest
|
|
520
504
|
exits 1 when no file matches, and it transpiles without type-checking, so `npm run check` runs
|
|
521
505
|
`tsc -p tsconfig.test.json` as its own gate.
|
|
522
506
|
|
|
523
|
-
**
|
|
524
|
-
the touched test files
|
|
525
|
-
|
|
526
|
-
|
|
507
|
+
**While working: the fast gates on what you touched. Before every push: `npm run check`.** While
|
|
508
|
+
editing, run vitest on the touched test files (`npx vitest run <file or folder>`), `npx tsc --noEmit`,
|
|
509
|
+
`npx eslint <touched files>` and `npm run fmt:check`. Before pushing, run the full `npm run check`
|
|
510
|
+
(about three minutes) and read its exit code: it includes the 100% coverage gate, which a
|
|
511
|
+
single-file run cannot judge. CI runs the same command plus the TeX e2e that cannot run locally.
|
|
527
512
|
|
|
528
513
|
⚠️ **Not `vigiles test .`** — the `.` is read as a FILE, the runner dies with
|
|
529
514
|
`ERR_UNSUPPORTED_DIR_IMPORT`, and it still exits 0. See the measured table below.
|
|
@@ -534,34 +519,44 @@ Skills are tested **through vigiles** — a colocated `<skill>.harness.mjs` besi
|
|
|
534
519
|
empty — issue #6.) Not through a bespoke script: a home-grown runner here once printed
|
|
535
520
|
confident, byte-identical "clean" verdicts for three different skills that had never loaded.
|
|
536
521
|
|
|
537
|
-
###
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
522
|
+
### How a test is written here
|
|
523
|
+
|
|
524
|
+
1. **Red first.** A new test is seen FAILING before the change that makes it pass — run it on
|
|
525
|
+
the unfixed code, watch it go red at its own assertion, then fix. A test that has only ever
|
|
526
|
+
been green is indistinguishable from one that cannot fail. Never add a file that patches
|
|
527
|
+
source to prove a test can fail — seeing it red once, here, is that proof.
|
|
528
|
+
2. **Assert the whole value.** Compare the entire returned value (`assert.deepEqual`,
|
|
529
|
+
`expect(x).toEqual(…)`), not a substring of it or one field. A substring assertion passes on
|
|
530
|
+
output that is wrong everywhere else — a defect found here over and over
|
|
531
|
+
(`"that is the"` matching two different messages, a bare word the usage block always prints).
|
|
532
|
+
A substring is right only when the value is prose whose wording is not the subject; then say
|
|
533
|
+
so in a comment.
|
|
534
|
+
3. **Test what the code DOES, never what its source SAYS.** A test that reads a source file (a
|
|
535
|
+
`.mjs`, a `SKILL.md`, a config) and asserts it contains some text restates the file: rewording
|
|
536
|
+
turns it red while a real regression stays green. Assert behaviour instead: a return value, the
|
|
537
|
+
output and exit code of a run, what landed on disk, what a parser reports. Text is a legitimate
|
|
538
|
+
subject only when it IS the output (a message the program prints, a file it generates).
|
|
539
|
+
4. **One assertion helper for harnesses: `lib/check.mjs`.** `const check = createChecker();` then
|
|
540
|
+
`check(label, cond, detail)`. A failure prints the label and the detail (a value is rendered
|
|
541
|
+
with `util.inspect`, a function is called only on failure), every call is counted
|
|
542
|
+
(`check.count`, for the summary line) and reported to vigiles. Do not define a local `check`.
|
|
543
|
+
5. **Coverage is a gate.** `npm run check` and CI run `npm run coverage` — `npm test` under c8,
|
|
544
|
+
`--check-coverage` against `.c8rc.json`. c8 reads `NODE_V8_COVERAGE`, so a CLI a harness
|
|
545
|
+
spawns is measured too — unless the harness hands the child a fresh `env` without it. vitest
|
|
546
|
+
runs in the `threads` pool with native `import` (`vitest.config.ts` says why): under its
|
|
547
|
+
defaults c8 saw nothing vitest ran. The run preloads `test/coverage-src.mjs`, which answers
|
|
548
|
+
every import of `dist/*.js` with `src/*.ts`: otherwise a module the tests import directly AND
|
|
549
|
+
the spawned CLI loads compiled is measured twice, and its branches and functions stay red in
|
|
550
|
+
whichever copy did not run them. `npm test` without coverage still runs `dist/`.
|
|
551
|
+
6. **No `c8 ignore`, and no guard deleted to reach 100%.** An effect a test cannot reach — a
|
|
552
|
+
race, a permission root is never denied, a broken install, a 270 MB download — is made
|
|
553
|
+
INJECTABLE (a `readdir`, an fs object, a manifest reader, a toolchain function) and the test
|
|
554
|
+
passes a fake. A precondition about an argument becomes the signature: the function takes the
|
|
555
|
+
value it needs, so a call without it cannot be written. A guard whose input can really occur
|
|
556
|
+
stays, with the test that reaches it. `scripts/coverage-config.test.ts` fails on any
|
|
557
|
+
coverage-ignore comment in a measured file (it reads comments off the parsed tree), and pins
|
|
558
|
+
`.c8rc.json`'s `exclude` list: a new exclusion fails there until it is added on purpose, with
|
|
559
|
+
its reason. Thresholds are 100 for lines, statements, functions and branches.
|
|
565
560
|
|
|
566
561
|
## `npm test` — `--min=1` stays, and here is what it is for
|
|
567
562
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -17,13 +17,20 @@ The second run is the one people skip, and it is the one that matters. A rule th
|
|
|
17
17
|
finds nothing passes the first kind of test by accident — from outside, "there is nothing wrong
|
|
18
18
|
here" and "this check never ran" look exactly the same.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
Three rules for writing the test itself:
|
|
21
|
+
|
|
22
|
+
1. **Red first.** Run a new test on the code BEFORE the fix and watch it fail at its own
|
|
23
|
+
assertion. A test that has only ever been green is indistinguishable from one that cannot fail.
|
|
24
|
+
2. **Assert the whole value** — `assert.deepEqual` / `toEqual` on the entire result, not a
|
|
25
|
+
substring or one field. A substring passes on output that is wrong everywhere else. The
|
|
26
|
+
exception is prose whose wording is not the subject; say so in a comment.
|
|
27
|
+
3. **Test what the code does, not what its source says.** A test that reads a source file and
|
|
28
|
+
asserts it contains some text restates the file: rewording turns it red, a real regression
|
|
29
|
+
stays green. Assert a return value, a run's output and exit code, or what landed on disk.
|
|
30
|
+
|
|
31
|
+
Say what an assertion guards in a comment directly above it (`// Guards: …`). A harness asserts
|
|
32
|
+
through the shared `lib/check.mjs` (`const check = createChecker()`), which prints the label and
|
|
33
|
+
the detail on failure and counts every call; a new harness does not define its own `check`.
|
|
27
34
|
|
|
28
35
|
A harness runs in this tree, against this working copy. That is the wrong shape for a defect that
|
|
29
36
|
only exists once somebody else has installed the package — a path written inside a skill, a file
|
|
@@ -31,9 +38,9 @@ that never made it into the tarball, a PDF whose content is wrong while the exit
|
|
|
31
38
|
Those are covered by the end-to-end runs, and [`docs/e2e.md`](docs/e2e.md) says which question
|
|
32
39
|
belongs to which tier, and **when a change owes a new e2e rather than a harness**.
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
An example of why asserting the value matters: `js-yaml` 5 stopped parsing an unquoted date as a
|
|
42
|
+
`Date`, every harness stayed green under both majors, and the rule's date coercion had quietly
|
|
43
|
+
become dead code — no test asserted the coerced value.
|
|
37
44
|
|
|
38
45
|
## Running the checks: one command
|
|
39
46
|
|
|
@@ -41,8 +48,8 @@ date coercion had become dead code.
|
|
|
41
48
|
npm run check
|
|
42
49
|
```
|
|
43
50
|
|
|
44
|
-
That is the whole instruction. It runs every gate in order — build, lint, every harness,
|
|
45
|
-
|
|
51
|
+
That is the whole instruction. It runs every gate in order — build, lint, every harness, the
|
|
52
|
+
install e2e under npm and pnpm, and a real `pdflatex` build — and ends
|
|
46
53
|
|
|
47
54
|
by printing **which CI jobs it does not reproduce, and why**.
|
|
48
55
|
|
|
@@ -65,8 +72,8 @@ nobody covered — verified by adding a `windows` job and watching it fail.
|
|
|
65
72
|
## Layout
|
|
66
73
|
|
|
67
74
|
```
|
|
68
|
-
eslint-rules/ the rules, each with its .harness.mjs beside it
|
|
69
|
-
lib/ shared readers — markdown, skill corpus
|
|
75
|
+
eslint-rules/ the rules, each with its .harness.mjs beside it
|
|
76
|
+
lib/ shared readers — markdown, skill corpus
|
|
70
77
|
hooks/ three hooks for vigiles — the code
|
|
71
78
|
plugin/ the Claude Code plugin: wiring for those hooks, no code, no package.json
|
|
72
79
|
skills/ 24 stage skills
|
|
@@ -102,13 +109,19 @@ The README links only what a user needs. These are for people changing the packa
|
|
|
102
109
|
|
|
103
110
|
```bash
|
|
104
111
|
npm install
|
|
105
|
-
npm test # every
|
|
106
|
-
|
|
112
|
+
npm test # every *.test.ts, then every harness (Node >= 22.18)
|
|
113
|
+
npm run coverage # the same, under c8, failing below the thresholds in .c8rc.json
|
|
107
114
|
```
|
|
108
115
|
|
|
109
116
|
None of these are needed to USE the tool — they are here because the gates are part of the
|
|
110
117
|
argument, not decoration.
|
|
111
118
|
|
|
119
|
+
Coverage is 100% for lines, statements, functions and branches, and there is no `c8 ignore`:
|
|
120
|
+
code a test cannot reach directly — a race, a permission, a broken install, a real download — takes
|
|
121
|
+
the effect as a parameter (the ports in `src/ports`, an injected `readdir` or runner), and the test
|
|
122
|
+
passes a fake. `scripts/coverage-config.test.ts` fails on a coverage-ignore comment and on any
|
|
123
|
+
change to `.c8rc.json`'s exclude list that was not made there on purpose.
|
|
124
|
+
|
|
112
125
|
## Adding a venue
|
|
113
126
|
|
|
114
127
|
A venue is a **preset**, a JSONC file in `skills/submit-paper/references/venues/`, validated by
|
|
@@ -166,8 +179,7 @@ Licensing differs too: that suite is CC BY-NC 4.0, this is MIT.
|
|
|
166
179
|
- **scorecard** — `PIPELINE-STATUS.md`, the file above. One per paper.
|
|
167
180
|
- **frozen** — a copy of the exact PDF or `.tex` that was sent, kept in `versions/`. Bytes, not a
|
|
168
181
|
commit reference: squash and `gc` destroy commit references, and did.
|
|
169
|
-
- **harness** — the test beside a rule.
|
|
170
|
-
(deprecated, being removed in #52).
|
|
182
|
+
- **harness** — the test beside a rule.
|
|
171
183
|
|
|
172
184
|
## Also in the box
|
|
173
185
|
|