paperlint 6.2.3 → 7.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.
Files changed (234) hide show
  1. package/CONTRIBUTING.md +4 -2
  2. package/README.md +10 -3
  3. package/dist/adapters/hotcrp/index.d.ts +7 -0
  4. package/dist/adapters/hotcrp/index.d.ts.map +1 -0
  5. package/dist/adapters/hotcrp/index.js +7 -0
  6. package/dist/adapters/hotcrp/index.js.map +1 -0
  7. package/dist/adapters/hotcrp/portal.io.d.ts +20 -0
  8. package/dist/adapters/hotcrp/portal.io.d.ts.map +1 -0
  9. package/dist/adapters/hotcrp/portal.io.js +90 -0
  10. package/dist/adapters/hotcrp/portal.io.js.map +1 -0
  11. package/dist/adapters/hotcrp/response.d.ts +13 -0
  12. package/dist/adapters/hotcrp/response.d.ts.map +1 -0
  13. package/dist/adapters/hotcrp/response.js +138 -0
  14. package/dist/adapters/hotcrp/response.js.map +1 -0
  15. package/dist/adapters/latex/conditionals.d.ts +21 -0
  16. package/dist/adapters/latex/conditionals.d.ts.map +1 -0
  17. package/dist/adapters/latex/conditionals.js +75 -0
  18. package/dist/adapters/latex/conditionals.js.map +1 -0
  19. package/dist/adapters/latex/headings.d.ts +10 -0
  20. package/dist/adapters/latex/headings.d.ts.map +1 -0
  21. package/dist/adapters/latex/headings.js +129 -0
  22. package/dist/adapters/latex/headings.js.map +1 -0
  23. package/dist/adapters/latex/index.d.ts +1 -0
  24. package/dist/adapters/latex/index.d.ts.map +1 -1
  25. package/dist/adapters/latex/index.js +3 -0
  26. package/dist/adapters/latex/index.js.map +1 -1
  27. package/dist/adapters/latex/parse.d.ts +1 -1
  28. package/dist/adapters/latex/parse.d.ts.map +1 -1
  29. package/dist/adapters/latex/parse.js +3 -2
  30. package/dist/adapters/latex/parse.js.map +1 -1
  31. package/dist/build.d.ts +1 -1
  32. package/dist/build.d.ts.map +1 -1
  33. package/dist/build.js +1 -1
  34. package/dist/build.js.map +1 -1
  35. package/dist/cli.d.ts +4 -2
  36. package/dist/cli.d.ts.map +1 -1
  37. package/dist/cli.js +126 -37
  38. package/dist/cli.js.map +1 -1
  39. package/dist/domain/heading-case.d.ts +100 -0
  40. package/dist/domain/heading-case.d.ts.map +1 -0
  41. package/dist/domain/heading-case.js +365 -0
  42. package/dist/domain/heading-case.js.map +1 -0
  43. package/dist/domain/result.d.ts +4 -0
  44. package/dist/domain/result.d.ts.map +1 -1
  45. package/dist/domain/result.js +2 -0
  46. package/dist/domain/result.js.map +1 -1
  47. package/dist/domain/submission.d.ts +106 -0
  48. package/dist/domain/submission.d.ts.map +1 -0
  49. package/dist/domain/submission.js +39 -0
  50. package/dist/domain/submission.js.map +1 -0
  51. package/dist/domain/tex-document.d.ts +32 -0
  52. package/dist/domain/tex-document.d.ts.map +1 -1
  53. package/dist/domain/tex-document.js +11 -0
  54. package/dist/domain/tex-document.js.map +1 -1
  55. package/dist/domain/venue-name.d.ts +38 -0
  56. package/dist/domain/venue-name.d.ts.map +1 -0
  57. package/dist/domain/venue-name.js +71 -0
  58. package/dist/domain/venue-name.js.map +1 -0
  59. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  60. package/dist/eslint-rules/latex-language.js +16 -3
  61. package/dist/eslint-rules/latex-language.js.map +1 -1
  62. package/dist/facts-file.d.ts +3 -3
  63. package/dist/facts-file.d.ts.map +1 -1
  64. package/dist/facts-file.js +3 -3
  65. package/dist/facts-file.js.map +1 -1
  66. package/dist/folder-venue-rule.d.ts +51 -0
  67. package/dist/folder-venue-rule.d.ts.map +1 -0
  68. package/dist/folder-venue-rule.js +96 -0
  69. package/dist/folder-venue-rule.js.map +1 -0
  70. package/dist/heading-case.d.ts +48 -0
  71. package/dist/heading-case.d.ts.map +1 -0
  72. package/dist/heading-case.js +191 -0
  73. package/dist/heading-case.js.map +1 -0
  74. package/dist/init.d.ts +6 -0
  75. package/dist/init.d.ts.map +1 -1
  76. package/dist/init.js +3 -3
  77. package/dist/init.js.map +1 -1
  78. package/dist/lib/paper-config.d.ts.map +1 -1
  79. package/dist/lib/paper-config.js +5 -1
  80. package/dist/lib/paper-config.js.map +1 -1
  81. package/dist/new-paper.d.ts +19 -5
  82. package/dist/new-paper.d.ts.map +1 -1
  83. package/dist/new-paper.js +23 -4
  84. package/dist/new-paper.js.map +1 -1
  85. package/dist/paper-settings.d.ts +3 -0
  86. package/dist/paper-settings.d.ts.map +1 -1
  87. package/dist/paper-settings.js +20 -4
  88. package/dist/paper-settings.js.map +1 -1
  89. package/dist/ports/latex.d.ts +7 -1
  90. package/dist/ports/latex.d.ts.map +1 -1
  91. package/dist/ports/submission-portal.d.ts +19 -0
  92. package/dist/ports/submission-portal.d.ts.map +1 -0
  93. package/dist/ports/submission-portal.js +2 -0
  94. package/dist/ports/submission-portal.js.map +1 -0
  95. package/dist/preset-card-rule.d.ts +100 -0
  96. package/dist/preset-card-rule.d.ts.map +1 -0
  97. package/dist/preset-card-rule.js +221 -0
  98. package/dist/preset-card-rule.js.map +1 -0
  99. package/dist/presets.d.ts +43 -2
  100. package/dist/presets.d.ts.map +1 -1
  101. package/dist/presets.js +130 -63
  102. package/dist/presets.js.map +1 -1
  103. package/dist/rules-config.js +1 -1
  104. package/dist/submission.d.ts +61 -0
  105. package/dist/submission.d.ts.map +1 -0
  106. package/dist/submission.js +211 -0
  107. package/dist/submission.js.map +1 -0
  108. package/dist/tex-requirements.d.ts +36 -3
  109. package/dist/tex-requirements.d.ts.map +1 -1
  110. package/dist/tex-requirements.js +30 -2
  111. package/dist/tex-requirements.js.map +1 -1
  112. package/dist/tex-venue-rules.d.ts +7 -8
  113. package/dist/tex-venue-rules.d.ts.map +1 -1
  114. package/dist/tex-venue-rules.js +10 -14
  115. package/dist/tex-venue-rules.js.map +1 -1
  116. package/dist/types.d.ts +13 -0
  117. package/dist/types.d.ts.map +1 -1
  118. package/docs/configuration.md +30 -11
  119. package/docs/optional-rules.md +4 -3
  120. package/docs/rules/format/page-limit.md +1 -1
  121. package/docs/rules/paper/folder-venue-leftover.md +104 -0
  122. package/docs/rules/talk/captions-cover.md +1 -1
  123. package/docs/rules/talk/duration-floor.md +1 -1
  124. package/docs/rules/talk/profile.md +1 -1
  125. package/docs/rules/tex/heading-case.md +424 -0
  126. package/docs/rules.md +64 -53
  127. package/docs/skills.md +5 -4
  128. package/docs/submission.md +64 -0
  129. package/docs/talk.md +1 -1
  130. package/eslint-rules/latex-language.test.mjs +12 -0
  131. package/eslint-rules/latex-language.ts +17 -4
  132. package/eslint-rules/paper-typography.test.mjs +12 -0
  133. package/eslint.config.mjs +59 -1
  134. package/fixtures/build-e2e/acmart/paper.tex +1 -1
  135. package/fixtures/build-e2e/fallback/expect.json +3 -3
  136. package/lib/paper-config.ts +6 -1
  137. package/lib/skill-checks.d.mts +7 -0
  138. package/lib/skill-checks.mjs +31 -0
  139. package/lib/skill-trigger-cases.mjs +11 -0
  140. package/package.json +8 -2
  141. package/presets/acm-sigconf.jsonc +5 -0
  142. package/presets/acm-sigconf.md +43 -0
  143. package/presets/agenticdev.jsonc +27 -6
  144. package/presets/agenticdev.md +151 -0
  145. package/presets/aidc.jsonc +12 -0
  146. package/presets/aidc.md +75 -0
  147. package/presets/aisec.jsonc +5 -1
  148. package/presets/aisec.md +117 -0
  149. package/presets/ieee-conference.jsonc +5 -0
  150. package/presets/ieee-conference.md +38 -0
  151. package/presets/realm.jsonc +7 -2
  152. package/presets/realm.md +166 -0
  153. package/presets/tex/agenticdev.tex +1 -1
  154. package/presets/tex-base.jsonc +6 -0
  155. package/presets/tex-base.md +30 -0
  156. package/presets/venue-profile.schema.json +57 -2
  157. package/skills/find-venue/SKILL.md +6 -5
  158. package/skills/find-venue/SKILL.md.spec.ts +5 -4
  159. package/skills/paper-pipeline/SKILL.md +5 -6
  160. package/skills/paper-pipeline/SKILL.md.spec.ts +4 -5
  161. package/skills/paper-pipeline/paper-pipeline.harness.mjs +4 -0
  162. package/skills/paper-pipeline/references/acl-venue-rules.md +1 -1
  163. package/skills/study-accepted-papers/SKILL.md +3 -3
  164. package/skills/study-accepted-papers/SKILL.md.spec.ts +2 -2
  165. package/skills/submission-portal/SKILL.md +69 -0
  166. package/skills/submission-portal/SKILL.md.spec.ts +71 -0
  167. package/skills/submission-portal/submission-portal.eval.mjs +15 -0
  168. package/skills/submission-portal/submission-portal.test.ts +81 -0
  169. package/skills/submit-paper/SKILL.md +27 -22
  170. package/skills/submit-paper/SKILL.md.spec.ts +26 -21
  171. package/skills/submit-paper/references/publishers/acm.md +1 -1
  172. package/src/adapters/hotcrp/cassette/README.md +24 -0
  173. package/src/adapters/hotcrp/cassette/get-noauth.json +10 -0
  174. package/src/adapters/hotcrp/cassette/get-paper.json +39 -0
  175. package/src/adapters/hotcrp/cassette/post-dot.json +21 -0
  176. package/src/adapters/hotcrp/cassette/post-ok.json +12 -0
  177. package/src/adapters/hotcrp/index.ts +11 -0
  178. package/src/adapters/hotcrp/portal.io.ts +130 -0
  179. package/src/adapters/hotcrp/portal.test.ts +503 -0
  180. package/src/adapters/hotcrp/response.ts +181 -0
  181. package/src/adapters/latex/conditionals.ts +130 -0
  182. package/src/adapters/latex/headings.ts +163 -0
  183. package/src/adapters/latex/index.test.ts +65 -0
  184. package/src/adapters/latex/index.ts +3 -0
  185. package/src/adapters/latex/parse.ts +3 -2
  186. package/src/build.ts +2 -2
  187. package/src/cli.test.ts +155 -3
  188. package/src/cli.ts +161 -25
  189. package/src/domain/heading-case.test.ts +660 -0
  190. package/src/domain/heading-case.ts +538 -0
  191. package/src/domain/result.test.ts +9 -1
  192. package/src/domain/result.ts +5 -0
  193. package/src/domain/submission.test.ts +45 -0
  194. package/src/domain/submission.ts +141 -0
  195. package/src/domain/tex-document.ts +36 -0
  196. package/src/domain/venue-name.test.ts +121 -0
  197. package/src/domain/venue-name.ts +129 -0
  198. package/src/facts-file.test.ts +4 -4
  199. package/src/facts-file.ts +6 -6
  200. package/src/folder-venue-rule.test.ts +182 -0
  201. package/src/folder-venue-rule.ts +141 -0
  202. package/src/heading-case.test.ts +500 -0
  203. package/src/heading-case.ts +275 -0
  204. package/src/init.test.ts +22 -0
  205. package/src/init.ts +9 -2
  206. package/src/new-paper.harness.mjs +11 -9
  207. package/src/new-paper.test.ts +135 -10
  208. package/src/new-paper.ts +38 -7
  209. package/src/paper-settings-commands.test.ts +25 -10
  210. package/src/paper-settings.test.ts +12 -1
  211. package/src/paper-settings.ts +25 -5
  212. package/src/parse-args.test.ts +19 -0
  213. package/src/ports/latex.ts +7 -0
  214. package/src/ports/submission-portal.ts +26 -0
  215. package/src/preset-card-rule.test.ts +266 -0
  216. package/src/preset-card-rule.ts +384 -0
  217. package/src/presets.test.ts +212 -37
  218. package/src/presets.ts +183 -71
  219. package/src/rule-cases.test.ts +147 -42
  220. package/src/rules-config.ts +1 -1
  221. package/src/submission.test.ts +497 -0
  222. package/src/submission.ts +311 -0
  223. package/src/talk-rules.test.ts +6 -3
  224. package/src/tex-requirements.harness.ts +25 -11
  225. package/src/tex-requirements.test.ts +127 -1
  226. package/src/tex-requirements.ts +78 -8
  227. package/src/tex-venue-rules.test.ts +36 -22
  228. package/src/tex-venue-rules.ts +14 -26
  229. package/src/types.ts +13 -0
  230. package/src/venue-rules.test.ts +7 -6
  231. package/templates/paper/paper.tex +1 -1
  232. package/skills/submit-paper/references/venues/agenticdev.md +0 -171
  233. package/skills/submit-paper/references/venues/aisec.md +0 -105
  234. package/skills/submit-paper/references/venues/realm.md +0 -155
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: submission-portal
3
+ description: "Read or change a paper's submission on the venue's HotCRP portal via `paperlint submission show|update`: replace the PDF or abstract, mark it submitted, check it is really \"Submitted\", and verify by sha256 that the portal holds the latest build. Use when the PDF or abstract on HotCRP needs replacing, when asked whether the paper is truly submitted or which PDF reviewers see, or before a deadline. Composes with submit-paper (what to submit); this gets it onto the portal and proves it landed."
4
+ allowed-tools: [Read, Grep, Glob, Bash]
5
+ ---
6
+
7
+ <!-- vigiles:sha256:deb3d74f5c3f93a8 compiled from skills/submission-portal/SKILL.md.spec.ts -->
8
+
9
+ # submission-portal — read and update the submission on the portal, then prove it landed
10
+
11
+ `paperlint submission` talks to the venue's submission portal through its API. Only HotCRP is
12
+ supported; for any other portal the command says so and the upload is done by hand.
13
+
14
+ ## What must be in place
15
+
16
+ - **The venue preset declares the portal**: `"portal": { "kind": "hotcrp", "url": "<the site>" }`
17
+ (`paperlint:aidc` has it). For your own venue preset, add it there.
18
+ - **The paper's `paperlint.json` names its submission**: `"submission": { "id": <number> }` — the
19
+ number HotCRP shows for the paper.
20
+ - **A token in `HOTCRP_TOKEN`**, created by the author on the HotCRP site under Account settings →
21
+ Developer (scopes `submeta:read` and `document:read` to read; `submeta:write` and
22
+ `document:write` to update). Never ask for it in chat, never print it, never write it to a file:
23
+ the command reads it from the environment and sends it only as the `Authorization` header.
24
+
25
+ A missing key or token stops the command with exit 2 and a line naming the file and key, or the
26
+ variable — fix exactly that and run again.
27
+
28
+ ## The procedure — always in this order
29
+
30
+ 1. **Read it.** `npx paperlint submission show papers/<paper>` — status, title, type, topics,
31
+ abstract length, the PDF the portal holds, and `match`: whether that PDF's sha256 equals the
32
+ local `paper.pdf`. Rebuild first (`npx paperlint build papers/<paper>`) if the local PDF is stale.
33
+ 2. **Dry run.** `npx paperlint submission update papers/<paper>` — sends the PDF (add
34
+ `--abstract <file>` for a plain-text abstract, `--submit` to mark it submitted) with HotCRP's
35
+ `dry_run=1`: the portal checks the change and **keeps nothing**. Read `valid`, `changes` and every
36
+ `portal says:` line. Exit 1 means the portal would refuse it — report why, do not go on.
37
+ 3. **Ask the author.** Show the dry run's output and ask for an explicit yes to change the real
38
+ submission.
39
+ 4. **Save — only after that yes.** `npx paperlint submission update papers/<paper> --save` (same
40
+ flags as the dry run). 🔴 `--save` CHANGES A REAL SUBMISSION that reviewers will see. Never run it
41
+ without the author's explicit yes in this conversation, never "to test", and never because a
42
+ dry run was valid.
43
+ 5. **Prove it landed.** `npx paperlint submission show papers/<paper>` again: `match` must read
44
+ `yes — the portal holds this build`, and the status must be what was intended (`submitted` if
45
+ `--submit` was sent). Anything else is not done — say so.
46
+
47
+ The same five steps as commands, in order (`<paper>` is the paper's folder):
48
+
49
+ ```bash
50
+ npx paperlint submission show papers/<paper>
51
+ npx paperlint submission update papers/<paper>
52
+ # … the author reads the dry run and says yes …
53
+ npx paperlint submission update papers/<paper> --save
54
+ npx paperlint submission show papers/<paper>
55
+ ```
56
+
57
+ ## Reading the answers
58
+
59
+ - `match yes` is the only proof the reviewers get the local build; a status of `submitted` alone
60
+ says nothing about WHICH PDF.
61
+ - `the portal refused (HTTP 401): …` — the token is missing, wrong or expired; `403` — the token
62
+ lacks the scope, or the deadline has passed for this change. The text after the colon is
63
+ HotCRP's own; quote it.
64
+ - `SAVE WITHHELD by the portal` — HotCRP did not save; nothing changed.
65
+
66
+ ## What this skill does not do
67
+
68
+ It does not decide whether the paper is ready (that is `harden-paper`), anonymize it, or fill the
69
+ submission form's other fields (authors, conflicts) — see `submit-paper`.
@@ -0,0 +1,71 @@
1
+ // Compiled to SKILL.md by `vigiles compile`. Edit THIS file, never the markdown.
2
+ import { experimental_skill } from "vigiles/spec";
3
+
4
+ export default experimental_skill({
5
+ name: "submission-portal",
6
+ description:
7
+ "Read or change a paper's submission on the venue's HotCRP portal via `paperlint submission show|update`: replace the PDF or abstract, mark it submitted, check it is really \"Submitted\", and verify by sha256 that the portal holds the latest build. Use when the PDF or abstract on HotCRP needs replacing, when asked whether the paper is truly submitted or which PDF reviewers see, or before a deadline. Composes with submit-paper (what to submit); this gets it onto the portal and proves it landed.",
8
+ tools: ["Read", "Grep", "Glob", "Bash"],
9
+ body: `
10
+ # submission-portal — read and update the submission on the portal, then prove it landed
11
+
12
+ \`paperlint submission\` talks to the venue's submission portal through its API. Only HotCRP is
13
+ supported; for any other portal the command says so and the upload is done by hand.
14
+
15
+ ## What must be in place
16
+
17
+ - **The venue preset declares the portal**: \`"portal": { "kind": "hotcrp", "url": "<the site>" }\`
18
+ (\`paperlint:aidc\` has it). For your own venue preset, add it there.
19
+ - **The paper's \`paperlint.json\` names its submission**: \`"submission": { "id": <number> }\` — the
20
+ number HotCRP shows for the paper.
21
+ - **A token in \`HOTCRP_TOKEN\`**, created by the author on the HotCRP site under Account settings →
22
+ Developer (scopes \`submeta:read\` and \`document:read\` to read; \`submeta:write\` and
23
+ \`document:write\` to update). Never ask for it in chat, never print it, never write it to a file:
24
+ the command reads it from the environment and sends it only as the \`Authorization\` header.
25
+
26
+ A missing key or token stops the command with exit 2 and a line naming the file and key, or the
27
+ variable — fix exactly that and run again.
28
+
29
+ ## The procedure — always in this order
30
+
31
+ 1. **Read it.** \`npx paperlint submission show papers/<paper>\` — status, title, type, topics,
32
+ abstract length, the PDF the portal holds, and \`match\`: whether that PDF's sha256 equals the
33
+ local \`paper.pdf\`. Rebuild first (\`npx paperlint build papers/<paper>\`) if the local PDF is stale.
34
+ 2. **Dry run.** \`npx paperlint submission update papers/<paper>\` — sends the PDF (add
35
+ \`--abstract <file>\` for a plain-text abstract, \`--submit\` to mark it submitted) with HotCRP's
36
+ \`dry_run=1\`: the portal checks the change and **keeps nothing**. Read \`valid\`, \`changes\` and every
37
+ \`portal says:\` line. Exit 1 means the portal would refuse it — report why, do not go on.
38
+ 3. **Ask the author.** Show the dry run's output and ask for an explicit yes to change the real
39
+ submission.
40
+ 4. **Save — only after that yes.** \`npx paperlint submission update papers/<paper> --save\` (same
41
+ flags as the dry run). 🔴 \`--save\` CHANGES A REAL SUBMISSION that reviewers will see. Never run it
42
+ without the author's explicit yes in this conversation, never "to test", and never because a
43
+ dry run was valid.
44
+ 5. **Prove it landed.** \`npx paperlint submission show papers/<paper>\` again: \`match\` must read
45
+ \`yes — the portal holds this build\`, and the status must be what was intended (\`submitted\` if
46
+ \`--submit\` was sent). Anything else is not done — say so.
47
+
48
+ The same five steps as commands, in order (\`<paper>\` is the paper's folder):
49
+
50
+ \`\`\`bash
51
+ npx paperlint submission show papers/<paper>
52
+ npx paperlint submission update papers/<paper>
53
+ # … the author reads the dry run and says yes …
54
+ npx paperlint submission update papers/<paper> --save
55
+ npx paperlint submission show papers/<paper>
56
+ \`\`\`
57
+
58
+ ## Reading the answers
59
+
60
+ - \`match yes\` is the only proof the reviewers get the local build; a status of \`submitted\` alone
61
+ says nothing about WHICH PDF.
62
+ - \`the portal refused (HTTP 401): …\` — the token is missing, wrong or expired; \`403\` — the token
63
+ lacks the scope, or the deadline has passed for this change. The text after the colon is
64
+ HotCRP's own; quote it.
65
+ - \`SAVE WITHHELD by the portal\` — HotCRP did not save; nothing changed.
66
+
67
+ ## What this skill does not do
68
+
69
+ It does not decide whether the paper is ready (that is \`harden-paper\`), anonymize it, or fill the
70
+ submission form's other fields (authors, conflicts) — see \`submit-paper\`.`,
71
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * submission-portal — the PAID tier: does this skill's description actually fire?
3
+ *
4
+ * The prompts live in `lib/skill-trigger-cases.mjs` beside every other skill's, so collisions with
5
+ * `submit-paper` and `osf-artifact-upload` are visible in one table. Measures recall on its own
6
+ * territory and precision against those siblings, against the real harness.
7
+ *
8
+ * ⚠️ NEVER RUN. Written 2026-10-03 with the skill; its rate is UNKNOWN, not assumed good.
9
+ *
10
+ * Costs money; not CI.
11
+ * node skills/submission-portal/submission-portal.eval.mjs [trials]
12
+ */
13
+ import { runSkillTriggerEval } from "../../lib/skill-eval-kit.mjs";
14
+
15
+ await runSkillTriggerEval("submission-portal");
@@ -0,0 +1,81 @@
1
+ /**
2
+ * submission-portal — the free tier: the skill's commands are commands the CLI accepts, in the
3
+ * order the skill promises, and the skill passes the shared per-skill checks. No model, no network,
4
+ * no portal.
5
+ *
6
+ * 🔴 WHY THE COMMANDS ARE PARSED, NOT GREPPED: a skill that tells the agent to run a flag the CLI
7
+ * refuses fails at the worst moment — before a deadline, on a real submission. So every
8
+ * `paperlint submission …` the skill writes goes through the CLI's own argument parser and its
9
+ * usage check, and must come out runnable.
10
+ */
11
+ import assert from "node:assert/strict";
12
+ import { readFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { test } from "vitest";
16
+ import { commandsIn } from "vigiles";
17
+ import { checkSkill } from "../../lib/skill-checks.mjs";
18
+ import { parseArgs } from "../../src/cli.ts";
19
+ import { usageProblem } from "../../src/submission.ts";
20
+
21
+ const SKILL = readFileSync(
22
+ join(dirname(fileURLToPath(import.meta.url)), "SKILL.md"),
23
+ "utf8",
24
+ );
25
+ const commands = commandsIn(
26
+ SKILL,
27
+ /(^|[|;&]\s*)npx paperlint submission\b/,
28
+ ).map((c) => c.text);
29
+
30
+ /** A command as the CLI receives it: the words after `npx paperlint`. */
31
+ const argvOf = (command: string): string[] =>
32
+ command
33
+ .replace(/^.*?npx paperlint /, "")
34
+ .split(/\s+/)
35
+ .filter(Boolean);
36
+
37
+ test("the skill names show, a dry run and a save — and nothing else", () => {
38
+ const kinds = commands.map((c) => {
39
+ const a = parseArgs(argvOf(c));
40
+ return `${String(a.paths[0])}${a.save ? " --save" : ""}`;
41
+ });
42
+ assert.deepEqual([...new Set(kinds)], ["show", "update", "update --save"]);
43
+ });
44
+
45
+ test("every `paperlint submission` command in the skill is one the CLI runs", () => {
46
+ assert.ok(
47
+ commands.length > 0,
48
+ "the skill names no `paperlint submission` command",
49
+ );
50
+ for (const c of commands) {
51
+ const a = parseArgs(argvOf(c));
52
+ assert.equal(a.cmd, "submission", c);
53
+ assert.equal(a.unknownFlag, undefined, c);
54
+ assert.equal(a.missingValue, undefined, c);
55
+ const problem = usageProblem({
56
+ sub: a.paths[0],
57
+ paperDir: a.paths[1] ?? ".",
58
+ extra: a.paths.slice(2),
59
+ pdf: a.pdf,
60
+ abstract: a.abstract,
61
+ submit: a.submit,
62
+ save: a.save,
63
+ });
64
+ assert.equal(problem, null, c);
65
+ }
66
+ });
67
+
68
+ test("🔴 the procedure reads, dry-runs, then saves, then reads again — never saves first", () => {
69
+ const order = commands.map((c) => {
70
+ const a = parseArgs(argvOf(c));
71
+ return a.paths[0] === "show" ? "show" : a.save ? "save" : "dry";
72
+ });
73
+ const firstSave = order.indexOf("save");
74
+ assert.ok(firstSave > order.indexOf("dry"), order.join(" → "));
75
+ assert.ok(order.indexOf("dry") > order.indexOf("show"), order.join(" → "));
76
+ assert.ok(order.lastIndexOf("show") > firstSave, order.join(" → "));
77
+ });
78
+
79
+ test("the shared per-skill checks pass", async () => {
80
+ await checkSkill("submission-portal");
81
+ });
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: submit-paper
3
- description: End-to-end playbook for submitting a peer-reviewed paper to a double-blind venue (HotCRP workshops/conferences) — from a hardened draft to "ready for review." Covers building and anonymously hosting a reproduction artifact, HotCRP profile + form mechanics, double-blind hygiene, and the authorship/credential angles for an independent researcher. Use when the paper is drafted and reviewed and it's time to actually submit. Compose with pc-panel-review / paper-adversarial-review (harden first), and with the venue data card (references/venues/<venue>.md) for venue specifics.
3
+ description: End-to-end playbook for submitting a peer-reviewed paper to a double-blind venue (HotCRP workshops/conferences) — from a hardened draft to "ready for review." Covers building and anonymously hosting a reproduction artifact, HotCRP profile + form mechanics, double-blind hygiene, and the authorship/credential angles for an independent researcher. Use when the paper is drafted and reviewed and it's time to actually submit. Compose with pc-panel-review / paper-adversarial-review (harden first), and with the venue card (presets/<venue>.md in the paperlint package) for venue specifics.
4
4
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Skill]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:9e57bcb2ba1fb055 compiled from skills/submit-paper/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:8f53cae32633b002 compiled from skills/submit-paper/SKILL.md.spec.ts -->
8
8
 
9
9
  # submit-paper — get a reviewed paper from "done" to "ready for review"
10
10
 
@@ -12,26 +12,30 @@ Assumes the paper is already hardened (run `pc-panel-review` — incl. its venue
12
12
  `paper-adversarial-review` first). This skill is the submission mechanics — the part that bit us with
13
13
  avoidable friction the first time. Distilled from the AgenticDev 2026 submission.
14
14
 
15
- ## Venue specifics — a DATA card, not a skill
15
+ ## Venue specifics — a CARD beside the venue's preset, not a skill
16
16
  The venue-specific facts (deadline, page limits, tracks, PC-member conflict list, blind model, format
17
- quirks, whether a supplementary-upload field exists, the extension target) live as **plain reference
18
- data files** under `references/venues/<venue>.md`:
19
- - `references/venues/agenticdev.md` — AgenticDev @ ASE (workshop; ACM DL/IEEE Xplore).
20
- - `references/venues/aisec.md` — AISec @ ACM CCS (security workshop; ACM DL; harder bar).
21
-
22
- **The pattern (follow it for every new venue):** when you fetch a venue's exact submission instructions,
23
- save them as a new **data file** `references/venues/<venue>.md` (short `title:` frontmatter only — NOT a
24
- skill with `name:`/`description:`). This preserves the "save the exact venue instructions" rule while
25
- keeping the skill namespace from proliferating one card per venue. The *mechanics* stay here; the venue
26
- file is only the facts.
27
-
28
- **The card is prose; the machine-checked format is a PRESET beside it.** A paper declares its venue
17
+ quirks, whether a supplementary-upload field exists) live in the venue's **card**, `presets/<venue>.md`
18
+ in the paperlint package, beside the preset `presets/<venue>.jsonc` that the lint rules check a
19
+ paper against:
20
+ - `presets/agenticdev.md` — AgenticDev @ ASE (workshop; ASE workshop proceedings).
21
+ - `presets/aisec.md` — AISec @ ACM CCS (security workshop; ACM DL; harder bar).
22
+ - `presets/aidc.md` — AIDC @ IEEE ACSAC (workshop; IEEE template, LLM Usage Statement).
23
+ - `presets/realm.md` — REALM @ EMNLP (ACL family, OpenReview).
24
+
25
+ **The card is prose; the machine-checked format is the PRESET beside it.** A paper declares its venue
29
26
  in `<paper>/paperlint.json`: `{ "extends": "paperlint:<venue>", "kind": "<kind>" }`, and the
30
27
  `pdf/*` lint rules judge the built PDF against that preset (page limit per kind, fonts, page size,
31
- columns, font sizes). A venue with no shipped preset extends a family — `paperlint:acm-sigconf`
32
- for an ACM venue — or a preset of the project's own, `./venues/<name>.jsonc` (paperlint's
33
- `docs/rules.md`, "Writing your own venue preset"). Put the page limits you fetched from the CFP in
34
- the preset's `format.kinds`, each with its quote.
28
+ columns, font sizes). The card's last section — what the preset is, its call for papers, the rules it
29
+ sets and where each comes from — is generated from the preset, so read the numbers there, not from
30
+ memory.
31
+
32
+ **The pattern (follow it for every new venue):** when you fetch a venue's exact submission
33
+ instructions, write the numbers into a preset with their quotes and the rest into a card beside it.
34
+ A venue with no shipped preset extends a family — `paperlint:acm-sigconf` for an ACM venue — in a
35
+ preset of the project's own, `./venues/<name>.jsonc`, with its card `./venues/<name>.md` next to
36
+ it (paperlint's `docs/rules.md`, "Writing your own venue preset"). A venue preset declares
37
+ `"type": "venue"`, the venue's `name` and the `url` of its call for papers. The *mechanics*
38
+ stay here; the card is only the facts.
35
39
 
36
40
  ## Publisher specifics — one level ABOVE the venue
37
41
  Camera-ready mechanics belong to the **publisher**, not the venue: ACM eRights, the submit-vs-final
@@ -110,6 +114,7 @@ Many workshop HotCRPs have **no supplementary-upload field** — check first; if
110
114
  is the step people miss.
111
115
  - You can edit until the deadline: use **Replace** on the Submission field to swap the PDF for a late
112
116
  fix (e.g. adding the artifact link after you host it). Submit early, upgrade before the deadline.
117
+ - When the venue preset declares its HotCRP portal, replacing the PDF or abstract and proving the portal holds the latest build is `submission-portal` (`paperlint submission show|update`).
113
118
 
114
119
  ## 6. Independent-researcher / dossier notes
115
120
  - **Name consistency** is the quiet risk: publications, LinkedIn, passport, letters must resolve to one
@@ -147,9 +152,9 @@ convention with no reader.
147
152
  ## Compose with
148
153
  - Harden first: `pc-panel-review` (incl. its venue-fit mode), `paper-adversarial-review`, `render-paper`.
149
154
  - Publisher mechanics (camera-ready): `references/publishers/<publisher>.md`.
150
- - Venue specifics: the matching data card `references/venues/<venue>.md` (e.g.
151
- `references/venues/agenticdev.md`) for deadlines, page limits, PC-member conflict lists, whether
152
- supplementary upload exists, and the extension target.
155
+ - Venue specifics: the venue's card `presets/<venue>.md` in the paperlint package (e.g.
156
+ `presets/agenticdev.md`) for deadlines, page limits, PC-member conflict lists, and whether a
157
+ supplementary upload exists.
153
158
 
154
159
  ## Provenance
155
160
  Written from the AgenticDev 2026 @ ASE submission (2026-07-13): PC-panel-reviewed, self-checking
@@ -9,7 +9,7 @@ import { experimental_skill } from "vigiles/spec";
9
9
  export default experimental_skill({
10
10
  name: "submit-paper",
11
11
  description:
12
- 'End-to-end playbook for submitting a peer-reviewed paper to a double-blind venue (HotCRP workshops/conferences) — from a hardened draft to "ready for review." Covers building and anonymously hosting a reproduction artifact, HotCRP profile + form mechanics, double-blind hygiene, and the authorship/credential angles for an independent researcher. Use when the paper is drafted and reviewed and it\'s time to actually submit. Compose with pc-panel-review / paper-adversarial-review (harden first), and with the venue data card (references/venues/<venue>.md) for venue specifics.',
12
+ 'End-to-end playbook for submitting a peer-reviewed paper to a double-blind venue (HotCRP workshops/conferences) — from a hardened draft to "ready for review." Covers building and anonymously hosting a reproduction artifact, HotCRP profile + form mechanics, double-blind hygiene, and the authorship/credential angles for an independent researcher. Use when the paper is drafted and reviewed and it\'s time to actually submit. Compose with pc-panel-review / paper-adversarial-review (harden first), and with the venue card (presets/<venue>.md in the paperlint package) for venue specifics.',
13
13
  tools: [
14
14
  "Read",
15
15
  "Write",
@@ -28,26 +28,30 @@ Assumes the paper is already hardened (run \`pc-panel-review\` — incl. its ven
28
28
  \`paper-adversarial-review\` first). This skill is the submission mechanics — the part that bit us with
29
29
  avoidable friction the first time. Distilled from the AgenticDev 2026 submission.
30
30
 
31
- ## Venue specifics — a DATA card, not a skill
31
+ ## Venue specifics — a CARD beside the venue's preset, not a skill
32
32
  The venue-specific facts (deadline, page limits, tracks, PC-member conflict list, blind model, format
33
- quirks, whether a supplementary-upload field exists, the extension target) live as **plain reference
34
- data files** under \`references/venues/<venue>.md\`:
35
- - \`references/venues/agenticdev.md\` — AgenticDev @ ASE (workshop; ACM DL/IEEE Xplore).
36
- - \`references/venues/aisec.md\` — AISec @ ACM CCS (security workshop; ACM DL; harder bar).
37
-
38
- **The pattern (follow it for every new venue):** when you fetch a venue's exact submission instructions,
39
- save them as a new **data file** \`references/venues/<venue>.md\` (short \`title:\` frontmatter only — NOT a
40
- skill with \`name:\`/\`description:\`). This preserves the "save the exact venue instructions" rule while
41
- keeping the skill namespace from proliferating one card per venue. The *mechanics* stay here; the venue
42
- file is only the facts.
43
-
44
- **The card is prose; the machine-checked format is a PRESET beside it.** A paper declares its venue
33
+ quirks, whether a supplementary-upload field exists) live in the venue's **card**, \`presets/<venue>.md\`
34
+ in the paperlint package, beside the preset \`presets/<venue>.jsonc\` that the lint rules check a
35
+ paper against:
36
+ - \`presets/agenticdev.md\` — AgenticDev @ ASE (workshop; ASE workshop proceedings).
37
+ - \`presets/aisec.md\` — AISec @ ACM CCS (security workshop; ACM DL; harder bar).
38
+ - \`presets/aidc.md\` — AIDC @ IEEE ACSAC (workshop; IEEE template, LLM Usage Statement).
39
+ - \`presets/realm.md\` — REALM @ EMNLP (ACL family, OpenReview).
40
+
41
+ **The card is prose; the machine-checked format is the PRESET beside it.** A paper declares its venue
45
42
  in \`<paper>/paperlint.json\`: \`{ "extends": "paperlint:<venue>", "kind": "<kind>" }\`, and the
46
43
  \`pdf/*\` lint rules judge the built PDF against that preset (page limit per kind, fonts, page size,
47
- columns, font sizes). A venue with no shipped preset extends a family — \`paperlint:acm-sigconf\`
48
- for an ACM venue — or a preset of the project's own, \`./venues/<name>.jsonc\` (paperlint's
49
- \`docs/rules.md\`, "Writing your own venue preset"). Put the page limits you fetched from the CFP in
50
- the preset's \`format.kinds\`, each with its quote.
44
+ columns, font sizes). The card's last section — what the preset is, its call for papers, the rules it
45
+ sets and where each comes from — is generated from the preset, so read the numbers there, not from
46
+ memory.
47
+
48
+ **The pattern (follow it for every new venue):** when you fetch a venue's exact submission
49
+ instructions, write the numbers into a preset with their quotes and the rest into a card beside it.
50
+ A venue with no shipped preset extends a family — \`paperlint:acm-sigconf\` for an ACM venue — in a
51
+ preset of the project's own, \`./venues/<name>.jsonc\`, with its card \`./venues/<name>.md\` next to
52
+ it (paperlint's \`docs/rules.md\`, "Writing your own venue preset"). A venue preset declares
53
+ \`"type": "venue"\`, the venue's \`name\` and the \`url\` of its call for papers. The *mechanics*
54
+ stay here; the card is only the facts.
51
55
 
52
56
  ## Publisher specifics — one level ABOVE the venue
53
57
  Camera-ready mechanics belong to the **publisher**, not the venue: ACM eRights, the submit-vs-final
@@ -126,6 +130,7 @@ Many workshop HotCRPs have **no supplementary-upload field** — check first; if
126
130
  is the step people miss.
127
131
  - You can edit until the deadline: use **Replace** on the Submission field to swap the PDF for a late
128
132
  fix (e.g. adding the artifact link after you host it). Submit early, upgrade before the deadline.
133
+ - When the venue preset declares its HotCRP portal, replacing the PDF or abstract and proving the portal holds the latest build is \`submission-portal\` (\`paperlint submission show|update\`).
129
134
 
130
135
  ## 6. Independent-researcher / dossier notes
131
136
  - **Name consistency** is the quiet risk: publications, LinkedIn, passport, letters must resolve to one
@@ -163,9 +168,9 @@ convention with no reader.
163
168
  ## Compose with
164
169
  - Harden first: \`pc-panel-review\` (incl. its venue-fit mode), \`paper-adversarial-review\`, \`render-paper\`.
165
170
  - Publisher mechanics (camera-ready): \`references/publishers/<publisher>.md\`.
166
- - Venue specifics: the matching data card \`references/venues/<venue>.md\` (e.g.
167
- \`references/venues/agenticdev.md\`) for deadlines, page limits, PC-member conflict lists, whether
168
- supplementary upload exists, and the extension target.
171
+ - Venue specifics: the venue's card \`presets/<venue>.md\` in the paperlint package (e.g.
172
+ \`presets/agenticdev.md\`) for deadlines, page limits, PC-member conflict lists, and whether a
173
+ supplementary upload exists.
169
174
 
170
175
  ## Provenance
171
176
  Written from the AgenticDev 2026 @ ASE submission (2026-07-13): PC-panel-reviewed, self-checking
@@ -8,7 +8,7 @@ tags: [publisher, acm, camera-ready, erights, ccs]
8
8
  # ACM: what camera-ready requires
9
9
 
10
10
  > This is the **publisher** level, not the venue level. Identical for AgenticDev@ASE, AISec@CCS,
11
- > and any other ACM conference. The venue card (`../venues/<venue>.md`) holds only what's its own:
11
+ > and any other ACM conference. The venue card (`presets/<venue>.md` in the paperlint package) holds only what's its own:
12
12
  > dates, page limit, its own DOI/ISBN, blind model.
13
13
  >
14
14
  > Captured 2026-08-24 from the live AgenticDev #20 HotCRP form — field by field, not from memory.
@@ -0,0 +1,24 @@
1
+ # HotCRP cassette
2
+
3
+ Real answers to the requests `paperlint submission` sends, recorded from a live HotCRP instance
4
+ through HotCRP's documented API (`/api/paper`). `portal.test.ts` replays them from its
5
+ local mock server and parses each one through the adapter's real zod schemas: a schema that rejects
6
+ a real answer is the defect class these files exist to catch. A hand-written mock body is what let
7
+ a wrong upload field name through once.
8
+
9
+ Only read-only requests were recorded: one `GET` and two `POST`s with `dry_run=1` — nothing was
10
+ saved. Identifying values (the submission id, title, abstract, authors, topics, the PDF's hash, the
11
+ timestamps) are replaced with fakes; HotCRP's keys, value types, statuses and message texts are
12
+ verbatim.
13
+
14
+ | file | request | HTTP |
15
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ---: |
16
+ | `get-paper.json` | `GET /api/paper?p=<id>&word_limit=hard` with a token | 200 |
17
+ | `get-noauth.json` | the same without a token | 401 |
18
+ | `post-dot.json` | `POST /api/paper?p=<id>&dry_run=1`, PDF part named `paper.pdf`, `content_file` the same — PHP renames the field to `paper_pdf`, so HotCRP finds no upload | 200 |
19
+ | `post-ok.json` | the same with the part and `content_file` named `paperlint_pdf` | 200 |
20
+
21
+ To re-record: run the same four requests with `curl` against a HotCRP site where you have a
22
+ submission and a token (`Authorization: bearer $HOTCRP_TOKEN`), always with `dry_run=1` on a `POST`,
23
+ save each body, then replace every identifying value with a fake of the same type before committing.
24
+ These files ship in the npm package with the rest of `src/`, like the tests beside them.
@@ -0,0 +1,10 @@
1
+ {
2
+ "ok": false,
3
+ "message_list": [
4
+ {
5
+ "status": 2,
6
+ "message": "<0>Missing credentials"
7
+ }
8
+ ],
9
+ "status_code": 401
10
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "ok": true,
3
+ "paper": {
4
+ "object": "paper",
5
+ "pid": 7,
6
+ "title": "An Example Paper Title",
7
+ "submission": {
8
+ "mimetype": "application/pdf",
9
+ "hash": "sha2-b3e602c135f2455f64aecfdc2e1e7f81dad20c5d69f0981e965b1a0131d47163",
10
+ "timestamp": 1790000000,
11
+ "size": 17,
12
+ "pages": 2
13
+ },
14
+ "abstract": "An example abstract of exactly ten words for the test.",
15
+ "contacts": [
16
+ {
17
+ "email": "author@example.org",
18
+ "given_name": "A.",
19
+ "family_name": "Author",
20
+ "affiliation": "Example University"
21
+ }
22
+ ],
23
+ "paper_type": "Example paper type",
24
+ "authors": [
25
+ {
26
+ "email": "author@example.org",
27
+ "given_name": "A.",
28
+ "family_name": "Author",
29
+ "affiliation": "Example University"
30
+ }
31
+ ],
32
+ "topics": ["Example topic one", "Example topic two"],
33
+ "pc_conflicts": {},
34
+ "status": "submitted",
35
+ "submitted": true,
36
+ "submitted_at": 1790000100,
37
+ "modified_at": 1790000100
38
+ }
39
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "ok": false,
3
+ "message_list": [
4
+ {
5
+ "status": 2,
6
+ "field": "submission",
7
+ "message": "<0>Ignored attempt to upload document without any content"
8
+ },
9
+ {
10
+ "status": 3,
11
+ "field": "submission"
12
+ },
13
+ {
14
+ "status": -3
15
+ }
16
+ ],
17
+ "dry_run": true,
18
+ "valid": false,
19
+ "change_list": [],
20
+ "pid": 7
21
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "ok": true,
3
+ "message_list": [
4
+ {
5
+ "status": -3
6
+ }
7
+ ],
8
+ "dry_run": true,
9
+ "valid": true,
10
+ "change_list": ["submission"],
11
+ "pid": 7
12
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * HotCRP, as the `SubmissionPortal` port: its REST API read and written over `fetch`, its answers
3
+ * parsed here. Everything that changes if HotCRP's API does lives in this folder.
4
+ */
5
+ export {
6
+ CONTENT_FIELD,
7
+ changeObject,
8
+ hotcrpPortal,
9
+ type HotcrpOptions,
10
+ } from "./portal.io.ts";
11
+ export { parseShow, parseUpdate, sha256OfHash } from "./response.ts";