paperlint 6.1.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/adapters/mp4box/index.d.ts +4 -0
  2. package/dist/adapters/mp4box/index.d.ts.map +1 -0
  3. package/dist/adapters/mp4box/index.js +46 -0
  4. package/dist/adapters/mp4box/index.js.map +1 -0
  5. package/dist/adapters/node/index.d.ts +1 -0
  6. package/dist/adapters/node/index.d.ts.map +1 -1
  7. package/dist/adapters/node/index.js +1 -0
  8. package/dist/adapters/node/index.js.map +1 -1
  9. package/dist/adapters/node/list-dir.io.d.ts +3 -0
  10. package/dist/adapters/node/list-dir.io.d.ts.map +1 -0
  11. package/dist/adapters/node/list-dir.io.js +15 -0
  12. package/dist/adapters/node/list-dir.io.js.map +1 -0
  13. package/dist/adapters/png/index.d.ts +11 -0
  14. package/dist/adapters/png/index.d.ts.map +1 -0
  15. package/dist/adapters/png/index.js +21 -0
  16. package/dist/adapters/png/index.js.map +1 -0
  17. package/dist/adapters/srt/index.d.ts +4 -0
  18. package/dist/adapters/srt/index.d.ts.map +1 -0
  19. package/dist/adapters/srt/index.js +12 -0
  20. package/dist/adapters/srt/index.js.map +1 -0
  21. package/dist/cli.d.ts.map +1 -1
  22. package/dist/cli.js +25 -4
  23. package/dist/cli.js.map +1 -1
  24. package/dist/domain/talk.d.ts +121 -0
  25. package/dist/domain/talk.d.ts.map +1 -0
  26. package/dist/domain/talk.js +121 -0
  27. package/dist/domain/talk.js.map +1 -0
  28. package/dist/lib/paper-config.d.ts +2 -0
  29. package/dist/lib/paper-config.d.ts.map +1 -1
  30. package/dist/lib/paper-config.js +3 -0
  31. package/dist/lib/paper-config.js.map +1 -1
  32. package/dist/paper-settings.d.ts +3 -0
  33. package/dist/paper-settings.d.ts.map +1 -1
  34. package/dist/paper-settings.js +15 -9
  35. package/dist/paper-settings.js.map +1 -1
  36. package/dist/ports/talk-media.d.ts +18 -0
  37. package/dist/ports/talk-media.d.ts.map +1 -0
  38. package/dist/ports/talk-media.js +2 -0
  39. package/dist/ports/talk-media.js.map +1 -0
  40. package/dist/presets.d.ts +3 -0
  41. package/dist/presets.d.ts.map +1 -1
  42. package/dist/presets.js +6 -10
  43. package/dist/presets.js.map +1 -1
  44. package/dist/talk-rules.d.ts +65 -0
  45. package/dist/talk-rules.d.ts.map +1 -0
  46. package/dist/talk-rules.js +451 -0
  47. package/dist/talk-rules.js.map +1 -0
  48. package/dist/tex-requirements.d.ts +3 -0
  49. package/dist/tex-requirements.d.ts.map +1 -1
  50. package/dist/tex-requirements.js +2 -0
  51. package/dist/tex-requirements.js.map +1 -1
  52. package/docs/configuration.md +2 -1
  53. package/docs/rules/talk/captions-cover.md +47 -0
  54. package/docs/rules/talk/duration-floor.md +38 -0
  55. package/docs/rules/talk/duration.md +43 -0
  56. package/docs/rules/talk/one-slide-size.md +38 -0
  57. package/docs/rules/talk/profile.md +64 -0
  58. package/docs/rules/talk/required-files.md +39 -0
  59. package/docs/rules/talk/undeclared.md +35 -0
  60. package/docs/rules/talk/video-format.md +43 -0
  61. package/docs/rules.md +11 -0
  62. package/docs/talk.md +94 -0
  63. package/fixtures/talk/audio-only.mp4 +0 -0
  64. package/fixtures/talk/one-slide.png +0 -0
  65. package/fixtures/talk/short-audio.mp4 +0 -0
  66. package/fixtures/talk/talk.mov +0 -0
  67. package/fixtures/talk/talk.mp4 +0 -0
  68. package/fixtures/talk/talk.srt +7 -0
  69. package/lib/paper-config.harness.mjs +5 -2
  70. package/lib/paper-config.ts +4 -0
  71. package/package.json +3 -1
  72. package/presets/agenticdev.jsonc +30 -0
  73. package/presets/venue-profile.schema.json +132 -0
  74. package/skills/submit-paper/references/venues/agenticdev.md +36 -9
  75. package/src/adapters/mp4box/index.test.ts +63 -0
  76. package/src/adapters/mp4box/index.ts +50 -0
  77. package/src/adapters/node/index.ts +1 -0
  78. package/src/adapters/node/list-dir.io.ts +15 -0
  79. package/src/adapters/node/list-dir.test.ts +30 -0
  80. package/src/adapters/png/index.test.ts +43 -0
  81. package/src/adapters/png/index.ts +29 -0
  82. package/src/adapters/srt/index.test.ts +24 -0
  83. package/src/adapters/srt/index.ts +13 -0
  84. package/src/cli.test.ts +12 -0
  85. package/src/cli.ts +31 -3
  86. package/src/domain/talk.test.ts +125 -0
  87. package/src/domain/talk.ts +274 -0
  88. package/src/new-paper.test.ts +1 -0
  89. package/src/paper-settings.test.ts +18 -1
  90. package/src/paper-settings.ts +21 -11
  91. package/src/ports/talk-media.ts +19 -0
  92. package/src/presets.ts +9 -10
  93. package/src/rule-cases.test.ts +110 -3
  94. package/src/talk-rules.test.ts +459 -0
  95. package/src/talk-rules.ts +672 -0
  96. package/src/tex-requirements.ts +9 -0
@@ -0,0 +1,38 @@
1
+ # talk/duration-floor
2
+
3
+ **Level:** warn · **Reads:** `paper.tex` → `paperlint.json` (`talk`, `kind`), the preset's
4
+ `talk.kinds`, and the video's headers
5
+
6
+ ## What it catches
7
+
8
+ The video runs shorter than the slot's `talk_s_min` for the paper's kind (`tooShort`).
9
+
10
+ ## Why
11
+
12
+ A venue that states a window ("6–7 min talk") states a floor, and a ceiling-only check pushes every
13
+ talk toward shorter. Whether a short talk is a problem is the author's call — some sessions welcome
14
+ extra questions — so this is a warning, and a separate rule: turn it off for a paper, or raise it to
15
+ an error, without touching the ceiling.
16
+
17
+ ## Examples
18
+
19
+ Warning — the AgenticDev talk measured 320.84 s against the organizers' 6–7 minutes:
20
+
21
+ > papers/agenticdev-2026/talk/agenticdev-2026-talk.mp4 runs 5:20.8 (320.84 s), under the 6:00.0
22
+ > (360 s) talk agenticdev asks for a short paper — decide whether that is fine, or set this rule to
23
+ > off for the paper
24
+
25
+ Passing: any length from `talk_s_min` up.
26
+
27
+ ## Options / preset fields
28
+
29
+ No rule options. Preset: `talk.kinds.<kind>.talk_s_min`; without it the rule is silent.
30
+
31
+ ## What it does not check
32
+
33
+ The ceiling (`talk/duration`).
34
+
35
+ ## How to fix
36
+
37
+ Lengthen the talk, or decide it is fine and set `"talk/duration-floor": "off"` in the paper's
38
+ `rules`.
@@ -0,0 +1,43 @@
1
+ # talk/duration
2
+
3
+ **Level:** error · **Reads:** `paper.tex` → `paperlint.json` (`talk`, `kind`), the preset's
4
+ `talk.kinds`, and the video's headers
5
+
6
+ ## What it catches
7
+
8
+ - The video runs longer than the slot's `talk_s_max` for the paper's kind (`tooLong`).
9
+ - The video's first audio track is more than `trackToleranceS` (default 1 s) shorter or longer than
10
+ the video itself (`trackMismatch`) — a cut or a re-encode dropped or padded sound.
11
+
12
+ The length is the container's own (the movie header), read with MP4Box.js; the audio track's length
13
+ is its own media duration. On the AgenticDev talk both read 320.84 s and 320.86 s, the same numbers
14
+ ffmpeg prints.
15
+
16
+ ## Why
17
+
18
+ A remote talk is played in a fixed slot; a video over it is cut by the session chair, usually at the
19
+ conclusion. Measuring the file — not a word count of the script — is the only number the venue
20
+ limits: a script of 948 words, timed at the script's own assumed 125 words per minute, predicted
21
+ 7.6 minutes; the recording ran 5.35.
22
+
23
+ ## Examples
24
+
25
+ Failing — an AgenticDev short paper (`talk_s_max` 420):
26
+
27
+ > papers/p/talk/talk.mp4 runs 7:12.0 (432 s), over the 7:00.0 (420 s) talk agenticdev sets for this
28
+ > kind of paper — cut it
29
+
30
+ Passing: a 5:20.8 video with its audio track ending within a second of it.
31
+
32
+ ## Options / preset fields
33
+
34
+ `{ "trackToleranceS": 1 }`. Preset: `talk.kinds.<kind>.talk_s_max`.
35
+
36
+ ## What it does not check
37
+
38
+ The floor — that is `talk/duration-floor`, separate so its severity can be set on its own. A video
39
+ that does not parse (see `talk/video-format`) or is missing (see `talk/required-files`).
40
+
41
+ ## How to fix
42
+
43
+ Cut the talk; re-export with the audio and the picture the same length.
@@ -0,0 +1,38 @@
1
+ # talk/one-slide-size
2
+
3
+ **Level:** error · **Reads:** `paper.tex` → `paperlint.json` (`talk.one_slide`), the preset's
4
+ `talk.one_slide`, and the one-slide image's PNG header
5
+
6
+ ## What it catches
7
+
8
+ - The one-slide image is not the declared size (`size`).
9
+ - The file is not a PNG (`notPng`).
10
+
11
+ The size is the paper's `talk.one_slide` when it states one, else the preset's. When neither states
12
+ a size, the rule is silent: a size nobody asked for is not a requirement. The width and height are
13
+ read from the PNG's `IHDR` header; the pixels are never decoded.
14
+
15
+ ## Why
16
+
17
+ A one-slide summary is shown on a fixed canvas — a poster wall, a session screen, a page of the
18
+ program. An image of another size is letterboxed or cropped.
19
+
20
+ ## Examples
21
+
22
+ Failing — the paper declares `"one_slide": { "width_px": 1920, "height_px": 1080 }`:
23
+
24
+ > papers/p/talk/one-slide.png is 1280×720 px, and the one-slide must be 1920×1080 px
25
+
26
+ Passing: a 1920×1080 PNG.
27
+
28
+ ## Options / preset fields
29
+
30
+ No rule options. Preset: `talk.one_slide`. Paper: `talk.one_slide`, which wins.
31
+
32
+ ## What it does not check
33
+
34
+ Its content; formats other than PNG; a missing file (`talk/required-files`).
35
+
36
+ ## How to fix
37
+
38
+ Render the image at the declared size, or correct the declared size.
@@ -0,0 +1,64 @@
1
+ # talk/profile
2
+
3
+ **Level:** error · **Reads:** `paper.tex` → `paperlint.json` (`talk`, `kind`, `extends`) and the
4
+ venue preset's `talk` block
5
+
6
+ ## What it catches
7
+
8
+ A talk the other `talk/*` rules cannot judge, one reason at a time:
9
+
10
+ - the paper declares `talk` and names no venue preset (`noPreset`);
11
+ - the preset has no `talk` block (`noTalkBlock`);
12
+ - the paper's `talk.mode` is not one of the preset's `modes` (`mode`);
13
+ - the paper names no `kind` (`kindMissing`), or the preset sets no talk slot for that kind
14
+ (`kindNoSlot`).
15
+
16
+ ## Why
17
+
18
+ Every other talk rule needs one of these to say anything. Without a slot the length is not checked;
19
+ without a talk block nothing is. A rule that went quiet for these reasons would look exactly like a
20
+ rule that checked and passed, so this one says which piece is missing.
21
+
22
+ ## Examples
23
+
24
+ Failing — AgenticDev allows only remote video:
25
+
26
+ ```json
27
+ {
28
+ "extends": "paperlint:agenticdev",
29
+ "kind": "short",
30
+ "talk": { "mode": "in-person" }
31
+ }
32
+ ```
33
+
34
+ > `agenticdev` allows the talk modes remote-video; this paper declares `in-person`
35
+
36
+ Failing — the email gave the short-paper slot only:
37
+
38
+ ```json
39
+ {
40
+ "extends": "paperlint:agenticdev",
41
+ "kind": "full",
42
+ "talk": { "mode": "remote-video" }
43
+ }
44
+ ```
45
+
46
+ > `agenticdev` sets no talk slot for a `full` paper, so the video's length is not checked; its talk
47
+ > kinds: short
48
+
49
+ Passing: `"kind": "short", "talk": { "mode": "remote-video" }`.
50
+
51
+ ## Options / preset fields
52
+
53
+ No rule options. Preset: `talk.modes` (a list; empty or absent allows any mode) and `talk.kinds`.
54
+
55
+ ## What it does not check
56
+
57
+ A preset or `paperlint.json` that does not resolve or parse at all: `pdf/profile` reports that, and
58
+ every talk rule is silent.
59
+
60
+ ## How to fix
61
+
62
+ Declare the mode the venue allows; set `kind`; when the venue's talk numbers are not in the shipped
63
+ preset, put them in your own preset (`"extends": "./my-venue.jsonc"`) with a `talk` block that
64
+ quotes the organizers.
@@ -0,0 +1,39 @@
1
+ # talk/required-files
2
+
3
+ **Level:** error · **Reads:** `paper.tex` → `paperlint.json` (`talk`), the preset's
4
+ `talk.artifacts` and `talk.video.captions`, and the talk folder
5
+
6
+ ## What it catches
7
+
8
+ A file the venue requires for the paper's talk mode that is not in the talk folder — one finding per
9
+ missing file (`missing`). The required set is `talk.artifacts[<mode>].required`, plus `captions`
10
+ when `talk.video.captions` is `"required"` and the mode owes a video.
11
+
12
+ ## Why
13
+
14
+ Submitting a talk is usually a form with several uploads, the evening before. A missing one-slide
15
+ image is noticed when the form asks for it; this rule notices it when the paper is linted.
16
+
17
+ ## Examples
18
+
19
+ Failing — AgenticDev requires a video and a one-slide for a remote talk, and `talk/one-slide.png` is
20
+ not there:
21
+
22
+ > agenticdev asks for the one-slide of a remote-video talk, and papers/p/talk/one-slide.png is not
23
+ > there — add it, or name the file in `talk.files`
24
+
25
+ Passing: `talk/talk.mp4` and `talk/one-slide.png` both present (or the names given in `talk.files`).
26
+
27
+ ## Options / preset fields
28
+
29
+ No rule options. Preset: `talk.artifacts`, `talk.video.captions`. Paper: `talk.dir`, `talk.files`.
30
+
31
+ ## What it does not check
32
+
33
+ Whether a present file is any good — the other talk rules read the contents. Optional artifacts.
34
+ A paper that declares no `talk` (see `talk/undeclared`).
35
+
36
+ ## How to fix
37
+
38
+ Put the file in the talk folder under its default name ([`docs/talk.md`](../../talk.md)), or name
39
+ it in the paper's `talk.files`.
@@ -0,0 +1,35 @@
1
+ # talk/undeclared
2
+
3
+ **Level:** warn · **Reads:** `paper.tex` → `paperlint.json` and the paper's `talk/` folder
4
+
5
+ ## What it catches
6
+
7
+ An `.mp4` file in the paper's `talk/` folder while the paper declares no `talk` (`undeclared`).
8
+
9
+ ## Why
10
+
11
+ Every other talk rule is silent until the paper declares `talk`. A video made and never declared
12
+ would be sent unchecked, and the silence would read as "checked and fine". This is the other
13
+ direction of `talk/required-files`: a file with no declaration, rather than a declaration with no
14
+ file.
15
+
16
+ ## Examples
17
+
18
+ Failing: `papers/p/talk/talk.mp4` exists, `papers/p/paperlint.json` has no `talk`:
19
+
20
+ > papers/p/talk/talk.mp4 is a talk video, and this paper declares no `talk`, so its length and
21
+ > format are not checked — declare it in paperlint.json: "talk": { "mode": "remote-video" }
22
+
23
+ Passing: the same file with `"talk": { "mode": "remote-video" }` declared.
24
+
25
+ ## Options / preset fields
26
+
27
+ None.
28
+
29
+ ## What it does not check
30
+
31
+ Only the default folder `talk/` and only `.mp4` files; a video elsewhere is not looked for.
32
+
33
+ ## How to fix
34
+
35
+ Declare the talk in the paper's `paperlint.json`, or move the file out of `talk/`.
@@ -0,0 +1,43 @@
1
+ # talk/video-format
2
+
3
+ **Level:** error · **Reads:** `paper.tex` → `paperlint.json` (`talk`), the preset's `talk.video`,
4
+ and the video's headers and size
5
+
6
+ ## What it catches
7
+
8
+ - The file is not ISO media at all — no `ftyp` box first, or no `moov` box (`unreadable`). Reported
9
+ whatever the preset says, because no other talk rule can read such a file.
10
+ - `container: "mp4"` and the file is QuickTime inside (major brand `qt `), whatever its extension
11
+ (`container`).
12
+ - `min_height_px` and the video track is shorter (`height`), or there is no video track
13
+ (`noVideoTrack`).
14
+ - `max_bytes` and the file is larger (`bytes`).
15
+
16
+ ## Why
17
+
18
+ Upload forms reject by format and size, and "HD" is a stated requirement. A `.mov` renamed to
19
+ `.mp4` plays on the author's machine and fails in the form or in the session's player.
20
+
21
+ ## Examples
22
+
23
+ Failing — a 1280×720 video against a venue asking for 1080 px:
24
+
25
+ > papers/p/talk/talk.mp4 is 720 px high, under the 1080 px myvenue asks for — export at a higher
26
+ > resolution
27
+
28
+ Passing: the AgenticDev talk, 1920×1080 H.264 in an `isom` MP4, against `min_height_px` 720.
29
+
30
+ ## Options / preset fields
31
+
32
+ No rule options. Preset: `talk.video.container`, `talk.video.min_height_px`, `talk.video.max_bytes`.
33
+ A field the preset does not set is not checked. Without a `talk` block the rule is silent.
34
+
35
+ ## What it does not check
36
+
37
+ Codec, frame rate, bitrate, audio format, rotation metadata (the height is the track's, before any
38
+ rotation), loudness.
39
+
40
+ ## How to fix
41
+
42
+ Export as MP4 (H.264 and AAC are what every player takes) at the venue's resolution; re-encode at a
43
+ lower bitrate to fit the size limit.
package/docs/rules.md CHANGED
@@ -36,6 +36,14 @@ reads and when it fails. Errors fail `paperlint lint`; warnings print and do not
36
36
  | [`format/page-limit`](rules/format/page-limit.md) | error | `paper.tex` → `_build/paper.facts.json` | body or reference pages exceed the limits of the paper's kind. With `body_ends_at: "references"` (AIDC) the body ends at the references or the appendix, whichever comes first — found by hyperref's anchors, then the heading line — and the page it ends on counts when body text stands above the heading; an end it cannot find, or signals that disagree, are reported — [page](rules/format/page-limit.md) |
37
37
  | [`pdf/body-size`](rules/pdf/body-size.md) | error | `paper.tex` → `_build/paper.facts.json` | the body font size is more than `body_pt_tol` off the preset's, or the reference font size is outside the preset's range widened by `body_pt_tol`. banal reads 0.3 pt above the size the glyphs are set in; checked on five built accepted papers (none reported) and three variants one size off (each reported) — [page](rules/pdf/body-size.md) |
38
38
  | `pdf/measured` | warn | `paper.tex` → `_build/paper.facts.json` | the paper's `paperlint.json` names no venue preset yet; or it names one and there are no facts (it was not built), or the facts carry no page geometry (banal was not found) — so the checks above did not run |
39
+ | [`talk/profile`](rules/talk/profile.md) | error | `paper.tex` → `paperlint.json` | the paper declares `talk` and names no preset, the preset has no `talk` block, the mode is not one the venue allows, or the venue sets no talk slot for the paper's kind — [page](rules/talk/profile.md) |
40
+ | [`talk/required-files`](rules/talk/required-files.md) | error | `paper.tex` → `paperlint.json`, the talk folder | a file the venue requires for the paper's talk mode (video, one-slide, captions…) is not in the talk folder — [page](rules/talk/required-files.md) |
41
+ | [`talk/undeclared`](rules/talk/undeclared.md) | warn | `paper.tex` → the paper's `talk/` folder | an `.mp4` sits in `talk/` and the paper declares no `talk`, so nothing checks it — [page](rules/talk/undeclared.md) |
42
+ | [`talk/duration`](rules/talk/duration.md) | error | `paper.tex` → the talk video | the video runs longer than the slot's `talk_s_max`, or its audio track is more than 1 s off the video's length — [page](rules/talk/duration.md) |
43
+ | [`talk/duration-floor`](rules/talk/duration-floor.md) | warn | `paper.tex` → the talk video | the video runs shorter than the slot's `talk_s_min` — [page](rules/talk/duration-floor.md) |
44
+ | [`talk/video-format`](rules/talk/video-format.md) | error | `paper.tex` → the talk video | the video is not ISO media, is QuickTime where the venue asks for MP4, is under `min_height_px`, or over `max_bytes` — [page](rules/talk/video-format.md) |
45
+ | [`talk/one-slide-size`](rules/talk/one-slide-size.md) | error | `paper.tex` → the one-slide PNG | the one-slide image is not the size the paper or the preset states, or not a PNG — [page](rules/talk/one-slide-size.md) |
46
+ | [`talk/captions-cover`](rules/talk/captions-cover.md) | warn | `paper.tex` → the captions and the video | the captions start late, end early or leave a long gap (uncalibrated thresholds) — [page](rules/talk/captions-cover.md) |
39
47
 
40
48
  **Body files** are the files `paper.tex` includes from its document body (`\input`, `\include`,
41
49
  `\subfile`, nested ones too), found in the paper's directory — not a preamble include, not a file
@@ -48,6 +56,9 @@ that names no file is printed as a note: lint did not read it, and the build wil
48
56
  LLMs are used», which no rule can know; an author who used none says so in the section
49
57
  ([page](rules/tex/required-section.md)).
50
58
 
59
+ The `talk/*` rules judge a talk's finished files and are silent for a paper that declares no
60
+ `talk` (but `talk/undeclared`): [`talk.md`](talk.md).
61
+
51
62
  Optional rules — off unless you turn them on in the `rules` setting, because only some venues need
52
63
  them — are on their own page: [`optional-rules.md`](optional-rules.md). Today there is one,
53
64
  `pdf/last-page-balance`.
package/docs/talk.md ADDED
@@ -0,0 +1,94 @@
1
+ # The talk
2
+
3
+ A venue that accepts a paper usually asks for a talk too: a video for a remote presenter, a
4
+ one-slide image, sometimes captions. paperlint does not make the talk. It **judges the finished
5
+ files** against what the venue asks, so the same check works whether the video came from Keynote,
6
+ OBS, Zoom, a script, or by hand.
7
+
8
+ ## Declaring a talk
9
+
10
+ The paper's own `paperlint.json` says how it is presented:
11
+
12
+ ```json
13
+ {
14
+ "extends": "paperlint:agenticdev",
15
+ "kind": "short",
16
+ "talk": { "mode": "remote-video" }
17
+ }
18
+ ```
19
+
20
+ | key | meaning | default |
21
+ | ----------- | -------------------------------------------------------------------------------------------- | --------- |
22
+ | `mode` | `remote-video`, `remote-live` or `in-person` — must be one the venue allows | required |
23
+ | `dir` | the talk folder, relative to the paper | `talk` |
24
+ | `files` | the file name of each artifact in that folder, when it is not the default (no slashes) | see below |
25
+ | `one_slide` | `{ "width_px": 1920, "height_px": 1080 }` — the one-slide's size, when the venue states none | none |
26
+
27
+ `talk` is a paper's setting: the root `paperlint.json` refuses it.
28
+
29
+ **File names.** Each artifact is looked for under one name in the talk folder:
30
+
31
+ | artifact | default name |
32
+ | ------------ | --------------- |
33
+ | `video` | `talk.mp4` |
34
+ | `one-slide` | `one-slide.png` |
35
+ | `captions` | `talk.srt` |
36
+ | `slides-pdf` | `slides.pdf` |
37
+ | `poster` | `poster.pdf` |
38
+
39
+ A video named after the paper is declared once:
40
+
41
+ ```json
42
+ "talk": {
43
+ "mode": "remote-video",
44
+ "files": { "video": "agenticdev-2026-talk.mp4", "captions": "agenticdev-2026-talk.srt" }
45
+ }
46
+ ```
47
+
48
+ ## What the venue says — the preset's `talk` block
49
+
50
+ ```jsonc
51
+ "talk": {
52
+ "modes": ["remote-video"],
53
+ "artifacts": { "remote-video": { "required": ["video", "one-slide"], "optional": ["poster"] } },
54
+ "kinds": { "short": { "slot_s": 600, "talk_s_min": 360, "talk_s_max": 420, "qa_s": 210 } },
55
+ "video": { "container": "mp4", "min_height_px": 720, "max_bytes": 500000000, "captions": "optional" },
56
+ "one_slide": { "width_px": 1920, "height_px": 1080 }
57
+ }
58
+ ```
59
+
60
+ Every key is optional. Durations are seconds. `kinds` uses the names of `format.kinds`, and the
61
+ paper's `kind` picks one. `video.captions: "required"` adds the captions to the files the video's
62
+ mode owes. A child preset's `talk` block replaces its parent's whole. The shipped `agenticdev`
63
+ preset carries the organizers' numbers, each quoted beside it; it states no one-slide size, because
64
+ the venue did not.
65
+
66
+ ## What is checked
67
+
68
+ All rules run on the paper's `paper.tex` and are silent for a paper that declares no `talk`, except
69
+ `talk/undeclared`.
70
+
71
+ | rule | level | fails when |
72
+ | ----------------------------------------------------- | ----- | ----------------------------------------------------------------------------------------------- |
73
+ | [`talk/profile`](rules/talk/profile.md) | error | no preset, a preset with no `talk` block, a mode the venue does not allow, no slot for the kind |
74
+ | [`talk/required-files`](rules/talk/required-files.md) | error | a file the venue requires for the mode is not in the talk folder |
75
+ | [`talk/undeclared`](rules/talk/undeclared.md) | warn | an `.mp4` sits in `talk/` and the paper declares no `talk` |
76
+ | [`talk/duration`](rules/talk/duration.md) | error | the video runs longer than `talk_s_max`, or its audio track is more than 1 s off its length |
77
+ | [`talk/duration-floor`](rules/talk/duration-floor.md) | warn | the video runs shorter than `talk_s_min` |
78
+ | [`talk/video-format`](rules/talk/video-format.md) | error | not an MP4 (QuickTime inside, or not ISO media at all), under `min_height_px`, over `max_bytes` |
79
+ | [`talk/one-slide-size`](rules/talk/one-slide-size.md) | error | the one-slide PNG is not the declared size |
80
+ | [`talk/captions-cover`](rules/talk/captions-cover.md) | warn | the captions start late, end early, or leave a long gap (thresholds uncalibrated) |
81
+
82
+ The video is read with [MP4Box.js](https://github.com/gpac/mp4box.js) — the container's own
83
+ headers, in JavaScript, in milliseconds. There is no ffmpeg anywhere in paperlint and no build step
84
+ for the talk.
85
+
86
+ ## What is not checked
87
+
88
+ - **What the talk says.** Whether the video matches the paper, its tone, its claims, the QR code on
89
+ a slide — those need a reader, not a count.
90
+ - **How it was made.** A script, slides, a voice recording, cut points: paperlint never sees them,
91
+ and nothing here requires them to exist.
92
+ - **Deadlines and upload channels.** A deadline is a clock condition; it belongs in a calendar.
93
+ - **Whether the file was sent.** Freezing what was sent is not part of the talk rules yet.
94
+ - **Codecs, frame rate, bitrate, loudness.** Only the container, the height and the byte size.
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ 1
2
+ 00:00:00,000 --> 00:00:01,200
3
+ A synthetic caption for a three-second tone.
4
+
5
+ 2
6
+ 00:00:01,400 --> 00:00:02,900
7
+ It carries no voice.
@@ -200,11 +200,14 @@ const loaded = await Promise.all(
200
200
  // refuses any key not in SETTINGS_KEYS. So the list must hold every key some reader reads — or a
201
201
  // consumer's valid key is refused — and nothing no one reads. The readers are FOUND, by the AST:
202
202
  // every `settingsOf(root)?.<key>` (and the older `[CONFIG_KEY]?.<key>` shape), plus every field of
203
- // `PaperlintConfig`, the CLI's typed view of the same object.
203
+ // `PaperlintConfig` and `PaperSettings`, the typed views of the root's and a paper's object.
204
204
  {
205
205
  const { SETTINGS_KEYS } = await import(join(HERE, "paper-config.ts"));
206
206
  const ts = (await import("typescript")).default;
207
207
  const readKeys = new Set();
208
+ // The two typed views of the object: the root file's (`PaperlintConfig`, cli.ts) and a paper's
209
+ // (`PaperSettings`, src/paper-settings.ts). A key only a paper may hold (`talk`) is read there.
210
+ const TYPED_VIEWS = ["PaperlintConfig", "PaperSettings"];
208
211
  const isConfigKey = (e) =>
209
212
  (ts.isIdentifier(e) && e.text === "CONFIG_KEY") ||
210
213
  (ts.isStringLiteral(e) && e.text === "paperlint");
@@ -217,7 +220,7 @@ const loaded = await Promise.all(
217
220
  const visit = (node) => {
218
221
  if (ts.isPropertyAccessExpression(node) && isSettings(node.expression))
219
222
  readKeys.add(node.name.text);
220
- if (ts.isInterfaceDeclaration(node) && node.name.text === "PaperlintConfig")
223
+ if (ts.isInterfaceDeclaration(node) && TYPED_VIEWS.includes(node.name.text))
221
224
  for (const m of node.members)
222
225
  if (m.name && ts.isIdentifier(m.name)) readKeys.add(m.name.text);
223
226
  ts.forEachChild(node, visit);
@@ -140,6 +140,7 @@ export const SETTINGS_KEYS: Readonly<Record<string, string>> = Object.freeze({
140
140
  pdf: "where the built PDF is, relative to the paper, when it is not paper.pdf — the facts",
141
141
  identity:
142
142
  "what identifies the authors — names, handles, emails, affiliations, own project names; a blind venue's PDF must not say any of them — anonymity/identity. The root's list and a paper's are joined",
143
+ talk: 'how this paper is presented: { "mode": "remote-video", "dir"?, "files"?, "one_slide"? } — the talk/ rules. A paper\'s own; refused in the root file',
143
144
  scripts: "how skill prose names the scripts — consumer.mjs",
144
145
  timezone: "the zone deadline anchors are written in — consumer.mjs",
145
146
  contactEmail: "the polite-pool address for citation lookups — consumer.mjs",
@@ -148,6 +149,9 @@ export const SETTINGS_KEYS: Readonly<Record<string, string>> = Object.freeze({
148
149
  $comment: "a note for humans (JSON Schema's own comment keyword); ignored",
149
150
  });
150
151
 
152
+ /** The keys that only mean something for one paper: refused in the root `paperlint.json`. */
153
+ export const PAPER_ONLY_KEYS: readonly string[] = Object.freeze(["talk"]);
154
+
151
155
  /** The keys that only mean something for the whole project: refused in a paper's `paperlint.json`. */
152
156
  export const ROOT_ONLY_KEYS: readonly string[] = Object.freeze([
153
157
  "papersDir",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "6.1.0",
3
+ "version": "6.2.1",
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",
@@ -27,7 +27,9 @@
27
27
  "eslint": "^10.9.1",
28
28
  "js-yaml": "^4.1.0",
29
29
  "markdown-it": "^14.3.0",
30
+ "mp4box": "^2.4.1",
30
31
  "remeda": "^2.50.0",
32
+ "srt-parser-2": "^1.2.3",
31
33
  "ts-essentials": "^10.2.1",
32
34
  "typescript": "^6.0.3",
33
35
  "unpdf": "^1.8.1",
@@ -31,6 +31,36 @@
31
31
  },
32
32
  },
33
33
 
34
+ // The talk (talk/* rules), for a paper whose paperlint.json declares `talk`. The numbers are the
35
+ // organizers', each with its source; one_slide is left out on purpose — the venue never stated a
36
+ // size, so a paper that wants its own checked declares it in its own `talk` key.
37
+ "talk": {
38
+ // AgenticDev adopted ASE's «not in person presentation» policy (co-chairs, 2026-08-27).
39
+ "modes": ["remote-video"],
40
+ "artifacts": {
41
+ // The chairs' email to accepted authors: a video and a one-slide; a poster is optional.
42
+ "remote-video": {
43
+ "required": ["video", "one-slide"],
44
+ "optional": ["poster"],
45
+ },
46
+ },
47
+ "kinds": {
48
+ // The chairs' email to all authors (2026-09-29): a short paper gets 10 min, about 6–7 for the
49
+ // talk and 3–4 for questions.
50
+ // qa_s is the middle of 3–4 min; it is informational and not judged. The full-paper slot
51
+ // was not in the email, so `full` and `demo` have no entry and talk/profile says so.
52
+ "short": {
53
+ "slot_s": 600,
54
+ "talk_s_min": 360,
55
+ "talk_s_max": 420,
56
+ "qa_s": 210,
57
+ },
58
+ },
59
+ // ASE 2026's not-in-person page: «recommendation: 10 min duration, mp4 file format, HD
60
+ // resolution» — the parent conference's recommendation; the workshop's own slot is above.
61
+ "video": { "container": "mp4", "min_height_px": 720 },
62
+ },
63
+
34
64
  // The proceedings are produced by Conference Publishing Consulting — agenticdev.md takes its
35
65
  // numbers from their author instructions — which require "balanced columns on the last page, if
36
66
  // the page is not filled" and sent an accepted camera-ready back for it on 2026-08-29, the day