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
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* skill-contract.mutations.mjs — the non-vacuity proof for the colocated skill harnesses.
|
|
3
|
-
*
|
|
4
|
-
* Run: `node skills/skill-contract.mutations.mjs` (~1 min; it rewrites each SKILL.md and restores
|
|
5
|
-
* it, so it is not part of the plain test run).
|
|
6
|
-
*
|
|
7
|
-
* ── WHY IT EXISTS, AND WHY IT ARRIVED WITH THE SKILLS ───────────────────────
|
|
8
|
-
* `scripts/run-mutations.mjs` refuses a repository where some `*.harness.mjs` is named by no
|
|
9
|
-
* battery, and the refusal is the whole argument of this package: silence is the success state of
|
|
10
|
-
* every check here, so «it passed» and «it cannot fail» are byte-identical from outside. The ten
|
|
11
|
-
* skill harnesses arrived on 2026-09-12 and were orphans the moment they landed — the guard said
|
|
12
|
-
* so on the first run, which is exactly what a guard is for.
|
|
13
|
-
*
|
|
14
|
-
* ── HOW THE SKILLS ARE REACHED FROM INSIDE THIS PACKAGE ─────────────────────
|
|
15
|
-
* 🔴 `checkSkill()` looks at `<consumerRoot>/.claude/skills/<name>/SKILL.md` — the fixed Claude
|
|
16
|
-
* Code layout — not at `skills/<name>` where this repository keeps them. So this repository is its
|
|
17
|
-
* OWN FIRST CONSUMER: `.claude/skills/<name>` is a symlink to `../../skills/<name>`, and
|
|
18
|
-
* `.claude/skills/paper-pipeline/scripts` is a symlink to the real scripts directory, so the path
|
|
19
|
-
* every SKILL.md's prose names (`node .claude/skills/paper-pipeline/scripts/announce.mjs …`)
|
|
20
|
-
* resolves here too. Without those symlinks `skill-corpus.mjs` throws while LOADING, and a harness
|
|
21
|
-
* whose module fails to load is reported by `vigiles test` as SKIPPED with exit code 0 — a green
|
|
22
|
-
* run that tested nothing. That is not a hypothetical: it is what the first attempt printed.
|
|
23
|
-
*
|
|
24
|
-
* Because the symlinks point back at `skills/`, an edit to `skills/<n>/SKILL.md` IS the edit the
|
|
25
|
-
* harness sees. The driver therefore writes the real file and restores it, verifying both.
|
|
26
|
-
*
|
|
27
|
-
* ── THE DEFECT EACH CASE PLANTS, AND WHY THAT ONE ───────────────────────────
|
|
28
|
-
* Nine of the ten are `checkSkill()` one-liners, so they all fail the same way and one defect
|
|
29
|
-
* shape covers them: make the skill announce under a SIBLING'S name. It is the defect the
|
|
30
|
-
* assertion exists for — these record blocks are copy-paste templates differing in one token,
|
|
31
|
-
* `announce.mjs` validates nothing, and the wrong gate then reads FRESH in `status.mjs` while the
|
|
32
|
-
* right one reads NEVER-RUN. Each case names a DIFFERENT sibling so that no two cases could pass
|
|
33
|
-
* on one shared accident.
|
|
34
|
-
*
|
|
35
|
-
* `osf-artifact-upload` is not a `checkSkill()` skill (it carries no `announce.mjs <self>` line —
|
|
36
|
-
* it is a utility `submit-paper` composes with, not a stage), so its case plants the defect ITS
|
|
37
|
-
* harness is about: a `curl` that loses `-g`, which makes a bracketed OSF parameter glob and the
|
|
38
|
-
* request fail silently.
|
|
39
|
-
*/
|
|
40
|
-
import { readdirSync } from "node:fs";
|
|
41
|
-
import { dirname, join } from "node:path";
|
|
42
|
-
import { fileURLToPath } from "node:url";
|
|
43
|
-
import { runMutations } from "../lib/mutation-driver.mjs";
|
|
44
|
-
import { consumerRoot } from "./paper-pipeline/scripts/consumer.mjs";
|
|
45
|
-
|
|
46
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
47
|
-
const md = (s) => join(HERE, s, "SKILL.md");
|
|
48
|
-
const harness = (s) => join(HERE, s, `${s}.harness.mjs`);
|
|
49
|
-
|
|
50
|
-
/** [skill, the sibling it is made to announce under] — every sibling distinct. */
|
|
51
|
-
const ANNOUNCERS = [
|
|
52
|
-
["analyze-sibling-paper", "map-prior-work"],
|
|
53
|
-
["argument-arc", "tighten-paper"],
|
|
54
|
-
["cold-read-diff", "argument-arc"],
|
|
55
|
-
["draft-paper", "build-benchmark"],
|
|
56
|
-
["map-prior-work", "find-venue"],
|
|
57
|
-
["paper-adversarial-review", "pc-panel-review"],
|
|
58
|
-
["pc-panel-review", "harden-paper"],
|
|
59
|
-
["sweep-design-space", "research-ideate"],
|
|
60
|
-
["tighten-paper", "grade-paper-writing"],
|
|
61
|
-
// ── wave ② (2026-09-12). Same defect shape: all twelve are `checkSkill()` stage skills
|
|
62
|
-
// carrying exactly one `announce.mjs <self>` line, verified unambiguous before these were
|
|
63
|
-
// written. Siblings stay pairwise distinct across the WHOLE list, so no two cases can pass
|
|
64
|
-
// on one shared accident.
|
|
65
|
-
["build-benchmark", "draft-paper"],
|
|
66
|
-
["camera-ready", "extend-paper"],
|
|
67
|
-
["harden-paper", "paper-adversarial-review"],
|
|
68
|
-
["paper-status", "render-paper"],
|
|
69
|
-
["extend-paper", "camera-ready"],
|
|
70
|
-
["study-accepted-papers", "analyze-sibling-paper"],
|
|
71
|
-
["verify-citations", "cold-read-diff"],
|
|
72
|
-
["render-paper", "paper-status"],
|
|
73
|
-
["find-venue", "plan-paper-timeline"],
|
|
74
|
-
["research-ideate", "sweep-design-space"],
|
|
75
|
-
["plan-paper-timeline", "study-accepted-papers"],
|
|
76
|
-
["grade-paper-writing", "verify-citations"],
|
|
77
|
-
// ── wave ③ (2026-09-12). `submit-paper` is a `checkSkill()` stage skill with exactly one
|
|
78
|
-
// `announce.mjs submit-paper` line (verified unambiguous before this was written). Its sibling
|
|
79
|
-
// is the conductor itself — the only name left that keeps the whole list pairwise distinct, and
|
|
80
|
-
// a fitting one: recording a submission under the orchestrator's gate is precisely the
|
|
81
|
-
// copy-paste slip that reads FRESH on the wrong row.
|
|
82
|
-
["submit-paper", "paper-pipeline"],
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
// 🔴 "Every sibling is distinct" is an ASSERTION, not a wish stated in a comment. The list is
|
|
86
|
-
// written by hand, and exactly this kind of list has already rotted silently before. If two cases
|
|
87
|
-
// mutate into the same name, they can pass on one shared accident, and the battery stops being
|
|
88
|
-
// able to tell their failures apart.
|
|
89
|
-
{
|
|
90
|
-
const seen = new Set();
|
|
91
|
-
for (const [skill, sibling] of ANNOUNCERS) {
|
|
92
|
-
if (skill === sibling)
|
|
93
|
-
throw new Error(
|
|
94
|
-
`${skill}: the sibling must be DIFFERENT from the skill itself`,
|
|
95
|
-
);
|
|
96
|
-
if (seen.has(sibling))
|
|
97
|
-
throw new Error(
|
|
98
|
-
`sibling "${sibling}" is named twice — the cases stopped being independent. ` +
|
|
99
|
-
`Each case needs its own, otherwise two of them can pass on one shared accident.`,
|
|
100
|
-
);
|
|
101
|
-
seen.add(sibling);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const CASES = ANNOUNCERS.map(([skill, sibling]) => ({
|
|
106
|
-
name: `${skill}: announces as ${sibling}`,
|
|
107
|
-
disables:
|
|
108
|
-
"the identity check — a run recorded under a sibling's gate, which then reads FRESH",
|
|
109
|
-
edits: [[md(skill), `announce.mjs ${skill} `, `announce.mjs ${sibling} `]],
|
|
110
|
-
harness: harness(skill),
|
|
111
|
-
expect: "its announce command files under",
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
CASES.push({
|
|
115
|
-
name: "osf-artifact-upload: one curl loses -g",
|
|
116
|
-
disables:
|
|
117
|
-
"the globbing rule this skill states in its own §gotchas, three paragraphs above the command",
|
|
118
|
-
// 🔴 ANCHORED ON THE ONE CALL THAT IS UNIQUE, not on the shared prefix. `curl -g -s --retry 3`
|
|
119
|
-
// appears three times, and the driver refuses an ambiguous target rather than guessing — which
|
|
120
|
-
// is how the first version of this case reported AMBIGUOUS instead of quietly mutating one of
|
|
121
|
-
// three and calling it a kill.
|
|
122
|
-
edits: [
|
|
123
|
-
[
|
|
124
|
-
md("osf-artifact-upload"),
|
|
125
|
-
'curl -g -s --retry 3 --cacert $CA -H "$AUTH" https://api.osf.io/v2/users/me/',
|
|
126
|
-
'curl -s --retry 3 --cacert $CA -H "$AUTH" https://api.osf.io/v2/users/me/',
|
|
127
|
-
],
|
|
128
|
-
],
|
|
129
|
-
harness: harness("osf-artifact-upload"),
|
|
130
|
-
expect: 'command(s) omit "-g"',
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// ── wave ③: the conductor. NOT a `checkSkill()` skill — its harness (`paper-pipeline.harness.mjs`)
|
|
134
|
-
// checks ROUTING INTEGRITY, so the defect has to be a routing one, the same way
|
|
135
|
-
// `osf-artifact-upload` above gets the defect ITS harness is about.
|
|
136
|
-
//
|
|
137
|
-
// 🔴 THE FORWARD DIRECTION IS THE ONE A MUTATION CAN REACH. Backward ("a wired stage the map never
|
|
138
|
-
// names") cannot be planted by editing the map alone — deleting a route makes the skill un-named,
|
|
139
|
-
// which is exactly what the MISSING allowance list tolerates for three skills, so the kill would
|
|
140
|
-
// depend on which name was picked. Forward is unconditional: a bolded backticked token in a routing
|
|
141
|
-
// table that resolves to no skill directory is a route to nowhere, and a run following the map
|
|
142
|
-
// stalls there. `**\`camera-ready\`**` occurs exactly once, so the edit cannot be ambiguous.
|
|
143
|
-
CASES.push({
|
|
144
|
-
name: "paper-pipeline: the map routes to a skill that does not exist",
|
|
145
|
-
disables:
|
|
146
|
-
"forward routing integrity — the conductor pointing at a name with no SKILL.md behind it",
|
|
147
|
-
edits: [
|
|
148
|
-
[md("paper-pipeline"), "**`camera-ready`**", "**`camera-ready-v2`**"],
|
|
149
|
-
],
|
|
150
|
-
harness: harness("paper-pipeline"),
|
|
151
|
-
expect: "no .claude/skills/camera-ready-v2/SKILL.md exists",
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// 🔴 A FLOOR, NOT A COMMENT. The list above is written by hand, and a hand-written list of paths
|
|
155
|
-
// is the thing this package replaced in `package.json` on 2026-09-11 because it had already
|
|
156
|
-
// rotted SILENTLY. A skill added to `skills/` with a colocated harness and no case here would be
|
|
157
|
-
// caught by `run-mutations.mjs` — but only on the run AFTER someone added it, and only if they
|
|
158
|
-
// ran it. This asserts the pairing from the other side, at import time.
|
|
159
|
-
// 🔴 THE `paper-pipeline` EXCLUSION IS GONE (wave ③, 2026-09-12). It was written when that
|
|
160
|
-
// directory held only `scripts/` — no SKILL.md, no colocated harness, nothing for a case to kill.
|
|
161
|
-
// Both arrived with wave ③, so the exclusion would now hide the orchestrator from the very floor
|
|
162
|
-
// that exists to notice an uncovered harness: `e.name !== "paper-pipeline"` and «it has no harness»
|
|
163
|
-
// were the same statement for one day and are opposite statements now.
|
|
164
|
-
const withHarness = readdirSync(HERE, { withFileTypes: true })
|
|
165
|
-
.filter((e) => e.isDirectory())
|
|
166
|
-
.map((e) => e.name)
|
|
167
|
-
.filter((n) => readdirSync(join(HERE, n)).includes(`${n}.harness.mjs`));
|
|
168
|
-
const covered = new Set(CASES.map((c) => c.harness));
|
|
169
|
-
const missing = withHarness.filter((n) => !covered.has(harness(n)));
|
|
170
|
-
if (missing.length)
|
|
171
|
-
throw new Error(
|
|
172
|
-
`skill-contract.mutations.mjs covers no case for: ${missing.join(", ")}.\n` +
|
|
173
|
-
`Each skill directory carrying <name>.harness.mjs needs a case here, or that harness is a ` +
|
|
174
|
-
`test nothing can kill — green and silent whether or not it still checks anything.`,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
process.exit(
|
|
178
|
-
runMutations({ root: consumerRoot(), runner: "vigiles", cases: CASES }),
|
|
179
|
-
);
|
package/src/build.mutations.mjs
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Battery for `build.ts` — the shell that compiles a paper.
|
|
3
|
-
*
|
|
4
|
-
* Every case reintroduces a defect this command either really had or exists to prevent, and names
|
|
5
|
-
* the assertion that must kill it. Two of them are checked on DISK rather than on the returned
|
|
6
|
-
* object, because that is where the damage lands: a paper script that runs anyway, and a stale
|
|
7
|
-
* PDF left beside a red build.
|
|
8
|
-
*/
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { dirname, join, resolve } from "node:path";
|
|
11
|
-
import { runMutations } from "../lib/mutation-driver.mjs";
|
|
12
|
-
|
|
13
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
const ROOT = resolve(HERE, "..");
|
|
15
|
-
const SRC = join(HERE, "build.ts");
|
|
16
|
-
const PORT = join(ROOT, "skills", "paper-pipeline", "scripts", "consumer.mjs");
|
|
17
|
-
const HARNESS = join(HERE, "build.harness.mjs");
|
|
18
|
-
|
|
19
|
-
process.exit(
|
|
20
|
-
runMutations({
|
|
21
|
-
root: ROOT,
|
|
22
|
-
runner: "node",
|
|
23
|
-
cases: [
|
|
24
|
-
{
|
|
25
|
-
name: "a leftover build.sh is executed again",
|
|
26
|
-
harness: HARNESS,
|
|
27
|
-
expect: "the paper's build scripts were NOT executed",
|
|
28
|
-
disables:
|
|
29
|
-
"the decision this command is built on: paperlint compiles the paper itself and never runs a " +
|
|
30
|
-
"file from the paper directory. The mutation keeps the 'is ignored' note and runs the " +
|
|
31
|
-
"script anyway — the worst form, because the output still claims it was ignored",
|
|
32
|
-
edits: [
|
|
33
|
-
[
|
|
34
|
-
SRC,
|
|
35
|
-
" for (const s of facts.ignoredScripts)\n log(` note: ${s} is ignored — paperlint builds the paper itself`);\n",
|
|
36
|
-
' for (const s of facts.ignoredScripts) {\n log(` note: ${s} is ignored — paperlint builds the paper itself`);\n spawnSync("bash", [join(paperDir, s)], { stdio: "ignore" });\n }\n',
|
|
37
|
-
],
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: "a failed build keeps the stale PDF",
|
|
42
|
-
harness: HARNESS,
|
|
43
|
-
expect: "the stale paper.pdf is DELETED",
|
|
44
|
-
disables:
|
|
45
|
-
"removing paper.pdf on failure. An old PDF beside a red build looks current, and 'the " +
|
|
46
|
-
"PDF is there' is exactly what a human checks first",
|
|
47
|
-
edits: [
|
|
48
|
-
[
|
|
49
|
-
SRC,
|
|
50
|
-
' removePdf(paperDir);\n return {\n dir,\n status: "failed",',
|
|
51
|
-
' return {\n dir,\n status: "failed",',
|
|
52
|
-
],
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: "the stale PDF is no longer removed before the engine is resolved",
|
|
57
|
-
harness: HARNESS,
|
|
58
|
-
expect:
|
|
59
|
-
"no engine: the stale paper.pdf is removed BEFORE the engine is resolved",
|
|
60
|
-
disables:
|
|
61
|
-
"the one up-front removal. Without it every path that ends before a step — no " +
|
|
62
|
-
"qualifying TeX Live, no paper.tex — leaves yesterday's PDF looking like today's",
|
|
63
|
-
edits: [
|
|
64
|
-
[
|
|
65
|
-
SRC,
|
|
66
|
-
" const stale = new Set(dryRun ? [] : targets.filter(removePdf));",
|
|
67
|
-
" const stale = new Set<string>();",
|
|
68
|
-
],
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: "`--dry-run` removes the stale PDF too",
|
|
73
|
-
harness: HARNESS,
|
|
74
|
-
expect: "buildPapers --dry-run: the PDF on disk is untouched",
|
|
75
|
-
disables:
|
|
76
|
-
"the dry-run guard on the up-front removal. A plan that deletes the author's PDF is " +
|
|
77
|
-
"not a plan",
|
|
78
|
-
edits: [
|
|
79
|
-
[
|
|
80
|
-
SRC,
|
|
81
|
-
" const stale = new Set(dryRun ? [] : targets.filter(removePdf));",
|
|
82
|
-
" const stale = new Set(targets.filter(removePdf));",
|
|
83
|
-
],
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
name: "pdflatex exit 0 with no PDF counts as built",
|
|
88
|
-
harness: HARNESS,
|
|
89
|
-
expect: "empty document: pdflatex exit 0 with no PDF FAILS the build",
|
|
90
|
-
disables:
|
|
91
|
-
"the existence check. An empty document compiles with exit 0 and no output, and the " +
|
|
92
|
-
"result line would print a green paper.pdf that does not exist",
|
|
93
|
-
edits: [
|
|
94
|
-
[
|
|
95
|
-
SRC,
|
|
96
|
-
" if (!existsSync(join(ctx.paperDir, `${JOB}.pdf`)))",
|
|
97
|
-
" if (false)",
|
|
98
|
-
],
|
|
99
|
-
],
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: "`--dry-run` runs the build anyway",
|
|
103
|
-
harness: HARNESS,
|
|
104
|
-
expect: "--dry-run: nothing was started",
|
|
105
|
-
disables:
|
|
106
|
-
"the one flag whose whole meaning is 'do not touch anything'. Its first version was " +
|
|
107
|
-
"swallowed by options destructuring and rewrote paper.pdf while looking like a verbose " +
|
|
108
|
-
"dry run",
|
|
109
|
-
edits: [
|
|
110
|
-
[
|
|
111
|
-
SRC,
|
|
112
|
-
' if (dryRun) return { dir, status: "built", plan, dry: true };\n',
|
|
113
|
-
"",
|
|
114
|
-
],
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "TEXINPUTS loses its trailing separator",
|
|
119
|
-
harness: HARNESS,
|
|
120
|
-
expect: "with no TEXINPUTS set, the value ENDS in the separator",
|
|
121
|
-
disables:
|
|
122
|
-
"the empty element that tells kpathsea 'and then the system tree'. Without it " +
|
|
123
|
-
"article.cls stops resolving, and every paper fails for a reason unrelated to it",
|
|
124
|
-
edits: [
|
|
125
|
-
[
|
|
126
|
-
SRC,
|
|
127
|
-
' TEXINPUTS: [...dirs, env.TEXINPUTS ?? ""].join(delimiter),',
|
|
128
|
-
" TEXINPUTS: [...dirs, ...(env.TEXINPUTS ? [env.TEXINPUTS] : [])].join(delimiter),",
|
|
129
|
-
],
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: "the port resolves the venues directory one level off",
|
|
134
|
-
harness: HARNESS,
|
|
135
|
-
expect:
|
|
136
|
-
"the venues directory is paperlint's own and holds paper-guards.tex",
|
|
137
|
-
disables:
|
|
138
|
-
"the one answer to 'where are paperlint's venue files'. A wrong directory does not fail " +
|
|
139
|
-
"pdflatex by itself — \\input{paper-guards} would fail later, in a paper, far from here",
|
|
140
|
-
edits: [
|
|
141
|
-
[
|
|
142
|
-
PORT,
|
|
143
|
-
'"../../submit-paper/references/venues"',
|
|
144
|
-
'"../../submit-paper/references"',
|
|
145
|
-
],
|
|
146
|
-
],
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: "a paper with no paper.tex stops being a refusal",
|
|
150
|
-
harness: HARNESS,
|
|
151
|
-
expect: "no paper.tex — status no-source",
|
|
152
|
-
disables:
|
|
153
|
-
"'nothing to compile' as a failure. Reading it as 'built' is the green run over an " +
|
|
154
|
-
"unbuilt paper this command was written against",
|
|
155
|
-
edits: [
|
|
156
|
-
[
|
|
157
|
-
SRC,
|
|
158
|
-
" if (plan.some((p) => !p.applies && p.required))",
|
|
159
|
-
" if (plan.some((p) => !p.applies && p.required && false))",
|
|
160
|
-
],
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "the final pass stops defining \\finalpass",
|
|
165
|
-
harness: HARNESS,
|
|
166
|
-
expect: "the FINAL pass defines \\finalpass",
|
|
167
|
-
disables:
|
|
168
|
-
"the switch that arms paper-guards.tex. Without it an undefined \\ref or \\cite builds " +
|
|
169
|
-
"green and ships as '??' in the PDF",
|
|
170
|
-
edits: [
|
|
171
|
-
[
|
|
172
|
-
SRC,
|
|
173
|
-
" `\\\\def\\\\finalpass{}\\\\input{${MAIN}}`,\n",
|
|
174
|
-
" MAIN,\n",
|
|
175
|
-
],
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
name: "the failure drops the log excerpt",
|
|
180
|
-
harness: HARNESS,
|
|
181
|
-
expect:
|
|
182
|
-
"…quotes the first error line and the l.NNN context from the log",
|
|
183
|
-
disables:
|
|
184
|
-
"the reason a failure is actionable: the error line and where in the source it is. " +
|
|
185
|
-
"'pdflatex exited with 1' alone sends the author to a 2 000-line log",
|
|
186
|
-
edits: [
|
|
187
|
-
[
|
|
188
|
-
SRC,
|
|
189
|
-
' ...(end.lines.length\n ? end.lines\n : ["(no error line found in the log)"]),\n',
|
|
190
|
-
' "(no error line found in the log)",\n',
|
|
191
|
-
],
|
|
192
|
-
],
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
}),
|
|
196
|
-
);
|