paperlint 10.3.0 → 10.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/.github/workflows/ci.yml +15 -4
  2. package/README.md +1 -0
  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/git/index.d.ts +10 -0
  7. package/dist/adapters/git/index.d.ts.map +1 -0
  8. package/dist/adapters/git/index.js +151 -0
  9. package/dist/adapters/git/index.js.map +1 -0
  10. package/dist/adapters/latex/captions.d.ts +9 -0
  11. package/dist/adapters/latex/captions.d.ts.map +1 -0
  12. package/dist/adapters/latex/captions.js +40 -0
  13. package/dist/adapters/latex/captions.js.map +1 -0
  14. package/dist/adapters/latex/index.d.ts +1 -0
  15. package/dist/adapters/latex/index.d.ts.map +1 -1
  16. package/dist/adapters/latex/index.js +1 -0
  17. package/dist/adapters/latex/index.js.map +1 -1
  18. package/dist/adapters/latex/parse.d.ts.map +1 -1
  19. package/dist/adapters/latex/parse.js +6 -0
  20. package/dist/adapters/latex/parse.js.map +1 -1
  21. package/dist/adapters/memory/index.d.ts +6 -0
  22. package/dist/adapters/memory/index.d.ts.map +1 -1
  23. package/dist/adapters/memory/index.js +8 -0
  24. package/dist/adapters/memory/index.js.map +1 -1
  25. package/dist/adapters/node/index.d.ts +1 -0
  26. package/dist/adapters/node/index.d.ts.map +1 -1
  27. package/dist/adapters/node/index.js +1 -0
  28. package/dist/adapters/node/index.js.map +1 -1
  29. package/dist/adapters/node/list-entries.io.d.ts +3 -0
  30. package/dist/adapters/node/list-entries.io.d.ts.map +1 -0
  31. package/dist/adapters/node/list-entries.io.js +26 -0
  32. package/dist/adapters/node/list-entries.io.js.map +1 -0
  33. package/dist/adapters/study-registry/index.d.ts +70 -10
  34. package/dist/adapters/study-registry/index.d.ts.map +1 -1
  35. package/dist/adapters/study-registry/index.js +84 -10
  36. package/dist/adapters/study-registry/index.js.map +1 -1
  37. package/dist/cli.d.ts.map +1 -1
  38. package/dist/cli.js +33 -3
  39. package/dist/cli.js.map +1 -1
  40. package/dist/domain/decide-first.d.ts +110 -0
  41. package/dist/domain/decide-first.d.ts.map +1 -0
  42. package/dist/domain/decide-first.js +108 -0
  43. package/dist/domain/decide-first.js.map +1 -0
  44. package/dist/domain/index-columns.d.ts +45 -0
  45. package/dist/domain/index-columns.d.ts.map +1 -0
  46. package/dist/domain/index-columns.js +64 -0
  47. package/dist/domain/index-columns.js.map +1 -0
  48. package/dist/domain/study-mode.d.ts +37 -0
  49. package/dist/domain/study-mode.d.ts.map +1 -0
  50. package/dist/domain/study-mode.js +45 -0
  51. package/dist/domain/study-mode.js.map +1 -0
  52. package/dist/domain/study-registry.d.ts +116 -12
  53. package/dist/domain/study-registry.d.ts.map +1 -1
  54. package/dist/domain/study-registry.js +164 -44
  55. package/dist/domain/study-registry.js.map +1 -1
  56. package/dist/outputs-before-decision-rule.d.ts +17 -0
  57. package/dist/outputs-before-decision-rule.d.ts.map +1 -0
  58. package/dist/outputs-before-decision-rule.js +94 -0
  59. package/dist/outputs-before-decision-rule.js.map +1 -0
  60. package/dist/ports/git-history.d.ts +44 -0
  61. package/dist/ports/git-history.d.ts.map +1 -0
  62. package/dist/ports/git-history.js +2 -0
  63. package/dist/ports/git-history.js.map +1 -0
  64. package/dist/ports/list-entries.d.ts +20 -0
  65. package/dist/ports/list-entries.d.ts.map +1 -0
  66. package/dist/ports/list-entries.js +2 -0
  67. package/dist/ports/list-entries.js.map +1 -0
  68. package/dist/ports/study-deps.d.ts +14 -0
  69. package/dist/ports/study-deps.d.ts.map +1 -0
  70. package/dist/ports/study-deps.js +2 -0
  71. package/dist/ports/study-deps.js.map +1 -0
  72. package/dist/ports/study-folder.d.ts +43 -0
  73. package/dist/ports/study-folder.d.ts.map +1 -0
  74. package/dist/ports/study-folder.js +2 -0
  75. package/dist/ports/study-folder.js.map +1 -0
  76. package/dist/ports/study-registry-codec.d.ts +6 -3
  77. package/dist/ports/study-registry-codec.d.ts.map +1 -1
  78. package/dist/presets.d.ts +1 -2
  79. package/dist/presets.d.ts.map +1 -1
  80. package/dist/presets.js +1 -2
  81. package/dist/presets.js.map +1 -1
  82. package/dist/studies.d.ts +4 -0
  83. package/dist/studies.d.ts.map +1 -1
  84. package/dist/studies.js +52 -21
  85. package/dist/studies.js.map +1 -1
  86. package/dist/study-folder.d.ts +14 -0
  87. package/dist/study-folder.d.ts.map +1 -0
  88. package/dist/study-folder.js +123 -0
  89. package/dist/study-folder.js.map +1 -0
  90. package/dist/study-gate.d.ts +14 -0
  91. package/dist/study-gate.d.ts.map +1 -0
  92. package/dist/study-gate.js +132 -0
  93. package/dist/study-gate.js.map +1 -0
  94. package/dist/study-index-input.d.ts +5 -0
  95. package/dist/study-index-input.d.ts.map +1 -0
  96. package/dist/study-index-input.js +18 -0
  97. package/dist/study-index-input.js.map +1 -0
  98. package/dist/study-modes.d.ts +56 -0
  99. package/dist/study-modes.d.ts.map +1 -0
  100. package/dist/study-modes.js +59 -0
  101. package/dist/study-modes.js.map +1 -0
  102. package/dist/study-registry-rule.d.ts +13 -1
  103. package/dist/study-registry-rule.d.ts.map +1 -1
  104. package/dist/study-registry-rule.js +33 -21
  105. package/dist/study-registry-rule.js.map +1 -1
  106. package/dist/study-run.d.ts +19 -0
  107. package/dist/study-run.d.ts.map +1 -0
  108. package/dist/study-run.js +9 -0
  109. package/dist/study-run.js.map +1 -0
  110. package/docs/design/study-modes.md +265 -0
  111. package/docs/design/study-registry.md +6 -0
  112. package/docs/e2e.md +8 -4
  113. package/docs/layout.md +22 -19
  114. package/docs/rules/repro/outputs-before-decision.md +122 -0
  115. package/docs/rules/repro/study-registered.md +49 -15
  116. package/docs/rules/tex/heading-case.md +1 -1
  117. package/docs/rules.md +54 -53
  118. package/docs/testing.md +1 -0
  119. package/eslint.config.mjs +36 -24
  120. package/package.json +2 -1
  121. package/scripts/check.ts +6 -0
  122. package/skills/camera-ready/SKILL.md +6 -1
  123. package/skills/camera-ready/SKILL.md.spec.ts +5 -0
  124. package/skills/draft-paper/SKILL.md +11 -3
  125. package/skills/draft-paper/SKILL.md.spec.ts +10 -2
  126. package/skills/grade-paper-writing/SKILL.md +4 -2
  127. package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -1
  128. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +16 -3
  129. package/skills/grade-paper-writing/prose-lint.mjs +127 -95
  130. package/skills/grade-paper-writing/prose-lint.test.ts +119 -10
  131. package/skills/harden-paper/SKILL.md +12 -2
  132. package/skills/harden-paper/SKILL.md.spec.ts +11 -1
  133. package/skills/osf-artifact-upload/SKILL.md +7 -1
  134. package/skills/osf-artifact-upload/SKILL.md.spec.ts +6 -0
  135. package/skills/pc-panel-review/SKILL.md +10 -3
  136. package/skills/pc-panel-review/SKILL.md.spec.ts +9 -2
  137. package/skills/tighten-paper/SKILL.md +5 -1
  138. package/skills/tighten-paper/SKILL.md.spec.ts +4 -0
  139. package/src/CLAUDE.md +7 -4
  140. package/src/CLAUDE.md.spec.ts +6 -3
  141. package/src/adapters/git/history.test.ts +121 -0
  142. package/src/adapters/git/index.test.ts +268 -0
  143. package/src/adapters/git/index.ts +227 -0
  144. package/src/adapters/latex/captions.test.ts +57 -0
  145. package/src/adapters/latex/captions.ts +56 -0
  146. package/src/adapters/latex/index.ts +1 -0
  147. package/src/adapters/latex/parse.ts +7 -0
  148. package/src/adapters/memory/index.test.ts +15 -1
  149. package/src/adapters/memory/index.ts +13 -0
  150. package/src/adapters/node/index.ts +1 -0
  151. package/src/adapters/node/list-entries.io.ts +27 -0
  152. package/src/adapters/node/list-entries.test.ts +61 -0
  153. package/src/adapters/study-registry/index.test.ts +12 -9
  154. package/src/adapters/study-registry/index.ts +130 -24
  155. package/src/adapters/study-registry/mode.test.ts +162 -0
  156. package/src/cli.ts +47 -3
  157. package/src/domain/decide-first.test.ts +291 -0
  158. package/src/domain/decide-first.ts +258 -0
  159. package/src/domain/index-columns.test.ts +261 -0
  160. package/src/domain/index-columns.ts +107 -0
  161. package/src/domain/study-measurements.test.ts +16 -8
  162. package/src/domain/study-mode.test.ts +55 -0
  163. package/src/domain/study-mode.ts +78 -0
  164. package/src/domain/study-registry.test.ts +72 -37
  165. package/src/domain/study-registry.ts +281 -50
  166. package/src/new-paper.test.ts +5 -1
  167. package/src/outputs-before-decision-rule.test.ts +251 -0
  168. package/src/outputs-before-decision-rule.ts +133 -0
  169. package/src/ports/git-history.ts +44 -0
  170. package/src/ports/list-entries.ts +21 -0
  171. package/src/ports/study-deps.ts +14 -0
  172. package/src/ports/study-folder.ts +38 -0
  173. package/src/ports/study-registry-codec.ts +10 -3
  174. package/src/presets.ts +1 -2
  175. package/src/rule-cases.test.ts +78 -3
  176. package/src/studies-index-modes.test.ts +129 -0
  177. package/src/studies.test.ts +35 -11
  178. package/src/studies.ts +96 -26
  179. package/src/study-folder.test.ts +237 -0
  180. package/src/study-folder.ts +176 -0
  181. package/src/study-gate.test.ts +467 -0
  182. package/src/study-gate.ts +234 -0
  183. package/src/study-index-input.ts +31 -0
  184. package/src/study-modes.ts +107 -0
  185. package/src/study-registry-rule.test.ts +285 -11
  186. package/src/study-registry-rule.ts +64 -35
  187. package/src/study-run.ts +21 -0
@@ -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,
@@ -30,11 +31,16 @@ import {
30
31
  PLAN_NAME_PROBLEMS,
31
32
  STUDIES_FILE,
32
33
  judgeStudies,
33
- planKeys,
34
+ folderKey,
35
+ planRefs,
36
+ studiesOf,
34
37
  studyMessage,
35
38
  type PlanState,
36
39
  type RegistryState,
40
+ type Study,
37
41
  type StudyFinding,
42
+ type Located,
43
+ type PlanRef,
38
44
  } from "./domain/study-registry.ts";
39
45
  import type { FolderLayoutDeps } from "./folder-layout-rule.ts";
40
46
  import type {
@@ -42,8 +48,11 @@ import type {
42
48
  FolderRuleModule,
43
49
  } from "./folder-venue-rule.ts";
44
50
  import type { Files } from "./ports/files.ts";
51
+ import type { ListEntries } from "./ports/list-entries.ts";
45
52
  import type { StudyRegistryCodec } from "./ports/study-registry-codec.ts";
46
53
  import { reproEntriesOf } from "./repro-layout-rule.ts";
54
+ import type { StudyFolder } from "./ports/study-folder.ts";
55
+ import { studyFolder } from "./study-folder.ts";
47
56
  import { rulePageUrl } from "./venue-rules.ts";
48
57
 
49
58
  const STATUS_FILE = "PIPELINE-STATUS.md";
@@ -51,10 +60,12 @@ const STATUS_FILE = "PIPELINE-STATUS.md";
51
60
  /** What the rule reads with: the disk, its listings, and the registry's parser. */
52
61
  export interface StudyRegistryDeps extends FolderLayoutDeps {
53
62
  readonly codec: StudyRegistryCodec;
63
+ /** `repro/`'s entries, links not followed: a study folder that is a link is never read through. */
64
+ readonly entries: ListEntries;
54
65
  }
55
66
 
56
67
  /** The text of the file at `p`; null when there is none, or a directory sits there. */
57
- const textAt = (files: Files, p: string): string | null => {
68
+ export const textAt = (files: Files, p: string): string | null => {
58
69
  const b = files.isFile(callerPath(p)) ? files.readBytes(callerPath(p)) : null;
59
70
  return b === null ? null : new TextDecoder().decode(b);
60
71
  };
@@ -83,61 +94,79 @@ const studyFolders = (dir: string, deps: FolderLayoutDeps): readonly string[] =>
83
94
  )
84
95
  .map((e) => e.name);
85
96
 
86
- /** Each `<folder>/<result_file>` of `r` that is a file. */
87
- const existingResults = (
88
- dir: string,
97
+ /** What each `<folder>/<result_file>` of `r`'s rows is, as the study folder's reader finds it. */
98
+ const resultsOf = (
89
99
  r: RegistryState,
90
- files: Files,
91
- ): ReadonlySet<string> =>
92
- new Set(
93
- (r.kind === "read" ? r.registry.studies : [])
94
- .flatMap((s) =>
95
- s.result_file === undefined ? [] : [`${s.folder}/${s.result_file}`],
96
- )
97
- // A NUL is no path the disk can hold, and probing one throws: such a row reads as missing.
98
- .filter(
99
- (p) =>
100
- !p.includes("\0") &&
101
- files.isFile(callerPath(join(dir, REPRO_DIR, p))),
102
- ),
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),
103
113
  );
104
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
+
105
120
  /**
106
121
  * The plan each row of `r` names, read and parsed, by `<folder>/<plan>`; a plan not there is left out.
107
122
  * A plan still named `prereg.json` is parsed by the loose schema of its old name.
108
123
  */
109
- const plansOf = (
110
- dir: string,
124
+ export const plansOf = (
111
125
  r: RegistryState,
112
- deps: Pick<StudyRegistryDeps, "files" | "codec">,
126
+ folder: StudyFolder,
127
+ codec: StudyRegistryCodec,
113
128
  ): ReadonlyMap<string, PlanState> =>
114
129
  new Map(
115
- (r.kind === "read" ? planKeys(r.registry) : [])
116
- // A NUL is no path the disk can hold, and probing one throws: such a plan reads as missing.
117
- .filter((key) => !key.includes("\0"))
118
- .flatMap((key): readonly (readonly [string, PlanState])[] => {
119
- const text = textAt(deps.files, join(dir, REPRO_DIR, key));
120
- if (text === null) return [];
121
- const parse =
122
- posix.basename(key) === LEGACY_PLAN_FILE
123
- ? deps.codec.parseLegacyPlan
124
- : deps.codec.parsePlan;
125
- return [[key, parse(text)]];
126
- }),
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
+ }),
127
136
  );
128
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
+
129
157
  /** Every finding of the paper folder `dir`'s study registry — THE function behind both rules. */
130
158
  export function studyFindings(
131
159
  dir: string,
132
160
  deps: StudyRegistryDeps,
133
161
  ): readonly StudyFinding[] {
134
162
  const registry = registryOf(dir, deps);
163
+ const folder = studyFolder(dir, deps);
135
164
  return judgeStudies({
136
165
  folders: studyFolders(dir, deps),
137
166
  registry,
138
167
  readme: textAt(deps.files, join(dir, REPRO_DIR, README)),
139
- existingResults: existingResults(dir, registry, deps.files),
140
- plans: plansOf(dir, registry, deps),
168
+ results: resultsOf(registry, folder),
169
+ plans: plansOf(registry, folder, deps.codec),
141
170
  });
142
171
  }
143
172
 
@@ -0,0 +1,21 @@
1
+ /** What a `paperlint studies` command on one study comes to: the lines it prints, or its refusal. */
2
+
3
+ /** A refusal: the exit code, and the line that explains it. */
4
+ export interface StudyRefusal {
5
+ readonly code: 1 | 2;
6
+ readonly line: string;
7
+ }
8
+
9
+ /** What a run came to: the lines it printed, or the refusal. */
10
+ export type StudyRun =
11
+ | { readonly ok: true; readonly lines: readonly string[] }
12
+ | { readonly ok: false; readonly refusal: StudyRefusal };
13
+
14
+ /** A refusal for the paper's state: exit 1. */
15
+ export const refusal = (line: string): StudyRefusal => ({ code: 1, line });
16
+
17
+ /** The run that is `r`. */
18
+ export const refused = (r: StudyRefusal): StudyRun => ({
19
+ ok: false,
20
+ refusal: r,
21
+ });