paperlint 12.0.0 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +2 -1
- package/dist/adapters/memory/index.d.ts +5 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +16 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/index.d.ts +1 -0
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/move-file.io.d.ts +4 -0
- package/dist/adapters/node/move-file.io.d.ts.map +1 -0
- package/dist/adapters/node/move-file.io.js +12 -0
- package/dist/adapters/node/move-file.io.js.map +1 -0
- package/dist/adapters/yaml/index.d.ts +3 -0
- package/dist/adapters/yaml/index.d.ts.map +1 -0
- package/dist/adapters/yaml/index.js +17 -0
- package/dist/adapters/yaml/index.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +48 -7
- package/dist/cli.js.map +1 -1
- package/dist/cycle-rules.d.ts +3 -3
- package/dist/cycle-rules.d.ts.map +1 -1
- package/dist/cycle-rules.js +1 -2
- package/dist/cycle-rules.js.map +1 -1
- package/dist/domain/cycle.d.ts +16 -2
- package/dist/domain/cycle.d.ts.map +1 -1
- package/dist/domain/cycle.js +21 -42
- package/dist/domain/cycle.js.map +1 -1
- package/dist/domain/paper-layout.d.ts.map +1 -1
- package/dist/domain/paper-layout.js +5 -2
- package/dist/domain/paper-layout.js.map +1 -1
- package/dist/domain/paths.d.ts +12 -0
- package/dist/domain/paths.d.ts.map +1 -1
- package/dist/domain/paths.js +22 -1
- package/dist/domain/paths.js.map +1 -1
- package/dist/domain/scaffold-layout.js +1 -1
- package/dist/domain/versions-migration.d.ts +51 -0
- package/dist/domain/versions-migration.d.ts.map +1 -0
- package/dist/domain/versions-migration.js +252 -0
- package/dist/domain/versions-migration.js.map +1 -0
- package/dist/domain/versions.d.ts +167 -0
- package/dist/domain/versions.d.ts.map +1 -0
- package/dist/domain/versions.js +457 -0
- package/dist/domain/versions.js.map +1 -0
- package/dist/eslint-rules/paper-stages.d.ts +7 -0
- package/dist/eslint-rules/paper-stages.d.ts.map +1 -1
- package/dist/eslint-rules/paper-stages.js +62 -7
- package/dist/eslint-rules/paper-stages.js.map +1 -1
- package/dist/eslint-rules/rule-context.d.ts +2 -0
- package/dist/eslint-rules/rule-context.d.ts.map +1 -1
- package/dist/ports/move-file.d.ts +7 -0
- package/dist/ports/move-file.d.ts.map +1 -0
- package/dist/ports/move-file.js +2 -0
- package/dist/ports/move-file.js.map +1 -0
- package/dist/ports/yaml.d.ts +5 -0
- package/dist/ports/yaml.d.ts.map +1 -0
- package/dist/ports/yaml.js +2 -0
- package/dist/ports/yaml.js.map +1 -0
- package/dist/submission.d.ts +2 -0
- package/dist/submission.d.ts.map +1 -1
- package/dist/submission.js +49 -9
- package/dist/submission.js.map +1 -1
- package/dist/version-rules.d.ts +16 -0
- package/dist/version-rules.d.ts.map +1 -0
- package/dist/version-rules.js +201 -0
- package/dist/version-rules.js.map +1 -0
- package/dist/versions-command.d.ts +37 -0
- package/dist/versions-command.d.ts.map +1 -0
- package/dist/versions-command.js +209 -0
- package/dist/versions-command.js.map +1 -0
- package/dist/versions.d.ts +76 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +164 -0
- package/dist/versions.js.map +1 -0
- package/docs/configuration.md +76 -2
- package/docs/layout.md +5 -5
- package/docs/rules/cycle/camera-ready-source.md +62 -0
- package/docs/rules/cycle/draft-drift.md +47 -0
- package/docs/rules/cycle/port-done.md +2 -2
- package/docs/rules/cycle/record.md +2 -1
- package/docs/rules/cycle/submitted-frozen.md +65 -0
- package/docs/rules/paper/allowed-files-only.md +1 -1
- package/docs/rules/versions/layout.md +88 -0
- package/docs/rules.md +25 -11
- package/docs/submission.md +21 -3
- package/eslint-rules/paper-stages.test.mjs +62 -0
- package/eslint-rules/paper-stages.ts +67 -8
- package/eslint-rules/prereg-coverage.test.ts +1 -0
- package/eslint-rules/rule-context.ts +2 -0
- package/package.json +2 -1
- package/presets/agent.jsonc +75 -0
- package/presets/agent.md +70 -0
- package/presets/agentverify.jsonc +72 -0
- package/presets/agentverify.md +85 -0
- package/presets/ieee-conference.md +1 -1
- package/presets/nlbse.jsonc +76 -0
- package/presets/nlbse.md +85 -0
- package/scripts/coverage-config.test.ts +3 -0
- package/skills/CLAUDE.md +2 -2
- package/skills/CLAUDE.md.spec.ts +1 -1
- package/skills/paper-pipeline/references/pipeline-status-template.md +4 -1
- package/skills/submit-paper/SKILL.md +18 -10
- package/skills/submit-paper/SKILL.md.spec.ts +17 -9
- package/skills/submit-paper/references/publishers/acm.md +5 -3
- package/src/adapters/memory/index.test.ts +22 -0
- package/src/adapters/memory/index.ts +21 -0
- package/src/adapters/node/index.ts +1 -0
- package/src/adapters/node/move-file.io.ts +21 -0
- package/src/adapters/node/move-file.test.ts +51 -0
- package/src/adapters/yaml/index.test.ts +16 -0
- package/src/adapters/yaml/index.ts +17 -0
- package/src/cli.test.ts +43 -0
- package/src/cli.ts +58 -6
- package/src/cycle-rules.ts +4 -5
- package/src/domain/cycle.test.ts +27 -0
- package/src/domain/cycle.ts +38 -6
- package/src/domain/paper-layout.ts +5 -2
- package/src/domain/paths.test.ts +28 -1
- package/src/domain/paths.ts +34 -1
- package/src/domain/scaffold-layout.ts +1 -1
- package/src/domain/versions-migration.test.ts +410 -0
- package/src/domain/versions-migration.ts +429 -0
- package/src/domain/versions.test.ts +564 -0
- package/src/domain/versions.ts +706 -0
- package/src/paper-settings-commands.test.ts +1 -1
- package/src/ports/move-file.ts +8 -0
- package/src/ports/yaml.ts +5 -0
- package/src/preset-card-rule.test.ts +3 -0
- package/src/presets.test.ts +55 -31
- package/src/rule-cases.test.ts +157 -8
- package/src/submission-freeze.test.ts +320 -0
- package/src/submission-new.test.ts +11 -1
- package/src/submission.ts +82 -11
- package/src/tex-venue-rules.test.ts +3 -0
- package/src/venue-rules.test.ts +2 -2
- package/src/version-rules.test.ts +222 -0
- package/src/version-rules.ts +282 -0
- package/src/versions-command.test.ts +594 -0
- package/src/versions-command.ts +360 -0
- package/src/versions.ts +286 -0
- package/dist/eslint-rules/cycle-stage.d.ts +0 -39
- package/dist/eslint-rules/cycle-stage.d.ts.map +0 -1
- package/dist/eslint-rules/cycle-stage.js +0 -119
- package/dist/eslint-rules/cycle-stage.js.map +0 -1
- package/docs/rules/cycle/stage.md +0 -85
- package/eslint-rules/cycle-stage.test.mjs +0 -131
- package/eslint-rules/cycle-stage.ts +0 -141
package/dist/versions.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A PAPER'S FROZEN VERSIONS, read from the disk: the folder `versions/`, its lock, and the draft
|
|
3
|
+
* measured against the frozen source it left (`src/domain/versions.ts` holds the convention and every
|
|
4
|
+
* decision; this file reads and writes through the ports).
|
|
5
|
+
*
|
|
6
|
+
* readVersions the cycles, the lock and the files of `versions/`, as the rules and the table read them
|
|
7
|
+
* draftDrift paper.tex, assembled as the build read it, against the frozen source of the attempt
|
|
8
|
+
* versionLines the version → state table (`paperlint versions`, `paperlint submission show`)
|
|
9
|
+
* freezePlan before a `submission --save` sends: what it will freeze, or why it cannot
|
|
10
|
+
* freezeSent after the portal took it: the PDF sent and its source, written and locked
|
|
11
|
+
*/
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { callerPath } from "./caller-path.js";
|
|
14
|
+
import { venueCycleOf } from "./domain/cycle.js";
|
|
15
|
+
import { sha256Hex } from "./domain/sha256.js";
|
|
16
|
+
import { messageOf } from "./domain/text.js";
|
|
17
|
+
import { err, ok } from "./domain/result.js";
|
|
18
|
+
import { driftBase, frozenKey, lineDrift, lockText, LOCK_FILE, parseLock, versionRows, versionTable, VERSIONS_DIR, } from "./domain/versions.js";
|
|
19
|
+
import { readCycles } from "./cycle-rules.js";
|
|
20
|
+
import { readPaper, decoded } from "./tex-paper.js";
|
|
21
|
+
import { MAIN } from "./build.js";
|
|
22
|
+
import { paperRecord } from "./paper-record.js";
|
|
23
|
+
/** The paper's `versions/` folder. */
|
|
24
|
+
export const versionsDir = (paperDir) => join(paperDir, VERSIONS_DIR);
|
|
25
|
+
/** A frozen file's path, by its key. */
|
|
26
|
+
export const frozenPath = (paperDir, key) => join(paperDir, VERSIONS_DIR, key);
|
|
27
|
+
/** Whether a frozen file is on disk, by its key. */
|
|
28
|
+
export const hasFrozen = (paperDir, files) => (key) => files.isFile(callerPath(frozenPath(paperDir, key)));
|
|
29
|
+
/** A frozen file's text, by its key; null when it is not there. */
|
|
30
|
+
export const frozenText = (paperDir, key, files) => decoded(files.readBytes(callerPath(frozenPath(paperDir, key))));
|
|
31
|
+
/** The size and digest of a frozen file on disk, by its key: zero bytes of nothing when it is gone. */
|
|
32
|
+
export const factsOfFrozen = (paperDir, files) => (key) => {
|
|
33
|
+
const b = files.readBytes(callerPath(frozenPath(paperDir, key))) ??
|
|
34
|
+
new Uint8Array();
|
|
35
|
+
return { bytes: b.length, sha256: sha256Hex(b) };
|
|
36
|
+
};
|
|
37
|
+
/** `versions/lock.json`: null when there is none; else parsed, or why it does not parse. */
|
|
38
|
+
export function readLock(paperDir, files) {
|
|
39
|
+
const b = files.readBytes(callerPath(join(versionsDir(paperDir), LOCK_FILE)));
|
|
40
|
+
if (b === null)
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
return parseLock(JSON.parse(new TextDecoder().decode(b)));
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return err(`not JSON: ${messageOf(e)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** The entries of a folder, each a file or a folder, by name. */
|
|
50
|
+
export const entriesIn = (dir, deps) => [...deps.listDir(callerPath(dir))].sort().map((name) => ({
|
|
51
|
+
name,
|
|
52
|
+
kind: deps.files.isFile(callerPath(join(dir, name))) ? "file" : "dir",
|
|
53
|
+
}));
|
|
54
|
+
/** The paper's cycles, when it keeps them and they read; null for the flat form or a broken record. */
|
|
55
|
+
export function cyclesIn(paperDir, files) {
|
|
56
|
+
const r = readCycles(paperDir, { files });
|
|
57
|
+
return r.ok ? r.value : null;
|
|
58
|
+
}
|
|
59
|
+
/** paper.tex, assembled as the last build read it (its includes spliced in); null without one. */
|
|
60
|
+
export function draftText(paperDir, deps) {
|
|
61
|
+
const main = join(paperDir, MAIN);
|
|
62
|
+
const src = decoded(deps.files.readBytes(callerPath(main)));
|
|
63
|
+
return src === null ? null : readPaper(main, src, deps).text;
|
|
64
|
+
}
|
|
65
|
+
/** The draft against the frozen source of the paper's attempt: none, nothing frozen, the same, or drifted. */
|
|
66
|
+
export function draftDrift(paperDir, cycles, draft, files) {
|
|
67
|
+
const base = driftBase(cycles.current, hasFrozen(paperDir, files));
|
|
68
|
+
if (base === null)
|
|
69
|
+
return { kind: "none" };
|
|
70
|
+
const frozen = frozenText(paperDir, base.key, files);
|
|
71
|
+
if (frozen === null)
|
|
72
|
+
return { kind: "no-base", key: base.key };
|
|
73
|
+
const drift = lineDrift(frozen, draft);
|
|
74
|
+
return drift.added === 0 && drift.removed === 0
|
|
75
|
+
? { kind: "same", key: base.key }
|
|
76
|
+
: { kind: "drift", key: base.key, drift };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The version → state table of the paper in `paperDir`: each attempt's frozen files and what they
|
|
80
|
+
* are, then the draft. Or why there is none: the paper keeps no `cycles`.
|
|
81
|
+
*/
|
|
82
|
+
export function versionLines(paperDir, deps) {
|
|
83
|
+
const cycles = cyclesIn(paperDir, deps.files);
|
|
84
|
+
if (cycles === null)
|
|
85
|
+
return err('the paper keeps no "cycles" in paperlint.json (or they do not read — `paperlint lint` says why): versions are kept per attempt, versions/<cycle>/<stage>.pdf');
|
|
86
|
+
const lock = readLock(paperDir, deps.files);
|
|
87
|
+
const draft = draftText(paperDir, deps);
|
|
88
|
+
return ok(versionTable(versionRows({
|
|
89
|
+
cycles: cycles.list,
|
|
90
|
+
has: hasFrozen(paperDir, deps.files),
|
|
91
|
+
lock: lock?.ok === true ? lock.value : {},
|
|
92
|
+
draft: draft === null
|
|
93
|
+
? { kind: "none" }
|
|
94
|
+
: draftDrift(paperDir, cycles, draft, deps.files),
|
|
95
|
+
})));
|
|
96
|
+
}
|
|
97
|
+
const refusal = (why, cycle) => `refused: ${why}, and the two could not be frozen together in versions/${cycle}/ — rebuild (\`paperlint build\`), run the dry run again, then --save; nothing was sent`;
|
|
98
|
+
/** Why the source on disk cannot be frozen as the PDF's; null when the build's record says it can. */
|
|
99
|
+
function sourceProblem(paperDir, deps) {
|
|
100
|
+
const record = paperRecord(paperDir, deps);
|
|
101
|
+
if (record.kind === "none")
|
|
102
|
+
return err("no record of the build (_build/sources.json), so which source the PDF was built from is not known");
|
|
103
|
+
if (record.kind === "stale")
|
|
104
|
+
return err(`${record.changed.map((x) => x.path).join(", ")} changed since the build, so the PDF is not what the source builds now`);
|
|
105
|
+
const source = draftText(paperDir, deps);
|
|
106
|
+
return source === null
|
|
107
|
+
? err(`no ${MAIN} to freeze beside the PDF`)
|
|
108
|
+
: ok(source);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Before a `--save` sends: what it will freeze — the open cycle's `submitted`, or the accepted one's
|
|
112
|
+
* `camera-ready` — and the source to freeze beside the PDF. Refused when the build's record does not
|
|
113
|
+
* say the source on disk is the one the PDF was built from: a PDF frozen with another source is a
|
|
114
|
+
* pair that lies.
|
|
115
|
+
*/
|
|
116
|
+
export function freezePlan(paperDir, cycles, deps) {
|
|
117
|
+
const c = cycles === null ? null : venueCycleOf(cycles.current);
|
|
118
|
+
if (cycles === null || c === null)
|
|
119
|
+
return ok({ kind: "flat" });
|
|
120
|
+
const source = sourceProblem(paperDir, deps);
|
|
121
|
+
return source.ok
|
|
122
|
+
? ok({
|
|
123
|
+
kind: "freeze",
|
|
124
|
+
cycle: c.id,
|
|
125
|
+
stage: cycles.current.kind === "accepted" ? "camera-ready" : "submitted",
|
|
126
|
+
source: source.value,
|
|
127
|
+
})
|
|
128
|
+
: err(refusal(source.error, c.id));
|
|
129
|
+
}
|
|
130
|
+
const lockEntry = (bytes, frozen) => ({
|
|
131
|
+
kind: "file",
|
|
132
|
+
bytes: bytes.length,
|
|
133
|
+
sha256: sha256Hex(bytes),
|
|
134
|
+
frozen,
|
|
135
|
+
});
|
|
136
|
+
/** The lock with the two frozen files recorded, written; or why the lock on disk does not read. */
|
|
137
|
+
function lockSent(s, entries, deps) {
|
|
138
|
+
const lock = readLock(s.paperDir, deps.files) ?? ok({});
|
|
139
|
+
if (!lock.ok)
|
|
140
|
+
return lock;
|
|
141
|
+
deps.files.writeAtomic(callerPath(join(versionsDir(s.paperDir), LOCK_FILE)), new TextEncoder().encode(lockText({ ...lock.value, ...entries })));
|
|
142
|
+
return ok(null);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* After the portal took the PDF: the PDF and its source written to `versions/<cycle>/<stage>.*`,
|
|
146
|
+
* replacing an earlier upload's, and recorded in the lock. The line to print; an error line when the
|
|
147
|
+
* lock does not read (the files are frozen, the lock left as it is).
|
|
148
|
+
*/
|
|
149
|
+
export function freezeSent(s, deps) {
|
|
150
|
+
const { cycle, stage } = s.plan;
|
|
151
|
+
const source = new TextEncoder().encode(s.plan.source);
|
|
152
|
+
const pdfKey = frozenKey(cycle, stage, "pdf");
|
|
153
|
+
const texKey = frozenKey(cycle, stage, "tex");
|
|
154
|
+
deps.files.writeAtomic(callerPath(frozenPath(s.paperDir, pdfKey)), s.pdf);
|
|
155
|
+
deps.files.writeAtomic(callerPath(frozenPath(s.paperDir, texKey)), source);
|
|
156
|
+
const frozen = deps.now();
|
|
157
|
+
const locked = lockSent(s, { [pdfKey]: lockEntry(s.pdf, frozen), [texKey]: lockEntry(source, frozen) }, deps);
|
|
158
|
+
return locked.ok
|
|
159
|
+
? ok(`frozen versions/${pdfKey} (${String(s.pdf.length)} bytes) and ${stage}.tex (${String(source.length)} bytes), recorded in versions/lock.json`)
|
|
160
|
+
: err(`versions/lock.json does not read (${locked.error}): versions/${pdfKey} and ${stage}.tex are frozen, not recorded — \`npx paperlint versions lock ${s.shownPaper}\` records them`);
|
|
161
|
+
}
|
|
162
|
+
/** What a send in the flat form says instead of a freeze. */
|
|
163
|
+
export const NOT_FROZEN = 'not frozen the paper keeps no "cycles" in paperlint.json: versions are frozen per attempt, in versions/<cycle>/ (docs/configuration.md, "The paper\'s cycles")';
|
|
164
|
+
//# sourceMappingURL=versions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.js","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"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"}
|
package/docs/configuration.md
CHANGED
|
@@ -164,7 +164,7 @@ is refused as a second source for one fact.
|
|
|
164
164
|
"pdf": "build/paper.pdf",
|
|
165
165
|
"cycles": [
|
|
166
166
|
{
|
|
167
|
-
"id": "alpha-2026", //
|
|
167
|
+
"id": "alpha-2026", // the name of its folder in versions/
|
|
168
168
|
"venue": { "kind": "preset", "extends": "paperlint:acm-sigconf" },
|
|
169
169
|
"kind": "short",
|
|
170
170
|
"opened": "2026-07-01", // the day this attempt was decided
|
|
@@ -225,7 +225,7 @@ is refused as a second source for one fact.
|
|
|
225
225
|
|
|
226
226
|
| field | what it is |
|
|
227
227
|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
228
|
-
| `id` | unique in the list;
|
|
228
|
+
| `id` | unique in the list; the name of the attempt's folder in `versions/` ([`versions/layout`](rules/versions/layout.md)), so one folder name: lowercase letters, digits and hyphens, starting with a letter or a digit (`msr-2027`). Anything else (`conf/2026`, `..`, `Conf-2026`) is refused when the record is read |
|
|
229
229
|
| `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 |
|
|
230
230
|
| `kind` | a key of the preset's `format.kinds`; omit when the venue has one kind |
|
|
231
231
|
| `opened` | `YYYY-MM-DD`, the day this attempt was decided — the boundary a consumer judges the scorecard's rows against |
|
|
@@ -242,6 +242,80 @@ camera-ready and talk are judged against it, and the venue's blindness no longer
|
|
|
242
242
|
|
|
243
243
|
Papers without `cycles` read exactly as before.
|
|
244
244
|
|
|
245
|
+
### The frozen versions: one folder per attempt
|
|
246
|
+
|
|
247
|
+
A paper that keeps `cycles` freezes each version it sends in `versions/<cycle id>/`, one file per
|
|
248
|
+
stage. No date is typed into a name: when a file was frozen is in `versions/lock.json`, which
|
|
249
|
+
paperlint writes.
|
|
250
|
+
|
|
251
|
+
```text
|
|
252
|
+
versions/
|
|
253
|
+
├── alpha-2026/ ← rejected: historical
|
|
254
|
+
│ ├── submitted.pdf
|
|
255
|
+
│ └── submitted.tex
|
|
256
|
+
├── beta-2027/ ← open: CURRENT, under review
|
|
257
|
+
│ ├── submitted.pdf
|
|
258
|
+
│ └── submitted.tex
|
|
259
|
+
└── lock.json ← generated: bytes, sha256 and when each file was frozen
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Each version's state follows from its cycle's `outcome`, never typed: a `submitted` version of the
|
|
263
|
+
open cycle is under review; anything in a rejected or withdrawn cycle's folder is historical; an
|
|
264
|
+
accepted cycle's `submitted` is the camera-ready's base, and its `camera-ready` is final. When a
|
|
265
|
+
decision arrives you change one value, the cycle's `outcome`, and every state follows.
|
|
266
|
+
`npx paperlint versions` prints the table, and `paperlint submission show` prints it after the
|
|
267
|
+
portal's copy:
|
|
268
|
+
|
|
269
|
+
```text
|
|
270
|
+
beta-2027 submitted CURRENT (under review at paperlint:ieee-conference) 186504 B frozen 2026-10-23 16:58 UTC
|
|
271
|
+
alpha-2026 submitted historical (rejected) 305412 B frozen 2026-07-22
|
|
272
|
+
paper.tex draft target not chosen differs from beta-2027/submitted.tex: 18 lines added, 18 removed
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`paperlint submission new --save` and `submission update --save` freeze what they send: the exact
|
|
276
|
+
PDF, and the source the build read — a paper split into `sections/` is frozen whole, its includes
|
|
277
|
+
spliced in — as `versions/<cycle>/submitted.{pdf,tex}`, or `camera-ready.{pdf,tex}` once the cycle is
|
|
278
|
+
accepted, and record both in the lock. A later save in the same cycle replaces them: the last upload
|
|
279
|
+
is the one under review. A save whose sources changed since the build is refused before anything is
|
|
280
|
+
sent, because the PDF and the source on disk would no longer be a pair. A file you put there yourself
|
|
281
|
+
(an arXiv copy, an upload made on the portal by hand) is recorded with `npx paperlint versions lock`.
|
|
282
|
+
|
|
283
|
+
**Between venues.** Freeze at the send — `--save` does it. Keep editing `paper.tex`: it is always the
|
|
284
|
+
draft, the only file you edit, and [`cycle/draft-drift`](rules/cycle/draft-drift.md) says how far it
|
|
285
|
+
has moved from what the reviewers read, so a reply to them quotes the frozen file. Open the next
|
|
286
|
+
cycle only once the current one has an outcome (one open cycle at most). If the venue accepts, the
|
|
287
|
+
camera-ready starts from the frozen `submitted.tex`, not from the drifted draft; once the camera-ready
|
|
288
|
+
is sent and frozen, [`cycle/camera-ready-source`](rules/cycle/camera-ready-source.md) checks that it
|
|
289
|
+
kept at least half of the accepted source's lines. A cycle that has a submission and nothing frozen is
|
|
290
|
+
[`cycle/submitted-frozen`](rules/cycle/submitted-frozen.md).
|
|
291
|
+
|
|
292
|
+
**From the `stages:` list.** A paper that recorded its frozen versions as a `stages:` list in
|
|
293
|
+
`PIPELINE-STATUS.md`, with dated files in `versions/`, moves to the folders with one command:
|
|
294
|
+
|
|
295
|
+
```console
|
|
296
|
+
$ npx paperlint versions migrate papers/my-paper
|
|
297
|
+
moved versions/2026-07-22-submitted.pdf → versions/alpha-2026/submitted.pdf
|
|
298
|
+
moved versions/2026-07-22-submitted.tex → versions/alpha-2026/submitted.tex
|
|
299
|
+
recorded versions/lock.json, 2 entries
|
|
300
|
+
removed the stages: list from PIPELINE-STATUS.md — the folders and the lock are the record now
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Each entry's `cycle:` names its folder, its `bytes:` and `sourceBytes:` are checked against the
|
|
304
|
+
files once more, its `date:` becomes the day the lock records, and `sourceLost: true` becomes a lock
|
|
305
|
+
entry that records the loss. An entry without a `cycle:`, a file that is no longer the one declared,
|
|
306
|
+
or a dated file no entry names stops the whole migration before anything moves, with what to do;
|
|
307
|
+
so does a destination that is already there, such as a version `submission update --save` froze
|
|
308
|
+
while the list was still in place. A source two stages were built from (a `submitted` and an `arxiv`
|
|
309
|
+
sharing one `.tex`) is copied to each. Paths in the list are read in one spelling (`./versions/a.tex` is `versions/a.tex`), and one
|
|
310
|
+
outside the paper folder (`../a.tex`) is refused. Before anything moves, the whole plan is replayed on
|
|
311
|
+
the paths it touches: a plan that could stop half-way is refused with nothing moved.
|
|
312
|
+
Any other loose file at the top of `versions/` (a withdrawn `STALE` copy, a note on the old
|
|
313
|
+
convention) moves to `extra/versions/`, since [`versions/layout`](rules/versions/layout.md) allows
|
|
314
|
+
nothing there beside `lock.json`. The YAML comments inside the list leave with it: read them first,
|
|
315
|
+
since a provenance note kept there (which commit a source came from, whether a PDF is the portal's
|
|
316
|
+
bytes or a rebuild) belongs in the cycle's evidence or the scorecard's prose. A paper without
|
|
317
|
+
`cycles` keeps its `stages:` list: `paper/stages` and `paper/source` still read it.
|
|
318
|
+
|
|
245
319
|
### Deadlines
|
|
246
320
|
|
|
247
321
|
A deadline records **what each page said**, and optionally **a human decision that overrides it**:
|
package/docs/layout.md
CHANGED
|
@@ -27,8 +27,8 @@ flowchart TB
|
|
|
27
27
|
end
|
|
28
28
|
S4["<b>4 · draft</b><br/>draft-paper · argument-arc · tighten-paper<br/>writes: paper.tex, sections/, figures/<br/>checked by: the tex/* rules<br/>hook paper-edit-guard: no shell writes"]
|
|
29
29
|
S5["<b>5 · review</b><br/>grade-paper-writing · verify-citations<br/>pc-panel-review · harden-paper<br/>study-accepted-papers, mode C or D<br/>writes: reviews/, the scorecard<br/>in PIPELINE-STATUS.md<br/>checked by: review/frontmatter"]
|
|
30
|
-
S6["<b>6 · submit</b><br/>paperlint build · submit-paper<br/>writes: paper.pdf, _build/,<br/>versions/, submission/<br/>checked by: format/page-limit, pdf/*,<br/>anonymity/identity,
|
|
31
|
-
S7["<b>7 · camera-ready</b><br/>camera-ready<br/>writes: versions/, talk/<br/>checked by:
|
|
30
|
+
S6["<b>6 · submit</b><br/>paperlint build · submit-paper<br/>writes: paper.pdf, _build/,<br/>versions/, submission/<br/>checked by: format/page-limit, pdf/*,<br/>anonymity/identity, versions/layout"]
|
|
31
|
+
S7["<b>7 · camera-ready</b><br/>camera-ready<br/>writes: versions/, talk/<br/>checked by: versions/layout, talk/*"]
|
|
32
32
|
S8["<b>8 · extend</b><br/>extend-paper<br/>writes: a new paper folder"]
|
|
33
33
|
N --> S1 --> S2 --> D
|
|
34
34
|
Y --> S4 --> S5 --> S6 --> S7 --> S8
|
|
@@ -64,7 +64,7 @@ creates one.
|
|
|
64
64
|
├── paper.pdf ← the built PDF
|
|
65
65
|
├── *.tex *.bib *.cls *.sty *.bst ← what TeX and bibtex look up beside paper.tex
|
|
66
66
|
├── _build/ ← the facts `paperlint build` writes
|
|
67
|
-
├── versions/ ← each stage's frozen PDF and source
|
|
67
|
+
├── versions/ ← each stage's frozen PDF and source — per attempt, versions/<cycle>/, with lock.json
|
|
68
68
|
├── reviews/ ← dated reviews and pass reports
|
|
69
69
|
├── siblings/ ← the cards of competing papers
|
|
70
70
|
├── research/ ← dated research and decision records
|
|
@@ -102,7 +102,7 @@ Both are explained in [`venues.md`](venues.md).
|
|
|
102
102
|
| ------------------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
103
103
|
| `<papers-root>/venues/<venue>/` | 2 venue | `study-accepted-papers`, its first run for that venue (mode A, usually) | nothing ([why](venues.md#what-checks-it)) |
|
|
104
104
|
| the paper folder's root | before 1 | `paperlint new`: the three files, and an empty `.gitkeep` folder for `reviews/`, `research/`, `figures/`, `versions/`, `submission/`, `repro/tools/`, `repro/data/` (`repro/` gets a stub index) | [`paper/allowed-files-only`](rules/paper/allowed-files-only.md); the hook `paper-folder-layout` refuses a new stray entry |
|
|
105
|
-
| `PIPELINE-STATUS.md` | every stage | `paperlint new`, then `paper-pipeline`, `paper-status`; `study-accepted-papers` (the scorecard) | `paper/stages`, `paper/source`, [`cycle/
|
|
105
|
+
| `PIPELINE-STATUS.md` | every stage | `paperlint new`, then `paper-pipeline`, `paper-status`; `study-accepted-papers` (the scorecard) | `paper/stages`, `paper/source`, [`cycle/submitted-frozen`](rules/cycle/submitted-frozen.md) ([rules](rules.md)) |
|
|
106
106
|
| `paperlint.json` | before 1, then 2 | `paperlint new`; `find-venue` names the venue | `paperlint lint` refuses an unknown key; [`cycle/record`](rules/cycle/record.md) |
|
|
107
107
|
| `siblings/` | 1 idea | `map-prior-work`, `analyze-sibling-paper` | `sibling/frontmatter` ([records](configuration.md#records-in-frontmatter-checked-by-shipped-json-schemas)) |
|
|
108
108
|
| `reviews/` | 2, then 4–5 | `study-accepted-papers` (its report), `grade-paper-writing`, `cold-read-diff`, the review skills | `review/frontmatter` ([records](configuration.md#records-in-frontmatter-checked-by-shipped-json-schemas)) |
|
|
@@ -119,7 +119,7 @@ Both are explained in [`venues.md`](venues.md).
|
|
|
119
119
|
| `paper.tex`, `sections/`, `*.bib` | 4 draft | `draft-paper`, `argument-arc`, `tighten-paper` | the `tex/*`, `paper/*` and `bib/*` rules ([rules](rules.md)); the hook `paper-edit-guard` refuses a shell write |
|
|
120
120
|
| `CLAIMS.md`, `figures/` | 4 draft | you | nothing |
|
|
121
121
|
| `paper.pdf`, `_build/` | 6 submit | `paperlint build` | `pdf/*`, [`format/page-limit`](rules/format/page-limit.md), [`anonymity/identity`](rules/anonymity/identity.md), the reference rules |
|
|
122
|
-
| `versions/` | 6, then 7 | you, at submission and at camera-ready (`submit-paper` says how): the frozen PDF and source of each stage | `paper/stages`, `paper/source`
|
|
122
|
+
| `versions/` | 6, then 7 | you, at submission and at camera-ready (`submit-paper` says how): the frozen PDF and source of each stage | [`versions/layout`](rules/versions/layout.md) with `cycles`; `paper/stages`, `paper/source` without |
|
|
123
123
|
| `submission/`, `SUBMIT-CHECKLIST.md` | 6 submit | you, while `submit-paper` and `submission-portal` do the submitting: forms, portal records, mail with the venue | [`cycle/evidence`](rules/cycle/evidence.md), for the files a closed cycle names |
|
|
124
124
|
| `talk/` | 7 camera-ready | you | the `talk/*` rules ([talk](talk.md)) |
|
|
125
125
|
| `extra/` | any | you | nothing, ever |
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# cycle/camera-ready-source
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** `paperlint.json` (`cycles`), `versions/<accepted cycle>/submitted.tex` and
|
|
4
|
+
`camera-ready.tex` · **Reported on:** the first line of `paper.tex`
|
|
5
|
+
|
|
6
|
+
## What it catches
|
|
7
|
+
|
|
8
|
+
The last cycle is `accepted`, its camera-ready is frozen (`versions/<cycle>/camera-ready.tex`, written
|
|
9
|
+
by `npx paperlint submission update <paper> --save`), and that frozen source keeps less than half of the
|
|
10
|
+
lines of what was accepted, the cycle's frozen `submitted.tex`. The message names both files and the
|
|
11
|
+
count.
|
|
12
|
+
|
|
13
|
+
Until the camera-ready is frozen the rule is silent: while it is being edited, `paper.tex` differs from
|
|
14
|
+
what was accepted by design, since the reviewers' changes go in.
|
|
15
|
+
|
|
16
|
+
## Why
|
|
17
|
+
|
|
18
|
+
The camera-ready is what the venue accepted plus the changes its reviewers asked for. Between the upload
|
|
19
|
+
and the decision `paper.tex` usually moved on (see [`cycle/draft-drift`](draft-drift.md)). Built from
|
|
20
|
+
that drifted draft, the camera-ready would send the venue a paper its reviewers never read.
|
|
21
|
+
|
|
22
|
+
A camera-ready differs from what was accepted, so any difference cannot be the test. The rule counts
|
|
23
|
+
the accepted source's lines the camera-ready keeps unchanged, with the same line diff as
|
|
24
|
+
`cycle/draft-drift`: one that keeps at least half started from the accepted source; one that keeps less
|
|
25
|
+
was built from something else.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
Failing: cycle `open-2027` is accepted, and its frozen camera-ready was built from a draft that moved
|
|
30
|
+
on:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
papers/my-paper/paper.tex
|
|
34
|
+
1:1 error cycle «open-2027» is accepted, and its frozen camera-ready,
|
|
35
|
+
`versions/open-2027/camera-ready.tex`, keeps 3 of the 13 lines of what was accepted,
|
|
36
|
+
`versions/open-2027/submitted.tex` — under half, so it was not built from the accepted
|
|
37
|
+
source. … cycle/camera-ready-source
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Passing:
|
|
41
|
+
|
|
42
|
+
- an accepted cycle whose camera-ready is not frozen yet, however far `paper.tex` has moved;
|
|
43
|
+
- a frozen camera-ready that is the accepted source plus the requested changes.
|
|
44
|
+
|
|
45
|
+
## Options / preset fields
|
|
46
|
+
|
|
47
|
+
No rule options.
|
|
48
|
+
|
|
49
|
+
## What it does not check
|
|
50
|
+
|
|
51
|
+
- Whether the changes are the ones the reviewers asked for. Half of the lines kept says the
|
|
52
|
+
camera-ready started from the accepted source, not that every change in it was requested.
|
|
53
|
+
- `paper.tex` after the camera-ready is frozen. What the venue holds is fixed; the draft is free to head
|
|
54
|
+
for the next paper.
|
|
55
|
+
- A camera-ready PDF frozen without its source: there is nothing to compare, and
|
|
56
|
+
[`versions/layout`](../versions/layout.md) names the missing source.
|
|
57
|
+
|
|
58
|
+
## How to fix
|
|
59
|
+
|
|
60
|
+
Copy `versions/<cycle>/submitted.tex` over `paper.tex`, make the changes the reviewers asked for, build,
|
|
61
|
+
and send again with `npx paperlint submission update <paper> --save`. That replaces the frozen
|
|
62
|
+
`camera-ready.{pdf,tex}`.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# cycle/draft-drift
|
|
2
|
+
|
|
3
|
+
**Level:** warn · **Reads:** `paper.tex` (with the files the last build spliced in), `paperlint.json`
|
|
4
|
+
(`cycles`), `versions/<open cycle>/submitted.tex` · **Reported on:** the first line of `paper.tex`
|
|
5
|
+
|
|
6
|
+
## What it catches
|
|
7
|
+
|
|
8
|
+
The last cycle is open, its `submitted.tex` is frozen, and `paper.tex` differs from it. The message
|
|
9
|
+
says by how much: the lines added and removed, as a line diff counts them.
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
`paper.tex` is always the draft; the frozen `submitted.tex` is what the venue's reviewers read. Between
|
|
14
|
+
the upload and the decision the draft keeps moving — towards the next venue, or answering what the
|
|
15
|
+
reviewers are likely to ask — and that is expected. What is not expected is to forget it: a reply to a
|
|
16
|
+
reviewer that quotes line numbers, or a rebuttal that quotes a sentence, has to quote the frozen file.
|
|
17
|
+
The warning keeps the distance visible. ESLint has no info level, so it is a warning: it prints and
|
|
18
|
+
does not fail the run.
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
Reported — the open cycle `open-2027` froze `submitted.tex`, and `paper.tex` changed since:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
papers/my-paper/paper.tex
|
|
26
|
+
1:1 warning paper.tex has moved on from `versions/open-2027/submitted.tex`, the version under
|
|
27
|
+
review at paperlint:acm-sigconf: 18 lines added, 18 removed — expected while the draft
|
|
28
|
+
heads for its next venue; … cycle/draft-drift
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Silent: `paper.tex` as frozen; no open cycle; or no frozen `submitted.tex` yet.
|
|
32
|
+
|
|
33
|
+
## Options / preset fields
|
|
34
|
+
|
|
35
|
+
No rule options. A paper that does not want the reminder turns it off in its `paperlint.json`:
|
|
36
|
+
`"rules": { "cycle/draft-drift": "off" }`.
|
|
37
|
+
|
|
38
|
+
## What it does not check
|
|
39
|
+
|
|
40
|
+
- Which of the changes matter: a changed number and a reflowed paragraph count the same.
|
|
41
|
+
- A paper split into several files is compared as the last build assembled it; when the build's record
|
|
42
|
+
is out of date (`paper/sources-fresh` says so), only `paper.tex` itself is compared.
|
|
43
|
+
|
|
44
|
+
## How to fix
|
|
45
|
+
|
|
46
|
+
Nothing to fix while the draft heads elsewhere. To answer the reviewers of the open cycle, quote
|
|
47
|
+
`versions/<cycle>/submitted.tex`.
|
|
@@ -65,8 +65,8 @@ reads, and compares the way that rule does.
|
|
|
65
65
|
are the format rules' to judge, and they judge again the moment the phase is dropped.
|
|
66
66
|
- A cycle declared `porting` whose class line is still the old one: that is the state the phase
|
|
67
67
|
describes.
|
|
68
|
-
- A `porting` cycle that has already sent a PDF — [`
|
|
69
|
-
|
|
68
|
+
- A `porting` cycle that has already sent a PDF — [`versions/layout`](../versions/layout.md) reports
|
|
69
|
+
a `submitted` or `camera-ready` PDF frozen in its folder.
|
|
70
70
|
|
|
71
71
|
## How to fix
|
|
72
72
|
|
|
@@ -144,7 +144,8 @@ scheduled workflow in paperlint's repository; your own preset may carry readings
|
|
|
144
144
|
- Whether a deadline has passed, or how long is left.
|
|
145
145
|
- A disagreement between sources — that is what the record holds; the earlier reading binds.
|
|
146
146
|
- Whether an override's evidence exists — that is [`cycle/evidence`](evidence.md).
|
|
147
|
-
- Whether
|
|
147
|
+
- Whether `versions/` holds a folder per cycle and nothing else — that is
|
|
148
|
+
[`versions/layout`](../versions/layout.md).
|
|
148
149
|
|
|
149
150
|
## How to fix
|
|
150
151
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# cycle/submitted-frozen
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** the paper's `paperlint.json` (`cycles`) and its `versions/` folder ·
|
|
4
|
+
**Reported on:** the first line of `PIPELINE-STATUS.md`
|
|
5
|
+
|
|
6
|
+
## What it catches
|
|
7
|
+
|
|
8
|
+
A cycle with `"submission": { "id": … }` — a PDF went to the venue's portal — and no
|
|
9
|
+
`versions/<cycle>/submitted.pdf`.
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
What the reviewers read is the one version an author has to be able to show again: a reviewer's
|
|
14
|
+
page and line numbers point into it, and a camera-ready starts from its source. Before this rule a
|
|
15
|
+
paper could record a submission on an open cycle, keep no frozen copy, and `paperlint lint` said
|
|
16
|
+
nothing; the only record of the text under review was a branch commit, which squash merges and
|
|
17
|
+
`git gc` destroy.
|
|
18
|
+
|
|
19
|
+
Since this rule, `paperlint submission new --save` and `submission update --save` freeze the exact
|
|
20
|
+
PDF they send, with the source it was built from, so the rule fires only for an upload made another
|
|
21
|
+
way (by hand on the portal, or before paperlint froze on send).
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
Failing — `paperlint.json` has
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"cycles": [
|
|
30
|
+
{
|
|
31
|
+
"id": "open-2027",
|
|
32
|
+
"venue": { "kind": "preset", "extends": "paperlint:acm-sigconf" },
|
|
33
|
+
"opened": "2026-09-09",
|
|
34
|
+
"submission": { "id": 7 }
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
and `versions/open-2027/` is empty:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
papers/my-paper/PIPELINE-STATUS.md
|
|
44
|
+
1:1 error cycle «open-2027» has submission 7 on the portal, and `versions/open-2027/submitted.pdf`
|
|
45
|
+
is not there: what the reviewers read is not frozen. … cycle/submitted-frozen
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Passing: the same cycle with `versions/open-2027/submitted.pdf` on disk; or a cycle with no
|
|
49
|
+
`submission` yet (still preparing).
|
|
50
|
+
|
|
51
|
+
## Options / preset fields
|
|
52
|
+
|
|
53
|
+
No rule options.
|
|
54
|
+
|
|
55
|
+
## What it does not check
|
|
56
|
+
|
|
57
|
+
- The source beside the PDF, the lock, the folder's names — [`versions/layout`](../versions/layout.md) does.
|
|
58
|
+
- That the frozen PDF is the one the portal holds: `paperlint submission show` compares sha256s.
|
|
59
|
+
|
|
60
|
+
## How to fix
|
|
61
|
+
|
|
62
|
+
Send with `npx paperlint submission update <paper> --save`: it freezes what it sends. For an upload
|
|
63
|
+
made before, put the PDF the portal holds at `versions/<cycle>/submitted.pdf` (`submission show`
|
|
64
|
+
prints its size and sha256 to check against), the source it was built from as `submitted.tex`, and
|
|
65
|
+
run `npx paperlint versions lock <paper>`.
|
|
@@ -29,7 +29,7 @@ it: [the map](../../layout.md).
|
|
|
29
29
|
├── paper.pdf ← the built PDF
|
|
30
30
|
├── *.tex *.bib *.cls *.sty *.bst ← what TeX and bibtex look up beside paper.tex
|
|
31
31
|
├── _build/ ← the facts `paperlint build` writes
|
|
32
|
-
├── versions/ ← each stage's frozen PDF and source
|
|
32
|
+
├── versions/ ← each stage's frozen PDF and source — per attempt, versions/<cycle>/, with lock.json
|
|
33
33
|
├── reviews/ ← dated reviews and pass reports
|
|
34
34
|
├── siblings/ ← the cards of competing papers
|
|
35
35
|
├── research/ ← dated research and decision records
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# versions/layout
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** the paper's `versions/` folder, `versions/lock.json` and `paperlint.json`
|
|
4
|
+
(`cycles`) · **Reported on:** the first line of `PIPELINE-STATUS.md`
|
|
5
|
+
|
|
6
|
+
## What it catches
|
|
7
|
+
|
|
8
|
+
A paper that keeps `cycles` freezes each version it sends in one folder per attempt:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
versions/
|
|
12
|
+
├── alpha-2026/ ← a cycle id in paperlint.json
|
|
13
|
+
│ ├── submitted.pdf
|
|
14
|
+
│ └── submitted.tex
|
|
15
|
+
├── beta-2027/
|
|
16
|
+
│ ├── submitted.pdf
|
|
17
|
+
│ ├── submitted.tex
|
|
18
|
+
│ └── camera-ready.pdf ← only after the attempt is accepted
|
|
19
|
+
└── lock.json ← written by paperlint: bytes, sha256, when each file was frozen
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The rule reports:
|
|
23
|
+
|
|
24
|
+
- an entry of `versions/` that is neither `lock.json` nor a folder: a dated file from the layout
|
|
25
|
+
before folders per attempt (`2026-07-22-submitted.pdf`) is named with the command that moves it;
|
|
26
|
+
- a folder that names no cycle in `paperlint.json`;
|
|
27
|
+
- a file in a cycle's folder that is not `<stage>.pdf` or `<stage>.tex`, the stage one of
|
|
28
|
+
`submitted`, `camera-ready`, `arxiv`;
|
|
29
|
+
- a frozen PDF without its source beside it, unless the lock records the source as lost;
|
|
30
|
+
- a `submitted` or `camera-ready` PDF in a cycle whose `phase` is still `porting`;
|
|
31
|
+
- frozen files and no lock, a lock that does not read, a frozen file the lock does not record, a file
|
|
32
|
+
whose bytes or digest are not the ones the lock recorded, and a lock entry whose file is gone.
|
|
33
|
+
|
|
34
|
+
Names and folders starting with `.` (`.gitkeep`) are not judged. A paper without `cycles` is not
|
|
35
|
+
judged either: its `stages:` list in `PIPELINE-STATUS.md` is still its record, checked by
|
|
36
|
+
`paper/stages` and `paper/source`.
|
|
37
|
+
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
The folder and the file name are the record: which attempt a file belongs to, and at which stage it
|
|
41
|
+
was frozen. What state it is in follows from the attempt's `outcome` in `paperlint.json` — under
|
|
42
|
+
review while the cycle is open, historical once it is rejected or withdrawn, the camera-ready's base
|
|
43
|
+
once accepted. A name outside the convention is a version whose state nobody can read off.
|
|
44
|
+
|
|
45
|
+
A frozen file is never supposed to change. The lock records its bytes and digest when paperlint froze
|
|
46
|
+
it, so a file that was replaced afterwards — the PDF a reviewer's page numbers point into, the source
|
|
47
|
+
their line numbers point into — is caught instead of trusted. The lock is generated, never typed:
|
|
48
|
+
`paperlint submission new|update --save` writes it when it freezes what it sent, and
|
|
49
|
+
`paperlint versions lock` records a file put there by hand.
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
Failing — `paperlint.json` declares the cycle `open-2027`, and `versions/` still holds a file of the
|
|
54
|
+
old layout:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
papers/my-paper/PIPELINE-STATUS.md
|
|
58
|
+
1:1 error `versions/2026-07-22-submitted.pdf` is not part of the layout — a frozen version in the
|
|
59
|
+
layout before folders per attempt: `npx paperlint versions migrate <paper>` moves it
|
|
60
|
+
into versions/<cycle>/ versions/layout
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Passing: `versions/open-2027/submitted.pdf` and `submitted.tex`, both recorded in `versions/lock.json`
|
|
64
|
+
as they stand.
|
|
65
|
+
|
|
66
|
+
## Options / preset fields
|
|
67
|
+
|
|
68
|
+
No rule options.
|
|
69
|
+
|
|
70
|
+
## What it does not check
|
|
71
|
+
|
|
72
|
+
- That a cycle with a submission froze anything at all — [`cycle/submitted-frozen`](../cycle/submitted-frozen.md) does.
|
|
73
|
+
- That the frozen source builds the frozen PDF: the lock records what was frozen together, and
|
|
74
|
+
`submission --save` freezes the source only when the build's record says it is the one the PDF was
|
|
75
|
+
built from.
|
|
76
|
+
- A lost source: the lock records it (`"sourceLost": true`) and `paperlint versions` shows it; there is
|
|
77
|
+
nothing left to fix, so it is not reported.
|
|
78
|
+
|
|
79
|
+
## How to fix
|
|
80
|
+
|
|
81
|
+
- A dated file: `npx paperlint versions migrate <paper>` moves every one named in `stages:` into its
|
|
82
|
+
cycle's folder, writes the lock and drops the list.
|
|
83
|
+
- A folder or file outside the convention: rename it to the cycle id or the stage, or move it to
|
|
84
|
+
`extra/`.
|
|
85
|
+
- A file put there by hand, or one replaced on purpose: `npx paperlint versions lock <paper>`.
|
|
86
|
+
- A file replaced by accident, or deleted: restore it from git.
|
|
87
|
+
- A PDF sent from a cycle still `porting`: the port is done — drop the phase
|
|
88
|
+
([`cycle/port-done`](../cycle/port-done.md) says when).
|