paperlint 10.1.0 → 10.2.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 (72) hide show
  1. package/README.md +2 -0
  2. package/dist/adapters/study-registry/index.d.ts +6 -0
  3. package/dist/adapters/study-registry/index.d.ts.map +1 -0
  4. package/dist/adapters/study-registry/index.js +58 -0
  5. package/dist/adapters/study-registry/index.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +40 -2
  8. package/dist/cli.js.map +1 -1
  9. package/dist/domain/repro-layout.d.ts +12 -4
  10. package/dist/domain/repro-layout.d.ts.map +1 -1
  11. package/dist/domain/repro-layout.js +49 -18
  12. package/dist/domain/repro-layout.js.map +1 -1
  13. package/dist/domain/scaffold-layout.d.ts +4 -3
  14. package/dist/domain/scaffold-layout.d.ts.map +1 -1
  15. package/dist/domain/scaffold-layout.js +7 -4
  16. package/dist/domain/scaffold-layout.js.map +1 -1
  17. package/dist/domain/study-registry.d.ts +176 -0
  18. package/dist/domain/study-registry.d.ts.map +1 -0
  19. package/dist/domain/study-registry.js +371 -0
  20. package/dist/domain/study-registry.js.map +1 -0
  21. package/dist/ports/study-registry-codec.d.ts +11 -0
  22. package/dist/ports/study-registry-codec.d.ts.map +1 -0
  23. package/dist/ports/study-registry-codec.js +2 -0
  24. package/dist/ports/study-registry-codec.js.map +1 -0
  25. package/dist/repro-layout-rule.d.ts.map +1 -1
  26. package/dist/repro-layout-rule.js +4 -3
  27. package/dist/repro-layout-rule.js.map +1 -1
  28. package/dist/studies.d.ts +27 -0
  29. package/dist/studies.d.ts.map +1 -0
  30. package/dist/studies.js +160 -0
  31. package/dist/studies.js.map +1 -0
  32. package/dist/study-registry-rule.d.ts +17 -0
  33. package/dist/study-registry-rule.d.ts.map +1 -0
  34. package/dist/study-registry-rule.js +115 -0
  35. package/dist/study-registry-rule.js.map +1 -0
  36. package/docs/design/study-registry.md +130 -0
  37. package/docs/layout.md +47 -44
  38. package/docs/rules/paper/allowed-files-only.md +3 -2
  39. package/docs/rules/repro/allowed-files-only.md +40 -30
  40. package/docs/rules/repro/study-registered.md +162 -0
  41. package/docs/rules/study/declare-sample.md +1 -1
  42. package/docs/rules.md +2 -1
  43. package/package.json +1 -1
  44. package/skills/build-benchmark/SKILL.md +20 -1
  45. package/skills/build-benchmark/SKILL.md.spec.ts +19 -0
  46. package/skills/build-label-page/SKILL.md +7 -4
  47. package/skills/build-label-page/SKILL.md.spec.ts +6 -3
  48. package/skills/paper-pipeline/references/artifact-checklist.md +2 -1
  49. package/skills/paper-pipeline/references/pipeline-status-template.md +5 -3
  50. package/skills/render-paper/check-render.sh +9 -0
  51. package/skills/render-paper/render-paper.harness.mjs +102 -0
  52. package/skills/run-agent-grid/SKILL.md +8 -1
  53. package/skills/run-agent-grid/SKILL.md.spec.ts +7 -0
  54. package/src/adapters/study-registry/index.test.ts +144 -0
  55. package/src/adapters/study-registry/index.ts +70 -0
  56. package/src/cli.ts +51 -2
  57. package/src/domain/paper-layout.test.ts +1 -1
  58. package/src/domain/repro-layout.test.ts +18 -9
  59. package/src/domain/repro-layout.ts +58 -18
  60. package/src/domain/scaffold-layout.ts +7 -4
  61. package/src/domain/study-registry.test.ts +657 -0
  62. package/src/domain/study-registry.ts +598 -0
  63. package/src/folder-layout-hook.test.ts +10 -0
  64. package/src/new-paper.test.ts +28 -2
  65. package/src/ports/study-registry-codec.ts +11 -0
  66. package/src/repro-layout-rule.ts +4 -3
  67. package/src/rule-cases.test.ts +41 -0
  68. package/src/studies-cli.test.ts +113 -0
  69. package/src/studies.test.ts +353 -0
  70. package/src/studies.ts +259 -0
  71. package/src/study-registry-rule.test.ts +317 -0
  72. package/src/study-registry-rule.ts +172 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * `repro/study-registered` — every study folder at the top of a paper's `repro/` has exactly one row in
3
+ * `repro/studies.json`, every row names a folder that exists, and the index in `repro/README.md` is the
4
+ * one the registry generates.
5
+ *
6
+ * What is judged, and why, is `src/domain/study-registry.ts` and docs/rules/repro/study-registered.md.
7
+ * This file lists `repro/`, reads the registry, the README and the result files, and reports.
8
+ *
9
+ * ── WHY A RULE OF ITS OWN, NOT MORE OF `repro/allowed-files-only` ─────────────────────
10
+ * `repro/allowed-files-only` judges what may stand at the top of `repro/`, by name, and accepts the
11
+ * dated `<slug>-YYYY-MM-DD` folders of a paper that predates the registry. This rule judges the
12
+ * registry against the folders. A paper that keeps its dated folders turns this one off and keeps
13
+ * its layout judged.
14
+ *
15
+ * ── WHICH FILE IT REPORTS ON ─────────────────────────────────────────────────────
16
+ * `PIPELINE-STATUS.md`, first line, like the layout rules: every paper has it, and ESLint lints no
17
+ * file in `repro/`.
18
+ */
19
+ import { basename, dirname, join } from "node:path";
20
+ import { callerPath } from "./caller-path.ts";
21
+ import {
22
+ README,
23
+ REPRO_BYPRODUCTS,
24
+ REPRO_DIR,
25
+ REPRO_DIRS,
26
+ } from "./domain/repro-layout.ts";
27
+ import {
28
+ STUDIES_FILE,
29
+ judgeStudies,
30
+ studyMessage,
31
+ type RegistryState,
32
+ type StudyFinding,
33
+ } from "./domain/study-registry.ts";
34
+ import type { FolderLayoutDeps } from "./folder-layout-rule.ts";
35
+ import type {
36
+ FolderRuleContext,
37
+ FolderRuleModule,
38
+ } from "./folder-venue-rule.ts";
39
+ import type { Files } from "./ports/files.ts";
40
+ import type { StudyRegistryCodec } from "./ports/study-registry-codec.ts";
41
+ import { reproEntriesOf } from "./repro-layout-rule.ts";
42
+ import { rulePageUrl } from "./venue-rules.ts";
43
+
44
+ const STATUS_FILE = "PIPELINE-STATUS.md";
45
+
46
+ /** What the rule reads with: the disk, its listings, and the registry's parser. */
47
+ export interface StudyRegistryDeps extends FolderLayoutDeps {
48
+ readonly codec: StudyRegistryCodec;
49
+ }
50
+
51
+ /** The text of the file at `p`; null when there is none, or a directory sits there. */
52
+ const textAt = (files: Files, p: string): string | null => {
53
+ const b = files.isFile(callerPath(p)) ? files.readBytes(callerPath(p)) : null;
54
+ return b === null ? null : new TextDecoder().decode(b);
55
+ };
56
+
57
+ /** The registry of the paper folder `dir`: absent, unreadable with why, or read. */
58
+ export function registryOf(
59
+ dir: string,
60
+ deps: Pick<StudyRegistryDeps, "files" | "codec">,
61
+ ): RegistryState {
62
+ const text = textAt(deps.files, join(dir, REPRO_DIR, STUDIES_FILE));
63
+ if (text === null) return { kind: "absent" };
64
+ const parsed = deps.codec.parse(text);
65
+ return parsed.ok
66
+ ? { kind: "read", registry: parsed.registry }
67
+ : { kind: "unreadable", why: parsed.why };
68
+ }
69
+
70
+ /** The study folders at the top of `<dir>/repro/`: every folder but the shared ones and the byproducts. */
71
+ const studyFolders = (dir: string, deps: FolderLayoutDeps): readonly string[] =>
72
+ reproEntriesOf(dir, deps)
73
+ .filter(
74
+ (e) =>
75
+ e.kind === "dir" &&
76
+ !REPRO_DIRS.includes(e.name) &&
77
+ !REPRO_BYPRODUCTS.includes(e.name),
78
+ )
79
+ .map((e) => e.name);
80
+
81
+ /** Each `<folder>/<result_file>` of `r` that is a file. */
82
+ const existingResults = (
83
+ dir: string,
84
+ r: RegistryState,
85
+ files: Files,
86
+ ): ReadonlySet<string> =>
87
+ new Set(
88
+ (r.kind === "read" ? r.registry.studies : [])
89
+ .flatMap((s) =>
90
+ s.result_file === undefined ? [] : [`${s.folder}/${s.result_file}`],
91
+ )
92
+ // A NUL is no path the disk can hold, and probing one throws: such a row reads as missing.
93
+ .filter(
94
+ (p) =>
95
+ !p.includes("\0") &&
96
+ files.isFile(callerPath(join(dir, REPRO_DIR, p))),
97
+ ),
98
+ );
99
+
100
+ /** Every finding of the paper folder `dir`'s study registry — THE function behind the rule. */
101
+ export function studyFindings(
102
+ dir: string,
103
+ deps: StudyRegistryDeps,
104
+ ): readonly StudyFinding[] {
105
+ const registry = registryOf(dir, deps);
106
+ return judgeStudies({
107
+ folders: studyFolders(dir, deps),
108
+ registry,
109
+ readme: textAt(deps.files, join(dir, REPRO_DIR, README)),
110
+ existingResults: existingResults(dir, registry, deps.files),
111
+ });
112
+ }
113
+
114
+ /** The problems a finding can be, each its own message id; the text is `studyMessage`. */
115
+ const PROBLEMS: readonly StudyFinding["problem"][] = [
116
+ "no-registry",
117
+ "unreadable",
118
+ "unregistered",
119
+ "no-folder",
120
+ "two-rows",
121
+ "bad-id",
122
+ "duplicate-id",
123
+ "folder-name",
124
+ "long-title",
125
+ "no-result",
126
+ "result-file",
127
+ "no-successor",
128
+ "stale-index",
129
+ ];
130
+
131
+ const META: FolderRuleModule["meta"] = {
132
+ type: "suggestion",
133
+ docs: {
134
+ description:
135
+ "every study folder at the top of a paper's repro/ has exactly one row in repro/studies.json, every row names a folder that exists, and repro/README.md's index is the one the registry generates",
136
+ url: rulePageUrl("repro/study-registered"),
137
+ },
138
+ schema: [],
139
+ // The whole text is `studyMessage` (src/domain/study-registry.ts).
140
+ messages: Object.fromEntries(PROBLEMS.map((p) => [p, "{{text}}"])),
141
+ };
142
+
143
+ /** The rule, reading the folder through `deps`. */
144
+ export function studyRegistryRules(
145
+ deps: StudyRegistryDeps,
146
+ ): Readonly<Record<"study-registered", FolderRuleModule>> {
147
+ return {
148
+ "study-registered": {
149
+ meta: META,
150
+ create(context: FolderRuleContext) {
151
+ if (basename(context.filename) !== STATUS_FILE) return {};
152
+ return {
153
+ root() {
154
+ const at = context.sourceCode.getLocFromIndex(0);
155
+ studyFindings(dirname(context.filename), deps).forEach((f) => {
156
+ context.report({
157
+ loc: { start: at, end: at },
158
+ messageId: f.problem,
159
+ data: { text: studyMessage(f) },
160
+ });
161
+ });
162
+ },
163
+ };
164
+ },
165
+ },
166
+ };
167
+ }
168
+
169
+ /** The level it is on at in paperlint's own config, for every paper's `PIPELINE-STATUS.md`. */
170
+ export const STUDY_REGISTRY_RULE_LEVELS: Readonly<
171
+ Record<"repro/study-registered", "error">
172
+ > = { "repro/study-registered": "error" };