paperlint 4.1.0 → 5.0.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/CLAUDE.md +26 -2
- package/CONTRIBUTING.md +7 -0
- package/README.md +15 -4
- package/action.harness.mjs +54 -36
- package/action.yml +12 -27
- package/dist/adapters/latex/document-class.d.ts +6 -5
- package/dist/adapters/latex/document-class.d.ts.map +1 -1
- package/dist/adapters/latex/document-class.js +18 -10
- package/dist/adapters/latex/document-class.js.map +1 -1
- package/dist/adapters/latex/includes.d.ts +16 -0
- package/dist/adapters/latex/includes.d.ts.map +1 -0
- package/dist/adapters/latex/includes.js +48 -0
- package/dist/adapters/latex/includes.js.map +1 -0
- package/dist/adapters/latex/index.d.ts +9 -5
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +8 -1
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +5 -1
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +6 -2
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +1 -0
- package/dist/adapters/latex/parse.js.map +1 -1
- 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/build.d.ts.map +1 -1
- package/dist/build.js +7 -3
- 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 +2 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +81 -31
- 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 +39 -4
- package/dist/domain/tex-document.d.ts.map +1 -1
- package/dist/domain/tex-document.js.map +1 -1
- package/dist/eslint-rules/latex-language.d.ts +5 -1
- package/dist/eslint-rules/latex-language.d.ts.map +1 -1
- package/dist/eslint-rules/latex-language.js +6 -3
- package/dist/eslint-rules/latex-language.js.map +1 -1
- package/dist/eslint-rules/paper-context.d.ts +20 -0
- package/dist/eslint-rules/paper-context.d.ts.map +1 -0
- package/dist/eslint-rules/paper-context.js +44 -0
- package/dist/eslint-rules/paper-context.js.map +1 -0
- package/dist/eslint-rules/paper-typography.d.ts +8 -1
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +85 -19
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/eslint-rules/tex-build.d.ts.map +1 -1
- package/dist/eslint-rules/tex-build.js +7 -1
- package/dist/eslint-rules/tex-build.js.map +1 -1
- package/dist/init.d.ts +4 -19
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +24 -29
- package/dist/init.js.map +1 -1
- package/dist/package-dirs.d.ts +2 -0
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +8 -0
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-files.d.ts +2 -2
- package/dist/paper-includes.d.ts +17 -0
- package/dist/paper-includes.d.ts.map +1 -0
- package/dist/paper-includes.js +51 -0
- package/dist/paper-includes.js.map +1 -0
- package/dist/ports/latex.d.ts +12 -1
- package/dist/ports/latex.d.ts.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/rules-config.d.ts +1 -0
- package/dist/rules-config.d.ts.map +1 -1
- package/dist/rules-config.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-paper.d.ts +33 -0
- package/dist/tex-paper.d.ts.map +1 -0
- package/dist/tex-paper.js +103 -0
- package/dist/tex-paper.js.map +1 -0
- package/dist/tex-venue-rules.d.ts +7 -2
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js +31 -17
- package/dist/tex-venue-rules.js.map +1 -1
- package/docs/configuration.md +5 -4
- package/docs/install.md +8 -8
- package/docs/prior-art/paper-folder-scaffolding.md +4 -7
- package/docs/rules/tex/claim-provenance.md +83 -0
- package/docs/rules/tex/register.md +77 -0
- package/docs/rules/tex/required-section.md +3 -1
- package/docs/rules/tex/template.md +12 -0
- package/docs/rules/tex/venue-leftover.md +4 -2
- package/docs/rules.md +17 -10
- package/eslint-rules/latex-language.ts +10 -4
- package/eslint-rules/paper-context.test.ts +47 -0
- package/eslint-rules/paper-context.ts +57 -0
- package/eslint-rules/paper-typography.test.mjs +48 -1
- package/eslint-rules/paper-typography.ts +139 -24
- package/eslint-rules/tex-build.ts +8 -1
- package/eslint.config.mjs +1 -0
- package/fixtures/real-markdown-paper/baseline.json +1 -2
- package/fixtures/real-markdown-paper/baseline.ts +8 -7
- package/package.json +2 -1
- package/scripts/check.ts +2 -2
- package/scripts/eslint-report-guard.mjs +5 -2
- package/scripts/harness-api.frozen.json +0 -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/SKILL.md +3 -3
- package/skills/paper-pipeline/SKILL.md.spec.ts +2 -2
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +6 -6
- package/skills/paper-pipeline/references/pipeline-status-template.md +2 -16
- package/skills/paper-pipeline/references/writing-craft.md +53 -18
- package/skills/pc-panel-review/SKILL.md +36 -11
- package/skills/pc-panel-review/SKILL.md.spec.ts +35 -10
- package/src/adapters/latex/document-class.ts +20 -12
- package/src/adapters/latex/includes.ts +74 -0
- package/src/adapters/latex/index.test.ts +183 -1
- package/src/adapters/latex/index.ts +14 -4
- package/src/adapters/latex/nodes.ts +11 -2
- package/src/adapters/latex/parse.ts +1 -0
- package/src/adapters/latex/prose.ts +484 -0
- package/src/build.harness.mjs +4 -2
- package/src/build.test.ts +18 -1
- package/src/build.ts +7 -3
- package/src/claim-provenance.test.ts +352 -0
- package/src/claim-provenance.ts +217 -0
- package/src/cli.harness.mjs +22 -54
- package/src/cli.test.ts +2 -18
- package/src/cli.ts +92 -36
- 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.ts +36 -3
- package/src/init.test.ts +33 -13
- package/src/init.ts +25 -48
- package/src/linted-set.test.ts +11 -1
- package/src/new-paper.harness.mjs +1 -5
- package/src/package-dirs.ts +9 -0
- package/src/paper-files.ts +2 -2
- package/src/paper-includes.test.ts +161 -0
- package/src/paper-includes.ts +77 -0
- package/src/ports/latex.ts +13 -0
- package/src/register.test.ts +165 -0
- package/src/register.ts +177 -0
- package/src/rule-cases.test.ts +43 -23
- package/src/rule-docs.test.ts +0 -1
- package/src/rules-config.ts +1 -0
- package/src/structure.harness.ts +1 -3
- package/src/structure.ts +1 -1
- package/src/tex-paper.test.ts +148 -0
- package/src/tex-paper.ts +161 -0
- package/src/tex-venue-rules.test.ts +87 -5
- package/src/tex-venue-rules.ts +62 -30
- package/templates/paper/PIPELINE-STATUS.md +0 -4
- package/dist/action-ref.d.ts +0 -12
- package/dist/action-ref.d.ts.map +0 -1
- package/dist/action-ref.js +0 -16
- package/dist/action-ref.js.map +0 -1
- package/dist/eslint-rules/paper-research-question.d.ts +0 -31
- package/dist/eslint-rules/paper-research-question.d.ts.map +0 -1
- package/dist/eslint-rules/paper-research-question.js +0 -188
- package/dist/eslint-rules/paper-research-question.js.map +0 -1
- package/eslint-rules/paper-research-question.harness.mjs +0 -212
- package/eslint-rules/paper-research-question.ts +0 -199
- package/fixtures/paper-research-question/comment-only/PIPELINE-STATUS.md +0 -9
- package/fixtures/paper-research-question/comment-only/paper.tex +0 -7
- package/fixtures/paper-research-question/declared-not-in-paper/PIPELINE-STATUS.md +0 -10
- package/fixtures/paper-research-question/declared-not-in-paper/paper.tex +0 -6
- package/fixtures/paper-research-question/draft/PIPELINE-STATUS.md +0 -6
- package/fixtures/paper-research-question/draft/paper.tex +0 -2
- package/fixtures/paper-research-question/markdown-no-rq/PIPELINE-STATUS.md +0 -9
- package/fixtures/paper-research-question/markdown-no-rq/paper.md +0 -4
- package/fixtures/paper-research-question/shipped-no-rq/PIPELINE-STATUS.md +0 -12
- package/fixtures/paper-research-question/shipped-no-rq/paper.tex +0 -3
- package/fixtures/paper-research-question/shipped-with-rq/PIPELINE-STATUS.md +0 -10
- package/fixtures/paper-research-question/shipped-with-rq/paper.tex +0 -2
- package/skills/harden-paper/fixtures/uncited-assertions-sample.md +0 -43
- package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +0 -77
- package/src/action-ref.test.ts +0 -26
- package/src/action-ref.ts +0 -15
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
*
|
|
57
57
|
* A0 baseline verbatim. The control.
|
|
58
58
|
* A1 short keep sentence 1 ("Use when asking …") and sentence 2 ("Grades WRITING CRAFT
|
|
59
|
-
* only —
|
|
59
|
+
* only — ten dimensions … the fix for each."). Delete everything after.
|
|
60
60
|
* 1174 -> ~530 chars. The blunt arm: does ANY deletion revive it?
|
|
61
61
|
* A2 no-subordination delete ONLY the two clauses announcing that this skill is a SUBROUTINE of
|
|
62
62
|
* other skills: "whose stall inventory … is the readability gate other skills
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
* deferring — declining to call a thing that presents itself as something
|
|
68
68
|
* another skill calls. No other skill in the roster says this about itself.
|
|
69
69
|
* A3 no-negation delete ONLY the sentence carrying the "NOT …" delegation clauses.
|
|
70
|
-
* A4 no-machinery delete ONLY the opaque internals and proper nouns: the
|
|
70
|
+
* A4 no-machinery delete ONLY the opaque internals and proper nouns: the ten-dimension
|
|
71
71
|
* parenthetical, the "(Peyton Jones, McEnerney, Gopen & Swan; exemplars …)"
|
|
72
72
|
* authorities, the PERSONA sentence and the blind-panel/claims-diff sentence.
|
|
73
73
|
* Keeps sentence 1 and the NOT-clauses.
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
* "grade-paper-writing" reads as a niche in-house verb the selector skips.
|
|
77
77
|
* The only arm that changes nothing about the description text.
|
|
78
78
|
* A6 persona-only ADDED AFTER A0-A5 RAN, on request, and it is A1's MIRROR. The skill has two
|
|
79
|
-
* jobs — a
|
|
79
|
+
* jobs — a ten-dimension rubric and a persona cold-read stall pass — and A1
|
|
80
80
|
* as specified keeps the rubric sentence and deletes the persona one, so the
|
|
81
81
|
* six-arm design had a rubric-only description and no persona-only twin. A6 is
|
|
82
82
|
* that twin: the opening sentence (minus the `"grade the writing"` trigger,
|
|
@@ -518,9 +518,9 @@ const D_NEGATION = [
|
|
|
518
518
|
" NOT a content/defect review (paper-adversarial-review / pc-panel-review), NOT a structural cut plan (tighten-paper — run that first on a bloated draft), NOT venue-bar content strength (study-accepted-papers).",
|
|
519
519
|
];
|
|
520
520
|
const D_MACHINERY = [
|
|
521
|
-
" (Title, Abstract, Intro architecture, Structure, Prose clarity, Jargon discipline, Landing-the-point, Figure economy, Honesty-without-hedge-stacking)",
|
|
521
|
+
" (Title, Abstract, Intro architecture, Structure, Prose clarity, Jargon discipline, Landing-the-point, Figure economy, Honesty-without-hedge-stacking, Register distance to the target venue)",
|
|
522
522
|
" (Peyton Jones, McEnerney, Gopen & Swan; exemplars Trusting Trust, Carlini, Greshake)",
|
|
523
|
-
" Runs the
|
|
523
|
+
" Runs the cold-read stall pass with TWO committed personas (a non-academic engineer per section, and a workshop reviewer skimming abstract, introduction and conclusion) whose stall inventory — not the rubric number — is the readability gate other skills consume (pc-panel-review, paper-adversarial-review, harden-paper).",
|
|
524
524
|
" Defaults to a blind multi-grader panel; after fixes, mandates the claims-preservation diff (row `claims`).",
|
|
525
525
|
];
|
|
526
526
|
// NOTE ON "FOUR NOT-CLAUSES": the description carries THREE uppercase "NOT …" delegation clauses
|
|
@@ -546,7 +546,7 @@ const applyCuts = (s, cuts) => {
|
|
|
546
546
|
// its subordination tail removed — see the header for why that tail goes.
|
|
547
547
|
const D_PERSONA_ONLY = [
|
|
548
548
|
' / "grade the writing"',
|
|
549
|
-
" Grades WRITING CRAFT only —
|
|
549
|
+
" Grades WRITING CRAFT only — ten dimensions 1–5 (Title, Abstract, Intro architecture, Structure, Prose clarity, Jargon discipline, Landing-the-point, Figure economy, Honesty-without-hedge-stacking, Register distance to the target venue) against how the best-written papers read (Peyton Jones, McEnerney, Gopen & Swan; exemplars Trusting Trust, Carlini, Greshake) AND against 2–3 accepted papers of the target venue, naming the offending sentence and the fix for each.",
|
|
550
550
|
" whose stall inventory — not the rubric number — is the readability gate other skills consume (pc-panel-review, paper-adversarial-review, harden-paper)",
|
|
551
551
|
" Defaults to a blind multi-grader panel; after fixes, mandates the claims-preservation diff (row `claims`).",
|
|
552
552
|
" NOT a content/defect review (paper-adversarial-review / pc-panel-review), NOT a structural cut plan (tighten-paper — run that first on a bloated draft), NOT venue-bar content strength (study-accepted-papers).",
|
|
@@ -11,7 +11,7 @@ the verdict line's wording and the `☐`/`☑` marks as they are, or the hook go
|
|
|
11
11
|
|
|
12
12
|
## The front matter
|
|
13
13
|
|
|
14
|
-
Above the sections, the file carries YAML.
|
|
14
|
+
Above the sections, the file carries YAML. The rules read its `stages` field:
|
|
15
15
|
|
|
16
16
|
```yaml
|
|
17
17
|
---
|
|
@@ -22,20 +22,9 @@ stages:
|
|
|
22
22
|
bytes: 305412
|
|
23
23
|
source: versions/2026-07-22-submitted.tex
|
|
24
24
|
sourceBytes: 57210
|
|
25
|
-
researchQuestion: "Does pruning the state space reduce review cost?"
|
|
26
25
|
---
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
`researchQuestion` is **your sentence, written once** — not a label the checker hunts for in the
|
|
30
|
-
prose. `paper/research-question` then asks two things it can actually answer: is it written down,
|
|
31
|
-
and does the paper contain it (whitespace collapsed). Leave the field out and a shipped paper gets
|
|
32
|
-
an advisory finding, which is the right outcome for a position paper with no question: the absence
|
|
33
|
-
becomes a decision on the record rather than an omission.
|
|
34
|
-
|
|
35
|
-
⚠️ Write the sentence the way it appears in the paper. If the paper breaks it across lines that is
|
|
36
|
-
fine — only whitespace is normalised — but LaTeX markup inside the sentence (`\emph{cost}`) will not
|
|
37
|
-
match, and the finding will quote exactly what it looked for.
|
|
38
|
-
|
|
39
28
|
## The row ids, and what each one runs
|
|
40
29
|
|
|
41
30
|
The `id` cell is the **join key** — the same string appears in this template, in every paper's
|
|
@@ -116,10 +105,7 @@ existed. Neither is expressible now. Keep new ids lowercase, one word, and say w
|
|
|
116
105
|
The template itself is a FILE, not this page: `templates/paper/PIPELINE-STATUS.md` at the package
|
|
117
106
|
root (beside `templates/paper/paper.tex` and `paper.md`). `paperlint new <name>` copies it with the paper's
|
|
118
107
|
name filled in, and a project that keeps its own richer scorecard puts one at
|
|
119
|
-
`<papers>/.template/PIPELINE-STATUS.md`, which `paperlint new` prefers.
|
|
120
|
-
here, which meant a command would have had to parse markdown to extract it, and a human had to copy
|
|
121
|
-
it by hand. The copy carries `researchQuestion` (empty) and no `stages` — a new paper has shipped
|
|
122
|
-
nothing.
|
|
108
|
+
`<papers>/.template/PIPELINE-STATUS.md`, which `paperlint new` prefers. The copy carries no `stages` — a new paper has shipped nothing.
|
|
123
109
|
|
|
124
110
|
---
|
|
125
111
|
|
|
@@ -79,10 +79,13 @@ attributed to Kent Beck — treat as widely-cited, not primary-verified.)
|
|
|
79
79
|
- **MapReduce** — "Simplified" in the title = the reader's _benefit_ as the promise; clean topic sentences;
|
|
80
80
|
motivating example before mechanism.
|
|
81
81
|
|
|
82
|
-
## The gradeable rubric —
|
|
82
|
+
## The gradeable rubric — 10 dimensions, score 1–5 (anchors), most-severe first
|
|
83
83
|
|
|
84
|
-
For the "wall of jargon" failure mode, **weight #5 Prose, #6 Jargon, #7 Landing-the-point ×2
|
|
85
|
-
|
|
84
|
+
For the "wall of jargon" failure mode, **weight #5 Prose, #6 Jargon, #7 Landing-the-point ×2**; for the
|
|
85
|
+
"blog post" failure mode, **weight #10 Register ×2** as well. Score each, name the offending
|
|
86
|
+
sentence/section, write the fix. Dimensions #1–#9 push toward plain and memorable; #10 is the floor
|
|
87
|
+
under them — a fix that raises one of them by leaving the venue's register lowers #10, and the
|
|
88
|
+
grade says so.
|
|
86
89
|
|
|
87
90
|
1. **Title — value + memorability.** 1 = generic topic label, forgettable. 5 = states the finding or the
|
|
88
91
|
reader's benefit and is quotable; repeatable after one read.
|
|
@@ -91,7 +94,7 @@ name the offending sentence/section, write the fix.
|
|
|
91
94
|
3. **Intro architecture — problem/why-care/contribution/evidence, fast.** 1 = background/lit-tour, point
|
|
92
95
|
arrives on page 2–3. 5 = SPJ arc in the first half-page + a bulleted, forward-referenced contributions list.
|
|
93
96
|
4. **Structure & signposting (incl. visual density).** 1 = arbitrary order, paragraphs open mid-thought,
|
|
94
|
-
|
|
97
|
+
no signposting between claims (the reader must infer how one claim relates to the next), OR a wall — one giant unbroken paragraph the reader's eye slides off. 5 = each
|
|
95
98
|
section one job, every paragraph's first sentence is its claim, forward refs not a roadmap, AND no wall:
|
|
96
99
|
long related-work / discussion blocks are broken into scannable chunks with bold/italic run-in sub-heads
|
|
97
100
|
(see the wall-of-text check below).
|
|
@@ -115,6 +118,14 @@ validity`, `null`, `coarse`, coined handles) used with no plain-words gloss —
|
|
|
115
118
|
self-contained. 5 = one "money figure" a skim-reader understands alone; self-contained captions; tables show the one comparison that matters.
|
|
116
119
|
9. **Honesty without hedge-stacking.** 1 = overclaims OR drowns in hedges + defensive early related-work
|
|
117
120
|
wall. 5 = explicit "what we do NOT claim," one clean threats-to-validity move, related work at the end as context.
|
|
121
|
+
10. **Register distance to the target venue — graded against 2–3 accepted papers of that venue (or its
|
|
122
|
+
parent conference), never against the exemplars above.** 1 = reads as another genre: aphorisms and
|
|
123
|
+
punchline closings, fragments, colloquial asides, claims with no stated owner, relations between
|
|
124
|
+
claims left for the reader to infer. 5 = a reviewer at the venue could not tell it from the
|
|
125
|
+
reference papers by register: results owned (_we measured_, a citation, a named population),
|
|
126
|
+
relations signposted (_therefore_, _in contrast_, _this section_), sentences complete. Every flag
|
|
127
|
+
quotes the reference sentence it was compared against. No reference papers → not graded, and the
|
|
128
|
+
report says so; this dimension is never scored from memory.
|
|
118
129
|
|
|
119
130
|
## Scoring calibration — how to avoid an inflated grade (read before scoring)
|
|
120
131
|
|
|
@@ -175,6 +186,25 @@ bucket-1 field terms and trips on buckets 2–3. Optionally also run it on a **d
|
|
|
175
186
|
lacks the vocabulary for real (not by role-play) and stalls even more like a human. The persona's inventory feeds
|
|
176
187
|
straight into the fix pass.
|
|
177
188
|
|
|
189
|
+
**Run the second persona too — the workshop reviewer. Both, never one instead of the other.** Sam cannot see two
|
|
190
|
+
defects by construction: he does not know what a paper at this venue sounds like, and a sentence with no stated
|
|
191
|
+
owner reads to him as confident rather than ambiguous. Those are exactly the two a real reviewer wrote down
|
|
192
|
+
("difficult at first to distinguish among properties of LLMs, statistical observations from repositories, and
|
|
193
|
+
experimental findings produced by the authors"; "blogpost"). Spawn a second fresh subagent:
|
|
194
|
+
|
|
195
|
+
> You are a program-committee member at <venue>. You review for it every year, you have eleven papers this
|
|
196
|
+
> round, and you have 40 minutes for this one. Your confidence will be 3: you know the area, not this exact
|
|
197
|
+
> topic. Read the abstract, then the introduction, then the conclusion — nothing else. For EVERY sentence ask:
|
|
198
|
+
> whose result does this state — the authors' own measurement, a cited work, or a general property of the
|
|
199
|
+
> world? If you cannot tell, quote it and write WHOSE?. Where two sentences sit side by side and you have to
|
|
200
|
+
> work out yourself how they relate, quote the pair and write RELATION?. Where a sentence would not appear in
|
|
201
|
+
> a paper at <venue> — an aphorism, a punchline, a fragment, a colloquial turn — quote it and write REGISTER.
|
|
202
|
+
> Then give the overall recommendation you would write after these 40 minutes, and the one sentence of your
|
|
203
|
+
> review a chair would read first. Output the list and the recommendation only.
|
|
204
|
+
|
|
205
|
+
Its WHOSE? / RELATION? / REGISTER lines go into the stall inventory as classes 7 and 8 of `grade-paper-writing`.
|
|
206
|
+
Give it the three sections as text, and no framing: it is a reviewer who has not talked to the authors.
|
|
207
|
+
|
|
178
208
|
**Run the persona PER SECTION for a dense or long (>~6pp) paper — never one whole-paper pass.** A single pass over
|
|
179
209
|
a full paper skims the middle: this session a whole-paper sweep scored 44/60 and missed the walls that per-section
|
|
180
210
|
persona reads (intro, method, results) each caught immediately. Chunk it to 2–3pp / one section per subagent, so
|
|
@@ -233,16 +263,18 @@ of `rm -rf` collapses to one"), a triple-nested-em-dash sentence you parse twice
|
|
|
233
263
|
(the author's rule: don't open on the spec sheet.) When unsure which bucket a term is in, ask: _would a reviewer at THIS
|
|
234
264
|
venue use it, or is it imported from stats/philosophy/our own coinage?_
|
|
235
265
|
|
|
236
|
-
**De-jargon is RE-VOICING the sentence, not swapping the word
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
sections "way too academicy for 0 reason"). The fix
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
instrument, not an injection defense" → "
|
|
244
|
-
|
|
245
|
-
|
|
266
|
+
**De-jargon is RE-VOICING the sentence, not swapping the word — and the new voice is the venue's, not
|
|
267
|
+
a conversation's.** Replacing a coined term with a plainer noun inside a sentence built around nominalizations
|
|
268
|
+
leaves the stall in place (GateBench 2026-07-25: a term-level de-jargon pass cleared the flagged words but the
|
|
269
|
+
author still hit sections "way too academicy for 0 reason"). The fix is the sentence shape: subject–verb–object,
|
|
270
|
+
active voice, the actor named, no nominalizations ("performs an evaluation of" → "checks"). **The fix stops at the
|
|
271
|
+
register of the venue's accepted papers:** no contractions, no fragments, no colloquial asides, and the signposting
|
|
272
|
+
stays (_therefore_, _in contrast_, _we show_). E.g. "We therefore position the guard as a defense against
|
|
273
|
+
trigger~A and a measurement instrument, not an injection defense" → "The guard is therefore a defense against
|
|
274
|
+
accidental triggers and an instrument for measuring them; it is not designed to stop an attacker." — plainer,
|
|
275
|
+
and still a sentence the venue prints. "So the guard is there to catch accidents — it is not built to stop an
|
|
276
|
+
attacker" goes one step too far: it is what a reviewer quotes back as informal. Test each rewrite against a
|
|
277
|
+
sentence from the reference papers of dimension #10, not against how it sounds aloud.
|
|
246
278
|
|
|
247
279
|
**Step 0 — mechanical pre-scan (BEFORE reading, so the pass can't rubber-stamp).** Run the coined-compound
|
|
248
280
|
grep from the avoid-list against the source and add EVERY hit to the stall inventory as a candidate; grep the
|
|
@@ -376,9 +408,11 @@ axis that catches "the conclusion isn't strong enough."
|
|
|
376
408
|
|
|
377
409
|
## The avoid-list — words that read as slop or stall a human (flag every hit)
|
|
378
410
|
|
|
379
|
-
The north star: **a paper should be
|
|
380
|
-
|
|
381
|
-
NOT a researcher get this sentence in 10 seconds? If not, it's too dense.
|
|
411
|
+
The north star: **a paper should be readable by a smart non-specialist AND read as a paper at its venue —
|
|
412
|
+
plain where plainness costs nothing, academic where the register expects it.** Two tests, both apply: would a
|
|
413
|
+
strong engineer who is NOT a researcher get this sentence in 10 seconds? If not, it's too dense. Would it sit
|
|
414
|
+
unremarked in one of the venue's accepted papers (dimension #10)? If not, it's too loose — a replacement that
|
|
415
|
+
passes the first test by failing the second is not a fix. The jargon dimension (#6) and the
|
|
382
416
|
stall pass are the judgment; the list below is the _checkable_ part (prose isn't policy — compile what's
|
|
383
417
|
mechanical). These are **scrutinize-words, not absolute bans** (some are legit in context — "significant" for
|
|
384
418
|
a real stat, "method"); on every hit, ask "does this earn its place, or is it slop / hype / a reader-stall?"
|
|
@@ -435,7 +469,8 @@ number, so the paper does not have to print them all — divide the labor:
|
|
|
435
469
|
|
|
436
470
|
1. **DO lead with the concrete crazy instance, not the aggregate stat** (Carlini's UUID).
|
|
437
471
|
2. **DO manufacture the "wait, what?" pivot** — surface the held assumption, then break it (Greshake).
|
|
438
|
-
3. **DO write the intro as the SPJ arc + a bulleted, forward-referenced contributions list**;
|
|
472
|
+
3. **DO write the intro as the SPJ arc + a bulleted, forward-referenced contributions list**; keep an
|
|
473
|
+
"organized as follows" paragraph only when the venue's accepted papers carry one.
|
|
439
474
|
4. **DON'T imitate Attention-style dense contribution-less prose** unless the audience already cares.
|
|
440
475
|
5. **DON'T stack jargon or hedges** — gloss every term plainly at first use; replace hedge-piles with one
|
|
441
476
|
precise scope sentence; land one repeatable moral (Trusting Trust).
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pc-panel-review
|
|
3
|
-
description: Use when asking "what would the program committee decide?" / "simulate the reviewers" / "what's this paper's accept probability?" on a drafted paper + artifact. Spawns N independent reviewers with DISTINCT lenses (one actually RUNS the artifact), then synthesizes a PC-chair meta-review into an accept/reject decision, a calibrated probability, and consensus must-fixes. Requires grade-paper-writing's persona stall inventory + tighten-paper's structural verdict as inputs (blocked until they exist — the panel cannot feel reader fatigue on its own). Also has a lightweight single-reviewer VENUE-FIT MODE for a quick CFP-fit spot-check. NOT a single fast defect hunt (paper-adversarial-review), a writing grade (grade-paper-writing), or the full pre-submit gate (harden-paper, which calls this) — this models the PC decision itself.
|
|
3
|
+
description: Use when asking "what would the program committee decide?" / "simulate the reviewers" / "what's this paper's accept probability?" on a drafted paper + artifact. Spawns N independent reviewers with DISTINCT lenses (one actually RUNS the artifact, one is a skimming genre/register reviewer), then synthesizes a PC-chair meta-review into an accept/reject decision reported as min/median/max, a calibrated probability, and consensus must-fixes. Requires grade-paper-writing's persona stall inventory + tighten-paper's structural verdict as inputs (blocked until they exist — the panel cannot feel reader fatigue on its own). Also has a lightweight single-reviewer VENUE-FIT MODE for a quick CFP-fit spot-check. NOT a single fast defect hunt (paper-adversarial-review), a writing grade (grade-paper-writing), or the full pre-submit gate (harden-paper, which calls this) — this models the PC decision itself.
|
|
4
4
|
allowed-tools: [Read, Write, Grep, Glob, Bash, WebSearch, WebFetch, Agent, Skill]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<!-- vigiles:sha256:
|
|
7
|
+
<!-- vigiles:sha256:7e5fb04d1cfa0548 compiled from skills/pc-panel-review/SKILL.md.spec.ts -->
|
|
8
8
|
|
|
9
9
|
# pc-panel-review — model the whole PC, not one reviewer
|
|
10
10
|
|
|
@@ -51,7 +51,15 @@ prose-only read can.
|
|
|
51
51
|
panel's only legitimate source for the readability/structure signal.
|
|
52
52
|
*(Scorecard: this gate is row **`panel`**, and it declares **`structure`** (tighten-paper) + **`writing`**
|
|
53
53
|
(grade-paper-writing) as its required inputs.)*
|
|
54
|
-
3. **Pick 3 (or 4) reviewer lenses** that a real PC for THIS venue would field.
|
|
54
|
+
3. **Pick 3 (or 4) reviewer lenses** that a real PC for THIS venue would field. One is fixed; the rest
|
|
55
|
+
come from the menu, by fit:
|
|
56
|
+
- **Genre/register reviewer — MANDATORY on every panel, at every venue.** Confidence 3 (knows the
|
|
57
|
+
area, not this topic), reads ONLY the abstract, the introduction and the conclusion, and judges
|
|
58
|
+
whether this reads as a paper at this venue: whose result each sentence states, whether the
|
|
59
|
+
relations between claims are signposted or left to infer, and whether anything is an aphorism,
|
|
60
|
+
a fragment or a colloquial turn. This is the reviewer who writes one short review, scores low,
|
|
61
|
+
and calls the paper a blog post — the one reviewer a panel of careful full readers never
|
|
62
|
+
predicts. Give it the three sections as text and the venue's name, nothing else.
|
|
55
63
|
- **Methods/stats skeptic** — sample size, construct validity, multiple comparisons, overclaims,
|
|
56
64
|
whether the CIs support the claims.
|
|
57
65
|
- **Domain practitioner** — relevance to the CFP, novelty vs the nearest prior work, does the
|
|
@@ -67,7 +75,13 @@ prose-only read can.
|
|
|
67
75
|
4. **Spawn them in PARALLEL, each on a separate model instance** (e.g. Fable via subagent), each blind
|
|
68
76
|
to the others — independence is the whole point. Give each: the paper (`.tex`/PDF), the artifact
|
|
69
77
|
path, the quoted rubric, its lens, **and the two step-2 artifacts (persona stall inventory +
|
|
70
|
-
tighten-paper structural verdict)
|
|
78
|
+
tighten-paper structural verdict)** — the genre/register reviewer gets only its three sections.
|
|
79
|
+
Force a filled review form (below).
|
|
80
|
+
|
|
81
|
+
🔴 **No reviewer is shown a previous round** — not its scores, not its reviews, not "the panel
|
|
82
|
+
gave 3.25 last time", not a list of what changed since. A reviewer who is told the last score
|
|
83
|
+
anchors on it (*"up from the 3.25 the earlier rounds reported"*), and the rounds then converge on
|
|
84
|
+
each other instead of on the paper. Comparing rounds is the chair's job, after the reviews land.
|
|
71
85
|
5. **Synthesize the meta-review yourself** (PC chair) once all land — see output.
|
|
72
86
|
|
|
73
87
|
## Each reviewer returns a review form
|
|
@@ -90,10 +104,19 @@ prose-only read can.
|
|
|
90
104
|
- One-line meta: does this belong at this venue?
|
|
91
105
|
|
|
92
106
|
## The meta-review (PC-chair synthesis) — the actual deliverable
|
|
93
|
-
- **Score table:** each reviewer × each criterion + overall + confidence, at a glance
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
- **Score table:** each reviewer × each criterion + overall + confidence, at a glance, and under it
|
|
108
|
+
the overall score's **min / median / max** across reviewers — never a mean alone, which hides the
|
|
109
|
+
one low review that decides a workshop.
|
|
110
|
+
- **Decision:** for a **workshop**, the decision follows the **MIN**: a workshop PC with three
|
|
111
|
+
reviews and a short discussion does not overrule a reject, so one Reject or Strong Reject sinks the
|
|
112
|
+
paper however confident the others are. For a conference, the rec the PC discussion converges on,
|
|
113
|
+
weighted by confidence, with the min named beside it. Either way, a **calibrated accept probability**
|
|
114
|
+
for this venue and edition.
|
|
115
|
+
- 🔴 **One reviewer is not a panel.** A run with fewer than three reviews and a meta-review — a
|
|
116
|
+
Venue-fit spot-check, a single re-review, a panel whose other seats did not return — is recorded as
|
|
117
|
+
what it is (`venue-fit`, `single review`), never in the scorecard's `panel` row. A single
|
|
118
|
+
review recorded as a panel verdict is how a paper reaches submission on an 88% that three real
|
|
119
|
+
reviewers turned into a reject.
|
|
97
120
|
- **Consensus must-fixes:** issues **≥2 reviewers independently raised** — these are the real ones;
|
|
98
121
|
apply before submitting.
|
|
99
122
|
- **Single-reviewer flags:** noted, triaged (fix if cheap, else defer to camera-ready).
|
|
@@ -199,9 +222,11 @@ reject-risk without rubber-stamping. For the real decision, escalate to the full
|
|
|
199
222
|
## The reproduce loop (why this beats a prose review)
|
|
200
223
|
This is "double-blind review as a real venue does it, WITH reproduction." The artifact-runner reviewer
|
|
201
224
|
is the difference-maker on both runs so far: it doesn't judge the paper's numbers, it re-derives them.
|
|
202
|
-
Run the panel, apply the consensus fixes, then **re-run the panel**
|
|
203
|
-
confirm the fixes landed and nothing regressed
|
|
204
|
-
|
|
225
|
+
Run the panel, apply the consensus fixes, then **re-run the panel** — same lenses, FRESH reviewers,
|
|
226
|
+
told nothing about the previous round — to confirm the fixes landed and nothing regressed; the
|
|
227
|
+
artifact-runner re-executes every harness each round. The chair, not the reviewers, sets the new
|
|
228
|
+
reviews beside the old ones and checks that each earlier must-fix is gone. Track the min / median / max
|
|
229
|
+
progression in the review ledger. Stop when the panel converges to
|
|
205
230
|
accept and the artifact reproduces clean; don't loop past one confirming round.
|
|
206
231
|
|
|
207
232
|
## Provenance (two papers, battle-tested)
|
|
@@ -9,7 +9,7 @@ import { experimental_skill } from "vigiles/spec";
|
|
|
9
9
|
export default experimental_skill({
|
|
10
10
|
name: "pc-panel-review",
|
|
11
11
|
description:
|
|
12
|
-
'Use when asking "what would the program committee decide?" / "simulate the reviewers" / "what\'s this paper\'s accept probability?" on a drafted paper + artifact. Spawns N independent reviewers with DISTINCT lenses (one actually RUNS the artifact), then synthesizes a PC-chair meta-review into an accept/reject decision, a calibrated probability, and consensus must-fixes. Requires grade-paper-writing\'s persona stall inventory + tighten-paper\'s structural verdict as inputs (blocked until they exist — the panel cannot feel reader fatigue on its own). Also has a lightweight single-reviewer VENUE-FIT MODE for a quick CFP-fit spot-check. NOT a single fast defect hunt (paper-adversarial-review), a writing grade (grade-paper-writing), or the full pre-submit gate (harden-paper, which calls this) — this models the PC decision itself.',
|
|
12
|
+
'Use when asking "what would the program committee decide?" / "simulate the reviewers" / "what\'s this paper\'s accept probability?" on a drafted paper + artifact. Spawns N independent reviewers with DISTINCT lenses (one actually RUNS the artifact, one is a skimming genre/register reviewer), then synthesizes a PC-chair meta-review into an accept/reject decision reported as min/median/max, a calibrated probability, and consensus must-fixes. Requires grade-paper-writing\'s persona stall inventory + tighten-paper\'s structural verdict as inputs (blocked until they exist — the panel cannot feel reader fatigue on its own). Also has a lightweight single-reviewer VENUE-FIT MODE for a quick CFP-fit spot-check. NOT a single fast defect hunt (paper-adversarial-review), a writing grade (grade-paper-writing), or the full pre-submit gate (harden-paper, which calls this) — this models the PC decision itself.',
|
|
13
13
|
tools: [
|
|
14
14
|
"Read",
|
|
15
15
|
"Write",
|
|
@@ -67,7 +67,15 @@ prose-only read can.
|
|
|
67
67
|
panel's only legitimate source for the readability/structure signal.
|
|
68
68
|
*(Scorecard: this gate is row **\`panel\`**, and it declares **\`structure\`** (tighten-paper) + **\`writing\`**
|
|
69
69
|
(grade-paper-writing) as its required inputs.)*
|
|
70
|
-
3. **Pick 3 (or 4) reviewer lenses** that a real PC for THIS venue would field.
|
|
70
|
+
3. **Pick 3 (or 4) reviewer lenses** that a real PC for THIS venue would field. One is fixed; the rest
|
|
71
|
+
come from the menu, by fit:
|
|
72
|
+
- **Genre/register reviewer — MANDATORY on every panel, at every venue.** Confidence 3 (knows the
|
|
73
|
+
area, not this topic), reads ONLY the abstract, the introduction and the conclusion, and judges
|
|
74
|
+
whether this reads as a paper at this venue: whose result each sentence states, whether the
|
|
75
|
+
relations between claims are signposted or left to infer, and whether anything is an aphorism,
|
|
76
|
+
a fragment or a colloquial turn. This is the reviewer who writes one short review, scores low,
|
|
77
|
+
and calls the paper a blog post — the one reviewer a panel of careful full readers never
|
|
78
|
+
predicts. Give it the three sections as text and the venue's name, nothing else.
|
|
71
79
|
- **Methods/stats skeptic** — sample size, construct validity, multiple comparisons, overclaims,
|
|
72
80
|
whether the CIs support the claims.
|
|
73
81
|
- **Domain practitioner** — relevance to the CFP, novelty vs the nearest prior work, does the
|
|
@@ -83,7 +91,13 @@ prose-only read can.
|
|
|
83
91
|
4. **Spawn them in PARALLEL, each on a separate model instance** (e.g. Fable via subagent), each blind
|
|
84
92
|
to the others — independence is the whole point. Give each: the paper (\`.tex\`/PDF), the artifact
|
|
85
93
|
path, the quoted rubric, its lens, **and the two step-2 artifacts (persona stall inventory +
|
|
86
|
-
tighten-paper structural verdict)
|
|
94
|
+
tighten-paper structural verdict)** — the genre/register reviewer gets only its three sections.
|
|
95
|
+
Force a filled review form (below).
|
|
96
|
+
|
|
97
|
+
🔴 **No reviewer is shown a previous round** — not its scores, not its reviews, not "the panel
|
|
98
|
+
gave 3.25 last time", not a list of what changed since. A reviewer who is told the last score
|
|
99
|
+
anchors on it (*"up from the 3.25 the earlier rounds reported"*), and the rounds then converge on
|
|
100
|
+
each other instead of on the paper. Comparing rounds is the chair's job, after the reviews land.
|
|
87
101
|
5. **Synthesize the meta-review yourself** (PC chair) once all land — see output.
|
|
88
102
|
|
|
89
103
|
## Each reviewer returns a review form
|
|
@@ -106,10 +120,19 @@ prose-only read can.
|
|
|
106
120
|
- One-line meta: does this belong at this venue?
|
|
107
121
|
|
|
108
122
|
## The meta-review (PC-chair synthesis) — the actual deliverable
|
|
109
|
-
- **Score table:** each reviewer × each criterion + overall + confidence, at a glance
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
- **Score table:** each reviewer × each criterion + overall + confidence, at a glance, and under it
|
|
124
|
+
the overall score's **min / median / max** across reviewers — never a mean alone, which hides the
|
|
125
|
+
one low review that decides a workshop.
|
|
126
|
+
- **Decision:** for a **workshop**, the decision follows the **MIN**: a workshop PC with three
|
|
127
|
+
reviews and a short discussion does not overrule a reject, so one Reject or Strong Reject sinks the
|
|
128
|
+
paper however confident the others are. For a conference, the rec the PC discussion converges on,
|
|
129
|
+
weighted by confidence, with the min named beside it. Either way, a **calibrated accept probability**
|
|
130
|
+
for this venue and edition.
|
|
131
|
+
- 🔴 **One reviewer is not a panel.** A run with fewer than three reviews and a meta-review — a
|
|
132
|
+
Venue-fit spot-check, a single re-review, a panel whose other seats did not return — is recorded as
|
|
133
|
+
what it is (\`venue-fit\`, \`single review\`), never in the scorecard's \`panel\` row. A single
|
|
134
|
+
review recorded as a panel verdict is how a paper reaches submission on an 88% that three real
|
|
135
|
+
reviewers turned into a reject.
|
|
113
136
|
- **Consensus must-fixes:** issues **≥2 reviewers independently raised** — these are the real ones;
|
|
114
137
|
apply before submitting.
|
|
115
138
|
- **Single-reviewer flags:** noted, triaged (fix if cheap, else defer to camera-ready).
|
|
@@ -215,9 +238,11 @@ reject-risk without rubber-stamping. For the real decision, escalate to the full
|
|
|
215
238
|
## The reproduce loop (why this beats a prose review)
|
|
216
239
|
This is "double-blind review as a real venue does it, WITH reproduction." The artifact-runner reviewer
|
|
217
240
|
is the difference-maker on both runs so far: it doesn't judge the paper's numbers, it re-derives them.
|
|
218
|
-
Run the panel, apply the consensus fixes, then **re-run the panel**
|
|
219
|
-
confirm the fixes landed and nothing regressed
|
|
220
|
-
|
|
241
|
+
Run the panel, apply the consensus fixes, then **re-run the panel** — same lenses, FRESH reviewers,
|
|
242
|
+
told nothing about the previous round — to confirm the fixes landed and nothing regressed; the
|
|
243
|
+
artifact-runner re-executes every harness each round. The chair, not the reviewers, sets the new
|
|
244
|
+
reviews beside the old ones and checks that each earlier must-fix is gone. Track the min / median / max
|
|
245
|
+
progression in the review ledger. Stop when the panel converges to
|
|
221
246
|
accept and the artifact reproduces clean; don't loop past one confirming round.
|
|
222
247
|
|
|
223
248
|
## Provenance (two papers, battle-tested)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
documentClassLine,
|
|
4
4
|
venueClass,
|
|
5
|
+
type ClassCandidate,
|
|
5
6
|
type ClassLine,
|
|
6
7
|
type DocumentClass,
|
|
7
8
|
} from "../../domain/tex-document.ts";
|
|
@@ -52,11 +53,12 @@ const optionText = (src: string, part: readonly Node[]): string =>
|
|
|
52
53
|
.join(""),
|
|
53
54
|
);
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
/** Every `\documentclass` at the top of the tree, in source order. */
|
|
57
|
+
const classNodes = (t: ParsedTex): readonly Macro[] =>
|
|
58
|
+
t.root.content.filter(isDocumentClass);
|
|
57
59
|
|
|
58
60
|
/** A `\documentclass` node as a class line: empty, or the class with its options as read. */
|
|
59
|
-
function classLine(src: string, node: Macro):
|
|
61
|
+
function classLine(src: string, node: Macro): ClassCandidate {
|
|
60
62
|
const place = macroPlace(node);
|
|
61
63
|
const cls = collapse(textOf(mandatory(node)[0]?.content));
|
|
62
64
|
if (cls === "") return { kind: "empty", place };
|
|
@@ -67,13 +69,18 @@ function classLine(src: string, node: Macro): ClassLine {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
* The
|
|
71
|
-
*
|
|
72
|
-
*
|
|
72
|
+
* The `\documentclass` at the top of the tree: missing; empty (`\documentclass{}`); the class with
|
|
73
|
+
* each of its options as written (`foo={a,b}` stays one option, braces kept), whitespace collapsed,
|
|
74
|
+
* its place spanning the macro and its arguments; or, when there are several (behind a TeX switch),
|
|
75
|
+
* each of them as a candidate.
|
|
73
76
|
*/
|
|
74
77
|
export function documentClassOf(t: ParsedTex): ClassLine {
|
|
75
|
-
const
|
|
76
|
-
|
|
78
|
+
const lines = classNodes(t).map((n) => classLine(t.src, n));
|
|
79
|
+
const [first, second, ...rest] = lines;
|
|
80
|
+
if (first === undefined) return { kind: "missing" };
|
|
81
|
+
return second === undefined
|
|
82
|
+
? first
|
|
83
|
+
: { kind: "ambiguous", candidates: [first, second, ...rest] };
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
/**
|
|
@@ -118,15 +125,16 @@ function addOptions(
|
|
|
118
125
|
/**
|
|
119
126
|
* The source with its `\documentclass` line changed to satisfy `want` (see `venueClass`): another
|
|
120
127
|
* class is replaced by the template's line; the same class gains the options it lacks in place. A
|
|
121
|
-
* source whose class already satisfies `want`,
|
|
122
|
-
* returned as it is.
|
|
128
|
+
* source whose class already satisfies `want`, that has no class line the parser could place, or
|
|
129
|
+
* that has several, is returned as it is.
|
|
123
130
|
*/
|
|
124
131
|
export function replaceDocumentClass(
|
|
125
132
|
t: ParsedTex,
|
|
126
133
|
want: DocumentClass,
|
|
127
134
|
): string {
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
// Several lines behind a switch: which one builds is not known, so none is rewritten.
|
|
136
|
+
const [node, ...others] = classNodes(t);
|
|
137
|
+
if (node === undefined || others.length > 0) return t.src;
|
|
130
138
|
const line = classLine(t.src, node);
|
|
131
139
|
if (line.kind !== "class" || line.place.kind === "unplaced") return t.src;
|
|
132
140
|
const { start, end } = line.place.span;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** The files a source pulls in, and the body of a source that is a document of its own. */
|
|
2
|
+
import type * as Ast from "@unified-latex/unified-latex-types";
|
|
3
|
+
import type { Include } from "../../domain/paper-source.ts";
|
|
4
|
+
import type { Span } from "../../domain/tex-document.ts";
|
|
5
|
+
import { isDefinition } from "./hidden.ts";
|
|
6
|
+
import {
|
|
7
|
+
inPlace,
|
|
8
|
+
isNode,
|
|
9
|
+
macroPlace,
|
|
10
|
+
mandatory,
|
|
11
|
+
placeOf,
|
|
12
|
+
visited,
|
|
13
|
+
type Macro,
|
|
14
|
+
type Node,
|
|
15
|
+
} from "./nodes.ts";
|
|
16
|
+
import type { ParsedTex } from "./parse.ts";
|
|
17
|
+
|
|
18
|
+
const INCLUDE_MACROS: ReadonlyMap<string, Include["macro"]> = new Map([
|
|
19
|
+
["input", "input"],
|
|
20
|
+
["include", "include"],
|
|
21
|
+
["subfile", "subfile"],
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The path an include names, as written: its braced argument, or — TeX's own form, `\input name` —
|
|
26
|
+
* the name up to the next space. unified-latex gives the unbraced form an argument of the first
|
|
27
|
+
* token only (`sections` of `sections/a`), so the source is read for it.
|
|
28
|
+
*/
|
|
29
|
+
function argumentText(src: string, m: Macro): string | null {
|
|
30
|
+
const start = mandatory(m)[0]?.content[0]?.position?.start.offset;
|
|
31
|
+
if (start === undefined) return null;
|
|
32
|
+
const rest = src.slice(start);
|
|
33
|
+
const stop = rest.search(src[start - 1] === "{" ? /\}/u : /[\s{}%]/u);
|
|
34
|
+
return stop < 0 ? rest : rest.slice(0, stop);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One include macro as an `Include`, or nothing when it names no file or has no place. */
|
|
38
|
+
function includeOf(src: string, m: Macro): readonly Include[] {
|
|
39
|
+
const macro = INCLUDE_MACROS.get(m.content);
|
|
40
|
+
const target = argumentText(src, m);
|
|
41
|
+
if (macro === undefined || target === null || target.trim() === "") return [];
|
|
42
|
+
return inPlace(macroPlace(m), (span) => [{ macro, target, span }]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Every `\input`, `\include` and `\subfile` of the source, in source order — not in a comment (a
|
|
47
|
+
* comment is a node of its own) and not in a macro definition's body, which is read where the macro
|
|
48
|
+
* is used, and nothing here expands macros.
|
|
49
|
+
*/
|
|
50
|
+
export const includesOf = (t: ParsedTex): readonly Include[] =>
|
|
51
|
+
visited(t.root, isDefinition)
|
|
52
|
+
.filter(isNode)
|
|
53
|
+
.flatMap((n) => (n.type === "macro" ? includeOf(t.src, n) : []));
|
|
54
|
+
|
|
55
|
+
const isDocument = (n: Node): n is Readonly<Ast.Environment> =>
|
|
56
|
+
n.type === "environment" && n.env === "document";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The span of the `document` environment's body, or null when the source has none. A macro's own
|
|
60
|
+
* position ends at its name, so its place is taken with its arguments: a body ending in
|
|
61
|
+
* `\input{sections/a}` ends at the closing brace.
|
|
62
|
+
*/
|
|
63
|
+
export const documentBodyOf = (t: ParsedTex): Span | null =>
|
|
64
|
+
(t.root.content.find(isDocument)?.content ?? [])
|
|
65
|
+
.flatMap((n) =>
|
|
66
|
+
inPlace(n.type === "macro" ? macroPlace(n) : placeOf(n), (span) => [
|
|
67
|
+
span,
|
|
68
|
+
]),
|
|
69
|
+
)
|
|
70
|
+
.reduce<Span | null>(
|
|
71
|
+
(body, span) =>
|
|
72
|
+
body === null ? span : { start: body.start, end: span.end },
|
|
73
|
+
null,
|
|
74
|
+
);
|