paperlint 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +16 -23
- package/CLAUDE.md +55 -11
- package/CONTRIBUTING.md +30 -7
- package/README.md +11 -8
- package/dist/adapters/banal/invocation.d.ts +1 -1
- package/dist/adapters/latex/document-class.d.ts +23 -0
- package/dist/adapters/latex/document-class.d.ts.map +1 -0
- package/dist/adapters/latex/document-class.js +110 -0
- package/dist/adapters/latex/document-class.js.map +1 -0
- package/dist/adapters/latex/hidden.d.ts +16 -0
- package/dist/adapters/latex/hidden.d.ts.map +1 -0
- package/dist/adapters/latex/hidden.js +49 -0
- package/dist/adapters/latex/hidden.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +12 -0
- package/dist/adapters/latex/includes.d.ts.map +1 -0
- package/dist/adapters/latex/includes.js +42 -0
- package/dist/adapters/latex/includes.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +33 -0
- package/dist/adapters/latex/index.d.ts.map +1 -0
- package/dist/adapters/latex/index.js +25 -0
- package/dist/adapters/latex/index.js.map +1 -0
- package/dist/adapters/latex/nodes.d.ts +58 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -0
- package/dist/adapters/latex/nodes.js +97 -0
- package/dist/adapters/latex/nodes.js.map +1 -0
- package/dist/adapters/latex/outline.d.ts +10 -0
- package/dist/adapters/latex/outline.d.ts.map +1 -0
- package/dist/adapters/latex/outline.js +39 -0
- package/dist/adapters/latex/outline.js.map +1 -0
- package/dist/adapters/latex/parse.d.ts +42 -0
- package/dist/adapters/latex/parse.d.ts.map +1 -0
- package/dist/adapters/latex/parse.js +95 -0
- package/dist/adapters/latex/parse.js.map +1 -0
- package/dist/adapters/latex/prose.d.ts +8 -0
- package/dist/adapters/latex/prose.d.ts.map +1 -0
- package/dist/adapters/latex/prose.js +353 -0
- package/dist/adapters/latex/prose.js.map +1 -0
- package/dist/adapters/latex/rendered.d.ts +10 -0
- package/dist/adapters/latex/rendered.d.ts.map +1 -0
- package/dist/adapters/latex/rendered.js +39 -0
- package/dist/adapters/latex/rendered.js.map +1 -0
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +12 -12
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/build.js +4 -4
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +34 -0
- package/dist/claim-provenance.d.ts.map +1 -0
- package/dist/claim-provenance.js +146 -0
- package/dist/claim-provenance.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +49 -5
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +58 -0
- package/dist/domain/paper-source.d.ts.map +1 -0
- package/dist/domain/paper-source.js +109 -0
- package/dist/domain/paper-source.js.map +1 -0
- package/dist/domain/sentences.d.ts +26 -0
- package/dist/domain/sentences.d.ts.map +1 -0
- package/dist/domain/sentences.js +48 -0
- package/dist/domain/sentences.js.map +1 -0
- package/dist/domain/tex-document.d.ts +119 -0
- package/dist/domain/tex-document.d.ts.map +1 -0
- package/dist/domain/tex-document.js +43 -0
- package/dist/domain/tex-document.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +7 -0
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +53 -1
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/facts-file.d.ts.map +1 -1
- package/dist/facts-file.js +2 -2
- package/dist/facts-file.js.map +1 -1
- package/dist/new-paper.d.ts +5 -0
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +3 -0
- package/dist/new-paper.js.map +1 -1
- package/dist/package-dirs.d.ts +5 -0
- package/dist/package-dirs.d.ts.map +1 -0
- package/dist/package-dirs.js +14 -0
- package/dist/package-dirs.js.map +1 -0
- package/dist/ports/latex.d.ts +35 -0
- package/dist/ports/latex.d.ts.map +1 -0
- package/dist/ports/latex.js +2 -0
- package/dist/ports/latex.js.map +1 -0
- package/dist/presets.d.ts +15 -2
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +19 -6
- package/dist/presets.js.map +1 -1
- package/dist/register.d.ts +40 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +134 -0
- package/dist/register.js.map +1 -0
- package/dist/tex-paper.d.ts +19 -0
- package/dist/tex-paper.d.ts.map +1 -0
- package/dist/tex-paper.js +105 -0
- package/dist/tex-paper.js.map +1 -0
- package/dist/tex-requirements.d.ts +11 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +14 -8
- package/dist/tex-requirements.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +88 -0
- package/dist/tex-venue-rules.d.ts.map +1 -0
- package/dist/tex-venue-rules.js +268 -0
- package/dist/tex-venue-rules.js.map +1 -0
- package/dist/venue-rules.d.ts +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/docs/configuration.md +3 -2
- package/docs/e2e.md +69 -40
- package/docs/install.md +1 -1
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/package-location.md +5 -5
- package/docs/prior-art/repro/README.md +1 -1
- package/docs/prior-art/test-tooling.md +21 -10
- package/docs/rules/tex/claim-provenance.md +83 -0
- package/docs/rules/tex/missing-input.md +58 -0
- package/docs/rules/tex/register.md +77 -0
- package/docs/rules/tex/required-section.md +75 -0
- package/docs/rules/tex/template.md +74 -0
- package/docs/rules/tex/venue-leftover.md +73 -0
- package/docs/rules.md +73 -48
- package/docs/testing.md +17 -4
- package/docs/texlive-install-decision.md +1 -1
- package/docs/toolchain.md +2 -2
- package/eslint-rules/paper-typography.test.mjs +48 -1
- package/eslint-rules/paper-typography.ts +72 -1
- package/eslint.config.mjs +156 -15
- package/fixtures/build-e2e/acmart/expect.json +26 -0
- package/fixtures/build-e2e/aidc/PIPELINE-STATUS.md +3 -0
- package/fixtures/build-e2e/aidc/expect.json +14 -0
- package/fixtures/build-e2e/aidc/paper.tex +46 -0
- package/fixtures/build-e2e/aidc/paperlint.json +1 -0
- package/fixtures/build-e2e/broken/expect.json +11 -0
- package/fixtures/build-e2e/cite/expect.json +27 -0
- package/fixtures/build-e2e/empty/expect.json +12 -0
- package/fixtures/build-e2e/fallback/expect.json +53 -0
- package/fixtures/build-e2e/guards/expect.json +6 -0
- package/fixtures/build-e2e/no-source/expect.json +10 -0
- package/fixtures/build-e2e/unbalanced/expect.json +60 -0
- package/fixtures/pdf-facts/README.md +1 -1
- package/fixtures/real-markdown-paper/baseline.ts +8 -7
- package/fixtures/render-paper/build-clean.sh +3 -5
- package/package.json +14 -2
- package/{skills/submit-paper/references/venues → presets}/acm-sigconf.jsonc +4 -1
- package/{skills/submit-paper/references/venues → presets}/agenticdev.jsonc +4 -0
- package/presets/aidc.jsonc +73 -0
- package/{skills/submit-paper/references/venues → presets}/aisec.jsonc +4 -0
- package/presets/ieee-conference.jsonc +66 -0
- package/{skills/submit-paper/references/venues → presets}/realm.jsonc +8 -1
- package/{skills/submit-paper/references/venues → presets/tex}/paper-guards.tex +1 -1
- package/{skills/submit-paper/references/venues → presets}/venue-profile.schema.json +30 -2
- package/scripts/check.harness.ts +64 -10
- package/scripts/check.test.ts +60 -13
- package/scripts/check.ts +83 -30
- package/scripts/test.test.ts +1 -1
- package/scripts/test.ts +1 -1
- package/skills/cold-read-diff/SKILL.md +31 -10
- package/skills/cold-read-diff/SKILL.md.spec.ts +30 -9
- package/skills/draft-paper/SKILL.md +2 -2
- package/skills/draft-paper/SKILL.md.spec.ts +1 -1
- package/skills/grade-paper-writing/SKILL.md +109 -44
- package/skills/grade-paper-writing/SKILL.md.spec.ts +108 -43
- package/skills/harden-paper/SKILL.md +35 -36
- package/skills/harden-paper/SKILL.md.spec.ts +34 -35
- package/skills/paper-adversarial-review/SKILL.md +2 -2
- package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
- package/skills/paper-pipeline/PIPELINE-MAP.md +2 -2
- package/skills/paper-pipeline/SKILL.md +4 -4
- package/skills/paper-pipeline/SKILL.md.spec.ts +3 -3
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +6 -6
- package/skills/paper-pipeline/references/writing-craft.md +53 -18
- package/skills/paper-pipeline/scripts/consumer.d.mts +0 -2
- package/skills/paper-pipeline/scripts/consumer.mjs +1 -15
- package/skills/pc-panel-review/SKILL.md +36 -11
- package/skills/pc-panel-review/SKILL.md.spec.ts +35 -10
- package/skills/render-paper/SKILL.md +2 -2
- package/skills/render-paper/SKILL.md.spec.ts +1 -1
- package/skills/render-paper/check-render.sh +16 -63
- package/skills/render-paper/render-paper.harness.mjs +17 -59
- package/src/adapters/banal/index.test.ts +1 -1
- package/src/adapters/banal/invocation.ts +1 -1
- package/src/adapters/banal/output.test.ts +1 -1
- package/src/adapters/banal/run.test.ts +1 -1
- package/src/adapters/banal/xml.test.ts +1 -1
- package/src/adapters/latex/document-class.ts +155 -0
- package/src/adapters/latex/hidden.ts +70 -0
- package/src/adapters/latex/includes.ts +66 -0
- package/src/adapters/latex/index.test.ts +535 -0
- package/src/adapters/latex/index.ts +65 -0
- package/src/adapters/latex/nodes.ts +150 -0
- package/src/adapters/latex/outline.ts +64 -0
- package/src/adapters/latex/parse.ts +137 -0
- package/src/adapters/latex/prose.ts +484 -0
- package/src/adapters/latex/rendered.ts +59 -0
- package/src/adapters/pdfjs/fill.test.ts +1 -1
- package/src/adapters/pdfjs/fill.ts +18 -10
- package/src/build.harness.mjs +5 -7
- package/src/build.ts +4 -4
- package/src/claim-provenance.test.ts +352 -0
- package/src/claim-provenance.ts +217 -0
- package/src/cli.harness.mjs +1 -1
- package/src/cli.test.ts +2 -8
- package/src/cli.ts +53 -8
- package/src/domain/paper-source.test.ts +156 -0
- package/src/domain/paper-source.ts +233 -0
- package/src/domain/sentences.ts +67 -0
- package/src/domain/tex-document.test.ts +80 -0
- package/src/domain/tex-document.ts +143 -0
- package/src/engine.harness.mjs +1 -1
- package/src/facts-file.test.ts +4 -7
- package/src/facts-file.ts +2 -2
- package/src/latex-loop.harness.ts +1 -1
- package/src/new-paper.test.ts +28 -3
- package/src/new-paper.ts +8 -0
- package/src/package-dirs.ts +17 -0
- package/src/paper-settings-commands.test.ts +6 -1
- package/src/ports/latex.ts +42 -0
- package/src/presets.test.ts +65 -4
- package/src/presets.ts +38 -15
- package/src/register.test.ts +165 -0
- package/src/register.ts +177 -0
- package/src/rule-cases.test.ts +524 -0
- package/src/rule-docs.test.ts +159 -0
- package/src/tex-paper.test.ts +131 -0
- package/src/tex-paper.ts +152 -0
- package/src/tex-requirements.harness.ts +13 -23
- package/src/tex-requirements.ts +35 -8
- package/src/tex-venue-rules.test.ts +741 -0
- package/src/tex-venue-rules.ts +461 -0
- package/src/toolchain.harness.mjs +1 -1
- package/src/venue-rules.test.ts +96 -4
- package/src/venue-rules.ts +1 -1
- package/templates/paper/paper.tex +2 -1
- package/skills/harden-paper/fixtures/uncited-assertions-sample.md +0 -43
- package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +0 -77
- /package/{skills/submit-paper/references/venues → presets/tex}/agenticdev.tex +0 -0
- /package/{skills/submit-paper/references/venues → presets}/tex-base.jsonc +0 -0
package/docs/e2e.md
CHANGED
|
@@ -1,37 +1,50 @@
|
|
|
1
1
|
# End-to-end tests
|
|
2
2
|
|
|
3
|
-
They are
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
They are vitest tests named `*.e2e.ts` under `test/e2e/`, one vitest project per environment they
|
|
4
|
+
need, found by the project's glob — no list of files exists in `package.json`, `scripts/check.ts`
|
|
5
|
+
or CI:
|
|
6
|
+
|
|
7
|
+
| script | project | what it needs | files |
|
|
8
|
+
| -------------------------- | ------------- | -------------------------------------------- | --------------------------- |
|
|
9
|
+
| `npm run test:e2e:install` | `e2e-install` | npm and pnpm, and the network for `npm pack` | `test/e2e/install/*.e2e.ts` |
|
|
10
|
+
| `npm run test:e2e:tex` | `e2e-tex` | TeX Live from `paperlint toolchain`, banal | `test/e2e/tex/*.e2e.ts` |
|
|
11
|
+
| `npm run test:e2e` | both | | |
|
|
12
|
+
|
|
13
|
+
Both areas are part of `npm run check`, so nobody has to remember to call them. CI runs each in the
|
|
14
|
+
job that has its environment: `test:e2e:install` in `gates`, `test:e2e:tex` in `build-e2e`.
|
|
6
15
|
|
|
7
16
|
This page says what they prove, what they deliberately do not, and when a change owes a new one.
|
|
8
17
|
|
|
9
|
-
## The line between
|
|
18
|
+
## The line between the other tests and an end-to-end run
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
The rest of the package is tested by unit and integration tests and, for what an agent sees, by
|
|
21
|
+
harnesses — [`testing.md`](testing.md) says which is which. A rule is tested twice before any e2e:
|
|
22
|
+
unit tests beside it hand its logic a defect and a clean input, and `src/rule-cases.test.ts` runs
|
|
23
|
+
every registered rule through real ESLint on a small paper tree, the way `paperlint lint` does.
|
|
24
|
+
That is still an integration test: no TeX, no install, the working tree as it is.
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
file
|
|
26
|
+
Those tests run **in this repository's process, against this repository's working tree**. They
|
|
27
|
+
import the modules directly. Every path resolves, every dependency is already installed, and the
|
|
28
|
+
file they read is the file in `git`. An end-to-end run exists because all three of those are
|
|
18
29
|
assumptions that stop holding the moment somebody else installs the package.
|
|
19
30
|
|
|
20
|
-
| question | answered by
|
|
21
|
-
| ------------------------------------------------------------------------------------ |
|
|
22
|
-
| does this rule find this defect, and stay quiet otherwise |
|
|
23
|
-
| does the
|
|
24
|
-
| does the
|
|
25
|
-
|
|
|
26
|
-
|
|
|
31
|
+
| question | answered by |
|
|
32
|
+
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
|
|
33
|
+
| does this rule find this defect, and stay quiet otherwise | unit, beside the rule |
|
|
34
|
+
| does the rule run through ESLint, wired, on the file it is for | integration (`src/rule-cases.test.ts`) |
|
|
35
|
+
| does the CLI pick the right script, with the right interpreter | integration (`src/build.harness.mjs` substitutes `spawnSync`) |
|
|
36
|
+
| does the tarball, installed into a tree that is not this one, contain what it claims | **e2e** |
|
|
37
|
+
| do the paths written inside a skill resolve where the package actually lands | **e2e** |
|
|
38
|
+
| did a PDF come out, and is it typeset in the fonts the venue requires | **e2e** |
|
|
27
39
|
|
|
28
|
-
The
|
|
29
|
-
|
|
40
|
+
The last three rows are the ones no other test can reach even in principle: the defect only exists
|
|
41
|
+
once the code is somewhere else, or once a real TeX has typeset it.
|
|
30
42
|
|
|
31
|
-
## `test/e2e/install.ts` — the package, installed
|
|
43
|
+
## `test/e2e/install/install.e2e.ts` — the package, installed
|
|
32
44
|
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
It runs `npm pack`, then installs the resulting tarball into a fresh temporary tree, **under npm
|
|
46
|
+
and under pnpm separately** — one test per manager, each the whole scenario, because its steps
|
|
47
|
+
depend on each other — and drives the installed binary:
|
|
35
48
|
|
|
36
49
|
- the install itself finishes
|
|
37
50
|
- `paperlint --help` answers with zero
|
|
@@ -55,13 +68,21 @@ that works under npm can be dead under pnpm with no error anywhere. The same run
|
|
|
55
68
|
binary **directly** rather than through `node <path>`: under npm `.bin` holds a symlink, under
|
|
56
69
|
pnpm a shell wrapper, and calling `node bin` measures the caller's habit instead of the package.
|
|
57
70
|
|
|
58
|
-
## `test/e2e/build.ts` — a real `pdflatex`
|
|
71
|
+
## `test/e2e/tex/build.e2e.ts` — a real `pdflatex`
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
It copies `fixtures/build-e2e/` — nine papers, none with a build script paperlint would run — into
|
|
74
|
+
a temporary tree, points a config at it, and runs `paperlint build --all`. paperlint compiles each
|
|
75
|
+
paper itself with the real `pdflatex` and `bibtex`; the artifacts are then measured with
|
|
63
76
|
paperlint's own pdf.js reader from `dist/`, and the fonts are cross-checked against the list of
|
|
64
|
-
programs pdfTeX writes into `paper.log
|
|
77
|
+
programs pdfTeX writes into `paper.log`.
|
|
78
|
+
|
|
79
|
+
**Each fixture declares what it must produce** in `fixtures/build-e2e/<name>/expect.json`, parsed
|
|
80
|
+
with the schema in `test/e2e/tex/build-expect.ts`: whether it builds (`build.outcome`), what its
|
|
81
|
+
lines of the build output say, its fonts, its last page's text, files in its directory, the facts
|
|
82
|
+
the build measured, and — for `paperlint lint` after the build — the exit code and **exactly** the
|
|
83
|
+
findings for each rule it names (`[]` is "none"). One `describe` per fixture directory turns each
|
|
84
|
+
declared field into a test. A folder without `expect.json`, or with one the schema rejects, fails.
|
|
85
|
+
A new venue fixture is a new folder: its paper, its `paperlint.json`, its `expect.json`.
|
|
65
86
|
|
|
66
87
|
| fixture | what it is there to prove |
|
|
67
88
|
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -72,6 +93,8 @@ programs pdfTeX writes into `paper.log`:
|
|
|
72
93
|
| `unbalanced` | a two-column acmart paper whose last page comes out 621.5 / 264.8 pt builds **green** — the build does not judge the layout and rewrites nothing — while `_build/paper.facts.json` records both heights, and `paperlint lint` with `pdf/last-page-balance` turned on in `rules` reports the page |
|
|
73
94
|
| `broken` | a failing build names pdflatex and its exit code, quotes the error line and its `l.NNN` context, and deletes the stale `paper.pdf` planted before the run |
|
|
74
95
|
| `no-source` | a paper with no `paper.tex` is named separately, and the run as a whole is a failure |
|
|
96
|
+
| `empty` | a document with no pages: pdflatex exits 0 and writes no PDF, and that is a failure, not a green `paper.pdf` |
|
|
97
|
+
| `aidc` | a real `[conference,compsoc]{IEEEtran}` paper builds on paperlint's TeX Live in NimbusRomNo9L, `tex/template`, `tex/required-section` and the `pdf/*` rules find nothing, and `tex/venue-leftover` warns once, on its planted line |
|
|
75
98
|
|
|
76
99
|
The `fallback` row is the point of the whole file. `acmart.cls` checks for `libertine.sty`,
|
|
77
100
|
`zi4.sty` and `newtxmath.sty`, and failing to find any of them sets `\@ACM@newfontsfalse` and
|
|
@@ -79,28 +102,30 @@ typesets the paper in Computer Modern. The build is green, the PDF looks fine, t
|
|
|
79
102
|
and therefore so does the pagination. A submitted paper went out that way. **An exit code cannot
|
|
80
103
|
see it, so the content of the artifact is what gets measured.**
|
|
81
104
|
|
|
82
|
-
|
|
105
|
+
Beside the fixtures, one test proves the refusal with no TeX at all: PATH holds `node` alone, the
|
|
83
106
|
cache directory is empty and `CI` is set, and `paperlint build` must exit 1 with one line naming
|
|
84
|
-
`npx paperlint toolchain` and the venue's packages, print no plan and create no PDF and no cache.
|
|
85
|
-
asks `paperlint build --dry-run` which TeX Live the real run will use; under
|
|
86
|
-
paperlint's own cache, because the runner has no other.
|
|
107
|
+
`npx paperlint toolchain` and the venue's packages, print no plan and create no PDF and no cache.
|
|
108
|
+
The rest asks `paperlint build --dry-run` which TeX Live the real run will use; under CI that must
|
|
109
|
+
be paperlint's own cache, because the runner has no other.
|
|
87
110
|
|
|
88
|
-
## `test/e2e/toolchain.ts` — real TeX Live, and only it
|
|
111
|
+
## `test/e2e/tex/toolchain.e2e.ts` — real TeX Live, and only it
|
|
89
112
|
|
|
90
113
|
`paperlint toolchain` into `$PAPERLINT_TEXLIVE_DIR` against real CTAN; a second run must say "nothing to do"
|
|
91
114
|
within seconds; `--check` must exit 0; then the `acmart` fixture is built with PATH holding `node`
|
|
92
115
|
only, so no other TeX Live and no PDF tool can stand in, and the PDF must carry Libertine and Biolinum
|
|
93
|
-
and no Computer Modern face. Without `PAPERLINT_TEXLIVE_DIR`
|
|
116
|
+
and no Computer Modern face. Without `PAPERLINT_TEXLIVE_DIR` its tests are skipped: installing ~270 MB
|
|
94
117
|
into a home directory as a side effect of `npm run check` is the unasked install rule 11 forbids.
|
|
95
118
|
`src/toolchain.harness.mjs` covers the installer's logic (mirror fallback, archive check, time
|
|
96
119
|
limit, verification, idempotence) against a fake mirror on disk, without the network.
|
|
97
120
|
|
|
98
|
-
## Skips are
|
|
121
|
+
## Skips are reported, never passed
|
|
99
122
|
|
|
100
123
|
A clone without TeX Live genuinely cannot run the build e2e; a machine without pnpm cannot run
|
|
101
|
-
half of the install e2e.
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
half of the install e2e. Their tests are then `skipIf`'d, and vitest reports them as skipped.
|
|
125
|
+
`npm run check` reads that count from vitest's JSON report and prints the gate as
|
|
126
|
+
`SKIPPED — not run, not passed`. In CI the same absence means a broken environment: with `CI` set
|
|
127
|
+
(or `PAPERLINT_E2E_STRICT=1`) each such file registers one more test that fails and names what is
|
|
128
|
+
missing (`test/e2e/need.ts`).
|
|
104
129
|
|
|
105
130
|
A skipped step and a passed one look identical in a CI interface. That is the class this whole
|
|
106
131
|
package is written against, so it is not allowed to happen inside it either.
|
|
@@ -115,9 +140,13 @@ Not every change does. The trigger is countable — ask whether the change touch
|
|
|
115
140
|
- anything read through `package.json` fields — `bin`, `files`, `exports`
|
|
116
141
|
- a new external program the pipeline shells out to
|
|
117
142
|
- a new artifact the pipeline produces whose **content** can be wrong while the exit code is zero
|
|
143
|
+
- a new template-family preset: the TeX packages it declares, and the page size and fonts measured
|
|
144
|
+
from a real build of its class → one fixture folder in `fixtures/build-e2e/`, with its
|
|
145
|
+
`expect.json`
|
|
118
146
|
|
|
119
|
-
If none of those apply, a
|
|
120
|
-
would only make the suite slower without making it stricter.
|
|
147
|
+
If none of those apply, a unit or integration test beside the changed module is the right test,
|
|
148
|
+
and an e2e run would only make the suite slower without making it stricter. A new rule is not a
|
|
149
|
+
trigger by itself: its cases in `src/rule-cases.test.ts` already run it through real ESLint.
|
|
121
150
|
|
|
122
151
|
## What these two do NOT cover
|
|
123
152
|
|
|
@@ -142,7 +171,7 @@ everything.
|
|
|
142
171
|
|
|
143
172
|
## The corpus
|
|
144
173
|
|
|
145
|
-
`stageCorpus()` in `test/e2e/install.ts` writes a small paper by hand — a declared stage, its
|
|
174
|
+
`stageCorpus()` in `test/e2e/install/install.e2e.ts` writes a small paper by hand — a declared stage, its
|
|
146
175
|
PDF, its byte counts, the cross-check between them — and copies `fixtures/build-e2e/acmart` beside
|
|
147
176
|
it so the LaTeX rules see LaTeX rather than a placeholder.
|
|
148
177
|
|
package/docs/install.md
CHANGED
|
@@ -121,7 +121,7 @@ a CI workflow and a heavy toolchain), asks two questions and guesses the rest.
|
|
|
121
121
|
|
|
122
122
|
**npm and pnpm are covered; Yarn Plug'n'Play is not supported.**
|
|
123
123
|
|
|
124
|
-
`test/e2e/install.ts` packs the tarball, installs it into a clean project with each manager that
|
|
124
|
+
`test/e2e/install/install.e2e.ts` packs the tarball, installs it into a clean project with each manager that
|
|
125
125
|
launches on the machine, and runs the hook command to see whether it resolves. A grep over
|
|
126
126
|
`hooks.json` would not do: the string is right under any manager, and whether it resolves depends
|
|
127
127
|
on the tree the manager laid out. It also checks that every shipped skill is reachable as
|
package/docs/prior-art/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Prior art — how comparable tools are shaped
|
|
2
2
|
|
|
3
3
|
**Why this folder lives in the repository.** These are technical records that code and other docs
|
|
4
|
-
cite by path: `test/e2e/install.ts` cites `package-location.md`, `scripts/marketplace-shape.mjs`
|
|
4
|
+
cite by path: `test/e2e/install/install.e2e.ts` cites `package-location.md`, `scripts/marketplace-shape.mjs`
|
|
5
5
|
and `docs/install.md` point at the probes in `repro/`, and `eslint.config.mjs` excludes `repro/`
|
|
6
6
|
by name. Moving the folder out would leave those references pointing nowhere. The notes are for
|
|
7
7
|
contributors and maintainers, not for someone checking a paper, which is why the README does not
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# How a package finds its own installed files, and its own bin
|
|
2
2
|
|
|
3
|
-
**Question this file answers:** `test/e2e/install.ts` inspects the tree it just installed by
|
|
3
|
+
**Question this file answers:** `test/e2e/install/install.e2e.ts` inspects the tree it just installed by
|
|
4
4
|
spelling the layout out — `node_modules/research-paper-pipeline` (line 172),
|
|
5
5
|
`node_modules/.bin/rpp` (line 276), and `node_modules/.../plugin/hooks/hooks.json` (line 135).
|
|
6
6
|
`contentDelivery()` separately tries THREE candidate base directories for a path written as prose
|
|
@@ -54,7 +54,7 @@ argument of `import.meta.resolve` is the non-standard part:
|
|
|
54
54
|
|
|
55
55
|
### 🔴 `import.meta.resolve` is the wrong tool here, and it fails SILENTLY
|
|
56
56
|
|
|
57
|
-
`test/e2e/install.ts` runs from the repository and inspects a consumer somewhere in `os.tmpdir()`.
|
|
57
|
+
`test/e2e/install/install.e2e.ts` runs from the repository and inspects a consumer somewhere in `os.tmpdir()`.
|
|
58
58
|
`import.meta.resolve` always resolves from **the file that calls it** — `cwd` is irrelevant and
|
|
59
59
|
the `parent` argument is ignored without the flag rather than rejected:
|
|
60
60
|
|
|
@@ -165,7 +165,7 @@ literal node_modules/ : <WORK>/consumer-pnpm/node_modules/research-paper-pipelin
|
|
|
165
165
|
Under pnpm the hardcode addresses the **symlink** and resolution addresses the **store realpath**
|
|
166
166
|
(Node realpaths by default; `--preserve-symlinks` flips it, and `resolve-package-path` carries
|
|
167
167
|
`lib/should-preserve-symlinks.js` for exactly that). `realpathSync` of one equals the other, so
|
|
168
|
-
every `existsSync` / `readdirSync` / `readFileSync` in `test/e2e/install.ts` gives the same answer
|
|
168
|
+
every `existsSync` / `readdirSync` / `readFileSync` in `test/e2e/install/install.e2e.ts` gives the same answer
|
|
169
169
|
either way. Two interesting asymmetries, both measured:
|
|
170
170
|
|
|
171
171
|
- `module.findPackageJSON` returns the **symlink** path under pnpm, not the store path — the
|
|
@@ -348,7 +348,7 @@ node_modules present? NO
|
|
|
348
348
|
today the npm channel is the one that delivers the runnable code — but it is not impossible.
|
|
349
349
|
3. 🔴 **The blocker the note does NOT name is the immovable one, and no resolution API fixes it.**
|
|
350
350
|
PnP resolves to a path **inside a zip**. Line 3: the outside process gets `existsSync: false`
|
|
351
|
-
for the very path PnP just handed it; only PnP's patched `fs` can open it. `test/e2e/install.ts`
|
|
351
|
+
for the very path PnP just handed it; only PnP's patched `fs` can open it. `test/e2e/install/install.e2e.ts`
|
|
352
352
|
inspects the installed tree with `existsSync`, `readdirSync` and `readFileSync` **from its own
|
|
353
353
|
process**. Under PnP those read nothing, whatever API produced the path — and `yarn bin rpp`
|
|
354
354
|
(line 4) hands back the same unreadable path, so the supported bin lookup does not help either.
|
|
@@ -359,7 +359,7 @@ and it should be recorded as such rather than as "PnP has no node_modules".
|
|
|
359
359
|
|
|
360
360
|
## 9. The second guess: the base directory for a path written in prose
|
|
361
361
|
|
|
362
|
-
`contentDelivery()` (in `test/e2e/install.ts`) tries three bases per reference.
|
|
362
|
+
`contentDelivery()` (in `test/e2e/install/install.e2e.ts`) tries three bases per reference.
|
|
363
363
|
Measured over the real corpus with `claim6-doc-path-candidates.mjs`:
|
|
364
364
|
|
|
365
365
|
```console
|
|
@@ -56,7 +56,7 @@ $ claude plugin validate /tmp/m --strict; echo "RC=$?"
|
|
|
56
56
|
## The CLAIM 4–6 probes — where an installed package is, and how to find it
|
|
57
57
|
|
|
58
58
|
These back [`../package-location.md`](../package-location.md). They measure the two hardcodes in
|
|
59
|
-
`test/e2e/install.ts` (`node_modules/<name>` and `node_modules/.bin/rpp`) against the
|
|
59
|
+
`test/e2e/install/install.e2e.ts` (`node_modules/<name>` and `node_modules/.bin/rpp`) against the
|
|
60
60
|
resolution APIs Node actually offers, under npm, pnpm and Yarn Berry (PnP).
|
|
61
61
|
|
|
62
62
|
Build the consumer trees once — it packs this repository and installs the SAME tarball twice:
|
|
@@ -27,16 +27,27 @@ So the genuinely agent-facing harnesses are three: `hooks/hooks.harness.ts` (`ru
|
|
|
27
27
|
(`runHarnessTest`, which spawns the real `claude` binary against a scripted model), and the skill
|
|
28
28
|
contract checks. Everything else is ordinary Node testing that happens to carry the suffix.
|
|
29
29
|
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
`test/e2e
|
|
33
|
-
— install
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
## The e2e runs are vitest projects
|
|
31
|
+
|
|
32
|
+
The end-to-end runs are vitest tests (`test/e2e/<area>/*.e2e.ts`), one project per environment
|
|
33
|
+
they need — `e2e-install` (npm and pnpm) and `e2e-tex` (TeX Live) — found by the project's glob, so
|
|
34
|
+
no list of files exists in `package.json`, `scripts/check.ts` or CI (#132). What the runner buys is
|
|
35
|
+
not the reporter alone:
|
|
36
|
+
|
|
37
|
+
- **the fixture table.** `fixtures/build-e2e/` is a table of independent cases, and each fixture's
|
|
38
|
+
`expect.json` becomes its own tests through one `describe.each` over the directory. A new venue
|
|
39
|
+
fixture is a folder, not a block of test code.
|
|
40
|
+
- **a skip the aggregate can read.** A missing tool is `skipIf`, and vitest's JSON report counts it,
|
|
41
|
+
which is what `npm run check` reads to print a gate as skipped rather than passed. Under CI the
|
|
42
|
+
same absence fails (`test/e2e/need.ts`).
|
|
43
|
+
|
|
44
|
+
`install.e2e.ts` stays ONE test per package manager, each the whole scenario — install, bin,
|
|
45
|
+
`init`, `lint`, hook commands, content delivery — because its steps depend on each other: a
|
|
46
|
+
failure in the install makes every later step meaningless. Inside it, each check is a soft
|
|
47
|
+
expectation, so a run reports every mismatch, not the first.
|
|
48
|
+
|
|
49
|
+
The e2e projects stay OUT of `npm test` (project `unit`): they need the network, TeX Live or
|
|
50
|
+
several minutes. Each runs in its own CI job, with its own timeout.
|
|
40
51
|
|
|
41
52
|
## Tarball-by-path versus a local registry
|
|
42
53
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# tex/claim-provenance
|
|
2
|
+
|
|
3
|
+
**Level:** warn · **Reads:** `paper.tex`
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
A sentence of the body that states a number and does not say whose the number is. The sentence is
|
|
8
|
+
reported once, however many numbers it holds. Any one of these owners silences it:
|
|
9
|
+
|
|
10
|
+
| owner | in the sentence |
|
|
11
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| a work outside the paper | a `\cite` (any of natbib's or biblatex's forms), a `\url`, an `\href`, or a footnote that cites or links |
|
|
13
|
+
| the authors | _we_, _our_, _ours_, _us_ (the capitalised country _US_ is not the authors) |
|
|
14
|
+
| a place in the paper | `\ref`, `\cref`, `\autoref` and their kin, or the words _Section 4_, _Table 2_, _Fig. 3_, _Appendix B_, _§4_ |
|
|
15
|
+
| a claimant named as one | _advertises_, _claims_: _"the skill advertises a 65% reduction"_, _"(claim 63%)"_ |
|
|
16
|
+
| the sample the number counts | _n = 134_ (in prose or in math), or a count followed by what it counts: _48 runs_, _48 independent runs_, _of 1,836 repositories_ |
|
|
17
|
+
|
|
18
|
+
It reads the body of the whole paper — `paper.tex` with the files it `\input`s, `\include`s or
|
|
19
|
+
`\subfile`s spliced in — from the start of the document, the abstract included, to `\appendix` or the
|
|
20
|
+
bibliography. A sentence in an included file is reported at that include, with the file and line at
|
|
21
|
+
the front of the message. Footnotes are read as sentences of their own. A citation or a footnote set right after
|
|
22
|
+
the full stop belongs to the sentence the full stop ends.
|
|
23
|
+
|
|
24
|
+
A number is digits a reader sees — `42\%`, `0.37`, `1,836`, `3x`, `3×`. Not a number: a year on its
|
|
25
|
+
own (1900–2099); a number right after a capitalised word inside the sentence (_Claude 3_, _Python
|
|
26
|
+
3.12_, _Node 22_), which is a name; the level of an interval (the 95 of _95% CI_); digits glued to a letter, a hyphen or a point (`GPT-4`, `COVID-19`, `v1.2`, `10k`,
|
|
27
|
+
`0x1F`); anything inside math, a table, a figure, a caption, a heading (a run-in one too: `\textbf{Threats.}` opening a paragraph), code, a comment, a macro
|
|
28
|
+
definition, or the title block.
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
A reviewer who cannot tell a property of the world from a cited finding from the authors' own
|
|
33
|
+
measurement says so, and scores the paper's soundness on it. Academic prose carries the owner by
|
|
34
|
+
default — _we found_, _[12] report_, _(Table 2)_ — and a sentence rewritten for punch loses it first:
|
|
35
|
+
_"Under test the rules held in 97% of cases"_ has no subject, and a reader takes it
|
|
36
|
+
for a result from the literature. This rule is the floor under the rules that shorten sentences.
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
```latex
|
|
41
|
+
Agents ignore 42\% of the rules they are given. % warns
|
|
42
|
+
Under test the rules held in 97\% of cases. % warns: no one ran the test
|
|
43
|
+
We find that agents ignore 42\% of the rules. % silent: the authors
|
|
44
|
+
Agents ignore 42\% of the rules~\cite{smith2025}. % silent: a citation
|
|
45
|
+
Agents ignore 42\% of the rules (Table~\ref{t:rates}). % silent: a place in the paper
|
|
46
|
+
Of 1,836 repositories, 12\% ship hooks. % silent: the sample
|
|
47
|
+
In 2024 the first agents shipped with GPT-4. % silent: a year and a name
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The first line reports:
|
|
51
|
+
|
|
52
|
+
> «42%» — whose number is this? A reader cannot tell a cited finding from your own measurement from
|
|
53
|
+
> a general fact. Say it in the sentence: cite the work (\cite), claim it (we measured, our census),
|
|
54
|
+
> point to where it is shown (\ref, Section N, Table N), or name the sample it counts (n = 134, 48
|
|
55
|
+
> runs)
|
|
56
|
+
|
|
57
|
+
## Options / preset fields
|
|
58
|
+
|
|
59
|
+
None. It is on, as a warning, for every `paper.tex`; turn it off or up in the `rules` setting like
|
|
60
|
+
any other rule.
|
|
61
|
+
|
|
62
|
+
## What it does not check
|
|
63
|
+
|
|
64
|
+
- Whether the owner is the right one: a citation that does not report the number, or a _we_ in a
|
|
65
|
+
sentence about someone else's work, silences the sentence all the same.
|
|
66
|
+
- What a custom macro prints: `\rate{}` defined as `42\%` is not a number here.
|
|
67
|
+
- Which numbers are names, beyond the two shapes it knows: a number after a lowercase word (_version
|
|
68
|
+
3 of the model_) is read as a quantity, and so is the number of a name that opens the sentence
|
|
69
|
+
(_Claude 3 fails …_).
|
|
70
|
+
- Whether a count followed by a plural noun is really the sample (_3 times faster_ passes as one).
|
|
71
|
+
- Markdown papers (`paper.md`): it reads the LaTeX parse tree only.
|
|
72
|
+
|
|
73
|
+
## How to fix
|
|
74
|
+
|
|
75
|
+
Say whose the number is, in the sentence — usually by putting back the subject a punchy rewrite took
|
|
76
|
+
out: _"We ran the rules in 48 sessions and saw no violation"_, not _"No violation in 48 runs."_ A
|
|
77
|
+
number that is common knowledge, or quoted on purpose, keeps its sentence with ESLint's directive and
|
|
78
|
+
the reason:
|
|
79
|
+
|
|
80
|
+
```latex
|
|
81
|
+
% eslint-disable-next-line tex/claim-provenance -- the venue's own page limit, quoted
|
|
82
|
+
The limit is 8.
|
|
83
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# tex/missing-input
|
|
2
|
+
|
|
3
|
+
**Level:** warn · **Reads:** `paper.tex`, and every file it includes
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
An `\input`, `\include` or `\subfile` that names a file which is not there. The path is resolved the
|
|
8
|
+
way TeX resolves it, from the paper's directory: `name.tex` first, then `name` as written. An
|
|
9
|
+
include inside an included file is resolved from the paper's directory too.
|
|
10
|
+
|
|
11
|
+
## Why
|
|
12
|
+
|
|
13
|
+
The rules that read the parse tree — `tex/template`, `tex/required-section`, `tex/venue-leftover`,
|
|
14
|
+
`tex/claim-provenance` — read the whole paper: `paper.tex` with every included file spliced in where
|
|
15
|
+
it is included, as TeX reads it. A file that is not there cannot be read, and without this rule the
|
|
16
|
+
others would report a clean body for text they never saw. TeX stops on it too.
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
```latex
|
|
21
|
+
\input{sections/results} % warns when neither sections/results.tex nor sections/results exists
|
|
22
|
+
\input{sections/intro} % silent: sections/intro.tex is there
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The first line reports:
|
|
26
|
+
|
|
27
|
+
> `sections/results` is not there (tried `sections/results.tex` and `sections/results`, from the
|
|
28
|
+
> paper's directory): TeX stops on it, and no rule read what it should hold
|
|
29
|
+
|
|
30
|
+
A missing file included by another included file is reported at the include in `paper.tex` that
|
|
31
|
+
brought that file in, and the message names the file that holds it.
|
|
32
|
+
|
|
33
|
+
## Options / preset fields
|
|
34
|
+
|
|
35
|
+
None.
|
|
36
|
+
|
|
37
|
+
## What it does not check
|
|
38
|
+
|
|
39
|
+
- A path built from a macro (`\input{\dir/intro}`): macros are not expanded, so the path is looked
|
|
40
|
+
up as written.
|
|
41
|
+
- `\import{dir}{file}` and other packages' include macros.
|
|
42
|
+
|
|
43
|
+
## How to fix
|
|
44
|
+
|
|
45
|
+
Correct the path, or add the file. A file that a build step writes (a table of generated numbers)
|
|
46
|
+
is absent before that step runs — keep the include with a disable directive and the reason:
|
|
47
|
+
|
|
48
|
+
```latex
|
|
49
|
+
% eslint-disable-next-line tex/missing-input -- written by repro/numbers.sh before the build
|
|
50
|
+
\input{generated/numbers}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Where findings in included files are reported
|
|
54
|
+
|
|
55
|
+
ESLint reports a finding in the file it lints, and paperlint lints `paper.tex`. A finding of any of
|
|
56
|
+
the rules above whose text came from an included file is reported at the include in `paper.tex`,
|
|
57
|
+
with the included file, line and column at the front of its message
|
|
58
|
+
(`sections/results.tex:12:3: …`). A disable directive for it goes above that include.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# tex/register
|
|
2
|
+
|
|
3
|
+
**Level:** warn · **Reads:** `paper.tex` and the files it includes
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
A body in which more than 0.8 sentences per 1000 words open with _And_, _So_, _But_, _Nor_, _Or_ or
|
|
8
|
+
_Yet_. It is reported once, at the body's first sentence, with the count, the rate and the first
|
|
9
|
+
three such sentences.
|
|
10
|
+
|
|
11
|
+
It reads the body of the whole paper — `paper.tex` with the files it `\input`s, `\include`s or
|
|
12
|
+
`\subfile`s spliced in — from the start of the document, the abstract included, to `\appendix` or the
|
|
13
|
+
bibliography: paragraphs, list items and footnotes. Headings are not sentences, and neither is a
|
|
14
|
+
run-in heading — a bold or italic phrase ending in `.` or `:` that opens a paragraph or an item
|
|
15
|
+
(`\textbf{Threats.} …`). Captions, tables, figures, code and math environments are left out. A
|
|
16
|
+
citation, a cross-reference or inline math counts as one word, as it reads on the page. Sentences
|
|
17
|
+
are cut exactly as [`tex/claim-provenance`](claim-provenance.md) cuts them.
|
|
18
|
+
|
|
19
|
+
A conjunction counts only as the capitalised first word of a sentence; the _and_ that opens a list
|
|
20
|
+
item continuing the sentence before the list does not.
|
|
21
|
+
|
|
22
|
+
A body under 2 000 words is not judged: there one sentence decides the rate.
|
|
23
|
+
|
|
24
|
+
## Why
|
|
25
|
+
|
|
26
|
+
Reviewers called our prose "too informal" and "a blogpost". Measured against eleven accepted
|
|
27
|
+
papers, sentences opening with a coordinating conjunction set our four drafts apart:
|
|
28
|
+
|
|
29
|
+
| measure | 11 accepted papers | our 4 drafts |
|
|
30
|
+
| ------------------------------------------------------------ | ---------------------------: | -----------: |
|
|
31
|
+
| sentences opening with And/So/But/Nor/Or/Yet, per 1000 words | 0–0.58 (the ACSAC papers: 0) | 0.29–2.98 |
|
|
32
|
+
|
|
33
|
+
The limit sits above the accepted range with room to spare: the rule flags a body that has drifted
|
|
34
|
+
to a post's register, not one a little above the median. Mean sentence length did not separate the
|
|
35
|
+
two groups. The share of sentences under eight words did not either, once measured on the parse
|
|
36
|
+
tree: 3.3–7.3% on the two accepted papers in this repository's corpus, 5.1–7.0% on three of ours.
|
|
37
|
+
|
|
38
|
+
On the corpus (`fixtures/accepted-papers/`) the rule measures:
|
|
39
|
+
|
|
40
|
+
| paper | per 1000 words |
|
|
41
|
+
| ----------------------------------- | -------------: |
|
|
42
|
+
| `secure-acsac24` (ACSAC 2024) | 0.00 |
|
|
43
|
+
| `llm-splained-acsac25` (ACSAC 2025) | 0.00 |
|
|
44
|
+
| `agenticdev-acm26` (ours, accepted) | 1.60 |
|
|
45
|
+
|
|
46
|
+
The vocabulary of machine-written text (_delve_, _crucial_) is a different question, and a different
|
|
47
|
+
check; a paper can avoid every such word and still read as a post.
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
```latex
|
|
52
|
+
We measured the cost on four tools. But the bill barely moves. % a conjunction start
|
|
53
|
+
We measured the cost on four tools, and the bill barely moves. % none
|
|
54
|
+
We measured the cost on four tools. However, the bill barely moves. % none
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
On a body where such sentences run above the limit, the rule reports:
|
|
58
|
+
|
|
59
|
+
> 7 sentences open with And, So, But, Nor, Or or Yet — 1.60 per 1000 words (first: «…», «…», «…»).
|
|
60
|
+
> Accepted papers measured 0–0.58; this rule warns above 0.8. Join the sentence to the one before,
|
|
61
|
+
> or open with the connective a paper uses (However, Therefore, Moreover)
|
|
62
|
+
|
|
63
|
+
## Options / preset fields
|
|
64
|
+
|
|
65
|
+
In a paper's `paperlint.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"rules": {
|
|
70
|
+
"tex/register": ["warn", { "conjunctionStartsPer1000": 0.8 }]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| option | default | what it is |
|
|
76
|
+
| -------------------------- | ------: | ---------------------------------------------------------- |
|
|
77
|
+
| `conjunctionStartsPer1000` | 0.8 | the conjunction starts per 1000 words above which it warns |
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# tex/required-section
|
|
2
|
+
|
|
3
|
+
**Level:** error · **Reads:** `paper.tex`, and the preset its `paperlint.json` extends
|
|
4
|
+
|
|
5
|
+
## What it catches
|
|
6
|
+
|
|
7
|
+
A section the venue requires is missing, or is not where the venue requires it. The preset lists
|
|
8
|
+
the required sections by title in `required_sections`; the paper must have a `\section` or
|
|
9
|
+
`\section*` with exactly that title (whitespace collapsed), and, when the preset says
|
|
10
|
+
`position: "last"`, no section of the main text may come after it.
|
|
11
|
+
|
|
12
|
+
It reads the sections of the whole paper, the files `paper.tex` includes spliced in where they are
|
|
13
|
+
included, so a required section kept in `sections/llm-usage.tex` counts.
|
|
14
|
+
|
|
15
|
+
It only knows the sections a preset lists. A paper whose `paperlint.json` names no preset, or a
|
|
16
|
+
preset with no `required_sections`, gets no finding.
|
|
17
|
+
|
|
18
|
+
## Why
|
|
19
|
+
|
|
20
|
+
Some venues make a section mandatory and desk-reject without it. ACSAC's AI Usage Policy, which
|
|
21
|
+
its AIDC workshop applies (https://www.acsac.org/2026/submissions/ai/):
|
|
22
|
+
|
|
23
|
+
> If LLMs are used, authors must include a separate, clearly marked section titled "LLM Usage
|
|
24
|
+
> Statement" at the end of the paper. This section does not count towards the page limit.
|
|
25
|
+
|
|
26
|
+
> Failure to comply with these requirements is grounds for desk rejection without further review.
|
|
27
|
+
|
|
28
|
+
A common near miss is a bold paragraph in another venue's wording («Use of Generative AI.»): not a
|
|
29
|
+
section, and not that title.
|
|
30
|
+
|
|
31
|
+
**"If LLMs are used" is not something a rule can know, so the rule always requires the section for
|
|
32
|
+
a preset that lists it.** An author who used none says so in it («No LLMs were used in this
|
|
33
|
+
work.»).
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
Under a preset with
|
|
38
|
+
`"required_sections": [{ "title": "LLM Usage Statement", "position": "last" }]`:
|
|
39
|
+
|
|
40
|
+
```latex
|
|
41
|
+
\paragraph{Use of Generative AI.} We used an LLM. % fails: a paragraph, not a section
|
|
42
|
+
\section*{LLM usage statement} % fails: not the exact title
|
|
43
|
+
\section*{LLM Usage Statement} % passes, after the last section of the text
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A missing section reports:
|
|
47
|
+
|
|
48
|
+
> aidc requires a section titled «LLM Usage Statement» and there is none — add
|
|
49
|
+
> `\section*{LLM Usage Statement}` (the title exactly; a bold paragraph does not count)
|
|
50
|
+
|
|
51
|
+
The right title in the wrong place — before the introduction — reports:
|
|
52
|
+
|
|
53
|
+
> «LLM Usage Statement» must close the paper at aidc, and the section «Introduction» comes after it
|
|
54
|
+
> — move it after the last section of the body (after the bibliography is fine)
|
|
55
|
+
|
|
56
|
+
## Preset field
|
|
57
|
+
|
|
58
|
+
No rule options. It reads `required_sections`; a child preset's list replaces its parent's:
|
|
59
|
+
|
|
60
|
+
```jsonc
|
|
61
|
+
"required_sections": [{ "title": "LLM Usage Statement", "position": "last" }]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`title` is compared exactly after collapsing whitespace — the venue's words are the contract.
|
|
65
|
+
`position: "last"` requires it after every section of the main text, which is every section before
|
|
66
|
+
`\appendix` and the bibliography (`\bibliography`, `\printbibliography`, `thebibliography`); after
|
|
67
|
+
the bibliography or an appendix is fine. Without `position`, anywhere will do.
|
|
68
|
+
|
|
69
|
+
## What it does not check
|
|
70
|
+
|
|
71
|
+
- Whether LLMs were used, or what the section says.
|
|
72
|
+
|
|
73
|
+
## How to fix
|
|
74
|
+
|
|
75
|
+
Add `\section*{<the title>}` where the message says, with the content the venue asks for.
|