paperlint 10.3.0 → 10.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +15 -4
- package/README.md +1 -0
- 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/git/index.d.ts +10 -0
- package/dist/adapters/git/index.d.ts.map +1 -0
- package/dist/adapters/git/index.js +151 -0
- package/dist/adapters/git/index.js.map +1 -0
- 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/memory/index.d.ts +6 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +8 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/node/index.d.ts +1 -0
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/list-entries.io.d.ts +3 -0
- package/dist/adapters/node/list-entries.io.d.ts.map +1 -0
- package/dist/adapters/node/list-entries.io.js +26 -0
- package/dist/adapters/node/list-entries.io.js.map +1 -0
- package/dist/adapters/study-registry/index.d.ts +70 -10
- package/dist/adapters/study-registry/index.d.ts.map +1 -1
- package/dist/adapters/study-registry/index.js +84 -10
- package/dist/adapters/study-registry/index.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +33 -3
- package/dist/cli.js.map +1 -1
- package/dist/domain/decide-first.d.ts +110 -0
- package/dist/domain/decide-first.d.ts.map +1 -0
- package/dist/domain/decide-first.js +108 -0
- package/dist/domain/decide-first.js.map +1 -0
- package/dist/domain/index-columns.d.ts +45 -0
- package/dist/domain/index-columns.d.ts.map +1 -0
- package/dist/domain/index-columns.js +64 -0
- package/dist/domain/index-columns.js.map +1 -0
- package/dist/domain/study-mode.d.ts +37 -0
- package/dist/domain/study-mode.d.ts.map +1 -0
- package/dist/domain/study-mode.js +45 -0
- package/dist/domain/study-mode.js.map +1 -0
- package/dist/domain/study-registry.d.ts +116 -12
- package/dist/domain/study-registry.d.ts.map +1 -1
- package/dist/domain/study-registry.js +164 -44
- package/dist/domain/study-registry.js.map +1 -1
- package/dist/outputs-before-decision-rule.d.ts +17 -0
- package/dist/outputs-before-decision-rule.d.ts.map +1 -0
- package/dist/outputs-before-decision-rule.js +94 -0
- package/dist/outputs-before-decision-rule.js.map +1 -0
- package/dist/ports/git-history.d.ts +44 -0
- package/dist/ports/git-history.d.ts.map +1 -0
- package/dist/ports/git-history.js +2 -0
- package/dist/ports/git-history.js.map +1 -0
- package/dist/ports/list-entries.d.ts +20 -0
- package/dist/ports/list-entries.d.ts.map +1 -0
- package/dist/ports/list-entries.js +2 -0
- package/dist/ports/list-entries.js.map +1 -0
- 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/ports/study-registry-codec.d.ts +6 -3
- package/dist/ports/study-registry-codec.d.ts.map +1 -1
- 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 +4 -0
- package/dist/studies.d.ts.map +1 -1
- package/dist/studies.js +52 -21
- 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 +14 -0
- package/dist/study-gate.d.ts.map +1 -0
- package/dist/study-gate.js +132 -0
- package/dist/study-gate.js.map +1 -0
- package/dist/study-index-input.d.ts +5 -0
- package/dist/study-index-input.d.ts.map +1 -0
- package/dist/study-index-input.js +18 -0
- package/dist/study-index-input.js.map +1 -0
- package/dist/study-modes.d.ts +56 -0
- package/dist/study-modes.d.ts.map +1 -0
- package/dist/study-modes.js +59 -0
- package/dist/study-modes.js.map +1 -0
- package/dist/study-registry-rule.d.ts +13 -1
- package/dist/study-registry-rule.d.ts.map +1 -1
- package/dist/study-registry-rule.js +33 -21
- package/dist/study-registry-rule.js.map +1 -1
- package/dist/study-run.d.ts +19 -0
- package/dist/study-run.d.ts.map +1 -0
- package/dist/study-run.js +9 -0
- package/dist/study-run.js.map +1 -0
- package/docs/design/study-modes.md +265 -0
- package/docs/design/study-registry.md +6 -0
- package/docs/e2e.md +8 -4
- package/docs/layout.md +22 -19
- package/docs/rules/repro/outputs-before-decision.md +122 -0
- package/docs/rules/repro/study-registered.md +49 -15
- package/docs/rules/tex/heading-case.md +1 -1
- package/docs/rules.md +54 -53
- package/docs/testing.md +1 -0
- package/eslint.config.mjs +36 -24
- package/package.json +2 -1
- package/scripts/check.ts +6 -0
- package/skills/camera-ready/SKILL.md +6 -1
- package/skills/camera-ready/SKILL.md.spec.ts +5 -0
- package/skills/draft-paper/SKILL.md +11 -3
- package/skills/draft-paper/SKILL.md.spec.ts +10 -2
- 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/skills/harden-paper/SKILL.md +12 -2
- package/skills/harden-paper/SKILL.md.spec.ts +11 -1
- package/skills/osf-artifact-upload/SKILL.md +7 -1
- package/skills/osf-artifact-upload/SKILL.md.spec.ts +6 -0
- package/skills/pc-panel-review/SKILL.md +10 -3
- package/skills/pc-panel-review/SKILL.md.spec.ts +9 -2
- package/skills/tighten-paper/SKILL.md +5 -1
- package/skills/tighten-paper/SKILL.md.spec.ts +4 -0
- package/src/CLAUDE.md +7 -4
- package/src/CLAUDE.md.spec.ts +6 -3
- package/src/adapters/git/history.test.ts +121 -0
- package/src/adapters/git/index.test.ts +268 -0
- package/src/adapters/git/index.ts +227 -0
- 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/memory/index.test.ts +15 -1
- package/src/adapters/memory/index.ts +13 -0
- package/src/adapters/node/index.ts +1 -0
- package/src/adapters/node/list-entries.io.ts +27 -0
- package/src/adapters/node/list-entries.test.ts +61 -0
- package/src/adapters/study-registry/index.test.ts +12 -9
- package/src/adapters/study-registry/index.ts +130 -24
- package/src/adapters/study-registry/mode.test.ts +162 -0
- package/src/cli.ts +47 -3
- package/src/domain/decide-first.test.ts +291 -0
- package/src/domain/decide-first.ts +258 -0
- package/src/domain/index-columns.test.ts +261 -0
- package/src/domain/index-columns.ts +107 -0
- package/src/domain/study-measurements.test.ts +16 -8
- package/src/domain/study-mode.test.ts +55 -0
- package/src/domain/study-mode.ts +78 -0
- package/src/domain/study-registry.test.ts +72 -37
- package/src/domain/study-registry.ts +281 -50
- package/src/new-paper.test.ts +5 -1
- package/src/outputs-before-decision-rule.test.ts +251 -0
- package/src/outputs-before-decision-rule.ts +133 -0
- package/src/ports/git-history.ts +44 -0
- package/src/ports/list-entries.ts +21 -0
- package/src/ports/study-deps.ts +14 -0
- package/src/ports/study-folder.ts +38 -0
- package/src/ports/study-registry-codec.ts +10 -3
- package/src/presets.ts +1 -2
- package/src/rule-cases.test.ts +78 -3
- package/src/studies-index-modes.test.ts +129 -0
- package/src/studies.test.ts +35 -11
- package/src/studies.ts +96 -26
- package/src/study-folder.test.ts +237 -0
- package/src/study-folder.ts +176 -0
- package/src/study-gate.test.ts +467 -0
- package/src/study-gate.ts +234 -0
- package/src/study-index-input.ts +31 -0
- package/src/study-modes.ts +107 -0
- package/src/study-registry-rule.test.ts +285 -11
- package/src/study-registry-rule.ts +64 -35
- package/src/study-run.ts +21 -0
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paperlint",
|
|
3
|
-
"version": "10.
|
|
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",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"test:e2e": "vitest run --project 'e2e-*'",
|
|
12
12
|
"test:e2e:install": "vitest run --project e2e-install",
|
|
13
13
|
"test:e2e:tex": "vitest run --project e2e-tex",
|
|
14
|
+
"test:e2e:studies": "vitest run --project e2e-studies",
|
|
14
15
|
"coverage": "NODE_OPTIONS=\"--import=./test/coverage-src.ts $NODE_OPTIONS\" c8 --check-coverage npm test --",
|
|
15
16
|
"lint": "eslint .",
|
|
16
17
|
"check": "node scripts/check.ts",
|
package/scripts/check.ts
CHANGED
|
@@ -173,6 +173,12 @@ export const GATES: readonly Gate[] = [
|
|
|
173
173
|
script: "test:e2e:install",
|
|
174
174
|
vitest: true,
|
|
175
175
|
},
|
|
176
|
+
{
|
|
177
|
+
name: "studies e2e — a study decided before its run, in a real git repository",
|
|
178
|
+
job: "gates",
|
|
179
|
+
script: "test:e2e:studies",
|
|
180
|
+
vitest: true,
|
|
181
|
+
},
|
|
176
182
|
{
|
|
177
183
|
name: "TeX e2e — real TeX Live: paperlint toolchain, a real pdflatex build of every fixture, the real banal",
|
|
178
184
|
job: "build-e2e",
|
|
@@ -4,7 +4,7 @@ description: On acceptance, turn the anonymized-for-review paper into the de-ano
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, WebSearch, WebFetch]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:66444fd7ce0e56a0 compiled from skills/camera-ready/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# camera-ready — de-anonymize an accepted paper into the final proceedings version
|
|
10
10
|
|
|
@@ -73,6 +73,11 @@ The review artifact was an OSF anonymized view-only link. The camera-ready needs
|
|
|
73
73
|
or `\doi{}`.
|
|
74
74
|
- Keep the artifact **self-checking** (it still recomputes every headline number and exits non-zero on
|
|
75
75
|
drift) — de-anonymizing it doesn't excuse it from reproducing the paper.
|
|
76
|
+
- **Ship each study's evidence of its mode**: its `plan.json`, its decision file, and
|
|
77
|
+
`repro/README.md` with the index regenerated (`npx paperlint studies index <paper-dir>`) — each study's mode,
|
|
78
|
+
decision commit and first output. The public repo keeps the **whole git history** — the order of
|
|
79
|
+
decision and outputs is read from it (`repro/outputs-before-decision`), and a squashed or shallow
|
|
80
|
+
copy loses it.
|
|
76
81
|
|
|
77
82
|
## 3. Responsible disclosure (security papers — BEFORE public release)
|
|
78
83
|
For the AISec "Safety Theater" paper the studied hooks are named-but-anonymized, and disclosure to the
|
|
@@ -88,6 +88,11 @@ The review artifact was an OSF anonymized view-only link. The camera-ready needs
|
|
|
88
88
|
or \`\\doi{}\`.
|
|
89
89
|
- Keep the artifact **self-checking** (it still recomputes every headline number and exits non-zero on
|
|
90
90
|
drift) — de-anonymizing it doesn't excuse it from reproducing the paper.
|
|
91
|
+
- **Ship each study's evidence of its mode**: its \`plan.json\`, its decision file, and
|
|
92
|
+
\`repro/README.md\` with the index regenerated (\`npx paperlint studies index <paper-dir>\`) — each study's mode,
|
|
93
|
+
decision commit and first output. The public repo keeps the **whole git history** — the order of
|
|
94
|
+
decision and outputs is read from it (\`repro/outputs-before-decision\`), and a squashed or shallow
|
|
95
|
+
copy loses it.
|
|
91
96
|
|
|
92
97
|
## 3. Responsible disclosure (security papers — BEFORE public release)
|
|
93
98
|
For the AISec "Safety Theater" paper the studied hooks are named-but-anonymized, and disclosure to the
|
|
@@ -4,7 +4,7 @@ description: Use when the numbers exist and it's time to WRITE the paper — "dr
|
|
|
4
4
|
allowed-tools: [Read, Write, Edit, Grep, Glob, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:a68aebea0917e4a6 compiled from skills/draft-paper/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# draft-paper — prose that survives review and gets cited
|
|
10
10
|
|
|
@@ -33,9 +33,17 @@ number in the body stays in the body**.
|
|
|
33
33
|
study, and — for security — the explicit threat model (attacker goals/capabilities/knowledge). A
|
|
34
34
|
fuzzy threat model is the fastest reject at a security venue.
|
|
35
35
|
4. **Method** — the reusable core. How the measurement/benchmark is *constructed* so someone else can
|
|
36
|
-
run it on a different target. This is the part that gets cited; give it the most care.
|
|
36
|
+
run it on a different target. This is the part that gets cited; give it the most care. **It states
|
|
37
|
+
each study's mode from the index, never from memory:** run `npx paperlint studies index <paper-dir>`, then read
|
|
38
|
+
each reported study's `mode`, `decision` and `first output` columns in `repro/README.md` and
|
|
39
|
+
write one sentence per study — decided before its run, with the decision's commit and date (and
|
|
40
|
+
its first output's, which came after); an exploratory study named exploratory, with its reason.
|
|
41
|
+
A study whose `decision` reads `not committed` was not decided before its run: say so.
|
|
37
42
|
5. **Results** — the numbers from `build-benchmark`, each tied to a claim. Report ranges/CIs, not
|
|
38
|
-
rounded maxes. One figure or table carries the paper — make it legible standalone.
|
|
43
|
+
rounded maxes. One figure or table carries the paper — make it legible standalone. A number from
|
|
44
|
+
an exploratory study says "exploratory" where it is reported; "pre-registered" or "decided before"
|
|
45
|
+
only for a study the index shows as `decide_before_run` with its decision committed before its
|
|
46
|
+
first output.
|
|
39
47
|
6. **Threats to validity** — construct / internal / external, honestly (see below). This section is a
|
|
40
48
|
trust signal, not a confession.
|
|
41
49
|
7. **Related work** — position against the *nearest* neighbor explicitly ("X measures A; we measure B
|
|
@@ -39,9 +39,17 @@ number in the body stays in the body**.
|
|
|
39
39
|
study, and — for security — the explicit threat model (attacker goals/capabilities/knowledge). A
|
|
40
40
|
fuzzy threat model is the fastest reject at a security venue.
|
|
41
41
|
4. **Method** — the reusable core. How the measurement/benchmark is *constructed* so someone else can
|
|
42
|
-
run it on a different target. This is the part that gets cited; give it the most care.
|
|
42
|
+
run it on a different target. This is the part that gets cited; give it the most care. **It states
|
|
43
|
+
each study's mode from the index, never from memory:** run \`npx paperlint studies index <paper-dir>\`, then read
|
|
44
|
+
each reported study's \`mode\`, \`decision\` and \`first output\` columns in \`repro/README.md\` and
|
|
45
|
+
write one sentence per study — decided before its run, with the decision's commit and date (and
|
|
46
|
+
its first output's, which came after); an exploratory study named exploratory, with its reason.
|
|
47
|
+
A study whose \`decision\` reads \`not committed\` was not decided before its run: say so.
|
|
43
48
|
5. **Results** — the numbers from \`build-benchmark\`, each tied to a claim. Report ranges/CIs, not
|
|
44
|
-
rounded maxes. One figure or table carries the paper — make it legible standalone.
|
|
49
|
+
rounded maxes. One figure or table carries the paper — make it legible standalone. A number from
|
|
50
|
+
an exploratory study says "exploratory" where it is reported; "pre-registered" or "decided before"
|
|
51
|
+
only for a study the index shows as \`decide_before_run\` with its decision committed before its
|
|
52
|
+
first output.
|
|
45
53
|
6. **Threats to validity** — construct / internal / external, honestly (see below). This section is a
|
|
46
54
|
trust signal, not a confession.
|
|
47
55
|
7. **Related work** — position against the *nearest* neighbor explicitly ("X measures A; we measure B
|
|
@@ -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.
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
+
exports[`--flags-only: a metric over its threshold (not a caption) is a finding too, and the report flags the same line 1`] = `
|
|
4
|
+
"✍️ prose-lint — paper.tex — 1 finding(s):
|
|
5
|
+
sentences whose main claim is a negation: 75%
|
|
6
|
+
run prose-lint.mjs on the file without --flags-only for every metric
|
|
7
|
+
"
|
|
8
|
+
`;
|
|
9
|
+
|
|
3
10
|
exports[`--flags-only: an over-long caption flags and exits 1 1`] = `
|
|
4
11
|
"✍️ prose-lint — paper.tex — 2 finding(s):
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
all metadiscourse/1000w 363.6 (Hyland RA baseline 64.9, warn >90)
|
|
13
|
+
figure captions over the limit: 2
|
|
14
|
+
> figures/fig1.tex: caption sentence of 46 words (warn >40) — "Big. word0 word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12 word13 word14 word15 wor…"
|
|
15
|
+
> figures/fig1.tex: caption is 106 words (warn >100) — a caption this long is a section under a picture
|
|
16
|
+
run prose-lint.mjs on the file without --flags-only for every metric
|
|
8
17
|
"
|
|
9
18
|
`;
|
|
10
19
|
|
|
@@ -37,6 +46,7 @@ ok all metadiscourse/1000w 0 (Hyland RA baseline 64.9, warn >90)
|
|
|
37
46
|
ok "rather than"/instead-of/10,000w 0 (warn >25; not epanorthosis, no published baseline)
|
|
38
47
|
ok sentences ending on a cross-ref/citation/hedge (wasted stress position): 0
|
|
39
48
|
ok sentences whose main claim is a negation: 0%
|
|
49
|
+
ok figure captions over the limit: 0
|
|
40
50
|
topic positions (whose story is this?): "a rendered page." | "it was hyphenated across a" | "next page text here."
|
|
41
51
|
|
|
42
52
|
(thresholds and their sources are in THRESHOLDS at the top of this file)
|
|
@@ -64,6 +74,9 @@ FLAG sentences ending on a cross-ref/citation/hedge (wasted stress position): 12
|
|
|
64
74
|
> …], and that results from a handful of runs need honest statistics [1].
|
|
65
75
|
> …g code [1], and models that memorize answers instead of reasoning [1].
|
|
66
76
|
ok sentences whose main claim is a negation: 37%
|
|
77
|
+
FLAG figure captions over the limit: 2
|
|
78
|
+
> paper.tex: caption sentence of 52 words (warn >40) — "Two things make it different from how efficiency tools are usually measured: the tool is installed the way a u…"
|
|
79
|
+
> paper.tex: caption sentence of 44 words (warn >40) — "The advertised 65% is an output-token reduction; because output is only math of the dollar cost, even a perfec…"
|
|
67
80
|
topic positions (whose story is this?): "a fast-growing ecosystem of plugins," | "the most popular single artifact---a" | "we show that such claims," | "efficiency tools are validated on" | "we introduce a reproducible, cost-aware," | "across 140 runs on two" | "output falls far short of" | "the ceiling is also structural:" | "we argue that trustworthy evaluation" | "ai coding agents and their" | "built on the reasoning-and-acting loops" | "assistants of this kind are" | "an unverified performance claim about" | "while the community has invested" | "we focus on the most" | "the flagship example is a" | "naming the commits matters more" | "two separate claims are bundled" | "the first is that the" | "that one has already been" | "the second claim is the" | "nobody had measured it." | "it fails for a reason" | "this paper is about the" | "rq: when a token-efficiency tool" | "the first half is measured," | "our contribution is not that" | "we identify why the headline" | "we build a cost-aware, correctness-gated" | "we measure two widely used" | "output falls far short of" | "the dollar bill does not" | "the broader point is about" | "a modern agent bills tokens" | "a multi-turn agentic session [1]" | "reading the upstream benchmark code" | "the reported number measures something" | "the fix is to measure" | "the compression literature these tools" | "the harness --- the ecosystem" | "we implement the evaluation as" | "figure 1 shows the whole" | "each tool is delivered as" | "a skill that ships as" | "this step is load-bearing: a" | "we use seven self-contained coding" | "the suite deliberately skews short" | "each task is run with" | "the runs date from july" | "the two tools are the" | "for every run we record" | "on a subscription the \`\`bill''" | "we report dollars---the weighting most" | "each task carries a deterministic" | "these are lightweight structural checks" | "a trial where the skill" | "for each task we report" | "we call an effect significant" | "we also note which effects" | "for the bill we report" | "averages taken over a few" | "this protocol is sized to" | "two things are still required" | "the flagship skill (claim 65%)" | "we sign bill changes so" | "for the flagship, the bill" | "either way the answer is" | "the second tool (claim 63%)" | "it is also not significant." | "its one per-task cell that" | "the intervals are wide: at" | "but they exclude anything near" | "whatever room remains is capped" | "correctness." | "both arms passed the correctness" | "a sharper signal hides in" | "this is a short-session effect" | "this is not a quirk" | "both shares below are measured," | "pooled across all tasks and" | "if output is a fraction" | "even a perfect 65% output" | "the advertised 63--75% and the" | "the flagship's other feature trims" | "output is a small fraction" | "we measure cost: latency and" | "we do not claim \`\`these" | "nor do we dispute the" | "our point is that they" | "we label each by its" | "we run five trials per" | "that is itself part of" | "across tasks the flagship's output" | "the honest quantity is therefore" | "our design refutes the advertised" | "what caps those is the" | "a formal equivalence test (tost---a" | "the harness is not toothless," | "at that scale we read" | "we test one model, and" | "anthropic's tiers all share a" | "the ceiling scales linearly with" | "even if output were priced" | "our tasks top out around" | "this cuts in our favor." | "a longer session re-reads a" | "that pushes the ceiling lower," | "one might object that terser" | "but that downstream saving is" | "so it adds a few" | "the compounded ceiling is --," | "the overhead runs the other" | "an always-loaded tool prompt adds" | "that fixed cost amortizes over" | "the durable long-session prediction is" | "an empirical long-session demonstration is" | "every run in the main" | "the harness decides how many" | "a wordier model would raise" | "the ceiling of section 1" | "a pilot on claude haiku" | "nearly double, and still about" | "both runs ship in the" | "a second harness is reachable" | "the flagship now ships wrappers" | "measuring a second harness therefore" | "wrapper list and the rejected" | "we do not study multi-agent" | "we test two tools, and" | "the harness can a/b any" | "the bias runs in our" | "our per-run bill uses the" | "a user can also invalidate" | "(the first two rewrite the" | "that is, they push cost" | "they lower the ceiling and" | "so our clean-caching tasks, if" | "the correctness gate is a" | "so \`\`zero regressions'' bounds gross" | "a stronger gate that runs" | "still, a gate that never" | "without it, a harness would" | "the efficiency-claim failure is a" | "a community norm of cost-aware," | "execution-based, correctness-gated benchmarks such as" | "we keep their correctness-first discipline" | "several lines of work push" | "we take the same stance---price" | "closest to us, [1] argue" | "we turn that lens on" | "a long line of work" | "capability benchmarks show the same" | "we aim the same critique" | "the industry a/b test cited" | "what we add over such" | "the efficiency-tool market runs on" | "these tools trim what the" | "we measured two of the" | "the numbers are not fabricated;" | "the output you are shown" | "trimming the model's replies to" | "we release the measurements with" | "before you install the next" | "the complete harness of section" | "the measurements are released per" | "individual trial rows are not"
|
|
68
81
|
|
|
69
82
|
(thresholds and their sources are in THRESHOLDS at the top of this file)
|
|
@@ -17,17 +17,18 @@
|
|
|
17
17
|
*
|
|
18
18
|
* A `.txt` is the text of the rendered PDF — the page a reviewer actually reads.
|
|
19
19
|
*
|
|
20
|
-
* The figure captions are measured too
|
|
20
|
+
* The figure captions are measured too, in the report: every `\caption{}` in `paper.tex` and the
|
|
21
21
|
* files it includes, or, beside a rendered page, in `figures/*.tex`.
|
|
22
22
|
*
|
|
23
|
-
* Advisory:
|
|
23
|
+
* Advisory: the report prints every check and exits 0. `--flags-only` prints the checks the report
|
|
24
|
+
* marks FLAG, each with the lines under it, and nothing on a clean paper; it exits 1 when there is
|
|
25
|
+
* one — the form for a hook.
|
|
24
26
|
*/
|
|
25
27
|
import { paperProse } from "#src/paper-prose";
|
|
26
28
|
import { paperRecord } from "#src/paper-record";
|
|
27
|
-
import { latexReader } from "#src/adapters/latex/index";
|
|
29
|
+
import { captionsOf, latexReader, parseLatex } from "#src/adapters/latex/index";
|
|
28
30
|
import { nodeFiles } from "#src/adapters/node/index";
|
|
29
31
|
import { sourcesCodec } from "#src/adapters/sources-record/index";
|
|
30
|
-
import { texToMdast } from "#eslint-rules/latex-language";
|
|
31
32
|
|
|
32
33
|
const THRESHOLDS = {
|
|
33
34
|
metadiscourseTotalPer1000: {
|
|
@@ -363,77 +364,116 @@ function figureFiles(pagePath, fs, path) {
|
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
/**
|
|
366
|
-
* Every `\caption
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* for the rules that read notes. So a caption is skipped when its argument is blank in the
|
|
370
|
-
* projection or it starts inside a comment: a quoted or commented-out `\caption` is not one.
|
|
367
|
+
* Every `\caption` of `sources` (`{ file, text }`) a reader sees, measured. The captions come from the
|
|
368
|
+
* parsed source (`captionsOf`, src/adapters/latex/captions.ts): its optional argument, star and spacing
|
|
369
|
+
* are the parser's, and a caption that is commented out, in verbatim or in a macro's definition is not one.
|
|
371
370
|
*/
|
|
372
371
|
function captions(sources) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
depth = 1,
|
|
382
|
-
j = i;
|
|
383
|
-
while (j < s.length && depth) {
|
|
384
|
-
if (s[j] === "{") depth++;
|
|
385
|
-
else if (s[j] === "}") depth--;
|
|
386
|
-
j++;
|
|
387
|
-
}
|
|
388
|
-
if (
|
|
389
|
-
!/\S/.test(seen.slice(i, j - 1)) ||
|
|
390
|
-
comments.some(([a, b]) => m.index >= a && m.index < b)
|
|
391
|
-
)
|
|
392
|
-
continue;
|
|
393
|
-
const text = s
|
|
394
|
-
.slice(i, j - 1)
|
|
395
|
-
.replace(/\\[a-zA-Z]+\s*/g, " ")
|
|
396
|
-
.replace(/[{}]/g, " ")
|
|
397
|
-
.replace(/---/g, " ")
|
|
398
|
-
.replace(/\s+/g, " ")
|
|
399
|
-
.trim();
|
|
400
|
-
if (!text) continue;
|
|
401
|
-
// A non-empty caption splits into at least one sentence.
|
|
402
|
-
const sents = splitSentences(text);
|
|
403
|
-
out.push({
|
|
404
|
-
file: f,
|
|
405
|
-
words: words(text),
|
|
372
|
+
return sources.flatMap(({ file, text }) =>
|
|
373
|
+
captionsOf(parseLatex(text)).map((raw) => {
|
|
374
|
+
// A dash with no spaces round it joins two words in the count, as it never did here.
|
|
375
|
+
const caption = raw.replace(/---/g, " ");
|
|
376
|
+
const sents = splitSentences(caption);
|
|
377
|
+
return {
|
|
378
|
+
file,
|
|
379
|
+
words: words(caption),
|
|
406
380
|
longest: Math.max(...sents.map(words)),
|
|
407
381
|
longestSentence: sents.slice().sort((a, b) => words(b) - words(a))[0],
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
382
|
+
};
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* The findings on the captions of the file `f`: one line per caption sentence or caption over its
|
|
389
|
+
* limit. A paper's captions are in its own files — paper.tex and what it includes, wherever a figure
|
|
390
|
+
* is set; a rendered page has the `.tex` of `figures/` beside it. The report and `--flags-only` both
|
|
391
|
+
* read them from here.
|
|
392
|
+
*/
|
|
393
|
+
function captionFindings(f, tex, fs, path) {
|
|
394
|
+
return captions(tex ? tex.files : figureFiles(f, fs, path)).flatMap((c) =>
|
|
395
|
+
CAPTION_LIMITS.filter(over(c)).map(say(c)),
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* The two limits a caption is held to: the `THRESHOLDS` key, what of a caption it is measured on, and
|
|
401
|
+
* how a finding reads. A caption is over a limit when its measure exceeds the threshold's `warn`.
|
|
402
|
+
*/
|
|
403
|
+
const CAPTION_LIMITS = [
|
|
404
|
+
{
|
|
405
|
+
key: "captionSentenceWords",
|
|
406
|
+
measure: (c) => c.longest,
|
|
407
|
+
say: (c, warn) =>
|
|
408
|
+
`${c.file}: caption sentence of ${c.longest} words (warn >${warn}) — "${c.longestSentence.slice(0, 110)}…"`,
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
key: "captionWords",
|
|
412
|
+
measure: (c) => c.words,
|
|
413
|
+
say: (c, warn) =>
|
|
414
|
+
`${c.file}: caption is ${c.words} words (warn >${warn}) — a caption this long is a section under a picture`,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
const over = (c) => (limit) => limit.measure(c) > THRESHOLDS[limit.key].warn;
|
|
418
|
+
const say = (c) => (limit) => limit.say(c, THRESHOLDS[limit.key].warn);
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Every line of the report as data, in the order it is printed: `ok` is true (passed), false (a
|
|
422
|
+
* finding, marked `FLAG`) or null (a figure, no check), `under` the lines that name the offenders.
|
|
423
|
+
* The report prints all of it; `--flags-only` prints the findings: one list, two renderings.
|
|
424
|
+
*/
|
|
425
|
+
function checks(r, captionLines) {
|
|
426
|
+
const T = THRESHOLDS;
|
|
427
|
+
return [
|
|
428
|
+
{
|
|
429
|
+
ok: r.metadiscoursePer1000 <= T.metadiscourseTotalPer1000.warn,
|
|
430
|
+
text: `all metadiscourse/1000w ${r.metadiscoursePer1000} (Hyland RA baseline 64.9, warn >${T.metadiscourseTotalPer1000.warn})`,
|
|
431
|
+
under: [],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
ok: null,
|
|
435
|
+
text: `nominalisations/1000w ${r.nominalisationsPer1000}`,
|
|
436
|
+
under: [],
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
ok: r.contrastivePer10k <= T.contrastivePer10k.warn,
|
|
440
|
+
text: `"rather than"/instead-of/10,000w ${r.contrastivePer10k} (warn >${T.contrastivePer10k.warn}; not epanorthosis, no published baseline)`,
|
|
441
|
+
under: [],
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
ok: r.wastedStress.length === 0,
|
|
445
|
+
text: `sentences ending on a cross-ref/citation/hedge (wasted stress position): ${r.wastedStress.length}`,
|
|
446
|
+
under: r.wastedStress.map((s) => `…${s.slice(-70)}`),
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
ok: r.negatedSentencePct <= T.negatedMainClausePct.warn,
|
|
450
|
+
text: `sentences whose main claim is a negation: ${r.negatedSentencePct}%`,
|
|
451
|
+
under: [],
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
ok: captionLines.length === 0,
|
|
455
|
+
text: `figure captions over the limit: ${captionLines.length}`,
|
|
456
|
+
under: captionLines,
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
ok: null,
|
|
460
|
+
text: `topic positions (whose story is this?): ${r.topics.map((t) => `"${t}"`).join(" | ")}`,
|
|
461
|
+
under: [],
|
|
462
|
+
},
|
|
463
|
+
];
|
|
412
464
|
}
|
|
413
465
|
|
|
414
|
-
|
|
415
|
-
|
|
466
|
+
const MARK = { true: "ok ", false: "FLAG", null: " " };
|
|
467
|
+
|
|
468
|
+
function report(name, r, rows) {
|
|
416
469
|
console.log(`\n=== ${name} — ${r.words} words, ${r.sentences} sentences ===`);
|
|
417
470
|
console.log(
|
|
418
471
|
`sentence lengths ${JSON.stringify(r.sentenceLens)} mean ${r.meanLen} sd ${r.sdLen} CV ${r.cv}`,
|
|
419
472
|
);
|
|
420
|
-
|
|
421
|
-
`${
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
console.log(
|
|
425
|
-
`${flag(r.contrastivePer10k <= THRESHOLDS.contrastivePer10k.warn)} "rather than"/instead-of/10,000w ${r.contrastivePer10k} (warn >${THRESHOLDS.contrastivePer10k.warn}; not epanorthosis, no published baseline)`,
|
|
426
|
-
);
|
|
427
|
-
console.log(
|
|
428
|
-
`${flag(r.wastedStress.length === 0)} sentences ending on a cross-ref/citation/hedge (wasted stress position): ${r.wastedStress.length}`,
|
|
429
|
-
);
|
|
430
|
-
r.wastedStress.forEach((s) => console.log(` > …${s.slice(-70)}`));
|
|
431
|
-
console.log(
|
|
432
|
-
`${flag(r.negatedSentencePct <= THRESHOLDS.negatedMainClausePct.warn)} sentences whose main claim is a negation: ${r.negatedSentencePct}%`,
|
|
433
|
-
);
|
|
434
|
-
console.log(
|
|
435
|
-
` topic positions (whose story is this?): ${r.topics.map((t) => `"${t}"`).join(" | ")}`,
|
|
436
|
-
);
|
|
473
|
+
rows.forEach(({ ok, text, under }) => {
|
|
474
|
+
console.log(`${MARK[ok]} ${text}`);
|
|
475
|
+
under.forEach((l) => console.log(` > ${l}`));
|
|
476
|
+
});
|
|
437
477
|
}
|
|
438
478
|
|
|
439
479
|
const args = process.argv.slice(2);
|
|
@@ -474,14 +514,14 @@ function pdfTextOnly(t) {
|
|
|
474
514
|
.replace(/\f/g, "\n\n");
|
|
475
515
|
}
|
|
476
516
|
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
517
|
+
// `--flags-only` prints the findings of the report (every line it marks FLAG, with the lines under
|
|
518
|
+
// it) and nothing on a clean paper, and exits 1 when there is one: the form for a hook. The report
|
|
519
|
+
// prints all of `checks` and exits 0.
|
|
520
|
+
const mode = args.includes("--flags-only")
|
|
521
|
+
? "flags"
|
|
522
|
+
: args.includes("--headings")
|
|
523
|
+
? "headings"
|
|
524
|
+
: "report";
|
|
485
525
|
let flagged = 0;
|
|
486
526
|
for (const f of args.filter((a) => !a.startsWith("--"))) {
|
|
487
527
|
const raw = fs.readFileSync(f, "utf8");
|
|
@@ -509,33 +549,25 @@ for (const f of args.filter((a) => !a.startsWith("--"))) {
|
|
|
509
549
|
process.exit(2);
|
|
510
550
|
}
|
|
511
551
|
const r = analyse(text, sentences);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (c.longest > THRESHOLDS.captionSentenceWords.warn)
|
|
518
|
-
lines.push(
|
|
519
|
-
` ${c.file}: caption sentence of ${c.longest} words (warn >${THRESHOLDS.captionSentenceWords.warn}) — "${c.longestSentence.slice(0, 110)}…"`,
|
|
520
|
-
);
|
|
521
|
-
if (c.words > THRESHOLDS.captionWords.warn)
|
|
522
|
-
lines.push(
|
|
523
|
-
` ${c.file}: caption is ${c.words} words (warn >${THRESHOLDS.captionWords.warn}) — a caption this long is a section under a picture`,
|
|
524
|
-
);
|
|
525
|
-
}
|
|
526
|
-
if (lines.length) {
|
|
527
|
-
flagged += lines.length;
|
|
552
|
+
const rows = () => checks(r, captionFindings(f, tex, fs, path));
|
|
553
|
+
if (mode === "flags") {
|
|
554
|
+
const found = rows().filter((row) => row.ok === false);
|
|
555
|
+
if (found.length) {
|
|
556
|
+
flagged += found.length;
|
|
528
557
|
// The header STATES the count, so a caller need not count output lines — header and footer
|
|
529
558
|
// are not findings.
|
|
530
559
|
console.error(
|
|
531
|
-
`✍️ prose-lint — ${f.split("/").pop()} — ${
|
|
560
|
+
`✍️ prose-lint — ${f.split("/").pop()} — ${found.length} finding(s):`,
|
|
532
561
|
);
|
|
533
|
-
|
|
562
|
+
found.forEach(({ text, under }) => {
|
|
563
|
+
console.error(` ${text}`);
|
|
564
|
+
under.forEach((l) => console.error(` > ${l}`));
|
|
565
|
+
});
|
|
534
566
|
console.error(
|
|
535
|
-
" run prose-lint.mjs on the file without --flags-only for
|
|
567
|
+
" run prose-lint.mjs on the file without --flags-only for every metric",
|
|
536
568
|
);
|
|
537
569
|
}
|
|
538
|
-
} else if (
|
|
570
|
+
} else if (mode === "headings") {
|
|
539
571
|
// The whole-document question, printed as one list. Not a gate: "does this heading name a
|
|
540
572
|
// concrete noun" is judgement and pretending otherwise would be the exact failure this file
|
|
541
573
|
// exists to stop. What was missing is cheaper and was the real cause — nobody had ever seen the
|
|
@@ -558,10 +590,10 @@ for (const f of args.filter((a) => !a.startsWith("--"))) {
|
|
|
558
590
|
'a linter, a configuration, a repository, a rule — not "answers", "kinds", "moves".',
|
|
559
591
|
);
|
|
560
592
|
} else {
|
|
561
|
-
report(f.split("/").pop(), r);
|
|
593
|
+
report(f.split("/").pop(), r, rows());
|
|
562
594
|
}
|
|
563
595
|
}
|
|
564
|
-
if (
|
|
596
|
+
if (mode !== "flags")
|
|
565
597
|
console.log(
|
|
566
598
|
"\n(thresholds and their sources are in THRESHOLDS at the top of this file)",
|
|
567
599
|
);
|