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.
Files changed (111) hide show
  1. package/dist/CLAUDE.md.spec.d.ts.map +1 -1
  2. package/dist/CLAUDE.md.spec.js +6 -3
  3. package/dist/CLAUDE.md.spec.js.map +1 -1
  4. package/dist/adapters/latex/captions.d.ts +9 -0
  5. package/dist/adapters/latex/captions.d.ts.map +1 -0
  6. package/dist/adapters/latex/captions.js +40 -0
  7. package/dist/adapters/latex/captions.js.map +1 -0
  8. package/dist/adapters/latex/index.d.ts +1 -0
  9. package/dist/adapters/latex/index.d.ts.map +1 -1
  10. package/dist/adapters/latex/index.js +1 -0
  11. package/dist/adapters/latex/index.js.map +1 -1
  12. package/dist/adapters/latex/parse.d.ts.map +1 -1
  13. package/dist/adapters/latex/parse.js +6 -0
  14. package/dist/adapters/latex/parse.js.map +1 -1
  15. package/dist/adapters/node/list-entries.io.d.ts.map +1 -1
  16. package/dist/adapters/node/list-entries.io.js +3 -1
  17. package/dist/adapters/node/list-entries.io.js.map +1 -1
  18. package/dist/domain/decide-first.d.ts +11 -1
  19. package/dist/domain/decide-first.d.ts.map +1 -1
  20. package/dist/domain/decide-first.js +6 -0
  21. package/dist/domain/decide-first.js.map +1 -1
  22. package/dist/domain/index-columns.d.ts.map +1 -1
  23. package/dist/domain/index-columns.js +2 -0
  24. package/dist/domain/index-columns.js.map +1 -1
  25. package/dist/domain/study-registry.d.ts +65 -8
  26. package/dist/domain/study-registry.d.ts.map +1 -1
  27. package/dist/domain/study-registry.js +63 -19
  28. package/dist/domain/study-registry.js.map +1 -1
  29. package/dist/outputs-before-decision-rule.d.ts +1 -1
  30. package/dist/outputs-before-decision-rule.d.ts.map +1 -1
  31. package/dist/outputs-before-decision-rule.js +21 -17
  32. package/dist/outputs-before-decision-rule.js.map +1 -1
  33. package/dist/ports/list-entries.d.ts +7 -4
  34. package/dist/ports/list-entries.d.ts.map +1 -1
  35. package/dist/ports/study-deps.d.ts +14 -0
  36. package/dist/ports/study-deps.d.ts.map +1 -0
  37. package/dist/ports/study-deps.js +2 -0
  38. package/dist/ports/study-deps.js.map +1 -0
  39. package/dist/ports/study-folder.d.ts +43 -0
  40. package/dist/ports/study-folder.d.ts.map +1 -0
  41. package/dist/ports/study-folder.js +2 -0
  42. package/dist/ports/study-folder.js.map +1 -0
  43. package/dist/presets.d.ts +1 -2
  44. package/dist/presets.d.ts.map +1 -1
  45. package/dist/presets.js +1 -2
  46. package/dist/presets.js.map +1 -1
  47. package/dist/studies.d.ts.map +1 -1
  48. package/dist/studies.js +10 -7
  49. package/dist/studies.js.map +1 -1
  50. package/dist/study-folder.d.ts +14 -0
  51. package/dist/study-folder.d.ts.map +1 -0
  52. package/dist/study-folder.js +123 -0
  53. package/dist/study-folder.js.map +1 -0
  54. package/dist/study-gate.d.ts +1 -1
  55. package/dist/study-gate.d.ts.map +1 -1
  56. package/dist/study-gate.js +65 -28
  57. package/dist/study-gate.js.map +1 -1
  58. package/dist/study-index-input.d.ts +1 -1
  59. package/dist/study-index-input.d.ts.map +1 -1
  60. package/dist/study-index-input.js +8 -3
  61. package/dist/study-index-input.js.map +1 -1
  62. package/dist/study-modes.d.ts +38 -27
  63. package/dist/study-modes.d.ts.map +1 -1
  64. package/dist/study-modes.js +42 -53
  65. package/dist/study-modes.js.map +1 -1
  66. package/dist/study-registry-rule.d.ts +2 -6
  67. package/dist/study-registry-rule.d.ts.map +1 -1
  68. package/dist/study-registry-rule.js +32 -41
  69. package/dist/study-registry-rule.js.map +1 -1
  70. package/docs/design/study-modes.md +46 -28
  71. package/docs/rules/repro/outputs-before-decision.md +3 -0
  72. package/docs/rules/repro/study-registered.md +3 -2
  73. package/docs/rules/tex/heading-case.md +1 -1
  74. package/eslint.config.mjs +36 -24
  75. package/package.json +1 -1
  76. package/skills/grade-paper-writing/SKILL.md +4 -2
  77. package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -1
  78. package/skills/grade-paper-writing/__snapshots__/prose-lint.test.ts.snap +16 -3
  79. package/skills/grade-paper-writing/prose-lint.mjs +127 -95
  80. package/skills/grade-paper-writing/prose-lint.test.ts +119 -10
  81. package/src/CLAUDE.md +7 -4
  82. package/src/CLAUDE.md.spec.ts +6 -3
  83. package/src/adapters/latex/captions.test.ts +57 -0
  84. package/src/adapters/latex/captions.ts +56 -0
  85. package/src/adapters/latex/index.ts +1 -0
  86. package/src/adapters/latex/parse.ts +7 -0
  87. package/src/adapters/node/list-entries.io.ts +2 -1
  88. package/src/adapters/node/list-entries.test.ts +18 -0
  89. package/src/domain/decide-first.test.ts +33 -0
  90. package/src/domain/decide-first.ts +16 -1
  91. package/src/domain/index-columns.test.ts +49 -4
  92. package/src/domain/index-columns.ts +2 -0
  93. package/src/domain/study-measurements.test.ts +7 -3
  94. package/src/domain/study-registry.test.ts +34 -6
  95. package/src/domain/study-registry.ts +123 -31
  96. package/src/outputs-before-decision-rule.test.ts +47 -2
  97. package/src/outputs-before-decision-rule.ts +31 -22
  98. package/src/ports/list-entries.ts +7 -4
  99. package/src/ports/study-deps.ts +14 -0
  100. package/src/ports/study-folder.ts +38 -0
  101. package/src/presets.ts +1 -2
  102. package/src/studies-index-modes.test.ts +2 -2
  103. package/src/studies.ts +19 -10
  104. package/src/study-folder.test.ts +237 -0
  105. package/src/study-folder.ts +176 -0
  106. package/src/study-gate.test.ts +158 -7
  107. package/src/study-gate.ts +127 -54
  108. package/src/study-index-input.ts +12 -4
  109. package/src/study-modes.ts +67 -80
  110. package/src/study-registry-rule.test.ts +87 -0
  111. package/src/study-registry-rule.ts +58 -66
@@ -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 the files under a study's folder. What `repro/outputs-before-decision`, `studies gate`
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 { basename, dirname, join } from "node:path";
8
- import { callerPath } from "./caller-path.js";
9
- import { REPRO_DIR } from "./domain/repro-layout.js";
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 `p` is on disk, read from its directory's entries without following a link: a decision must
19
- * be a regular file — git answers for a directory with the commits of anything under it, and for a
20
- * link with the link, not the file it points to.
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 onDisk = (p, deps) => {
23
- const e = deps
24
- .entries(callerPath(dirname(p)))
25
- .find((x) => x.name === basename(p));
26
- return e === undefined ? "absent" : ON_DISK[e.kind];
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
- * Every file under `dir`, relative to it, `/`-separated; none when it is not a directory. A symbolic
30
- * link is listed as a file — git stores it as one — and never followed: a link to the folder itself
31
- * would recurse forever, and one to a tree outside would bring its files into the study.
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 modeStudyOf = (paper, s, deps, readable) => {
33
+ export const readModeStudy = (s, folder, codec) => {
43
34
  const plan = s.plan;
44
- const text = plan === undefined ||
45
- !readable(s.folder) ||
46
- !isInside(plan) ||
47
- plan.includes("\0")
48
- ? null
49
- : textAt(deps.files, join(paper, REPRO_DIR, s.folder, plan));
50
- if (plan === undefined || text === null)
51
- return null;
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 null;
44
+ return { kind: "none" };
55
45
  const empty = parsed.kind === "draft" ? parsed.empty : [];
56
- return { row: s, plan, mode: parsed.plan.mode, empty };
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 paper folder `paper` whose plan declares a mode, in registry order. A plan that
60
- * does not parse is `repro/study-registered`'s to report; here it declares nothing.
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 = (paper, deps) => {
63
- const r = registryOf(paper, deps);
64
- const readable = readableFolders(paper, deps);
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
@@ -1 +1 @@
1
- {"version":3,"file":"study-modes.js","sourceRoot":"","sources":["../src/study-modes.ts"],"names":[],"mappings":"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"}
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: (dir: string, r: RegistryState, deps: Pick<StudyRegistryDeps, "files" | "codec">, readable: (folder: string) => boolean) => ReadonlyMap<string, PlanState>;
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":"AA2BA,OAAO,EAQL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,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;AAM1E,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;AAcD;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK,MAAM,EACX,MAAM,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,KACvC,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAG9B,CAAC;AAEF,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;AAmCD;;;GAGG;AACH,eAAO,MAAM,OAAO,GAClB,KAAK,MAAM,EACX,GAAG,aAAa,EAChB,MAAM,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,OAAO,CAAC,EAChD,UAAU,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,KACpC,WAAW,CAAC,MAAM,EAAE,SAAS,CAkB7B,CAAC;AAEJ,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"}
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, isFolderName, judgeStudies, planRefs, studyMessage, } from "./domain/study-registry.js";
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
- /** Each `<folder>/<result_file>` of `r` that is a file. */
62
- const existingResults = (dir, r, files, readable) => new Set((r.kind === "read" ? r.registry.studies : [])
63
- // A row whose folder is not one inside `repro/`, or is a link, is never read through.
64
- .filter((s) => readable(s.folder))
65
- .flatMap((s) => s.result_file === undefined ? [] : [`${s.folder}/${s.result_file}`])
66
- // A NUL is no path the disk can hold, and probing one throws: such a row reads as missing.
67
- .filter((p) => !p.includes("\0") &&
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 = (dir, r, deps, readable) => new Map((r.kind === "read" ? planRefs(r.registry) : [])
74
- // A NUL is no path the disk can hold, and probing one throws: such a plan reads as missing. A
75
- // folder that is a link is never read through.
76
- .filter(({ key, folder }) => !key.includes("\0") && readable(folder))
77
- .flatMap(({ key, kind }) => {
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
- existingResults: existingResults(dir, registry, deps.files, readable),
99
- plans: plansOf(dir, registry, deps, readable),
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":"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"}
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, or whose
71
- plan is still a draft, is refused.
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
- reads a row only through one predicate, `readableFolders` (`src/study-registry-rule.ts`): its
136
- `folder` is one folder directly inside `repro/` (`isFolderName`: not empty, not `.` or `..`, no
137
- separator or NUL) and not a symbolic link (`ListEntries`, read without following links). Such a row
138
- is never read through: `studies index` and `new` refuse the registry, the gate refuses the study,
139
- the order rule treats it as no study, and `repro/study-registered` reads its plan and results as
140
- missing. A plan's path is held inside its folder by the registry rule's own `isInside`, the same on
141
- every platform (a backslash is refused outright); the decision file is held inside the paper by the
142
- schema (`staysInside`) and must be a regular file on disk — a directory or a symbolic link is refused
143
- by the gate and reported by the rule (`decision-not-file`). Only a **ready** plan's mode decides
144
- anything: the gate refuses a draft, naming its empty fields, and the order rule judges no draft — a
145
- draft may name no outputs, or an empty decision file.
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 | file | layer |
224
- | ------------------------------------------------------------- | ------------------------------------- | ------- |
225
- | the modes, what is an output, which decision a mode waits on | `src/domain/study-mode.ts` | domain |
226
- | the order judge and its messages | `src/domain/decide-first.ts` | domain |
227
- | the index's mode columns and the history they show | `src/domain/index-columns.ts` | domain |
228
- | the index, which columns the lint compares, `isFolderName` | `src/domain/study-registry.ts` | domain |
229
- | the schema of `mode`; readiness by kind | `src/adapters/study-registry/` | adapter |
230
- | git: committed state, first commits, ancestry, shallow | `src/adapters/git/` | adapter |
231
- | what the app asks of git | `src/ports/git-history.ts` | port |
232
- | a directory's entries, links not followed | `src/ports/list-entries.ts` | port |
233
- | the studies with a mode, and the files under a study's folder | `src/study-modes.ts` | app |
234
- | the order rule | `src/outputs-before-decision-rule.ts` | app |
235
- | the plans and histories `studies index` and `new` write from | `src/study-index-input.ts` | app |
236
- | `gate`; `new` and `index` | `src/study-gate.ts`, `src/studies.ts` | app |
237
- | wiring: git with PATH and HOME, the clock | `src/cli.ts` | root |
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`](../../../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
- /** What the inside may import from outside the repository: types, pure path arithmetic, hashing. */
216
- export const INSIDE_EXTERNALS = [
217
- "ts-essentials",
218
- "path",
219
- "node:path",
220
- "crypto",
221
- "node:crypto",
222
- ];
223
- /** What the flat app files may import besides: Node's pure helpers, measured on the first v7 run. */
224
- export const APP_EXTERNALS = [
225
- ...INSIDE_EXTERNALS,
226
- "util",
227
- "node:util",
228
- "url",
229
- "node:url",
230
- "module",
231
- "node:module",
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: modules(INSIDE_EXTERNALS),
348
+ from: [elements("domain", "port", "adapter"), APP, ROOT],
349
+ allow: { to: { module: { origin: ORIGINS } } },
343
350
  },
344
- { from: APP, allow: modules(APP_EXTERNALS) },
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("adapter"), ROOT],
347
- allow: { to: { module: { origin: ORIGINS } } },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "10.4.0",
3
+ "version": "10.4.1",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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:1897016b69f3f9b8 compiled from skills/grade-paper-writing/SKILL.md.spec.ts -->
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. Run
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. Run
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.