paperlint 7.2.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1 -6
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +3 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -42
- package/dist/cli.js.map +1 -1
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +10 -13
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.js +2 -2
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +22 -91
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/papers.d.ts +1 -3
- package/dist/eslint-rules/papers.d.ts.map +1 -1
- package/dist/eslint-rules/papers.js +2 -4
- package/dist/eslint-rules/papers.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +8 -25
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +1 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +3 -9
- package/dist/init.js.map +1 -1
- package/dist/lib/markdown.d.ts +5 -81
- package/dist/lib/markdown.d.ts.map +1 -1
- package/dist/lib/markdown.js +5 -116
- package/dist/lib/markdown.js.map +1 -1
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +0 -2
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/lib/skill-eval-fixture.d.ts +6 -4
- package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
- package/dist/lib/skill-eval-fixture.js +25 -18
- package/dist/lib/skill-eval-fixture.js.map +1 -1
- package/dist/new-paper.d.ts +5 -11
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +12 -23
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +4 -4
- package/dist/paper-prose.d.ts +17 -0
- package/dist/paper-prose.d.ts.map +1 -0
- package/dist/paper-prose.js +46 -0
- package/dist/paper-prose.js.map +1 -0
- package/dist/structure.d.ts.map +1 -1
- package/dist/structure.js +4 -3
- package/dist/structure.js.map +1 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/configuration.md +7 -9
- package/docs/e2e.md +4 -6
- package/docs/prior-art/paper-folder-scaffolding.md +24 -29
- package/docs/rules/tex/claim-provenance.md +0 -1
- package/docs/rules.md +2 -6
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.ts +10 -13
- package/eslint-rules/paper-context.ts +2 -2
- package/eslint-rules/paper-typography.test.mjs +17 -85
- package/eslint-rules/paper-typography.ts +22 -98
- package/eslint-rules/papers.harness.mjs +1 -1
- package/eslint-rules/papers.ts +2 -4
- package/eslint-rules/tex-build.harness.mjs +2 -2
- package/eslint-rules/tex-build.ts +8 -25
- package/eslint.config.mjs +0 -1
- package/hooks/hooks.harness.ts +15 -15
- package/hooks/paper-edit-guard.hook.mjs +5 -8
- package/hooks/paper-edit-guard.test.ts +12 -3
- package/hooks/paper-skills-nudge.hook.mjs +2 -3
- package/hooks/paper-skills-nudge.test.ts +13 -3
- package/hooks/paper-status-gates.hook.mjs +2 -2
- package/hooks/paper-status-gates.test.ts +14 -2
- package/lib/markdown.test.mjs +5 -60
- package/lib/markdown.ts +9 -136
- package/lib/paper-config.ts +0 -2
- package/lib/skill-checks.test.mjs +4 -1
- package/lib/skill-eval-fixture.ts +25 -18
- package/package.json +1 -3
- package/presets/realm.jsonc +1 -1
- package/scripts/harness-api.frozen.json +0 -5
- package/skills/cold-read-diff/SKILL.md +3 -3
- package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
- package/skills/grade-paper-writing/SKILL.md +14 -22
- package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
- package/skills/grade-paper-writing/prose-lint.mjs +108 -278
- package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/SKILL.md +9 -12
- package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
- package/skills/paper-pipeline/description-language.eval.mjs +25 -15
- package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
- package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
- package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
- package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
- package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
- package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
- package/skills/paper-pipeline/scripts/README.md +0 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
- package/skills/paper-status/SKILL.md +2 -2
- package/skills/paper-status/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/extract-pdf-facts.mjs +2 -2
- package/skills/tighten-paper/SKILL.md +29 -42
- package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
- package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
- package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
- package/src/build.harness.mjs +2 -2
- package/src/build.test.ts +1 -1
- package/src/build.ts +1 -6
- package/src/cli.harness.mjs +26 -46
- package/src/cli.test.ts +32 -40
- package/src/cli.ts +14 -54
- package/src/folder-venue-rule.test.ts +1 -1
- package/src/init.test.ts +6 -7
- package/src/init.ts +3 -22
- package/src/lint-fix.test.ts +10 -4
- package/src/new-paper.harness.mjs +9 -28
- package/src/new-paper.test.ts +6 -6
- package/src/new-paper.ts +11 -30
- package/src/paper-files.test.ts +5 -4
- package/src/paper-files.ts +4 -4
- package/src/paper-prose.test.ts +49 -0
- package/src/paper-prose.ts +67 -0
- package/src/parse-args.test.ts +0 -1
- package/src/structure.harness.ts +5 -11
- package/src/structure.ts +4 -3
- package/src/types.ts +0 -2
- package/fixtures/real-markdown-paper/baseline.json +0 -22
- package/fixtures/real-markdown-paper/baseline.ts +0 -70
- package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
- package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
- package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
- package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
- package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
- package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
- package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
- package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
- package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
- package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
- package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
- package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
- package/skills/tighten-paper/structure.mjs +0 -364
- package/skills/tighten-paper/structure.test.ts +0 -117
- package/templates/paper/paper.md +0 -4
package/src/cli.harness.mjs
CHANGED
|
@@ -42,6 +42,10 @@ const { PROGRAMS } = await import(join(HERE, "doctor.ts"));
|
|
|
42
42
|
// loads vigiles without recording a check is reported as having verified nothing.
|
|
43
43
|
const check = createChecker();
|
|
44
44
|
|
|
45
|
+
/** A paper.tex whose whole body is `body`. */
|
|
46
|
+
const tex = (body) =>
|
|
47
|
+
`\\documentclass{article}\n\\begin{document}\n${body}\n\\end{document}\n`;
|
|
48
|
+
|
|
45
49
|
/** Runs the utility with output captured — quieter and faster than spawning a process. */
|
|
46
50
|
async function cli(args, cwd) {
|
|
47
51
|
// 🔴 THE STREAMS ARE SPLIT, AND THAT IS LOAD-BEARING. As long as the harness dumped log and
|
|
@@ -129,11 +133,11 @@ check(
|
|
|
129
133
|
const ruleBlocks = (c) => c.filter((b) => Array.isArray(b.files));
|
|
130
134
|
check(
|
|
131
135
|
"without a LaTeX language the config still gets built — a corpus with no .tex is not a reason to refuse",
|
|
132
|
-
Array.isArray(cfg) && ruleBlocks(cfg).length ===
|
|
136
|
+
Array.isArray(cfg) && ruleBlocks(cfg).length === 3,
|
|
133
137
|
);
|
|
134
138
|
check(
|
|
135
139
|
"with a LaTeX language two blocks are added: paper.tex, and the files it includes",
|
|
136
|
-
ruleBlocks(buildConfig({}, {})).length ===
|
|
140
|
+
ruleBlocks(buildConfig({}, {})).length === 5,
|
|
137
141
|
);
|
|
138
142
|
check(
|
|
139
143
|
"🔴 the project's paper TEMPLATE directory is ignored — flat config does not skip dot-directories",
|
|
@@ -178,7 +182,7 @@ check(
|
|
|
178
182
|
join(paper, "versions", "2026-07-22-submitted.pdf"),
|
|
179
183
|
"x".repeat(100),
|
|
180
184
|
);
|
|
181
|
-
writeFileSync(join(paper, "paper.
|
|
185
|
+
writeFileSync(join(paper, "paper.tex"), tex("RQ1: does it hold?"));
|
|
182
186
|
writeFileSync(
|
|
183
187
|
join(paper, "PIPELINE-STATUS.md"),
|
|
184
188
|
`---\nstages:\n - stage: submitted\n date: 2026-07-22\n pdf: versions/2026-07-22-submitted.pdf\n bytes: 100\n source: versions/s.tex\n sourceBytes: 4\n---\n# S\n\n| id | note |\n|---|---|\n| cites | bib-authors run |\n`,
|
|
@@ -703,7 +707,7 @@ check(
|
|
|
703
707
|
const status = (bytes) =>
|
|
704
708
|
`---\nstages:\n - stage: submitted\n date: 2026-07-22\n pdf: versions/2026-07-22-submitted.pdf\n bytes: ${bytes}\n source: versions/s.tex\n sourceBytes: 4\n---\n# S\n\n| id | note |\n|---|---|\n| cites | bib-authors run |\n`;
|
|
705
709
|
writeFileSync(join(paper, "versions", "s.tex"), "abcd");
|
|
706
|
-
writeFileSync(join(paper, "paper.
|
|
710
|
+
writeFileSync(join(paper, "paper.tex"), tex("RQ1: does it hold?"));
|
|
707
711
|
|
|
708
712
|
writeFileSync(join(paper, "PIPELINE-STATUS.md"), status(100));
|
|
709
713
|
const clean = await cli(["lint", "papers"], root);
|
|
@@ -788,7 +792,7 @@ check(
|
|
|
788
792
|
join(paper, "versions", "2026-07-22-submitted.pdf"),
|
|
789
793
|
"x".repeat(100),
|
|
790
794
|
);
|
|
791
|
-
writeFileSync(join(paper, "paper.
|
|
795
|
+
writeFileSync(join(paper, "paper.tex"), tex("RQ1: does it hold?"));
|
|
792
796
|
const status = (bytes) =>
|
|
793
797
|
`---\nstages:\n - stage: submitted\n date: 2026-07-22\n pdf: versions/2026-07-22-submitted.pdf\n bytes: ${bytes}\n source: versions/s.tex\n sourceBytes: 4\n---\n# S\n\n| id | note |\n|---|---|\n| cites | bib-authors run |\n`;
|
|
794
798
|
writeFileSync(join(paper, "PIPELINE-STATUS.md"), status(100));
|
|
@@ -897,8 +901,8 @@ check(
|
|
|
897
901
|
);
|
|
898
902
|
// Three § signs — `paper/section-word`, warn level and only warn.
|
|
899
903
|
writeFileSync(
|
|
900
|
-
join(paper, "paper.
|
|
901
|
-
"
|
|
904
|
+
join(paper, "paper.tex"),
|
|
905
|
+
tex("RQ1: does it hold?\n\nSee \u00a7 5 and \u00a7 6 and \u00a7 7."),
|
|
902
906
|
);
|
|
903
907
|
writeFileSync(
|
|
904
908
|
join(paper, "PIPELINE-STATUS.md"),
|
|
@@ -1040,7 +1044,7 @@ check(
|
|
|
1040
1044
|
const paper = join(tree, "papers", "p");
|
|
1041
1045
|
mkdirSync(paper, { recursive: true });
|
|
1042
1046
|
// Two findings of different kinds: a structural one (no scorecard) and a rule one (`§`).
|
|
1043
|
-
writeFileSync(join(paper, "paper.
|
|
1047
|
+
writeFileSync(join(paper, "paper.tex"), tex("See § 3 and §4."));
|
|
1044
1048
|
|
|
1045
1049
|
const inside = await cli(["lint", "--json", "papers"], tree);
|
|
1046
1050
|
const outside = await cli(
|
|
@@ -1273,22 +1277,10 @@ console.log(
|
|
|
1273
1277
|
|
|
1274
1278
|
// ── the new flags parse, and none of them becomes the directory argument ─────────────────
|
|
1275
1279
|
{
|
|
1276
|
-
const a = parseArgs([
|
|
1277
|
-
"init",
|
|
1278
|
-
"--yes",
|
|
1279
|
-
"--no-hooks",
|
|
1280
|
-
"--paper",
|
|
1281
|
-
"demo",
|
|
1282
|
-
"--format",
|
|
1283
|
-
"md",
|
|
1284
|
-
]);
|
|
1280
|
+
const a = parseArgs(["init", "--yes", "--no-hooks", "--paper", "demo"]);
|
|
1285
1281
|
check(
|
|
1286
1282
|
"init flags parse into fields, not into paths",
|
|
1287
|
-
a.yes &&
|
|
1288
|
-
a.noHooks &&
|
|
1289
|
-
a.paper === "demo" &&
|
|
1290
|
-
a.format === "md" &&
|
|
1291
|
-
a.paths.length === 0,
|
|
1283
|
+
a.yes && a.noHooks && a.paper === "demo" && a.paths.length === 0,
|
|
1292
1284
|
);
|
|
1293
1285
|
check("-y is --yes", parseArgs(["init", "-y"]).yes === true);
|
|
1294
1286
|
check(
|
|
@@ -1300,11 +1292,6 @@ console.log(
|
|
|
1300
1292
|
"🔴 --hooks=local is NOT implemented, and it is refused by name rather than read as a path",
|
|
1301
1293
|
r.code === 2 && /--hooks=local is not implemented/.test(r.out),
|
|
1302
1294
|
);
|
|
1303
|
-
const f = await cli(["init", "--format", "pdf"]);
|
|
1304
|
-
check(
|
|
1305
|
-
"an unknown --format is refused before anything is written",
|
|
1306
|
-
f.code === 2 && /--format must be one of tex, md/.test(f.out),
|
|
1307
|
-
);
|
|
1308
1295
|
}
|
|
1309
1296
|
|
|
1310
1297
|
// ── init wires the hooks, and a human can say no ─────────────────────────────────────────
|
|
@@ -1315,7 +1302,7 @@ console.log(
|
|
|
1315
1302
|
const mk = (name) => {
|
|
1316
1303
|
const dir = join(root, name);
|
|
1317
1304
|
mkdirSync(join(dir, "papers", "p1"), { recursive: true });
|
|
1318
|
-
writeFileSync(join(dir, "papers", "p1", "paper.
|
|
1305
|
+
writeFileSync(join(dir, "papers", "p1", "paper.tex"), "% P\n");
|
|
1319
1306
|
writeFileSync(
|
|
1320
1307
|
join(dir, "papers", "p1", "PIPELINE-STATUS.md"),
|
|
1321
1308
|
"---\n---\n",
|
|
@@ -1426,19 +1413,17 @@ console.log(
|
|
|
1426
1413
|
ask: async (q) => {
|
|
1427
1414
|
asked.push(q);
|
|
1428
1415
|
if (/create a first paper/.test(q)) return "first";
|
|
1429
|
-
if (/format:/.test(q)) return "md";
|
|
1430
1416
|
return "";
|
|
1431
1417
|
},
|
|
1432
|
-
createPaper: async (papersRoot, name
|
|
1433
|
-
made.push({ papersRoot, name
|
|
1418
|
+
createPaper: async (papersRoot, name) => {
|
|
1419
|
+
made.push({ papersRoot, name });
|
|
1434
1420
|
return 0;
|
|
1435
1421
|
},
|
|
1436
1422
|
});
|
|
1437
1423
|
check(
|
|
1438
|
-
"a human with no paper is offered one, and the answer
|
|
1424
|
+
"a human with no paper is offered one, and the answer reaches `paperlint new`'s routine",
|
|
1439
1425
|
made.length === 1 &&
|
|
1440
1426
|
made[0].name === "first" &&
|
|
1441
|
-
made[0].format === "md" &&
|
|
1442
1427
|
made[0].papersRoot === join(dir, "papers"),
|
|
1443
1428
|
);
|
|
1444
1429
|
}
|
|
@@ -1466,22 +1451,17 @@ console.log(
|
|
|
1466
1451
|
log: () => {},
|
|
1467
1452
|
interactive: false,
|
|
1468
1453
|
paper: "given",
|
|
1469
|
-
createPaper: async (papersRoot, name
|
|
1470
|
-
made.push({ name, format }),
|
|
1471
|
-
0
|
|
1472
|
-
),
|
|
1454
|
+
createPaper: async (papersRoot, name) => (made.push({ name }), 0),
|
|
1473
1455
|
});
|
|
1474
1456
|
check(
|
|
1475
|
-
"--paper creates it without a human
|
|
1476
|
-
made.length === 1 &&
|
|
1477
|
-
made[0].name === "given" &&
|
|
1478
|
-
made[0].format === "tex",
|
|
1457
|
+
"--paper creates it without a human",
|
|
1458
|
+
made.length === 1 && made[0].name === "given",
|
|
1479
1459
|
);
|
|
1480
1460
|
}
|
|
1481
1461
|
{
|
|
1482
1462
|
const dir = bare("has-one");
|
|
1483
1463
|
mkdirSync(join(dir, "papers", "p1"), { recursive: true });
|
|
1484
|
-
writeFileSync(join(dir, "papers", "p1", "paper.
|
|
1464
|
+
writeFileSync(join(dir, "papers", "p1", "paper.tex"), "% P\n");
|
|
1485
1465
|
const asked = [];
|
|
1486
1466
|
await init(dir, {
|
|
1487
1467
|
...base,
|
|
@@ -1569,7 +1549,7 @@ console.log(
|
|
|
1569
1549
|
`{"${PAPERS_DIR_FIELD}":"papers"}\n`,
|
|
1570
1550
|
);
|
|
1571
1551
|
mkdirSync(join(root, "papers", "p1"), { recursive: true });
|
|
1572
|
-
writeFileSync(join(root, "papers", "p1", "paper.
|
|
1552
|
+
writeFileSync(join(root, "papers", "p1", "paper.tex"), "% P\n");
|
|
1573
1553
|
writeFileSync(
|
|
1574
1554
|
join(root, "papers", "p1", "PIPELINE-STATUS.md"),
|
|
1575
1555
|
"---\n---\n",
|
|
@@ -1591,7 +1571,7 @@ console.log(
|
|
|
1591
1571
|
join(root, "papers", "p2", "PIPELINE-STATUS.md"),
|
|
1592
1572
|
readFileSync(join(root, "papers", ".template", "PIPELINE-STATUS.md")),
|
|
1593
1573
|
);
|
|
1594
|
-
writeFileSync(join(root, "papers", "p2", "paper.
|
|
1574
|
+
writeFileSync(join(root, "papers", "p2", "paper.tex"), "% P\n");
|
|
1595
1575
|
const r2 = await cli(["lint"], root);
|
|
1596
1576
|
check(
|
|
1597
1577
|
"and the same scorecard in a real paper folder fails — the ignore is scoped, not a blind spot",
|
|
@@ -1625,11 +1605,11 @@ console.log(
|
|
|
1625
1605
|
/names no venue preset yet/.test(r.out) &&
|
|
1626
1606
|
/\(0 errors, 1 warning\)/.test(r.out),
|
|
1627
1607
|
);
|
|
1628
|
-
const again = await cli(["new", "demo"
|
|
1608
|
+
const again = await cli(["new", "demo"], root);
|
|
1629
1609
|
check(
|
|
1630
1610
|
"a second run on the same folder adds nothing — the tex source already stands",
|
|
1631
1611
|
again.code === 0 &&
|
|
1632
|
-
|
|
1612
|
+
!/\+ /.test(again.out) &&
|
|
1633
1613
|
/kept — never overwritten/.test(again.out),
|
|
1634
1614
|
);
|
|
1635
1615
|
const bad = await cli(["new", "Demo"], root);
|
package/src/cli.test.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import assert from "node:assert/strict";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
9
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
10
10
|
import { createServer } from "node:http";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import { test } from "vitest";
|
|
@@ -50,14 +50,14 @@ test("parseArgs: the boolean flags of build and toolchain, and a value flag with
|
|
|
50
50
|
[
|
|
51
51
|
parseArgs(["build", "--all", "--dry-run", "papers/p"]),
|
|
52
52
|
parseArgs(["toolchain", "--check"]),
|
|
53
|
-
parseArgs(["new", "x", "--
|
|
53
|
+
parseArgs(["new", "x", "--paper"]),
|
|
54
54
|
parseArgs(["new", "x", "--venue="]),
|
|
55
55
|
parseArgs(["new", "x", "--kind", "short", "--max-warnings=3"]),
|
|
56
56
|
],
|
|
57
57
|
[
|
|
58
58
|
{ ...base, cmd: "build", all: true, dryRun: true, paths: ["papers/p"] },
|
|
59
59
|
{ ...base, cmd: "toolchain", check: true },
|
|
60
|
-
{ ...base, cmd: "new", paths: ["x"], missingValue: "--
|
|
60
|
+
{ ...base, cmd: "new", paths: ["x"], missingValue: "--paper" },
|
|
61
61
|
{ ...base, cmd: "new", paths: ["x"], missingValue: "--venue" },
|
|
62
62
|
{ ...base, cmd: "new", paths: ["x"], kind: "short", maxWarnings: 3 },
|
|
63
63
|
],
|
|
@@ -142,20 +142,18 @@ test("build refuses with no target, and --all over no papers", async () => {
|
|
|
142
142
|
);
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
test("new refuses
|
|
145
|
+
test("new refuses a broken config, and a config that names no papers directory", async () => {
|
|
146
146
|
const broken = join(root, "new-broken");
|
|
147
147
|
writeTree(broken, { "paperlint.json": "{ not json" });
|
|
148
148
|
const none = join(root, "new-none");
|
|
149
149
|
writeTree(none, { "paperlint.json": JSON.stringify({ papersDir: [] }) });
|
|
150
150
|
const results = [
|
|
151
|
-
await cli(["new", "p"
|
|
152
|
-
await cli(["new", "p"
|
|
153
|
-
await cli(["new", "p", "--format", "md"], none),
|
|
151
|
+
await cli(["new", "p"], broken),
|
|
152
|
+
await cli(["new", "p"], none),
|
|
154
153
|
];
|
|
155
154
|
assert.deepEqual(
|
|
156
155
|
results.map((r) => ({ code: r.code, err: r.err.split(":")[0] })),
|
|
157
156
|
[
|
|
158
|
-
{ code: 2, err: "--format must be one of tex, md — got `docx`" },
|
|
159
157
|
{ code: 2, err: join(broken, "paperlint.json is not valid JSON") },
|
|
160
158
|
{
|
|
161
159
|
code: 2,
|
|
@@ -170,7 +168,7 @@ test("new with several papers directories says which one it used", async () => {
|
|
|
170
168
|
writeTree(dir, {
|
|
171
169
|
"paperlint.json": JSON.stringify({ papersDir: ["a", "b"] }),
|
|
172
170
|
});
|
|
173
|
-
const r = await cli(["new", "p", "--
|
|
171
|
+
const r = await cli(["new", "p", "--yes"], dir);
|
|
174
172
|
assert.equal(
|
|
175
173
|
r.out.split("\n")[0],
|
|
176
174
|
"several papers directories are declared — using the first: a",
|
|
@@ -274,20 +272,20 @@ async function withoutTex<T>(dir: string, fn: () => Promise<T>): Promise<T> {
|
|
|
274
272
|
const noTexDir = join(root, "build-notex");
|
|
275
273
|
writeTree(noTexDir, {
|
|
276
274
|
"package.json": "{}",
|
|
277
|
-
"papers/
|
|
275
|
+
"papers/nosource/notes.txt": "x\n",
|
|
278
276
|
"papers/tex/paper.tex":
|
|
279
277
|
"\\documentclass{article}\\begin{document}x\\end{document}\n",
|
|
280
278
|
});
|
|
281
279
|
const venues = texInputsDir();
|
|
282
280
|
|
|
283
|
-
test("build: a
|
|
284
|
-
const
|
|
285
|
-
cli(["build", "papers/
|
|
281
|
+
test("build: a paper with no paper.tex is refused, naming paper.tex", async () => {
|
|
282
|
+
const none = await withoutTex(noTexDir, () =>
|
|
283
|
+
cli(["build", "papers/nosource"], noTexDir),
|
|
286
284
|
);
|
|
287
|
-
assert.deepEqual(
|
|
285
|
+
assert.deepEqual(none, {
|
|
288
286
|
code: 1,
|
|
289
287
|
out: [
|
|
290
|
-
"papers/
|
|
288
|
+
"papers/nosource",
|
|
291
289
|
` inputs: TEXINPUTS += the paper's directory, then ${venues}`,
|
|
292
290
|
" compile: refused — no paper.tex; paperlint compiles LaTeX, and this paper has none",
|
|
293
291
|
" measure: skipped — nothing is compiled",
|
|
@@ -295,7 +293,7 @@ test("build: a markdown-only paper is refused", async () => {
|
|
|
295
293
|
" ✗ nothing to compile: no paper.tex",
|
|
296
294
|
].join("\n"),
|
|
297
295
|
err:
|
|
298
|
-
"\nNo paper.tex in: papers/
|
|
296
|
+
"\nNo paper.tex in: papers/nosource.\n" +
|
|
299
297
|
'This is NOT "nothing to build" — paperlint compiles LaTeX, and these papers have no LaTeX source.\n' +
|
|
300
298
|
"Write the paper in paper.tex; `paperlint new <name>` creates one.",
|
|
301
299
|
});
|
|
@@ -399,7 +397,7 @@ test("build --dry-run on a paper with a venue plans for the preset's packages",
|
|
|
399
397
|
|
|
400
398
|
test("a paper's paperlint.json that does not parse stops the lint with its path", () => {
|
|
401
399
|
const dir = writeTree(join(root, "rules-broken"), {
|
|
402
|
-
"p/paper.
|
|
400
|
+
"p/paper.tex": "% P\n",
|
|
403
401
|
"p/paperlint.json": "{ nope",
|
|
404
402
|
});
|
|
405
403
|
const r = paperRuleBlocks([join(dir, "p")]);
|
|
@@ -444,15 +442,12 @@ test("silentOptionalRules: a rule no block names for any paper is silent — a g
|
|
|
444
442
|
);
|
|
445
443
|
});
|
|
446
444
|
|
|
447
|
-
test("init with no path
|
|
445
|
+
test("init with no path sets up the current directory, and its first paper is paper.tex", async () => {
|
|
448
446
|
const dir = writeTree(join(root, "init-here"), { "package.json": "{}" });
|
|
449
|
-
const r = await cli(
|
|
450
|
-
|
|
451
|
-
dir,
|
|
452
|
-
|
|
453
|
-
assert.equal(
|
|
454
|
-
existsSync(join(dir, "papers", "first", "paper.md")),
|
|
455
|
-
true,
|
|
447
|
+
const r = await cli(["init", "--yes", "--no-hooks", "--paper", "first"], dir);
|
|
448
|
+
assert.deepEqual(
|
|
449
|
+
readdirSync(join(dir, "papers", "first")).sort(),
|
|
450
|
+
["PIPELINE-STATUS.md", "paper.tex", "paperlint.json"],
|
|
456
451
|
r.out + r.err,
|
|
457
452
|
);
|
|
458
453
|
});
|
|
@@ -488,12 +483,9 @@ test("lint: a named directory with nothing paperlint lints is named in full when
|
|
|
488
483
|
test("new from inside the paper it names adds the missing files and names the paper in full", async () => {
|
|
489
484
|
const dir = writeTree(join(root, "new-inside"), {
|
|
490
485
|
"paperlint.json": JSON.stringify({ papersDir: "papers" }),
|
|
491
|
-
"papers/p/paper.
|
|
486
|
+
"papers/p/paper.tex": "% P\n",
|
|
492
487
|
});
|
|
493
|
-
const r = await cli(
|
|
494
|
-
["new", "p", "--format", "md", "--yes"],
|
|
495
|
-
join(dir, "papers", "p"),
|
|
496
|
-
);
|
|
488
|
+
const r = await cli(["new", "p", "--yes"], join(dir, "papers", "p"));
|
|
497
489
|
assert.match(
|
|
498
490
|
r.out,
|
|
499
491
|
new RegExp(
|
|
@@ -507,7 +499,7 @@ test("new with several directories, run from the first, names it in full", async
|
|
|
507
499
|
"paperlint.json": JSON.stringify({ papersDir: ["a", "b"] }),
|
|
508
500
|
"a/.keep": "",
|
|
509
501
|
});
|
|
510
|
-
const r = await cli(["new", "p", "--
|
|
502
|
+
const r = await cli(["new", "p", "--yes"], join(dir, "a"));
|
|
511
503
|
assert.equal(
|
|
512
504
|
r.out.split("\n")[0],
|
|
513
505
|
`several papers directories are declared — using the first: ${join(dir, "a")}`,
|
|
@@ -519,7 +511,7 @@ test("lint: an ESLint failure that is not an empty set is not swallowed", async
|
|
|
519
511
|
"paperlint.json": JSON.stringify({
|
|
520
512
|
rules: { "paper/stages": ["error", { x: 1 }] },
|
|
521
513
|
}),
|
|
522
|
-
"papers/p/paper.
|
|
514
|
+
"papers/p/paper.tex": "% P\n",
|
|
523
515
|
"papers/p/PIPELINE-STATUS.md": "---\nstages: []\n---\n# S\n",
|
|
524
516
|
});
|
|
525
517
|
await assert.rejects(cli(["lint"], dir), {
|
|
@@ -582,11 +574,11 @@ function newAtTerminal(cwd: string, answers: Record<string, string>) {
|
|
|
582
574
|
});
|
|
583
575
|
}
|
|
584
576
|
|
|
585
|
-
test("new at a terminal asks for the
|
|
586
|
-
const
|
|
587
|
-
const r = await newAtTerminal(
|
|
577
|
+
test("new at a terminal asks for the venue; the answer picks it", async () => {
|
|
578
|
+
const dir = writeTree(join(root, "new-tty"), { "package.json": "{}" });
|
|
579
|
+
const r = await newAtTerminal(dir, { "venue:": "none\n" });
|
|
588
580
|
assert.equal(r.status, 0, r.out);
|
|
589
|
-
assert.equal(existsSync(join(
|
|
581
|
+
assert.equal(existsSync(join(dir, "papers", "first", "paper.tex")), true);
|
|
590
582
|
});
|
|
591
583
|
|
|
592
584
|
test("chooseVenue: a question that fails (the stream ended) takes the default, no venue", async () => {
|
|
@@ -625,9 +617,9 @@ test("init's TeX Live install runs the toolchain over the project's papers, neve
|
|
|
625
617
|
assert.equal(typeof here.installed(), "boolean");
|
|
626
618
|
});
|
|
627
619
|
|
|
628
|
-
test("lint with its LaTeX language missing from the install still lints the
|
|
620
|
+
test("lint with its LaTeX language missing from the install still lints the scorecards", () => {
|
|
629
621
|
const dir = writeTree(join(root, "lint-no-tex-language"), {
|
|
630
|
-
"papers/p/
|
|
622
|
+
"papers/p/PIPELINE-STATUS.md": "---\nstages: []\n---\n# S\n",
|
|
631
623
|
"no-latex.mjs":
|
|
632
624
|
"import { register } from 'node:module';\n" +
|
|
633
625
|
"register('data:text/javascript,' + encodeURIComponent(\"export async function resolve(s, c, next) { if (s === '#eslint-rules/latex-language') throw Object.assign(new Error('gone'), { code: 'ERR_MODULE_NOT_FOUND' }); return next(s, c); }\"));\n",
|
|
@@ -637,9 +629,9 @@ test("lint with its LaTeX language missing from the install still lints the mark
|
|
|
637
629
|
nodeArgs: ["--import", join(dir, "no-latex.mjs")],
|
|
638
630
|
});
|
|
639
631
|
const whole = runNode(BIN, ["lint", "papers"], { cwd: dir });
|
|
640
|
-
// A
|
|
632
|
+
// A scorecard needs no LaTeX language: the same report, the same code.
|
|
641
633
|
assert.deepEqual(without, whole);
|
|
642
|
-
assert.match(whole.stdout, /missing `
|
|
634
|
+
assert.match(whole.stdout, /missing all of `paper\.tex`/);
|
|
643
635
|
});
|
|
644
636
|
|
|
645
637
|
test("`paperlint hook` with no name: exit 2, and the usage names an example", async () => {
|
package/src/cli.ts
CHANGED
|
@@ -132,15 +132,7 @@ import {
|
|
|
132
132
|
init,
|
|
133
133
|
processInteractivity,
|
|
134
134
|
} from "./init.ts";
|
|
135
|
-
import {
|
|
136
|
-
DEFAULT_FORMAT,
|
|
137
|
-
FORMATS,
|
|
138
|
-
isFormat,
|
|
139
|
-
newPaper,
|
|
140
|
-
reportNewPaper,
|
|
141
|
-
type PaperFormat,
|
|
142
|
-
type VenueSetting,
|
|
143
|
-
} from "./new-paper.ts";
|
|
135
|
+
import { newPaper, reportNewPaper, type VenueSetting } from "./new-paper.ts";
|
|
144
136
|
// The one source for the consumer's config key lives in lib/ (the ESLint rules and the skill scripts
|
|
145
137
|
// import it too). It is imported compiled, from dist/, the one path that resolves the same from src/
|
|
146
138
|
// and from dist/ — see CONTRIBUTING.md.
|
|
@@ -189,7 +181,7 @@ const USAGE = `paperlint — machine-checkable gates for a paper kept in git
|
|
|
189
181
|
npx paperlint init [dir] set the project up: detect the papers directory, declare it
|
|
190
182
|
in package.json, link the skills, wire the hooks into
|
|
191
183
|
.claude/settings.json, offer the CI step, report what is missing
|
|
192
|
-
npx paperlint new <name> [--venue <preset>] [--kind <kind>]
|
|
184
|
+
npx paperlint new <name> [--venue <preset>] [--kind <kind>]
|
|
193
185
|
create <papers>/<name>/ from the template; never overwrites,
|
|
194
186
|
on an existing folder adds only the missing files, then lints it.
|
|
195
187
|
A <name> that names a venue (aisec-2026) is refused: name the
|
|
@@ -225,7 +217,6 @@ init:
|
|
|
225
217
|
or with CI set, nothing is asked either
|
|
226
218
|
--no-hooks do not wire the hooks (the default without a human is to wire them)
|
|
227
219
|
--paper <name> create this paper too (without a human, the only way init creates one)
|
|
228
|
-
--format tex|md the new paper's source format; default tex
|
|
229
220
|
|
|
230
221
|
new:
|
|
231
222
|
--venue <preset> the venue preset, written as "extends" into the paper's paperlint.json:
|
|
@@ -234,7 +225,6 @@ new:
|
|
|
234
225
|
On a terminal without --venue, new asks; "none" leaves it unset
|
|
235
226
|
--kind <kind> the paper's kind at that venue (its page limit), e.g. short — one of the
|
|
236
227
|
preset's kinds; needs --venue
|
|
237
|
-
--format tex|md the paper's source format; default tex
|
|
238
228
|
--allow-venue-name create the folder even though its name names a venue (a whole word such as
|
|
239
229
|
"realm" can be the work's own). Without it such a name is refused: a rejected
|
|
240
230
|
paper moves to another venue and keeps its folder
|
|
@@ -399,8 +389,8 @@ export function buildConfig(
|
|
|
399
389
|
},
|
|
400
390
|
{
|
|
401
391
|
files: ["**/PIPELINE-STATUS.md"],
|
|
402
|
-
// Every paper folder has one
|
|
403
|
-
//
|
|
392
|
+
// Every paper folder has one: the rules about the folder itself (its stages, its frozen
|
|
393
|
+
// sources, its name) are judged here, once per paper.
|
|
404
394
|
plugins: {
|
|
405
395
|
markdown,
|
|
406
396
|
paper: {
|
|
@@ -419,12 +409,6 @@ export function buildConfig(
|
|
|
419
409
|
...CYCLE_RULE_LEVELS,
|
|
420
410
|
},
|
|
421
411
|
},
|
|
422
|
-
{
|
|
423
|
-
files: ["**/paper.md", "**/draft.md"],
|
|
424
|
-
plugins: { markdown, paper: { rules: paperRules } },
|
|
425
|
-
...md,
|
|
426
|
-
rules: prose,
|
|
427
|
-
},
|
|
428
412
|
{
|
|
429
413
|
files: ["**/reviews/*.md"],
|
|
430
414
|
plugins: { markdown, review: reviewFrontmatter },
|
|
@@ -585,8 +569,7 @@ export interface RulePlugin {
|
|
|
585
569
|
* file knows every rule id. `@eslint/markdown` is a dependency's plugin and is left out. The LaTeX
|
|
586
570
|
* language rides on `tex` when `texLanguage` is given, and that is how a consumer registers it: a
|
|
587
571
|
* second `tex` plugin of its own is refused by ESLint ("Cannot redefine plugin "tex"", measured
|
|
588
|
-
* with and without the language here). Without it, `tex` carries rules only
|
|
589
|
-
* lints markdown papers alone.
|
|
572
|
+
* with and without the language here). Without it, `tex` carries rules only.
|
|
590
573
|
*/
|
|
591
574
|
export function rulePlugins(texLanguage?: unknown): Record<string, RulePlugin> {
|
|
592
575
|
const out: Record<string, { rules: Record<string, unknown> }> = {};
|
|
@@ -888,7 +871,6 @@ export function parseArgs(argv: readonly string[]): Args {
|
|
|
888
871
|
noHooks: false,
|
|
889
872
|
allowVenueName: false,
|
|
890
873
|
paper: null,
|
|
891
|
-
format: null,
|
|
892
874
|
venue: null,
|
|
893
875
|
kind: null,
|
|
894
876
|
pdf: null,
|
|
@@ -987,10 +969,9 @@ const SWITCHES: ReadonlyMap<
|
|
|
987
969
|
/** The flags whose value is one string field of `Args`. */
|
|
988
970
|
const STRING_FLAGS: ReadonlyMap<
|
|
989
971
|
string,
|
|
990
|
-
"paper" | "
|
|
972
|
+
"paper" | "venue" | "kind" | "pdf" | "abstract"
|
|
991
973
|
> = new Map([
|
|
992
974
|
["--paper", "paper"],
|
|
993
|
-
["--format", "format"],
|
|
994
975
|
["--venue", "venue"],
|
|
995
976
|
["--kind", "kind"],
|
|
996
977
|
["--pdf", "pdf"],
|
|
@@ -1184,7 +1165,6 @@ export function runHook(
|
|
|
1184
1165
|
export async function createPaperAt(
|
|
1185
1166
|
papersRoot: string,
|
|
1186
1167
|
name: string,
|
|
1187
|
-
format: PaperFormat,
|
|
1188
1168
|
{
|
|
1189
1169
|
log,
|
|
1190
1170
|
err,
|
|
@@ -1201,7 +1181,7 @@ export async function createPaperAt(
|
|
|
1201
1181
|
allowVenueName?: boolean;
|
|
1202
1182
|
},
|
|
1203
1183
|
): Promise<number> {
|
|
1204
|
-
const result = newPaper(papersRoot, name,
|
|
1184
|
+
const result = newPaper(papersRoot, name, {
|
|
1205
1185
|
venue,
|
|
1206
1186
|
venues: allowVenueName ? [] : shippedVenueNames(PRESET_DEPS),
|
|
1207
1187
|
});
|
|
@@ -1366,25 +1346,10 @@ async function runNew(
|
|
|
1366
1346
|
const [name, ...extra] = a.paths;
|
|
1367
1347
|
if (!name || extra.length > 0) {
|
|
1368
1348
|
err(
|
|
1369
|
-
`\`new\` takes exactly one paper name: \`paperlint new my-paper [--venue <preset>] [--kind <kind>]
|
|
1349
|
+
`\`new\` takes exactly one paper name: \`paperlint new my-paper [--venue <preset>] [--kind <kind>]\``,
|
|
1370
1350
|
);
|
|
1371
1351
|
return 2;
|
|
1372
1352
|
}
|
|
1373
|
-
let format: PaperFormat = DEFAULT_FORMAT;
|
|
1374
|
-
if (a.format !== null) {
|
|
1375
|
-
if (!isFormat(a.format)) {
|
|
1376
|
-
err(
|
|
1377
|
-
`--format must be one of ${FORMATS.join(", ")} — got \`${a.format}\``,
|
|
1378
|
-
);
|
|
1379
|
-
return 2;
|
|
1380
|
-
}
|
|
1381
|
-
format = a.format;
|
|
1382
|
-
} else if (processInteractivity(a.yes).interactive) {
|
|
1383
|
-
const f = (
|
|
1384
|
-
await askOrDefault(ask, `format: tex / md [${DEFAULT_FORMAT}] `)
|
|
1385
|
-
)?.trim();
|
|
1386
|
-
if (isFormat(f)) format = f;
|
|
1387
|
-
}
|
|
1388
1353
|
const cfg = readConfig({ ...a, json: false }, { log: () => {}, err, cwd });
|
|
1389
1354
|
if (cfg.code !== undefined) return cfg.code;
|
|
1390
1355
|
const roots = papersRoots(cfg);
|
|
@@ -1417,7 +1382,7 @@ async function runNew(
|
|
|
1417
1382
|
ask,
|
|
1418
1383
|
});
|
|
1419
1384
|
if (!venue.ok) return (err(venue.error), 2);
|
|
1420
|
-
return createPaperAt(papersRoot, name,
|
|
1385
|
+
return createPaperAt(papersRoot, name, {
|
|
1421
1386
|
log,
|
|
1422
1387
|
err,
|
|
1423
1388
|
cwd,
|
|
@@ -1668,10 +1633,6 @@ async function runInit(
|
|
|
1668
1633
|
);
|
|
1669
1634
|
return 2;
|
|
1670
1635
|
}
|
|
1671
|
-
if (a.format !== null && !isFormat(a.format)) {
|
|
1672
|
-
err(`--format must be one of ${FORMATS.join(", ")} — got \`${a.format}\``);
|
|
1673
|
-
return 2;
|
|
1674
|
-
}
|
|
1675
1636
|
return await init(a.paths[0] ?? ".", {
|
|
1676
1637
|
log,
|
|
1677
1638
|
err,
|
|
@@ -1679,9 +1640,8 @@ async function runInit(
|
|
|
1679
1640
|
yes: a.yes,
|
|
1680
1641
|
hooks: !a.noHooks,
|
|
1681
1642
|
paper: a.paper,
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
createPaperAt(papersRoot, name, format, { log, err, cwd }),
|
|
1643
|
+
createPaper: (papersRoot, name) =>
|
|
1644
|
+
createPaperAt(papersRoot, name, { log, err, cwd }),
|
|
1685
1645
|
venues: shippedVenueNames(PRESET_DEPS),
|
|
1686
1646
|
tex: initTexLive(a, { log, err, cwd }),
|
|
1687
1647
|
resolveCliPapers: (root: string): string | null =>
|
|
@@ -1803,8 +1763,8 @@ export async function run(
|
|
|
1803
1763
|
};
|
|
1804
1764
|
|
|
1805
1765
|
// Loaded lazily because only `lint` needs it. If the module cannot be loaded (a broken install:
|
|
1806
|
-
// the file missing from the package), lint still runs over the
|
|
1807
|
-
// failing outright — the LaTeX rules simply have no language to run in.
|
|
1766
|
+
// the file missing from the package), lint still runs over the scorecards and the review notes
|
|
1767
|
+
// rather than failing outright — the LaTeX rules simply have no language to run in.
|
|
1808
1768
|
let texLanguage: unknown = null;
|
|
1809
1769
|
try {
|
|
1810
1770
|
({ texLanguage } = await import("#eslint-rules/latex-language"));
|
|
@@ -1870,7 +1830,7 @@ export async function run(
|
|
|
1870
1830
|
// is not invoked — and, not being invoked, it physically cannot report that.
|
|
1871
1831
|
if (results.length === 0) {
|
|
1872
1832
|
err(
|
|
1873
|
-
`nothing was linted under ${paths.map((x) => shown(cwd, x)).join(", ")} — no PIPELINE-STATUS.md, paper.
|
|
1833
|
+
`nothing was linted under ${paths.map((x) => shown(cwd, x)).join(", ")} — no PIPELINE-STATUS.md, paper.tex or reviews/ found there. A clean report over zero files is not a clean report.`,
|
|
1874
1834
|
);
|
|
1875
1835
|
return 1;
|
|
1876
1836
|
}
|
|
@@ -160,7 +160,7 @@ describe("paper/folder-venue-leftover — silent", () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
it("judged once per paper, on PIPELINE-STATUS.md only", () => {
|
|
163
|
-
expect(lint("aisec-2026", AIDC, { file: "paper.
|
|
163
|
+
expect(lint("aisec-2026", AIDC, { file: "paper.tex" })).toEqual([]);
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
it("a paper on a bare template family declares no venue: a venue in its folder may be its own", () => {
|
package/src/init.test.ts
CHANGED
|
@@ -55,8 +55,8 @@ function project(): string {
|
|
|
55
55
|
"paperlint.json": JSON.stringify({ papersDir: "papers" }),
|
|
56
56
|
"papers/a/PIPELINE-STATUS.md": "---\nstages: []\n---\n",
|
|
57
57
|
"eslint-rules/fixtures/x/PIPELINE-STATUS.md": "---\nstages: []\n---\n",
|
|
58
|
-
"eslint-rules/fixtures/y/paper.
|
|
59
|
-
"drafts/z/paper.
|
|
58
|
+
"eslint-rules/fixtures/y/paper.tex": "% y\n",
|
|
59
|
+
"drafts/z/paper.tex": "% z\n",
|
|
60
60
|
};
|
|
61
61
|
for (const [p, text] of Object.entries(files)) {
|
|
62
62
|
mkdirSync(join(root, p, ".."), { recursive: true });
|
|
@@ -291,7 +291,7 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
291
291
|
for (const json of ["null\n", "[1]\n"]) {
|
|
292
292
|
const root = tree({
|
|
293
293
|
"paperlint.json": json,
|
|
294
|
-
"drafts/a/paper.
|
|
294
|
+
"drafts/a/paper.tex": "% a\n",
|
|
295
295
|
});
|
|
296
296
|
const decl = await declarePapers(root, () =>
|
|
297
297
|
choosePapers(root, { interactive: false }),
|
|
@@ -308,7 +308,7 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
308
308
|
it("a file without a trailing newline keeps having none", async () => {
|
|
309
309
|
const root = tree({
|
|
310
310
|
"paperlint.json": '{"x":1}',
|
|
311
|
-
"drafts/a/paper.
|
|
311
|
+
"drafts/a/paper.tex": "% a\n",
|
|
312
312
|
});
|
|
313
313
|
const decl = await declarePapers(root, () =>
|
|
314
314
|
choosePapers(root, { interactive: false }),
|
|
@@ -322,7 +322,7 @@ describe("paperlint init — a paperlint.json it cannot use stops it before anyt
|
|
|
322
322
|
|
|
323
323
|
describe("paperlint init — how several candidate directories were decided, said", () => {
|
|
324
324
|
const several = () =>
|
|
325
|
-
tree({ "drafts/a/paper.
|
|
325
|
+
tree({ "drafts/a/paper.tex": "% a\n", "writing/b/paper.tex": "% b\n" });
|
|
326
326
|
const section = (out: string[]) =>
|
|
327
327
|
out.slice(out.indexOf("papers directory") + 1, out.indexOf("settings") - 1);
|
|
328
328
|
|
|
@@ -530,7 +530,6 @@ describe("paperlint init — a first paper named after a venue", () => {
|
|
|
530
530
|
createPaper: (_root, name) => Promise.resolve((made.push(name), 0)),
|
|
531
531
|
});
|
|
532
532
|
expect(made).toEqual([]);
|
|
533
|
-
expect(asked.filter((q) => /format/.test(q))).toEqual([]);
|
|
534
533
|
expect(r.out[r.out.indexOf("first paper") + 1]).toBe(
|
|
535
534
|
' ✗ no paper created — `aisec-2026` — the paper folder names a venue ("AISec"); venues change on resubmission, so name it after the work\n if the venue really belongs in the name, rerun `paperlint new` with --allow-venue-name',
|
|
536
535
|
);
|
|
@@ -539,7 +538,7 @@ describe("paperlint init — a first paper named after a venue", () => {
|
|
|
539
538
|
|
|
540
539
|
describe("paperlint init — paths are shown relative to where it was run", () => {
|
|
541
540
|
it("a path that IS the working directory is shown whole, not as an empty string", async () => {
|
|
542
|
-
const root = tree({ "papers/a/paper.
|
|
541
|
+
const root = tree({ "papers/a/paper.tex": "% a\n" });
|
|
543
542
|
const home = join(root, ".claude", "skills");
|
|
544
543
|
mkdirSync(home, { recursive: true });
|
|
545
544
|
const r = await initRun(root, {
|