paperlint 7.1.0 → 8.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 (261) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/.github/workflows/deadlines.yml +100 -0
  3. package/README.md +6 -0
  4. package/dist/adapters/hotcrp/deadlines.d.ts +8 -0
  5. package/dist/adapters/hotcrp/deadlines.d.ts.map +1 -0
  6. package/dist/adapters/hotcrp/deadlines.io.d.ts +6 -0
  7. package/dist/adapters/hotcrp/deadlines.io.d.ts.map +1 -0
  8. package/dist/adapters/hotcrp/deadlines.io.js +26 -0
  9. package/dist/adapters/hotcrp/deadlines.io.js.map +1 -0
  10. package/dist/adapters/hotcrp/deadlines.js +134 -0
  11. package/dist/adapters/hotcrp/deadlines.js.map +1 -0
  12. package/dist/adapters/hotcrp/index.d.ts +2 -0
  13. package/dist/adapters/hotcrp/index.d.ts.map +1 -1
  14. package/dist/adapters/hotcrp/index.js +2 -0
  15. package/dist/adapters/hotcrp/index.js.map +1 -1
  16. package/dist/build.d.ts.map +1 -1
  17. package/dist/build.js +1 -6
  18. package/dist/build.js.map +1 -1
  19. package/dist/cli.d.ts +3 -4
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +53 -47
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cycle-rules.d.ts +24 -0
  24. package/dist/cycle-rules.d.ts.map +1 -0
  25. package/dist/cycle-rules.js +212 -0
  26. package/dist/cycle-rules.js.map +1 -0
  27. package/dist/domain/cycle.d.ts +148 -0
  28. package/dist/domain/cycle.d.ts.map +1 -0
  29. package/dist/domain/cycle.js +281 -0
  30. package/dist/domain/cycle.js.map +1 -0
  31. package/dist/domain/deadline.d.ts +128 -0
  32. package/dist/domain/deadline.d.ts.map +1 -0
  33. package/dist/domain/deadline.js +281 -0
  34. package/dist/domain/deadline.js.map +1 -0
  35. package/dist/eslint-rules/cycle-stage.d.ts +39 -0
  36. package/dist/eslint-rules/cycle-stage.d.ts.map +1 -0
  37. package/dist/eslint-rules/cycle-stage.js +119 -0
  38. package/dist/eslint-rules/cycle-stage.js.map +1 -0
  39. package/dist/eslint-rules/latex-language.d.ts.map +1 -1
  40. package/dist/eslint-rules/latex-language.js +10 -13
  41. package/dist/eslint-rules/latex-language.js.map +1 -1
  42. package/dist/eslint-rules/paper-context.js +2 -2
  43. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  44. package/dist/eslint-rules/paper-typography.js +22 -91
  45. package/dist/eslint-rules/paper-typography.js.map +1 -1
  46. package/dist/eslint-rules/papers.d.ts +1 -3
  47. package/dist/eslint-rules/papers.d.ts.map +1 -1
  48. package/dist/eslint-rules/papers.js +2 -4
  49. package/dist/eslint-rules/papers.js.map +1 -1
  50. package/dist/eslint-rules/tex-build.d.ts.map +1 -1
  51. package/dist/eslint-rules/tex-build.js +8 -25
  52. package/dist/eslint-rules/tex-build.js.map +1 -1
  53. package/dist/init.d.ts +1 -4
  54. package/dist/init.d.ts.map +1 -1
  55. package/dist/init.js +3 -9
  56. package/dist/init.js.map +1 -1
  57. package/dist/lib/markdown.d.ts +5 -81
  58. package/dist/lib/markdown.d.ts.map +1 -1
  59. package/dist/lib/markdown.js +5 -116
  60. package/dist/lib/markdown.js.map +1 -1
  61. package/dist/lib/paper-config.d.ts.map +1 -1
  62. package/dist/lib/paper-config.js +2 -2
  63. package/dist/lib/paper-config.js.map +1 -1
  64. package/dist/lib/skill-eval-fixture.d.ts +6 -4
  65. package/dist/lib/skill-eval-fixture.d.ts.map +1 -1
  66. package/dist/lib/skill-eval-fixture.js +25 -18
  67. package/dist/lib/skill-eval-fixture.js.map +1 -1
  68. package/dist/new-paper.d.ts +5 -11
  69. package/dist/new-paper.d.ts.map +1 -1
  70. package/dist/new-paper.js +12 -23
  71. package/dist/new-paper.js.map +1 -1
  72. package/dist/paper-files.d.ts +4 -4
  73. package/dist/paper-prose.d.ts +17 -0
  74. package/dist/paper-prose.d.ts.map +1 -0
  75. package/dist/paper-prose.js +46 -0
  76. package/dist/paper-prose.js.map +1 -0
  77. package/dist/paper-settings.d.ts +7 -0
  78. package/dist/paper-settings.d.ts.map +1 -1
  79. package/dist/paper-settings.js +61 -8
  80. package/dist/paper-settings.js.map +1 -1
  81. package/dist/ports/venue-deadlines.d.ts +14 -0
  82. package/dist/ports/venue-deadlines.d.ts.map +1 -0
  83. package/dist/ports/venue-deadlines.js +2 -0
  84. package/dist/ports/venue-deadlines.js.map +1 -0
  85. package/dist/presets.d.ts +12 -0
  86. package/dist/presets.d.ts.map +1 -1
  87. package/dist/presets.js +19 -11
  88. package/dist/presets.js.map +1 -1
  89. package/dist/structure.d.ts.map +1 -1
  90. package/dist/structure.js +4 -3
  91. package/dist/structure.js.map +1 -1
  92. package/dist/submission.d.ts +8 -0
  93. package/dist/submission.d.ts.map +1 -1
  94. package/dist/submission.js +41 -10
  95. package/dist/submission.js.map +1 -1
  96. package/dist/tex-requirements.d.ts +3 -0
  97. package/dist/tex-requirements.d.ts.map +1 -1
  98. package/dist/tex-requirements.js +19 -0
  99. package/dist/tex-requirements.js.map +1 -1
  100. package/dist/tex-venue-rules.d.ts +19 -0
  101. package/dist/tex-venue-rules.d.ts.map +1 -1
  102. package/dist/tex-venue-rules.js +69 -0
  103. package/dist/tex-venue-rules.js.map +1 -1
  104. package/dist/types.d.ts +0 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/venue-rules.d.ts +10 -0
  107. package/dist/venue-rules.d.ts.map +1 -1
  108. package/dist/venue-rules.js +26 -10
  109. package/dist/venue-rules.js.map +1 -1
  110. package/docs/configuration.md +146 -20
  111. package/docs/e2e.md +4 -6
  112. package/docs/prior-art/paper-folder-scaffolding.md +24 -29
  113. package/docs/rules/cycle/evidence.md +87 -0
  114. package/docs/rules/cycle/port-done.md +75 -0
  115. package/docs/rules/cycle/record.md +152 -0
  116. package/docs/rules/cycle/stage.md +85 -0
  117. package/docs/rules/tex/claim-provenance.md +0 -1
  118. package/docs/rules.md +48 -47
  119. package/docs/submission.md +5 -0
  120. package/eslint-rules/cycle-stage.test.mjs +131 -0
  121. package/eslint-rules/cycle-stage.ts +141 -0
  122. package/eslint-rules/latex-language.harness.mjs +3 -3
  123. package/eslint-rules/latex-language.ts +10 -13
  124. package/eslint-rules/paper-context.ts +2 -2
  125. package/eslint-rules/paper-typography.test.mjs +17 -85
  126. package/eslint-rules/paper-typography.ts +22 -98
  127. package/eslint-rules/papers.harness.mjs +1 -1
  128. package/eslint-rules/papers.ts +2 -4
  129. package/eslint-rules/tex-build.harness.mjs +2 -2
  130. package/eslint-rules/tex-build.ts +8 -25
  131. package/eslint.config.mjs +41 -2
  132. package/hooks/hooks.harness.ts +15 -15
  133. package/hooks/paper-edit-guard.hook.mjs +5 -8
  134. package/hooks/paper-edit-guard.test.ts +12 -3
  135. package/hooks/paper-skills-nudge.hook.mjs +2 -3
  136. package/hooks/paper-skills-nudge.test.ts +13 -3
  137. package/hooks/paper-status-gates.hook.mjs +2 -2
  138. package/hooks/paper-status-gates.test.ts +14 -2
  139. package/lib/markdown.test.mjs +5 -60
  140. package/lib/markdown.ts +9 -136
  141. package/lib/paper-config.ts +3 -2
  142. package/lib/skill-checks.test.mjs +4 -1
  143. package/lib/skill-eval-fixture.ts +25 -18
  144. package/package.json +1 -3
  145. package/presets/aidc.jsonc +24 -3
  146. package/presets/aidc.md +6 -3
  147. package/presets/msr.jsonc +25 -1
  148. package/presets/msr.md +5 -1
  149. package/presets/realm.jsonc +1 -1
  150. package/presets/venue-profile.schema.json +39 -0
  151. package/scripts/check.ts +4 -0
  152. package/scripts/harness-api.frozen.json +0 -5
  153. package/scripts/refresh-deadlines.test.ts +431 -0
  154. package/scripts/refresh-deadlines.ts +387 -0
  155. package/skills/cold-read-diff/SKILL.md +3 -3
  156. package/skills/cold-read-diff/SKILL.md.spec.ts +2 -2
  157. package/skills/grade-paper-writing/SKILL.md +14 -22
  158. package/skills/grade-paper-writing/SKILL.md.spec.ts +13 -21
  159. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +40 -26
  160. package/skills/grade-paper-writing/prose-lint.mjs +108 -278
  161. package/skills/grade-paper-writing/prose-lint.test.ts +184 -81
  162. package/skills/paper-adversarial-review/SKILL.md +2 -2
  163. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  164. package/skills/paper-pipeline/SKILL.md +9 -12
  165. package/skills/paper-pipeline/SKILL.md.spec.ts +8 -11
  166. package/skills/paper-pipeline/description-language.eval.mjs +25 -15
  167. package/skills/paper-pipeline/framing-vs-vocabulary.eval.mjs +25 -15
  168. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +25 -15
  169. package/skills/paper-pipeline/pipeline-firing.eval.mjs +27 -16
  170. package/skills/paper-pipeline/pipeline-language.eval.mjs +25 -15
  171. package/skills/paper-pipeline/references/acceptance-gate.md +16 -20
  172. package/skills/paper-pipeline/references/artifact-checklist.md +4 -5
  173. package/skills/paper-pipeline/references/pipeline-status-template.md +1 -1
  174. package/skills/paper-pipeline/scripts/README.md +0 -4
  175. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +8 -72
  176. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +48 -156
  177. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +81 -75
  178. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +3 -3
  179. package/skills/paper-status/SKILL.md +2 -2
  180. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  181. package/skills/render-paper/SKILL.md +2 -2
  182. package/skills/render-paper/SKILL.md.spec.ts +1 -1
  183. package/skills/render-paper/extract-pdf-facts.mjs +2 -2
  184. package/skills/tighten-paper/SKILL.md +29 -42
  185. package/skills/tighten-paper/SKILL.md.spec.ts +28 -41
  186. package/skills/verify-citations/scripts/bib-authors.check.test.mjs +0 -13
  187. package/skills/verify-citations/scripts/bib-authors.cli.test.ts +23 -11
  188. package/skills/verify-citations/scripts/bib-authors.mjs +8 -93
  189. package/skills/verify-citations/scripts/bib-authors.test.mjs +1 -44
  190. package/src/adapters/hotcrp/cassette/README.md +22 -0
  191. package/src/adapters/hotcrp/cassette/deadlines-aidc26.html +17 -0
  192. package/src/adapters/hotcrp/cassette/deadlines-fse2027.html +24 -0
  193. package/src/adapters/hotcrp/cassette/deadlines-icse2027.html +25 -0
  194. package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +25 -0
  195. package/src/adapters/hotcrp/cassette/deadlines-nosuch.html +18 -0
  196. package/src/adapters/hotcrp/cassette/deadlines-sosp26.html +26 -0
  197. package/src/adapters/hotcrp/deadlines.io.test.ts +86 -0
  198. package/src/adapters/hotcrp/deadlines.io.ts +27 -0
  199. package/src/adapters/hotcrp/deadlines.test.ts +170 -0
  200. package/src/adapters/hotcrp/deadlines.ts +161 -0
  201. package/src/adapters/hotcrp/index.ts +2 -0
  202. package/src/build.harness.mjs +2 -2
  203. package/src/build.test.ts +1 -1
  204. package/src/build.ts +1 -6
  205. package/src/cli.harness.mjs +26 -46
  206. package/src/cli.test.ts +41 -40
  207. package/src/cli.ts +57 -58
  208. package/src/cycle-rules.test.ts +278 -0
  209. package/src/cycle-rules.ts +273 -0
  210. package/src/domain/cycle.test.ts +456 -0
  211. package/src/domain/cycle.ts +432 -0
  212. package/src/domain/deadline.test.ts +386 -0
  213. package/src/domain/deadline.ts +472 -0
  214. package/src/folder-venue-rule.test.ts +1 -1
  215. package/src/init.test.ts +6 -7
  216. package/src/init.ts +3 -22
  217. package/src/lint-fix.test.ts +10 -4
  218. package/src/new-paper.harness.mjs +9 -28
  219. package/src/new-paper.test.ts +7 -6
  220. package/src/new-paper.ts +11 -30
  221. package/src/paper-files.test.ts +5 -4
  222. package/src/paper-files.ts +4 -4
  223. package/src/paper-prose.test.ts +49 -0
  224. package/src/paper-prose.ts +67 -0
  225. package/src/paper-settings.test.ts +167 -0
  226. package/src/paper-settings.ts +85 -11
  227. package/src/parse-args.test.ts +0 -1
  228. package/src/ports/venue-deadlines.ts +14 -0
  229. package/src/presets.test.ts +105 -0
  230. package/src/presets.ts +43 -10
  231. package/src/rule-cases.test.ts +144 -0
  232. package/src/structure.harness.ts +5 -11
  233. package/src/structure.ts +4 -3
  234. package/src/submission.test.ts +105 -1
  235. package/src/submission.ts +73 -12
  236. package/src/tex-requirements.ts +31 -0
  237. package/src/tex-venue-rules.test.ts +76 -0
  238. package/src/tex-venue-rules.ts +85 -0
  239. package/src/types.ts +0 -2
  240. package/src/venue-rules.test.ts +82 -0
  241. package/src/venue-rules.ts +44 -12
  242. package/fixtures/real-markdown-paper/baseline.json +0 -22
  243. package/fixtures/real-markdown-paper/baseline.ts +0 -70
  244. package/skills/paper-pipeline/PIPELINE-MAP.md +0 -381
  245. package/skills/paper-pipeline/pipeline-firing.baseline.json +0 -270
  246. package/skills/paper-pipeline/references/sweep-2026-08-09-ideal-pipeline.md +0 -585
  247. package/skills/paper-pipeline/scripts/artifact-coverage.harness.mjs +0 -492
  248. package/skills/paper-pipeline/scripts/artifact-coverage.mjs +0 -397
  249. package/skills/paper-pipeline/scripts/artifact-coverage.test.ts +0 -86
  250. package/skills/paper-pipeline/scripts/check-provenance.mjs +0 -184
  251. package/skills/paper-pipeline/scripts/population-map.harness.mjs +0 -135
  252. package/skills/paper-pipeline/scripts/population-map.mjs +0 -188
  253. package/skills/paper-pipeline/scripts/population-map.test.mjs +0 -154
  254. package/skills/paper-pipeline/scripts/provenance.harness.mjs +0 -266
  255. package/skills/paper-pipeline/scripts/round-diff.harness.ts +0 -907
  256. package/skills/paper-pipeline/scripts/round-diff.mjs +0 -583
  257. package/skills/paper-pipeline/scripts/round-diff.test.mjs +0 -159
  258. package/skills/tighten-paper/__snapshots__/structure.test.ts.snap +0 -133
  259. package/skills/tighten-paper/structure.mjs +0 -364
  260. package/skills/tighten-paper/structure.test.ts +0 -117
  261. package/templates/paper/paper.md +0 -4
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
5
+ <meta name="google" content="notranslate">
6
+ <meta name="theme-color" content="#ffb7ca">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link href="https://static.hotcrp.com/@1790985506/stylesheets/style.css" rel="stylesheet">
9
+ <link href="https://static.hotcrp.com/@1786633883/stylesheets/mobile.css" media="screen and (max-width: 1100px)" rel="stylesheet">
10
+ <link href="https://static.hotcrp.com/@1788721683/stylesheets/plugin.css" rel="stylesheet">
11
+ <link rel="icon" type="image/svg+xml" href="https://static.hotcrp.com/images/review48.svg">
12
+ <title>Deadlines - MSR 2027</title>
13
+ </head>
14
+ <body id="m-deadlines" data-upload-limit="104857600" data-document-max-size="600000000"><div id="p-page" class="need-banner-offset"><header id="p-header"><hr class="c"><div id="h-site" class="header-site-page"><a class="q" href="/"><span class="header-site-name">MSR 2027</span></a></div><div id="h-right"></div><div id="h-page"><h1 id="h-title">Deadlines</h1></div><nav id="p-quicklinks" aria-label="Submission search"></nav><hr class="c">
15
+ <div id="h-messages" class="want-mx-auto"></div></header>
16
+ <main id="p-body">
17
+ <h3>Upcoming</h3><dl class="bsp"><dt><strong>Submission deadline</strong>: <span class="need-usertime" data-ts="1792468800">Tuesday Oct 20, 2026, 12 AM EDT</span></dt><dd>Submissions must be completed by this deadline to be reviewed.</dd>
18
+ <dt><strong>Resubmission deadline</strong>: <span class="need-usertime" data-ts="1792728000">Friday Oct 23, 2026, 12 AM EDT</span></dt><dd>Completed submissions may be updated until this deadline.</dd></dl>
19
+ <hr class="c"></main></div><footer id="p-footer" class="need-banner-offset banner-bottom"><a class="noq" href="https://hotcrp.com/">HotCRP</a><!-- Version 3.4 --></footer><!--[if lt IE 9]><script nonce="8XgRxNPYP5XebPvVSL6MyA==" src="https://static.hotcrp.com/@1749780400/scripts/jquery-1.12.4.min.js" crossorigin="anonymous" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="></script><![endif]-->
20
+ <![if !IE|gte IE 9]><script nonce="8XgRxNPYP5XebPvVSL6MyA==" src="https://static.hotcrp.com/@1749780400/scripts/jquery-3.7.1.min.js" crossorigin="anonymous" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="></script><![endif]>
21
+ <script nonce="8XgRxNPYP5XebPvVSL6MyA==">window.siteinfo={"site_relative":"","base":"\/","suffix":"","assets":"https:\/\/static.hotcrp.com\/","icons_url":"@1787797517\/images\/icons.svg","cookie_params":"; Domain=.hotcrp.com; Secure; SameSite=Lax","postvalue":".empty","download_prefix":"msr2027-","snouns":["submission","submissions","Submission","Submissions"],"user":[]}</script><script nonce="8XgRxNPYP5XebPvVSL6MyA==" src="https://static.hotcrp.com/@1790985505/scripts/script.js" crossorigin="anonymous"></script>
22
+ <script nonce="8XgRxNPYP5XebPvVSL6MyA==" src="https://static.hotcrp.com/@1777496734/scripts/markdown-it.min.js" crossorigin="anonymous"></script>
23
+ <script nonce="8XgRxNPYP5XebPvVSL6MyA==" src="https://static.hotcrp.com/@1787955704/scripts/plugin.js" crossorigin="anonymous"></script>
24
+ <script nonce="8XgRxNPYP5XebPvVSL6MyA==">hotcrp.onload.time(240,0);hotcrp.init_deadlines({"now":1791194488.329003,"email":null,"sub":{"open":true,"sub":1792468800,"blind":true}})</script></body>
25
+ </html>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
5
+ <link href="@1790985506/stylesheets/style.css" rel="stylesheet">
6
+ <link rel="icon" type="image/svg+xml" href="images/review48.svg">
7
+ <title>No such conference - acsac2026</title>
8
+ </head>
9
+ <body data-upload-limit="104857600" data-document-max-size="600000000"><div id="p-page" class="need-banner-offset"><header id="p-header"><hr class="c"><div id="h-site" class="header-site-page"><a class="q" href="https://acsac2026.hotcrp.com/"><span class="header-site-name">acsac2026</span></a></div><div id="h-right"></div><div id="h-page"><h1 id="h-title">No such conference</h1></div><hr class="c">
10
+ <div id="h-messages" class="want-mx-auto"></div></header>
11
+ <main id="p-body">
12
+ <p>Did you mean:</p><ul><li><a href="https://acsac20posters.hotcrp.com">ACSAC 2020 Posters &amp; WIPS</a> (<a href="https://acsac20posters.hotcrp.com">acsac20posters.hotcrp.com</a>)</li><li><a href="https://acsac20.hotcrp.com">ACSAC 2020</a> (<a href="https://acsac20.hotcrp.com">acsac20.hotcrp.com</a>)</li><li><a href="https://acsac19.hotcrp.com">ACSAC 2019</a> (<a href="https://acsac19.hotcrp.com">acsac19.hotcrp.com</a>)</li></ul><hr class="c"></main></div><footer id="p-footer" class="need-banner-offset banner-bottom"><a class="noq" href="https://hotcrp.com/">HotCRP</a><!-- Version 3.4 --></footer><!--[if lt IE 9]><script src="@1749780400/scripts/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="></script><![endif]-->
13
+ <![if !IE|gte IE 9]><script src="@1749780400/scripts/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="></script><![endif]>
14
+ <script>window.siteinfo={"site_relative":"","base":"\/","suffix":"","assets":"","icons_url":"","cookie_params":"; Domain=.hotcrp.com; SameSite=Lax","postvalue":".empty","download_prefix":"acsac2026-","snouns":["submission","submissions","Submission","Submissions"],"user":[]}</script><script src="@1790985505/scripts/script.js"></script>
15
+ <script src="@1777496734/scripts/markdown-it.min.js"></script>
16
+ <script src="@1787955704/scripts/plugin.js"></script>
17
+ <script>hotcrp.onload.time(0,0)</script></body>
18
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
5
+ <meta name="google" content="notranslate">
6
+ <meta name="theme-color" content="#ffb7ca">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link href="https://static.hotcrp.com/@1790985506/stylesheets/style.css" rel="stylesheet">
9
+ <link href="https://static.hotcrp.com/@1786633883/stylesheets/mobile.css" media="screen and (max-width: 1100px)" rel="stylesheet">
10
+ <link href="https://static.hotcrp.com/@1788721683/stylesheets/plugin.css" rel="stylesheet">
11
+ <link rel="icon" type="image/svg+xml" href="https://static.hotcrp.com/images/review48.svg">
12
+ <title>Deadlines - SOSP 2026</title>
13
+ </head>
14
+ <body id="m-deadlines" data-upload-limit="104857600" data-document-max-size="600000000"><div id="p-page" class="need-banner-offset"><header id="p-header"><hr class="c"><div id="h-site" class="header-site-page"><a class="q" href="/"><span class="header-site-name">SOSP 2026</span></a></div><div id="h-right"></div><div id="h-page"><h1 id="h-title">Deadlines</h1></div><nav id="p-quicklinks" aria-label="Submission search"></nav><hr class="c">
15
+ <div id="h-messages" class="want-mx-auto"></div></header>
16
+ <main id="p-body">
17
+ <h3>Past</h3><dl class="bsp"><dt><strong>Resubmission deadline</strong>: <span class="need-usertime" data-ts="1786751999">Friday Aug 14, 2026, 7:59:59 PM EDT</span></dt><dd>Completed submissions may be updated until this deadline.</dd>
18
+ <dt><strong>Submission deadline</strong>: <span class="need-usertime" data-ts="1775131199">Thursday Apr 2, 2026, 7:59:59 AM EDT</span></dt><dd>Submissions must be completed by this deadline to be reviewed.</dd>
19
+ <dt><strong>Registration deadline</strong>: <span class="need-usertime" data-ts="1774612799">Friday Mar 27, 2026, 7:59:59 AM EDT</span></dt><dd>You can register new submissions until this deadline.</dd></dl>
20
+ <hr class="c"></main></div><footer id="p-footer" class="need-banner-offset banner-bottom"><a class="noq" href="https://hotcrp.com/">HotCRP</a><!-- Version 3.4 --></footer><!--[if lt IE 9]><script nonce="LW8qxXSv09MzquWfF8dAZw==" src="https://static.hotcrp.com/@1749780400/scripts/jquery-1.12.4.min.js" crossorigin="anonymous" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="></script><![endif]-->
21
+ <![if !IE|gte IE 9]><script nonce="LW8qxXSv09MzquWfF8dAZw==" src="https://static.hotcrp.com/@1749780400/scripts/jquery-3.7.1.min.js" crossorigin="anonymous" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="></script><![endif]>
22
+ <script nonce="LW8qxXSv09MzquWfF8dAZw==">window.siteinfo={"site_relative":"","base":"\/","suffix":"","assets":"https:\/\/static.hotcrp.com\/","icons_url":"@1787797517\/images\/icons.svg","cookie_params":"; Domain=.hotcrp.com; Secure; SameSite=Lax","postvalue":".empty","download_prefix":"sosp26-","snouns":["submission","submissions","Submission","Submissions"],"user":[]}</script><script nonce="LW8qxXSv09MzquWfF8dAZw==" src="https://static.hotcrp.com/@1790985505/scripts/script.js" crossorigin="anonymous"></script>
23
+ <script nonce="LW8qxXSv09MzquWfF8dAZw==" src="https://static.hotcrp.com/@1777496734/scripts/markdown-it.min.js" crossorigin="anonymous"></script>
24
+ <script nonce="LW8qxXSv09MzquWfF8dAZw==" src="https://static.hotcrp.com/@1787955704/scripts/plugin.js" crossorigin="anonymous"></script>
25
+ <script nonce="LW8qxXSv09MzquWfF8dAZw==">hotcrp.onload.time(240,0);hotcrp.init_deadlines({"now":1791194489.724288,"email":null,"sub":{"open":true,"sub":1775131199,"reg":1774612799,"blind":true},"final":{"open":true,"done":1788004799}})</script></body>
26
+ </html>
@@ -0,0 +1,86 @@
1
+ /**
2
+ * `hotcrpDeadlines` over a local server that answers `GET /deadlines` with a recorded page: the
3
+ * request paperlint sends (the path, no credential) and what comes back through the parser; and a
4
+ * site that does not answer at all.
5
+ */
6
+ import assert from "node:assert/strict";
7
+ import { readFileSync } from "node:fs";
8
+ import {
9
+ createServer,
10
+ type IncomingMessage,
11
+ type Server,
12
+ type ServerResponse,
13
+ } from "node:http";
14
+ import { afterAll, beforeAll, test } from "vitest";
15
+ import { hotcrpDeadlines } from "./deadlines.io.ts";
16
+
17
+ const page = readFileSync(
18
+ new URL("./cassette/deadlines-msr2027.html", import.meta.url),
19
+ "utf8",
20
+ );
21
+
22
+ const seen: { url: string; authorization: string | undefined }[] = [];
23
+ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
24
+ seen.push({ url: req.url ?? "", authorization: req.headers.authorization });
25
+ if (req.url === "/deadlines") {
26
+ res.writeHead(200, { "content-type": "text/html" });
27
+ res.end(page);
28
+ } else {
29
+ res.writeHead(404, { "content-type": "text/html" });
30
+ res.end('<h1 id="h-title">No such conference</h1>');
31
+ }
32
+ });
33
+
34
+ const portOf = (s: Server): number => {
35
+ const a = s.address();
36
+ assert.ok(a !== null && typeof a === "object", "the server is not listening");
37
+ return a.port;
38
+ };
39
+ const closeServer = (s: Server): Promise<void> =>
40
+ new Promise<void>((r) => {
41
+ s.close(() => {
42
+ r();
43
+ });
44
+ });
45
+
46
+ let base = "";
47
+ beforeAll(async () => {
48
+ await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
49
+ base = `http://127.0.0.1:${String(portOf(server))}`;
50
+ });
51
+ afterAll(() => closeServer(server));
52
+
53
+ test("GET <site>/deadlines, no credential; the page parses to the venue's deadlines", async () => {
54
+ const r = await hotcrpDeadlines({ url: base }).read();
55
+ assert.deepEqual(seen.at(-1), {
56
+ url: "/deadlines",
57
+ authorization: undefined,
58
+ });
59
+ assert.ok(r.ok, JSON.stringify(r));
60
+ assert.equal(r.value.open, true);
61
+ assert.deepEqual(
62
+ r.value.deadlines.map((d) => [d.what, d.at]),
63
+ [
64
+ ["submission", "2026-10-20T04:00:00Z"],
65
+ ["resubmission", "2026-10-23T04:00:00Z"],
66
+ ],
67
+ );
68
+ });
69
+
70
+ test("a site that is not a conference: refused with the page's title", async () => {
71
+ const r = await hotcrpDeadlines({ url: `${base}/nosuch` }).read();
72
+ assert.ok(!r.ok);
73
+ assert.equal(r.error.kind, "refused");
74
+ });
75
+
76
+ test("a site that does not answer is unreachable, with the network's words", async () => {
77
+ const closed = createServer();
78
+ await new Promise<void>((r) => closed.listen(0, "127.0.0.1", r));
79
+ const port = portOf(closed);
80
+ await closeServer(closed);
81
+ const r = await hotcrpDeadlines({
82
+ url: `http://127.0.0.1:${String(port)}`,
83
+ }).read();
84
+ assert.ok(!r.ok);
85
+ assert.equal(r.error.kind, "unreachable");
86
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * HotCRP's deadlines page over Node's `fetch`: `GET <site>/deadlines`, no token — the page is
3
+ * public, and a deadline is nobody's secret.
4
+ */
5
+ import { messageOf } from "../../domain/text.ts";
6
+ import { err } from "../../domain/result.ts";
7
+ import type { VenueDeadlines } from "../../ports/venue-deadlines.ts";
8
+ import { parseDeadlinesPage } from "./deadlines.ts";
9
+
10
+ const READ_TIMEOUT_MS = 60_000;
11
+
12
+ /** The deadlines of the HotCRP site at `url` (no trailing slash). */
13
+ export function hotcrpDeadlines(o: { readonly url: string }): VenueDeadlines {
14
+ return {
15
+ async read() {
16
+ try {
17
+ const r = await fetch(`${o.url}/deadlines`, {
18
+ headers: { Accept: "text/html" },
19
+ signal: AbortSignal.timeout(READ_TIMEOUT_MS),
20
+ });
21
+ return parseDeadlinesPage(r.status, await r.text());
22
+ } catch (e) {
23
+ return err({ kind: "unreachable", detail: messageOf(e) });
24
+ }
25
+ },
26
+ };
27
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The deadlines page parser on the pages five live HotCRP sites answered on 2026-10-05
3
+ * (`cassette/deadlines-*.html`, recorded whole), and on a site that does not exist. Each expected
4
+ * value was read off the page's text by hand before the parser existed.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { describe, expect, it } from "vitest";
8
+ import { parseDeadlinesPage, whatOf } from "./index.ts";
9
+
10
+ type Site = "msr2027" | "aidc26" | "sosp26" | "icse2027" | "fse2027" | "nosuch";
11
+ const page = (site: Site): string =>
12
+ readFileSync(
13
+ new URL(`./cassette/deadlines-${site}.html`, import.meta.url),
14
+ "utf8",
15
+ );
16
+
17
+ const read = (site: Site, status = 200) => {
18
+ const r = parseDeadlinesPage(status, page(site));
19
+ if (!r.ok) throw new Error(JSON.stringify(r.error));
20
+ return r.value;
21
+ };
22
+
23
+ describe("parseDeadlinesPage", () => {
24
+ it("a conference with a resubmission deadline: the list carries it, the status object does not", () => {
25
+ expect(read("msr2027")).toEqual({
26
+ now: 1791194488.329003,
27
+ open: true,
28
+ blind: true,
29
+ deadlines: [
30
+ {
31
+ what: "submission",
32
+ label: "Submission deadline",
33
+ at: "2026-10-20T04:00:00Z",
34
+ },
35
+ {
36
+ what: "resubmission",
37
+ label: "Resubmission deadline",
38
+ at: "2026-10-23T04:00:00Z",
39
+ },
40
+ ],
41
+ });
42
+ });
43
+
44
+ it("a workshop past its submission deadline, with a final-version deadline only the status object names", () => {
45
+ expect(read("aidc26")).toEqual({
46
+ now: 1791194489.473936,
47
+ open: true,
48
+ blind: true,
49
+ deadlines: [
50
+ {
51
+ what: "submission",
52
+ label: "Submission deadline",
53
+ at: "2026-10-03T11:59:59Z",
54
+ },
55
+ {
56
+ what: "camera-ready",
57
+ label: "camera-ready",
58
+ at: "2026-10-31T11:59:59Z",
59
+ },
60
+ ],
61
+ });
62
+ });
63
+ });
64
+
65
+ describe("parseDeadlinesPage — every kind at once", () => {
66
+ it("registration, submission, a late resubmission and a final version: sorted by time", () => {
67
+ expect(read("sosp26").deadlines).toEqual([
68
+ {
69
+ what: "registration",
70
+ label: "Registration deadline",
71
+ at: "2026-03-27T11:59:59Z",
72
+ },
73
+ {
74
+ what: "submission",
75
+ label: "Submission deadline",
76
+ at: "2026-04-02T11:59:59Z",
77
+ },
78
+ {
79
+ what: "resubmission",
80
+ label: "Resubmission deadline",
81
+ at: "2026-08-14T23:59:59Z",
82
+ },
83
+ {
84
+ what: "camera-ready",
85
+ label: "camera-ready",
86
+ at: "2026-08-29T11:59:59Z",
87
+ },
88
+ ]);
89
+ });
90
+ });
91
+
92
+ describe("parseDeadlinesPage — a closed round, AoE, and pages that are not deadlines", () => {
93
+ it("a round that is not open: `open` is false and the dates still read", () => {
94
+ const r = read("icse2027");
95
+ expect(r.open).toBe(false);
96
+ expect(r.deadlines.map((d) => [d.what, d.at])).toEqual([
97
+ ["registration", "2026-06-24T11:59:59Z"],
98
+ ["submission", "2026-07-09T11:59:00Z"],
99
+ ]);
100
+ });
101
+ });
102
+
103
+ describe("parseDeadlinesPage — AoE and non-pages", () => {
104
+ it("the AoE a page prints is already an instant in UTC: «Oct 3, 11:59 PM AoE» is Oct 4, 11:59 UTC", () => {
105
+ expect(read("fse2027").deadlines).toEqual([
106
+ {
107
+ what: "submission",
108
+ label: "Submission deadline",
109
+ at: "2026-10-04T11:59:00Z",
110
+ },
111
+ ]);
112
+ });
113
+
114
+ it("a status object with no submission round: closed, blindness unknown, no deadlines", () => {
115
+ expect(
116
+ parseDeadlinesPage(200, 'hotcrp.init_deadlines({"now": 1})'),
117
+ ).toEqual({
118
+ ok: true,
119
+ value: { now: 1, open: false, blind: null, deadlines: [] },
120
+ });
121
+ });
122
+
123
+ it("a refusal without a page title carries no message", () => {
124
+ expect(parseDeadlinesPage(503, "<html>down</html>")).toEqual({
125
+ ok: false,
126
+ error: { kind: "refused", httpStatus: 503, messages: [] },
127
+ });
128
+ });
129
+
130
+ it("a site that does not exist: refused with the page's title", () => {
131
+ expect(parseDeadlinesPage(404, page("nosuch"))).toEqual({
132
+ ok: false,
133
+ error: {
134
+ kind: "refused",
135
+ httpStatus: 404,
136
+ messages: [
137
+ { message: "No such conference", field: null, status: null },
138
+ ],
139
+ },
140
+ });
141
+ });
142
+
143
+ it("a 200 page that is not a deadlines page, and a status object of another shape: malformed", () => {
144
+ const detail: unknown = expect.stringContaining(
145
+ "no `hotcrp.init_deadlines(…)` call",
146
+ );
147
+ expect(parseDeadlinesPage(200, "<html><body>hello</body></html>")).toEqual({
148
+ ok: false,
149
+ error: {
150
+ kind: "malformed",
151
+ httpStatus: 200,
152
+ detail,
153
+ },
154
+ });
155
+ const r = parseDeadlinesPage(200, 'hotcrp.init_deadlines({"sub":1})');
156
+ expect(r.ok ? "parsed" : r.error.kind).toBe("malformed");
157
+ const notJson = parseDeadlinesPage(200, "hotcrp.init_deadlines({now: 1})");
158
+ expect(notJson.ok ? "parsed" : notJson.error.kind).toBe("malformed");
159
+ });
160
+ });
161
+
162
+ describe("whatOf", () => {
163
+ it("names the deadlines paperlint knows by their HotCRP labels, with or without a round name", () => {
164
+ expect(whatOf("Submission deadline")).toBe("submission");
165
+ expect(whatOf("Resubmission deadline")).toBe("resubmission");
166
+ expect(whatOf("Short papers registration deadline")).toBe("registration");
167
+ expect(whatOf("Final version deadline")).toBe("camera-ready");
168
+ expect(whatOf("Response deadline")).toBeNull();
169
+ });
170
+ });
@@ -0,0 +1,161 @@
1
+ /**
2
+ * HotCRP's deadlines page (`<site>/deadlines`), parsed at the boundary into paperlint's
3
+ * `PortalDeadlines`. Pure.
4
+ *
5
+ * ── WHAT THE PAGE IS, measured 2026-10-05 on five live sites (`cassette/deadlines-*.html`) ───────
6
+ * The page is HTML, public, no account needed; the API endpoint of the same name (`/api/deadlines`)
7
+ * answers 401 «Missing credentials» without a token. The machine-readable part is the argument of
8
+ * `hotcrp.init_deadlines({…})` in the page's last script: the author's `status_json()`
9
+ * (kohler/hotcrp, src/contact.php), of which an anonymous visitor gets
10
+ *
11
+ * now the server's clock, Unix seconds with a fraction
12
+ * sub.open whether the submission round is open now
13
+ * sub.sub the submission deadline, Unix seconds (SubmissionRound::$submit)
14
+ * sub.reg the registration deadline, when it is earlier (SubmissionRound::$register)
15
+ * sub.blind true | "optional" | "until-review" (Conf::submission_blindness)
16
+ * final.done the final-version (camera-ready) deadline (final_deadline_for_display)
17
+ *
18
+ * The RESUBMISSION deadline (updates allowed until, `SubmissionRound::$resubmit`) is not in that
19
+ * object; the page lists it only in its `<dl>`, each entry a `<dt>` with the deadline's label and a
20
+ * `<span data-ts="…">` holding the same Unix seconds (pages/p_deadlines.php, `dl1`). So both are
21
+ * read: the JSON for what it names, the list for every deadline the page shows, matched by label.
22
+ */
23
+ import { z } from "zod";
24
+ import {
25
+ instantOfUnix,
26
+ type DeadlineWhat,
27
+ type PortalDeadline,
28
+ type PortalDeadlines,
29
+ } from "../../domain/deadline.ts";
30
+ import { err, ok, type Result } from "../../domain/result.ts";
31
+ import type { PortalFailure } from "../../domain/submission.ts";
32
+
33
+ const Blind = z.union([
34
+ z.boolean(),
35
+ z.literal("optional"),
36
+ z.literal("until-review"),
37
+ ]);
38
+ const Status = z.looseObject({
39
+ now: z.number(),
40
+ sub: z
41
+ .looseObject({
42
+ open: z.boolean().optional(),
43
+ sub: z.number().optional(),
44
+ reg: z.number().optional(),
45
+ blind: Blind.optional(),
46
+ })
47
+ .optional(),
48
+ final: z.looseObject({ done: z.number().optional() }).optional(),
49
+ });
50
+
51
+ /** The script call that carries the status object; the argument is JSON. */
52
+ const INIT = /hotcrp\.init_deadlines\((\{.*?\})\)/s;
53
+ /** One listed deadline: its label in `<strong>`, its time in `data-ts`. */
54
+ const ENTRY = /<dt><strong>([^<]*)<\/strong>:\s*<span[^>]*\bdata-ts="(\d+)"/g;
55
+ /** The page's title, for a page that is no deadlines page (a site that does not exist). */
56
+ const TITLE = /<h1 id="h-title">([^<]*)</;
57
+
58
+ /** What a listed deadline's label names, in paperlint's words; null for one paperlint does not name. */
59
+ export function whatOf(label: string): DeadlineWhat | null {
60
+ const l = label.toLowerCase();
61
+ if (/\bresubmission deadline\b/.test(l)) return "resubmission";
62
+ if (/\bregistration deadline\b/.test(l)) return "registration";
63
+ if (/\bsubmission deadline\b/.test(l)) return "submission";
64
+ if (/\bfinal\b|\bcamera[- ]ready\b/.test(l)) return "camera-ready";
65
+ return null;
66
+ }
67
+
68
+ /** The deadlines the status object names, each with the label the page would give it. */
69
+ type StatusJson = Readonly<z.infer<typeof Status>>;
70
+
71
+ function fromStatus(s: StatusJson): readonly PortalDeadline[] {
72
+ const named: readonly (readonly [DeadlineWhat, number | undefined])[] = [
73
+ ["registration", s.sub?.reg],
74
+ ["submission", s.sub?.sub],
75
+ ["camera-ready", s.final?.done],
76
+ ];
77
+ return named.flatMap(([what, at]) =>
78
+ at === undefined ? [] : [{ what, label: what, at: instantOfUnix(at) }],
79
+ );
80
+ }
81
+
82
+ /** Every deadline the page lists, with its label as the page spells it. */
83
+ function fromList(html: string): readonly PortalDeadline[] {
84
+ return [...html.matchAll(ENTRY)].map((m) => {
85
+ // Both groups always participate in a match of ENTRY: there is no fallback to take.
86
+ const label = String(m[1]).trim();
87
+ return { what: whatOf(label), label, at: instantOfUnix(Number(m[2])) };
88
+ });
89
+ }
90
+
91
+ /** Listed entries first (they carry the page's label); a status entry only when the list lacks its kind. */
92
+ function merged(
93
+ listed: readonly PortalDeadline[],
94
+ named: readonly PortalDeadline[],
95
+ ): readonly PortalDeadline[] {
96
+ const extra = named.filter(
97
+ (n) => !listed.some((l) => l.what === n.what && l.at === n.at),
98
+ );
99
+ return [...listed, ...extra].toSorted((a, b) => a.at.localeCompare(b.at));
100
+ }
101
+
102
+ const refused = (httpStatus: number, html: string): PortalFailure => {
103
+ const title = TITLE.exec(html)?.[1]?.trim();
104
+ return {
105
+ kind: "refused",
106
+ httpStatus,
107
+ messages:
108
+ title === undefined
109
+ ? []
110
+ : [{ message: title, field: null, status: null }],
111
+ };
112
+ };
113
+
114
+ /** The status object in the page's `init_deadlines` call, or why there is none. */
115
+ function statusOf(
116
+ httpStatus: number,
117
+ html: string,
118
+ ): Result<StatusJson, PortalFailure> {
119
+ const m = INIT.exec(html);
120
+ if (m === null)
121
+ return err({
122
+ kind: "malformed",
123
+ httpStatus,
124
+ detail:
125
+ "the page has no `hotcrp.init_deadlines(…)` call — not a HotCRP deadlines page",
126
+ });
127
+ const json = ((): unknown => {
128
+ try {
129
+ return JSON.parse(String(m[1])); // the group always participates in a match of INIT
130
+ } catch {
131
+ return undefined;
132
+ }
133
+ })();
134
+ const parsed = Status.safeParse(json);
135
+ return parsed.success
136
+ ? ok(parsed.data)
137
+ : err({
138
+ kind: "malformed",
139
+ httpStatus,
140
+ detail:
141
+ "the `init_deadlines` argument is not the status object HotCRP documents",
142
+ });
143
+ }
144
+
145
+ /** `GET /deadlines` → the portal's deadlines, or why the page is not them. */
146
+ export function parseDeadlinesPage(
147
+ httpStatus: number,
148
+ html: string,
149
+ ): Result<PortalDeadlines, PortalFailure> {
150
+ if (httpStatus < 200 || httpStatus >= 300)
151
+ return err(refused(httpStatus, html));
152
+ const status = statusOf(httpStatus, html);
153
+ if (!status.ok) return status;
154
+ const s = status.value;
155
+ return ok({
156
+ now: s.now,
157
+ open: s.sub?.open ?? false,
158
+ blind: s.sub?.blind ?? null,
159
+ deadlines: merged(fromList(html), fromStatus(s)),
160
+ });
161
+ }
@@ -9,3 +9,5 @@ export {
9
9
  type HotcrpOptions,
10
10
  } from "./portal.io.ts";
11
11
  export { parseShow, parseUpdate, sha256OfHash } from "./response.ts";
12
+ export { hotcrpDeadlines } from "./deadlines.io.ts";
13
+ export { parseDeadlinesPage, whatOf } from "./deadlines.ts";
@@ -482,7 +482,7 @@ try {
482
482
  );
483
483
 
484
484
  // ── no paper.tex: a REFUSAL, not a skip ───────────────────────────────────────────────
485
- const bare = paper("bare", { "paper.md": "# x" });
485
+ const bare = paper("bare", { "PIPELINE-STATUS.md": "# x" });
486
486
  const ntex = fakeTex();
487
487
  const nr = await buildPaper(bare, {
488
488
  cwd: root,
@@ -546,7 +546,7 @@ try {
546
546
 
547
547
  // (c) no paper.tex: refused, and a PDF left from some earlier build goes too.
548
548
  const bareStale = paper("bare-stale", {
549
- "paper.md": "# x",
549
+ "PIPELINE-STATUS.md": "# x",
550
550
  "paper.pdf": "%PDF-stale-from-yesterday",
551
551
  });
552
552
  const bsTex = fakeTex();
package/src/build.test.ts CHANGED
@@ -162,7 +162,7 @@ test("documentclass: an empty name is no class; a macro inside the name contribu
162
162
  });
163
163
 
164
164
  test("facts of a paper with no paper.tex: no class", () => {
165
- assert.deepEqual(readFacts(paper({ "paper.md": "# P\n" })), {
165
+ assert.deepEqual(readFacts(paper({ "PIPELINE-STATUS.md": "# P\n" })), {
166
166
  main: null,
167
167
  documentclass: null,
168
168
  venue: null,
package/src/build.ts CHANGED
@@ -89,12 +89,7 @@ import { messageOf, printed } from "./domain/text.ts";
89
89
  const PIPED: ["ignore", "pipe", "pipe"] = ["ignore", "pipe", "pipe"];
90
90
 
91
91
  /** A directory counts as a paper by the same markers as `structure.ts` — one shared dictionary. */
92
- export const PAPER_MARKERS = [
93
- "PIPELINE-STATUS.md",
94
- "paper.tex",
95
- "paper.md",
96
- CONFIG_FILE,
97
- ];
92
+ export const PAPER_MARKERS = ["PIPELINE-STATUS.md", "paper.tex", CONFIG_FILE];
98
93
 
99
94
  /** The source paperlint compiles, and the job name every output file carries. */
100
95
  export const MAIN = "paper.tex";