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
@@ -16,10 +16,6 @@ still left to a human copying something by hand?**
16
16
  > - `--hooks=local` is **deferred, not implemented**; `init` refuses it by name.
17
17
  >
18
18
  > Where the build differed from this text, it is listed at the end, in § 8.
19
- >
20
- > **Later, 2026-09-24:** Markdown papers are deprecated and being removed
21
- > ([#57](https://github.com/zernie/research-paper-pipeline/issues/57)). Where this record mentions
22
- > `paper.md` or `--format md`, it describes the tool as it was when written.
23
19
 
24
20
  1. A paper folder needs a `PIPELINE-STATUS.md`, and today nothing makes it — you copy a template.
25
21
  Should there be a command, and where should per-paper settings live: in a file in every paper
@@ -66,11 +62,11 @@ changes, no migration.
66
62
  (`src/init.ts:448`), offers a CI workflow, reports missing programs, and **prints** the two
67
63
  `/plugin` lines (`src/init.ts:257-258`). It never creates the papers directory and never creates
68
64
  a paper.
69
- - **A directory is a paper** if it holds any of `PIPELINE-STATUS.md`, `paper.tex`, `paper.md`,
70
- `venue.json` (`src/build.ts:41-46`, `src/structure.ts:39`). Detection is generous.
71
- - **`rpp lint` requires `PIPELINE-STATUS.md`** in every such directory, plus one of
72
- `paper.tex`/`paper.md` (`src/structure.ts:38-43`). A paper with only `paper.md` fails the very
73
- first `rpp lint` with `missing PIPELINE-STATUS.md` — that is the README's own "First run" example.
65
+ - **A directory is a paper** if it holds any of `PIPELINE-STATUS.md`, `paper.tex`, `venue.json`
66
+ (`src/build.ts`, `src/structure.ts`). Detection is generous.
67
+ - **`rpp lint` requires `PIPELINE-STATUS.md`** in every such directory, plus `paper.tex`
68
+ (`src/structure.ts`). A paper with only `paper.tex` fails the very first `rpp lint` with
69
+ `missing PIPELINE-STATUS.md` — that is the README's own "First run" example.
74
70
 
75
71
  ⚠️ **One inconsistency worth knowing.** `paper/stages` says in its header that a missing `stages`
76
72
  field is _legitimate_ — "a paper that has shipped nothing owes nothing" (`eslint-rules/paper-stages.mjs:24-27`) —
@@ -101,7 +97,7 @@ human to copy and tick them.
101
97
  ## 2. What `PIPELINE-STATUS.md` actually holds — the split
102
98
 
103
99
  Read from the template (`skills/paper-pipeline/references/pipeline-status-template.md`), the
104
- fixtures (`fixtures/paper-stages/*`, `fixtures/real-markdown-paper/`) and four real files in the
100
+ fixtures (`fixtures/paper-stages/*`) and four real files in the
105
101
  owner's corpus. Real sizes: 86, 95, 1 057 and 1 301 lines. In the two big ones the machine-read
106
102
  part is the first 10–25 lines of front matter; the rest is a dated journal of decisions. (No
107
103
  content of those files is reproduced here.)
@@ -110,7 +106,6 @@ content of those files is reproduced here.)
110
106
  | ------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
111
107
  | paper short name / title | **config** | human, once — `rpp new` can prefill from the folder name |
112
108
  | venue, deadline, blind model (the `Venue:` header line) | **config** | human; changes when the paper is re-targeted |
113
- | format (`.tex` vs `.md`) | **derived** | from which source file exists |
114
109
  | venue profile, paper kind, pdf path (a separate `venue.json` in one real paper) | **config** | human; already a per-paper file in practice |
115
110
  | typography allowance (`typographyDebt`) | **config** | human, but it sits in the root key keyed by path today |
116
111
  | `stages[]`: `stage`, `date`, `venue` | **state** | **tool** at the moment of freezing (`rpp freeze`, a later step) |
@@ -172,9 +167,9 @@ parsing markdown — the fix is to ship the template as its own file and have th
172
167
  (entry front matter), Cargo (member `Cargo.toml`), Eleventy (directory data). The tools that put
173
168
  per-unit settings in the root (ESLint `files`, Vale sections) are _linters of code_, where units
174
169
  have no identity of their own — a paper does.
175
- 3. **Front matter of the source itself is not available to rpp**, because half the papers are
176
- LaTeX, which has no YAML front matter. Quarto and Astro can use the document's own header only
177
- because every document is markdown. So rpp needs a per-paper **file**, and it already has one:
170
+ 3. **Front matter of the source itself is not available to rpp**, because a paper is LaTeX, which
171
+ has no YAML front matter. Quarto and Astro can use the document's own header only because their
172
+ documents carry one. So rpp needs a per-paper **file**, and it already has one:
178
173
  `PIPELINE-STATUS.md`, whose front matter is exactly where Quarto would put these fields.
179
174
  4. **Root-level keys that are really per-paper** (`typographyDebt: { "papers/my-paper": … }`,
180
175
  `src/cli.ts:104`) are the ESLint-overrides style. They work, but they break silently on a folder
@@ -209,9 +204,9 @@ written once at freeze time, then checked forever — and today a human types it
209
204
 
210
205
  ### Commands
211
206
 
212
- **`rpp new <name> [--format tex|md]`** — creates `<papers>/<name>/` with:
207
+ **`rpp new <name>`** — creates `<papers>/<name>/` with:
213
208
 
214
- - `paper.tex` or `paper.md` (a minimal stub; `--format` defaults to `tex` — ask when interactive);
209
+ - `paper.tex` (a minimal stub);
215
210
  - `PIPELINE-STATUS.md` from a **template file** shipped in the package, with the name filled in and
216
211
  **no** `stages` field (a new paper has shipped nothing — `paper-stages.mjs:24`).
217
212
 
@@ -228,7 +223,7 @@ Rules for it, each taken from a tool above:
228
223
  **Later step, NOT part of this change — `rpp freeze <paper> <stage> [--pdf <file>] [--date <iso>]`**,
229
224
  the release-please move, recorded so the design is not lost:
230
225
 
231
- - copies the built pdf and the source into `versions/<date>-<stage>.pdf|.tex|.md`;
226
+ - copies the built pdf and the source into `versions/<date>-<stage>.pdf|.tex`;
232
227
  - **appends** one record to `stages` with `stage`, `date`, `pdf`, `bytes`, `source`, `sourceBytes`;
233
228
  - refuses a stage outside `STAGES` (`paper-stages.mjs:53`) and a date/stage pair already present.
234
229
 
@@ -259,15 +254,15 @@ the skills tell agents to update them.
259
254
 
260
255
  - **Existing folders with a `PIPELINE-STATUS.md`** (the owner's four): nothing changes. Their
261
256
  front matter is already the per-paper carrier; a later `rpp freeze` would append to their lists.
262
- - **Existing folders without one** (a draft that has only `paper.md`): `rpp new <that-name>` fills
263
- the missing file and touches nothing else.
257
+ - **Existing folders without one** (a draft that has only `paper.tex`): `rpp new <that-name>`
258
+ fills the missing file and touches nothing else.
264
259
  - **No `rpp migrate` is needed** for this recommendation. One would be needed only if the carrier
265
260
  later moves to `paper.yml`.
266
261
 
267
262
  ### The smallest first step
268
263
 
269
264
  Move the template out of the code fence into its own file (say
270
- `templates/paper/PIPELINE-STATUS.md`, plus `paper.tex` and `paper.md` stubs), make
265
+ `templates/paper/PIPELINE-STATUS.md`, plus a `paper.tex` stub), make
271
266
  `pipeline-status-template.md` point at it instead of containing it, and add `rpp new` that copies
272
267
  it. Then the README sentence "Nothing generates it and no command refreshes it" becomes "`rpp new`
273
268
  creates it". No rule changes, no consumer migration. `rpp freeze` is step two; `init` offering to
@@ -435,13 +430,13 @@ unset **and** no `--yes`._
435
430
 
436
431
  ### What `init` asks, and what it does without asking
437
432
 
438
- | decision | human at a terminal | agent / CI / `--yes` | flag |
439
- | ----------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------- |
440
- | papers directory | asked only if several candidates (today) | first candidate, said so (today) | `init <dir>` then the key |
441
- | **hooks** | "Wire the three paper hooks into `.claude/settings.json` (committed, shared)? **[Y/n]**" | **yes** — the guard is what the package is for, the edit is idempotent and shows up in `git diff` | `--no-hooks` (`--hooks=local` deferred) |
442
- | CI workflow | "Add a GitHub Actions workflow? **[y/N]**" (today) | **no** (today) — it writes into `.github/` and spends Actions minutes, which are metered | `--ci` / `--no-ci` |
443
- | first paper | only when the papers dir holds no paper: "Create a first paper? name: [skip]" then "format: tex / md [tex]" | **no**, unless `--paper <name>` is given | `--paper <name> --format tex\|md` |
444
- | external programs | reported (today) | reported (today) | — |
433
+ | decision | human at a terminal | agent / CI / `--yes` | flag |
434
+ | ----------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------------------------- |
435
+ | papers directory | asked only if several candidates (today) | first candidate, said so (today) | `init <dir>` then the key |
436
+ | **hooks** | "Wire the three paper hooks into `.claude/settings.json` (committed, shared)? **[Y/n]**" | **yes** — the guard is what the package is for, the edit is idempotent and shows up in `git diff` | `--no-hooks` (`--hooks=local` deferred) |
437
+ | CI workflow | "Add a GitHub Actions workflow? **[y/N]**" (today) | **no** (today) — it writes into `.github/` and spends Actions minutes, which are metered | `--ci` / `--no-ci` |
438
+ | first paper | only when the papers dir holds no paper: "Create a first paper? name: [skip]" | **no**, unless `--paper <name>` is given | `--paper <name>` |
439
+ | external programs | reported (today) | reported (today) | — |
445
440
 
446
441
  Why the hooks default is "yes" even without asking, while CI is "no": the rule `install.md` already
447
442
  adopted from Playwright is _"ask only about what cannot be guessed or is expensive"_. Installing a
@@ -495,8 +490,8 @@ Recorded so the design and the code do not quietly disagree.
495
490
  - **`rpp new` lints the new folder itself** (`rpp lint <papers>/<name>`), so the structure check —
496
491
  which looks at the subdirectories of the path it is given — does not run there; the folder was
497
492
  just made with the required files, so there is nothing for it to find.
498
- - **Interactive `init` asks "Create a first paper? name: [skip]" and then the format** only when the
499
- name was given and `--format` was not.
493
+ - **Interactive `init` asks "Create a first paper? name: [skip]"** only when the papers directory
494
+ holds no paper.
500
495
 
501
496
  ## Sources checked
502
497
 
@@ -0,0 +1,87 @@
1
+ # cycle/evidence
2
+
3
+ **Level:** error · **Reads:** the paper's `paperlint.json` (`cycles`) and the paper folder ·
4
+ **Reported on:** the first line of the paper's `PIPELINE-STATUS.md`
5
+
6
+ ## What it catches
7
+
8
+ A closed cycle — `outcome.kind` is `accepted`, `rejected` or `withdrawn` — names an `evidence`
9
+ file that is not in the paper folder: the decision mail, the reviews, or the note that records the
10
+ withdrawal was never saved, or the path is wrong.
11
+
12
+ The same for a deadline's `override`: the instant a human decided binds instead of what the pages
13
+ said rests on its `evidence` — the chairs' mail that extended the deadline, a saved page — and that
14
+ file must be in the paper folder.
15
+
16
+ ## Why
17
+
18
+ The outcome of an attempt is a claim about what a venue decided, and the record is read by people
19
+ and programs that were not there when the mail arrived: a status command, a scorecard check, the
20
+ next attempt's prior-work delta. A decision whose evidence is not in the folder is a claim with
21
+ nothing behind it, and the day it is doubted there is nowhere to look. The record therefore refuses
22
+ an outcome without `evidence` at all, and this rule requires the file to exist.
23
+
24
+ ## Examples
25
+
26
+ Failing — `paperlint.json`:
27
+
28
+ ```json
29
+ {
30
+ "cycles": [
31
+ {
32
+ "id": "first-2026",
33
+ "venue": { "kind": "preset", "extends": "paperlint:acm-sigconf" },
34
+ "opened": "2026-07-01",
35
+ "outcome": {
36
+ "kind": "rejected",
37
+ "date": "2026-09-08",
38
+ "evidence": "reviews/decision.md"
39
+ }
40
+ }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ with no `reviews/decision.md` in the paper folder:
46
+
47
+ ```text
48
+ papers/my-paper/PIPELINE-STATUS.md
49
+ 1:1 error cycle «first-2026» is rejected (2026-09-08) with evidence `reviews/decision.md`, which is
50
+ not in the paper folder — save the decision mail, the reviews or the note that records
51
+ the decision there, or correct the path cycle/evidence
52
+ ```
53
+
54
+ An override whose evidence was never saved:
55
+
56
+ ```text
57
+ 1:1 error cycle «msr-2027»: the override of the "submission" deadline (2026-10-27T04:00:00Z)
58
+ rests on `mail/extension.eml`, which is not in the paper folder — save the mail or the
59
+ page that grants it there, or correct the path cycle/evidence
60
+ ```
61
+
62
+ Passing:
63
+
64
+ - the same record with `reviews/decision.md` present;
65
+ - an open cycle (`"outcome": { "kind": "open" }`, or no `outcome`): nothing has been decided;
66
+ - a paper whose `paperlint.json` has no `cycles`.
67
+
68
+ ## Options / preset fields
69
+
70
+ No rule options. `evidence` is a path relative to the paper folder; it may name any file — the
71
+ saved mail, a markdown note with the reviews, a PDF.
72
+
73
+ ## What it does not check
74
+
75
+ - What the file says. A rejection recorded as `accepted` with the rejection mail as evidence passes
76
+ here; the person who records the outcome reads the mail. Likewise an override's mail is not read
77
+ for its date.
78
+ - The `date`. It must be a day (`YYYY-MM-DD`), nothing more.
79
+ - Whether a `desk` rejection is marked as such (`"desk": true`); it changes what work follows, not
80
+ whether the record is consistent.
81
+
82
+ ## How to fix
83
+
84
+ Save the decision in the paper folder — the mail as it arrived, the reviews as the portal shows
85
+ them, or a dated note that records a withdrawal and why — and name that file in `evidence`. If the
86
+ file exists under another name, correct the path. For an override, save the mail or page that moved
87
+ the deadline and name it in the override's `evidence`.
@@ -0,0 +1,75 @@
1
+ # cycle/port-done
2
+
3
+ **Level:** error · **Reads:** `paper.tex` → the paper's `paperlint.json` (`cycles`) → the venue
4
+ preset's `template` · **Reported on:** `paper.tex`
5
+
6
+ ## What it catches
7
+
8
+ The current cycle declares `"phase": "porting"` — the source is still in the previous venue's
9
+ template, so the venue's format rules are silent — and the paper's `\documentclass` is already the
10
+ one the venue's preset requires. The port is done and the declaration is stale.
11
+
12
+ ## Why
13
+
14
+ `"phase": "porting"` is a record of open work, not a softer severity: the day a new venue is chosen
15
+ the source is still in the old venue's template, and a page count under the wrong template says
16
+ nothing about the new limit. While the phase is declared, every `pdf/*`, `format/*`, `anonymity/*`
17
+ and `tex/*` venue rule stays silent for that paper. A declaration that silences rules must not be
18
+ able to stay on after its reason is gone: once the class line is the venue's, this rule fires, and
19
+ the only way to be quiet again is to drop the phase — which turns the format rules back on.
20
+
21
+ ## Examples
22
+
23
+ Failing — `paperlint.json`:
24
+
25
+ ```json
26
+ {
27
+ "cycles": [
28
+ {
29
+ "id": "blind-2027",
30
+ "venue": { "kind": "preset", "extends": "./blindconf.jsonc" },
31
+ "opened": "2026-09-09",
32
+ "phase": "porting"
33
+ }
34
+ ]
35
+ }
36
+ ```
37
+
38
+ where the preset's `template` is `\documentclass[conference,compsoc]{IEEEtran}`, and `paper.tex`
39
+ begins with that very line:
40
+
41
+ ```text
42
+ papers/my-paper/paper.tex
43
+ 1:1 error the \documentclass is already `\documentclass[conference,compsoc]{IEEEtran}`, which
44
+ BlindConf requires, and the cycle «blind-2027» still declares "phase": "porting" — the
45
+ port is done: remove the phase, so the venue's format rules judge this paper again
46
+ cycle/port-done
47
+ ```
48
+
49
+ Passing:
50
+
51
+ - the same paper with the phase removed (or `"phase": "prepared"`);
52
+ - a cycle still porting whose `paper.tex` still has the old venue's class line — the port is open
53
+ work, as declared;
54
+ - a preset that names no `template`: there is nothing to compare, as for `tex/template`;
55
+ - a paper whose `paperlint.json` has no `cycles`.
56
+
57
+ ## Options / preset fields
58
+
59
+ No rule options. It reads the preset's `template`, the field [`tex/template`](../tex/template.md)
60
+ reads, and compares the way that rule does.
61
+
62
+ ## What it does not check
63
+
64
+ - Whether the port is complete beyond the class line — the page limit, the fonts, the sections. Those
65
+ are the format rules' to judge, and they judge again the moment the phase is dropped.
66
+ - A cycle declared `porting` whose class line is still the old one: that is the state the phase
67
+ describes.
68
+ - A `porting` cycle that has already sent a PDF — [`cycle/stage`](stage.md) reports that on the
69
+ scorecard.
70
+
71
+ ## How to fix
72
+
73
+ Remove `"phase": "porting"` from the current cycle in `paperlint.json`, then run `paperlint lint`
74
+ and `paperlint build`: the venue's format rules judge the paper again, and what they report is the
75
+ rest of the port.
@@ -0,0 +1,152 @@
1
+ # cycle/record
2
+
3
+ **Level:** error · **Reads:** the paper's `paperlint.json` (`cycles`) and the venue presets it names ·
4
+ **Reported on:** the first line of the paper's `PIPELINE-STATUS.md`
5
+
6
+ ## What it catches
7
+
8
+ The paper's `cycles` — its attempts at venues, one entry each, oldest first — do not describe one
9
+ consistent history:
10
+
11
+ - **one source's deadlines are out of order.** The order a venue's deadlines keep is
12
+
13
+ ```text
14
+ registration ≤ submission ≤ resubmission < notification < camera-ready
15
+ ```
16
+
17
+ and it is checked within each source: the portal's readings among themselves, the call's among
18
+ themselves, the overrides among themselves. A call's abstract day after its own paper day is a
19
+ date typed wrong or an AoE day miscounted.
20
+
21
+ - **a portal reading the venue's preset already carries.** A cycle whose venue is a preset derives
22
+ the portal's readings from it (`presets/msr.jsonc` carries MSR's); the same reading typed into the
23
+ paper is a second copy, and it goes stale the day paperlint updates the preset.
24
+
25
+ The structural problems of the record — a bare date where an instant is needed, two open cycles, an
26
+ open cycle that is not the last, two cycles with one id, one deadline in two entries, two readings
27
+ from one source, an override without its reason or evidence, an unknown key anywhere in the record
28
+ — are refused earlier, by `paperlint lint` itself before any rule runs, with one line naming the
29
+ file, the place and the problem, and exit code 2, the same way an unknown key in `paperlint.json` is
30
+ refused. This rule judges what the record parses with.
31
+
32
+ ## Why
33
+
34
+ A deadline is the one fact in the record whose error costs the attempt. The record keeps what each
35
+ page said — the portal, which refuses the upload, and the call, which only announces — with where and
36
+ when it was read, and the **earlier** reading binds unless a human override says otherwise. That the
37
+ portal and the call disagree is expected and is not a finding: at MSR 2027 the portal's "submissions
38
+ must be completed by" falls at 04:00 UTC on the call's _abstract_ day, four days before the call's
39
+ _paper_ day, and keeping both is how the earlier one is seen. What is a finding is a source that
40
+ contradicts itself, and a fact recorded in two places.
41
+
42
+ ## Examples
43
+
44
+ Failing — `paperlint.json`, the call's own dates inverted:
45
+
46
+ ```json
47
+ {
48
+ "cycles": [
49
+ {
50
+ "id": "conf-2027",
51
+ "venue": { "kind": "preset", "extends": "paperlint:acm-sigconf" },
52
+ "opened": "2026-09-09",
53
+ "deadlines": [
54
+ {
55
+ "what": "registration",
56
+ "observed": [
57
+ {
58
+ "at": "2026-10-25 AoE",
59
+ "source": "call",
60
+ "url": "https://conf.example/cfp",
61
+ "read": "2026-09-09"
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ "what": "submission",
67
+ "observed": [
68
+ {
69
+ "at": "2026-10-23 AoE",
70
+ "source": "call",
71
+ "url": "https://conf.example/cfp",
72
+ "read": "2026-09-09"
73
+ }
74
+ ]
75
+ }
76
+ ]
77
+ }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ ```text
83
+ papers/my-paper/PIPELINE-STATUS.md
84
+ 1:1 error paperlint.json, cycle «conf-2027»: the call's "registration" (2026-10-26T11:59:59Z) is
85
+ after its "submission" (2026-10-24T11:59:59Z) — a date typed wrong, or an AoE day
86
+ miscounted (write a call's date as "YYYY-MM-DD AoE" and paperlint converts it)
87
+ cycle/record
88
+ ```
89
+
90
+ Failing — a cycle at `paperlint:msr` that copies the portal's reading the preset carries:
91
+
92
+ ```text
93
+ 1:1 error paperlint.json, cycle «msr-2027»: the "submission" deadline records a portal reading,
94
+ and the preset paperlint:msr carries the portal's reading of it (2026-10-20T04:00:00Z,
95
+ read 2026-10-05) — a second copy drifts when paperlint updates the preset: delete this
96
+ one (the call's reading and an override stay) cycle/record
97
+ ```
98
+
99
+ Passing:
100
+
101
+ - the first record with the registration on `2026-10-20 AoE`;
102
+ - a cycle at `paperlint:msr` recording the call's abstract day as `registration` (`2026-10-20 AoE`)
103
+ and its paper day as `submission` (`2026-10-23 AoE`): the preset's portal `submission`
104
+ (`2026-10-20T04:00:00Z`) is earlier than both, binds, and is not a contradiction;
105
+ - a cycle with no `deadlines` at all;
106
+ - a paper whose `paperlint.json` has no `cycles` (the flat form, `"extends": "paperlint:…"`).
107
+
108
+ ## Options / preset fields
109
+
110
+ No rule options. One deadline entry:
111
+
112
+ ```jsonc
113
+ {
114
+ "what": "submission", // registration (the call's abstract) · submission (the full paper) · resubmission · notification · camera-ready
115
+ "observed": [
116
+ {
117
+ "at": "2026-10-23 AoE", // or an instant with its zone: 2026-10-20T04:00:00Z
118
+ "source": "call", // or "portal"
119
+ "url": "https://conf.example/cfp",
120
+ "read": "2026-10-05", // the day the page was read
121
+ },
122
+ ],
123
+ // optional; wins over every reading
124
+ "override": {
125
+ "at": "2026-10-27T04:00:00Z",
126
+ "reason": "the chairs extended the deadline by mail",
127
+ "evidence": "mail/extension.eml",
128
+ },
129
+ }
130
+ ```
131
+
132
+ The preset field is `deadlines` in a venue preset: a list of readings `{ "what", "at", "source",
133
+ "url", "read" }`, `at` in UTC. Shipped presets carry their portal's readings and are refreshed by a
134
+ scheduled workflow in paperlint's repository; your own preset may carry readings the same way.
135
+
136
+ ## What it does not check
137
+
138
+ - Whether a reading is still what the page says. The rule has no network and no clock; a shipped
139
+ preset's readings are re-read daily in paperlint's repository and reach you as a patch release.
140
+ A venue without a preset (`"kind": "named"`) is re-read by you.
141
+ - Whether a deadline has passed, or how long is left.
142
+ - A disagreement between sources — that is what the record holds; the earlier reading binds.
143
+ - Whether an override's evidence exists — that is [`cycle/evidence`](evidence.md).
144
+ - Whether the scorecard's stages name these cycles — that is [`cycle/stage`](stage.md).
145
+
146
+ ## How to fix
147
+
148
+ Correct the date. For a deadline read from a call for papers, write it as the call prints it,
149
+ `"YYYY-MM-DD AoE"`, and let paperlint do the arithmetic; for one read from a portal, write the instant
150
+ the portal shows with its zone. Delete a portal reading the venue's preset already carries. A
151
+ deadline the chairs moved by mail is an `override`, with the mail saved in the paper folder as its
152
+ `evidence`.
@@ -0,0 +1,85 @@
1
+ # cycle/stage
2
+
3
+ **Level:** error · **Reads:** the paper's `PIPELINE-STATUS.md` frontmatter (`stages`) and its
4
+ `paperlint.json` (`cycles`) · **Reported on:** the frontmatter of `PIPELINE-STATUS.md`
5
+
6
+ ## What it catches
7
+
8
+ With `cycles` declared in the paper's `paperlint.json`, a `stages` entry in the scorecard's
9
+ frontmatter must say which attempt it belongs to, `cycle: <id>`, and that attempt must exist. Three
10
+ things can be wrong:
11
+
12
+ - a stage names no `cycle`;
13
+ - a stage names a `cycle` no declared cycle has as its `id`;
14
+ - a `submitted` or `camera-ready` stage belongs to a cycle whose `phase` is still `porting` — a PDF
15
+ was sent to the venue while the record says the source is still in the previous venue's template.
16
+
17
+ Without `cycles` (the flat form) the rule is silent and `venue:` stays what it was.
18
+
19
+ ## Why
20
+
21
+ A paper's attempts are recorded once, in `paperlint.json`; the scorecard's stages are the evidence
22
+ — which PDF, how many bytes, when. A stage that names its cycle says which attempt the evidence
23
+ belongs to, and the venue's name is then spelled once, in the preset, instead of as a free string
24
+ retyped per stage (`REALM @ EMNLP 2026`, `AISec 2026 @ ACM CCS`, …). A stage whose cycle does not
25
+ exist is evidence of nothing.
26
+
27
+ The `porting` case is the other half of a declaration: `"phase": "porting"` silences the venue's
28
+ format rules because the source is, by declaration, not yet in the venue's template. A PDF sent
29
+ from such a cycle contradicts that declaration — either the port is done and the phase is stale, or
30
+ the stage is wrong.
31
+
32
+ ## Examples
33
+
34
+ Failing — `paperlint.json` declares one cycle, `open-2027`, and `PIPELINE-STATUS.md` has
35
+
36
+ ```yaml
37
+ ---
38
+ stages:
39
+ - stage: submitted
40
+ date: 2026-07-22
41
+ pdf: versions/a.pdf
42
+ bytes: 1
43
+ ---
44
+ ```
45
+
46
+ ```text
47
+ papers/my-paper/PIPELINE-STATUS.md
48
+ 1:1 error the «submitted» stage (2026-07-22) names no `cycle` — this paper declares `cycles` in
49
+ paperlint.json, so each stage says which attempt it belongs to: `cycle: <id>`, one of
50
+ open-2027 cycle/stage
51
+ ```
52
+
53
+ Passing:
54
+
55
+ ```yaml
56
+ ---
57
+ stages:
58
+ - stage: submitted
59
+ cycle: open-2027
60
+ date: 2026-07-22
61
+ pdf: versions/a.pdf
62
+ bytes: 1
63
+ ---
64
+ ```
65
+
66
+ Also passing: a paper without `cycles`, whatever its stages say.
67
+
68
+ ## Options / preset fields
69
+
70
+ No rule options.
71
+
72
+ ## What it does not check
73
+
74
+ - The PDF, its bytes, or the frozen source — [`paper/stages`](../../rules.md) and `paper/source`
75
+ do.
76
+ - Whether a stage's `date` falls inside its cycle (after `opened`, before the outcome's `date`).
77
+ - Whether a cycle without any `submitted` stage was in fact submitted; that is the portal's record
78
+ (`paperlint submission`), not the scorecard's.
79
+ - The rest of the cycle record — [`cycle/record`](record.md) does.
80
+
81
+ ## How to fix
82
+
83
+ Add `cycle: <id>` to each stage, with the id of the cycle the PDF was sent in. For a stage whose
84
+ cycle is still `porting`: if the port is done, remove `"phase": "porting"` from the cycle
85
+ ([`cycle/port-done`](port-done.md) says when); if no PDF was sent, remove the stage.
@@ -68,7 +68,6 @@ any other rule.
68
68
  3 of the model_) is read as a quantity, and so is the number of a name that opens the sentence
69
69
  (_Claude 3 fails …_).
70
70
  - Whether a count followed by a plural noun is really the sample (_3 times faster_ passes as one).
71
- - Markdown papers (`paper.md`): it reads the LaTeX parse tree only.
72
71
 
73
72
  ## How to fix
74
73