paperlint 10.4.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.
- package/dist/CLAUDE.md.spec.d.ts.map +1 -1
- package/dist/CLAUDE.md.spec.js +6 -3
- package/dist/CLAUDE.md.spec.js.map +1 -1
- package/dist/adapters/latex/captions.d.ts +9 -0
- package/dist/adapters/latex/captions.d.ts.map +1 -0
- package/dist/adapters/latex/captions.js +40 -0
- package/dist/adapters/latex/captions.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +1 -0
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +1 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +6 -0
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/node/list-entries.io.d.ts.map +1 -1
- package/dist/adapters/node/list-entries.io.js +3 -1
- package/dist/adapters/node/list-entries.io.js.map +1 -1
- package/dist/domain/decide-first.d.ts +11 -1
- package/dist/domain/decide-first.d.ts.map +1 -1
- package/dist/domain/decide-first.js +6 -0
- package/dist/domain/decide-first.js.map +1 -1
- package/dist/domain/index-columns.d.ts.map +1 -1
- package/dist/domain/index-columns.js +2 -0
- package/dist/domain/index-columns.js.map +1 -1
- package/dist/domain/study-registry.d.ts +65 -8
- package/dist/domain/study-registry.d.ts.map +1 -1
- package/dist/domain/study-registry.js +63 -19
- package/dist/domain/study-registry.js.map +1 -1
- package/dist/outputs-before-decision-rule.d.ts +1 -1
- package/dist/outputs-before-decision-rule.d.ts.map +1 -1
- package/dist/outputs-before-decision-rule.js +21 -17
- package/dist/outputs-before-decision-rule.js.map +1 -1
- package/dist/ports/list-entries.d.ts +7 -4
- package/dist/ports/list-entries.d.ts.map +1 -1
- package/dist/ports/study-deps.d.ts +14 -0
- package/dist/ports/study-deps.d.ts.map +1 -0
- package/dist/ports/study-deps.js +2 -0
- package/dist/ports/study-deps.js.map +1 -0
- package/dist/ports/study-folder.d.ts +43 -0
- package/dist/ports/study-folder.d.ts.map +1 -0
- package/dist/ports/study-folder.js +2 -0
- package/dist/ports/study-folder.js.map +1 -0
- package/dist/presets.d.ts +1 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +1 -2
- package/dist/presets.js.map +1 -1
- package/dist/studies.d.ts.map +1 -1
- package/dist/studies.js +10 -7
- package/dist/studies.js.map +1 -1
- package/dist/study-folder.d.ts +14 -0
- package/dist/study-folder.d.ts.map +1 -0
- package/dist/study-folder.js +123 -0
- package/dist/study-folder.js.map +1 -0
- package/dist/study-gate.d.ts +1 -1
- package/dist/study-gate.d.ts.map +1 -1
- package/dist/study-gate.js +65 -28
- package/dist/study-gate.js.map +1 -1
- package/dist/study-index-input.d.ts +1 -1
- package/dist/study-index-input.d.ts.map +1 -1
- package/dist/study-index-input.js +8 -3
- package/dist/study-index-input.js.map +1 -1
- package/dist/study-modes.d.ts +38 -27
- package/dist/study-modes.d.ts.map +1 -1
- package/dist/study-modes.js +42 -53
- package/dist/study-modes.js.map +1 -1
- package/dist/study-registry-rule.d.ts +2 -6
- package/dist/study-registry-rule.d.ts.map +1 -1
- package/dist/study-registry-rule.js +32 -41
- package/dist/study-registry-rule.js.map +1 -1
- package/docs/design/study-modes.md +46 -28
- package/docs/rules/repro/outputs-before-decision.md +3 -0
- package/docs/rules/repro/study-registered.md +3 -2
- package/docs/rules/tex/heading-case.md +1 -1
- package/eslint.config.mjs +36 -24
- package/package.json +1 -1
- package/skills/grade-paper-writing/SKILL.md +4 -2
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -1
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +16 -3
- package/skills/grade-paper-writing/prose-lint.mjs +127 -95
- package/skills/grade-paper-writing/prose-lint.test.ts +119 -10
- package/src/CLAUDE.md +7 -4
- package/src/CLAUDE.md.spec.ts +6 -3
- package/src/adapters/latex/captions.test.ts +57 -0
- package/src/adapters/latex/captions.ts +56 -0
- package/src/adapters/latex/index.ts +1 -0
- package/src/adapters/latex/parse.ts +7 -0
- package/src/adapters/node/list-entries.io.ts +2 -1
- package/src/adapters/node/list-entries.test.ts +18 -0
- package/src/domain/decide-first.test.ts +33 -0
- package/src/domain/decide-first.ts +16 -1
- package/src/domain/index-columns.test.ts +49 -4
- package/src/domain/index-columns.ts +2 -0
- package/src/domain/study-measurements.test.ts +7 -3
- package/src/domain/study-registry.test.ts +34 -6
- package/src/domain/study-registry.ts +123 -31
- package/src/outputs-before-decision-rule.test.ts +47 -2
- package/src/outputs-before-decision-rule.ts +31 -22
- package/src/ports/list-entries.ts +7 -4
- package/src/ports/study-deps.ts +14 -0
- package/src/ports/study-folder.ts +38 -0
- package/src/presets.ts +1 -2
- package/src/studies-index-modes.test.ts +2 -2
- package/src/studies.ts +19 -10
- package/src/study-folder.test.ts +237 -0
- package/src/study-folder.ts +176 -0
- package/src/study-gate.test.ts +158 -7
- package/src/study-gate.ts +127 -54
- package/src/study-index-input.ts +12 -4
- package/src/study-modes.ts +67 -80
- package/src/study-registry-rule.test.ts +87 -0
- package/src/study-registry-rule.ts +58 -66
package/dist/study-modes.js
CHANGED
|
@@ -1,70 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reading a paper's studies with their modes: each registered study whose `plan.json` parses with a
|
|
3
|
-
* `mode`, and
|
|
3
|
+
* `mode`, and how a decision's path stands. What `repro/outputs-before-decision`, `studies gate`
|
|
4
4
|
* and the index's mode columns read with (docs/design/study-modes.md). A row whose folder is not one
|
|
5
5
|
* folder inside `repro/` is never read through.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { isInside, } from "./domain/study-registry.js";
|
|
11
|
-
import { readableFolders, registryOf, textAt } from "./study-registry-rule.js";
|
|
12
|
-
const ON_DISK = {
|
|
13
|
-
file: "file",
|
|
14
|
-
dir: "directory",
|
|
15
|
-
symlink: "symbolic link",
|
|
16
|
-
};
|
|
7
|
+
import { posix } from "node:path";
|
|
8
|
+
import { filter, map, pipe } from "remeda";
|
|
9
|
+
import { studiesOf, } from "./domain/study-registry.js";
|
|
17
10
|
/**
|
|
18
|
-
* What `
|
|
19
|
-
* be a regular file — git answers for a directory with
|
|
20
|
-
* link with the link
|
|
11
|
+
* What the decision file `file` (relative to the paper) is, read through `folder` without following
|
|
12
|
+
* a link: a decision must be a regular file in real directories — git answers for a directory with
|
|
13
|
+
* the commits of anything under it, for a link with the link and not the file it points to, and for a
|
|
14
|
+
* path through a linked directory with what lies behind the link.
|
|
21
15
|
*/
|
|
22
|
-
export const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
export const decisionEntry = (folder, file) => {
|
|
17
|
+
const at = folder.entryAt(file);
|
|
18
|
+
if (at.kind === "dir")
|
|
19
|
+
return { kind: "not-a-file", what: "directory" };
|
|
20
|
+
if (at.kind === "other")
|
|
21
|
+
return { kind: "not-a-file", what: "special file" };
|
|
22
|
+
if (at.kind !== "linked")
|
|
23
|
+
return { kind: "ok" };
|
|
24
|
+
return at.link === posix.normalize(file)
|
|
25
|
+
? { kind: "not-a-file", what: "symbolic link" }
|
|
26
|
+
: at;
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*/
|
|
33
|
-
export const filesUnder = (dir, deps, prefix = "") => deps.entries(callerPath(dir)).flatMap(({ name, kind }) => {
|
|
34
|
-
const rel = prefix === "" ? name : `${prefix}/${name}`;
|
|
35
|
-
return kind === "dir" ? filesUnder(join(dir, name), deps, rel) : [rel];
|
|
36
|
-
});
|
|
37
|
-
/**
|
|
38
|
-
* The study `s`'s plan, when it is a plan inside a folder every reader may read through
|
|
39
|
-
* (`readableFolders`), by the registry rule's own containment test (`isInside`), that parsed with a
|
|
40
|
-
* mode; else null.
|
|
29
|
+
* The plan of the row `s`, read through `folder` (`src/study-folder.ts`, which follows no link and
|
|
30
|
+
* enters no folder that is not one inside `repro/`): its mode study when the plan parsed with a
|
|
31
|
+
* mode; a link on the way to it; else nothing.
|
|
41
32
|
*/
|
|
42
|
-
const
|
|
33
|
+
export const readModeStudy = (s, folder, codec) => {
|
|
43
34
|
const plan = s.plan;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const parsed = deps.codec.parsePlan(text, s.kind);
|
|
35
|
+
if (plan === undefined)
|
|
36
|
+
return { kind: "none" };
|
|
37
|
+
const read = folder.read(s.folder, plan);
|
|
38
|
+
if (read.kind === "linked")
|
|
39
|
+
return read;
|
|
40
|
+
if (read.kind === "absent")
|
|
41
|
+
return { kind: "none" };
|
|
42
|
+
const parsed = codec.parsePlan(read.text, s.kind);
|
|
53
43
|
if (parsed.kind === "unreadable" || parsed.plan.mode === undefined)
|
|
54
|
-
return
|
|
44
|
+
return { kind: "none" };
|
|
55
45
|
const empty = parsed.kind === "draft" ? parsed.empty : [];
|
|
56
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
kind: "mode",
|
|
48
|
+
study: { row: s, plan, mode: parsed.plan.mode, empty },
|
|
49
|
+
};
|
|
57
50
|
};
|
|
58
51
|
/**
|
|
59
|
-
* Every study of the
|
|
60
|
-
*
|
|
52
|
+
* Every study of the registry `r` whose plan declares a mode, in registry order. A plan that does
|
|
53
|
+
* not parse, or is reached through a link, is `repro/study-registered`'s to report; here it declares
|
|
54
|
+
* nothing.
|
|
61
55
|
*/
|
|
62
|
-
export const modeStudies = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return (r.kind === "read" ? r.registry.studies : []).flatMap((s) => {
|
|
66
|
-
const m = modeStudyOf(paper, s, deps, readable);
|
|
67
|
-
return m === null ? [] : [m];
|
|
68
|
-
});
|
|
69
|
-
};
|
|
56
|
+
export const modeStudies = (r, folder, codec) => pipe(studiesOf(r), map((s) => readModeStudy(s, folder, codec)), filter(isMode), map((m) => m.study));
|
|
57
|
+
/** A plan read that declared a mode. */
|
|
58
|
+
const isMode = (m) => m.kind === "mode";
|
|
70
59
|
//# sourceMappingURL=study-modes.js.map
|
package/dist/study-modes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"study-modes.js","sourceRoot":"","sources":["../src/study-modes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"study-modes.js","sourceRoot":"","sources":["../src/study-modes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG3C,OAAO,EACL,SAAS,GAIV,MAAM,4BAA4B,CAAC;AAUpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,MAAmB,EACnB,IAAY,EACG,EAAE;IACjB,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACxE,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC7E,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAChD,OAAO,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC;QACtC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE;QAC/C,CAAC,CAAC,EAAE,CAAC;AACT,CAAC,CAAC;AAoBF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,CAAQ,EACR,MAAmB,EACnB,KAAyB,EACf,EAAE;IACZ,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;IACpB,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;QAChE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;KACvD,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,CAAgB,EAChB,MAAmB,EACnB,KAAyB,EACH,EAAE,CACxB,IAAI,CACF,SAAS,CAAC,CAAC,CAAC,EACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAC3C,MAAM,CAAC,MAAM,CAAC,EACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CACpB,CAAC;AAEJ,wCAAwC;AACxC,MAAM,MAAM,GAAG,CAAC,CAAW,EAA4C,EAAE,CACvE,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC"}
|
|
@@ -4,17 +4,13 @@ import type { FolderRuleModule } from "./folder-venue-rule.ts";
|
|
|
4
4
|
import type { Files } from "./ports/files.ts";
|
|
5
5
|
import type { ListEntries } from "./ports/list-entries.ts";
|
|
6
6
|
import type { StudyRegistryCodec } from "./ports/study-registry-codec.ts";
|
|
7
|
+
import type { StudyFolder } from "./ports/study-folder.ts";
|
|
7
8
|
/** What the rule reads with: the disk, its listings, and the registry's parser. */
|
|
8
9
|
export interface StudyRegistryDeps extends FolderLayoutDeps {
|
|
9
10
|
readonly codec: StudyRegistryCodec;
|
|
10
11
|
/** `repro/`'s entries, links not followed: a study folder that is a link is never read through. */
|
|
11
12
|
readonly entries: ListEntries;
|
|
12
13
|
}
|
|
13
|
-
/**
|
|
14
|
-
* The test every reader of a study's folder passes a row through: its folder is one folder name
|
|
15
|
-
* inside `repro/` (`isFolderName`) and not a symbolic link, which would lead the reads anywhere.
|
|
16
|
-
*/
|
|
17
|
-
export declare const readableFolders: (dir: string, deps: Pick<StudyRegistryDeps, "entries">) => ((folder: string) => boolean);
|
|
18
14
|
/** The text of the file at `p`; null when there is none, or a directory sits there. */
|
|
19
15
|
export declare const textAt: (files: Files, p: string) => string | null;
|
|
20
16
|
/** The registry of the paper folder `dir`: absent, unreadable with why, or read. */
|
|
@@ -23,7 +19,7 @@ export declare function registryOf(dir: string, deps: Pick<StudyRegistryDeps, "f
|
|
|
23
19
|
* The plan each row of `r` names, read and parsed, by `<folder>/<plan>`; a plan not there is left out.
|
|
24
20
|
* A plan still named `prereg.json` is parsed by the loose schema of its old name.
|
|
25
21
|
*/
|
|
26
|
-
export declare const plansOf: (
|
|
22
|
+
export declare const plansOf: (r: RegistryState, folder: StudyFolder, codec: StudyRegistryCodec) => ReadonlyMap<string, PlanState>;
|
|
27
23
|
/** Every finding of the paper folder `dir`'s study registry — THE function behind both rules. */
|
|
28
24
|
export declare function studyFindings(dir: string, deps: StudyRegistryDeps): readonly StudyFinding[];
|
|
29
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"study-registry-rule.d.ts","sourceRoot":"","sources":["../src/study-registry-rule.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"study-registry-rule.d.ts","sourceRoot":"","sources":["../src/study-registry-rule.ts"],"names":[],"mappings":"AA4BA,OAAO,EASL,KAAK,SAAS,EACd,KAAK,aAAa,EAElB,KAAK,YAAY,EAGlB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAEV,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAM3D,mFAAmF;AACnF,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B;AAED,uFAAuF;AACvF,eAAO,MAAM,MAAM,GAAI,OAAO,KAAK,EAAE,GAAG,MAAM,KAAG,MAAM,GAAG,IAGzD,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,OAAO,CAAC,GAC/C,aAAa,CAOf;AAoCD;;;GAGG;AACH,eAAO,MAAM,OAAO,GAClB,GAAG,aAAa,EAChB,QAAQ,WAAW,EACnB,OAAO,kBAAkB,KACxB,WAAW,CAAC,MAAM,EAAE,SAAS,CAQ7B,CAAC;AAqBJ,iGAAiG;AACjG,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,iBAAiB,GACtB,SAAS,YAAY,EAAE,CAUzB;AA+DD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,iBAAiB,GACtB,QAAQ,CAAC,MAAM,CAAC,kBAAkB,GAAG,iBAAiB,EAAE,gBAAgB,CAAC,CAAC,CAqB5E;AAED,mGAAmG;AACnG,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC;IAChD,wBAAwB,EAAE,OAAO,CAAC;IAClC,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAA0E,CAAC"}
|
|
@@ -18,25 +18,14 @@
|
|
|
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.js";
|
|
22
23
|
import { README, REPRO_BYPRODUCTS, REPRO_DIR, REPRO_DIRS, } from "./domain/repro-layout.js";
|
|
23
|
-
import { LEGACY_PLAN_FILE, PLAN_NAME_PROBLEMS, STUDIES_FILE,
|
|
24
|
+
import { LEGACY_PLAN_FILE, PLAN_NAME_PROBLEMS, STUDIES_FILE, judgeStudies, folderKey, planRefs, studiesOf, studyMessage, } from "./domain/study-registry.js";
|
|
24
25
|
import { reproEntriesOf } from "./repro-layout-rule.js";
|
|
26
|
+
import { studyFolder } from "./study-folder.js";
|
|
25
27
|
import { rulePageUrl } from "./venue-rules.js";
|
|
26
28
|
const STATUS_FILE = "PIPELINE-STATUS.md";
|
|
27
|
-
/** The names at the top of `<dir>/repro/` that are symbolic links. */
|
|
28
|
-
const linksIn = (dir, deps) => new Set(deps
|
|
29
|
-
.entries(callerPath(join(dir, REPRO_DIR)))
|
|
30
|
-
.filter((e) => e.kind === "symlink")
|
|
31
|
-
.map((e) => e.name));
|
|
32
|
-
/**
|
|
33
|
-
* The test every reader of a study's folder passes a row through: its folder is one folder name
|
|
34
|
-
* inside `repro/` (`isFolderName`) and not a symbolic link, which would lead the reads anywhere.
|
|
35
|
-
*/
|
|
36
|
-
export const readableFolders = (dir, deps) => {
|
|
37
|
-
const links = linksIn(dir, deps);
|
|
38
|
-
return (folder) => isFolderName(folder) && !links.has(folder);
|
|
39
|
-
};
|
|
40
29
|
/** The text of the file at `p`; null when there is none, or a directory sits there. */
|
|
41
30
|
export const textAt = (files, p) => {
|
|
42
31
|
const b = files.isFile(callerPath(p)) ? files.readBytes(callerPath(p)) : null;
|
|
@@ -58,45 +47,47 @@ const studyFolders = (dir, deps) => reproEntriesOf(dir, deps)
|
|
|
58
47
|
!REPRO_DIRS.includes(e.name) &&
|
|
59
48
|
!REPRO_BYPRODUCTS.includes(e.name))
|
|
60
49
|
.map((e) => e.name);
|
|
61
|
-
/**
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
files.isFile(callerPath(join(dir, REPRO_DIR, p)))));
|
|
50
|
+
/** What each `<folder>/<result_file>` of `r`'s rows is, as the study folder's reader finds it. */
|
|
51
|
+
const resultsOf = (r, folder) => pipe(studiesOf(r), filter(hasResultFile), map((s) => [
|
|
52
|
+
folderKey(s.folder, s.result_file),
|
|
53
|
+
folder.locate(s.folder, s.result_file),
|
|
54
|
+
]), (entries) => new Map(entries));
|
|
55
|
+
/** A row that names a result file. */
|
|
56
|
+
const hasResultFile = (s) => s.result_file !== undefined;
|
|
69
57
|
/**
|
|
70
58
|
* The plan each row of `r` names, read and parsed, by `<folder>/<plan>`; a plan not there is left out.
|
|
71
59
|
* A plan still named `prereg.json` is parsed by the loose schema of its old name.
|
|
72
60
|
*/
|
|
73
|
-
export const plansOf = (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const text = textAt(deps.files, join(dir, REPRO_DIR, key));
|
|
79
|
-
if (text === null)
|
|
80
|
-
return [];
|
|
81
|
-
return [
|
|
82
|
-
[
|
|
83
|
-
key,
|
|
84
|
-
posix.basename(key) === LEGACY_PLAN_FILE
|
|
85
|
-
? deps.codec.parseLegacyPlan(text)
|
|
86
|
-
: deps.codec.parsePlan(text, kind),
|
|
87
|
-
],
|
|
88
|
-
];
|
|
61
|
+
export const plansOf = (r, folder, codec) => new Map((r.kind === "read" ? planRefs(r.registry) : []).flatMap((ref) => {
|
|
62
|
+
const state = planStateOf(ref, folder, codec);
|
|
63
|
+
return state === null
|
|
64
|
+
? []
|
|
65
|
+
: [[folderKey(ref.folder, ref.plan), state]];
|
|
89
66
|
}));
|
|
67
|
+
/** The state of the plan `ref` names, read through `folder`; null when it is not there. */
|
|
68
|
+
const planStateOf = ({ folder: of, plan, kind }, folder, codec) => {
|
|
69
|
+
const read = folder.read(of, plan);
|
|
70
|
+
switch (read.kind) {
|
|
71
|
+
case "absent":
|
|
72
|
+
return null;
|
|
73
|
+
case "linked":
|
|
74
|
+
return { kind: "linked", link: read.link };
|
|
75
|
+
case "text":
|
|
76
|
+
return posix.basename(plan) === LEGACY_PLAN_FILE
|
|
77
|
+
? codec.parseLegacyPlan(read.text)
|
|
78
|
+
: codec.parsePlan(read.text, kind);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
90
81
|
/** Every finding of the paper folder `dir`'s study registry — THE function behind both rules. */
|
|
91
82
|
export function studyFindings(dir, deps) {
|
|
92
|
-
const readable = readableFolders(dir, deps);
|
|
93
83
|
const registry = registryOf(dir, deps);
|
|
84
|
+
const folder = studyFolder(dir, deps);
|
|
94
85
|
return judgeStudies({
|
|
95
86
|
folders: studyFolders(dir, deps),
|
|
96
87
|
registry,
|
|
97
88
|
readme: textAt(deps.files, join(dir, REPRO_DIR, README)),
|
|
98
|
-
|
|
99
|
-
plans: plansOf(
|
|
89
|
+
results: resultsOf(registry, folder),
|
|
90
|
+
plans: plansOf(registry, folder, deps.codec),
|
|
100
91
|
});
|
|
101
92
|
}
|
|
102
93
|
/** The problems `repro/study-registered` reports, each its own message id; the text is `studyMessage`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"study-registry-rule.js","sourceRoot":"","sources":["../src/study-registry-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,MAAM,EACN,gBAAgB,EAChB,SAAS,EACT,UAAU,GACX,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,
|
|
1
|
+
{"version":3,"file":"study-registry-rule.js","sourceRoot":"","sources":["../src/study-registry-rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,8 +67,9 @@ decision that is a directory, a decision its own outputs match). A runner calls
|
|
|
67
67
|
npx paperlint studies gate papers/my-paper P1-S03 && ./run.sh
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
For an exploratory study it exits 0 and says it has no gate. A study that declares no mode,
|
|
71
|
-
plan is still a draft,
|
|
70
|
+
For an exploratory study it exits 0 and says it has no gate. A study that declares no mode, whose
|
|
71
|
+
plan is still a draft, or whose id two rows of the registry share (the gate answers for exactly one
|
|
72
|
+
study), is refused.
|
|
72
73
|
|
|
73
74
|
### 3. `repro/outputs-before-decision` — the proof of order a reviewer can re-run
|
|
74
75
|
|
|
@@ -132,17 +133,31 @@ make a command or a rule read, judge or write files that belong to nothing in it
|
|
|
132
133
|
|
|
133
134
|
Every command and rule that reads a study's folder — `studies gate`, `studies index` and `new`,
|
|
134
135
|
`repro/outputs-before-decision`, and `repro/study-registered`'s reads of plans and result files —
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
goes through one reader, `studyFolder` (`src/study-folder.ts`), built once per run from the paper's
|
|
137
|
+
folder over `ListEntries`, which does not follow links. It decides first whether a row's folder may
|
|
138
|
+
be entered at all (`refusal`): its `folder` is one folder directly inside `repro/` (`isFolderName`:
|
|
139
|
+
not empty, not `.` or `..`, no separator or NUL) and not a symbolic link. Such a row is never read
|
|
140
|
+
through: `studies index` and `new` refuse the registry, the gate refuses the study, the order rule
|
|
141
|
+
treats it as no study, and `repro/study-registered` reads its plan and results as missing.
|
|
142
|
+
|
|
143
|
+
A plan's path is held inside its folder by the registry rule's own `isInside`, the same on every
|
|
144
|
+
platform (a backslash is refused outright). The reader walks the path one component at a time, and a
|
|
145
|
+
plan or a result file whose path passes through a symbolic link — the file itself, or a directory
|
|
146
|
+
above it — is never read. The gate refuses it naming the link, the order rule treats the study as
|
|
147
|
+
declaring no mode, and `repro/study-registered` reports it (`bad-plan`, `result-file`) with the link.
|
|
148
|
+
|
|
149
|
+
The decision file is held inside the paper by the schema (`staysInside`) and is judged by the same
|
|
150
|
+
walk (`decisionEntry`, `entryAt`): every component of its path must be a real directory and the last
|
|
151
|
+
a regular file. A directory or a symbolic link as the file itself is refused by the gate and reported
|
|
152
|
+
by the rule (`decision-not-file`); a symbolic link above the file is refused naming that link
|
|
153
|
+
(`decision-linked`).
|
|
154
|
+
|
|
155
|
+
The plan reads, the result-file existence checks and the decision's path check take a `StudyFolder`
|
|
156
|
+
and not `Files`, so a read that follows a link is not what those readers are handed. A study's
|
|
157
|
+
outputs are listed through it too (`StudyFolder.list`).
|
|
158
|
+
|
|
159
|
+
Only a **ready** plan's mode decides anything: the gate refuses a draft, naming its empty fields, and
|
|
160
|
+
the order rule judges no draft — a draft may name no outputs, or an empty decision file.
|
|
146
161
|
|
|
147
162
|
### 6. Legacy plans
|
|
148
163
|
|
|
@@ -220,21 +235,24 @@ Read for this note (2026-10-16):
|
|
|
220
235
|
|
|
221
236
|
## Where each part lives
|
|
222
237
|
|
|
223
|
-
| part
|
|
224
|
-
|
|
|
225
|
-
| the modes, what is an output, which decision a mode waits on
|
|
226
|
-
| the order judge and its messages
|
|
227
|
-
| the index's mode columns and the history they show
|
|
228
|
-
| the index, which columns the lint compares, `isFolderName`
|
|
229
|
-
| the schema of `mode`; readiness by kind
|
|
230
|
-
| git: committed state, first commits, ancestry, shallow
|
|
231
|
-
| what the app asks of git
|
|
232
|
-
| a directory's entries, links not followed
|
|
233
|
-
| the studies with a mode, and
|
|
234
|
-
| the
|
|
235
|
-
| the
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
+
| part | file | layer |
|
|
239
|
+
| ------------------------------------------------------------ | ------------------------------------- | ------- |
|
|
240
|
+
| the modes, what is an output, which decision a mode waits on | `src/domain/study-mode.ts` | domain |
|
|
241
|
+
| the order judge and its messages | `src/domain/decide-first.ts` | domain |
|
|
242
|
+
| the index's mode columns and the history they show | `src/domain/index-columns.ts` | domain |
|
|
243
|
+
| the index, which columns the lint compares, `isFolderName` | `src/domain/study-registry.ts` | domain |
|
|
244
|
+
| the schema of `mode`; readiness by kind | `src/adapters/study-registry/` | adapter |
|
|
245
|
+
| git: committed state, first commits, ancestry, shallow | `src/adapters/git/` | adapter |
|
|
246
|
+
| what the app asks of git | `src/ports/git-history.ts` | port |
|
|
247
|
+
| a directory's entries, links not followed | `src/ports/list-entries.ts` | port |
|
|
248
|
+
| the studies with a mode, and how a decision's path stands | `src/study-modes.ts` | app |
|
|
249
|
+
| what the study readers need of a study's folder | `src/ports/study-folder.ts` | port |
|
|
250
|
+
| what the study readers are handed | `src/ports/study-deps.ts` | port |
|
|
251
|
+
| the one reader of a study's folder, links not followed | `src/study-folder.ts` | app |
|
|
252
|
+
| the order rule | `src/outputs-before-decision-rule.ts` | app |
|
|
253
|
+
| the plans and histories `studies index` and `new` write from | `src/study-index-input.ts` | app |
|
|
254
|
+
| `gate`; `new` and `index` | `src/study-gate.ts`, `src/studies.ts` | app |
|
|
255
|
+
| wiring: git with PATH and HOME, the clock | `src/cli.ts` | root |
|
|
238
256
|
|
|
239
257
|
## What it does not check
|
|
240
258
|
|
|
@@ -36,6 +36,9 @@ A study decided before its run — its plan's `mode` is `decide_before_run`, the
|
|
|
36
36
|
a directory with the commits of anything under it, and records a link rather than the file it
|
|
37
37
|
points to, so no order is read from either. (A path that names the paper's folder
|
|
38
38
|
itself, `.`, or ends in `/` is refused when the plan is parsed.)
|
|
39
|
+
- **`decision-linked`** — the decision path passes through a symbolic link above the file: git
|
|
40
|
+
records the link, not the files behind it, so no order is read from it, and the finding names the
|
|
41
|
+
link.
|
|
39
42
|
- **`shallow`** — the clone is shallow, so every file's first commit is the clone's: no order can be
|
|
40
43
|
read, and the rule says so instead of judging.
|
|
41
44
|
- **`no-history`** — git could not answer (not installed, not a repository).
|
|
@@ -209,7 +209,7 @@ The rule reports:
|
|
|
209
209
|
must meet a requirement the registry does not hold (`unknown-requirement`);
|
|
210
210
|
- a `headline` measurement with no `approval`, or, when the registry names its `author`, one approved
|
|
211
211
|
by anyone else (`headline-approval`);
|
|
212
|
-
- a row's `plan` that is outside its folder, not a file, or not of the plan's shape — and, once the
|
|
212
|
+
- a row's `plan` that is outside its folder, not a file, reached through a symbolic link, or not of the plan's shape — and, once the
|
|
213
213
|
study has run (`running`, `done`, `stopped`, `superseded`), a plan that leaves `question`,
|
|
214
214
|
`population.source`, `population.draw.method` or `primary_outcome` empty, or — for a measurement —
|
|
215
215
|
declares no `mode` or leaves one of its texts empty (`bad-plan`);
|
|
@@ -316,7 +316,8 @@ No rule options.
|
|
|
316
316
|
- Whether the paper cites a study: `in_paper` is declared, not detected.
|
|
317
317
|
- The index's `decision` and `first output` cells: they name commits a squash merge renames.
|
|
318
318
|
- Inside a study folder that is a symbolic link: nothing is read through it, so its plan and its
|
|
319
|
-
result file read as missing.
|
|
319
|
+
result file read as missing. A plan or a result file whose path passes through a link inside the
|
|
320
|
+
folder is not read either, and is reported naming the link (`bad-plan`, `result-file`).
|
|
320
321
|
- What may stand at the top of `repro/` besides the study folders, and whether a study folder has a
|
|
321
322
|
`README.md`: that is [`repro/allowed-files-only`](allowed-files-only.md).
|
|
322
323
|
|
|
@@ -376,7 +376,7 @@ author wrote. Two corpora, 2026-10-03, both measured on headings, before the rul
|
|
|
376
376
|
- **The accepted paper in this repository**, `fixtures/accepted-papers/agenticdev-acm26`: 9
|
|
377
377
|
headings, 6 of them in sentence case, which the vendor's checklist does not accept. The rule
|
|
378
378
|
reports 18 words, every one a true positive, and its fixes write the six corrected
|
|
379
|
-
headings ([`fixtures/accepted-papers/README.md`](
|
|
379
|
+
headings ([`fixtures/accepted-papers/README.md`](https://github.com/zernie/paperlint/blob/main/fixtures/accepted-papers/README.md)).
|
|
380
380
|
Its `\title` and short title, read since 2026-10-04, are already in headline style and add no
|
|
381
381
|
finding.
|
|
382
382
|
- **277 `acmart` sources from arXiv** (authors' versions of papers whose arXiv comment names an ACM
|
package/eslint.config.mjs
CHANGED
|
@@ -212,27 +212,33 @@ export const IO_MODULES = [
|
|
|
212
212
|
"readline/promises",
|
|
213
213
|
].flatMap((m) => [m, `node:${m}`]);
|
|
214
214
|
|
|
215
|
-
/**
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
|
|
215
|
+
/**
|
|
216
|
+
* The npm packages that exist because of ONE external program or format, from `dependencies` in
|
|
217
|
+
* package.json: only that tool's adapter (or `src/cli.ts`, the root) imports them; the domain, a
|
|
218
|
+
* port and the app take a port. A package that exists because of one external tool goes here in the
|
|
219
|
+
* same PR that adds its adapter. A pure utility library (remeda, zod, ts-essentials) needs nothing:
|
|
220
|
+
* the inside may import every package and Node built-in that is not listed here and is not an effect
|
|
221
|
+
* (`IO_MODULES`).
|
|
222
|
+
*/
|
|
223
|
+
export const TOOL_PACKAGES = [
|
|
224
|
+
"@eslint/markdown", // ESLint's Markdown language
|
|
225
|
+
"@eslint/plugin-kit",
|
|
226
|
+
"@retorquere/bibtex-parser", // BibTeX
|
|
227
|
+
"@unified-latex/unified-latex-types", // LaTeX
|
|
228
|
+
"@unified-latex/unified-latex-util-parse",
|
|
229
|
+
"@unified-latex/unified-latex-util-visit",
|
|
230
|
+
"ajv", // JSON Schema
|
|
231
|
+
"eslint",
|
|
232
|
+
"js-yaml", // YAML
|
|
233
|
+
"markdown-it", // Markdown
|
|
234
|
+
"mp4box", // MP4
|
|
235
|
+
"srt-parser-2", // SRT subtitles
|
|
236
|
+
"typescript", // the compiler API
|
|
237
|
+
"unpdf", // PDF
|
|
238
|
+
"vigiles", // the agent harness
|
|
239
|
+
].flatMap((m) => [m, `${m}/**`]);
|
|
233
240
|
|
|
234
241
|
const ORIGINS = ["external", "core"];
|
|
235
|
-
const modules = (source) => ({ to: { module: { origin: ORIGINS, source } } });
|
|
236
242
|
const elements = (...types) => ({ element: { types: { anyOf: types } } });
|
|
237
243
|
const APP = { file: { categories: "app" } };
|
|
238
244
|
const ROOT = { file: { categories: "root" } };
|
|
@@ -337,14 +343,20 @@ export const layerBoundaries = (root) => ({
|
|
|
337
343
|
allow: { to: elements("js-module") },
|
|
338
344
|
},
|
|
339
345
|
// ── libraries and built-ins (the plugin calls Node built-ins origin "core") ──
|
|
346
|
+
// Everyone may import any package and built-in …
|
|
340
347
|
{
|
|
341
|
-
from: elements("domain", "port"),
|
|
342
|
-
allow:
|
|
348
|
+
from: [elements("domain", "port", "adapter"), APP, ROOT],
|
|
349
|
+
allow: { to: { module: { origin: ORIGINS } } },
|
|
343
350
|
},
|
|
344
|
-
|
|
351
|
+
// … but the inside — not an adapter, not the root — not a package that exists because of
|
|
352
|
+
// one external tool (TOOL_PACKAGES). Effects are axis B's, below.
|
|
345
353
|
{
|
|
346
|
-
from: [elements("
|
|
347
|
-
|
|
354
|
+
from: [elements("domain", "port"), APP],
|
|
355
|
+
disallow: {
|
|
356
|
+
to: { module: { origin: "external", source: TOOL_PACKAGES } },
|
|
357
|
+
},
|
|
358
|
+
message:
|
|
359
|
+
"{{ dependency.source }} exists because of one external tool or format: only that tool's adapter (src/adapters/<tool>/) and src/cli.ts import it; this file takes a port (src/CLAUDE.md, layers). A new package of that kind is listed in TOOL_PACKAGES in the PR that adds its adapter.",
|
|
348
360
|
},
|
|
349
361
|
// Tests may import anything, fakes included …
|
|
350
362
|
{
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: Grade how a paper reads and fix it sentence by sentence. Use when a
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:2b7193c739a374e6 compiled from skills/grade-paper-writing/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# grade-paper-writing — grade how the paper READS, then fix it sentence by sentence
|
|
10
10
|
|
|
@@ -223,7 +223,9 @@ is what happened.
|
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
It measures the prose a reader sees — the files the last build read spliced in, no preamble,
|
|
226
|
-
comments, floats or inline `.bib` — so its rates are the paper's, not the source file's
|
|
226
|
+
comments, floats or inline `.bib` — so its rates are the paper's, not the source file's — and, on
|
|
227
|
+
its own line, the figure captions that float outside that prose, flagging a caption sentence over
|
|
228
|
+
40 words or a caption over 100. Run
|
|
227
229
|
`npx paperlint build` first: with no current build record it reads `paper.tex` alone and says so. The skill is what must ACT on it.
|
|
228
230
|
Its thresholds are sourced; a FLAG means the text sits above a published human or machine
|
|
229
231
|
baseline, which is a defect with a number attached, not an opinion.
|
|
@@ -229,7 +229,9 @@ is what happened.
|
|
|
229
229
|
\`\`\`
|
|
230
230
|
|
|
231
231
|
It measures the prose a reader sees — the files the last build read spliced in, no preamble,
|
|
232
|
-
comments, floats or inline \`.bib\` — so its rates are the paper's, not the source file's
|
|
232
|
+
comments, floats or inline \`.bib\` — so its rates are the paper's, not the source file's — and, on
|
|
233
|
+
its own line, the figure captions that float outside that prose, flagging a caption sentence over
|
|
234
|
+
40 words or a caption over 100. Run
|
|
233
235
|
\`npx paperlint build\` first: with no current build record it reads \`paper.tex\` alone and says so. The skill is what must ACT on it.
|
|
234
236
|
Its thresholds are sourced; a FLAG means the text sits above a published human or machine
|
|
235
237
|
baseline, which is a defect with a number attached, not an opinion.
|