paperlint 7.1.0 → 7.2.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/.github/workflows/ci.yml +3 -0
- package/.github/workflows/deadlines.yml +100 -0
- package/README.md +6 -0
- package/dist/adapters/hotcrp/deadlines.d.ts +8 -0
- package/dist/adapters/hotcrp/deadlines.d.ts.map +1 -0
- package/dist/adapters/hotcrp/deadlines.io.d.ts +6 -0
- package/dist/adapters/hotcrp/deadlines.io.d.ts.map +1 -0
- package/dist/adapters/hotcrp/deadlines.io.js +26 -0
- package/dist/adapters/hotcrp/deadlines.io.js.map +1 -0
- package/dist/adapters/hotcrp/deadlines.js +134 -0
- package/dist/adapters/hotcrp/deadlines.js.map +1 -0
- package/dist/adapters/hotcrp/index.d.ts +2 -0
- package/dist/adapters/hotcrp/index.d.ts.map +1 -1
- package/dist/adapters/hotcrp/index.js +2 -0
- package/dist/adapters/hotcrp/index.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +40 -5
- package/dist/cli.js.map +1 -1
- package/dist/cycle-rules.d.ts +24 -0
- package/dist/cycle-rules.d.ts.map +1 -0
- package/dist/cycle-rules.js +212 -0
- package/dist/cycle-rules.js.map +1 -0
- package/dist/domain/cycle.d.ts +148 -0
- package/dist/domain/cycle.d.ts.map +1 -0
- package/dist/domain/cycle.js +281 -0
- package/dist/domain/cycle.js.map +1 -0
- package/dist/domain/deadline.d.ts +128 -0
- package/dist/domain/deadline.d.ts.map +1 -0
- package/dist/domain/deadline.js +281 -0
- package/dist/domain/deadline.js.map +1 -0
- package/dist/eslint-rules/cycle-stage.d.ts +39 -0
- package/dist/eslint-rules/cycle-stage.d.ts.map +1 -0
- package/dist/eslint-rules/cycle-stage.js +119 -0
- package/dist/eslint-rules/cycle-stage.js.map +1 -0
- package/dist/lib/paper-config.d.ts.map +1 -1
- package/dist/lib/paper-config.js +2 -0
- package/dist/lib/paper-config.js.map +1 -1
- package/dist/paper-settings.d.ts +7 -0
- package/dist/paper-settings.d.ts.map +1 -1
- package/dist/paper-settings.js +61 -8
- package/dist/paper-settings.js.map +1 -1
- package/dist/ports/venue-deadlines.d.ts +14 -0
- package/dist/ports/venue-deadlines.d.ts.map +1 -0
- package/dist/ports/venue-deadlines.js +2 -0
- package/dist/ports/venue-deadlines.js.map +1 -0
- package/dist/presets.d.ts +12 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -11
- package/dist/presets.js.map +1 -1
- package/dist/submission.d.ts +8 -0
- package/dist/submission.d.ts.map +1 -1
- package/dist/submission.js +41 -10
- package/dist/submission.js.map +1 -1
- package/dist/tex-requirements.d.ts +3 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +19 -0
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +19 -0
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +69 -0
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/venue-rules.d.ts +10 -0
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +26 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +139 -11
- package/docs/rules/cycle/evidence.md +87 -0
- package/docs/rules/cycle/port-done.md +75 -0
- package/docs/rules/cycle/record.md +152 -0
- package/docs/rules/cycle/stage.md +85 -0
- package/docs/rules.md +48 -43
- package/docs/submission.md +5 -0
- package/eslint-rules/cycle-stage.test.mjs +131 -0
- package/eslint-rules/cycle-stage.ts +141 -0
- package/eslint.config.mjs +41 -1
- package/lib/paper-config.ts +3 -0
- package/package.json +1 -1
- package/presets/aidc.jsonc +24 -3
- package/presets/aidc.md +6 -3
- package/presets/msr.jsonc +25 -1
- package/presets/msr.md +5 -1
- package/presets/venue-profile.schema.json +39 -0
- package/scripts/check.ts +4 -0
- package/scripts/refresh-deadlines.test.ts +431 -0
- package/scripts/refresh-deadlines.ts +387 -0
- package/src/adapters/hotcrp/cassette/README.md +22 -0
- package/src/adapters/hotcrp/cassette/deadlines-aidc26.html +17 -0
- package/src/adapters/hotcrp/cassette/deadlines-fse2027.html +24 -0
- package/src/adapters/hotcrp/cassette/deadlines-icse2027.html +25 -0
- package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +25 -0
- package/src/adapters/hotcrp/cassette/deadlines-nosuch.html +18 -0
- package/src/adapters/hotcrp/cassette/deadlines-sosp26.html +26 -0
- package/src/adapters/hotcrp/deadlines.io.test.ts +86 -0
- package/src/adapters/hotcrp/deadlines.io.ts +27 -0
- package/src/adapters/hotcrp/deadlines.test.ts +170 -0
- package/src/adapters/hotcrp/deadlines.ts +161 -0
- package/src/adapters/hotcrp/index.ts +2 -0
- package/src/cli.test.ts +9 -0
- package/src/cli.ts +43 -4
- package/src/cycle-rules.test.ts +278 -0
- package/src/cycle-rules.ts +273 -0
- package/src/domain/cycle.test.ts +456 -0
- package/src/domain/cycle.ts +432 -0
- package/src/domain/deadline.test.ts +386 -0
- package/src/domain/deadline.ts +472 -0
- package/src/new-paper.test.ts +1 -0
- package/src/paper-settings.test.ts +167 -0
- package/src/paper-settings.ts +85 -11
- package/src/ports/venue-deadlines.ts +14 -0
- package/src/presets.test.ts +105 -0
- package/src/presets.ts +43 -10
- package/src/rule-cases.test.ts +144 -0
- package/src/submission.test.ts +105 -1
- package/src/submission.ts +73 -12
- package/src/tex-requirements.ts +31 -0
- package/src/tex-venue-rules.test.ts +76 -0
- package/src/tex-venue-rules.ts +85 -0
- package/src/venue-rules.test.ts +82 -0
- package/src/venue-rules.ts +44 -12
package/docs/configuration.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
| `deadlines` | one entry per deadline, `{ "what", "observed", "override" }` — see [Deadlines](#deadlines) below |
|
|
2
|
+
|
|
1
3
|
# Configuration reference
|
|
2
4
|
|
|
3
5
|
paperlint reads one file name at two levels, `paperlint.json`, with one schema. Both files are
|
|
@@ -96,16 +98,17 @@ The same keys as the root file, minus the project-only ones (`papersDir`, `struc
|
|
|
96
98
|
skills' keys), which are refused here by name, plus `talk` and `submission`, which only a paper has. **It merges over the root file:** its `extends`,
|
|
97
99
|
`kind` and `pdf` win; one it does not set comes from the root.
|
|
98
100
|
|
|
99
|
-
| key | what it is
|
|
100
|
-
| ------------ |
|
|
101
|
-
| `extends` | the venue preset the built PDF is judged against: `paperlint:<name>` (shipped) or `./path` / `../path` (your own, relative to this file) — [`rules.md`](rules.md#checks-against-the-venue)
|
|
102
|
-
| `kind` | the kind of paper (`short`, `research`, …) whose page limit applies
|
|
103
|
-
| `pdf` | where the built PDF is, relative to the paper, when it is not `paper.pdf`
|
|
104
|
-
| `identity` | what identifies the authors — names, handles, emails, affiliations, your own project names. A blind venue's PDF must say none of it ([`anonymity/identity`](rules/anonymity/identity.md)). Joined with the root's list
|
|
105
|
-
| `talk` | how this paper is presented, `{ "mode": "remote-video" }` plus optional `dir`, `files`, `one_slide` — the `talk/*` rules judge its finished files ([`talk.md`](talk.md)). Only here: the root file refuses it
|
|
106
|
-
| `submission` | which submission on the venue's portal is this paper's, `{ "id": 7 }` — read by [`paperlint submission`](submission.md), with the preset's `portal`. Only here: the root file refuses it
|
|
107
|
-
| `
|
|
108
|
-
|
|
|
101
|
+
| key | what it is |
|
|
102
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `extends` | the venue preset the built PDF is judged against: `paperlint:<name>` (shipped) or `./path` / `../path` (your own, relative to this file) — [`rules.md`](rules.md#checks-against-the-venue) |
|
|
104
|
+
| `kind` | the kind of paper (`short`, `research`, …) whose page limit applies |
|
|
105
|
+
| `pdf` | where the built PDF is, relative to the paper, when it is not `paper.pdf` |
|
|
106
|
+
| `identity` | what identifies the authors — names, handles, emails, affiliations, your own project names. A blind venue's PDF must say none of it ([`anonymity/identity`](rules/anonymity/identity.md)). Joined with the root's list |
|
|
107
|
+
| `talk` | how this paper is presented, `{ "mode": "remote-video" }` plus optional `dir`, `files`, `one_slide` — the `talk/*` rules judge its finished files ([`talk.md`](talk.md)). Only here: the root file refuses it |
|
|
108
|
+
| `submission` | which submission on the venue's portal is this paper's, `{ "id": 7 }` — read by [`paperlint submission`](submission.md), with the preset's `portal`. Only here: the root file refuses it |
|
|
109
|
+
| `cycles` | the paper's attempts at venues, one entry per attempt, oldest first — see [The paper's cycles](#the-papers-cycles) below. With it, `extends`, `kind` and `submission` are the current attempt's and may not be written beside it. Only here: the root file refuses it |
|
|
110
|
+
| `rules` | rule overrides for this paper alone — `{ "<rule>": "<severity>" }`, or blocks with globs relative to the paper |
|
|
111
|
+
| `$comment` | a note for humans; ignored |
|
|
109
112
|
|
|
110
113
|
`paperlint new` writes this file from the template (`templates/paper/paperlint.json`, or your
|
|
111
114
|
`<papers>/.template/paperlint.json` if you keep one). With `--venue` it writes the venue into it:
|
|
@@ -132,7 +135,8 @@ npx paperlint new my-paper --venue ./venues/my-workshop.jsonc # "extends": "
|
|
|
132
135
|
Without a venue the file has `"extends": null` and a `$comment` saying what goes there. Until
|
|
133
136
|
`extends` names a preset (here or in the root file), `paperlint lint` gives that paper one warning,
|
|
134
137
|
`pdf/measured`: "this paper names no venue preset yet … set "extends" in
|
|
135
|
-
papers/my-paper/paperlint.json".
|
|
138
|
+
papers/my-paper/paperlint.json". A paper keeping `cycles` is told instead to set the venue in its
|
|
139
|
+
current cycle — `extends` beside `cycles` is refused.
|
|
136
140
|
|
|
137
141
|
**Any other key is an error**, in either file, named in the message: `paperlint.json: unknown key
|
|
138
142
|
"papersdir"`. A misspelt key would otherwise read as "not set", and the setting you meant would
|
|
@@ -144,6 +148,130 @@ the root `paperlint.json`'s `rules` → the paper's own `rules`. So a venue can
|
|
|
144
148
|
project can change that for every paper, and one paper can still change it for itself. Only rules
|
|
145
149
|
paperlint ships may be named, at every level.
|
|
146
150
|
|
|
151
|
+
## The paper's cycles
|
|
152
|
+
|
|
153
|
+
A paper goes to a venue, is rejected, and goes to the next one. `cycles` records each attempt as
|
|
154
|
+
one entry, oldest first; `extends`, `kind` and `submission` are then **derived** from the current
|
|
155
|
+
attempt — the one open cycle, or the accepted last one — and writing any of them beside `cycles`
|
|
156
|
+
is refused as a second source for one fact.
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
{
|
|
160
|
+
"pdf": "build/paper.pdf",
|
|
161
|
+
"cycles": [
|
|
162
|
+
{
|
|
163
|
+
"id": "alpha-2026", // what a stage's `cycle:` names
|
|
164
|
+
"venue": { "kind": "preset", "extends": "paperlint:acm-sigconf" },
|
|
165
|
+
"kind": "short",
|
|
166
|
+
"opened": "2026-07-01", // the day this attempt was decided
|
|
167
|
+
"deadlines": [
|
|
168
|
+
{
|
|
169
|
+
"what": "submission",
|
|
170
|
+
"observed": [
|
|
171
|
+
{
|
|
172
|
+
"at": "2026-08-06 AoE",
|
|
173
|
+
"source": "call",
|
|
174
|
+
"url": "https://alpha.example/cfp",
|
|
175
|
+
"read": "2026-07-01",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
"outcome": {
|
|
181
|
+
"kind": "rejected",
|
|
182
|
+
"date": "2026-09-08",
|
|
183
|
+
"desk": false,
|
|
184
|
+
"evidence": "reviews/alpha-decision.md",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "beta-2027",
|
|
189
|
+
"venue": { "kind": "preset", "extends": "paperlint:ieee-conference" },
|
|
190
|
+
"opened": "2026-09-09",
|
|
191
|
+
"phase": "porting", // the source is still in alpha's template
|
|
192
|
+
"deadlines": [
|
|
193
|
+
{
|
|
194
|
+
"what": "submission",
|
|
195
|
+
"observed": [
|
|
196
|
+
{
|
|
197
|
+
"at": "2026-10-20T04:00:00Z",
|
|
198
|
+
"source": "portal",
|
|
199
|
+
"url": "https://beta.example/deadlines",
|
|
200
|
+
"read": "2026-09-09",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"at": "2026-10-23 AoE",
|
|
204
|
+
"source": "call",
|
|
205
|
+
"url": "https://beta.example/cfp",
|
|
206
|
+
"read": "2026-09-09",
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
"override": {
|
|
210
|
+
"at": "2026-10-27T04:00:00Z",
|
|
211
|
+
"reason": "the chairs extended the deadline by mail",
|
|
212
|
+
"evidence": "mail/extension.eml",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
"submission": { "id": 7 },
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
| field | what it is |
|
|
223
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
224
|
+
| `id` | unique in the list; a scorecard stage names it as `cycle: <id>` ([`cycle/stage`](rules/cycle/stage.md)) |
|
|
225
|
+
| `venue` | `{ "kind": "preset", "extends": "paperlint:<name>" \| "./path" }` — resolved as `extends` is; or `{ "kind": "named", "name", "url" }` for a venue with no preset yet. A named venue has no format rules |
|
|
226
|
+
| `kind` | a key of the preset's `format.kinds`; omit when the venue has one kind |
|
|
227
|
+
| `opened` | `YYYY-MM-DD`, the day this attempt was decided — the boundary a consumer judges the scorecard's rows against |
|
|
228
|
+
| `phase` | `"porting"` while the source is still in the previous venue's template: every `pdf/*`, `format/*`, `anonymity/*` and `tex/*` venue rule is silent for the paper, and [`cycle/port-done`](rules/cycle/port-done.md) fires once the class line is the venue's. Default `"prepared"` |
|
|
229
|
+
| `deadlines` | a list of `{ "what", "at", "source", "url" }`: `what` is `registration`, `submission`, `resubmission`, `notification` or `camera-ready`; `at` an instant with its zone (`2026-10-20T04:00:00Z`) or a call's `"YYYY-MM-DD AoE"`; `source` is `"portal"` or `"call"` — where it was read ([`cycle/record`](rules/cycle/record.md)) |
|
|
230
|
+
| `submission` | which submission on the venue's portal is this paper's, `{ "id": 7 }` — what the flat `submission` key is |
|
|
231
|
+
| `outcome` | `{ "kind": "open" }` (the default), or `accepted` \| `rejected` \| `withdrawn` with `date` and `evidence`, a file in the paper folder ([`cycle/evidence`](rules/cycle/evidence.md)); `rejected` may add `"desk": true` |
|
|
232
|
+
|
|
233
|
+
**One open cycle at most, and it is the last entry.** Two open cycles is dual submission or a
|
|
234
|
+
decision nobody recorded; `paperlint lint` refuses the file with one line naming both ids. A paper
|
|
235
|
+
whose last cycle is rejected or withdrawn and has no open one is between venues: `extends` derives
|
|
236
|
+
to null and the venue rules say nothing. An accepted last cycle keeps the paper at that venue —
|
|
237
|
+
camera-ready and talk are judged against it, and the venue's blindness no longer applies.
|
|
238
|
+
|
|
239
|
+
Papers without `cycles` read exactly as before.
|
|
240
|
+
|
|
241
|
+
### Deadlines
|
|
242
|
+
|
|
243
|
+
A deadline records **what each page said**, and optionally **a human decision that overrides it**:
|
|
244
|
+
|
|
245
|
+
| field | what it is |
|
|
246
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
247
|
+
| `what` | `registration` — the abstract registration, a call's "abstract" date · `submission` — the full paper, a call's "paper" date, HotCRP's "submissions must be completed by" · `resubmission` — updates to a completed submission · `notification` · `camera-ready` |
|
|
248
|
+
| `observed` | the readings: `{ "at", "source", "url", "read" }` — `at` an instant with its zone (`2026-10-20T04:00:00Z`) or a call's `"YYYY-MM-DD AoE"` (paperlint converts it to the end of that day at UTC−12); `source` `"portal"` or `"call"`; `url` the page; `read` the day it was read. One reading per source |
|
|
249
|
+
| `override` | `{ "at", "reason", "evidence" }` — an instant that binds instead, why, and a file in the paper folder it rests on (the chairs' mail). [`cycle/evidence`](rules/cycle/evidence.md) requires the file |
|
|
250
|
+
|
|
251
|
+
**Which instant is in force:** the `override` when there is one; otherwise the **earliest** reading.
|
|
252
|
+
The portal and the call often disagree — at MSR 2027 the portal's "submissions must be completed by"
|
|
253
|
+
is 04:00 UTC on the call's _abstract_ day, four days before the call's _paper_ day — and both are kept:
|
|
254
|
+
the earlier one is the safe one, and recording both is not an error. The kinds map a call's words
|
|
255
|
+
honestly: the call's "abstract" is `registration`, its "paper" is `submission`; the portal's earlier
|
|
256
|
+
`submission` then binds. A deadline moved by the chairs' mail is an `override` with the mail as its
|
|
257
|
+
evidence, never a reading with a made-up source. Any key the record does not know — in a deadline, a
|
|
258
|
+
reading, an override, an outcome, a venue — is an error, so an override typed under another name
|
|
259
|
+
cannot look recorded and not be in force.
|
|
260
|
+
|
|
261
|
+
**Where the readings come from.** A shipped venue preset whose venue takes submissions on HotCRP
|
|
262
|
+
carries the portal's readings itself, as `deadlines` (`presets/msr.jsonc`). A cycle whose venue is that
|
|
263
|
+
preset **derives** them: write only what the call says and any override; a portal reading the preset
|
|
264
|
+
already carries is reported by [`cycle/record`](rules/cycle/record.md) as a second copy. A scheduled
|
|
265
|
+
workflow in paperlint's repository re-reads every such portal once a day and, when a value changed,
|
|
266
|
+
opens a pull request that releases a patch — **so new dates reach your papers through your normal
|
|
267
|
+
dependency update** (Dependabot or Renovate bumping `paperlint`); there is nothing to run. A venue
|
|
268
|
+
without a preset (`"venue": { "kind": "named", … }`) has no one reading it for you: type its
|
|
269
|
+
deadlines from its call, `"source": "call"`, and re-read the call yourself.
|
|
270
|
+
|
|
271
|
+
**`paperlint lint` never reads the clock.** It checks the record — instants with zones, each source's
|
|
272
|
+
deadlines in order, the evidence on disk — and gives the same answer tomorrow for the same files.
|
|
273
|
+
How many days are left is a status view's or a calendar's question, not a gate's.
|
|
274
|
+
|
|
147
275
|
## Records in frontmatter, checked by shipped JSON Schemas
|
|
148
276
|
|
|
149
277
|
Two kinds of file under a paper keep a record in their YAML frontmatter, and paperlint validates
|
|
@@ -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.
|