paperlint 4.1.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.
Files changed (113) hide show
  1. package/CLAUDE.md +24 -0
  2. package/CONTRIBUTING.md +7 -0
  3. package/dist/adapters/latex/document-class.d.ts +6 -5
  4. package/dist/adapters/latex/document-class.d.ts.map +1 -1
  5. package/dist/adapters/latex/document-class.js +18 -10
  6. package/dist/adapters/latex/document-class.js.map +1 -1
  7. package/dist/adapters/latex/includes.d.ts +12 -0
  8. package/dist/adapters/latex/includes.d.ts.map +1 -0
  9. package/dist/adapters/latex/includes.js +42 -0
  10. package/dist/adapters/latex/includes.js.map +1 -0
  11. package/dist/adapters/latex/index.d.ts +9 -5
  12. package/dist/adapters/latex/index.d.ts.map +1 -1
  13. package/dist/adapters/latex/index.js +8 -1
  14. package/dist/adapters/latex/index.js.map +1 -1
  15. package/dist/adapters/latex/nodes.d.ts +5 -1
  16. package/dist/adapters/latex/nodes.d.ts.map +1 -1
  17. package/dist/adapters/latex/nodes.js +6 -2
  18. package/dist/adapters/latex/nodes.js.map +1 -1
  19. package/dist/adapters/latex/parse.d.ts.map +1 -1
  20. package/dist/adapters/latex/parse.js +1 -0
  21. package/dist/adapters/latex/parse.js.map +1 -1
  22. package/dist/adapters/latex/prose.d.ts +8 -0
  23. package/dist/adapters/latex/prose.d.ts.map +1 -0
  24. package/dist/adapters/latex/prose.js +353 -0
  25. package/dist/adapters/latex/prose.js.map +1 -0
  26. package/dist/claim-provenance.d.ts +34 -0
  27. package/dist/claim-provenance.d.ts.map +1 -0
  28. package/dist/claim-provenance.js +146 -0
  29. package/dist/claim-provenance.js.map +1 -0
  30. package/dist/cli.d.ts.map +1 -1
  31. package/dist/cli.js +20 -0
  32. package/dist/cli.js.map +1 -1
  33. package/dist/domain/paper-source.d.ts +58 -0
  34. package/dist/domain/paper-source.d.ts.map +1 -0
  35. package/dist/domain/paper-source.js +109 -0
  36. package/dist/domain/paper-source.js.map +1 -0
  37. package/dist/domain/sentences.d.ts +26 -0
  38. package/dist/domain/sentences.d.ts.map +1 -0
  39. package/dist/domain/sentences.js +48 -0
  40. package/dist/domain/sentences.js.map +1 -0
  41. package/dist/domain/tex-document.d.ts +39 -4
  42. package/dist/domain/tex-document.d.ts.map +1 -1
  43. package/dist/domain/tex-document.js.map +1 -1
  44. package/dist/eslint-rules/paper-typography.d.ts +7 -0
  45. package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
  46. package/dist/eslint-rules/paper-typography.js +53 -1
  47. package/dist/eslint-rules/paper-typography.js.map +1 -1
  48. package/dist/ports/latex.d.ts +12 -1
  49. package/dist/ports/latex.d.ts.map +1 -1
  50. package/dist/register.d.ts +40 -0
  51. package/dist/register.d.ts.map +1 -0
  52. package/dist/register.js +134 -0
  53. package/dist/register.js.map +1 -0
  54. package/dist/tex-paper.d.ts +19 -0
  55. package/dist/tex-paper.d.ts.map +1 -0
  56. package/dist/tex-paper.js +105 -0
  57. package/dist/tex-paper.js.map +1 -0
  58. package/dist/tex-venue-rules.d.ts +7 -2
  59. package/dist/tex-venue-rules.d.ts.map +1 -1
  60. package/dist/tex-venue-rules.js +31 -17
  61. package/dist/tex-venue-rules.js.map +1 -1
  62. package/docs/rules/tex/claim-provenance.md +83 -0
  63. package/docs/rules/tex/missing-input.md +58 -0
  64. package/docs/rules/tex/register.md +77 -0
  65. package/docs/rules/tex/required-section.md +3 -1
  66. package/docs/rules/tex/template.md +12 -0
  67. package/docs/rules/tex/venue-leftover.md +4 -2
  68. package/docs/rules.md +3 -0
  69. package/eslint-rules/paper-typography.test.mjs +48 -1
  70. package/eslint-rules/paper-typography.ts +72 -1
  71. package/eslint.config.mjs +1 -0
  72. package/fixtures/real-markdown-paper/baseline.ts +8 -7
  73. package/package.json +2 -1
  74. package/skills/cold-read-diff/SKILL.md +31 -10
  75. package/skills/cold-read-diff/SKILL.md.spec.ts +30 -9
  76. package/skills/draft-paper/SKILL.md +2 -2
  77. package/skills/draft-paper/SKILL.md.spec.ts +1 -1
  78. package/skills/grade-paper-writing/SKILL.md +109 -44
  79. package/skills/grade-paper-writing/SKILL.md.spec.ts +108 -43
  80. package/skills/harden-paper/SKILL.md +35 -36
  81. package/skills/harden-paper/SKILL.md.spec.ts +34 -35
  82. package/skills/paper-adversarial-review/SKILL.md +2 -2
  83. package/skills/paper-adversarial-review/SKILL.md.spec.ts +1 -1
  84. package/skills/paper-pipeline/SKILL.md +3 -3
  85. package/skills/paper-pipeline/SKILL.md.spec.ts +2 -2
  86. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +6 -6
  87. package/skills/paper-pipeline/references/writing-craft.md +53 -18
  88. package/skills/pc-panel-review/SKILL.md +36 -11
  89. package/skills/pc-panel-review/SKILL.md.spec.ts +35 -10
  90. package/src/adapters/latex/document-class.ts +20 -12
  91. package/src/adapters/latex/includes.ts +66 -0
  92. package/src/adapters/latex/index.test.ts +175 -1
  93. package/src/adapters/latex/index.ts +14 -4
  94. package/src/adapters/latex/nodes.ts +11 -2
  95. package/src/adapters/latex/parse.ts +1 -0
  96. package/src/adapters/latex/prose.ts +484 -0
  97. package/src/claim-provenance.test.ts +352 -0
  98. package/src/claim-provenance.ts +217 -0
  99. package/src/cli.ts +20 -0
  100. package/src/domain/paper-source.test.ts +156 -0
  101. package/src/domain/paper-source.ts +233 -0
  102. package/src/domain/sentences.ts +67 -0
  103. package/src/domain/tex-document.ts +36 -3
  104. package/src/ports/latex.ts +13 -0
  105. package/src/register.test.ts +165 -0
  106. package/src/register.ts +177 -0
  107. package/src/rule-cases.test.ts +54 -4
  108. package/src/tex-paper.test.ts +131 -0
  109. package/src/tex-paper.ts +152 -0
  110. package/src/tex-venue-rules.test.ts +87 -5
  111. package/src/tex-venue-rules.ts +62 -30
  112. package/skills/harden-paper/fixtures/uncited-assertions-sample.md +0 -43
  113. package/skills/harden-paper/fixtures/uncited-assertions-sample.tex +0 -77
@@ -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 — nine dimensions … the fix for each."). Delete everything after.
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 nine-dimension
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 nine-dimension rubric and a persona cold-read stall pass — and A1
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 PERSONA cold-read stall pass (a committed non-academic persona subagent, per-section) whose stall inventory — not the rubric number — is the readability gate other skills consume (pc-panel-review, paper-adversarial-review, harden-paper).",
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 — nine dimensions 1–5 (Title, Abstract, Intro architecture, Structure, Prose clarity, Jargon discipline, Landing-the-point, Figure economy, Honesty-without-hedge-stacking) against how the best-written papers read (Peyton Jones, McEnerney, Gopen & Swan; exemplars Trusting Trust, Carlini, Greshake), naming the offending sentence and the fix for each.",
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).",
@@ -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 — 9 dimensions, score 1–5 (anchors), most-severe first
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.** Score each,
85
- name the offending sentence/section, write the fix.
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
- boilerplate roadmap, OR a wall — one giant unbroken paragraph the reader's eye slides off. 5 = each
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.** Replacing a coined term with a plainer noun inside
237
- an otherwise stiff academic sentence leaves the sentence academic — the reader still stalls on the _register_, not
238
- just the word (GateBench 2026-07-25: a term-level de-jargon pass cleared the flagged words but the author still hit
239
- sections "way too academicy for 0 reason"). The fix: rewrite the whole SENTENCE the way you'd say it to a colleague
240
- at a whiteboard — subject–verb–object, active voice, one clause, contractions fine, no nominalizations ("performs
241
- an evaluation of" → "checks"). **Read each rewritten sentence ALOUD: if you'd never say it that way to a person,
242
- it's still academic.** E.g. "We therefore position the guard as a defense against trigger~A and a measurement
243
- instrument, not an injection defense" → "So the guard is there to catch accidents and to measure the problem — it
244
- is not built to stop an attacker." A word-swap that leaves the academic sentence shape is a HALF-fix; the
245
- deliverable is plain-spoken sentences, and a de-jargon/tighten pass is not done until the prose reads spoken.
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 engaging and human-readable — write for a smart non-specialist who will
380
- read it willingly, not to sound academic.** The test (the author's 10-second rule): would a strong engineer who is
381
- NOT a researcher get this sentence in 10 seconds? If not, it's too dense. The jargon dimension (#6) and the
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**; kill the "organized as follows" roadmap.
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:b9feb099347291ff compiled from skills/pc-panel-review/SKILL.md.spec.ts -->
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. Menu — choose by fit:
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)**. Force a filled review form (below).
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
- - **Decision:** the rec the PC discussion converges on (weight by confidence; a lone low-confidence
95
- outlier doesn't sink two confident accepts), + a **calibrated accept probability** for this venue
96
- and edition.
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** (same lenses, told what changed) to
203
- confirm the fixes landed and nothing regressed — the artifact-runner re-executes every harness each
204
- round. Track the accept-probability progression in the review ledger. Stop when the panel converges to
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. Menu — choose by fit:
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)**. Force a filled review form (below).
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
- - **Decision:** the rec the PC discussion converges on (weight by confidence; a lone low-confidence
111
- outlier doesn't sink two confident accepts), + a **calibrated accept probability** for this venue
112
- and edition.
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** (same lenses, told what changed) to
219
- confirm the fixes landed and nothing regressed — the artifact-runner re-executes every harness each
220
- round. Track the accept-probability progression in the review ledger. Stop when the panel converges to
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
- const classNode = (t: ParsedTex): Macro | undefined =>
56
- t.root.content.find(isDocumentClass);
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): ClassLine {
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 first `\documentclass` at the top of the tree: missing; empty (`\documentclass{}`); or the
71
- * class with each of its options as written (`foo={a,b}` stays one option, braces kept), whitespace
72
- * collapsed. Its place spans the macro and its arguments.
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 node = classNode(t);
76
- return node === undefined ? { kind: "missing" } : classLine(t.src, node);
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`, or that has no class line the parser could place, is
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
- const node = classNode(t);
129
- if (node === undefined) return t.src;
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,66 @@
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
+ /** The span of the `document` environment's body, or null when the source has none. */
59
+ export const documentBodyOf = (t: ParsedTex): Span | null =>
60
+ (t.root.content.find(isDocument)?.content ?? [])
61
+ .flatMap((n) => inPlace(placeOf(n), (span) => [span]))
62
+ .reduce<Span | null>(
63
+ (body, span) =>
64
+ body === null ? span : { start: body.start, end: span.end },
65
+ null,
66
+ );