paperlint 7.1.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.
Files changed (261) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/.github/workflows/deadlines.yml +100 -0
  3. package/README.md +6 -0
  4. package/dist/adapters/hotcrp/deadlines.d.ts +8 -0
  5. package/dist/adapters/hotcrp/deadlines.d.ts.map +1 -0
  6. package/dist/adapters/hotcrp/deadlines.io.d.ts +6 -0
  7. package/dist/adapters/hotcrp/deadlines.io.d.ts.map +1 -0
  8. package/dist/adapters/hotcrp/deadlines.io.js +26 -0
  9. package/dist/adapters/hotcrp/deadlines.io.js.map +1 -0
  10. package/dist/adapters/hotcrp/deadlines.js +134 -0
  11. package/dist/adapters/hotcrp/deadlines.js.map +1 -0
  12. package/dist/adapters/hotcrp/index.d.ts +2 -0
  13. package/dist/adapters/hotcrp/index.d.ts.map +1 -1
  14. package/dist/adapters/hotcrp/index.js +2 -0
  15. package/dist/adapters/hotcrp/index.js.map +1 -1
  16. package/dist/build.d.ts.map +1 -1
  17. package/dist/build.js +1 -6
  18. package/dist/build.js.map +1 -1
  19. package/dist/cli.d.ts +3 -4
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +53 -47
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cycle-rules.d.ts +24 -0
  24. package/dist/cycle-rules.d.ts.map +1 -0
  25. package/dist/cycle-rules.js +212 -0
  26. package/dist/cycle-rules.js.map +1 -0
  27. package/dist/domain/cycle.d.ts +148 -0
  28. package/dist/domain/cycle.d.ts.map +1 -0
  29. package/dist/domain/cycle.js +281 -0
  30. package/dist/domain/cycle.js.map +1 -0
  31. package/dist/domain/deadline.d.ts +128 -0
  32. package/dist/domain/deadline.d.ts.map +1 -0
  33. package/dist/domain/deadline.js +281 -0
  34. package/dist/domain/deadline.js.map +1 -0
  35. package/dist/eslint-rules/cycle-stage.d.ts +39 -0
  36. package/dist/eslint-rules/cycle-stage.d.ts.map +1 -0
  37. package/dist/eslint-rules/cycle-stage.js +119 -0
  38. package/dist/eslint-rules/cycle-stage.js.map +1 -0
  39. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  40. package/dist/eslint-rules/latex-language.js +10 -13
  41. package/dist/eslint-rules/latex-language.js.map +1 -1
  42. package/dist/eslint-rules/paper-context.js +2 -2
  43. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  44. package/dist/eslint-rules/paper-typography.js +22 -91
  45. package/dist/eslint-rules/paper-typography.js.map +1 -1
  46. package/dist/eslint-rules/papers.d.ts +1 -3
  47. package/dist/eslint-rules/papers.d.ts.map +1 -1
  48. package/dist/eslint-rules/papers.js +2 -4
  49. package/dist/eslint-rules/papers.js.map +1 -1
  50. package/dist/eslint-rules/tex-build.d.ts.map +1 -1
  51. package/dist/eslint-rules/tex-build.js +8 -25
  52. package/dist/eslint-rules/tex-build.js.map +1 -1
  53. package/dist/init.d.ts +1 -4
  54. package/dist/init.d.ts.map +1 -1
  55. package/dist/init.js +3 -9
  56. package/dist/init.js.map +1 -1
  57. package/dist/lib/markdown.d.ts +5 -81
  58. package/dist/lib/markdown.d.ts.map +1 -1
  59. package/dist/lib/markdown.js +5 -116
  60. package/dist/lib/markdown.js.map +1 -1
  61. package/dist/lib/paper-config.d.ts.map +1 -1
  62. package/dist/lib/paper-config.js +2 -2
  63. package/dist/lib/paper-config.js.map +1 -1
  64. package/dist/lib/skill-eval-fixture.d.ts +6 -4
  65. package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
  66. package/dist/lib/skill-eval-fixture.js +25 -18
  67. package/dist/lib/skill-eval-fixture.js.map +1 -1
  68. package/dist/new-paper.d.ts +5 -11
  69. package/dist/new-paper.d.ts.map +1 -1
  70. package/dist/new-paper.js +12 -23
  71. package/dist/new-paper.js.map +1 -1
  72. package/dist/paper-files.d.ts +4 -4
  73. package/dist/paper-prose.d.ts +17 -0
  74. package/dist/paper-prose.d.ts.map +1 -0
  75. package/dist/paper-prose.js +46 -0
  76. package/dist/paper-prose.js.map +1 -0
  77. package/dist/paper-settings.d.ts +7 -0
  78. package/dist/paper-settings.d.ts.map +1 -1
  79. package/dist/paper-settings.js +61 -8
  80. package/dist/paper-settings.js.map +1 -1
  81. package/dist/ports/venue-deadlines.d.ts +14 -0
  82. package/dist/ports/venue-deadlines.d.ts.map +1 -0
  83. package/dist/ports/venue-deadlines.js +2 -0
  84. package/dist/ports/venue-deadlines.js.map +1 -0
  85. package/dist/presets.d.ts +12 -0
  86. package/dist/presets.d.ts.map +1 -1
  87. package/dist/presets.js +19 -11
  88. package/dist/presets.js.map +1 -1
  89. package/dist/structure.d.ts.map +1 -1
  90. package/dist/structure.js +4 -3
  91. package/dist/structure.js.map +1 -1
  92. package/dist/submission.d.ts +8 -0
  93. package/dist/submission.d.ts.map +1 -1
  94. package/dist/submission.js +41 -10
  95. package/dist/submission.js.map +1 -1
  96. package/dist/tex-requirements.d.ts +3 -0
  97. package/dist/tex-requirements.d.ts.map +1 -1
  98. package/dist/tex-requirements.js +19 -0
  99. package/dist/tex-requirements.js.map +1 -1
  100. package/dist/tex-venue-rules.d.ts +19 -0
  101. package/dist/tex-venue-rules.d.ts.map +1 -1
  102. package/dist/tex-venue-rules.js +69 -0
  103. package/dist/tex-venue-rules.js.map +1 -1
  104. package/dist/types.d.ts +0 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/venue-rules.d.ts +10 -0
  107. package/dist/venue-rules.d.ts.map +1 -1
  108. package/dist/venue-rules.js +26 -10
  109. package/dist/venue-rules.js.map +1 -1
  110. package/docs/configuration.md +146 -20
  111. package/docs/e2e.md +4 -6
  112. package/docs/prior-art/paper-folder-scaffolding.md +24 -29
  113. package/docs/rules/cycle/evidence.md +87 -0
  114. package/docs/rules/cycle/port-done.md +75 -0
  115. package/docs/rules/cycle/record.md +152 -0
  116. package/docs/rules/cycle/stage.md +85 -0
  117. package/docs/rules/tex/claim-provenance.md +0 -1
  118. package/docs/rules.md +48 -47
  119. package/docs/submission.md +5 -0
  120. package/eslint-rules/cycle-stage.test.mjs +131 -0
  121. package/eslint-rules/cycle-stage.ts +141 -0
  122. package/eslint-rules/latex-language.harness.mjs +3 -3
  123. package/eslint-rules/latex-language.ts +10 -13
  124. package/eslint-rules/paper-context.ts +2 -2
  125. package/eslint-rules/paper-typography.test.mjs +17 -85
  126. package/eslint-rules/paper-typography.ts +22 -98
  127. package/eslint-rules/papers.harness.mjs +1 -1
  128. package/eslint-rules/papers.ts +2 -4
  129. package/eslint-rules/tex-build.harness.mjs +2 -2
  130. package/eslint-rules/tex-build.ts +8 -25
  131. package/eslint.config.mjs +41 -2
  132. package/hooks/hooks.harness.ts +15 -15
  133. package/hooks/paper-edit-guard.hook.mjs +5 -8
  134. package/hooks/paper-edit-guard.test.ts +12 -3
  135. package/hooks/paper-skills-nudge.hook.mjs +2 -3
  136. package/hooks/paper-skills-nudge.test.ts +13 -3
  137. package/hooks/paper-status-gates.hook.mjs +2 -2
  138. package/hooks/paper-status-gates.test.ts +14 -2
  139. package/lib/markdown.test.mjs +5 -60
  140. package/lib/markdown.ts +9 -136
  141. package/lib/paper-config.ts +3 -2
  142. package/lib/skill-checks.test.mjs +4 -1
  143. package/lib/skill-eval-fixture.ts +25 -18
  144. package/package.json +1 -3
  145. package/presets/aidc.jsonc +24 -3
  146. package/presets/aidc.md +6 -3
  147. package/presets/msr.jsonc +25 -1
  148. package/presets/msr.md +5 -1
  149. package/presets/realm.jsonc +1 -1
  150. package/presets/venue-profile.schema.json +39 -0
  151. package/scripts/check.ts +4 -0
  152. package/scripts/harness-api.frozen.json +0 -5
  153. package/scripts/refresh-deadlines.test.ts +431 -0
  154. package/scripts/refresh-deadlines.ts +387 -0
  155. package/skills/cold-read-diff/SKILL.md +3 -3
  156. package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
  157. package/skills/grade-paper-writing/SKILL.md +14 -22
  158. package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
  159. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
  160. package/skills/grade-paper-writing/prose-lint.mjs +108 -278
  161. package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
  162. package/skills/paper-adversarial-review/SKILL.md +2 -2
  163. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  164. package/skills/paper-pipeline/SKILL.md +9 -12
  165. package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
  166. package/skills/paper-pipeline/description-language.eval.mjs +25 -15
  167. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
  168. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
  169. package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
  170. package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
  171. package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
  172. package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
  173. package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
  174. package/skills/paper-pipeline/scripts/README.md +0 -4
  175. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
  176. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
  177. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
  178. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
  179. package/skills/paper-status/SKILL.md +2 -2
  180. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  181. package/skills/render-paper/SKILL.md +2 -2
  182. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  183. package/skills/render-paper/extract-pdf-facts.mjs +2 -2
  184. package/skills/tighten-paper/SKILL.md +29 -42
  185. package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
  186. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
  187. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
  188. package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
  189. package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
  190. package/src/adapters/hotcrp/cassette/README.md +22 -0
  191. package/src/adapters/hotcrp/cassette/deadlines-aidc26.html +17 -0
  192. package/src/adapters/hotcrp/cassette/deadlines-fse2027.html +24 -0
  193. package/src/adapters/hotcrp/cassette/deadlines-icse2027.html +25 -0
  194. package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +25 -0
  195. package/src/adapters/hotcrp/cassette/deadlines-nosuch.html +18 -0
  196. package/src/adapters/hotcrp/cassette/deadlines-sosp26.html +26 -0
  197. package/src/adapters/hotcrp/deadlines.io.test.ts +86 -0
  198. package/src/adapters/hotcrp/deadlines.io.ts +27 -0
  199. package/src/adapters/hotcrp/deadlines.test.ts +170 -0
  200. package/src/adapters/hotcrp/deadlines.ts +161 -0
  201. package/src/adapters/hotcrp/index.ts +2 -0
  202. package/src/build.harness.mjs +2 -2
  203. package/src/build.test.ts +1 -1
  204. package/src/build.ts +1 -6
  205. package/src/cli.harness.mjs +26 -46
  206. package/src/cli.test.ts +41 -40
  207. package/src/cli.ts +57 -58
  208. package/src/cycle-rules.test.ts +278 -0
  209. package/src/cycle-rules.ts +273 -0
  210. package/src/domain/cycle.test.ts +456 -0
  211. package/src/domain/cycle.ts +432 -0
  212. package/src/domain/deadline.test.ts +386 -0
  213. package/src/domain/deadline.ts +472 -0
  214. package/src/folder-venue-rule.test.ts +1 -1
  215. package/src/init.test.ts +6 -7
  216. package/src/init.ts +3 -22
  217. package/src/lint-fix.test.ts +10 -4
  218. package/src/new-paper.harness.mjs +9 -28
  219. package/src/new-paper.test.ts +7 -6
  220. package/src/new-paper.ts +11 -30
  221. package/src/paper-files.test.ts +5 -4
  222. package/src/paper-files.ts +4 -4
  223. package/src/paper-prose.test.ts +49 -0
  224. package/src/paper-prose.ts +67 -0
  225. package/src/paper-settings.test.ts +167 -0
  226. package/src/paper-settings.ts +85 -11
  227. package/src/parse-args.test.ts +0 -1
  228. package/src/ports/venue-deadlines.ts +14 -0
  229. package/src/presets.test.ts +105 -0
  230. package/src/presets.ts +43 -10
  231. package/src/rule-cases.test.ts +144 -0
  232. package/src/structure.harness.ts +5 -11
  233. package/src/structure.ts +4 -3
  234. package/src/submission.test.ts +105 -1
  235. package/src/submission.ts +73 -12
  236. package/src/tex-requirements.ts +31 -0
  237. package/src/tex-venue-rules.test.ts +76 -0
  238. package/src/tex-venue-rules.ts +85 -0
  239. package/src/types.ts +0 -2
  240. package/src/venue-rules.test.ts +82 -0
  241. package/src/venue-rules.ts +44 -12
  242. package/fixtures/real-markdown-paper/baseline.json +0 -22
  243. package/fixtures/real-markdown-paper/baseline.ts +0 -70
  244. package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
  245. package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
  246. package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
  247. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
  248. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
  249. package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
  250. package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
  251. package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
  252. package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
  253. package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
  254. package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
  255. package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
  256. package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
  257. package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
  258. package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
  259. package/skills/tighten-paper/structure.mjs +0 -364
  260. package/skills/tighten-paper/structure.test.ts +0 -117
  261. package/templates/paper/paper.md +0 -4
@@ -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 === 4,
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 === 6,
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.md"), "# Intro\n\nRQ1: does it hold?\n");
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.md"), "# Intro\n\nRQ1: does it hold?\n");
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.md"), "# Intro\n\nRQ1: does it hold?\n");
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.md"),
901
- "# Intro\n\nRQ1: does it hold?\n\nSee \u00a7 5 and \u00a7 6 and \u00a7 7.\n",
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.md"), "# T\n\nSee § 3 and §4.\n");
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.md"), "# P\n");
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, format) => {
1433
- made.push({ papersRoot, name, format });
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 and format reach `paperlint new`'s routine",
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, format) => (
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, in the default format",
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.md"), "# P\n");
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.md"), "# P\n");
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.md"), "# P\n");
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", "--format", "md"], root);
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
- !existsSync(join(root, "writing", "demo", "paper.md")) &&
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", "--format"]),
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: "--format" },
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 an unknown format, a broken config, and a config that names no papers directory", async () => {
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", "--format", "docx"], root),
152
- await cli(["new", "p", "--format", "md"], broken),
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", "--format", "md", "--yes"], dir);
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/md/paper.md": "# P\n\nx\n",
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 markdown-only paper is refused", async () => {
284
- const md = await withoutTex(noTexDir, () =>
285
- cli(["build", "papers/md"], noTexDir),
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(md, {
285
+ assert.deepEqual(none, {
288
286
  code: 1,
289
287
  out: [
290
- "papers/md",
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/md.\n" +
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
  });
@@ -344,6 +342,15 @@ test("no command prints the usage and exits 2; --kind with no value is refused",
344
342
  assert.equal(parseArgs(["new", "x", "--kind"]).missingValue, "--kind");
345
343
  });
346
344
 
345
+ test("--help names the cycles and where a deadline comes from — the only place a newcomer looks first", async () => {
346
+ const r = await cli(["--help"], root);
347
+ assert.equal(r.code, 0);
348
+ assert.match(r.out, /"cycles": \[ \{ "id"/);
349
+ assert.match(r.out, /A venue preset carries the portal's deadlines/);
350
+ assert.match(r.out, /"override": \{ "at", "reason", "evidence" \}/);
351
+ assert.match(r.out, /the earlier reading binds/);
352
+ });
353
+
347
354
  test("src/cli.ts run as the program itself answers like the bin", () => {
348
355
  const r = runNode(join(import.meta.dirname, "cli.ts"), ["--help"]);
349
356
  assert.equal(r.status, 0);
@@ -390,7 +397,7 @@ test("build --dry-run on a paper with a venue plans for the preset's packages",
390
397
 
391
398
  test("a paper's paperlint.json that does not parse stops the lint with its path", () => {
392
399
  const dir = writeTree(join(root, "rules-broken"), {
393
- "p/paper.md": "# P\n",
400
+ "p/paper.tex": "% P\n",
394
401
  "p/paperlint.json": "{ nope",
395
402
  });
396
403
  const r = paperRuleBlocks([join(dir, "p")]);
@@ -435,15 +442,12 @@ test("silentOptionalRules: a rule no block names for any paper is silent — a g
435
442
  );
436
443
  });
437
444
 
438
- test("init with no path and --format md sets up the current directory", async () => {
445
+ test("init with no path sets up the current directory, and its first paper is paper.tex", async () => {
439
446
  const dir = writeTree(join(root, "init-here"), { "package.json": "{}" });
440
- const r = await cli(
441
- ["init", "--yes", "--no-hooks", "--paper", "first", "--format", "md"],
442
- dir,
443
- );
444
- assert.equal(
445
- existsSync(join(dir, "papers", "first", "paper.md")),
446
- 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"],
447
451
  r.out + r.err,
448
452
  );
449
453
  });
@@ -479,12 +483,9 @@ test("lint: a named directory with nothing paperlint lints is named in full when
479
483
  test("new from inside the paper it names adds the missing files and names the paper in full", async () => {
480
484
  const dir = writeTree(join(root, "new-inside"), {
481
485
  "paperlint.json": JSON.stringify({ papersDir: "papers" }),
482
- "papers/p/paper.md": "# P\n",
486
+ "papers/p/paper.tex": "% P\n",
483
487
  });
484
- const r = await cli(
485
- ["new", "p", "--format", "md", "--yes"],
486
- join(dir, "papers", "p"),
487
- );
488
+ const r = await cli(["new", "p", "--yes"], join(dir, "papers", "p"));
488
489
  assert.match(
489
490
  r.out,
490
491
  new RegExp(
@@ -498,7 +499,7 @@ test("new with several directories, run from the first, names it in full", async
498
499
  "paperlint.json": JSON.stringify({ papersDir: ["a", "b"] }),
499
500
  "a/.keep": "",
500
501
  });
501
- const r = await cli(["new", "p", "--format", "md", "--yes"], join(dir, "a"));
502
+ const r = await cli(["new", "p", "--yes"], join(dir, "a"));
502
503
  assert.equal(
503
504
  r.out.split("\n")[0],
504
505
  `several papers directories are declared — using the first: ${join(dir, "a")}`,
@@ -510,7 +511,7 @@ test("lint: an ESLint failure that is not an empty set is not swallowed", async
510
511
  "paperlint.json": JSON.stringify({
511
512
  rules: { "paper/stages": ["error", { x: 1 }] },
512
513
  }),
513
- "papers/p/paper.md": "# P\n",
514
+ "papers/p/paper.tex": "% P\n",
514
515
  "papers/p/PIPELINE-STATUS.md": "---\nstages: []\n---\n# S\n",
515
516
  });
516
517
  await assert.rejects(cli(["lint"], dir), {
@@ -573,11 +574,11 @@ function newAtTerminal(cwd: string, answers: Record<string, string>) {
573
574
  });
574
575
  }
575
576
 
576
- test("new at a terminal asks for the format and the venue; the answers pick them", async () => {
577
- const md = writeTree(join(root, "new-tty-md"), { "package.json": "{}" });
578
- const r = await newAtTerminal(md, { "format:": "md\n", "venue:": "none\n" });
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" });
579
580
  assert.equal(r.status, 0, r.out);
580
- assert.equal(existsSync(join(md, "papers", "first", "paper.md")), true);
581
+ assert.equal(existsSync(join(dir, "papers", "first", "paper.tex")), true);
581
582
  });
582
583
 
583
584
  test("chooseVenue: a question that fails (the stream ended) takes the default, no venue", async () => {
@@ -616,9 +617,9 @@ test("init's TeX Live install runs the toolchain over the project's papers, neve
616
617
  assert.equal(typeof here.installed(), "boolean");
617
618
  });
618
619
 
619
- test("lint with its LaTeX language missing from the install still lints the markdown papers", () => {
620
+ test("lint with its LaTeX language missing from the install still lints the scorecards", () => {
620
621
  const dir = writeTree(join(root, "lint-no-tex-language"), {
621
- "papers/p/paper.md": "# P\n\nSome prose.\n",
622
+ "papers/p/PIPELINE-STATUS.md": "---\nstages: []\n---\n# S\n",
622
623
  "no-latex.mjs":
623
624
  "import { register } from 'node:module';\n" +
624
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",
@@ -628,9 +629,9 @@ test("lint with its LaTeX language missing from the install still lints the mark
628
629
  nodeArgs: ["--import", join(dir, "no-latex.mjs")],
629
630
  });
630
631
  const whole = runNode(BIN, ["lint", "papers"], { cwd: dir });
631
- // A markdown paper needs no LaTeX language: the same report, the same code.
632
+ // A scorecard needs no LaTeX language: the same report, the same code.
632
633
  assert.deepEqual(without, whole);
633
- assert.match(whole.stdout, /missing `PIPELINE-STATUS\.md`/);
634
+ assert.match(whole.stdout, /missing all of `paper\.tex`/);
634
635
  });
635
636
 
636
637
  test("`paperlint hook` with no name: exit 2, and the usage names an example", async () => {