paperlint 10.4.0 → 11.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 (232) hide show
  1. package/.github/workflows/deadlines.yml +27 -7
  2. package/README.md +16 -15
  3. package/dist/CLAUDE.md.spec.d.ts.map +1 -1
  4. package/dist/CLAUDE.md.spec.js +6 -3
  5. package/dist/CLAUDE.md.spec.js.map +1 -1
  6. package/dist/adapters/hotcrp/index.d.ts +6 -4
  7. package/dist/adapters/hotcrp/index.d.ts.map +1 -1
  8. package/dist/adapters/hotcrp/index.js +6 -4
  9. package/dist/adapters/hotcrp/index.js.map +1 -1
  10. package/dist/adapters/hotcrp/portal.io.d.ts +3 -1
  11. package/dist/adapters/hotcrp/portal.io.d.ts.map +1 -1
  12. package/dist/adapters/hotcrp/portal.io.js +36 -11
  13. package/dist/adapters/hotcrp/portal.io.js.map +1 -1
  14. package/dist/adapters/hotcrp/response.d.ts +6 -1
  15. package/dist/adapters/hotcrp/response.d.ts.map +1 -1
  16. package/dist/adapters/hotcrp/response.js +17 -0
  17. package/dist/adapters/hotcrp/response.js.map +1 -1
  18. package/dist/adapters/hotcrp/site.d.ts +9 -0
  19. package/dist/adapters/hotcrp/site.d.ts.map +1 -0
  20. package/dist/adapters/hotcrp/site.js +22 -0
  21. package/dist/adapters/hotcrp/site.js.map +1 -0
  22. package/dist/adapters/hotcrp/token.d.ts +13 -0
  23. package/dist/adapters/hotcrp/token.d.ts.map +1 -0
  24. package/dist/adapters/hotcrp/token.js +48 -0
  25. package/dist/adapters/hotcrp/token.js.map +1 -0
  26. package/dist/adapters/latex/abstract.d.ts +22 -0
  27. package/dist/adapters/latex/abstract.d.ts.map +1 -0
  28. package/dist/adapters/latex/abstract.js +187 -0
  29. package/dist/adapters/latex/abstract.js.map +1 -0
  30. package/dist/adapters/latex/captions.d.ts +9 -0
  31. package/dist/adapters/latex/captions.d.ts.map +1 -0
  32. package/dist/adapters/latex/captions.js +40 -0
  33. package/dist/adapters/latex/captions.js.map +1 -0
  34. package/dist/adapters/latex/headings.d.ts +6 -0
  35. package/dist/adapters/latex/headings.d.ts.map +1 -1
  36. package/dist/adapters/latex/headings.js +5 -4
  37. package/dist/adapters/latex/headings.js.map +1 -1
  38. package/dist/adapters/latex/index.d.ts +1 -0
  39. package/dist/adapters/latex/index.d.ts.map +1 -1
  40. package/dist/adapters/latex/index.js +3 -0
  41. package/dist/adapters/latex/index.js.map +1 -1
  42. package/dist/adapters/latex/parse.d.ts +15 -0
  43. package/dist/adapters/latex/parse.d.ts.map +1 -1
  44. package/dist/adapters/latex/parse.js +64 -1
  45. package/dist/adapters/latex/parse.js.map +1 -1
  46. package/dist/adapters/node/list-entries.io.d.ts.map +1 -1
  47. package/dist/adapters/node/list-entries.io.js +3 -1
  48. package/dist/adapters/node/list-entries.io.js.map +1 -1
  49. package/dist/cli.d.ts.map +1 -1
  50. package/dist/cli.js +20 -8
  51. package/dist/cli.js.map +1 -1
  52. package/dist/cycle-rules.d.ts.map +1 -1
  53. package/dist/cycle-rules.js +3 -4
  54. package/dist/cycle-rules.js.map +1 -1
  55. package/dist/domain/deadline.d.ts +8 -1
  56. package/dist/domain/deadline.d.ts.map +1 -1
  57. package/dist/domain/deadline.js +26 -12
  58. package/dist/domain/deadline.js.map +1 -1
  59. package/dist/domain/decide-first.d.ts +11 -1
  60. package/dist/domain/decide-first.d.ts.map +1 -1
  61. package/dist/domain/decide-first.js +6 -0
  62. package/dist/domain/decide-first.js.map +1 -1
  63. package/dist/domain/index-columns.d.ts.map +1 -1
  64. package/dist/domain/index-columns.js +2 -0
  65. package/dist/domain/index-columns.js.map +1 -1
  66. package/dist/domain/portal-token.d.ts +44 -0
  67. package/dist/domain/portal-token.d.ts.map +1 -0
  68. package/dist/domain/portal-token.js +32 -0
  69. package/dist/domain/portal-token.js.map +1 -0
  70. package/dist/domain/study-registry.d.ts +65 -8
  71. package/dist/domain/study-registry.d.ts.map +1 -1
  72. package/dist/domain/study-registry.js +63 -19
  73. package/dist/domain/study-registry.js.map +1 -1
  74. package/dist/domain/submission.d.ts +30 -0
  75. package/dist/domain/submission.d.ts.map +1 -1
  76. package/dist/domain/submission.js +68 -0
  77. package/dist/domain/submission.js.map +1 -1
  78. package/dist/lib/paper-config.d.ts +1 -1
  79. package/dist/lib/paper-config.d.ts.map +1 -1
  80. package/dist/lib/paper-config.js +6 -4
  81. package/dist/lib/paper-config.js.map +1 -1
  82. package/dist/outputs-before-decision-rule.d.ts +1 -1
  83. package/dist/outputs-before-decision-rule.d.ts.map +1 -1
  84. package/dist/outputs-before-decision-rule.js +21 -17
  85. package/dist/outputs-before-decision-rule.js.map +1 -1
  86. package/dist/paper-settings.d.ts +3 -1
  87. package/dist/paper-settings.d.ts.map +1 -1
  88. package/dist/paper-settings.js +11 -5
  89. package/dist/paper-settings.js.map +1 -1
  90. package/dist/ports/latex.d.ts +23 -0
  91. package/dist/ports/latex.d.ts.map +1 -1
  92. package/dist/ports/list-entries.d.ts +7 -4
  93. package/dist/ports/list-entries.d.ts.map +1 -1
  94. package/dist/ports/study-deps.d.ts +14 -0
  95. package/dist/ports/study-deps.d.ts.map +1 -0
  96. package/dist/ports/study-deps.js +2 -0
  97. package/dist/ports/study-deps.js.map +1 -0
  98. package/dist/ports/study-folder.d.ts +43 -0
  99. package/dist/ports/study-folder.d.ts.map +1 -0
  100. package/dist/ports/study-folder.js +2 -0
  101. package/dist/ports/study-folder.js.map +1 -0
  102. package/dist/ports/submission-portal.d.ts +39 -2
  103. package/dist/ports/submission-portal.d.ts.map +1 -1
  104. package/dist/ports/venue-deadlines.d.ts +1 -1
  105. package/dist/presets.d.ts +1 -2
  106. package/dist/presets.d.ts.map +1 -1
  107. package/dist/presets.js +1 -2
  108. package/dist/presets.js.map +1 -1
  109. package/dist/studies.d.ts.map +1 -1
  110. package/dist/studies.js +10 -7
  111. package/dist/studies.js.map +1 -1
  112. package/dist/study-folder.d.ts +14 -0
  113. package/dist/study-folder.d.ts.map +1 -0
  114. package/dist/study-folder.js +123 -0
  115. package/dist/study-folder.js.map +1 -0
  116. package/dist/study-gate.d.ts +1 -1
  117. package/dist/study-gate.d.ts.map +1 -1
  118. package/dist/study-gate.js +65 -28
  119. package/dist/study-gate.js.map +1 -1
  120. package/dist/study-index-input.d.ts +1 -1
  121. package/dist/study-index-input.d.ts.map +1 -1
  122. package/dist/study-index-input.js +8 -3
  123. package/dist/study-index-input.js.map +1 -1
  124. package/dist/study-modes.d.ts +38 -27
  125. package/dist/study-modes.d.ts.map +1 -1
  126. package/dist/study-modes.js +42 -53
  127. package/dist/study-modes.js.map +1 -1
  128. package/dist/study-registry-rule.d.ts +2 -6
  129. package/dist/study-registry-rule.d.ts.map +1 -1
  130. package/dist/study-registry-rule.js +32 -41
  131. package/dist/study-registry-rule.js.map +1 -1
  132. package/dist/submission.d.ts +49 -11
  133. package/dist/submission.d.ts.map +1 -1
  134. package/dist/submission.js +332 -44
  135. package/dist/submission.js.map +1 -1
  136. package/docs/configuration.md +2 -1
  137. package/docs/design/study-modes.md +46 -28
  138. package/docs/rules/cycle/record.md +9 -6
  139. package/docs/rules/repro/outputs-before-decision.md +3 -0
  140. package/docs/rules/repro/study-registered.md +3 -2
  141. package/docs/rules/tex/heading-case.md +1 -1
  142. package/docs/submission.md +47 -48
  143. package/eslint.config.mjs +39 -24
  144. package/lib/paper-config.ts +7 -4
  145. package/package.json +6 -1
  146. package/presets/msr.jsonc +9 -7
  147. package/presets/msr.md +14 -10
  148. package/scripts/check.ts +1 -1
  149. package/scripts/refresh-deadlines/compare.test.ts +75 -0
  150. package/scripts/refresh-deadlines/compare.ts +101 -0
  151. package/scripts/refresh-deadlines/main.test.ts +140 -0
  152. package/scripts/refresh-deadlines/main.ts +56 -0
  153. package/scripts/refresh-deadlines/refresh.test.ts +287 -0
  154. package/scripts/refresh-deadlines/refresh.ts +183 -0
  155. package/skills/grade-paper-writing/SKILL.md +4 -2
  156. package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -1
  157. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +16 -3
  158. package/skills/grade-paper-writing/prose-lint.mjs +127 -95
  159. package/skills/grade-paper-writing/prose-lint.test.ts +119 -10
  160. package/skills/submission-portal/SKILL.md +60 -44
  161. package/skills/submission-portal/SKILL.md.spec.ts +62 -43
  162. package/skills/submission-portal/submission-portal.test.ts +4 -73
  163. package/src/CLAUDE.md +7 -4
  164. package/src/CLAUDE.md.spec.ts +6 -3
  165. package/src/adapters/hotcrp/cassette/README.md +4 -4
  166. package/src/adapters/hotcrp/cassette/deadlines-msr2027.html +9 -9
  167. package/src/adapters/hotcrp/deadlines.io.test.ts +2 -2
  168. package/src/adapters/hotcrp/deadlines.test.ts +3 -3
  169. package/src/adapters/hotcrp/index.ts +15 -3
  170. package/src/adapters/hotcrp/portal.io.ts +61 -17
  171. package/src/adapters/hotcrp/portal.test.ts +105 -1
  172. package/src/adapters/hotcrp/response.ts +21 -0
  173. package/src/adapters/hotcrp/site.ts +34 -0
  174. package/src/adapters/hotcrp/token.test.ts +69 -0
  175. package/src/adapters/hotcrp/token.ts +51 -0
  176. package/src/adapters/latex/abstract.test.ts +280 -0
  177. package/src/adapters/latex/abstract.ts +272 -0
  178. package/src/adapters/latex/captions.test.ts +57 -0
  179. package/src/adapters/latex/captions.ts +56 -0
  180. package/src/adapters/latex/headings.ts +5 -4
  181. package/src/adapters/latex/index.test.ts +31 -1
  182. package/src/adapters/latex/index.ts +3 -0
  183. package/src/adapters/latex/parse.ts +71 -3
  184. package/src/adapters/node/list-entries.io.ts +2 -1
  185. package/src/adapters/node/list-entries.test.ts +18 -0
  186. package/src/cli.test.ts +20 -8
  187. package/src/cli.ts +22 -13
  188. package/src/cycle-rules.test.ts +6 -6
  189. package/src/cycle-rules.ts +7 -4
  190. package/src/domain/deadline.test.ts +31 -2
  191. package/src/domain/deadline.ts +42 -16
  192. package/src/domain/decide-first.test.ts +33 -0
  193. package/src/domain/decide-first.ts +16 -1
  194. package/src/domain/index-columns.test.ts +49 -4
  195. package/src/domain/index-columns.ts +2 -0
  196. package/src/domain/portal-token.test.ts +55 -0
  197. package/src/domain/portal-token.ts +63 -0
  198. package/src/domain/study-measurements.test.ts +7 -3
  199. package/src/domain/study-registry.test.ts +34 -6
  200. package/src/domain/study-registry.ts +123 -31
  201. package/src/domain/submission.test.ts +88 -0
  202. package/src/domain/submission.ts +99 -0
  203. package/src/new-paper.test.ts +1 -0
  204. package/src/outputs-before-decision-rule.test.ts +47 -2
  205. package/src/outputs-before-decision-rule.ts +31 -22
  206. package/src/paper-settings.test.ts +20 -0
  207. package/src/paper-settings.ts +17 -5
  208. package/src/ports/latex.ts +25 -0
  209. package/src/ports/list-entries.ts +7 -4
  210. package/src/ports/study-deps.ts +14 -0
  211. package/src/ports/study-folder.ts +38 -0
  212. package/src/ports/submission-portal.ts +43 -1
  213. package/src/ports/venue-deadlines.ts +1 -1
  214. package/src/presets.test.ts +8 -5
  215. package/src/presets.ts +1 -2
  216. package/src/studies-index-modes.test.ts +2 -2
  217. package/src/studies.ts +19 -10
  218. package/src/study-folder.test.ts +237 -0
  219. package/src/study-folder.ts +176 -0
  220. package/src/study-gate.test.ts +158 -7
  221. package/src/study-gate.ts +127 -54
  222. package/src/study-index-input.ts +12 -4
  223. package/src/study-modes.ts +67 -80
  224. package/src/study-registry-rule.test.ts +87 -0
  225. package/src/study-registry-rule.ts +58 -66
  226. package/src/submission-new.test.ts +569 -0
  227. package/src/submission-show.test.ts +210 -0
  228. package/src/submission-update.test.ts +160 -0
  229. package/src/submission.test.ts +140 -518
  230. package/src/submission.ts +494 -61
  231. package/scripts/refresh-deadlines.test.ts +0 -431
  232. package/scripts/refresh-deadlines.ts +0 -387
@@ -18,6 +18,7 @@
18
18
  * file in `repro/`.
19
19
  */
20
20
  import { basename, dirname, join, posix } from "node:path";
21
+ import { filter, map, pipe } from "remeda";
21
22
  import { callerPath } from "./caller-path.ts";
22
23
  import {
23
24
  README,
@@ -29,13 +30,17 @@ import {
29
30
  LEGACY_PLAN_FILE,
30
31
  PLAN_NAME_PROBLEMS,
31
32
  STUDIES_FILE,
32
- isFolderName,
33
33
  judgeStudies,
34
+ folderKey,
34
35
  planRefs,
36
+ studiesOf,
35
37
  studyMessage,
36
38
  type PlanState,
37
39
  type RegistryState,
40
+ type Study,
38
41
  type StudyFinding,
42
+ type Located,
43
+ type PlanRef,
39
44
  } from "./domain/study-registry.ts";
40
45
  import type { FolderLayoutDeps } from "./folder-layout-rule.ts";
41
46
  import type {
@@ -46,6 +51,8 @@ import type { Files } from "./ports/files.ts";
46
51
  import type { ListEntries } from "./ports/list-entries.ts";
47
52
  import type { StudyRegistryCodec } from "./ports/study-registry-codec.ts";
48
53
  import { reproEntriesOf } from "./repro-layout-rule.ts";
54
+ import type { StudyFolder } from "./ports/study-folder.ts";
55
+ import { studyFolder } from "./study-folder.ts";
49
56
  import { rulePageUrl } from "./venue-rules.ts";
50
57
 
51
58
  const STATUS_FILE = "PIPELINE-STATUS.md";
@@ -57,30 +64,6 @@ export interface StudyRegistryDeps extends FolderLayoutDeps {
57
64
  readonly entries: ListEntries;
58
65
  }
59
66
 
60
- /** The names at the top of `<dir>/repro/` that are symbolic links. */
61
- const linksIn = (
62
- dir: string,
63
- deps: Pick<StudyRegistryDeps, "entries">,
64
- ): ReadonlySet<string> =>
65
- new Set(
66
- deps
67
- .entries(callerPath(join(dir, REPRO_DIR)))
68
- .filter((e) => e.kind === "symlink")
69
- .map((e) => e.name),
70
- );
71
-
72
- /**
73
- * The test every reader of a study's folder passes a row through: its folder is one folder name
74
- * inside `repro/` (`isFolderName`) and not a symbolic link, which would lead the reads anywhere.
75
- */
76
- export const readableFolders = (
77
- dir: string,
78
- deps: Pick<StudyRegistryDeps, "entries">,
79
- ): ((folder: string) => boolean) => {
80
- const links = linksIn(dir, deps);
81
- return (folder) => isFolderName(folder) && !links.has(folder);
82
- };
83
-
84
67
  /** The text of the file at `p`; null when there is none, or a directory sits there. */
85
68
  export const textAt = (files: Files, p: string): string | null => {
86
69
  const b = files.isFile(callerPath(p)) ? files.readBytes(callerPath(p)) : null;
@@ -111,70 +94,79 @@ const studyFolders = (dir: string, deps: FolderLayoutDeps): readonly string[] =>
111
94
  )
112
95
  .map((e) => e.name);
113
96
 
114
- /** Each `<folder>/<result_file>` of `r` that is a file. */
115
- const existingResults = (
116
- dir: string,
97
+ /** What each `<folder>/<result_file>` of `r`'s rows is, as the study folder's reader finds it. */
98
+ const resultsOf = (
117
99
  r: RegistryState,
118
- files: Files,
119
- readable: (folder: string) => boolean,
120
- ): ReadonlySet<string> =>
121
- new Set(
122
- (r.kind === "read" ? r.registry.studies : [])
123
- // A row whose folder is not one inside `repro/`, or is a link, is never read through.
124
- .filter((s) => readable(s.folder))
125
- .flatMap((s) =>
126
- s.result_file === undefined ? [] : [`${s.folder}/${s.result_file}`],
127
- )
128
- // A NUL is no path the disk can hold, and probing one throws: such a row reads as missing.
129
- .filter(
130
- (p) =>
131
- !p.includes("\0") &&
132
- files.isFile(callerPath(join(dir, REPRO_DIR, p))),
133
- ),
100
+ folder: StudyFolder,
101
+ ): ReadonlyMap<string, Located> =>
102
+ pipe(
103
+ studiesOf(r),
104
+ filter(hasResultFile),
105
+ map(
106
+ (s) =>
107
+ [
108
+ folderKey(s.folder, s.result_file),
109
+ folder.locate(s.folder, s.result_file),
110
+ ] as const,
111
+ ),
112
+ (entries) => new Map(entries),
134
113
  );
135
114
 
115
+ /** A row that names a result file. */
116
+ const hasResultFile = (
117
+ s: Study,
118
+ ): s is Study & { readonly result_file: string } => s.result_file !== undefined;
119
+
136
120
  /**
137
121
  * The plan each row of `r` names, read and parsed, by `<folder>/<plan>`; a plan not there is left out.
138
122
  * A plan still named `prereg.json` is parsed by the loose schema of its old name.
139
123
  */
140
124
  export const plansOf = (
141
- dir: string,
142
125
  r: RegistryState,
143
- deps: Pick<StudyRegistryDeps, "files" | "codec">,
144
- readable: (folder: string) => boolean,
126
+ folder: StudyFolder,
127
+ codec: StudyRegistryCodec,
145
128
  ): ReadonlyMap<string, PlanState> =>
146
129
  new Map(
147
- (r.kind === "read" ? planRefs(r.registry) : [])
148
- // A NUL is no path the disk can hold, and probing one throws: such a plan reads as missing. A
149
- // folder that is a link is never read through.
150
- .filter(({ key, folder }) => !key.includes("\0") && readable(folder))
151
- .flatMap(({ key, kind }): readonly (readonly [string, PlanState])[] => {
152
- const text = textAt(deps.files, join(dir, REPRO_DIR, key));
153
- if (text === null) return [];
154
- return [
155
- [
156
- key,
157
- posix.basename(key) === LEGACY_PLAN_FILE
158
- ? deps.codec.parseLegacyPlan(text)
159
- : deps.codec.parsePlan(text, kind),
160
- ],
161
- ];
162
- }),
130
+ (r.kind === "read" ? planRefs(r.registry) : []).flatMap((ref) => {
131
+ const state = planStateOf(ref, folder, codec);
132
+ return state === null
133
+ ? []
134
+ : [[folderKey(ref.folder, ref.plan), state] as const];
135
+ }),
163
136
  );
164
137
 
138
+ /** The state of the plan `ref` names, read through `folder`; null when it is not there. */
139
+ const planStateOf = (
140
+ { folder: of, plan, kind }: PlanRef,
141
+ folder: StudyFolder,
142
+ codec: StudyRegistryCodec,
143
+ ): PlanState | null => {
144
+ const read = folder.read(of, plan);
145
+ switch (read.kind) {
146
+ case "absent":
147
+ return null;
148
+ case "linked":
149
+ return { kind: "linked", link: read.link };
150
+ case "text":
151
+ return posix.basename(plan) === LEGACY_PLAN_FILE
152
+ ? codec.parseLegacyPlan(read.text)
153
+ : codec.parsePlan(read.text, kind);
154
+ }
155
+ };
156
+
165
157
  /** Every finding of the paper folder `dir`'s study registry — THE function behind both rules. */
166
158
  export function studyFindings(
167
159
  dir: string,
168
160
  deps: StudyRegistryDeps,
169
161
  ): readonly StudyFinding[] {
170
- const readable = readableFolders(dir, deps);
171
162
  const registry = registryOf(dir, deps);
163
+ const folder = studyFolder(dir, deps);
172
164
  return judgeStudies({
173
165
  folders: studyFolders(dir, deps),
174
166
  registry,
175
167
  readme: textAt(deps.files, join(dir, REPRO_DIR, README)),
176
- existingResults: existingResults(dir, registry, deps.files, readable),
177
- plans: plansOf(dir, registry, deps, readable),
168
+ results: resultsOf(registry, folder),
169
+ plans: plansOf(registry, folder, deps.codec),
178
170
  });
179
171
  }
180
172