paperlint 2.0.0 → 2.1.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 (207) hide show
  1. package/.github/workflows/ci.yml +4 -4
  2. package/CLAUDE.md +3 -3
  3. package/CONTRIBUTING.md +21 -0
  4. package/LICENSE +1 -1
  5. package/README.md +81 -24
  6. package/action.harness.mjs +10 -10
  7. package/action.mutations.mjs +3 -3
  8. package/action.yml +11 -11
  9. package/bin/{rpp.mjs → paperlint.mjs} +2 -2
  10. package/dist/adapters/banal/invocation.js +3 -3
  11. package/dist/adapters/banal/invocation.js.map +1 -1
  12. package/dist/adapters/banal/run.d.ts +1 -1
  13. package/dist/adapters/banal/run.js +1 -1
  14. package/dist/adapters/banal/run.js.map +1 -1
  15. package/dist/adapters/banal/settings.d.ts +4 -4
  16. package/dist/adapters/banal/settings.d.ts.map +1 -1
  17. package/dist/adapters/banal/settings.js +4 -4
  18. package/dist/adapters/banal/settings.js.map +1 -1
  19. package/dist/adapters/banal/xml.js +1 -1
  20. package/dist/adapters/banal/xml.js.map +1 -1
  21. package/dist/adapters/curl/download.io.d.ts.map +1 -1
  22. package/dist/adapters/curl/download.io.js +2 -2
  23. package/dist/adapters/curl/download.io.js.map +1 -1
  24. package/dist/build-engine.d.ts +1 -1
  25. package/dist/build-engine.d.ts.map +1 -1
  26. package/dist/build-engine.js +6 -3
  27. package/dist/build-engine.js.map +1 -1
  28. package/dist/build.d.ts +1 -1
  29. package/dist/build.d.ts.map +1 -1
  30. package/dist/build.js +5 -4
  31. package/dist/build.js.map +1 -1
  32. package/dist/cli.d.ts +29 -30
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +174 -98
  35. package/dist/cli.js.map +1 -1
  36. package/dist/doctor.d.ts.map +1 -1
  37. package/dist/doctor.js +21 -6
  38. package/dist/doctor.js.map +1 -1
  39. package/dist/engine.d.ts +2 -2
  40. package/dist/engine.d.ts.map +1 -1
  41. package/dist/engine.js +1 -1
  42. package/dist/facts-file.d.ts +33 -9
  43. package/dist/facts-file.d.ts.map +1 -1
  44. package/dist/facts-file.js +117 -17
  45. package/dist/facts-file.js.map +1 -1
  46. package/dist/hooks-settings.d.ts +0 -18
  47. package/dist/hooks-settings.d.ts.map +1 -1
  48. package/dist/hooks-settings.js +70 -42
  49. package/dist/hooks-settings.js.map +1 -1
  50. package/dist/init.d.ts +13 -10
  51. package/dist/init.d.ts.map +1 -1
  52. package/dist/init.js +74 -42
  53. package/dist/init.js.map +1 -1
  54. package/dist/link-skills.js +2 -2
  55. package/dist/link-skills.js.map +1 -1
  56. package/dist/new-paper.d.ts +2 -2
  57. package/dist/new-paper.d.ts.map +1 -1
  58. package/dist/new-paper.js +17 -2
  59. package/dist/new-paper.js.map +1 -1
  60. package/dist/paper-settings.d.ts +68 -0
  61. package/dist/paper-settings.d.ts.map +1 -0
  62. package/dist/paper-settings.js +144 -0
  63. package/dist/paper-settings.js.map +1 -0
  64. package/dist/presets.d.ts +84 -0
  65. package/dist/presets.d.ts.map +1 -0
  66. package/dist/presets.js +186 -0
  67. package/dist/presets.js.map +1 -0
  68. package/dist/rules-config.d.ts +8 -1
  69. package/dist/rules-config.d.ts.map +1 -1
  70. package/dist/rules-config.js +13 -8
  71. package/dist/rules-config.js.map +1 -1
  72. package/dist/structure.d.ts.map +1 -1
  73. package/dist/structure.js +2 -1
  74. package/dist/structure.js.map +1 -1
  75. package/dist/tex-requirements.d.ts +63 -7
  76. package/dist/tex-requirements.d.ts.map +1 -1
  77. package/dist/tex-requirements.js +91 -24
  78. package/dist/tex-requirements.js.map +1 -1
  79. package/dist/toolchain.d.ts +2 -2
  80. package/dist/toolchain.d.ts.map +1 -1
  81. package/dist/toolchain.js +8 -8
  82. package/dist/toolchain.js.map +1 -1
  83. package/dist/types.d.ts +4 -4
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/venue-rules.d.ts +109 -0
  86. package/dist/venue-rules.d.ts.map +1 -0
  87. package/dist/venue-rules.js +387 -0
  88. package/dist/venue-rules.js.map +1 -0
  89. package/docs/configuration.md +73 -17
  90. package/docs/e2e.md +3 -4
  91. package/docs/install.md +11 -3
  92. package/docs/optional-rules.md +26 -25
  93. package/docs/rules.md +132 -3
  94. package/docs/toolchain.md +5 -5
  95. package/eslint-rules/paper-typography.mjs +1 -1
  96. package/eslint-rules/paper-typography.mutations.mjs +1 -1
  97. package/eslint-rules/papers.mjs +6 -2
  98. package/eslint-rules/pdf-last-page-balance.harness.mjs +5 -3
  99. package/eslint-rules/pdf-last-page-balance.mjs +23 -2
  100. package/fixtures/build-e2e/acmart/paperlint.json +1 -0
  101. package/fixtures/build-e2e/guards/paper.tex +1 -1
  102. package/fixtures/pdf-facts/README.md +1 -1
  103. package/fixtures/real-markdown-paper/baseline.json +1 -1
  104. package/fixtures/real-markdown-paper/baseline.mjs +2 -2
  105. package/fixtures/toolchain-mirror/install-tl +2 -2
  106. package/fixtures/toolchain-mirror/stub-pdflatex +1 -1
  107. package/fixtures/toolchain-mirror/stub-tlmgr +1 -1
  108. package/hooks/hooks.harness.mjs +1 -1
  109. package/lib/paper-config.d.mts +7 -0
  110. package/lib/paper-config.harness.mjs +3 -3
  111. package/lib/paper-config.mjs +34 -3
  112. package/lib/skill-trigger-cases.harness.mjs +1 -1
  113. package/package.json +4 -4
  114. package/plugin/hooks/hooks.json +3 -3
  115. package/scripts/check.mjs +1 -1
  116. package/scripts/harness-api.frozen.json +1 -1
  117. package/scripts/harness-api.test.ts +2 -2
  118. package/scripts/layer-legacy.frozen.json +1 -1
  119. package/scripts/mutation-batteries-frozen.harness.mjs +1 -1
  120. package/scripts/mutation-batteries.frozen.json +2 -2
  121. package/scripts/release-config.test.ts +1 -1
  122. package/skills/find-venue/SKILL.md +5 -1
  123. package/skills/find-venue/SKILL.md.spec.ts +4 -0
  124. package/skills/paper-pipeline/PIPELINE-MAP.md +14 -4
  125. package/skills/paper-pipeline/SKILL.md +4 -3
  126. package/skills/paper-pipeline/SKILL.md.spec.ts +3 -2
  127. package/skills/paper-pipeline/description-language.eval.mjs +1 -1
  128. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +1 -1
  129. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +2 -2
  130. package/skills/paper-pipeline/pipeline-firing.eval.mjs +1 -1
  131. package/skills/paper-pipeline/pipeline-language.eval.mjs +1 -1
  132. package/skills/paper-pipeline/scripts/consumer.d.mts +1 -0
  133. package/skills/paper-pipeline/scripts/consumer.harness.mjs +10 -4
  134. package/skills/paper-pipeline/scripts/consumer.mjs +8 -1
  135. package/skills/paper-pipeline/scripts/consumer.mutations.mjs +1 -1
  136. package/skills/render-paper/SKILL.md +3 -3
  137. package/skills/render-paper/SKILL.md.spec.ts +2 -2
  138. package/skills/render-paper/extract-pdf-facts.harness.mjs +6 -6
  139. package/skills/render-paper/extract-pdf-facts.mjs +4 -4
  140. package/skills/study-accepted-papers/SKILL.md +2 -1
  141. package/skills/study-accepted-papers/SKILL.md.spec.ts +1 -0
  142. package/skills/submit-paper/SKILL.md +9 -1
  143. package/skills/submit-paper/SKILL.md.spec.ts +8 -0
  144. package/skills/submit-paper/references/venues/acm-sigconf.jsonc +86 -0
  145. package/skills/submit-paper/references/venues/agenticdev.jsonc +34 -102
  146. package/skills/submit-paper/references/venues/aisec.jsonc +21 -82
  147. package/skills/submit-paper/references/venues/realm.jsonc +45 -43
  148. package/skills/submit-paper/references/venues/tex-base.jsonc +1 -1
  149. package/skills/submit-paper/references/venues/venue-profile.schema.json +120 -30
  150. package/src/adapters/banal/index.test.ts +1 -1
  151. package/src/adapters/banal/invocation.test.ts +3 -3
  152. package/src/adapters/banal/invocation.ts +3 -3
  153. package/src/adapters/banal/locate.test.ts +4 -4
  154. package/src/adapters/banal/run.ts +2 -2
  155. package/src/adapters/banal/settings.test.ts +12 -6
  156. package/src/adapters/banal/settings.ts +9 -5
  157. package/src/adapters/banal/xml.test.ts +1 -1
  158. package/src/adapters/banal/xml.ts +1 -1
  159. package/src/adapters/curl/download.io.ts +4 -2
  160. package/src/adapters/curl/download.test.ts +3 -1
  161. package/src/adapters/node/files.test.ts +1 -1
  162. package/src/adapters/node/process.test.ts +2 -2
  163. package/src/adapters/node/workspace.test.ts +4 -4
  164. package/src/build-engine.harness.mjs +2 -2
  165. package/src/build-engine.ts +11 -3
  166. package/src/build.harness.mjs +27 -22
  167. package/src/build.mutations.mjs +3 -2
  168. package/src/build.ts +6 -5
  169. package/src/cli.harness.mjs +72 -131
  170. package/src/cli.mutations.mjs +7 -32
  171. package/src/cli.ts +204 -121
  172. package/src/doctor.harness.mjs +9 -25
  173. package/src/doctor.ts +28 -5
  174. package/src/engine.harness.mjs +4 -2
  175. package/src/engine.ts +2 -2
  176. package/src/facts-file.test.ts +54 -7
  177. package/src/facts-file.ts +145 -24
  178. package/src/hooks-settings.harness.mjs +24 -15
  179. package/src/hooks-settings.mutations.mjs +2 -4
  180. package/src/hooks-settings.ts +92 -49
  181. package/src/init.ts +88 -52
  182. package/src/latex-log.harness.mjs +1 -1
  183. package/src/link-skills.harness.mjs +3 -1
  184. package/src/link-skills.mutations.mjs +1 -1
  185. package/src/link-skills.ts +2 -2
  186. package/src/new-paper.harness.mjs +10 -7
  187. package/src/new-paper.test.ts +145 -0
  188. package/src/new-paper.ts +22 -2
  189. package/src/paper-settings-commands.test.ts +311 -0
  190. package/src/paper-settings.test.ts +220 -0
  191. package/src/paper-settings.ts +206 -0
  192. package/src/pdf-facts.harness.mjs +1 -1
  193. package/src/presets.test.ts +236 -0
  194. package/src/presets.ts +307 -0
  195. package/src/rules-config.ts +14 -9
  196. package/src/structure.harness.mjs +4 -4
  197. package/src/structure.ts +2 -1
  198. package/src/tex-requirements.harness.mjs +12 -18
  199. package/src/tex-requirements.ts +179 -29
  200. package/src/toolchain.harness.mjs +34 -25
  201. package/src/toolchain.ts +14 -8
  202. package/src/types.ts +4 -4
  203. package/src/venue-rules.test.ts +545 -0
  204. package/src/venue-rules.ts +573 -0
  205. package/templates/paper/paperlint.json +4 -0
  206. package/fixtures/build-e2e/acmart/venue.json +0 -1
  207. package/plugin/.claude-plugin/plugin.json +0 -8
@@ -44,7 +44,7 @@ That list was replaced on 2026-08-03; nothing about the toolchain below changed.
44
44
  ## Toolchain (one command, not a checklist)
45
45
  - **Compiler:** TeX Live, installed by paperlint with exactly the packages the venue profiles declare:
46
46
  \`\`\`
47
- npx paperlint toolchain # into ~/.cache/rpp/texlive (RPP_TEXLIVE_DIR overrides); idempotent
47
+ npx paperlint toolchain # into ~/.cache/paperlint/texlive (PAPERLINT_TEXLIVE_DIR overrides); idempotent
48
48
  npx paperlint toolchain --check # report what is missing, change nothing
49
49
  \`\`\`
50
50
  Measured 2026-09-24: 3 min from an empty directory, 269 MB, TeX Live 2026, 47 packages verified
@@ -56,7 +56,7 @@ That list was replaced on 2026-08-03; nothing about the toolchain below changed.
56
56
  package gets it there — never in a script, never here. A TeX Live without \`libertine\` builds an
57
57
  acmart paper GREEN in Computer Modern; that is why the files are checked, not the exit code.
58
58
  - **Reading the PDF** needs nothing installed: \`paperlint build\` and \`extract-pdf-facts.mjs\` use pdf.js,
59
- which comes with rpp. Page size, columns and font sizes come from banal (HotCRP's page-geometry
59
+ which comes with paperlint. Page size, columns and font sizes come from banal (HotCRP's page-geometry
60
60
  script), which \`npx paperlint toolchain\` installs and which runs on pdf.js output — it needs \`perl\`,
61
61
  and no poppler. Without banal those facts are \`null\` and the build says so.
62
62
  - **Renderer:** \`pip install --quiet pymupdf\` (system rasterizers and ghostscript are often missing or
@@ -36,7 +36,7 @@ const check = (label, cond, detail = "") => {
36
36
  n++;
37
37
  };
38
38
 
39
- const root = realpathSync(mkdtempSync(join(tmpdir(), "rpp-extract-")));
39
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "paperlint-extract-")));
40
40
  /**
41
41
  * Run the shim from `root`, with no banal anywhere unless `env` names one. `HOME` is the temp root:
42
42
  * without it Node falls back to the account's home, where `paperlint toolchain` may have installed banal.
@@ -59,8 +59,8 @@ try {
59
59
  mkdirSync(paper, { recursive: true });
60
60
  cpSync(join(FIX, "t3-mixed.pdf"), join(paper, "paper.pdf"));
61
61
  writeFileSync(
62
- join(paper, "venue.json"),
63
- JSON.stringify({ venue: "agenticdev", kind: "short" }),
62
+ join(paper, "paperlint.json"),
63
+ JSON.stringify({ extends: "paperlint:agenticdev", kind: "short" }),
64
64
  );
65
65
  const factsFile = join(paper, "_build", "paper.facts.json");
66
66
 
@@ -88,7 +88,7 @@ try {
88
88
  );
89
89
  const facts = JSON.parse(readFileSync(factsFile, "utf8"));
90
90
  check(
91
- "the facts are schema 2, about paper.pdf, for the venue venue.json declares",
91
+ "the facts are schema 2, about paper.pdf, for the venue paperlint.json declares",
92
92
  facts.schema === 2 &&
93
93
  facts.pdf === "paper.pdf" &&
94
94
  facts.venue === "agenticdev" &&
@@ -148,8 +148,8 @@ try {
148
148
  );
149
149
 
150
150
  writeFileSync(
151
- join(paper, "venue.json"),
152
- JSON.stringify({ venue: "x", pdf: "build/other.pdf" }),
151
+ join(paper, "paperlint.json"),
152
+ JSON.stringify({ pdf: "build/other.pdf" }),
153
153
  );
154
154
  const missing = shim([paper, "--strict"], { BANAL: fake });
155
155
  check(
@@ -8,7 +8,7 @@
8
8
  * it compiles a paper. This file is a composition root: it reads the environment and builds the real
9
9
  * adapters (`dist/adapters/node/`), and it owns the `--strict` policy. This
10
10
  * script exists for two callers the build does not serve: a PDF paperlint did not build (a paper that
11
- * declares its artifact elsewhere in `venue.json`), and CI steps that name this script by path.
11
+ * declares its artifact elsewhere in `paperlint.json`), and CI steps that name this script by path.
12
12
  * It reaches the package's compiled code through `../../dist/`, resolved from this file's real
13
13
  * location, so it works the same from a checkout, from `node_modules` and through a symlink.
14
14
  *
@@ -48,14 +48,14 @@ import {
48
48
  import { whyNoGeometry } from "../../dist/domain/geometry.js";
49
49
  import { exitCodeFor } from "../../dist/exit-code.js";
50
50
 
51
- /** The paper's `venue.json`, read from disk. */
51
+ /** The venue the paper's `paperlint.json` declares, read from disk. */
52
52
  export const declaredVenue = (paperDir) => declaredIn(nodeFiles, paperDir);
53
53
 
54
54
  const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
55
55
 
56
56
  /**
57
57
  * Accept either a path to a PDF or a PAPER DIRECTORY — and in the second case find the artifact on
58
- * its own: the path the paper declares in the `pdf` field of `venue.json`, else `paper.pdf` beside
58
+ * its own: the path the paper declares in the `pdf` field of `paperlint.json`, else `paper.pdf` beside
59
59
  * it. A paper written in markdown and built by its own script into `build/acl_latex.pdf` would
60
60
  * otherwise be skipped silently. The facts ALWAYS go into `<paper directory>/_build/`, not next to
61
61
  * the PDF, where neither a rule's glob nor a human looks.
@@ -111,7 +111,7 @@ if (isMain(import.meta.url)) {
111
111
  finish(
112
112
  "no-artifact",
113
113
  strict,
114
- `🛑 no artifact ${pdf} — the paper declared it in venue.json, but it is not built`,
114
+ `🛑 no artifact ${pdf} — the paper declared it in paperlint.json, but it is not built`,
115
115
  );
116
116
  // The composition root: banal as the measurer, wired from this process's environment.
117
117
  const settings = parseBanalSettings(process.env, hostDirs());
@@ -5,7 +5,7 @@ context: fork
5
5
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Agent]
6
6
  ---
7
7
 
8
- <!-- vigiles:sha256:eb77acd927708132 compiled from skills/study-accepted-papers/SKILL.md.spec.ts -->
8
+ <!-- vigiles:sha256:177ca8805990361e compiled from skills/study-accepted-papers/SKILL.md.spec.ts -->
9
9
 
10
10
  # study-accepted-papers — learn the venue's bar from its own accepted corpus, then lever your draft up
11
11
 
@@ -196,6 +196,7 @@ label on it.
196
196
  - `verify-citations` — consumes the Step-4 citation gaps.
197
197
  - `extend-paper` — the natural home for every EXPENSIVE lever this skill surfaces.
198
198
  - `submit-paper` venue data card (`submit-paper/references/venues/<venue>.md`) — save durable venue-bar findings there as data, not as a new skill per venue.
199
+ - the paper's `<paper>/paperlint.json` (`{ "extends": "paperlint:<venue>", "kind": "<kind>" }`) — which venue preset the `pdf/*` rules check the built PDF against; a venue with no shipped preset extends a family (`paperlint:acm-sigconf`) or the project's own `./venues/<name>.jsonc`. Findings about the venue's page limit or format belong in that preset, with their source quote.
199
200
 
200
201
  ## Provenance
201
202
  Built from the **AISec 2026 @ ACM CCS** polish run (2026-07): the "Safety Theater in Agentic Coding /
@@ -212,6 +212,7 @@ label on it.
212
212
  - \`verify-citations\` — consumes the Step-4 citation gaps.
213
213
  - \`extend-paper\` — the natural home for every EXPENSIVE lever this skill surfaces.
214
214
  - \`submit-paper\` venue data card (\`submit-paper/references/venues/<venue>.md\`) — save durable venue-bar findings there as data, not as a new skill per venue.
215
+ - the paper's \`<paper>/paperlint.json\` (\`{ "extends": "paperlint:<venue>", "kind": "<kind>" }\`) — which venue preset the \`pdf/*\` rules check the built PDF against; a venue with no shipped preset extends a family (\`paperlint:acm-sigconf\`) or the project's own \`./venues/<name>.jsonc\`. Findings about the venue's page limit or format belong in that preset, with their source quote.
215
216
 
216
217
  ## Provenance
217
218
  Built from the **AISec 2026 @ ACM CCS** polish run (2026-07): the "Safety Theater in Agentic Coding /
@@ -4,7 +4,7 @@ description: End-to-end playbook for submitting a peer-reviewed paper to a doubl
4
4
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch, Skill]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:6befc6c0f3e910bd compiled from skills/submit-paper/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:4a42424fd18098e5 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
 
@@ -36,6 +36,14 @@ skill with `name:`/`description:`). This preserves the "save the exact venue ins
36
36
  keeping the skill namespace from proliferating one card per venue. The *mechanics* stay here; the venue
37
37
  file is only the facts.
38
38
 
39
+ **The card is prose; the machine-checked format is a PRESET beside it.** A paper declares its venue
40
+ in `<paper>/paperlint.json`: `{ "extends": "paperlint:<venue>", "kind": "<kind>" }`, and the
41
+ `pdf/*` lint rules judge the built PDF against that preset (page limit per kind, fonts, page size,
42
+ columns, font sizes). A venue with no shipped preset extends a family — `paperlint:acm-sigconf`
43
+ for an ACM venue — or a preset of the project's own, `./venues/<name>.jsonc` (paperlint's
44
+ `docs/rules.md`, "Writing your own venue preset"). Put the page limits you fetched from the CFP in
45
+ the preset's `format.kinds`, each with its quote.
46
+
39
47
  ## Publisher specifics — one level ABOVE the venue
40
48
  Camera-ready mechanics belong to the **publisher**, not the venue: ACM eRights, the submit-vs-final
41
49
  preamble swap, the copyright block, CCS 2012 codes and the mandatory Source-files upload are identical
@@ -52,6 +52,14 @@ skill with \`name:\`/\`description:\`). This preserves the "save the exact venue
52
52
  keeping the skill namespace from proliferating one card per venue. The *mechanics* stay here; the venue
53
53
  file is only the facts.
54
54
 
55
+ **The card is prose; the machine-checked format is a PRESET beside it.** A paper declares its venue
56
+ in \`<paper>/paperlint.json\`: \`{ "extends": "paperlint:<venue>", "kind": "<kind>" }\`, and the
57
+ \`pdf/*\` lint rules judge the built PDF against that preset (page limit per kind, fonts, page size,
58
+ columns, font sizes). A venue with no shipped preset extends a family — \`paperlint:acm-sigconf\`
59
+ for an ACM venue — or a preset of the project's own, \`./venues/<name>.jsonc\` (paperlint's
60
+ \`docs/rules.md\`, "Writing your own venue preset"). Put the page limits you fetched from the CFP in
61
+ the preset's \`format.kinds\`, each with its quote.
62
+
55
63
  ## Publisher specifics — one level ABOVE the venue
56
64
  Camera-ready mechanics belong to the **publisher**, not the venue: ACM eRights, the submit-vs-final
57
65
  preamble swap, the copyright block, CCS 2012 codes and the mandatory Source-files upload are identical
@@ -0,0 +1,86 @@
1
+ // ACM `acmart` sigconf — the TEMPLATE FAMILY: everything the ACM template decides, whatever the
2
+ // venue. A venue preset extends it ("extends": "paperlint:acm-sigconf") and adds what its call for
3
+ // papers sets: the page limit of each kind of paper, and the rules its producer implies.
4
+ //
5
+ // The numbers were measured on acmart sigconf builds (banal + pdf.js, 2026-08-26) and quoted from
6
+ // the Conference Publishing author instructions for ACM (2026-08-25), which state ACM's own
7
+ // requirements. A paper may extend this family directly for an ACM venue nobody has profiled:
8
+ // page size, columns and fonts are checked; with no kinds, the page limit is not.
9
+ {
10
+ "template": "acmart",
11
+
12
+ // TeX Live packages the acmart template needs ON TOP OF tex-base.jsonc, each with the files that
13
+ // prove it is present (checked with kpsewhich after every install). Read by `paperlint toolchain` and
14
+ // `paperlint build`; the shape is venue-profile.schema.json.
15
+ // libertine · inconsolata (zi4) · newtx — acmart.cls checks ALL THREE and, if ANY is missing,
16
+ // silently falls back to Computer Modern: the PDF compiles, looks normal, is set in the wrong
17
+ // font, and the other metric gives another pagination. A submitted paper went out that way.
18
+ // kastrup (binhex.tex) — pulled in by newtx. Missing it is a HARD stop, so adding the fonts
19
+ // without it breaks a build that worked.
20
+ // fancyhdr — required by acmart itself; present on most images by accident, absent on a
21
+ // minimal one ("File `fancyhdr.sty' not found", measured on TinyTeX, issue #37).
22
+ // the rest — pulled in by acmart ITSELF, not by the paper, so reading \usepackage never finds
23
+ // them. Found 2026-09-01 by the loop build → missing file → tlmgr search --file → install.
24
+ "tex": {
25
+ "packages": {
26
+ "acmart": ["acmart.cls"],
27
+ "amscls": ["amsart.cls"],
28
+ "libertine": ["libertine.sty"],
29
+ "inconsolata": ["zi4.sty"],
30
+ "newtx": ["newtxmath.sty"],
31
+ "kastrup": ["binhex.tex"],
32
+ "fancyhdr": ["fancyhdr.sty"],
33
+ "totpages": ["totpages.sty"],
34
+ "environ": ["environ.sty"],
35
+ "preprint": ["balance.sty"],
36
+ // acmart's `pbalance` option (the vendor's first fix for an unbalanced last page, which the
37
+ // optional rule `pdf/last-page-balance` recommends) loads pbalance, which needs filehook and
38
+ // zref. Measured 2026-09-25 on an acmart sigconf camera-ready: without them the option stops
39
+ // the build; with them the last page went from 639.6 / 224.1 to 424.4 / 439.2 pt, same page count.
40
+ "pbalance": ["pbalance.sty"],
41
+ "filehook": ["filehook.sty"],
42
+ "zref": ["zref-abspage.sty"],
43
+ "xstring": ["xstring.sty"],
44
+ "everyshi": ["everyshi.sty"],
45
+ "hyperxmp": ["hyperxmp.sty"],
46
+ "ncctools": ["manyfoot.sty"],
47
+ "cmap": ["cmap.sty"],
48
+ "float": ["float.sty"],
49
+ "comment": ["comment.sty"],
50
+ "upquote": ["upquote.sty"],
51
+ "doclicense": ["doclicense.sty"],
52
+ "textcase": ["textcase.sty"],
53
+ },
54
+ },
55
+
56
+ "format": {
57
+ // 8.5 x 11 in
58
+ "page_size": "letter",
59
+ "page_w_in": 8.5,
60
+ "page_h_in": 11.0,
61
+
62
+ // «text width must not exceed 18 cm»
63
+ "textblock_w_in": 7.087,
64
+ // «text height must not exceed 23.5 cm»
65
+ "textblock_h_in": 9.252,
66
+ // Last-page balance is NOT a format field: it is the optional rule pdf/last-page-balance, which
67
+ // a VENUE preset turns on in its `rules` when its producer asks (see agenticdev.jsonc).
68
+
69
+ "columns": 2,
70
+
71
+ // «font size is 9 pt for all body text»
72
+ "body_pt": 9,
73
+ // banal measures the MODE of the rendered text, not the declared size (measured 08-26: 9.30 at
74
+ // acmart's declared 9). Hence the tolerance — and hence why body size stays a nudge, not a gate.
75
+ "body_pt_tol": 0.5,
76
+
77
+ // «font size of the references can be from 7 pt to 9 pt»
78
+ "ref_pt_min": 7,
79
+ "ref_pt_max": 9,
80
+
81
+ // «text must be in serif font (Libertine)»
82
+ "fonts_text": "LinLibertine",
83
+ // «The title must be in sans-serif font (Biolinum)»
84
+ "fonts_title": "LinBiolinum",
85
+ },
86
+ }
@@ -1,108 +1,40 @@
1
- // Venue format profile — DATA read by the `pdf/profile` rule in eslint-rules/pdf-facts.mjs.
2
- // Prose about the venue is in agenticdev.md nearby.
3
- // Source for the numbers: the Conference Publishing author instructions 2026-08-25 + ACM's
4
- // requirements.
5
- //
6
- // 🔴 Why JSONC and not YAML (decided 08-26). I had this in YAML, justifying it with "JSON has no
7
- // comments, and every number here carries a quote from the instructions." That objection turned
8
- // out to be false: @eslint/json ships three languages — json, jsonc, json5 — and jsonc is exactly
9
- // JSON with comments. So the quotes stay in place, the file is read by a real parser
10
- // (@humanwhocodes/momoa, already a transitive dependency), AND it's linted by the same engine as
11
- // everything else. One extra dependency (js-yaml on this path) goes away.
1
+ // AgenticDev (an ASE workshop) — a VENUE preset over the ACM sigconf family. Prose about the venue
2
+ // is in agenticdev.md nearby. Everything the ACM template decides (page size, columns, fonts, font
3
+ // sizes, TeX packages) comes from acm-sigconf.jsonc; this file adds what AgenticDev's call for
4
+ // papers sets.
5
+ // Source for the numbers: the Conference Publishing author instructions 2026-08-25.
12
6
  {
13
- "template": "acmart",
14
-
15
- // TeX Live packages the acmart template needs ON TOP OF tex-base.jsonc, each with the files that
16
- // prove it is present (checked with kpsewhich after every install). Read by `paperlint toolchain` and
17
- // `paperlint build`; the shape is venue-profile.schema.json.
18
- // libertine · inconsolata (zi4) · newtx — acmart.cls checks ALL THREE and, if ANY is missing,
19
- // silently falls back to Computer Modern: the PDF compiles, looks normal, is set in the wrong
20
- // font, and the other metric gives another pagination. A submitted paper went out that way.
21
- // kastrup (binhex.tex) — pulled in by newtx. Missing it is a HARD stop, so adding the fonts
22
- // without it breaks a build that worked.
23
- // fancyhdr — required by acmart itself; present on most images by accident, absent on a
24
- // minimal one ("File `fancyhdr.sty' not found", measured on TinyTeX, issue #37).
25
- // the rest — pulled in by acmart ITSELF, not by the paper, so reading \usepackage never finds
26
- // them. Found 2026-09-01 by the loop build → missing file → tlmgr search --file → install.
27
- "tex": {
28
- "packages": {
29
- "acmart": ["acmart.cls"],
30
- "amscls": ["amsart.cls"],
31
- "libertine": ["libertine.sty"],
32
- "inconsolata": ["zi4.sty"],
33
- "newtx": ["newtxmath.sty"],
34
- "kastrup": ["binhex.tex"],
35
- "fancyhdr": ["fancyhdr.sty"],
36
- "totpages": ["totpages.sty"],
37
- "environ": ["environ.sty"],
38
- "preprint": ["balance.sty"],
39
- // acmart's `pbalance` option (the vendor's first fix for an unbalanced last page, which the
40
- // optional rule `pdf/last-page-balance` recommends) loads pbalance, which needs filehook and
41
- // zref. Measured 2026-09-25 on an acmart sigconf camera-ready: without them the option stops
42
- // the build; with them the last page went from 639.6 / 224.1 to 424.4 / 439.2 pt, same page count.
43
- "pbalance": ["pbalance.sty"],
44
- "filehook": ["filehook.sty"],
45
- "zref": ["zref-abspage.sty"],
46
- "xstring": ["xstring.sty"],
47
- "everyshi": ["everyshi.sty"],
48
- "hyperxmp": ["hyperxmp.sty"],
49
- "ncctools": ["manyfoot.sty"],
50
- "cmap": ["cmap.sty"],
51
- "float": ["float.sty"],
52
- "comment": ["comment.sty"],
53
- "upquote": ["upquote.sty"],
54
- "doclicense": ["doclicense.sty"],
55
- "textcase": ["textcase.sty"],
7
+ "extends": "paperlint:acm-sigconf",
8
+
9
+ "format": {
10
+ // The limits are a property of the paper's TYPE at this venue, not of the venue itself.
11
+ // A given paper's type is stated in its <paper-dir>/paperlint.json (`kind`).
12
+ "kinds": {
13
+ "short": {
14
+ "body_pages_max": 5,
15
+ // «up to 2 extra pages of references»
16
+ "ref_pages_max": 2,
17
+ },
18
+ "full": {
19
+ // «Full ≤10pp (mature)»
20
+ "body_pages_max": 10,
21
+ "ref_pages_max": 2,
22
+ },
23
+ "demo": {
24
+ "body_pages_max": 5,
25
+ "ref_pages_max": 2,
26
+ },
56
27
  },
57
28
  },
58
29
 
59
- // 8.5 x 11 in
60
- "page_size": "letter",
61
- "page_w_in": 8.5,
62
- "page_h_in": 11.0,
63
-
64
- // «text width must not exceed 18 cm»
65
- "textblock_w_in": 7.087,
66
- // «text height must not exceed 23.5 cm»
67
- "textblock_h_in": 9.252,
68
- // Last-page balance is NOT a profile field. It is the optional lint rule pdf/last-page-balance,
69
- // which a consumer turns on for the papers whose publisher asks (Conference Publishing bounced
70
- // an accepted paper for it on 08-29: "Please correct the last page balancing") — see
71
- // docs/optional-rules.md.
72
-
73
- "columns": 2,
74
-
75
- // «font size is 9 pt for all body text»
76
- "body_pt": 9,
77
- // banal measures the MODE of the rendered text, not the declared size (measured 08-26: 9.30 at
78
- // acmart's declared 9). Hence the tolerance — and hence why body size stays a nudge, not a gate.
79
- "body_pt_tol": 0.5,
80
-
81
- // «font size of the references can be from 7 pt to 9 pt»
82
- "ref_pt_min": 7,
83
- "ref_pt_max": 9,
84
-
85
- // «text must be in serif font (Libertine)»
86
- "fonts_text": "LinLibertine",
87
- // «The title must be in sans-serif font (Biolinum)»
88
- "fonts_title": "LinBiolinum",
89
-
90
- // The limits are a property of the paper's TYPE at this venue, not of the venue itself.
91
- // A given paper's type is stated in its <paper-dir>/venue.json.
92
- "kinds": {
93
- "short": {
94
- "body_pages_max": 5,
95
- // «up to 2 extra pages of references»
96
- "ref_pages_max": 2,
97
- },
98
- "full": {
99
- // «Full ≤10pp (mature)»
100
- "body_pages_max": 10,
101
- "ref_pages_max": 2,
102
- },
103
- "demo": {
104
- "body_pages_max": 5,
105
- "ref_pages_max": 2,
106
- },
30
+ // The proceedings are produced by Conference Publishing Consulting — agenticdev.md takes its
31
+ // numbers from their author instructions — which require "balanced columns on the last page, if
32
+ // the page is not filled" and sent an accepted camera-ready back for it on 2026-08-29, the day
33
+ // after AgenticDev's camera-ready deadline ("Please correct the last page balancing"). So every
34
+ // AgenticDev paper gets the optional rule; a paper can still turn it off in its own
35
+ // paperlint.json. 120 pt is the rule's default, measured: the balanced build ended 2.7 pt apart,
36
+ // the returned one 321.4 pt apart (docs/optional-rules.md).
37
+ "rules": {
38
+ "pdf/last-page-balance": ["error", { "tolerancePt": 120 }],
107
39
  },
108
40
  }
@@ -1,4 +1,5 @@
1
- // AISec @ ACM CCS format profile. Data for the `pdf/*` rules over `_build/paper.facts.json`.
1
+ // AISec @ ACM CCS — a VENUE preset over the ACM sigconf family. Data for the `pdf/*` rules over
2
+ // `_build/paper.facts.json`.
2
3
  // Prose about the venue and tracks is in `aisec.md` nearby.
3
4
  //
4
5
  // PAGE LIMITS — verbatim from https://aisec.cc/ (verified 2026-08-26, not from memory):
@@ -14,88 +15,26 @@
14
15
  // a 13th page. That's deliberate: the case is rare, and automation that knows "sometimes it's
15
16
  // fine" is worse than a plain rejection a human clears by eye.
16
17
  //
17
- // GEOMETRY AND FONTS are inherited from `acmart sigconf` — the same class as agenticdev, and
18
- // measured the same way (banal + pdffonts, 08-26). AISec points to the "main ACM CCS'26 formatting
19
- // instructions" and does not publish its own numbers.
18
+ // GEOMETRY, FONTS AND TeX PACKAGES come from acm-sigconf.jsonc: AISec points to the "main ACM
19
+ // CCS'26 formatting instructions" and does not publish its own numbers.
20
+ //
21
+ // NO `rules` HERE, on purpose: nothing in aisec.md says who produces the AISec proceedings or
22
+ // whether they require a balanced last page (Sheridan does for CCS itself — docs/optional-rules.md —
23
+ // but that is the main conference). Turn pdf/last-page-balance on in the paper's paperlint.json if
24
+ // the camera-ready instructions ask for it.
20
25
  {
21
- "template": "acmart",
22
-
23
- // TeX Live packages the acmart template needs ON TOP OF tex-base.jsonc, each with the files that
24
- // prove it is present (checked with kpsewhich after every install). Read by `paperlint toolchain` and
25
- // `paperlint build`; the shape is venue-profile.schema.json.
26
- // libertine · inconsolata (zi4) · newtx — acmart.cls checks ALL THREE and, if ANY is missing,
27
- // silently falls back to Computer Modern: the PDF compiles, looks normal, is set in the wrong
28
- // font, and the other metric gives another pagination. A submitted paper went out that way.
29
- // kastrup (binhex.tex) — pulled in by newtx. Missing it is a HARD stop, so adding the fonts
30
- // without it breaks a build that worked.
31
- // fancyhdr — required by acmart itself; present on most images by accident, absent on a
32
- // minimal one ("File `fancyhdr.sty' not found", measured on TinyTeX, issue #37).
33
- // the rest — pulled in by acmart ITSELF, not by the paper, so reading \usepackage never finds
34
- // them. Found 2026-09-01 by the loop build → missing file → tlmgr search --file → install.
35
- "tex": {
36
- "packages": {
37
- "acmart": ["acmart.cls"],
38
- "amscls": ["amsart.cls"],
39
- "libertine": ["libertine.sty"],
40
- "inconsolata": ["zi4.sty"],
41
- "newtx": ["newtxmath.sty"],
42
- "kastrup": ["binhex.tex"],
43
- "fancyhdr": ["fancyhdr.sty"],
44
- "totpages": ["totpages.sty"],
45
- "environ": ["environ.sty"],
46
- "preprint": ["balance.sty"],
47
- // acmart's `pbalance` option (the vendor's first fix for an unbalanced last page, which the
48
- // optional rule `pdf/last-page-balance` recommends) loads pbalance, which needs filehook and
49
- // zref. Measured 2026-09-25 on an acmart sigconf camera-ready: without them the option stops
50
- // the build; with them the last page went from 639.6 / 224.1 to 424.4 / 439.2 pt, same page count.
51
- "pbalance": ["pbalance.sty"],
52
- "filehook": ["filehook.sty"],
53
- "zref": ["zref-abspage.sty"],
54
- "xstring": ["xstring.sty"],
55
- "everyshi": ["everyshi.sty"],
56
- "hyperxmp": ["hyperxmp.sty"],
57
- "ncctools": ["manyfoot.sty"],
58
- "cmap": ["cmap.sty"],
59
- "float": ["float.sty"],
60
- "comment": ["comment.sty"],
61
- "upquote": ["upquote.sty"],
62
- "doclicense": ["doclicense.sty"],
63
- "textcase": ["textcase.sty"],
26
+ "extends": "paperlint:acm-sigconf",
27
+
28
+ "format": {
29
+ // Four tracks (original research · position/open-problem · SoK · benchmark) — the limits are
30
+ // the SAME for all of them, the venue doesn't split them. The keys are kept separate so a
31
+ // paper's `paperlint.json` can name its own track: not for the numbers, but so it's visible which
32
+ // track it was submitted under.
33
+ "kinds": {
34
+ "research": { "body_pages_max": 10, "ref_pages_max": 2 },
35
+ "benchmark": { "body_pages_max": 10, "ref_pages_max": 2 },
36
+ "position": { "body_pages_max": 10, "ref_pages_max": 2 },
37
+ "sok": { "body_pages_max": 10, "ref_pages_max": 2 },
64
38
  },
65
39
  },
66
-
67
- "page_size": "letter",
68
- "page_w_in": 8.5,
69
- "page_h_in": 11.0,
70
-
71
- "textblock_w_in": 7.087,
72
- "textblock_h_in": 9.252,
73
- // Last-page balance is NOT a profile field. It is the optional lint rule pdf/last-page-balance,
74
- // which a consumer turns on for the papers whose publisher asks (Conference Publishing bounced
75
- // an accepted paper for it on 08-29: "Please correct the last page balancing") — see
76
- // docs/optional-rules.md.
77
-
78
- "columns": 2,
79
-
80
- "body_pt": 9,
81
- // banal measures the rendered text's mode, not the declared size (measured: 9.30 at a
82
- // declared 9).
83
- "body_pt_tol": 0.5,
84
-
85
- "ref_pt_min": 7,
86
- "ref_pt_max": 9,
87
-
88
- "fonts_text": "LinLibertine",
89
- "fonts_title": "LinBiolinum",
90
-
91
- // Four tracks (original research · position/open-problem · SoK · benchmark) — the limits are
92
- // the SAME for all of them, the venue doesn't split them. The keys are kept separate so a
93
- // paper's `venue.json` can name its own track: not for the numbers, but so it's visible which
94
- // track it was submitted under.
95
- "kinds": {
96
- "research": { "body_pages_max": 10, "ref_pages_max": 2 },
97
- "benchmark": { "body_pages_max": 10, "ref_pages_max": 2 },
98
- "position": { "body_pages_max": 10, "ref_pages_max": 2 },
99
- "sok": { "body_pages_max": 10, "ref_pages_max": 2 },
100
- },
101
40
  }
@@ -28,54 +28,56 @@
28
28
  },
29
29
  },
30
30
 
31
- "page_size": "a4",
32
- "page_w_in": 8.27,
33
- "page_h_in": 11.69,
31
+ "format": {
32
+ "page_size": "a4",
33
+ "page_w_in": 8.27,
34
+ "page_h_in": 11.69,
34
35
 
35
- "columns": 2,
36
+ "columns": 2,
36
37
 
37
- // ACL's stated body size is 11pt; banal measures the render's mode and gives 11.3.
38
- "body_pt": 11,
39
- "body_pt_tol": 0.5,
38
+ // ACL's stated body size is 11pt; banal measures the render's mode and gives 11.3.
39
+ "body_pt": 11,
40
+ "body_pt_tol": 0.5,
40
41
 
41
- // ⚠️ The range is derived from a SINGLE measurement (10.3), not from ACL's spec — I could not
42
- // find ACL's own numbers for the reference size. So it's set wide: for an `error`-level rule, a
43
- // false positive is worse than a miss. Narrow it once a primary source turns up.
44
- "ref_pt_min": 9,
45
- "ref_pt_max": 11,
42
+ // ⚠️ The range is derived from a SINGLE measurement (10.3), not from ACL's spec — I could not
43
+ // find ACL's own numbers for the reference size. So it's set wide: for an `error`-level rule, a
44
+ // false positive is worse than a miss. Narrow it once a primary source turns up.
45
+ "ref_pt_min": 9,
46
+ "ref_pt_max": 11,
46
47
 
47
- // The Times clone from the ACL template. Headings are NOT gated: the build also contains
48
- // NimbusSanL-Bold, but a `pdffonts` measurement can't establish exactly what the heading is set
49
- // in — it only says a typeface IS present in the file, not where it's applied.
50
- "fonts_text": "NimbusRomNo9L",
48
+ // The Times clone from the ACL template. Headings are NOT gated: the build also contains
49
+ // NimbusSanL-Bold, but a `pdffonts` measurement can't establish exactly what the heading is set
50
+ // in — it only says a typeface IS present in the file, not where it's applied.
51
+ "fonts_text": "NimbusRomNo9L",
51
52
 
52
- // 🔴 PAGE LIMITS ARE NOT GATED HERE, AND THAT IS A MEASUREMENT, NOT LAZINESS.
53
- //
54
- // banal classifies pages by content and counts Limitations and Ethics as part of the BODY.
55
- // ACL excludes them from the limit. Measured 08-26 on our own paper: banal said "body 13," the
56
- // build's own counter said "body pages: 9," and both are right — they're just counting
57
- // different things (the body ends at Conclusion on p.9; pp.10-13 are Limitations/Ethics; the
58
- // bibliography starts at 14).
59
- //
60
- // A gate on banal's number would give 13 against a limit of 8 and would redden on a paper that
61
- // is within the limit. For an `error`-level rule that is the worst outcome. The limit stays
62
- // owned by the counter inside `build-submission.sh`: it reads the markdown source and therefore
63
- // KNOWS where the body ends, which the PDF does not.
64
- //
65
- // The numbers below are DATA for that counter and for humans, not an input to the rule. Source:
66
- // `realm.md`, verified by directly fetching the 2026-08-02/03 CFP.
67
- "page_limits_not_gated": {
68
- "_": "8 at submission, 9 at camera-ready (+1 page). Both numbers are CURRENTLY hardcoded in build-submission.sh — 8 in the header, 9 in the run — which reads like a mismatch even though these are different STAGES. The script should pull them from here instead.",
69
- "long_submission": 8,
70
- "long_camera_ready": 9,
71
- "short_submission": 4,
72
- "short_camera_ready": 5,
73
- "refs_and_appendices": "unlimited",
74
- "limitations_and_ethics": "not counted (ACL rule)",
75
- },
53
+ // 🔴 PAGE LIMITS ARE NOT GATED HERE, AND THAT IS A MEASUREMENT, NOT LAZINESS.
54
+ //
55
+ // banal classifies pages by content and counts Limitations and Ethics as part of the BODY.
56
+ // ACL excludes them from the limit. Measured 08-26 on our own paper: banal said "body 13," the
57
+ // build's own counter said "body pages: 9," and both are right — they're just counting
58
+ // different things (the body ends at Conclusion on p.9; pp.10-13 are Limitations/Ethics; the
59
+ // bibliography starts at 14).
60
+ //
61
+ // A gate on banal's number would give 13 against a limit of 8 and would redden on a paper that
62
+ // is within the limit. For an `error`-level rule that is the worst outcome. The limit stays
63
+ // owned by the counter inside `build-submission.sh`: it reads the markdown source and therefore
64
+ // KNOWS where the body ends, which the PDF does not.
65
+ //
66
+ // The numbers below are DATA for that counter and for humans, not an input to the rule. Source:
67
+ // `realm.md`, verified by directly fetching the 2026-08-02/03 CFP.
68
+ "page_limits_not_gated": {
69
+ "_": "8 at submission, 9 at camera-ready (+1 page). Both numbers are CURRENTLY hardcoded in build-submission.sh — 8 in the header, 9 in the run — which reads like a mismatch even though these are different STAGES. The script should pull them from here instead.",
70
+ "long_submission": 8,
71
+ "long_camera_ready": 9,
72
+ "short_submission": 4,
73
+ "short_camera_ready": 5,
74
+ "refs_and_appendices": "unlimited",
75
+ "limitations_and_ethics": "not counted (ACL rule)",
76
+ },
76
77
 
77
- "kinds": {
78
- "long": {},
79
- "short": {},
78
+ "kinds": {
79
+ "long": {},
80
+ "short": {},
81
+ },
80
82
  },
81
83
  }
@@ -1,5 +1,5 @@
1
1
  // The TeX Live packages EVERY paper gets — and ALL a paper gets when its venue is unknown (no
2
- // venue.json, or a venue.json naming a venue with no profile here). A venue's own `tex.packages`
2
+ // paperlint.json, or a paperlint.json naming a venue with no profile here). A venue's own `tex.packages`
3
3
  // is added on top of this set, never instead of it.
4
4
  //
5
5
  // Read by `paperlint toolchain` (it installs this set plus every venue's) and by `paperlint build` (it checks