paperlint 3.1.0 → 3.1.2
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 +21 -105
- package/CONTRIBUTING.md +23 -25
- package/README.md +11 -7
- 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/clock.io.d.ts +5 -0
- package/dist/adapters/references/clock.io.d.ts.map +1 -0
- package/dist/adapters/references/clock.io.js +7 -0
- package/dist/adapters/references/clock.io.js.map +1 -0
- package/dist/adapters/references/index.d.ts +24 -2
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +131 -12
- 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.d.ts +14 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +17 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +71 -55
- 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/lookup-cache.d.ts +75 -0
- package/dist/domain/lookup-cache.d.ts.map +1 -0
- package/dist/domain/lookup-cache.js +137 -0
- package/dist/domain/lookup-cache.js.map +1 -0
- 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/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 +3 -3
- 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/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +6 -4
- 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/ports/check-references.d.ts +12 -2
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/check-references.js +0 -5
- package/dist/ports/check-references.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.d.ts +11 -3
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +63 -15
- package/dist/references.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 +3 -2
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +4 -5
- package/dist/toolchain.js.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +11 -9
- package/dist/venue-rules.js.map +1 -1
- package/docs/prior-art/README.md +11 -10
- package/docs/prior-art/citation-checkers.md +174 -0
- package/docs/prior-art/test-tooling.md +7 -30
- package/docs/references.md +126 -0
- package/docs/testing.md +118 -0
- package/docs/toolchain.md +6 -4
- 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 +9 -2
- package/eslint-rules/paper-typography.test.mjs +146 -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.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/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/references/artifact-checklist.md +7 -7
- 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/checkers-requirements.txt +30 -19
- package/skills/render-paper/ensure-checkers.sh +20 -4
- 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 +30 -14
- package/skills/verify-citations/SKILL.md.spec.ts +29 -13
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +253 -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 +6 -6
- package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +360 -215
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +588 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +151 -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/clock.io.ts +8 -0
- package/src/adapters/references/index.test.ts +560 -0
- package/src/adapters/references/index.ts +203 -19
- 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.harness.mjs +5 -9
- package/src/build.test.ts +362 -0
- package/src/build.ts +23 -12
- package/src/cli.harness.mjs +10 -17
- package/src/cli.test.ts +584 -0
- package/src/cli.ts +112 -66
- package/src/doctor.harness.mjs +3 -8
- package/src/doctor.test.ts +64 -0
- package/src/doctor.ts +12 -4
- package/src/domain/lookup-cache.test.ts +178 -0
- package/src/domain/lookup-cache.ts +202 -0
- package/src/domain/result.test.ts +4 -0
- package/src/domain/result.ts +0 -5
- package/src/engine.harness.mjs +27 -7
- 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 +3 -3
- 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/new-paper.harness.mjs +3 -7
- package/src/new-paper.test.ts +31 -1
- package/src/paper-files.test.ts +12 -0
- package/src/paper-files.ts +9 -5
- package/src/paper-settings.test.ts +20 -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/ports/check-references.ts +16 -2
- package/src/presets.test.ts +2 -0
- package/src/presets.ts +7 -1
- package/src/references.test.ts +257 -10
- package/src/references.ts +93 -17
- package/src/rules-config.harness.mjs +10 -8
- package/src/structure.harness.mjs +3 -7
- package/src/tex-requirements.harness.mjs +34 -7
- package/src/tex-requirements.ts +12 -6
- package/src/toolchain.harness.mjs +3 -7
- package/src/toolchain.test.ts +280 -0
- package/src/toolchain.ts +8 -7
- package/src/venue-rules.test.ts +60 -0
- package/src/venue-rules.ts +13 -11
- 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 -65
- 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 ([`docs/testing.md`](docs/testing.md)).
|
|
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
|
|
@@ -189,6 +188,12 @@ the instruction and never the evidence — the same split as rules 9 and 10 and
|
|
|
189
188
|
🔴 The argument "we need another command for X" is, in every tool examined, an argument that
|
|
190
189
|
the CONFIG is not declaring something. Check that before adding a verb.
|
|
191
190
|
|
|
191
|
+
Rejected on 2026-09-27, so it is not proposed again: a separate `paperlint refs` command for the
|
|
192
|
+
reference check. The slowness it would have moved (#107) was a cache thrown away after every build,
|
|
193
|
+
not a missing verb. The fix keeps the check in `build` and commits what the services answered in
|
|
194
|
+
`<paper>/repro/references-cache.json` — **raw responses, never verdicts**: verdicts are derived
|
|
195
|
+
again on every build, so a checker fix reaches every paper ([`docs/references.md`](docs/references.md)).
|
|
196
|
+
|
|
192
197
|
## Distribution — one install path: npm, then `paperlint init`
|
|
193
198
|
|
|
194
199
|
`npm i -D paperlint` brings all the code — rules, skills, hooks, scripts. `paperlint init`
|
|
@@ -461,26 +466,8 @@ node scripts/rules-see-files.mjs # also part of npm run check
|
|
|
461
466
|
It is per RULE, not per glob, and that distinction is the point: a rule can be enabled in one
|
|
462
467
|
block whose glob is empty while a different block is busy, so "some glob matched something" is
|
|
463
468
|
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.
|
|
469
|
+
(`scripts/rules-see-files.harness.mjs`), under-reporting and over-reporting each by an
|
|
470
|
+
assertion of its own.
|
|
484
471
|
|
|
485
472
|
## Cost
|
|
486
473
|
|
|
@@ -506,88 +493,17 @@ justified two hundred lines above precisely by these minutes being free.
|
|
|
506
493
|
|
|
507
494
|
## Testing
|
|
508
495
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
`
|
|
520
|
-
exits 1 when no file matches, and it transpiles without type-checking, so `npm run check` runs
|
|
521
|
-
`tsc -p tsconfig.test.json` as its own gate.
|
|
522
|
-
|
|
523
|
-
**Local = the fast gates on what you touched; the full `npm run check` = CI.** Locally run vitest on
|
|
524
|
-
the touched test files, `npx tsc --noEmit`, `npx eslint <touched files>` and `npm run fmt:check`, then
|
|
525
|
-
push and read CI by job name. The repo is public, so CI is free, and it also runs the TeX e2e that
|
|
526
|
-
cannot run locally; a local full run takes 10-15 min, most of it the mutation batteries (#52).
|
|
527
|
-
|
|
528
|
-
⚠️ **Not `vigiles test .`** — the `.` is read as a FILE, the runner dies with
|
|
529
|
-
`ERR_UNSUPPORTED_DIR_IMPORT`, and it still exits 0. See the measured table below.
|
|
530
|
-
|
|
531
|
-
Skills are tested **through vigiles** — a colocated `<skill>.harness.mjs` beside the skill.
|
|
532
|
-
(This read «Skills, if and when they arrive» until 2026-09-17; there are 24 of them under
|
|
533
|
-
`skills/` carrying a `SKILL.md`, and the README's opening line claimed the repository was
|
|
534
|
-
empty — issue #6.) Not through a bespoke script: a home-grown runner here once printed
|
|
535
|
-
confident, byte-identical "clean" verdicts for three different skills that had never loaded.
|
|
536
|
-
|
|
537
|
-
### Every npm script takes an exclusive lock, and that is not ceremony
|
|
538
|
-
|
|
539
|
-
`npm run *` in this repository goes through `scripts/exclusive.mjs`, which holds
|
|
540
|
-
`.vigiles/exclusive.lock` for the duration. A second gate started while one is running does not
|
|
541
|
-
queue and does not race — it **refuses**, names the holder, and exits 3.
|
|
542
|
-
|
|
543
|
-
🔴 **The reason is that the mutation batteries edit the working tree in place.** That strategy is
|
|
544
|
-
deliberate (see `lib/mutation-driver.mjs` — copying the repo per mutation costs minutes instead
|
|
545
|
-
of seconds), and its one cost is that any parallel reader sees a source file mid-mutation. The
|
|
546
|
-
resulting failure is **false, non-deterministic, and blames the wrong file**: it reports a broken
|
|
547
|
-
assertion, not a mutation, and it reads as "the suite is flaky". That has already cost a wrong
|
|
548
|
-
conclusion here — two runs in a row produced _different_ error messages and the diagnosis "I broke
|
|
549
|
-
round-diff" was incorrect.
|
|
550
|
-
|
|
551
|
-
A prose instruction "don't run them at the same time" existed and did not work: prose does not
|
|
552
|
-
execute, so it does not apply to the person in the other terminal, the agent, or the editor with
|
|
553
|
-
tests on save. Measured live, with the batteries running:
|
|
554
|
-
|
|
555
|
-
```
|
|
556
|
-
$ npm test
|
|
557
|
-
🔴 refused: this repository is busy with a run that EDITS FILES IN PLACE.
|
|
558
|
-
held by: pid 6645, "node scripts/run-mutations.mjs", since 2026-09-17T05:22:28.757Z
|
|
559
|
-
RC=3
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
⚠️ A lock left behind by a process that no longer exists is **taken over** with a message, not
|
|
563
|
-
respected. Otherwise one interrupted run would block the repository forever, and the first cure
|
|
564
|
-
anybody reaches for would be "delete the lock by hand" — i.e. switching the mechanism off.
|
|
565
|
-
|
|
566
|
-
## `npm test` — `--min=1` stays, and here is what it is for
|
|
567
|
-
|
|
568
|
-
The script is `vigiles test --min=1`. It went green on 2026-09-11 when the first harnesses
|
|
569
|
-
landed; before that it correctly exited 1:
|
|
570
|
-
|
|
571
|
-
```
|
|
572
|
-
✗ vigiles test: --min=1 but only 0 test file(s) matched — evals never executed
|
|
573
|
-
(check the paths/globs, or that the run was reached).
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
Do **not** "fix" a future red by dropping `--min` — the flag is the only thing standing
|
|
577
|
-
between "every test passed" and "no test ran", which is rule 4 applied to the test runner
|
|
578
|
-
itself.
|
|
579
|
-
|
|
580
|
-
🔴 **Two ways this command lies if written differently, both measured 2026-09-11:**
|
|
581
|
-
|
|
582
|
-
| form | what happens | exit |
|
|
583
|
-
| ---------------------- | --------------------------------------------------------------------------- | ----- |
|
|
584
|
-
| `vigiles test .` | `.` is read as a FILE — `ERR_UNSUPPORTED_DIR_IMPORT`, uncaught, runner dies | **0** |
|
|
585
|
-
| `vigiles test` | `No **/*.harness.{mjs,cjs,js,mts,cts,ts} files found.` | **0** |
|
|
586
|
-
| `vigiles test --min=1` | names the empty match and fails | **1** |
|
|
587
|
-
|
|
588
|
-
The first row is the worse one: the runner crashed with a stack trace and still reported
|
|
589
|
-
success. `package.json` shipped `vigiles test .` from the initial scaffold until this was
|
|
590
|
-
measured — so the repo's own test command had never once executed a test, and said nothing.
|
|
496
|
+
| you are testing | kind |
|
|
497
|
+
| -------------------------------------------------------------------------- | --------------------------------------------- |
|
|
498
|
+
| a pure function | unit — vitest, `<module>.test.ts` beside it |
|
|
499
|
+
| a use case or adapter that reaches disk, a process, the network, the clock | integration — vitest, fakes through the ports |
|
|
500
|
+
| a hook's decision, a skill's contract | harness — vigiles, `<surface>.harness.mjs` |
|
|
501
|
+
| the installed package, a real TeX build | e2e — `test/e2e/*.mjs` |
|
|
502
|
+
|
|
503
|
+
Red first · assert the whole value · test what the code does, never what its source says · no test
|
|
504
|
+
touches the real network · 100% coverage, no `c8 ignore`. **Before every push: `npm run check`,
|
|
505
|
+
exit code read without a pipe.** Everything else — where files live, how fakes are injected, the
|
|
506
|
+
`--min=1` trap: [`docs/testing.md`](docs/testing.md).
|
|
591
507
|
|
|
592
508
|
## Commits
|
|
593
509
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -10,20 +10,10 @@ it is below.
|
|
|
10
10
|
|
|
11
11
|
## How this is tested
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
finds nothing passes the first kind of test by accident — from outside, "there is nothing wrong
|
|
18
|
-
here" and "this check never ran" look exactly the same.
|
|
19
|
-
|
|
20
|
-
Older rules also have a **battery** (`*.mutations.mjs`): it deletes one thing the rule depends on
|
|
21
|
-
and then demands the harness go red, at the specific assertion that thing belongs to. **These
|
|
22
|
-
hand-written batteries are deprecated and being removed (#52).** Do not write a new one and do
|
|
23
|
-
not add cases to an existing one; instead, say what an assertion guards in a comment directly
|
|
24
|
-
above it (`// Guards: …`). `npm run check` fails on a new or grown battery — the frozen list in
|
|
25
|
-
`scripts/mutation-batteries.frozen.json` may only shrink. Whether a real mutation-testing tool
|
|
26
|
-
replaces them is decided in #52.
|
|
13
|
+
Unit and integration tests run under vitest beside the module they test; harnesses run under
|
|
14
|
+
vigiles and cover what an agent sees; end-to-end runs install the package and build real PDFs.
|
|
15
|
+
Every test is seen red first, asserts the whole value, and tests what the code does rather than
|
|
16
|
+
what a file says. [`docs/testing.md`](docs/testing.md) says which kind to write and how.
|
|
27
17
|
|
|
28
18
|
A harness runs in this tree, against this working copy. That is the wrong shape for a defect that
|
|
29
19
|
only exists once somebody else has installed the package — a path written inside a skill, a file
|
|
@@ -31,9 +21,9 @@ that never made it into the tarball, a PDF whose content is wrong while the exit
|
|
|
31
21
|
Those are covered by the end-to-end runs, and [`docs/e2e.md`](docs/e2e.md) says which question
|
|
32
22
|
belongs to which tier, and **when a change owes a new e2e rather than a harness**.
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
An example of why asserting the value matters: `js-yaml` 5 stopped parsing an unquoted date as a
|
|
25
|
+
`Date`, every harness stayed green under both majors, and the rule's date coercion had quietly
|
|
26
|
+
become dead code — no test asserted the coerced value.
|
|
37
27
|
|
|
38
28
|
## Running the checks: one command
|
|
39
29
|
|
|
@@ -41,8 +31,8 @@ date coercion had become dead code.
|
|
|
41
31
|
npm run check
|
|
42
32
|
```
|
|
43
33
|
|
|
44
|
-
That is the whole instruction. It runs every gate in order — build, lint, every harness,
|
|
45
|
-
|
|
34
|
+
That is the whole instruction. It runs every gate in order — build, lint, every harness, the
|
|
35
|
+
install e2e under npm and pnpm, and a real `pdflatex` build — and ends
|
|
46
36
|
|
|
47
37
|
by printing **which CI jobs it does not reproduce, and why**.
|
|
48
38
|
|
|
@@ -65,8 +55,8 @@ nobody covered — verified by adding a `windows` job and watching it fail.
|
|
|
65
55
|
## Layout
|
|
66
56
|
|
|
67
57
|
```
|
|
68
|
-
eslint-rules/ the rules, each with its .harness.mjs beside it
|
|
69
|
-
lib/ shared readers — markdown, skill corpus
|
|
58
|
+
eslint-rules/ the rules, each with its .harness.mjs beside it
|
|
59
|
+
lib/ shared readers — markdown, skill corpus
|
|
70
60
|
hooks/ three hooks for vigiles — the code
|
|
71
61
|
plugin/ the Claude Code plugin: wiring for those hooks, no code, no package.json
|
|
72
62
|
skills/ 24 stage skills
|
|
@@ -78,6 +68,7 @@ docs/ evidence that would otherwise bloat CLAUDE.md:
|
|
|
78
68
|
incidents.md what broke, measured
|
|
79
69
|
install.md the install contract
|
|
80
70
|
e2e.md the end-to-end runs, and when a change owes one
|
|
71
|
+
testing.md which kind of test to write, and the rules for all of them
|
|
81
72
|
```
|
|
82
73
|
|
|
83
74
|
Most of the package's rules run on users' papers and are described for users in
|
|
@@ -94,6 +85,8 @@ The README links only what a user needs. These are for people changing the packa
|
|
|
94
85
|
`init` writes, supported package managers, troubleshooting, and why it is shaped this way
|
|
95
86
|
- [`docs/e2e.md`](docs/e2e.md) — the end-to-end runs: what each proves, what they do not cover,
|
|
96
87
|
and when a change owes one
|
|
88
|
+
- [`docs/testing.md`](docs/testing.md) — the kinds of test, which one a change needs, and the rules
|
|
89
|
+
for every one
|
|
97
90
|
- [`docs/incidents.md`](docs/incidents.md) — what broke, measured
|
|
98
91
|
- [`docs/package-shape-options.md`](docs/package-shape-options.md) — the options for the
|
|
99
92
|
package's shape, and the ranking
|
|
@@ -102,13 +95,19 @@ The README links only what a user needs. These are for people changing the packa
|
|
|
102
95
|
|
|
103
96
|
```bash
|
|
104
97
|
npm install
|
|
105
|
-
npm test # every
|
|
106
|
-
|
|
98
|
+
npm test # every *.test.ts, then every harness (Node >= 22.18)
|
|
99
|
+
npm run coverage # the same, under c8, failing below the thresholds in .c8rc.json
|
|
107
100
|
```
|
|
108
101
|
|
|
109
102
|
None of these are needed to USE the tool — they are here because the gates are part of the
|
|
110
103
|
argument, not decoration.
|
|
111
104
|
|
|
105
|
+
Coverage is 100% for lines, statements, functions and branches, and there is no `c8 ignore`:
|
|
106
|
+
code a test cannot reach directly — a race, a permission, a broken install, a real download — takes
|
|
107
|
+
the effect as a parameter (the ports in `src/ports`, an injected `readdir` or runner), and the test
|
|
108
|
+
passes a fake. `scripts/coverage-config.test.ts` fails on a coverage-ignore comment and on any
|
|
109
|
+
change to `.c8rc.json`'s exclude list that was not made there on purpose.
|
|
110
|
+
|
|
112
111
|
## Adding a venue
|
|
113
112
|
|
|
114
113
|
A venue is a **preset**, a JSONC file in `skills/submit-paper/references/venues/`, validated by
|
|
@@ -166,8 +165,7 @@ Licensing differs too: that suite is CC BY-NC 4.0, this is MIT.
|
|
|
166
165
|
- **scorecard** — `PIPELINE-STATUS.md`, the file above. One per paper.
|
|
167
166
|
- **frozen** — a copy of the exact PDF or `.tex` that was sent, kept in `versions/`. Bytes, not a
|
|
168
167
|
commit reference: squash and `gc` destroy commit references, and did.
|
|
169
|
-
- **harness** — the test beside a rule.
|
|
170
|
-
(deprecated, being removed in #52).
|
|
168
|
+
- **harness** — the test beside a rule.
|
|
171
169
|
|
|
172
170
|
## Also in the box
|
|
173
171
|
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ before you submit.** It comes in two parts:
|
|
|
27
27
|
|
|
28
28
|
Smaller slips too: `§` for "Section", `.05` for `0.05` — fixed for you.
|
|
29
29
|
|
|
30
|
-
- 🧠 **Skills for [Claude Code](https://claude.com/claude-code)** — optional,
|
|
30
|
+
- 🧠 **Skills for [Claude Code](https://claude.com/claude-code)** — optional, covering the
|
|
31
31
|
whole pipeline: the idea, the venue, the study, the draft, the reviews, submission.
|
|
32
32
|
|
|
33
33
|
## Contents
|
|
@@ -173,17 +173,20 @@ what it does — "is this idea worth a paper?", "find me a venue for this". `ini
|
|
|
173
173
|
- **A ready / not ready verdict before you submit**, worst problem first. `harden-paper`
|
|
174
174
|
- **Where your paper stands**, measured from the real build. `paper-status`
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
Every skill, by stage: [`docs/skills.md`](docs/skills.md).
|
|
177
177
|
|
|
178
178
|
## 🔍 Lint and build
|
|
179
179
|
|
|
180
|
-
| | `paperlint build`
|
|
181
|
-
| ------ |
|
|
182
|
-
| needs | Node, TeX Live, the network
|
|
183
|
-
| does | compiles `paper.tex`, measures the PDF, checks each reference online (exists, right authors)
|
|
184
|
-
| writes | `paper.pdf`,
|
|
180
|
+
| | `paperlint build` | `paperlint lint` |
|
|
181
|
+
| ------ | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
182
|
+
| needs | Node, TeX Live, the network | Node |
|
|
183
|
+
| does | compiles `paper.tex`, measures the PDF, checks each reference online (exists, right authors) | checks the source, and judges what build recorded: page limit, fonts, the reference results — offline |
|
|
184
|
+
| writes | `paper.pdf`, in `_build/` the measurements and the reference results, and `repro/references-cache.json` | nothing (`--fix`: the three fixable slips) |
|
|
185
185
|
|
|
186
186
|
- If `paper.pdf` or the bibliography changed since the last build, lint fails and tells you to rebuild.
|
|
187
|
+
- Commit `repro/references-cache.json`: it keeps what the citation services answered, so a build
|
|
188
|
+
asks only about new or edited entries (and answers older than 30 days), so an unchanged
|
|
189
|
+
bibliography usually builds without the network ([`docs/references.md`](docs/references.md)).
|
|
187
190
|
- Lint also checks the pipeline's own records: `PIPELINE-STATUS.md`, reviews, notes on related
|
|
188
191
|
papers ([`docs/rules.md`](docs/rules.md)).
|
|
189
192
|
- The rules run on ESLint, so a deliberate exception is a comment on the line above:
|
|
@@ -295,6 +298,7 @@ Only `paperlint lint --fix`, and only three rules: `paper/section-word` (`§`
|
|
|
295
298
|
- [`docs/install.md`](docs/install.md) — what `init` does, the hooks, package managers, troubleshooting
|
|
296
299
|
- [`docs/configuration.md`](docs/configuration.md) — every setting, how `build` compiles, using your own ESLint
|
|
297
300
|
- [`docs/rules.md`](docs/rules.md) — every check, venue presets, recording a submitted PDF
|
|
301
|
+
- [`docs/references.md`](docs/references.md) — the reference check, and the cache file to commit
|
|
298
302
|
- [`docs/optional-rules.md`](docs/optional-rules.md) — checks only some venues need
|
|
299
303
|
- [`docs/toolchain.md`](docs/toolchain.md) — TeX Live, and Banal (HotCRP's page-geometry checker, GPL)
|
|
300
304
|
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — how the package is tested and released, and adding a venue to it
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bin/paperlint.mjs` — the shim's one decision of its own: with no `dist/` beside it (a clone
|
|
3
|
+
* before `npm run build`, a git dependency installed with `--ignore-scripts`) it refuses loudly,
|
|
4
|
+
* naming the missing file and both remedies, instead of an ERR_MODULE_NOT_FOUND from the loader.
|
|
5
|
+
*
|
|
6
|
+
* The missing build is staged with a preload that makes `existsSync` answer "no" for exactly
|
|
7
|
+
* `dist/cli.js` — the shim reads the real file, so the real shim is what is measured.
|
|
8
|
+
*/
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { test } from "vitest";
|
|
13
|
+
|
|
14
|
+
const BIN = fileURLToPath(new URL("./paperlint.mjs", import.meta.url));
|
|
15
|
+
const DIST = fileURLToPath(new URL("../dist/cli.js", import.meta.url));
|
|
16
|
+
|
|
17
|
+
const HIDE_DIST = [
|
|
18
|
+
'import fs from "node:fs";',
|
|
19
|
+
'import { syncBuiltinESMExports } from "node:module";',
|
|
20
|
+
"const real = fs.existsSync;",
|
|
21
|
+
'fs.existsSync = (p) => (String(p).endsWith("/dist/cli.js") ? false : real(p));',
|
|
22
|
+
"syncBuiltinESMExports();",
|
|
23
|
+
].join("\n");
|
|
24
|
+
|
|
25
|
+
test("no dist/: exit 2, the missing path, and both remedies — nothing from the loader", () => {
|
|
26
|
+
const r = spawnSync(
|
|
27
|
+
process.execPath,
|
|
28
|
+
[
|
|
29
|
+
"--import",
|
|
30
|
+
`data:text/javascript,${encodeURIComponent(HIDE_DIST)}`,
|
|
31
|
+
BIN,
|
|
32
|
+
"--version",
|
|
33
|
+
],
|
|
34
|
+
{ encoding: "utf8" },
|
|
35
|
+
);
|
|
36
|
+
assert.equal(r.status, 2);
|
|
37
|
+
assert.equal(r.stdout, "");
|
|
38
|
+
const [first, ...rest] = r.stderr.split("\n");
|
|
39
|
+
assert.equal(first, `paperlint: not built — ${DIST} missing.`);
|
|
40
|
+
assert.match(
|
|
41
|
+
rest.join("\n"),
|
|
42
|
+
/In a clone: `npm run build`\. For a git dependency: reinstall without `--ignore-scripts`\./,
|
|
43
|
+
);
|
|
44
|
+
assert.doesNotMatch(r.stderr, /ERR_MODULE_NOT_FOUND/);
|
|
45
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.io.d.ts","sourceRoot":"","sources":["../../../src/adapters/node/files.io.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"files.io.d.ts","sourceRoot":"","sources":["../../../src/adapters/node/files.io.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAElD,4EAA4E;AAC5E,eAAO,MAAM,SAAS,GAAI,GAAG,OAAO,KAAG,OAC6B,CAAC;AAErE,eAAO,MAAM,SAAS,EAAE,KAsBvB,CAAC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** `Files` over `node:fs`. */
|
|
2
2
|
import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
|
-
|
|
4
|
+
/** An error's errno code; anything thrown that is not an Error has none. */
|
|
5
|
+
export const errorCode = (e) => e instanceof Error ? e.code : undefined;
|
|
5
6
|
export const nodeFiles = {
|
|
6
7
|
isFile(p) {
|
|
7
8
|
return statSync(p, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
@@ -11,7 +12,7 @@ export const nodeFiles = {
|
|
|
11
12
|
return readFileSync(p);
|
|
12
13
|
}
|
|
13
14
|
catch (e) {
|
|
14
|
-
if (
|
|
15
|
+
if (errorCode(e) === "ENOENT")
|
|
15
16
|
return null;
|
|
16
17
|
throw e;
|
|
17
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.io.js","sourceRoot":"","sources":["../../../src/adapters/node/files.io.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,
|
|
1
|
+
{"version":3,"file":"files.io.js","sourceRoot":"","sources":["../../../src/adapters/node/files.io.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAU,EAAW,EAAE,CAC/C,CAAC,YAAY,KAAK,CAAC,CAAC,CAAE,CAA2B,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAErE,MAAM,CAAC,MAAM,SAAS,GAAU;IAC9B,MAAM,CAAC,CAAe;QACpB,OAAO,QAAQ,CAAC,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,KAAK,CAAC;IACnE,CAAC;IACD,SAAS,CAAC,CAAe;QACvB,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC3C,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IACD,WAAW,CAAC,CAAe,EAAE,KAAiB;QAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC;QACzB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3B,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;CACF,CAAC"}
|
|
@@ -25,11 +25,13 @@ export interface OperatorList {
|
|
|
25
25
|
}
|
|
26
26
|
/** `setFillRGBColor`'s argument: a `#rrggbb` string in current pdf.js, three 0-255 numbers in older ones. */
|
|
27
27
|
export declare function hexOf(args: unknown): string;
|
|
28
|
+
/** One drawn character and the fill it was drawn in. */
|
|
29
|
+
interface Drawn {
|
|
30
|
+
readonly ch: string;
|
|
31
|
+
readonly fill: Fill;
|
|
32
|
+
}
|
|
28
33
|
/** Every drawn character, in content order, with the fill it was drawn in. */
|
|
29
|
-
export declare function drawnCharacters(ops: ColourOps, list: OperatorList):
|
|
30
|
-
chars: string[];
|
|
31
|
-
fills: Fill[];
|
|
32
|
-
};
|
|
34
|
+
export declare function drawnCharacters(ops: ColourOps, list: OperatorList): Drawn[];
|
|
33
35
|
/** How far ahead the walk looks for an item it cannot find where it expects it. */
|
|
34
36
|
export declare const LOOKAHEAD = 4000;
|
|
35
37
|
/**
|
|
@@ -39,4 +41,13 @@ export declare const LOOKAHEAD = 4000;
|
|
|
39
41
|
* a missed match can keep light text, never drop dark text.
|
|
40
42
|
*/
|
|
41
43
|
export declare function fillsOf(ops: ColourOps, list: OperatorList, items: readonly string[]): Fill[];
|
|
44
|
+
/**
|
|
45
|
+
* Each item paired with its fill — `fillsOf` for items that are not bare strings, so a caller never
|
|
46
|
+
* matches the two lists up by index.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fillsFor<T>(ops: ColourOps, list: OperatorList, items: readonly T[], textOf: (item: T) => string): {
|
|
49
|
+
readonly item: T;
|
|
50
|
+
readonly fill: Fill;
|
|
51
|
+
}[];
|
|
52
|
+
export {};
|
|
42
53
|
//# sourceMappingURL=fill.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fill.d.ts","sourceRoot":"","sources":["../../../src/adapters/pdfjs/fill.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAExD,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGvD;AAED,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,CAAC;CACxC;AAKD,6GAA6G;AAC7G,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAQ3C;AAuCD,
|
|
1
|
+
{"version":3,"file":"fill.d.ts","sourceRoot":"","sources":["../../../src/adapters/pdfjs/fill.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAExD,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGvD;AAED,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,CAAC;CACxC;AAKD,6GAA6G;AAC7G,wBAAgB,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAQ3C;AAuCD,wDAAwD;AACxD,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,KAAK,EAAE,CAW3E;AAED,mFAAmF;AACnF,eAAO,MAAM,SAAS,OAAO,CAAC;AAoB9B;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,IAAI,EAAE,CAER;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;CAAE,EAAE,CAY7C"}
|