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/docs/rules.md
CHANGED
|
@@ -5,13 +5,16 @@ reads and when it fails. Errors fail `paperlint lint`; warnings print and do not
|
|
|
5
5
|
|
|
6
6
|
| Rule | Level | Reads | Fails when |
|
|
7
7
|
| ------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
-
| `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares
|
|
9
|
-
| `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it)
|
|
8
|
+
| `paper/stages` | error | `PIPELINE-STATUS.md` | a declared stage's PDF is missing, or the `bytes:` it names does not match the file's real size, or a frozen PDF exists that no stage declares. In a paper that keeps `cycles`, the `stages:` list is retired: one finding names `paperlint versions migrate`, and [`versions/layout`](rules/versions/layout.md) judges `versions/` |
|
|
9
|
+
| `paper/source` | error | `PIPELINE-STATUS.md` | a declared stage has no frozen `.tex` beside its PDF (a commit hash does not count — squash and gc destroy it). Silent in a paper that keeps `cycles`: there [`versions/layout`](rules/versions/layout.md) holds each PDF to its source |
|
|
10
10
|
| [`study/declare-sample`](rules/study/declare-sample.md) | experimental; off unless a paper turns it on (`"error"`) | each study's `repro/<study>/prereg.json` | a study's pre-registration does not declare its `population` (what the claim is about, source, size), the `strata` and what each of its `screens` removed per stratum value, its `coverage` (share 0–1 or null with a reason, and the strata it cannot reach), or the `author_check` of the drawn units (a doubtful verdict needs a recorded `decision`); reported on `PIPELINE-STATUS.md`, once per problem — [page](rules/study/declare-sample.md) |
|
|
11
11
|
| [`cycle/record`](rules/cycle/record.md) | error | `paperlint.json` (`cycles`) | the paper's attempts at venues (`cycles`, one entry per attempt) hold one source's deadlines out of order (registration ≤ submission ≤ resubmission < notification < camera-ready), or copy a portal reading the venue's preset carries; reported on `PIPELINE-STATUS.md`. A record that does not parse — a bare date, two open cycles, an unknown key — is refused by `paperlint lint` itself before any rule runs, exit 2 — [page](rules/cycle/record.md) |
|
|
12
12
|
| [`cycle/evidence`](rules/cycle/evidence.md) | error | `paperlint.json` (`cycles`) → the paper folder | a closed cycle (`accepted`, `rejected`, `withdrawn`) or a deadline's `override` names an `evidence` file — the decision mail, the reviews, the withdrawal note, the chairs' mail — that is not in the paper folder; reported on `PIPELINE-STATUS.md` — [page](rules/cycle/evidence.md) |
|
|
13
|
-
| [`cycle/stage`](rules/cycle/stage.md) | error | `PIPELINE-STATUS.md` → `paperlint.json` (`cycles`) | with `cycles` declared, a stage names no `cycle: <id>`, names one no cycle has, or a `submitted`/`camera-ready` stage belongs to a cycle whose `phase` is still `porting` — [page](rules/cycle/stage.md) |
|
|
14
13
|
| [`cycle/port-done`](rules/cycle/port-done.md) | error | `paper.tex` → `paperlint.json` (`cycles`) → the preset's `template` | the current cycle still declares `"phase": "porting"` (which silences the venue's format rules) while the `\documentclass` is already the venue's template: the port is done, drop the phase — [page](rules/cycle/port-done.md) |
|
|
14
|
+
| [`cycle/submitted-frozen`](rules/cycle/submitted-frozen.md) | error | `paperlint.json` (`cycles`) → `versions/` | a cycle has `"submission": { "id" }` — a PDF went to the portal — and no `versions/<cycle>/submitted.pdf`; `submission new | update --save`freeze what they send, so this fires for an upload made another way; reported on`PIPELINE-STATUS.md` — [page](rules/cycle/submitted-frozen.md) |
|
|
15
|
+
| [`cycle/draft-drift`](rules/cycle/draft-drift.md) | warn | `paper.tex` → `paperlint.json` (`cycles`) → `versions/<cycle>/submitted.tex` | the last cycle is open and `paper.tex` (as the last build assembled it) differs from the source frozen when its PDF was sent; says how many lines were added and removed — [page](rules/cycle/draft-drift.md) |
|
|
16
|
+
| [`cycle/camera-ready-source`](rules/cycle/camera-ready-source.md) | error | `paper.tex` → `paperlint.json` (`cycles`) → `versions/<cycle>/` | the last cycle is accepted, its camera-ready is frozen, and the frozen `camera-ready.tex` keeps less than half of the lines of the frozen `submitted.tex`, so it was not built from what was accepted; silent until the camera-ready is frozen — [page](rules/cycle/camera-ready-source.md) |
|
|
17
|
+
| [`versions/layout`](rules/versions/layout.md) | error | `versions/`, `versions/lock.json` → `paperlint.json` (`cycles`) | with `cycles` declared: an entry of `versions/` that is not `lock.json` or a folder named by a cycle id, a file in a cycle's folder that is not `<stage>.pdf`/`.tex` (`submitted`, `camera-ready`, `arxiv`), a PDF without its source (unless the lock records it lost), a PDF sent from a cycle still `porting`, or a frozen file that does not match `versions/lock.json`; reported on `PIPELINE-STATUS.md` — [page](rules/versions/layout.md) |
|
|
15
18
|
| [`paper/folder-venue-leftover`](rules/paper/folder-venue-leftover.md) | warn | the folder name → `paperlint.json` | the paper's folder is named after another shipped venue than the one it extends (`papers/aisec-2026/` for a paper now set for AIDC); reported on `PIPELINE-STATUS.md` — [page](rules/paper/folder-venue-leftover.md) |
|
|
16
19
|
| [`paper/allowed-files-only`](rules/paper/allowed-files-only.md) | error | the paper folder's root listing → `paperlint.json` | a file or folder at the paper's root is not part of the layout (`PLAN.md`, `archive/`, a stray PDF); reported on `PIPELINE-STATUS.md`, once per entry, with where it goes; `extra/` takes anything — [page](rules/paper/allowed-files-only.md) |
|
|
17
20
|
| [`repro/allowed-files-only`](rules/repro/allowed-files-only.md) | error | the listing of the paper's `repro/` | an entry at the top of `repro/` is not `README.md`, `studies.json`, `.gitignore`, `tools/`, `data/` or a study folder named `<id>-<slug>` (or, in a paper without the registry, `<slug>-YYYY-MM-DD`), or a study folder has no `README.md`; reported on `PIPELINE-STATUS.md`, once per entry, with where it goes by kind (a script → `repro/tools/`, data → `repro/data/`, a note → its study or `research/`) — [page](rules/repro/allowed-files-only.md) |
|
|
@@ -279,13 +282,24 @@ The paper body is `paper.tex`, and it gets three rules of its own: `paper/typogr
|
|
|
279
282
|
run on it but judge the files beside it. The scorecard and the review notes are Markdown files, and
|
|
280
283
|
the other five rules read those.
|
|
281
284
|
|
|
282
|
-
##
|
|
285
|
+
## Frozen versions: `versions/<cycle>/` and its lock
|
|
286
|
+
|
|
287
|
+
**A paper that keeps `cycles`** freezes what it sends without a manual step:
|
|
288
|
+
`paperlint submission new --save` and `submission update --save` write the exact PDF they sent and the
|
|
289
|
+
source it was built from into `versions/<cycle>/submitted.{pdf,tex}` (`camera-ready.*` once the cycle
|
|
290
|
+
is accepted), and record their bytes, sha256 and the time in `versions/lock.json`, a file paperlint
|
|
291
|
+
writes and nobody edits. [`versions/layout`](rules/versions/layout.md) then fails if a frozen file no
|
|
292
|
+
longer matches the lock, and `npx paperlint versions` prints each version's state. A file you freeze
|
|
293
|
+
yourself is recorded with `npx paperlint versions lock`. The layout, the states and the move from the
|
|
294
|
+
`stages:` list (`npx paperlint versions migrate`) are in
|
|
295
|
+
[configuration](configuration.md#the-frozen-versions-one-folder-per-attempt).
|
|
296
|
+
|
|
297
|
+
### Without `cycles`: the scorecard's `bytes:` and `sourceBytes:`
|
|
283
298
|
|
|
284
299
|
**When you submit,** copy the PDF and `paper.tex` you sent into the paper's `versions/` folder and
|
|
285
300
|
record the stage in the front matter of `PIPELINE-STATUS.md` (the paper's scorecard). From then on
|
|
286
301
|
`paper/stages` fails if that PDF goes missing or changes size, and `paper/source` if its source was
|
|
287
|
-
not kept.
|
|
288
|
-
([#100](https://github.com/zernie/paperlint/issues/100)). A stage looks like this:
|
|
302
|
+
not kept. A stage looks like this:
|
|
289
303
|
|
|
290
304
|
```yaml
|
|
291
305
|
---
|
|
@@ -301,8 +315,8 @@ stages:
|
|
|
301
315
|
```
|
|
302
316
|
|
|
303
317
|
A stage name may use `a-z`, `0-9`, `.`, `_` and `-`. You write `bytes:` and `sourceBytes:` yourself
|
|
304
|
-
(`wc -c < versions/2026-07-22-submitted.pdf`), or the agent does, once, when the stage is recorded
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
318
|
+
(`wc -c < versions/2026-07-22-submitted.pdf`), or the agent does, once, when the stage is recorded.
|
|
319
|
+
They are not a checksum to maintain: a frozen PDF is never supposed to change, so `bytes:`
|
|
320
|
+
disagreeing with the file means the file was replaced after it was declared, and that is exactly the
|
|
321
|
+
finding. If you really did re-freeze a stage, update the number in the same commit. Recording the
|
|
322
|
+
paper's attempts as `cycles` moves all of this to the folders and the generated lock.
|
package/docs/submission.md
CHANGED
|
@@ -88,6 +88,22 @@ against the venue's call for papers.
|
|
|
88
88
|
The safe order: `new`, `new --save` once; then `show`, `update`, `update --save`, and `show` again
|
|
89
89
|
until it says `match yes`. Every `--save` comes after the author has read the dry run and said yes.
|
|
90
90
|
|
|
91
|
+
## What a save freezes
|
|
92
|
+
|
|
93
|
+
In a paper that keeps `cycles`, a `new --save` or `update --save` the portal took freezes what it
|
|
94
|
+
sent: the exact PDF, and the source the build read (a paper split into several files, assembled), as
|
|
95
|
+
`versions/<cycle>/submitted.{pdf,tex}` — `camera-ready.{pdf,tex}` once the cycle is accepted — and
|
|
96
|
+
records both in `versions/lock.json`. It prints one line:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
frozen versions/beta-2027/submitted.pdf (186504 bytes) and submitted.tex (57210 bytes), recorded in versions/lock.json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A later save in the same cycle replaces them: the last upload is the one under review. `show` ends
|
|
103
|
+
with the table of the paper's versions and their states, as `paperlint versions` prints it. A paper
|
|
104
|
+
without `cycles` is sent as before, and the save says `not frozen`. The layout and the states:
|
|
105
|
+
[configuration](configuration.md#the-frozen-versions-one-folder-per-attempt).
|
|
106
|
+
|
|
91
107
|
## The check before sending
|
|
92
108
|
|
|
93
109
|
A person confirms every send. To help them decide, `new` and `update` first run the venue rules of
|
|
@@ -107,9 +123,11 @@ With no findings it prints one line, `venue rules at their default options: no e
|
|
|
107
123
|
run at paperlint's default levels and options: a `rules` override in your `paperlint.json` or
|
|
108
124
|
preset is applied by `paperlint lint`, not here.
|
|
109
125
|
|
|
110
|
-
The command refuses
|
|
111
|
-
of other bytes)
|
|
112
|
-
|
|
126
|
+
The command refuses two things by itself, each with exit 2 and nothing sent: `--save` when no build
|
|
127
|
+
facts describe this PDF (none, or of other bytes), and — in a paper that keeps `cycles` — `--save`
|
|
128
|
+
when the sources changed since the build, because the PDF and the source on disk would no longer be a
|
|
129
|
+
pair to freeze. Rebuild (`paperlint build`) and run the dry run again. Everything else, including over
|
|
130
|
+
the limit, is the person's call.
|
|
113
131
|
|
|
114
132
|
**The `submission-confirm` hook** (installed by `paperlint init`) stops any `submission new|update`
|
|
115
133
|
with `--save` or `--submit` until the person has said yes to that exact PDF in that exact folder. The flow:
|
|
@@ -139,3 +139,65 @@ test("a frontmatter node without a value (another markdown language) reads as em
|
|
|
139
139
|
stages.rules[rule].create(context).yaml({ type: "yaml" });
|
|
140
140
|
assert.deepEqual(reports, []);
|
|
141
141
|
});
|
|
142
|
+
|
|
143
|
+
const CYCLES = { "paperlint.json": JSON.stringify({ cycles: [] }) };
|
|
144
|
+
const RETIRED = (paper) =>
|
|
145
|
+
`\`stages\` is retired for a paper that keeps \`cycles\` in paperlint.json: its frozen versions are versions/<cycle>/<stage>.{pdf,tex}, recorded in versions/lock.json — \`npx paperlint versions migrate ${paper}\` moves the files there and takes the list out`;
|
|
146
|
+
|
|
147
|
+
test("under `cycles`: a `stages` list is one finding naming the migration, and dated files owe it nothing", () => {
|
|
148
|
+
const files = {
|
|
149
|
+
...CYCLES,
|
|
150
|
+
"versions/2026-08-02-submitted.pdf": "x",
|
|
151
|
+
};
|
|
152
|
+
assert.deepEqual(
|
|
153
|
+
lint(
|
|
154
|
+
"retired",
|
|
155
|
+
"stages:\n - stage: submitted\n date: 2026-08-02\n pdf: versions/2026-08-02-submitted.pdf\n bytes: 9",
|
|
156
|
+
{ files },
|
|
157
|
+
),
|
|
158
|
+
[RETIRED("retired")],
|
|
159
|
+
);
|
|
160
|
+
assert.deepEqual(
|
|
161
|
+
lint("retired-src", "stages:\n - stage: submitted\n date: 2026-08-02", {
|
|
162
|
+
rule: "source",
|
|
163
|
+
files,
|
|
164
|
+
}),
|
|
165
|
+
[],
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("under `cycles`: no list, an empty one, or no frontmatter is silent; YAML that does not parse is still named", () => {
|
|
170
|
+
assert.deepEqual(lint("retired-none", "title: x", { files: CYCLES }), []);
|
|
171
|
+
assert.deepEqual(lint("retired-empty", "stages: []", { files: CYCLES }), []);
|
|
172
|
+
const [msg] = lint("retired-yaml", "stages: [", { files: CYCLES });
|
|
173
|
+
assert.match(msg, /^the frontmatter does not parse as YAML: /);
|
|
174
|
+
// A paperlint.json that does not parse keeps the flat form's checks: `paperlint lint` refuses it before any rule runs.
|
|
175
|
+
assert.deepEqual(
|
|
176
|
+
lint("retired-broken", "stages:", {
|
|
177
|
+
files: { "paperlint.json": "{" },
|
|
178
|
+
}),
|
|
179
|
+
[
|
|
180
|
+
"`stages` must be a LIST of entries, not empty — a paper can reach the same stage twice",
|
|
181
|
+
],
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("under `cycles`: a frontmatter node without a value reads as empty YAML", () => {
|
|
186
|
+
const dir = writeTree(join(root, "retired-valueless"), CYCLES);
|
|
187
|
+
const reports = [];
|
|
188
|
+
const context = {
|
|
189
|
+
filename: join(dir, "PIPELINE-STATUS.md"),
|
|
190
|
+
cwd: dir,
|
|
191
|
+
report: (r) => reports.push(r),
|
|
192
|
+
};
|
|
193
|
+
stages.rules.stages.create(context).yaml({ type: "yaml" });
|
|
194
|
+
assert.deepEqual(reports, []);
|
|
195
|
+
// Linted from the paper itself, the command names it as `.`.
|
|
196
|
+
stages.rules.stages
|
|
197
|
+
.create(context)
|
|
198
|
+
.yaml({ type: "yaml", value: "stages:\n - stage: submitted" });
|
|
199
|
+
assert.deepEqual(
|
|
200
|
+
reports.map((r) => r.data),
|
|
201
|
+
[{ paper: "." }],
|
|
202
|
+
);
|
|
203
|
+
});
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
* keyed by stage name holds one. The `versions/<date>-<stage>.pdf` convention already holds
|
|
47
47
|
* many, and the field must not be weaker than the filenames it describes.
|
|
48
48
|
*/
|
|
49
|
-
import { existsSync, statSync, readdirSync } from "node:fs";
|
|
50
|
-
import { join, dirname } from "node:path";
|
|
49
|
+
import { existsSync, readFileSync, statSync, readdirSync } from "node:fs";
|
|
50
|
+
import { join, dirname, relative } from "node:path";
|
|
51
51
|
import assert from "node:assert/strict";
|
|
52
52
|
import { load } from "js-yaml";
|
|
53
53
|
import { z } from "zod";
|
|
@@ -68,6 +68,58 @@ const StageRecord = z
|
|
|
68
68
|
sourceLost: z.unknown().optional(),
|
|
69
69
|
})
|
|
70
70
|
.catch({});
|
|
71
|
+
/** What these rules read of the paper's settings: whether it keeps `cycles` at all. */
|
|
72
|
+
const Settings = z
|
|
73
|
+
.looseObject({ cycles: z.array(z.unknown()).optional() })
|
|
74
|
+
.catch({});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether the paper keeps `cycles` in its paperlint.json. Its frozen versions are then
|
|
78
|
+
* `versions/<cycle>/<stage>.{pdf,tex}` recorded in `versions/lock.json`, judged by `versions/layout`,
|
|
79
|
+
* and the `stages:` list is retired: `paperlint versions migrate` turns it into folders.
|
|
80
|
+
*/
|
|
81
|
+
function keepsCycles(dir: string): boolean {
|
|
82
|
+
try {
|
|
83
|
+
const raw: unknown = JSON.parse(
|
|
84
|
+
readFileSync(join(dir, "paperlint.json"), "utf8"),
|
|
85
|
+
);
|
|
86
|
+
return Settings.parse(raw).cycles !== undefined;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Under `cycles`: a non-empty `stages:` list is one finding, naming the command that retires it. */
|
|
93
|
+
function retired(context: RuleContext, dir: string) {
|
|
94
|
+
return {
|
|
95
|
+
yaml(node: { readonly value?: string }) {
|
|
96
|
+
const parsed = (() => {
|
|
97
|
+
try {
|
|
98
|
+
return { ok: true as const, data: load(node.value ?? "") };
|
|
99
|
+
} catch (e) {
|
|
100
|
+
assert.ok(e instanceof Error);
|
|
101
|
+
return { ok: false as const, reason: e.message };
|
|
102
|
+
}
|
|
103
|
+
})();
|
|
104
|
+
if (!parsed.ok) {
|
|
105
|
+
context.report({
|
|
106
|
+
node,
|
|
107
|
+
messageId: "badYaml",
|
|
108
|
+
data: { reason: parsed.reason },
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const raw = Frontmatter.parse(parsed.data).stages;
|
|
113
|
+
if (raw === undefined || (Array.isArray(raw) && raw.length === 0)) return;
|
|
114
|
+
context.report({
|
|
115
|
+
node,
|
|
116
|
+
messageId: "retired",
|
|
117
|
+
data: { paper: relative(context.cwd, dir) || "." },
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
71
123
|
const REQUIRED = ["date", "pdf", "bytes"] satisfies readonly (
|
|
72
124
|
"date" | "pdf" | "bytes"
|
|
73
125
|
)[];
|
|
@@ -126,6 +178,12 @@ function frozenPdfs(
|
|
|
126
178
|
return out;
|
|
127
179
|
}
|
|
128
180
|
|
|
181
|
+
/** The frozen PDFs in `versions/` that no declared stage of the same day names. */
|
|
182
|
+
const undeclared = (dir: string, records: readonly Declared[]) =>
|
|
183
|
+
frozenPdfs(join(dir, "versions")).filter(
|
|
184
|
+
(f) => !records.some((r) => r.stage === f.stage && r.date === f.date),
|
|
185
|
+
);
|
|
186
|
+
|
|
129
187
|
export default {
|
|
130
188
|
rules: {
|
|
131
189
|
stages: {
|
|
@@ -150,12 +208,14 @@ export default {
|
|
|
150
208
|
"«{{stage}}» ({{date}}): {{want}} bytes declared, {{got}} on disk — this is NOT that file",
|
|
151
209
|
fileNotDeclared:
|
|
152
210
|
"`versions/{{file}}` is frozen, but no «{{stage}}» stage on {{date}} is declared in `stages` — the artefact ran ahead of the declaration",
|
|
211
|
+
retired:
|
|
212
|
+
"`stages` is retired for a paper that keeps `cycles` in paperlint.json: its frozen versions are versions/<cycle>/<stage>.{pdf,tex}, recorded in versions/lock.json — `npx paperlint versions migrate {{paper}}` moves the files there and takes the list out",
|
|
153
213
|
},
|
|
154
214
|
},
|
|
155
215
|
create(context: RuleContext) {
|
|
156
216
|
const dir = dirname(context.filename);
|
|
217
|
+
if (keepsCycles(dir)) return retired(context, dir);
|
|
157
218
|
let declared: Declared[] | null = null; // null = there was no frontmatter at all
|
|
158
|
-
|
|
159
219
|
return {
|
|
160
220
|
yaml(node: { readonly value?: string }) {
|
|
161
221
|
let data: unknown;
|
|
@@ -252,16 +312,13 @@ export default {
|
|
|
252
312
|
// 🔴 On `root:exit` rather than inside `yaml`, because the case worth catching is
|
|
253
313
|
// precisely the one with NO frontmatter at all — where the `yaml` visitor never runs.
|
|
254
314
|
"root:exit"(node: object) {
|
|
255
|
-
|
|
256
|
-
for (const f of frozenPdfs(join(dir, "versions"))) {
|
|
257
|
-
if (records.some((r) => r.stage === f.stage && r.date === f.date))
|
|
258
|
-
continue;
|
|
315
|
+
undeclared(dir, declared ?? []).forEach((f) => {
|
|
259
316
|
context.report({
|
|
260
317
|
node,
|
|
261
318
|
messageId: "fileNotDeclared",
|
|
262
319
|
data: { file: f.name, stage: f.stage, date: f.date },
|
|
263
320
|
});
|
|
264
|
-
}
|
|
321
|
+
});
|
|
265
322
|
},
|
|
266
323
|
};
|
|
267
324
|
},
|
|
@@ -319,6 +376,8 @@ export default {
|
|
|
319
376
|
},
|
|
320
377
|
create(context: RuleContext) {
|
|
321
378
|
const dir = dirname(context.filename);
|
|
379
|
+
// Under `cycles` the lock records each frozen source, and `versions/layout` checks it.
|
|
380
|
+
if (keepsCycles(dir)) return {};
|
|
322
381
|
return {
|
|
323
382
|
yaml(node: { readonly value?: string }) {
|
|
324
383
|
let data: unknown;
|
|
@@ -55,6 +55,8 @@ export interface RuleReport {
|
|
|
55
55
|
|
|
56
56
|
export interface RuleContext {
|
|
57
57
|
readonly filename: string;
|
|
58
|
+
/** The directory ESLint runs from: what a command a message names is relative to. */
|
|
59
|
+
readonly cwd: string;
|
|
58
60
|
readonly options: readonly unknown[];
|
|
59
61
|
readonly settings: Readonly<Record<string, unknown>>;
|
|
60
62
|
readonly sourceCode: RuleSourceCode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperlint",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@unified-latex/unified-latex-util-replace": "^1.8.4",
|
|
31
31
|
"@unified-latex/unified-latex-util-visit": "^1.8.4",
|
|
32
32
|
"ajv": "^6.12.6",
|
|
33
|
+
"diff": "^8.0.4",
|
|
33
34
|
"eslint": "^10.9.1",
|
|
34
35
|
"js-yaml": "^4.1.0",
|
|
35
36
|
"markdown-it": "^14.3.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// AGENT'27 (the Second International Workshop on Agentic Engineering, an ICSE 2027 workshop) — a
|
|
2
|
+
// VENUE preset over the IEEE conference family. Everything the IEEEtran template decides (page
|
|
3
|
+
// size, columns, fonts, body size, TeX packages) comes from ieee-conference.jsonc; this file adds
|
|
4
|
+
// what AGENT's call for papers sets. Its card, in prose: `agent.md` beside this file.
|
|
5
|
+
//
|
|
6
|
+
// Verbatim from https://conf.researchr.org/home/icse-2027/agent-2027 (fetched 2026-10-26):
|
|
7
|
+
// «Full papers (research or experience): up to 8 pages, including references.»
|
|
8
|
+
// «Short papers (position, emerging research, tool, benchmark, or experience): up to 5 pages,
|
|
9
|
+
// including references.»
|
|
10
|
+
// «All submissions must be original, written in English, and formatted according to the IEEE
|
|
11
|
+
// conference proceedings template. Submissions will be reviewed single-anonymously. Accepted
|
|
12
|
+
// workshop papers will be published by IEEE. It is not possible to pay for extra pages.»
|
|
13
|
+
// «Workshop paper submission deadline: November 13, 2026 / Notification of acceptance: December
|
|
14
|
+
// 11, 2026 / Camera-ready deadline: January 29, 2027 / All dates are Anywhere on Earth (AoE).»
|
|
15
|
+
{
|
|
16
|
+
"type": "venue",
|
|
17
|
+
"name": "AGENT 2027",
|
|
18
|
+
// The call quoted above.
|
|
19
|
+
"url": "https://conf.researchr.org/home/icse-2027/agent-2027",
|
|
20
|
+
"extends": "paperlint:ieee-conference",
|
|
21
|
+
|
|
22
|
+
// «AGENT» alone is an ordinary word: as a name it would flag every paper folder called
|
|
23
|
+
// `agent-…` (paper/folder-venue-leftover compares folder names by words, and `mentions` reaches
|
|
24
|
+
// only the text rules). So the venue is «AGENT 2027» and «AGENT'27» (the call's own spelling,
|
|
25
|
+
// with either apostrophe). ICSE is left out: papers cite ICSE papers by name.
|
|
26
|
+
"aliases": ["AGENT'27", "AGENT\u201927"],
|
|
27
|
+
|
|
28
|
+
// No `template` and no `blind`, on purpose. The call names «the IEEE conference proceedings
|
|
29
|
+
// template» and no class line, so the family's `[conference]` stands (the ICSE workshops page
|
|
30
|
+
// gives `[10pt,conference]{IEEEtran}` for workshop PROPOSALS only). The review is
|
|
31
|
+
// single-anonymous (above), so `blind` — which turns on anonymity/identity — stays unset; the
|
|
32
|
+
// portal agrees: its status data carries no `"blind":true` (read 2026-10-26).
|
|
33
|
+
|
|
34
|
+
// «Submission link» on the call's page: HotCRP.
|
|
35
|
+
"portal": { "kind": "hotcrp", "url": "https://icse2027-agent.hotcrp.com" },
|
|
36
|
+
|
|
37
|
+
// The portal's reading: `"sub":1794657599` on https://icse2027-agent.hotcrp.com/deadlines, shown
|
|
38
|
+
// as «Saturday Nov 14, 2026, 6:59:59 AM EST» = 2026-11-14T11:59:59Z = the end of 13 Nov AoE. The
|
|
39
|
+
// scheduled refresh rewrites it. The call's two other dates are not on the portal; they are the
|
|
40
|
+
// call's, in UTC at the end of the AoE day.
|
|
41
|
+
"deadlines": [
|
|
42
|
+
{
|
|
43
|
+
"what": "submission",
|
|
44
|
+
"at": "2026-11-14T11:59:59Z",
|
|
45
|
+
"source": "portal",
|
|
46
|
+
"url": "https://icse2027-agent.hotcrp.com/deadlines",
|
|
47
|
+
"read": "2026-10-26",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"what": "notification",
|
|
51
|
+
"at": "2026-12-12T11:59:59Z",
|
|
52
|
+
"source": "call",
|
|
53
|
+
"url": "https://conf.researchr.org/home/icse-2027/agent-2027",
|
|
54
|
+
"read": "2026-10-26",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"what": "camera-ready",
|
|
58
|
+
"at": "2027-01-30T11:59:59Z",
|
|
59
|
+
"source": "call",
|
|
60
|
+
"url": "https://conf.researchr.org/home/icse-2027/agent-2027",
|
|
61
|
+
"read": "2026-10-26",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
"format": {
|
|
66
|
+
// 🔴 THE LIMITS INCLUDE THE REFERENCES («up to 8 pages, including references»), and no key
|
|
67
|
+
// gates a total. `body_pages_max` is banal's body count, which is never more than the total, so
|
|
68
|
+
// a paper over it is over the limit; a paper whose references sit alone on a page after a full
|
|
69
|
+
// body passes this gate and is still over. The card says so.
|
|
70
|
+
"kinds": {
|
|
71
|
+
"full": { "body_pages_max": 8 },
|
|
72
|
+
"short": { "body_pages_max": 5 },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
package/presets/agent.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "AGENT @ ICSE — venue card"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# AGENT @ ICSE
|
|
6
|
+
|
|
7
|
+
The card of the venue preset `paperlint:agent` ([`agent.jsonc`](agent.jsonc)): what the venue asks
|
|
8
|
+
for, in prose. The numbers a machine checks live in the preset; the section at the end is generated
|
|
9
|
+
from it. The `submit-paper` skill reads this card for the venue's specifics.
|
|
10
|
+
|
|
11
|
+
**Re-verify the call for papers every year**
|
|
12
|
+
([conf.researchr.org/home/icse-2027/agent-2027](https://conf.researchr.org/home/icse-2027/agent-2027)).
|
|
13
|
+
Sources read 2026-10-26: that page, the portal's
|
|
14
|
+
[deadlines page](https://icse2027-agent.hotcrp.com/deadlines), and the
|
|
15
|
+
[ICSE 2027 workshops page](https://conf.researchr.org/track/icse-2027/icse-2027-workshops).
|
|
16
|
+
|
|
17
|
+
## The venue (2027 edition)
|
|
18
|
+
|
|
19
|
+
- **Venue**: The Second International Workshop on Agentic Engineering, an ICSE 2027 workshop,
|
|
20
|
+
Mon 26 April 2027, Dublin.
|
|
21
|
+
- **Portal**: HotCRP, `https://icse2027-agent.hotcrp.com/` (the page's «Submission link»).
|
|
22
|
+
- **Format**: «formatted according to the IEEE conference proceedings template». No class line is
|
|
23
|
+
given for papers, so the preset keeps the family's `\documentclass[conference]{IEEEtran}`.
|
|
24
|
+
- **Review**: «Submissions will be reviewed single-anonymously.» The authors' names stay on the
|
|
25
|
+
paper; the portal's status data carries no `"blind":true`, which agrees.
|
|
26
|
+
- **Publisher**: «Accepted workshop papers will be published by IEEE.» The page does not say
|
|
27
|
+
«archival» or offer a non-archival choice.
|
|
28
|
+
- **Deadlines (AoE)**: submission 13 Nov 2026, notification 11 Dec 2026, camera-ready 29 Jan 2027.
|
|
29
|
+
The portal reads `"sub":1794657599`, 2026-11-14T11:59:59Z, the end of 13 Nov AoE: the two agree.
|
|
30
|
+
|
|
31
|
+
## Paper kinds and limits
|
|
32
|
+
|
|
33
|
+
| kind | pages | what it is (the call's words) |
|
|
34
|
+
| ------- | ----: | ------------------------------------------------------------------------------------------------------------------ |
|
|
35
|
+
| `full` | 8 | «Full papers (research or experience): up to 8 pages, including references.» |
|
|
36
|
+
| `short` | 5 | «Short papers (position, emerging research, tool, benchmark, or experience): up to 5 pages, including references.» |
|
|
37
|
+
|
|
38
|
+
🔴 **The limit is a total, references included**, and «It is not possible to pay for extra pages.»
|
|
39
|
+
`format/page-limit` can only compare banal's body count with the number, so a paper whose body
|
|
40
|
+
fills the pages and whose references stand alone on one more page passes the rule and is over the
|
|
41
|
+
limit. Count the PDF's pages yourself before submitting.
|
|
42
|
+
|
|
43
|
+
## What else the preset encodes
|
|
44
|
+
|
|
45
|
+
- **The IEEE template**, through `extends: paperlint:ieee-conference` ([`ieee-conference.md`](ieee-conference.md)).
|
|
46
|
+
- **The venue's name** is `AGENT 2027`, with the aliases `AGENT'27` and `AGENT’27` (what
|
|
47
|
+
`tex/venue-leftover` looks for in another venue's paper). «AGENT» alone is an ordinary word, and
|
|
48
|
+
as a name it would flag every paper folder called `agent-…` (`paper/folder-venue-leftover`).
|
|
49
|
+
- **Portal and deadlines** (`portal`, `deadlines`): the submission reading is the portal's and is
|
|
50
|
+
refreshed by paperlint's scheduled workflow; notification and camera-ready are the call's.
|
|
51
|
+
|
|
52
|
+
## Not verified
|
|
53
|
+
|
|
54
|
+
- **Remote presentation**: neither the workshop page, the ICSE workshops page nor the ICSE 2027
|
|
55
|
+
site's «Attending» menu states a policy. Ask the chairs before relying on one.
|
|
56
|
+
- **AI / LLM usage policy and required sections**: none stated on the workshop page.
|
|
57
|
+
- **The proceedings' class line**: see Format above; the page links the IEEE templates page only.
|
|
58
|
+
|
|
59
|
+
## What the preset resolves to
|
|
60
|
+
|
|
61
|
+
<!-- paperlint:preset-rules -->
|
|
62
|
+
<!-- prettier-ignore-start -->
|
|
63
|
+
<!-- Generated from agent.jsonc by `npx eslint --fix` (rule preset/card-rules). Edit the preset, not this section. -->
|
|
64
|
+
|
|
65
|
+
**`paperlint:agent`** — venue preset for AGENT 2027 · call for papers: <https://conf.researchr.org/home/icse-2027/agent-2027> · extends `paperlint:ieee-conference`
|
|
66
|
+
|
|
67
|
+
No preset of this chain sets a rule: under it, every rule keeps paperlint's default.
|
|
68
|
+
|
|
69
|
+
<!-- prettier-ignore-end -->
|
|
70
|
+
<!-- /paperlint:preset-rules -->
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// AGENTVERIFY 2027 (the International Workshop on Infrastructure for Trustworthy Software Agents, an
|
|
2
|
+
// ICSE 2027 workshop) — a VENUE preset over the IEEE conference family. Everything the IEEEtran
|
|
3
|
+
// template decides comes from ieee-conference.jsonc; this file adds what AGENTVERIFY's call sets.
|
|
4
|
+
// Its card, in prose: `agentverify.md` beside this file.
|
|
5
|
+
//
|
|
6
|
+
// Verbatim from https://agentverify2027.github.io/cfp.html (fetched 2026-10-26):
|
|
7
|
+
// «Full Papers: Maximum of 8 pages, including references.»
|
|
8
|
+
// «Short & Demonstration Papers: Maximum of 4 pages, including references.»
|
|
9
|
+
// «Extended Abstracts: Maximum of 2 pages, including references.»
|
|
10
|
+
// «Papers will be submitted via HotCRP and reviewed double-blind. Submissions should follow the
|
|
11
|
+
// IEEE format in line with ICSE 2027 and present an original contribution.»
|
|
12
|
+
// «Papers are due November 13th, 2026 (AoE).»
|
|
13
|
+
// and from https://agentverify2027.github.io/ (same day): «Paper submission deadline November 13,
|
|
14
|
+
// 2026 / Final notification December 11, 2026 / Camera-ready deadline January 29, 2027 / All
|
|
15
|
+
// deadlines are Anywhere on Earth (AoE).»
|
|
16
|
+
{
|
|
17
|
+
"type": "venue",
|
|
18
|
+
"name": "AGENTVERIFY",
|
|
19
|
+
// The call quoted above.
|
|
20
|
+
"url": "https://agentverify2027.github.io/cfp.html",
|
|
21
|
+
"extends": "paperlint:ieee-conference",
|
|
22
|
+
|
|
23
|
+
// «reviewed double-blind» (the call above), and `"blind":true` in the portal's status data
|
|
24
|
+
// (https://icse2027-agentverify.hotcrp.com/deadlines, read 2026-10-26). anonymity/identity then
|
|
25
|
+
// requires the paper's `identity`.
|
|
26
|
+
"blind": true,
|
|
27
|
+
|
|
28
|
+
// «Submit Your Paper →» on the workshop's site: HotCRP.
|
|
29
|
+
"portal": {
|
|
30
|
+
"kind": "hotcrp",
|
|
31
|
+
"url": "https://icse2027-agentverify.hotcrp.com",
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// The portal's reading: `"sub":1794657599` = 2026-11-14T11:59:59Z = the end of 13 Nov AoE. It
|
|
35
|
+
// agrees with the call and with the ICSE workshops page; the organizers' email of 2026-10-23
|
|
36
|
+
// (agentverify.md) says the same and that there is no separate abstract deadline, so there is no
|
|
37
|
+
// `registration`. Notification and camera-ready are the site's, at the end of the AoE day.
|
|
38
|
+
"deadlines": [
|
|
39
|
+
{
|
|
40
|
+
"what": "submission",
|
|
41
|
+
"at": "2026-11-14T11:59:59Z",
|
|
42
|
+
"source": "portal",
|
|
43
|
+
"url": "https://icse2027-agentverify.hotcrp.com/deadlines",
|
|
44
|
+
"read": "2026-10-26",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"what": "notification",
|
|
48
|
+
"at": "2026-12-12T11:59:59Z",
|
|
49
|
+
"source": "call",
|
|
50
|
+
"url": "https://agentverify2027.github.io/",
|
|
51
|
+
"read": "2026-10-26",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"what": "camera-ready",
|
|
55
|
+
"at": "2027-01-30T11:59:59Z",
|
|
56
|
+
"source": "call",
|
|
57
|
+
"url": "https://agentverify2027.github.io/",
|
|
58
|
+
"read": "2026-10-26",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
"format": {
|
|
63
|
+
// 🔴 THE LIMITS INCLUDE THE REFERENCES, and no key gates a total: `body_pages_max` is banal's
|
|
64
|
+
// body count, never more than the total, so a paper over it is over the limit; references alone
|
|
65
|
+
// on a page after a full body pass this gate and are still over (agentverify.md).
|
|
66
|
+
"kinds": {
|
|
67
|
+
"full": { "body_pages_max": 8 },
|
|
68
|
+
"short": { "body_pages_max": 4 },
|
|
69
|
+
"abstract": { "body_pages_max": 2 },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|