paperlint 7.0.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 +24 -11
- 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 +42 -7
- 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/body-pages.d.ts +29 -0
- package/dist/domain/body-pages.d.ts.map +1 -1
- package/dist/domain/body-pages.js +25 -0
- package/dist/domain/body-pages.js.map +1 -1
- 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-includes.d.ts +3 -0
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +17 -2
- package/dist/paper-includes.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 +22 -3
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +26 -17
- package/dist/presets.js.map +1 -1
- package/dist/submission.d.ts +16 -0
- package/dist/submission.d.ts.map +1 -1
- package/dist/submission.js +59 -16
- package/dist/submission.js.map +1 -1
- package/dist/tex-requirements.d.ts +12 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +26 -3
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +24 -2
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +98 -10
- package/dist/tex-venue-rules.js.map +1 -1
- package/dist/venue-rules.d.ts +12 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +81 -17
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +140 -12
- package/docs/e2e.md +14 -12
- 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/format/page-limit.md +18 -1
- package/docs/rules/tex/template.md +25 -5
- package/docs/rules/tex/venue-leftover.md +13 -0
- package/docs/rules.md +68 -55
- package/docs/submission.md +10 -1
- package/eslint-rules/cycle-stage.test.mjs +131 -0
- package/eslint-rules/cycle-stage.ts +141 -0
- package/eslint.config.mjs +41 -1
- package/fixtures/build-e2e/msr/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/msr/expect.json +18 -0
- package/fixtures/build-e2e/msr/paper.tex +49 -0
- package/fixtures/build-e2e/msr/paperlint.json +1 -0
- package/fixtures/build-e2e/msr-appendix-after/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/msr-appendix-after/expect.json +20 -0
- package/fixtures/build-e2e/msr-appendix-after/paper.tex +48 -0
- package/fixtures/build-e2e/msr-appendix-after/paperlint.json +1 -0
- 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/ieee-conference.md +1 -1
- package/presets/msr.jsonc +95 -0
- package/presets/msr.md +98 -0
- package/presets/venue-profile.schema.json +64 -0
- package/scripts/check.ts +4 -0
- package/scripts/refresh-deadlines.test.ts +431 -0
- package/scripts/refresh-deadlines.ts +387 -0
- package/skills/camera-ready/SKILL.md +8 -1
- package/skills/camera-ready/SKILL.md.spec.ts +7 -0
- package/skills/submission-portal/SKILL.md +2 -2
- package/skills/submission-portal/SKILL.md.spec.ts +1 -1
- package/skills/submit-paper/SKILL.md +9 -1
- package/skills/submit-paper/SKILL.md.spec.ts +8 -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 +50 -6
- package/src/cycle-rules.test.ts +278 -0
- package/src/cycle-rules.ts +273 -0
- package/src/domain/body-pages.test.ts +144 -0
- package/src/domain/body-pages.ts +61 -1
- 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 +28 -0
- package/src/paper-includes.test.ts +26 -0
- package/src/paper-includes.ts +20 -2
- package/src/paper-settings-commands.test.ts +1 -1
- 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/preset-card-rule.test.ts +1 -0
- package/src/presets.test.ts +171 -1
- package/src/presets.ts +65 -15
- package/src/rule-cases.test.ts +144 -0
- package/src/submission.test.ts +147 -4
- package/src/submission.ts +100 -18
- package/src/tex-requirements.ts +52 -4
- package/src/tex-venue-rules.test.ts +237 -0
- package/src/tex-venue-rules.ts +130 -15
- package/src/venue-rules.test.ts +255 -2
- package/src/venue-rules.ts +115 -22
package/src/venue-rules.ts
CHANGED
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
* no geometry (no banal) pdf/measured (warn); geometry and body-size silent, page-limit judges
|
|
38
38
|
* only a body counted before the references; fonts still judge
|
|
39
39
|
* kind does not resolve pdf/profile (error); page-limit silent; everything else still judges
|
|
40
|
+
* cycle phase `porting` every rule silent — the source is in the previous venue's template by
|
|
41
|
+
* declaration (src/domain/cycle.ts); cycle/port-done watches the declaration
|
|
40
42
|
*/
|
|
41
43
|
import { dirname, isAbsolute, join, basename, relative } from "node:path";
|
|
42
44
|
import {
|
|
@@ -58,8 +60,14 @@ import { fieldOf } from "./domain/record.ts";
|
|
|
58
60
|
import type { Files } from "./ports/files.ts";
|
|
59
61
|
import { CONFIG_FILE } from "#lib/paper-config";
|
|
60
62
|
import { findLeaks, type Leak, type Searchable } from "./domain/anonymity.ts";
|
|
61
|
-
import {
|
|
63
|
+
import {
|
|
64
|
+
bodyEnd,
|
|
65
|
+
pageSplit,
|
|
66
|
+
type BodyEnd,
|
|
67
|
+
type PageSplit,
|
|
68
|
+
} from "./domain/body-pages.ts";
|
|
62
69
|
import type { TextFacts } from "./facts-file.ts";
|
|
70
|
+
import { effectiveBlind, isPorting } from "./domain/cycle.ts";
|
|
63
71
|
|
|
64
72
|
// ── the verdict's vocabulary ─────────────────────────────────────────────────────────
|
|
65
73
|
|
|
@@ -91,8 +99,19 @@ export interface Resolved {
|
|
|
91
99
|
export type Assessment =
|
|
92
100
|
| { readonly kind: "no-venue" }
|
|
93
101
|
/** A `paperlint.json` that extends no preset yet — `pdf/measured` names the file to set. */
|
|
94
|
-
| {
|
|
102
|
+
| {
|
|
103
|
+
readonly kind: "no-preset";
|
|
104
|
+
readonly file: string;
|
|
105
|
+
/** The paper keeps `cycles`: its venue is the current cycle's, and `extends` beside them is refused. */
|
|
106
|
+
readonly cycles: boolean;
|
|
107
|
+
}
|
|
95
108
|
| { readonly kind: "unresolved"; readonly finding: Finding }
|
|
109
|
+
/**
|
|
110
|
+
* The current cycle declares its port to the venue's template as open work (`"phase": "porting"`):
|
|
111
|
+
* a PDF built with the previous venue's template says nothing about this venue's limits, so every
|
|
112
|
+
* venue rule is silent, and `cycle/port-done` speaks when the port is done and the phase still says so.
|
|
113
|
+
*/
|
|
114
|
+
| { readonly kind: "porting" }
|
|
96
115
|
| { readonly kind: "unbuilt"; readonly venue: Resolved }
|
|
97
116
|
| { readonly kind: "stale"; readonly finding: Finding }
|
|
98
117
|
| {
|
|
@@ -142,9 +161,13 @@ function kindOf(
|
|
|
142
161
|
/** How the venue reviews, and who the paper says wrote it: what `anonymity/*` judges by. */
|
|
143
162
|
const reviewOf = (
|
|
144
163
|
preset: Pick<Preset, "blind">,
|
|
145
|
-
settings: Pick<PaperSettings, "identity">,
|
|
164
|
+
settings: Pick<PaperSettings, "identity" | "cycles">,
|
|
146
165
|
): Pick<Resolved, "blind" | "identity"> => ({
|
|
147
|
-
|
|
166
|
+
// An accepted attempt's camera-ready carries the authors: no longer blind.
|
|
167
|
+
blind: effectiveBlind(
|
|
168
|
+
preset.blind,
|
|
169
|
+
settings.cycles?.current ?? { kind: "none" },
|
|
170
|
+
),
|
|
148
171
|
identity: settings.identity,
|
|
149
172
|
});
|
|
150
173
|
|
|
@@ -169,13 +192,25 @@ function freshFacts(
|
|
|
169
192
|
|
|
170
193
|
const isFinding = (v: object): v is Finding => "messageId" in v;
|
|
171
194
|
|
|
195
|
+
/** The paper's declared venue and kind, resolved: what the judges compare the facts against. */
|
|
196
|
+
const resolvedOf = (preset: Preset, settings: PaperSettings): Resolved => ({
|
|
197
|
+
venue: preset.label,
|
|
198
|
+
format: preset.format,
|
|
199
|
+
...kindOf(preset.label, preset.format, settings.kind),
|
|
200
|
+
...reviewOf(preset, settings),
|
|
201
|
+
});
|
|
202
|
+
|
|
172
203
|
/** One paper, assessed. Reads through `deps.files` only; never throws on a paper's files. */
|
|
173
204
|
export function assessPaper(paperDir: string, deps: VenueRuleDeps): Assessment {
|
|
174
205
|
const p = paperPreset(paperDir, deps);
|
|
175
206
|
if (p.kind === "none")
|
|
176
207
|
return p.settings === null
|
|
177
208
|
? { kind: "no-venue" }
|
|
178
|
-
: {
|
|
209
|
+
: {
|
|
210
|
+
kind: "no-preset",
|
|
211
|
+
file: join(paperDir, CONFIG_FILE),
|
|
212
|
+
cycles: p.settings.cycles !== null,
|
|
213
|
+
};
|
|
179
214
|
if (p.kind === "settings-problem")
|
|
180
215
|
return {
|
|
181
216
|
kind: "unresolved",
|
|
@@ -186,13 +221,9 @@ export function assessPaper(paperDir: string, deps: VenueRuleDeps): Assessment {
|
|
|
186
221
|
kind: "unresolved",
|
|
187
222
|
finding: finding("preset", { why: presetProblemText(p.problem) }),
|
|
188
223
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
format: p.preset.format,
|
|
193
|
-
...kindOf(label, p.preset.format, p.settings.kind),
|
|
194
|
-
...reviewOf(p.preset, p.settings),
|
|
195
|
-
};
|
|
224
|
+
if (p.settings.cycles !== null && isPorting(p.settings.cycles.current))
|
|
225
|
+
return { kind: "porting" };
|
|
226
|
+
const venue = resolvedOf(p.preset, p.settings);
|
|
196
227
|
const factsText = text(deps.files, factsPath(paperDir));
|
|
197
228
|
if (factsText === null) return { kind: "unbuilt", venue };
|
|
198
229
|
const facts = freshFacts(deps.files, paperDir, factsText);
|
|
@@ -261,9 +292,19 @@ export function judgeGeometry(
|
|
|
261
292
|
return out;
|
|
262
293
|
}
|
|
263
294
|
|
|
295
|
+
/**
|
|
296
|
+
* How many of banal's two counts — body, then references — a venue's counting replaces with its own
|
|
297
|
+
* from the text: none for banal's count, the body for `body_ends_at`, both with `appendix_in_body`.
|
|
298
|
+
*/
|
|
299
|
+
function countedFromText(format: VenueFormat): number {
|
|
300
|
+
if (format.bodyEndsAt !== "references") return 0;
|
|
301
|
+
return format.appendixInBody === true ? 2 : 1;
|
|
302
|
+
}
|
|
303
|
+
|
|
264
304
|
/**
|
|
265
305
|
* Body and reference pages, as banal classifies them, against the kind's limits. A venue that counts
|
|
266
|
-
* the body before the references is judged on its body by `judgeBodyEnd` instead
|
|
306
|
+
* the body before the references is judged on its body by `judgeBodyEnd` instead, and one that
|
|
307
|
+
* counts the appendix as body on both.
|
|
267
308
|
*/
|
|
268
309
|
export function judgePages(g: FlatGeometry, resolved: Resolved): Finding[] {
|
|
269
310
|
const kind = resolved.kind;
|
|
@@ -272,7 +313,7 @@ export function judgePages(g: FlatGeometry, resolved: Resolved): Finding[] {
|
|
|
272
313
|
[kind.limits.bodyPagesMax, g.body_pages, "body pages"],
|
|
273
314
|
[kind.limits.refPagesMax, g.ref_pages, "reference pages"],
|
|
274
315
|
];
|
|
275
|
-
const from = resolved.format
|
|
316
|
+
const from = countedFromText(resolved.format);
|
|
276
317
|
return pages.slice(from).flatMap(([max, got, what]) =>
|
|
277
318
|
max !== null && got > max
|
|
278
319
|
? [
|
|
@@ -300,13 +341,11 @@ export function judgeBodyEnd(
|
|
|
300
341
|
resolved: Resolved,
|
|
301
342
|
): readonly Finding[] {
|
|
302
343
|
const kind = resolved.kind;
|
|
303
|
-
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
)
|
|
309
|
-
return [];
|
|
344
|
+
if (resolved.format.bodyEndsAt !== "references" || kind === null) return [];
|
|
345
|
+
if (resolved.format.appendixInBody === true)
|
|
346
|
+
return judgeSplit(text, resolved, kind);
|
|
347
|
+
const max = kind.limits.bodyPagesMax;
|
|
348
|
+
if (max === null) return [];
|
|
310
349
|
const end = endOfBody(text);
|
|
311
350
|
const venue = resolved.venue;
|
|
312
351
|
switch (end.kind) {
|
|
@@ -324,6 +363,54 @@ export function judgeBodyEnd(
|
|
|
324
363
|
}
|
|
325
364
|
}
|
|
326
365
|
|
|
366
|
+
/** The findings of a split that was found: the body, and the pages holding only references. */
|
|
367
|
+
function splitFindings(
|
|
368
|
+
split: Extract<PageSplit, { readonly kind: "found" }>,
|
|
369
|
+
limits: KindLimits,
|
|
370
|
+
at: { readonly venue: string; readonly kind: string },
|
|
371
|
+
): readonly Finding[] {
|
|
372
|
+
const refs = `the references on page ${String(split.page)}`;
|
|
373
|
+
const body =
|
|
374
|
+
split.appendixFrom === null
|
|
375
|
+
? `body pages (up to ${refs}, appendices included)`
|
|
376
|
+
: `body pages (up to ${refs}, and the appendix from page ${String(split.appendixFrom)})`;
|
|
377
|
+
const counts: readonly [number | null, number, string][] = [
|
|
378
|
+
[limits.bodyPagesMax, split.bodyPages, body],
|
|
379
|
+
[limits.refPagesMax, split.refPages, "pages holding only references"],
|
|
380
|
+
];
|
|
381
|
+
return counts.flatMap(([max, got, what]) =>
|
|
382
|
+
max !== null && got > max
|
|
383
|
+
? [finding("pages", { what, got, max, ...at })]
|
|
384
|
+
: [],
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The body, appendices included, and the pages holding only references (`appendix_in_body`),
|
|
390
|
+
* against the kind's two limits. Like `judgeBodyEnd`, an end it cannot find is said, not passed.
|
|
391
|
+
*/
|
|
392
|
+
function judgeSplit(
|
|
393
|
+
text: Pick<TextFacts, "pages" | "bibAnchorPage" | "appendixAnchorPage">,
|
|
394
|
+
resolved: Resolved,
|
|
395
|
+
kind: NonNullable<Resolved["kind"]>,
|
|
396
|
+
): readonly Finding[] {
|
|
397
|
+
const limits = kind.limits;
|
|
398
|
+
if (limits.bodyPagesMax === null && limits.refPagesMax === null) return [];
|
|
399
|
+
const split = pageSplit(text.pages, {
|
|
400
|
+
bib: text.bibAnchorPage,
|
|
401
|
+
appendix: text.appendixAnchorPage,
|
|
402
|
+
});
|
|
403
|
+
const venue = resolved.venue;
|
|
404
|
+
switch (split.kind) {
|
|
405
|
+
case "missing":
|
|
406
|
+
return [finding("noReferences", { venue })];
|
|
407
|
+
case "disagree":
|
|
408
|
+
return [finding("unclear", { venue, why: disagreement(split) })];
|
|
409
|
+
case "found":
|
|
410
|
+
return splitFindings(split, limits, { venue, kind: kind.name });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
327
414
|
/** Where the body ends in what the PDF says, and where hyperref anchored the two ends. */
|
|
328
415
|
const endOfBody = (
|
|
329
416
|
t: Pick<TextFacts, "pages" | "bibAnchorPage" | "appendixAnchorPage">,
|
|
@@ -497,7 +584,11 @@ const JUDGES: Readonly<Record<VenueRuleName, Judge>> = {
|
|
|
497
584
|
fresh: (a) => (a.kind === "stale" ? [a.finding] : []),
|
|
498
585
|
measured: (a, { shown }) =>
|
|
499
586
|
a.kind === "no-preset"
|
|
500
|
-
? [
|
|
587
|
+
? [
|
|
588
|
+
finding(a.cycles ? "noPresetCycle" : "noPreset", {
|
|
589
|
+
file: shown(a.file),
|
|
590
|
+
}),
|
|
591
|
+
]
|
|
501
592
|
: a.kind === "unbuilt"
|
|
502
593
|
? [
|
|
503
594
|
finding("unbuilt", {
|
|
@@ -594,6 +685,8 @@ const META: Readonly<Record<VenueRuleName, Meta>> = {
|
|
|
594
685
|
"not built yet, so `{{venue}}`'s page limit, fonts and format are not checked — run `paperlint build`",
|
|
595
686
|
noPreset:
|
|
596
687
|
'this paper names no venue preset yet, so its page limit, fonts and format are not checked — set "extends" in {{file}} (e.g. "paperlint:agenticdev"; see docs/rules.md)',
|
|
688
|
+
noPresetCycle:
|
|
689
|
+
'this paper\'s current cycle names no venue preset, so its page limit, fonts and format are not checked — set the venue in the current cycle in {{file}} ("venue": { "kind": "preset", "extends": "paperlint:…" }), or open a new cycle when the last one has ended',
|
|
597
690
|
noGeometry:
|
|
598
691
|
"the build measured no page geometry (banal was not found or failed), so page size, columns, page limits and font sizes were NOT checked — run `paperlint toolchain`, then `paperlint build`",
|
|
599
692
|
},
|