paperlint 10.1.1 → 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.
- package/README.md +2 -0
- package/dist/adapters/study-registry/index.d.ts +6 -0
- package/dist/adapters/study-registry/index.d.ts.map +1 -0
- package/dist/adapters/study-registry/index.js +58 -0
- package/dist/adapters/study-registry/index.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +40 -2
- package/dist/cli.js.map +1 -1
- package/dist/domain/repro-layout.d.ts +12 -4
- package/dist/domain/repro-layout.d.ts.map +1 -1
- package/dist/domain/repro-layout.js +49 -18
- package/dist/domain/repro-layout.js.map +1 -1
- package/dist/domain/scaffold-layout.d.ts +4 -3
- package/dist/domain/scaffold-layout.d.ts.map +1 -1
- package/dist/domain/scaffold-layout.js +7 -4
- package/dist/domain/scaffold-layout.js.map +1 -1
- package/dist/domain/study-registry.d.ts +176 -0
- package/dist/domain/study-registry.d.ts.map +1 -0
- package/dist/domain/study-registry.js +371 -0
- package/dist/domain/study-registry.js.map +1 -0
- package/dist/ports/study-registry-codec.d.ts +11 -0
- package/dist/ports/study-registry-codec.d.ts.map +1 -0
- package/dist/ports/study-registry-codec.js +2 -0
- package/dist/ports/study-registry-codec.js.map +1 -0
- package/dist/repro-layout-rule.d.ts.map +1 -1
- package/dist/repro-layout-rule.js +4 -3
- package/dist/repro-layout-rule.js.map +1 -1
- package/dist/studies.d.ts +27 -0
- package/dist/studies.d.ts.map +1 -0
- package/dist/studies.js +160 -0
- package/dist/studies.js.map +1 -0
- package/dist/study-registry-rule.d.ts +17 -0
- package/dist/study-registry-rule.d.ts.map +1 -0
- package/dist/study-registry-rule.js +115 -0
- package/dist/study-registry-rule.js.map +1 -0
- package/docs/design/study-registry.md +130 -0
- package/docs/layout.md +47 -44
- package/docs/rules/paper/allowed-files-only.md +3 -2
- package/docs/rules/repro/allowed-files-only.md +40 -30
- package/docs/rules/repro/study-registered.md +162 -0
- package/docs/rules/study/declare-sample.md +1 -1
- package/docs/rules.md +2 -1
- package/package.json +1 -1
- package/skills/build-benchmark/SKILL.md +20 -1
- package/skills/build-benchmark/SKILL.md.spec.ts +19 -0
- package/skills/build-label-page/SKILL.md +7 -4
- package/skills/build-label-page/SKILL.md.spec.ts +6 -3
- package/skills/paper-pipeline/references/artifact-checklist.md +2 -1
- package/skills/paper-pipeline/references/pipeline-status-template.md +5 -3
- package/skills/run-agent-grid/SKILL.md +8 -1
- package/skills/run-agent-grid/SKILL.md.spec.ts +7 -0
- package/src/adapters/study-registry/index.test.ts +144 -0
- package/src/adapters/study-registry/index.ts +70 -0
- package/src/cli.ts +51 -2
- package/src/domain/paper-layout.test.ts +1 -1
- package/src/domain/repro-layout.test.ts +18 -9
- package/src/domain/repro-layout.ts +58 -18
- package/src/domain/scaffold-layout.ts +7 -4
- package/src/domain/study-registry.test.ts +657 -0
- package/src/domain/study-registry.ts +598 -0
- package/src/folder-layout-hook.test.ts +10 -0
- package/src/new-paper.test.ts +28 -2
- package/src/ports/study-registry-codec.ts +11 -0
- package/src/repro-layout-rule.ts +4 -3
- package/src/rule-cases.test.ts +41 -0
- package/src/studies-cli.test.ts +113 -0
- package/src/studies.test.ts +353 -0
- package/src/studies.ts +259 -0
- package/src/study-registry-rule.test.ts +317 -0
- package/src/study-registry-rule.ts +172 -0
|
@@ -7,15 +7,21 @@
|
|
|
7
7
|
* where a new run goes. The layout gives each kind of material one place:
|
|
8
8
|
*
|
|
9
9
|
* repro/
|
|
10
|
-
* ├── README.md the index: one line per study
|
|
11
|
-
* ├──
|
|
10
|
+
* ├── README.md the index: one line per study, generated from studies.json
|
|
11
|
+
* ├── studies.json the study registry: one row per study (src/domain/study-registry.ts)
|
|
12
|
+
* ├── <id>-<slug>/ one folder per study or spike, named by its row, with a README.md inside
|
|
12
13
|
* ├── tools/ scripts used across the paper (numbers, figures, anonymity check, harnesses)
|
|
13
14
|
* └── data/ shared data (tables, caches)
|
|
14
15
|
*
|
|
15
16
|
* plus `.gitignore`. A loose note belongs in its study's folder or in the paper's `research/`.
|
|
16
17
|
*
|
|
17
|
-
* ──
|
|
18
|
-
* `<slug
|
|
18
|
+
* ── A STUDY'S NAME ───────────────────────────────────────────────────────────────
|
|
19
|
+
* `<id>-<slug>` (`P1-S03-baseline`): the id of the study's row in `repro/studies.json` — the paper's
|
|
20
|
+
* prefix, `-S`, two or more digits — then the slug. Whether the id has a row, and the row this folder,
|
|
21
|
+
* is `repro/study-registered`'s question; this layout only accepts the shape.
|
|
22
|
+
*
|
|
23
|
+
* The older form, `<slug>-YYYY-MM-DD`, stays accepted, so a paper that turned the registry off is not
|
|
24
|
+
* broken: the date is the day the study began. `<slug>-YYYY-MM` is accepted too: a study that ran
|
|
19
25
|
* over weeks (labelling, a reading of a corpus) has a month as its honest date, and both forms sort
|
|
20
26
|
* by time. The date must be a real one (no month 13, no 30 February). The slug is lowercase letters
|
|
21
27
|
* and digits in words joined by single hyphens.
|
|
@@ -32,6 +38,9 @@ import { leaf, renderTree, type TreeNode } from "./layout-tree.ts";
|
|
|
32
38
|
/** The folder this layout governs, at the paper folder's root. */
|
|
33
39
|
export const REPRO_DIR = "repro";
|
|
34
40
|
|
|
41
|
+
/** The study registry, at the top of `repro/` (src/domain/study-registry.ts). */
|
|
42
|
+
export const STUDIES_FILE = "studies.json";
|
|
43
|
+
|
|
35
44
|
/** The index at the top of `repro/`, and the file every study folder holds. */
|
|
36
45
|
export const README = "README.md";
|
|
37
46
|
|
|
@@ -40,7 +49,14 @@ export const REPRO_FILE_NOTES: readonly {
|
|
|
40
49
|
readonly name: string;
|
|
41
50
|
readonly note: string;
|
|
42
51
|
}[] = [
|
|
43
|
-
{
|
|
52
|
+
{
|
|
53
|
+
name: README,
|
|
54
|
+
note: "the index: one line per study, generated from studies.json",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: STUDIES_FILE,
|
|
58
|
+
note: "the study registry: id, title, status and result of each study",
|
|
59
|
+
},
|
|
44
60
|
{ name: ".gitignore", note: "keeps the scripts' byproducts out of git" },
|
|
45
61
|
];
|
|
46
62
|
export const REPRO_FILES: readonly string[] = REPRO_FILE_NOTES.map(
|
|
@@ -64,8 +80,8 @@ export const REPRO_DIRS: readonly string[] = REPRO_DIR_NOTES.map((d) => d.name);
|
|
|
64
80
|
|
|
65
81
|
/** The study folder as the layout draws it: the name pattern, and the README.md it must hold. */
|
|
66
82
|
const STUDY_NODE: TreeNode = {
|
|
67
|
-
name: "<slug
|
|
68
|
-
note: "one folder per study or spike
|
|
83
|
+
name: "<id>-<slug>/",
|
|
84
|
+
note: "one folder per study or spike, named by its row in studies.json (P1-S03-baseline)",
|
|
69
85
|
children: [
|
|
70
86
|
leaf(README, "required: what it asks, how to run it, what it found"),
|
|
71
87
|
],
|
|
@@ -100,9 +116,20 @@ export const REPRO_BYPRODUCTS: readonly string[] = [
|
|
|
100
116
|
"Thumbs.db",
|
|
101
117
|
];
|
|
102
118
|
|
|
103
|
-
/** A
|
|
119
|
+
/** A slug: lowercase letters and digits, in words joined by single hyphens. */
|
|
120
|
+
export const STUDY_SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
121
|
+
|
|
122
|
+
/** A dated study's name: a slug, then the year, the month, and optionally the day it began. */
|
|
104
123
|
const STUDY_NAME = /^([a-z0-9]+(?:-[a-z0-9]+)*)-(\d{4})-(\d{2})(?:-(\d{2}))?$/u;
|
|
105
124
|
|
|
125
|
+
/** A registered study's name: the id (a prefix, `-S`, two or more digits), then the slug. */
|
|
126
|
+
const REGISTERED_STUDY_NAME =
|
|
127
|
+
/^[A-Z][A-Z0-9]*-S\d{2,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
128
|
+
|
|
129
|
+
/** Whether `name` is a registered study folder's: `<id>-<slug>`, as `P1-S03-baseline`. */
|
|
130
|
+
export const isRegisteredStudyName = (name: string): boolean =>
|
|
131
|
+
REGISTERED_STUDY_NAME.test(name);
|
|
132
|
+
|
|
106
133
|
const isLeapYear = (y: number): boolean =>
|
|
107
134
|
(y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
|
108
135
|
|
|
@@ -110,8 +137,8 @@ const isLeapYear = (y: number): boolean =>
|
|
|
110
137
|
const daysIn = (y: number, m: number): number =>
|
|
111
138
|
m === 2 ? (isLeapYear(y) ? 29 : 28) : [4, 6, 9, 11].includes(m) ? 30 : 31;
|
|
112
139
|
|
|
113
|
-
/** Whether `name` is a study folder's: `<slug>-YYYY-MM-DD` or `<slug>-YYYY-MM`, a real date. */
|
|
114
|
-
|
|
140
|
+
/** Whether `name` is a dated study folder's: `<slug>-YYYY-MM-DD` or `<slug>-YYYY-MM`, a real date. */
|
|
141
|
+
function isDatedStudyName(name: string): boolean {
|
|
115
142
|
const m = STUDY_NAME.exec(name);
|
|
116
143
|
if (m === null) return false;
|
|
117
144
|
const year = Number(m[2]);
|
|
@@ -120,6 +147,10 @@ export function isStudyName(name: string): boolean {
|
|
|
120
147
|
return month >= 1 && month <= 12 && day >= 1 && day <= daysIn(year, month);
|
|
121
148
|
}
|
|
122
149
|
|
|
150
|
+
/** Whether `name` is a study folder's: `<id>-<slug>`, or the dated `<slug>-YYYY-MM-DD` / `<slug>-YYYY-MM`. */
|
|
151
|
+
export const isStudyName = (name: string): boolean =>
|
|
152
|
+
isRegisteredStudyName(name) || isDatedStudyName(name);
|
|
153
|
+
|
|
123
154
|
/**
|
|
124
155
|
* The study name to suggest for a folder named `name`: its name as a slug, with the date to fill in.
|
|
125
156
|
* A name that becomes a study name once lowercased and hyphenated is suggested as it is.
|
|
@@ -129,9 +160,18 @@ export function suggestedStudyName(name: string): string {
|
|
|
129
160
|
.toLowerCase()
|
|
130
161
|
.replace(/[^a-z0-9]+/gu, "-")
|
|
131
162
|
.replace(/^-+|-+$/gu, "");
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
163
|
+
if (isDatedStudyName(slug)) return slug;
|
|
164
|
+
return `${suggestedSlug(name)}-YYYY-MM-DD`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The slug to suggest for a folder named `name`: its name as a slug, any trailing date dropped. */
|
|
168
|
+
export function suggestedSlug(name: string): string {
|
|
169
|
+
const base = name
|
|
170
|
+
.toLowerCase()
|
|
171
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
172
|
+
.replace(/^-+|-+$/gu, "")
|
|
173
|
+
.replace(/(?:^|-)\d{4}(?:-\d{1,2}){0,2}$/u, "");
|
|
174
|
+
return base === "" ? "<slug>" : base;
|
|
135
175
|
}
|
|
136
176
|
|
|
137
177
|
/** The extensions of a script: what a study or the paper's build runs. */
|
|
@@ -203,11 +243,11 @@ export const DATA_CURE =
|
|
|
203
243
|
|
|
204
244
|
/** Where a note goes. */
|
|
205
245
|
export const NOTE_CURE =
|
|
206
|
-
"the
|
|
246
|
+
"the folder of the study it is about (repro/<id>-<slug>/), or the paper's research/ when it is research or a decision rather than the record of a run";
|
|
207
247
|
|
|
208
248
|
/** Where a file of any other kind goes. */
|
|
209
249
|
export const FILE_CURE_BY_KIND =
|
|
210
|
-
"decide by what it is — a script → repro/tools/; data → repro/data/; a note → the
|
|
250
|
+
"decide by what it is — a script → repro/tools/; data → repro/data/; a note → the folder of its study, or the paper's research/; what one study produced → that study's folder";
|
|
211
251
|
|
|
212
252
|
/** The files paperlint itself once kept at the top of `repro/`, each with where it is read now. */
|
|
213
253
|
export const MOVED_FILES: readonly {
|
|
@@ -242,7 +282,7 @@ function fileCure(name: string): string {
|
|
|
242
282
|
|
|
243
283
|
/** Where a folder with no study name goes: renamed as a study, or emptied into the shared folders. */
|
|
244
284
|
const folderCure = (name: string): string =>
|
|
245
|
-
`rename it to
|
|
285
|
+
`rename it to \`<id>-${suggestedSlug(name)}\` — the next free id in repro/${STUDIES_FILE}, then what it is — give it its row there and a README.md (\`npx paperlint studies new\` makes all three for a new study); in a paper with repro/study-registered off, \`${suggestedStudyName(name)}\` is accepted instead, dated by the day it began — its first commit is a fine default (\`git log --reverse --format=%as -- repro/${name} | head -1\`, run in the paper folder); if it holds scripts or data shared across the paper instead, move them into repro/tools/ or repro/data/`;
|
|
246
286
|
|
|
247
287
|
/** An entry at the top of `repro/`: a file or a folder, and for a folder whether it holds a README.md. */
|
|
248
288
|
export interface ReproEntry {
|
|
@@ -280,7 +320,7 @@ function judgeEntry(e: ReproEntry): ReproFinding | null {
|
|
|
280
320
|
return {
|
|
281
321
|
path,
|
|
282
322
|
problem: "no-readme",
|
|
283
|
-
cure: `add ${path}${README} — what the study asks, how to run it, what it found — and
|
|
323
|
+
cure: `add ${path}${README} — what the study asks, how to run it, what it found — and give the study its row in repro/${STUDIES_FILE}`,
|
|
284
324
|
};
|
|
285
325
|
return null;
|
|
286
326
|
}
|
|
@@ -300,7 +340,7 @@ export function judgeRepro(
|
|
|
300
340
|
export function reproMessage(f: ReproFinding): string {
|
|
301
341
|
const head =
|
|
302
342
|
f.problem === "stray"
|
|
303
|
-
? `\`${f.path}\` is loose at the top of the paper's repro/ folder, which holds only an index, the shared tools and data, and one
|
|
343
|
+
? `\`${f.path}\` is loose at the top of the paper's repro/ folder, which holds only an index, the study registry, the shared tools and data, and one folder per study. Where it goes: ${f.cure}.`
|
|
304
344
|
: `\`${f.path}\` is a study folder without a ${README}, and every study in repro/ has one. What to do: ${f.cure}.`;
|
|
305
345
|
return [
|
|
306
346
|
head,
|
|
@@ -70,13 +70,16 @@ export const SCAFFOLD_DIRS: readonly string[] = ROOT_DIR_NOTES.map(
|
|
|
70
70
|
).filter((name) => !NOT_SCAFFOLDED.some((n) => n.name === name));
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* The stub index of `repro/`: it makes the folder non-empty (no `.gitkeep`) and says what the
|
|
74
|
-
*
|
|
73
|
+
* The stub index of `repro/`: it makes the folder non-empty (no `.gitkeep`) and says what the rules
|
|
74
|
+
* ask of it. The table of studies is generated into it by `paperlint studies new` and
|
|
75
|
+
* `paperlint studies index`, from `repro/studies.json`.
|
|
75
76
|
*/
|
|
76
77
|
export const REPRO_INDEX_STUB = `# Reproduction index
|
|
77
78
|
|
|
78
|
-
One line per study
|
|
79
|
-
paper
|
|
79
|
+
One line per study, generated from \`studies.json\` — create a study with
|
|
80
|
+
\`npx paperlint studies new <paper-dir> "<title>"\`, and after editing its row run
|
|
81
|
+
\`npx paperlint studies index <paper-dir>\`. Scripts used across the paper go in \`${TOOLS_DIR}/\`,
|
|
82
|
+
shared data in \`${DATA_DIR}/\`.
|
|
80
83
|
`;
|
|
81
84
|
|
|
82
85
|
/** A file `paperlint new` writes into a new paper folder, with its path from that folder. */
|