paperlint 3.1.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/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.d.ts +10 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +7 -8
- 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/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/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/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 +1 -1
- package/docs/prior-art/test-tooling.md +7 -30
- 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/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.harness.mjs +5 -9
- package/src/build.test.ts +362 -0
- package/src/build.ts +13 -9
- 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/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/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/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
|
@@ -56,7 +56,8 @@ const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
56
56
|
//
|
|
57
57
|
// This is exactly the "four forms of a reference" class from `CLAUDE.md`: a file that moves changes
|
|
58
58
|
// the DEPTH of `../` to the root, and a grep over the path does not show it. All fifteen neighbours
|
|
59
|
-
// in the directory (`*.harness.mjs
|
|
59
|
+
// in the directory (`*.harness.mjs` and the since-removed `*.mutations.mjs`) got four `..` during
|
|
60
|
+
// the resettlement; this
|
|
60
61
|
// file did not, because it was the only one that was not a test and nobody ran it.
|
|
61
62
|
const ROOT = consumerRoot();
|
|
62
63
|
|
|
@@ -418,7 +419,7 @@ function writeReport(dir, g, count, body) {
|
|
|
418
419
|
return rel;
|
|
419
420
|
}
|
|
420
421
|
|
|
421
|
-
function main(argv) {
|
|
422
|
+
export function main(argv) {
|
|
422
423
|
const dir = resolve(argv[2] || ".");
|
|
423
424
|
if (!existsSync(join(dir, "paper.md"))) {
|
|
424
425
|
console.error(`no paper.md in ${dir}`);
|
|
@@ -437,9 +438,17 @@ function main(argv) {
|
|
|
437
438
|
}
|
|
438
439
|
|
|
439
440
|
let worst = 0;
|
|
441
|
+
// 🔴 A FACT CHECK THAT DID NOT RUN IS NOT A CLEAN FACT CHECK. Until 2026-09-27 the summary looked
|
|
442
|
+
// only at blocking findings, so a run where every checker crashed printed "🟢 no FACT check
|
|
443
|
+
// recorded a blocking finding" and exited 0 — a counter reporting on work it never did. FACT rows
|
|
444
|
+
// are the ones whose verdict can block (`exit`, and `eslint` through severity 2).
|
|
445
|
+
const isFact = (g) => g.read === "exit" || g.read === "eslint";
|
|
446
|
+
const factCrashed = [];
|
|
447
|
+
let factJudged = 0;
|
|
440
448
|
for (const g of GATES) {
|
|
441
449
|
const key = `${g.skill}/${g.check}`;
|
|
442
450
|
const abstain = (reason, note) => {
|
|
451
|
+
if (reason === "crashed" && isFact(g)) factCrashed.push(key);
|
|
443
452
|
console.log(
|
|
444
453
|
`\n⬜ ${key} — abstained (${reason}): ${ABSTENTIONS.get(reason)}`,
|
|
445
454
|
);
|
|
@@ -570,6 +579,7 @@ function main(argv) {
|
|
|
570
579
|
count = stated ? Number(stated[1]) : lines > 1 ? lines : 0;
|
|
571
580
|
}
|
|
572
581
|
|
|
582
|
+
if (isFact(g)) factJudged++;
|
|
573
583
|
if (count === 0) {
|
|
574
584
|
// 🔴 NOT a pass. The check ran and produced no finding, and it has no witness for the
|
|
575
585
|
// negative — so it abstains and the reader derives cleanliness from the absence, if they
|
|
@@ -612,13 +622,27 @@ function main(argv) {
|
|
|
612
622
|
if (blocking) worst = 1;
|
|
613
623
|
}
|
|
614
624
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
625
|
+
// Exit codes, extending the contract above (2 already meant "could not judge this paper at all"):
|
|
626
|
+
// 1 — a FACT check found a blocking defect (wins: it is certain and actionable);
|
|
627
|
+
// 2 — otherwise, a FACT check's checker crashed, so that fact is unknown;
|
|
628
|
+
// 0 — every FACT check that had its input judged it clean, or none had its input.
|
|
629
|
+
const summary = [];
|
|
630
|
+
if (worst) summary.push("🔴 a FACT check found something — see above");
|
|
631
|
+
if (factCrashed.length)
|
|
632
|
+
summary.push(
|
|
633
|
+
`🔴 ${factCrashed.length} FACT check(s) could not run: ${factCrashed.join(", ")} — what they check is UNKNOWN, not clean`,
|
|
634
|
+
);
|
|
635
|
+
if (!summary.length)
|
|
636
|
+
summary.push(
|
|
637
|
+
factJudged
|
|
638
|
+
? "🟢 no FACT check recorded a blocking finding"
|
|
639
|
+
: "⬜ no FACT check ran — each one lacked its input, so nothing here was judged",
|
|
640
|
+
);
|
|
641
|
+
console.log(`\n${summary.join("\n")}`);
|
|
618
642
|
console.log(
|
|
619
643
|
` Judgement checks report and never fail the run; read them, and never grep this for green.\n`,
|
|
620
644
|
);
|
|
621
|
-
return worst;
|
|
645
|
+
return worst || (factCrashed.length ? 2 : 0);
|
|
622
646
|
}
|
|
623
647
|
|
|
624
648
|
if (isMain(import.meta.url)) process.exit(main(process.argv));
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-mechanical.mjs — every row against a fake consumer whose checkers are stand-ins with a
|
|
3
|
+
* scripted answer, so each way of READING a result (`exit`, `flags`, `json`, `eslint`) and each way
|
|
4
|
+
* of NOT getting one (input missing, checker not installed, output that is not what the row reads)
|
|
5
|
+
* is driven on purpose. What is compared is what the run tells a reader (stdout) and what it
|
|
6
|
+
* records (the ledger rows, minus hashes and timestamps).
|
|
7
|
+
*
|
|
8
|
+
* The consumer root and the ledger are set BEFORE import: the module resolves both at load time.
|
|
9
|
+
*/
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import {
|
|
12
|
+
chmodSync,
|
|
13
|
+
existsSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { afterEach, test, vi } from "vitest";
|
|
21
|
+
import { runNode, useTempDir, writeTree } from "../../../test/support.mjs";
|
|
22
|
+
|
|
23
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const SCRIPT = join(HERE, "run-mechanical.mjs");
|
|
25
|
+
const root = useTempDir("run-mechanical-");
|
|
26
|
+
const consumer = join(root, "consumer");
|
|
27
|
+
const LEDGER = join(root, "runs.jsonl");
|
|
28
|
+
|
|
29
|
+
/** A node stand-in that prints what FAKE_<NAME> says and exits with FAKE_<NAME>_EXIT. */
|
|
30
|
+
const nodeFake = (name) =>
|
|
31
|
+
`const out = process.env.FAKE_${name} ?? "";\n` +
|
|
32
|
+
`process.stdout.write(out);\n` +
|
|
33
|
+
`process.exit(Number(process.env.FAKE_${name}_EXIT ?? 0));\n`;
|
|
34
|
+
/** The same, in python — the citation checkers and the repro scripts are python. */
|
|
35
|
+
const pyFake = (name) =>
|
|
36
|
+
`import os, sys\n` +
|
|
37
|
+
`sys.stdout.write(os.environ.get("FAKE_${name}", ""))\n` +
|
|
38
|
+
`sys.exit(int(os.environ.get("FAKE_${name}_EXIT", "0")))\n`;
|
|
39
|
+
|
|
40
|
+
writeTree(consumer, {
|
|
41
|
+
"paperlint.json": JSON.stringify({ citeChecks: "cite" }),
|
|
42
|
+
"cite/report-submission.py": pyFake("REPORT"),
|
|
43
|
+
"cite/uncited_refs.py": pyFake("UNCITED"),
|
|
44
|
+
"eslint-rules/paper-structure.mjs":
|
|
45
|
+
'export default { "section-lead": {}, "subsection-size": {} };\n',
|
|
46
|
+
"node_modules/.bin/eslint": `#!${process.execPath}\n${nodeFake("ESLINT")}`,
|
|
47
|
+
// The stand-in hands over to the REAL prose-lint when asked, so the count the ledger records can
|
|
48
|
+
// be checked against what the real checker printed.
|
|
49
|
+
".claude/skills/grade-paper-writing/prose-lint.mjs":
|
|
50
|
+
`if (process.env.FAKE_PROSE_REAL) await import(${JSON.stringify(
|
|
51
|
+
join(HERE, "..", "..", "grade-paper-writing", "prose-lint.mjs"),
|
|
52
|
+
)});\n` + nodeFake("PROSE"),
|
|
53
|
+
".claude/skills/verify-citations/scripts/verify-cites.test.mjs":
|
|
54
|
+
nodeFake("CITES"),
|
|
55
|
+
// Paper A: a submission — a venue limit, a build, every repro checker present.
|
|
56
|
+
"papers/a/paper.md": "# A\n\nA plain paragraph.\n",
|
|
57
|
+
"papers/a/.body-limit": "8\n",
|
|
58
|
+
"papers/a/build/paper.log": "log\n",
|
|
59
|
+
"papers/a/build/paper.aux": "aux\n",
|
|
60
|
+
"papers/a/build/acl_latex.pdf": "%PDF\n",
|
|
61
|
+
"papers/a/repro/arm_permutation.py": pyFake("ARM"),
|
|
62
|
+
"papers/a/repro/delivered_pdf.py": pyFake("DELIVERED"),
|
|
63
|
+
"papers/a/repro/textidote_check.py": pyFake("TEXTIDOTE"),
|
|
64
|
+
"papers/a/rounds/.keep": "",
|
|
65
|
+
// Paper B: built in place, no venue, nothing optional.
|
|
66
|
+
"papers/b/paper.md": "# B\n\nAnother paragraph.\n",
|
|
67
|
+
// Paper C: one figure whose caption breaks BOTH caption limits — two prose-lint findings.
|
|
68
|
+
"papers/c/paper.md": "# C\n\nA paragraph of prose.\n",
|
|
69
|
+
"papers/c/figures/f.tex": `\\caption{${"word ".repeat(110)}}`,
|
|
70
|
+
"papers/b/paper.log": "log\n",
|
|
71
|
+
"textidote.jar": "",
|
|
72
|
+
});
|
|
73
|
+
chmodSync(join(consumer, "node_modules/.bin/eslint"), 0o755);
|
|
74
|
+
|
|
75
|
+
process.env.CLAUDE_PROJECT_DIR = consumer;
|
|
76
|
+
process.env.PIPELINE_LEDGER = LEDGER;
|
|
77
|
+
process.env.TEXTIDOTE_JAR = join(consumer, "textidote.jar");
|
|
78
|
+
const { GATES, main } = await import("./run-mechanical.mjs");
|
|
79
|
+
|
|
80
|
+
const ESLINT_FINDINGS = JSON.stringify([
|
|
81
|
+
{
|
|
82
|
+
filePath: "paper.md",
|
|
83
|
+
messages: [
|
|
84
|
+
{
|
|
85
|
+
ruleId: "paper/section-lead",
|
|
86
|
+
severity: 2,
|
|
87
|
+
line: 1,
|
|
88
|
+
column: 1,
|
|
89
|
+
message: "no lead",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
ruleId: "paper/subsection-size",
|
|
93
|
+
severity: 1,
|
|
94
|
+
line: 3,
|
|
95
|
+
column: 1,
|
|
96
|
+
message: "long",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
ruleId: "paper/other-row",
|
|
100
|
+
severity: 2,
|
|
101
|
+
line: 4,
|
|
102
|
+
column: 1,
|
|
103
|
+
message: "not ours",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Run `main` on a paper with the given stand-in answers. Returns the exit code, the full stdout,
|
|
111
|
+
* the closing summary (the lines after the last blank line — what a reader acts on), and the
|
|
112
|
+
* ledger rows written, one compact string each: `skill/check KIND reason|findings [blocking]`.
|
|
113
|
+
*/
|
|
114
|
+
function run(paper, fakes = {}) {
|
|
115
|
+
rmSync(LEDGER, { force: true });
|
|
116
|
+
const saved = { ...process.env };
|
|
117
|
+
Object.assign(process.env, fakes);
|
|
118
|
+
const lines = [];
|
|
119
|
+
const log = vi.spyOn(console, "log").mockImplementation((...a) => {
|
|
120
|
+
lines.push(a.join(" "));
|
|
121
|
+
});
|
|
122
|
+
const err = vi.spyOn(console, "error").mockImplementation((...a) => {
|
|
123
|
+
lines.push(`stderr: ${a.join(" ")}`);
|
|
124
|
+
});
|
|
125
|
+
let code;
|
|
126
|
+
try {
|
|
127
|
+
code = main(["node", SCRIPT, paper]);
|
|
128
|
+
} finally {
|
|
129
|
+
log.mockRestore();
|
|
130
|
+
err.mockRestore();
|
|
131
|
+
process.env = saved;
|
|
132
|
+
}
|
|
133
|
+
const rows = existsSync(LEDGER)
|
|
134
|
+
? readFileSync(LEDGER, "utf8")
|
|
135
|
+
.trim()
|
|
136
|
+
.split("\n")
|
|
137
|
+
.map((l) => JSON.parse(l))
|
|
138
|
+
.map(
|
|
139
|
+
({ skill, check, kind, reason, findings, blocking }) =>
|
|
140
|
+
`${skill}/${check} ${kind} ${kind === "FINDING" ? findings : reason}` +
|
|
141
|
+
(blocking ? " blocking" : ""),
|
|
142
|
+
)
|
|
143
|
+
: [];
|
|
144
|
+
const out = lines.join("\n").replaceAll(root, "<root>");
|
|
145
|
+
const summary = out.trimEnd().split("\n\n").pop().split("\n");
|
|
146
|
+
return { code, out, summary, rows };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
afterEach(() => rmSync(LEDGER, { force: true }));
|
|
150
|
+
|
|
151
|
+
const NOTE =
|
|
152
|
+
" Judgement checks report and never fail the run; read them, and never grep this for green.";
|
|
153
|
+
|
|
154
|
+
test("a directory with no paper.md: exit 2, named", () => {
|
|
155
|
+
const { code, out, rows } = run(join(root, "nowhere"));
|
|
156
|
+
assert.deepEqual(
|
|
157
|
+
{ code, out, rows },
|
|
158
|
+
{ code: 2, out: "stderr: no paper.md in <root>/nowhere", rows: [] },
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("two rows sharing a key are refused before anything runs", () => {
|
|
163
|
+
GATES.push({ ...GATES[0] });
|
|
164
|
+
try {
|
|
165
|
+
const { code, out, rows } = run(join(consumer, "papers", "a"));
|
|
166
|
+
assert.deepEqual(
|
|
167
|
+
{ code, out, rows },
|
|
168
|
+
{
|
|
169
|
+
code: 2,
|
|
170
|
+
out: 'stderr: two checks share the row key "render-paper/report-submission" — they would take turns being the answer',
|
|
171
|
+
rows: [],
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
} finally {
|
|
175
|
+
GATES.pop();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("paper A, every checker answering with findings: exit 1 on the blocking FACT findings", () => {
|
|
180
|
+
const { code, summary, rows } = run(join(consumer, "papers", "a"), {
|
|
181
|
+
FAKE_REPORT: "overfull box\nunresolved ref\n",
|
|
182
|
+
FAKE_REPORT_EXIT: "1",
|
|
183
|
+
FAKE_ESLINT: ESLINT_FINDINGS,
|
|
184
|
+
FAKE_PROSE: "✍️ prose-lint — paper.md:\n a long caption\n",
|
|
185
|
+
FAKE_PROSE_EXIT: "1",
|
|
186
|
+
FAKE_CITES: "1 fabricated entry\n",
|
|
187
|
+
FAKE_CITES_EXIT: "1",
|
|
188
|
+
FAKE_ARM: JSON.stringify([{ q: 1 }, { q: 2 }]),
|
|
189
|
+
FAKE_DELIVERED: "not json",
|
|
190
|
+
FAKE_TEXTIDOTE: "[]",
|
|
191
|
+
FAKE_UNCITED: JSON.stringify(["smith2020"]),
|
|
192
|
+
});
|
|
193
|
+
assert.deepEqual(
|
|
194
|
+
{ code, summary, rows },
|
|
195
|
+
{
|
|
196
|
+
code: 1,
|
|
197
|
+
summary: ["🔴 a FACT check found something — see above", NOTE],
|
|
198
|
+
rows: [
|
|
199
|
+
"render-paper/report-submission FINDING 2 blocking",
|
|
200
|
+
"render-paper/report-submission-citations ABSTAINED input-missing",
|
|
201
|
+
// Two of the three messages are this row's rules; the third belongs to another row.
|
|
202
|
+
"tighten-paper/structure FINDING 2 blocking",
|
|
203
|
+
// No stated count: the stand-in's two lines are counted.
|
|
204
|
+
"grade-paper-writing/prose-lint FINDING 2",
|
|
205
|
+
"verify-citations/verify-cites FINDING 1 blocking",
|
|
206
|
+
"harden-paper/artifact-coverage ABSTAINED no-witness",
|
|
207
|
+
"build-benchmark/check-provenance ABSTAINED no-witness",
|
|
208
|
+
"build-benchmark/arm-permutation FINDING 2",
|
|
209
|
+
"build-benchmark/delivered-pdf ABSTAINED crashed",
|
|
210
|
+
"build-benchmark/generated-code ABSTAINED no-witness",
|
|
211
|
+
"grade-paper-writing/textidote ABSTAINED no-witness",
|
|
212
|
+
"verify-citations/uncited-refs FINDING 1",
|
|
213
|
+
"draft-paper/population-map ABSTAINED no-witness",
|
|
214
|
+
"tighten-paper/round-diff FINDING 1",
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("a finding's lines reach the reader, capped at twelve", () => {
|
|
221
|
+
const { out } = run(join(consumer, "papers", "a"), {
|
|
222
|
+
FAKE_ESLINT: "[]",
|
|
223
|
+
FAKE_UNCITED: JSON.stringify(Array.from({ length: 20 }, (_, i) => `e${i}`)),
|
|
224
|
+
});
|
|
225
|
+
const block = out
|
|
226
|
+
.split("\n\n")
|
|
227
|
+
.find((b) => b.includes("uncited-refs — 20 finding(s)"));
|
|
228
|
+
assert.deepEqual(block.split("\n"), [
|
|
229
|
+
"🟠 verify-citations/uncited-refs — 20 finding(s) → reviews/mechanical/verify-citations--uncited-refs.md",
|
|
230
|
+
" a bibliography entry no \\cite in the paper points at",
|
|
231
|
+
" [",
|
|
232
|
+
...Array.from({ length: 11 }, (_, i) => ` "e${i}",`),
|
|
233
|
+
]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("a FACT check whose checker crashed makes the run exit 2, and the summary says so — not green", () => {
|
|
237
|
+
const { code, summary, rows } = run(join(consumer, "papers", "a"), {
|
|
238
|
+
FAKE_ESLINT: "Oops! Something went wrong",
|
|
239
|
+
FAKE_PROSE: "✍️ prose-lint — paper.md:\n",
|
|
240
|
+
FAKE_UNCITED: "{}",
|
|
241
|
+
});
|
|
242
|
+
assert.deepEqual(
|
|
243
|
+
{ code, summary, rows },
|
|
244
|
+
{
|
|
245
|
+
code: 2,
|
|
246
|
+
summary: [
|
|
247
|
+
"🔴 1 FACT check(s) could not run: tighten-paper/structure — what they check is UNKNOWN, not clean",
|
|
248
|
+
NOTE,
|
|
249
|
+
],
|
|
250
|
+
rows: [
|
|
251
|
+
"render-paper/report-submission ABSTAINED no-witness",
|
|
252
|
+
"render-paper/report-submission-citations ABSTAINED input-missing",
|
|
253
|
+
// eslint printed something that is not JSON.
|
|
254
|
+
"tighten-paper/structure ABSTAINED crashed",
|
|
255
|
+
// A flags header alone is not a finding.
|
|
256
|
+
"grade-paper-writing/prose-lint ABSTAINED no-witness",
|
|
257
|
+
"verify-citations/verify-cites ABSTAINED no-witness",
|
|
258
|
+
"harden-paper/artifact-coverage ABSTAINED no-witness",
|
|
259
|
+
"build-benchmark/check-provenance ABSTAINED no-witness",
|
|
260
|
+
// Empty output where a JSON array is documented.
|
|
261
|
+
"build-benchmark/arm-permutation ABSTAINED crashed",
|
|
262
|
+
"build-benchmark/delivered-pdf ABSTAINED crashed",
|
|
263
|
+
"build-benchmark/generated-code ABSTAINED no-witness",
|
|
264
|
+
"grade-paper-writing/textidote ABSTAINED crashed",
|
|
265
|
+
// A JSON object where an array is documented.
|
|
266
|
+
"verify-citations/uncited-refs ABSTAINED crashed",
|
|
267
|
+
"draft-paper/population-map ABSTAINED no-witness",
|
|
268
|
+
"tighten-paper/round-diff FINDING 1",
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Three full runs of the script: 1.6 s alone, past vitest's 5 s default under c8 with the whole
|
|
275
|
+
// suite in parallel (measured in `npm run check`), so the budget is stated here.
|
|
276
|
+
test(
|
|
277
|
+
"eslint that crashed on the file, and eslint that ignored it, are not clean runs",
|
|
278
|
+
{
|
|
279
|
+
timeout: 30_000,
|
|
280
|
+
},
|
|
281
|
+
() => {
|
|
282
|
+
const fatal = JSON.stringify([
|
|
283
|
+
{
|
|
284
|
+
filePath: "p",
|
|
285
|
+
messages: [{ ruleId: null, fatal: true, message: "Parsing error" }],
|
|
286
|
+
},
|
|
287
|
+
]);
|
|
288
|
+
const ignored = JSON.stringify([
|
|
289
|
+
{
|
|
290
|
+
filePath: "p",
|
|
291
|
+
messages: [
|
|
292
|
+
{
|
|
293
|
+
ruleId: null,
|
|
294
|
+
message: "File ignored because outside of base path.",
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
]);
|
|
299
|
+
const structure = (fakes) =>
|
|
300
|
+
run(join(consumer, "papers", "a"), fakes).rows.find((x) =>
|
|
301
|
+
x.startsWith("tighten-paper/structure "),
|
|
302
|
+
);
|
|
303
|
+
assert.deepEqual(
|
|
304
|
+
[
|
|
305
|
+
structure({ FAKE_ESLINT: fatal }),
|
|
306
|
+
structure({ FAKE_ESLINT: ignored }),
|
|
307
|
+
structure({ FAKE_ESLINT: "[]" }),
|
|
308
|
+
],
|
|
309
|
+
[
|
|
310
|
+
"tighten-paper/structure ABSTAINED crashed",
|
|
311
|
+
"tighten-paper/structure ABSTAINED input-missing",
|
|
312
|
+
"tighten-paper/structure ABSTAINED no-witness",
|
|
313
|
+
],
|
|
314
|
+
);
|
|
315
|
+
},
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
test("paper B: every row whose input is absent abstains as input-missing; the rest judge", () => {
|
|
319
|
+
const { code, summary, rows } = run(join(consumer, "papers", "b"), {
|
|
320
|
+
FAKE_ESLINT: "[]",
|
|
321
|
+
});
|
|
322
|
+
assert.deepEqual(
|
|
323
|
+
{ code, summary, rows },
|
|
324
|
+
{
|
|
325
|
+
code: 0,
|
|
326
|
+
summary: ["🟢 no FACT check recorded a blocking finding", NOTE],
|
|
327
|
+
rows: [
|
|
328
|
+
"render-paper/report-submission ABSTAINED input-missing",
|
|
329
|
+
"render-paper/report-submission-citations ABSTAINED no-witness",
|
|
330
|
+
"tighten-paper/structure ABSTAINED no-witness",
|
|
331
|
+
"grade-paper-writing/prose-lint ABSTAINED no-witness",
|
|
332
|
+
"verify-citations/verify-cites ABSTAINED no-witness",
|
|
333
|
+
"harden-paper/artifact-coverage ABSTAINED no-witness",
|
|
334
|
+
"build-benchmark/check-provenance ABSTAINED no-witness",
|
|
335
|
+
"build-benchmark/arm-permutation ABSTAINED input-missing",
|
|
336
|
+
"build-benchmark/delivered-pdf ABSTAINED input-missing",
|
|
337
|
+
"build-benchmark/generated-code ABSTAINED no-witness",
|
|
338
|
+
"grade-paper-writing/textidote ABSTAINED input-missing",
|
|
339
|
+
"verify-citations/uncited-refs ABSTAINED input-missing",
|
|
340
|
+
"draft-paper/population-map ABSTAINED no-witness",
|
|
341
|
+
"tighten-paper/round-diff ABSTAINED input-missing",
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("no checker installed: every row that ran is `crashed`, never a finding, and the run is not green", () => {
|
|
348
|
+
const { code, summary, rows } = run(join(consumer, "papers", "b"), {
|
|
349
|
+
PATH: join(root, "empty-path"),
|
|
350
|
+
});
|
|
351
|
+
assert.deepEqual(
|
|
352
|
+
{ code, summary, rows },
|
|
353
|
+
{
|
|
354
|
+
code: 2,
|
|
355
|
+
summary: [
|
|
356
|
+
"🔴 3 FACT check(s) could not run: render-paper/report-submission-citations, tighten-paper/structure, verify-citations/verify-cites — what they check is UNKNOWN, not clean",
|
|
357
|
+
NOTE,
|
|
358
|
+
],
|
|
359
|
+
rows: [
|
|
360
|
+
"render-paper/report-submission ABSTAINED input-missing",
|
|
361
|
+
"render-paper/report-submission-citations ABSTAINED crashed",
|
|
362
|
+
"tighten-paper/structure ABSTAINED crashed",
|
|
363
|
+
"grade-paper-writing/prose-lint ABSTAINED crashed",
|
|
364
|
+
"verify-citations/verify-cites ABSTAINED crashed",
|
|
365
|
+
"harden-paper/artifact-coverage ABSTAINED crashed",
|
|
366
|
+
"build-benchmark/check-provenance ABSTAINED crashed",
|
|
367
|
+
"build-benchmark/arm-permutation ABSTAINED input-missing",
|
|
368
|
+
"build-benchmark/delivered-pdf ABSTAINED input-missing",
|
|
369
|
+
"build-benchmark/generated-code ABSTAINED crashed",
|
|
370
|
+
"grade-paper-writing/textidote ABSTAINED input-missing",
|
|
371
|
+
"verify-citations/uncited-refs ABSTAINED input-missing",
|
|
372
|
+
"draft-paper/population-map ABSTAINED crashed",
|
|
373
|
+
"tighten-paper/round-diff ABSTAINED input-missing",
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("as a process, in a consumer that declares nothing: no FACT check can run, and it says so", () => {
|
|
380
|
+
const bare = join(root, "bare");
|
|
381
|
+
writeTree(bare, { "papers/p/paper.md": "# P\n\nText.\n" });
|
|
382
|
+
writeFileSync(join(bare, "package.json"), "{}\n");
|
|
383
|
+
const r = runNode(SCRIPT, [join(bare, "papers", "p")], {
|
|
384
|
+
env: {
|
|
385
|
+
CLAUDE_PROJECT_DIR: bare,
|
|
386
|
+
PIPELINE_LEDGER: join(root, "bare.jsonl"),
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
assert.deepEqual(
|
|
390
|
+
{
|
|
391
|
+
status: r.status,
|
|
392
|
+
stderr: r.stderr,
|
|
393
|
+
summary: r.stdout.trimEnd().split("\n\n").pop().split("\n"),
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
status: 0,
|
|
397
|
+
stderr: "",
|
|
398
|
+
summary: [
|
|
399
|
+
"⬜ no FACT check ran — each one lacked its input, so nothing here was judged",
|
|
400
|
+
NOTE,
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("the prose-lint row records the number of findings prose-lint found, not its line count", () => {
|
|
407
|
+
const row = run(join(consumer, "papers", "c"), {
|
|
408
|
+
FAKE_PROSE_REAL: "1",
|
|
409
|
+
}).rows.find((x) => x.startsWith("grade-paper-writing/prose-lint "));
|
|
410
|
+
assert.equal(row, "grade-paper-writing/prose-lint FINDING 2");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("no directory argument means the current directory", () => {
|
|
414
|
+
const { code, out, rows } = run(undefined);
|
|
415
|
+
assert.deepEqual(
|
|
416
|
+
{ code, out, rows },
|
|
417
|
+
{ code: 2, out: `stderr: no paper.md in ${process.cwd()}`, rows: [] },
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("a blocking finding with no output still gets a report a reader can open", () => {
|
|
422
|
+
const r = run(join(consumer, "papers", "a"), { FAKE_REPORT_EXIT: "1" });
|
|
423
|
+
const report = readFileSync(
|
|
424
|
+
join(
|
|
425
|
+
consumer,
|
|
426
|
+
"papers",
|
|
427
|
+
"a",
|
|
428
|
+
"reviews",
|
|
429
|
+
"mechanical",
|
|
430
|
+
"render-paper--report-submission.md",
|
|
431
|
+
),
|
|
432
|
+
"utf8",
|
|
433
|
+
).replace(/^created: .*$/m, "created: <date>");
|
|
434
|
+
assert.deepEqual(
|
|
435
|
+
{ row: r.rows[0], report },
|
|
436
|
+
{
|
|
437
|
+
row: "render-paper/report-submission FINDING 1 blocking",
|
|
438
|
+
report: [
|
|
439
|
+
"---",
|
|
440
|
+
'title: "render-paper/report-submission — mechanical check output"',
|
|
441
|
+
"created: <date>",
|
|
442
|
+
"findings: 1",
|
|
443
|
+
"---",
|
|
444
|
+
"",
|
|
445
|
+
"Written by `run-mechanical.mjs`. What this check looks at: body pages, overfull boxes, unresolved refs, dropped characters, anonymity, artifact URLs",
|
|
446
|
+
"",
|
|
447
|
+
"```",
|
|
448
|
+
"(the check produced no output)",
|
|
449
|
+
"```",
|
|
450
|
+
"",
|
|
451
|
+
].join("\n"),
|
|
452
|
+
},
|
|
453
|
+
);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("an empty $TEXTIDOTE_JAR is no declaration: the row looks at /opt/textidote, like the checker", () => {
|
|
457
|
+
// The answer depends on this machine (some images ship the jar in /opt), so the expectation is
|
|
458
|
+
// derived from the same fact the row reads rather than hard-coded.
|
|
459
|
+
const atOpt = existsSync("/opt/textidote/textidote.jar");
|
|
460
|
+
const row = run(join(consumer, "papers", "a"), {
|
|
461
|
+
TEXTIDOTE_JAR: "",
|
|
462
|
+
FAKE_TEXTIDOTE: "[]",
|
|
463
|
+
}).rows.find((x) => x.startsWith("grade-paper-writing/textidote "));
|
|
464
|
+
assert.equal(
|
|
465
|
+
row,
|
|
466
|
+
`grade-paper-writing/textidote ABSTAINED ${atOpt ? "no-witness" : "input-missing"}`,
|
|
467
|
+
);
|
|
468
|
+
});
|
|
@@ -120,12 +120,12 @@ function produced(r) {
|
|
|
120
120
|
return "no row";
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
function main(argv) {
|
|
123
|
+
export function main(argv) {
|
|
124
124
|
const dir = resolve(argv[2] || ".");
|
|
125
125
|
const rows = status(dir, { gates: EXPECTED_GATES });
|
|
126
126
|
|
|
127
127
|
console.log(`\n ${basename(dir)}\n`);
|
|
128
|
-
const pad = (s, n) => String(s
|
|
128
|
+
const pad = (s, n) => String(s).padEnd(n); // every cell is a string or a count
|
|
129
129
|
console.log(
|
|
130
130
|
` ${pad("check", 36)}${pad("freshness", 14)}${pad("last run produced", 22)}${pad("runs", 6)}when`,
|
|
131
131
|
);
|