paperlint 9.0.0 → 10.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.
Files changed (190) hide show
  1. package/.github/CLAUDE.md +22 -0
  2. package/.github/CLAUDE.md.spec.ts +20 -0
  3. package/CLAUDE.md +59 -515
  4. package/CONTRIBUTING.md +2 -2
  5. package/README.md +68 -128
  6. package/dist/CLAUDE.md.spec.d.ts +3 -0
  7. package/dist/CLAUDE.md.spec.d.ts.map +1 -0
  8. package/dist/CLAUDE.md.spec.js +62 -0
  9. package/dist/CLAUDE.md.spec.js.map +1 -0
  10. package/dist/build.d.ts +1 -1
  11. package/dist/build.js +2 -2
  12. package/dist/cli.d.ts +5 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +52 -6
  15. package/dist/cli.js.map +1 -1
  16. package/dist/domain/layout-tree.d.ts +19 -0
  17. package/dist/domain/layout-tree.d.ts.map +1 -0
  18. package/dist/domain/layout-tree.js +32 -0
  19. package/dist/domain/layout-tree.js.map +1 -0
  20. package/dist/domain/lookup-cache.d.ts +2 -2
  21. package/dist/domain/lookup-cache.js +2 -2
  22. package/dist/domain/paper-layout.d.ts +81 -0
  23. package/dist/domain/paper-layout.d.ts.map +1 -0
  24. package/dist/domain/paper-layout.js +259 -0
  25. package/dist/domain/paper-layout.js.map +1 -0
  26. package/dist/domain/repro-layout.d.ts +80 -0
  27. package/dist/domain/repro-layout.d.ts.map +1 -0
  28. package/dist/domain/repro-layout.js +247 -0
  29. package/dist/domain/repro-layout.js.map +1 -0
  30. package/dist/domain/scaffold-layout.d.ts +28 -0
  31. package/dist/domain/scaffold-layout.d.ts.map +1 -0
  32. package/dist/domain/scaffold-layout.js +81 -0
  33. package/dist/domain/scaffold-layout.js.map +1 -0
  34. package/dist/eslint-rules/CLAUDE.md.spec.d.ts +3 -0
  35. package/dist/eslint-rules/CLAUDE.md.spec.d.ts.map +1 -0
  36. package/dist/eslint-rules/CLAUDE.md.spec.js +39 -0
  37. package/dist/eslint-rules/CLAUDE.md.spec.js.map +1 -0
  38. package/dist/eslint-rules/install-path-literals.d.ts +1 -1
  39. package/dist/eslint-rules/install-path-literals.js +4 -4
  40. package/dist/eslint-rules/install-path-literals.js.map +1 -1
  41. package/dist/eslint-rules/pdf-last-page-balance.js +1 -1
  42. package/dist/eslint-rules/prereg-coverage.d.ts +98 -0
  43. package/dist/eslint-rules/prereg-coverage.d.ts.map +1 -0
  44. package/dist/eslint-rules/prereg-coverage.js +274 -0
  45. package/dist/eslint-rules/prereg-coverage.js.map +1 -0
  46. package/dist/folder-layout-hook.d.ts +21 -0
  47. package/dist/folder-layout-hook.d.ts.map +1 -0
  48. package/dist/folder-layout-hook.js +117 -0
  49. package/dist/folder-layout-hook.js.map +1 -0
  50. package/dist/folder-layout-rule.d.ts +22 -0
  51. package/dist/folder-layout-rule.d.ts.map +1 -0
  52. package/dist/folder-layout-rule.js +107 -0
  53. package/dist/folder-layout-rule.js.map +1 -0
  54. package/dist/init.js +1 -1
  55. package/dist/init.js.map +1 -1
  56. package/dist/lib/agent-cli-version.js +1 -1
  57. package/dist/lib/agent-cli-version.js.map +1 -1
  58. package/dist/new-paper.d.ts +4 -1
  59. package/dist/new-paper.d.ts.map +1 -1
  60. package/dist/new-paper.js +40 -6
  61. package/dist/new-paper.js.map +1 -1
  62. package/dist/references.d.ts +1 -1
  63. package/dist/references.d.ts.map +1 -1
  64. package/dist/references.js +2 -2
  65. package/dist/references.js.map +1 -1
  66. package/dist/repro-layout-rule.d.ts +16 -0
  67. package/dist/repro-layout-rule.d.ts.map +1 -0
  68. package/dist/repro-layout-rule.js +81 -0
  69. package/dist/repro-layout-rule.js.map +1 -0
  70. package/dist/tex-requirements.js +1 -1
  71. package/dist/toolchain.js +1 -1
  72. package/docs/ci.md +60 -0
  73. package/docs/configuration.md +2 -0
  74. package/docs/e2e.md +3 -3
  75. package/docs/incidents.md +5 -5
  76. package/docs/install.md +20 -16
  77. package/docs/layout.md +154 -0
  78. package/docs/optional-rules.md +5 -4
  79. package/docs/references.md +5 -2
  80. package/docs/rules/paper/allowed-files-only.md +192 -0
  81. package/docs/rules/repro/allowed-files-only.md +128 -0
  82. package/docs/rules/study/declare-sample.md +146 -0
  83. package/docs/rules.md +53 -50
  84. package/docs/skills.md +6 -3
  85. package/docs/venues.md +94 -0
  86. package/eslint-rules/CLAUDE.md +44 -0
  87. package/eslint-rules/CLAUDE.md.spec.ts +43 -0
  88. package/eslint-rules/install-path-literals.harness.mjs +1 -1
  89. package/eslint-rules/install-path-literals.ts +4 -4
  90. package/eslint-rules/pdf-last-page-balance.ts +1 -1
  91. package/eslint-rules/prereg-coverage.test.ts +448 -0
  92. package/eslint-rules/prereg-coverage.ts +419 -0
  93. package/eslint.config.mjs +6 -3
  94. package/hooks/CLAUDE.md +34 -0
  95. package/hooks/CLAUDE.md.spec.ts +30 -0
  96. package/lib/agent-cli-version.ts +1 -1
  97. package/lib/skill-trigger-cases.harness.mjs +51 -5
  98. package/lib/skill-trigger-cases.mjs +53 -15
  99. package/package.json +2 -2
  100. package/plugin/CLAUDE.md +22 -0
  101. package/plugin/CLAUDE.md.spec.ts +22 -0
  102. package/plugin/hooks/hooks.json +9 -0
  103. package/scripts/coverage-config.test.ts +2 -1
  104. package/skills/CLAUDE.md +19 -0
  105. package/skills/CLAUDE.md.spec.ts +18 -0
  106. package/skills/analyze-sibling-paper/SKILL.md +2 -2
  107. package/skills/analyze-sibling-paper/SKILL.md.spec.ts +1 -1
  108. package/skills/build-benchmark/SKILL.md +14 -1
  109. package/skills/build-benchmark/SKILL.md.spec.ts +13 -0
  110. package/skills/build-label-page/SKILL.md +118 -0
  111. package/skills/build-label-page/SKILL.md.spec.ts +129 -0
  112. package/skills/build-label-page/build-label-page.eval.mjs +15 -0
  113. package/skills/build-label-page/build-label-page.test.ts +959 -0
  114. package/skills/build-label-page/references/checklist.md +148 -0
  115. package/skills/build-label-page/references/example-generator.py +803 -0
  116. package/skills/find-venue/SKILL.md +5 -3
  117. package/skills/find-venue/SKILL.md.spec.ts +4 -2
  118. package/skills/grade-paper-writing/SKILL.md +7 -7
  119. package/skills/grade-paper-writing/SKILL.md.spec.ts +6 -6
  120. package/skills/map-prior-work/SKILL.md +2 -2
  121. package/skills/map-prior-work/SKILL.md.spec.ts +1 -1
  122. package/skills/paper-pipeline/SKILL.md +8 -6
  123. package/skills/paper-pipeline/SKILL.md.spec.ts +7 -5
  124. package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +19 -9
  125. package/skills/paper-pipeline/references/acceptance-gate.md +19 -19
  126. package/skills/paper-pipeline/references/artifact-checklist.md +5 -0
  127. package/skills/paper-pipeline/references/pipeline-status-template.md +30 -1
  128. package/skills/paper-pipeline/repro/2026-08-07-fork-check.log +1 -1
  129. package/skills/paper-pipeline/repro/2026-08-07-fork-check2.log +1 -1
  130. package/skills/paper-pipeline/repro/2026-08-07-language-eval-pilot.log +1 -1
  131. package/skills/paper-pipeline/repro/2026-08-07-language-eval-raw.log +1 -1
  132. package/skills/paper-pipeline/repro/2026-08-08-framing-vs-vocabulary-oracle.log +48 -48
  133. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-A6-oracle.log +8 -8
  134. package/skills/paper-pipeline/repro/2026-08-08-grade-paper-writing-ablation-oracle.log +48 -48
  135. package/skills/paper-pipeline/repro/2026-08-08-parent-replication-gpw.log +2 -2
  136. package/skills/paper-pipeline/repro/2026-08-08-parent-replication.mjs +3 -3
  137. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-preflight-after.log +81 -0
  138. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-preflight-before.log +81 -0
  139. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-ablation-setupdiff.log +59 -0
  140. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-trigger.jsonl +1 -0
  141. package/skills/paper-pipeline/repro/2026-10-13-grade-paper-writing-trigger.log +22 -0
  142. package/skills/paper-pipeline/repro/README.md +4 -0
  143. package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +2 -2
  144. package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +2 -2
  145. package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +4 -2
  146. package/skills/paper-pipeline/scripts/generated-code.harness.mjs +7 -2
  147. package/skills/paper-pipeline/scripts/generated-code.mjs +8 -4
  148. package/skills/paper-pipeline/scripts/generated-code.test.ts +15 -1
  149. package/skills/paper-status/SKILL.md +2 -2
  150. package/skills/paper-status/SKILL.md.spec.ts +1 -1
  151. package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +21 -102
  152. package/skills/study-accepted-papers/SKILL.md +163 -161
  153. package/skills/study-accepted-papers/SKILL.md.spec.ts +167 -165
  154. package/skills/study-accepted-papers/references/base-rates.md +69 -0
  155. package/skills/study-accepted-papers/references/mechanisms.md +181 -0
  156. package/skills/verify-citations/SKILL.md +2 -2
  157. package/skills/verify-citations/SKILL.md.spec.ts +1 -1
  158. package/src/CLAUDE.md +23 -1
  159. package/src/CLAUDE.md.spec.ts +72 -0
  160. package/src/build.ts +2 -2
  161. package/src/cli.harness.mjs +42 -0
  162. package/src/cli.test.ts +90 -6
  163. package/src/cli.ts +78 -5
  164. package/src/domain/layout-tree.test.ts +32 -0
  165. package/src/domain/layout-tree.ts +47 -0
  166. package/src/domain/lookup-cache.ts +2 -2
  167. package/src/domain/paper-layout.test.ts +264 -0
  168. package/src/domain/paper-layout.ts +336 -0
  169. package/src/domain/repro-layout.test.ts +222 -0
  170. package/src/domain/repro-layout.ts +311 -0
  171. package/src/domain/scaffold-layout.ts +104 -0
  172. package/src/folder-layout-hook.test.ts +214 -0
  173. package/src/folder-layout-hook.ts +141 -0
  174. package/src/folder-layout-rule.test.ts +235 -0
  175. package/src/folder-layout-rule.ts +151 -0
  176. package/src/hooks-settings.harness.ts +9 -8
  177. package/src/init.ts +1 -1
  178. package/src/layout-docs.test.ts +52 -0
  179. package/src/new-paper.harness.mjs +23 -1
  180. package/src/new-paper.test.ts +299 -6
  181. package/src/new-paper.ts +51 -5
  182. package/src/paper-files.test.ts +9 -6
  183. package/src/references.test.ts +5 -5
  184. package/src/references.ts +2 -2
  185. package/src/repro-layout-rule.test.ts +114 -0
  186. package/src/repro-layout-rule.ts +121 -0
  187. package/src/rule-cases.test.ts +98 -0
  188. package/src/tex-requirements.ts +1 -1
  189. package/src/toolchain.ts +1 -1
  190. package/templates/paper/PIPELINE-STATUS.md +41 -7
@@ -0,0 +1,81 @@
1
+
2
+ 26 skills installed → 25 competitors per run
3
+
4
+ ARMS — description length, word-set delta vs A0, mean prompt overlap
5
+ arm label chars words − + ovAH ovSH
6
+ A0 baseline (verbatim) 1371 103 0 0 0.85 0.78
7
+ A1 short (sentences 1-2 only) 618 51 52 0 0.71 0.78
8
+ A2 no-subordination 1189 94 9 0 0.85 0.78
9
+ A3 no-negation 1141 92 11 0 0.85 0.78
10
+ A4 no-machinery 670 45 58 0 0.44 0.35
11
+ A5 renamed (description identical) 1371 103 0 0 0.85 0.78
12
+ A6 persona-only (A1's mirror) 298 28 75 0 0.23 0.42
13
+
14
+ ⚠ ovAH/ovSH FALL AS TEXT IS DELETED — caveat 2 in the header, quantified. The deletion arms carry
15
+ a vocabulary handicap against the parent's dominant factor. A rising deletion arm rose DESPITE it;
16
+ a flat one may be a real gain cancelled by it, and this design cannot tell. A5 is the only arm
17
+ with zero handicap (identical word set).
18
+
19
+ WORD-SET DELTAS (stemmed content words removed from A0 — none may be added)
20
+
21
+ A0 baseline (verbatim)
22
+ removed (0): —
23
+ added (0): —
24
+
25
+ A1 short (sentences 1-2 only)
26
+ removed (52): run cold stall pas committ persona non academic engineer per section workshop reviewer skimm introduction conclusion inventory rubric number readability gate skill consume panel review adversarial harden default blind multi grader mandate claim preservation diff row content defect structural cut plan tighten first bloat strength reward study compose call generative counterpart render
27
+ added (0): —
28
+
29
+ A2 no-subordination
30
+ removed (9): inventory rubric number readability gate skill consume harden call
31
+ added (0): —
32
+
33
+ A3 no-negation
34
+ removed (11): content defect structural cut plan tighten first bloat strength reward study
35
+ added (0): —
36
+
37
+ A4 no-machinery
38
+ removed (58): title abstract intro architecture structure prose clarity discipline land point figure economy honesty without hedge stack register distance peyton jone mcenerney gopen swan exemplar trust carlini greshake cold stall pas committ persona non academic engineer per section workshop reviewer skimm introduction conclusion inventory rubric number readability gate skill consume default blind multi grader mandate claim preservation diff row
39
+ added (0): —
40
+
41
+ A5 renamed (description identical)
42
+ removed (0): —
43
+ added (0): —
44
+
45
+ A6 persona-only (A1's mirror)
46
+ removed (75): grade craft ten dimension title intro architecture structure prose clarity discipline land point figure economy honesty without hedge stack register distance target venue best written peyton jone mcenerney gopen swan exemplar trust carlini greshake accept nam offend sentence fix inventory rubric number readability gate skill consume panel review adversarial harden default blind multi grader mandate claim preservation diff row content defect structural cut plan tighten first bloat strength reward study compose call generative counterpart render
47
+ added (0): —
48
+
49
+ RESULTING DESCRIPTIONS
50
+
51
+ ── A0 (grade-paper-writing, 1371 chars)
52
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" / "grade the writing" on a draft. 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. 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). Defaults to a blind multi-grader panel; after fixes, mandates the claims-preservation diff (row `claims`). 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 content strength against what the venue rewards (study-accepted-papers). Compose with harden-paper (which calls it), draft-paper (generative counterpart), render-paper.
53
+
54
+ ── A1 (grade-paper-writing, 618 chars)
55
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" / "grade the writing" on a draft. 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.
56
+
57
+ ── A2 (grade-paper-writing, 1189 chars)
58
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" / "grade the writing" on a draft. 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. 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). Defaults to a blind multi-grader panel; after fixes, mandates the claims-preservation diff (row `claims`). 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 content strength against what the venue rewards (study-accepted-papers). Compose with draft-paper (generative counterpart), render-paper.
59
+
60
+ ── A3 (grade-paper-writing, 1141 chars)
61
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" / "grade the writing" on a draft. 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. 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). Defaults to a blind multi-grader panel; after fixes, mandates the claims-preservation diff (row `claims`). Compose with harden-paper (which calls it), draft-paper (generative counterpart), render-paper.
62
+
63
+ ── A4 (grade-paper-writing, 670 chars)
64
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" / "grade the writing" on a draft. Grades WRITING CRAFT only — ten dimensions 1–5 against how the best-written papers read AND against 2–3 accepted papers of the target venue, naming the offending sentence and the fix for each. 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 content strength against what the venue rewards (study-accepted-papers). Compose with harden-paper (which calls it), draft-paper (generative counterpart), render-paper.
65
+
66
+ ── A5 (paper-readability-review, 1371 chars)
67
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" / "grade the writing" on a draft. 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. 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). Defaults to a blind multi-grader panel; after fixes, mandates the claims-preservation diff (row `claims`). 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 content strength against what the venue rewards (study-accepted-papers). Compose with harden-paper (which calls it), draft-paper (generative counterpart), render-paper.
68
+
69
+ ── A6 (grade-paper-writing, 298 chars)
70
+ Use when asking "does this paper read well?" / "is the writing any good?" / "it reads like a wall of text and jargon" on a draft. 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).
71
+
72
+ building each arm's skills dir under <scratch>/gpw-ablation (no real file is written) …
73
+ A0 → <scratch>/gpw-ablation/arm-A0
74
+ A1 → <scratch>/gpw-ablation/arm-A1
75
+ A2 → <scratch>/gpw-ablation/arm-A2
76
+ A3 → <scratch>/gpw-ablation/arm-A3
77
+ A4 → <scratch>/gpw-ablation/arm-A4
78
+ A5 → <scratch>/gpw-ablation/arm-A5
79
+ A6 → <scratch>/gpw-ablation/arm-A6
80
+
81
+ ALL PREFLIGHT GUARDS PASSED. No tokens spent. Run --mode main next.
@@ -0,0 +1,59 @@
1
+
2
+ 26 skills installed → 25 competitors per run
3
+
4
+ ARMS — description length, word-set delta vs A0, mean prompt overlap
5
+ arm label chars words − + ovAH ovSH
6
+ A0 baseline (verbatim) 1371 103 0 0 0.85 0.78
7
+ A1 short (sentences 1-2 only) 618 51 52 0 0.71 0.78
8
+ A2 no-subordination 1189 94 9 0 0.85 0.78
9
+ A3 no-negation 1141 92 11 0 0.85 0.78
10
+ A4 no-machinery 670 45 58 0 0.44 0.35
11
+ A5 renamed (description identical) 1371 103 0 0 0.85 0.78
12
+ A6 persona-only (A1's mirror) 298 28 75 0 0.23 0.42
13
+
14
+ ⚠ ovAH/ovSH FALL AS TEXT IS DELETED — caveat 2 in the header, quantified. The deletion arms carry
15
+ a vocabulary handicap against the parent's dominant factor. A rising deletion arm rose DESPITE it;
16
+ a flat one may be a real gain cancelled by it, and this design cannot tell. A5 is the only arm
17
+ with zero handicap (identical word set).
18
+
19
+ HARNESS OPTIONS — this file vs the parent (extracted from both sources)
20
+ option ablation parent
21
+ allowedTools ["Skill" ["Skill"
22
+ trials TRIALS TRIALS
23
+ concurrency 3 3
24
+ spacingSec 2 2
25
+ timeoutMs 180000 180000
26
+ minPrompts 4 4
27
+ fixture /.exec(src)?.[0] ?? "" FIXTURE
28
+ skillsDir dir SKILLS_DIR
29
+
30
+ fixture sha256/16 ablation 89d1b720c4cedde6
31
+ FIXTURE source block identical to the parent's: YES
32
+ fired predicate identical (modulo the id it matches): YES
33
+ firedSkills() helper identical: YES
34
+
35
+ ARM DIR INTEGRITY — sha256/16 of each SKILL.md frontmatter vs the real .claude/skills
36
+ A0 26 skills · unintended frontmatter diffs: none
37
+ A1 26 skills · unintended frontmatter diffs: none
38
+ A2 26 skills · unintended frontmatter diffs: none
39
+ A3 26 skills · unintended frontmatter diffs: none
40
+ A4 26 skills · unintended frontmatter diffs: none
41
+ A5 26 skills · unintended frontmatter diffs: none
42
+ A6 26 skills · unintended frontmatter diffs: none
43
+
44
+ ROW BY ROW — parent (8 prompts) vs this file's A0 (8 prompts)
45
+ cell parent A0 Δ prompt
46
+ AH 0/3 1/3 +1 grade the writing craft in this draft and name the off
47
+ AH 3/3 3/3 0 run the blind multi-grader panel over the prose clarit
48
+ AH 0/3 3/3 +3 score the title, the abstract and the intro architectu
49
+ AH 0/3 0/3 0 grade how well this reads, hedge stacking included, an
50
+ SH 0/3 0/3 0 the abstract reads like a wall of jargon
51
+ SH 0/3 2/3 +2 the intro architecture is fine but the prose clarity i
52
+ SH 0/3 2/3 +2 there is hedge stacking everywhere and the point never
53
+ SH 2/3 2/3 0 the writing does not read well even though the draft i
54
+ 5/24 13/24 +8 TOTAL
55
+
56
+ 3 of 8 prompts moved by ≥2 of 3 trials. Spread across many prompts ⇒ a global shift
57
+ (session, load, model routing). Concentrated in one or two ⇒ something prompt-specific.
58
+
59
+ SETUPDIFF COMPLETE. No tokens spent.
@@ -0,0 +1 @@
1
+ {"skill":"grade-paper-writing","model":"sonnet","trials":1,"rate":0.875,"falsePositiveRate":0,"precision":1,"competitors":25,"perPrompt":[{"prompt":"оцени как написано — читается как стена жаргона","rate":1},{"prompt":"is the writing any good or does it read like shit","rate":1},{"prompt":"abstract звучит криво хотя по смыслу всё на месте, дай оценку прозе","rate":1},{"prompt":"grade the craft: title, abstract, sentence clarity, hedge stacking","rate":1},{"prompt":"прочитай черновик и скажи честно, нормально ли он читается","rate":0},{"prompt":"the reviewer says our paper reads like a blog post — is he right about the prose","rate":1},{"prompt":"каждое предложение по отдельности понятно, а абзацы всё равно сливаются в сплошную простыню","rate":1},{"prompt":"grade the prose in section 3 and give me the sentences to rewrite","rate":1}]}
@@ -0,0 +1,22 @@
1
+ Spent: 1,807,963 tokens (88 in · 12.6k out · 1562.4k cache) · ~$1.30 API-equivalent
2
+ Billed to: your Claude subscription — $0 metered ✅
3
+
4
+ grade-paper-writing — prose craft — the sentence is the unit, NOT the section [model: sonnet]
5
+ trigger-rate: 88% (8 runs)
6
+ 1.00 оцени как написано — читается как стена жаргона
7
+ 1.00 is the writing any good or does it read like shit
8
+ 1.00 abstract звучит криво хотя по смыслу всё на месте, дай оценк
9
+ 1.00 grade the craft: title, abstract, sentence clarity, hedge st
10
+ 0.00 прочитай черновик и скажи честно, нормально ли он читается
11
+ 1.00 the reviewer says our paper reads like a blog post — is he r
12
+ 1.00 каждое предложение по отдельности понятно, а абзацы всё равн
13
+ 1.00 grade the prose in section 3 and give me the sentences to re
14
+ false-positive: 0% precision: 100%
15
+ 0.00 [irrelevant] статья раздулась, середина провисает — что резат
16
+ 0.00 [irrelevant] does the paper actually carry a reader to one co
17
+ 0.00 [irrelevant] поправил формулировки в 4.2, прогони свежим чита
18
+ 0.00 [irrelevant] what is our desk reject risk and where do we ove
19
+ 0.00 [irrelevant] какая вероятность принятия у этой статьи, если ч
20
+ 0.00 [irrelevant] статья готова к сабмиту или нет
21
+ 0.00 [irrelevant] посмотри что принимали на этой площадке и чем те
22
+ whole-harness: measured against 25 competing skill(s)
@@ -25,6 +25,10 @@ the session.
25
25
  | `2026-08-08-parent-replication.mjs` | the replication driver | Runs `framing-vs-vocabulary.eval.mjs` **itself** with two asserted patches (main loop → this skill + AH/SH; output path), everything else untouched, against the real `.claude/skills`. Written because the ablation's control contradicted the parent. |
26
26
  | `2026-08-08-parent-replication-gpw.log` | that driver's run | 🔴 **The parent's own code returns AH 9/12 + SH 3/12 = 12/24 (50%)** hours after the same code returned 3/12 + 2/12 = 5/24 (21%). |
27
27
  | `2026-08-08-parent-replication-gpw.json` | per-run rows for the above | The parent's own row schema, so it diffs directly against `2026-08-08-framing-vs-vocabulary.json`. |
28
+ | `2026-10-13-grade-paper-writing-trigger.log` | `grade-paper-writing/grade-paper-writing.eval.mjs`, the rewritten 499-char description (ledger `description_sha` dc41f0994474b368) | 1 trial, sonnet, 25 competing skills. Recall **7/8 (88%)**: the four measured prompts 4/4, the four new ones 3/4 (missed: «прочитай черновик и скажи честно, нормально ли он читается»). False positives **0/7**, one prompt from each collider. One trial is a smoke run, not a rate. |
29
+ | `2026-10-13-grade-paper-writing-trigger.jsonl` | per-prompt rows for the above | The kit's `VIGILES_RESULTS` row: rate per should-fire prompt. |
30
+ | `2026-10-13-grade-paper-writing-ablation-preflight-before.log`, `…-after.log` | `grade-paper-writing-ablation.eval.mjs --mode preflight`, **free**, before and after its A0 was pinned | Byte-identical: every arm builds from the same text after the live description was rewritten. Scratch paths replaced by `<scratch>`. |
31
+ | `2026-10-13-grade-paper-writing-ablation-setupdiff.log` | the same file's `--mode setupdiff`, **free**, after the pin | Passes; the parent-vs-A0 row-by-row is the recorded 2026-08-08 data, unchanged. |
28
32
 
29
33
  ## How to read these, and the trap in them
30
34
 
@@ -400,7 +400,7 @@ process.on("exit", () => rmSync(TMP, { recursive: true, force: true }));
400
400
  {
401
401
  const { spawnSync } = await import("node:child_process");
402
402
  const dir = join(TMP, "e2e");
403
- mkdirSync(join(dir, "repro"), { recursive: true });
403
+ mkdirSync(join(dir, "repro", "data"), { recursive: true });
404
404
  const paper =
405
405
  "@misc{lovelace2025,\n author={Lovelace, Ada and Hopper, Grace},\n title={A Study},\n" +
406
406
  " note={arXiv:2503.09002},\n year={2025}}\n";
@@ -411,7 +411,7 @@ process.on("exit", () => rmSync(TMP, { recursive: true, force: true }));
411
411
  );
412
412
  built(dir);
413
413
  writeFileSync(
414
- join(dir, "repro", "refs-cache.json"),
414
+ join(dir, "repro", "data", "refs-cache.json"),
415
415
  JSON.stringify({
416
416
  "arxiv:2503.09002": {
417
417
  httpStatus: 200,
@@ -46,7 +46,7 @@
46
46
  * only one that returns a STRUCTURED name; with any other the surname would have to be cut out by
47
47
  * hand, that is, we would be writing ourselves the very half that was broken.
48
48
  *
49
- * 🔴 THE CACHE IS EVIDENCE, NOT A SECOND DECLARATION. `<paper>/repro/refs-cache.json` keeps the RAW
49
+ * 🔴 THE CACHE IS EVIDENCE, NOT A SECOND DECLARATION. `<paper>/repro/data/refs-cache.json` keeps the RAW
50
50
  * BODIES of the registries' responses and is committed (for `<paper-a>` it has been in git since
51
51
  * 16.08). It cannot be made up — only refreshed from the registry. The facts in `_build/` are
52
52
  * derived from it and from the bibliography, which is why `_build/` is in `.gitignore` while the
@@ -450,7 +450,7 @@ async function main(argv) {
450
450
  const { paperDir, texts } = found;
451
451
  const cachePath = opt("cache")
452
452
  ? resolve(opt("cache"))
453
- : join(paperDir, "repro", "refs-cache.json");
453
+ : join(paperDir, "repro", "data", "refs-cache.json");
454
454
  const out = opt("out")
455
455
  ? resolve(opt("out"))
456
456
  : join(paperDir, "_build", "refs.facts.json");
@@ -236,7 +236,9 @@ test("online: hits are fetched and cached, a transport failure is named and stay
236
236
  stdout: r.stdout,
237
237
  stderr: r.stderr,
238
238
  cached: Object.keys(
239
- JSON.parse(readFileSync(join(root, "paper/repro/refs-cache.json"))),
239
+ JSON.parse(
240
+ readFileSync(join(root, "paper/repro/data/refs-cache.json")),
241
+ ),
240
242
  ),
241
243
  },
242
244
  {
@@ -265,7 +267,7 @@ test("the paper file itself as the target, with --cache, --out and --offline", (
265
267
  const r = cli([
266
268
  join(root, "paper/refs.bib"),
267
269
  "--offline",
268
- `--cache=${join(root, "paper/repro/refs-cache.json")}`,
270
+ `--cache=${join(root, "paper/repro/data/refs-cache.json")}`,
269
271
  `--out=${join(root, "out/facts.json")}`,
270
272
  ]);
271
273
  assert.deepEqual(r, {
@@ -79,8 +79,13 @@ function fixture({ scripts = {}, allow = null, bundle = {}, at = "repro" }) {
79
79
  mkdirSync(join(d, at, "artifact-anon", dirname(p)), { recursive: true });
80
80
  writeFileSync(join(d, at, "artifact-anon", p), src);
81
81
  }
82
- if (allow !== null)
83
- writeFileSync(join(d, at, "generated-code-grandfathered.txt"), allow);
82
+ if (allow !== null) {
83
+ mkdirSync(join(d, at, "tools"), { recursive: true });
84
+ writeFileSync(
85
+ join(d, at, "tools", "generated-code-grandfathered.txt"),
86
+ allow,
87
+ );
88
+ }
84
89
  return d;
85
90
  }
86
91
 
@@ -42,7 +42,7 @@
42
42
  * 🔴 AND THE SAME FALSE-POSITIVE DISCIPLINE AS THE REVERSE COVERAGE LEG. `repro/` is mostly not
43
43
  * analysis code, so an ignore set is unavoidable — and an ignore set applied in silence is how a
44
44
  * check becomes a green light. Every structural rule prints the names it swallowed, and
45
- * `repro/generated-code-grandfathered.txt` prints every row and every reason, on every run,
45
+ * `repro/tools/generated-code-grandfathered.txt` prints every row and every reason, on every run,
46
46
  * findings or none. Same reasoning as `check_grandfather` in `repro/paper_numbers.py`.
47
47
  */
48
48
  import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
@@ -97,8 +97,12 @@ const repro = roots[0];
97
97
  // model synthesised and is the most literal reading of "generated code" there is. Below that are
98
98
  // fixtures, working clones and raw transcripts; going deeper buys tens of thousands of files and no
99
99
  // analysis. This is a SCOPE and therefore a blind spot, and it is printed as one.
100
+ // `repro/tools/` (the paper's shared scripts, by the repro/ layout) is a root of its own: the layout
101
+ // moved `repro/<pkg>/src/…` to `repro/tools/<pkg>/src/…`, and counting tools/ as a level would have
102
+ // dropped that code out of the scan.
100
103
  const EXT = /\.(py|mjs|cjs|js)$/;
101
104
  const MAX_DEPTH = 3;
105
+ const TOOLS_DIR = "tools";
102
106
  const swallowed = new Map([
103
107
  [
104
108
  "the released bundle — `check-anon.sh` cat. 5 owns it, and it asks a different question there " +
@@ -155,13 +159,13 @@ const files = [];
155
159
  swallowed.get(RULE[2]).push(relative(repro, p));
156
160
  continue;
157
161
  }
158
- walk(p, depth + 1);
162
+ walk(p, abs === repro && name === TOOLS_DIR ? depth : depth + 1);
159
163
  } else if (EXT.test(name)) files.push(p);
160
164
  }
161
165
  })(repro, 1);
162
166
 
163
167
  // ── the allow file ─────────────────────────────────────────────────────────────────────────────
164
- const allowPath = join(repro, "generated-code-grandfathered.txt");
168
+ const allowPath = join(repro, TOOLS_DIR, "generated-code-grandfathered.txt");
165
169
  const allowed = new Map();
166
170
  if (existsSync(allowPath)) {
167
171
  for (const line of readFileSync(allowPath, "utf8").split("\n")) {
@@ -305,7 +309,7 @@ for (const [rule, names] of swallowed) {
305
309
  );
306
310
  }
307
311
  ledger.push(
308
- ` • ${ignored.length} row(s) in repro/generated-code-grandfathered.txt — every row, every reason:`,
312
+ ` • ${ignored.length} row(s) in repro/tools/generated-code-grandfathered.txt — every row, every reason:`,
309
313
  );
310
314
  for (const [key, reason] of ignored) ledger.push(` ${key} → ${reason}`);
311
315
  if (!ignored.length) ledger.push(" (nothing is being waved through)");
@@ -46,7 +46,7 @@ test("repro/: a dangling link is passed over; skipped trees are named, more than
46
46
  "repro/__pycache__/x.py": "",
47
47
  // Draws randomness unseeded: a NO_SEED finding, which the reasonless row below waves through.
48
48
  "repro/exp/run.py": "import random\nprint(random.random())\n",
49
- "repro/generated-code-grandfathered.txt": "exp/run.py\t*\n",
49
+ "repro/tools/generated-code-grandfathered.txt": "exp/run.py\t*\n",
50
50
  });
51
51
  symlinkSync(join(root, "gone"), join(dir, "repro", "dangling.py"));
52
52
  const out = run(dir).stdout;
@@ -60,3 +60,17 @@ test("repro/: a dangling link is passed over; skipped trees are named, more than
60
60
  );
61
61
  assert.match(out, / {7}exp\/run\.py · NO_SEED {2}→ {2}\(NO REASON GIVEN\)/);
62
62
  });
63
+
64
+ test("🔴 shared code under repro/tools/ is scanned as deep as a study's: tools/ is a root of its own, not a level of the scan", () => {
65
+ const dir = writeTree(join(root, "paper-tools"), {
66
+ // `repro/<pkg>/src/check.py` was scanned before the layout moved shared code under tools/.
67
+ "repro/tools/pkg/src/check.py": "import random\nprint(random.random())\n",
68
+ "repro/tools/pkg/src/deeper/x.py": "print(1)\n",
69
+ });
70
+ const out = run(dir).stdout;
71
+ assert.match(out, /NO_SEED {3}repro\/tools\/pkg\/src\/check\.py — /);
72
+ assert.match(
73
+ out,
74
+ /• 1 tree\(s\) not entered — nested deeper than 3 levels[^\n]*\n {7}tools\/pkg\/src\/deeper/,
75
+ );
76
+ });
@@ -4,7 +4,7 @@ description: Answer "what's the status of the paper?" in one pass — MEASURE wh
4
4
  allowed-tools: [Bash, Read, Grep, Glob]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:c90bd362b565fa22 compiled from skills/paper-status/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:c28cf2904fc2aaeb compiled from skills/paper-status/SKILL.md.spec.ts -->
8
8
 
9
9
  # paper-status — state, measured before it is narrated
10
10
 
@@ -51,7 +51,7 @@ status; the status is what that measurement makes possible next.
51
51
 
52
52
  ```bash
53
53
  cd <paper-dir>
54
- bash repro/build-submission.sh 2>&1 | grep -Ei 'body pages|VERDICT|Overfull|undefined|anonymity'
54
+ bash repro/tools/build-submission.sh 2>&1 | grep -Ei 'body pages|VERDICT|Overfull|undefined|anonymity'
55
55
  git status --porcelain # uncommitted work
56
56
  git log --oneline @{u}..HEAD # unpushed commits
57
57
  ```
@@ -57,7 +57,7 @@ status; the status is what that measurement makes possible next.
57
57
 
58
58
  \`\`\`bash
59
59
  cd <paper-dir>
60
- bash repro/build-submission.sh 2>&1 | grep -Ei 'body pages|VERDICT|Overfull|undefined|anonymity'
60
+ bash repro/tools/build-submission.sh 2>&1 | grep -Ei 'body pages|VERDICT|Overfull|undefined|anonymity'
61
61
  git status --porcelain # uncommitted work
62
62
  git log --oneline @{u}..HEAD # unpushed commits
63
63
  \`\`\`
@@ -1,51 +1,23 @@
1
1
  /**
2
- * plan-paper-timeline — the EFFECTS tier. What the run can actually reach, not what
3
- * SKILL.md says it reaches.
2
+ * plan-paper-timeline — the EFFECTS tier. What the run actually does, not what
3
+ * SKILL.md says it does.
4
4
  *
5
5
  * SIBLING, NOT REPLACEMENT. `plan-paper-timeline.harness.mjs` next door is static
6
6
  * (`checkSkill()`): frontmatter, wiring, script paths, verdict vocabulary. It is untouched.
7
7
  * This file runs the REAL `claude` CLI against a SCRIPTED MOCK MODEL (`runHarnessTest`),
8
- * so it costs $0 and no API key — and observes the one thing static analysis structurally
9
- * cannot: whether the tools this skill DECLARES EXIST WHERE IT RUNS.
8
+ * so it costs $0 and no API key.
10
9
  *
11
- * ── WHY THIS SKILL FOR THIS CHANNEL ──────────────────────────────────────────────
12
- * It is the only skill in the corpus whose core step is an MCP call. §3 says, literally,
13
- * «Use `mcp__Google_Calendar__create_event` … 🔴 Read the zone, do not assume it». Four of its ten
14
- * declared entries are MCP calendar tools. A declaration naming a tool that is not there is
15
- * invisible to every static check in this repo and to `vigiles audit`, which scores this
16
- * corpus Structure 100.
10
+ * Run A: the skill activates, its body reaches the model, and a declared, granted tool
11
+ * (`Read`) works. Run B: §3's prescribed `create_event` call, parsed out of SKILL.md,
12
+ * replayed against a strict calendar double.
17
13
  *
18
- * ── SAFETY: WHY THIS CANNOT TOUCH A REAL CALENDAR ───────────────────────────
19
- * Measured before this file was written, not assumed. The probe below is `list_events`,
20
- * which is READ-ONLY; `create_event`/`update_event` are NEVER called here, on purpose,
21
- * even though the surface turns out to be absent. The run also happens in a `mkdtemp`
22
- * work dir the runner deletes. If a future CLI ever exposes the calendar headlessly, the
23
- * read-only probe starts PASSING and this test FAILS loudly — which is the point of
24
- * pinning it, and is a far better way to learn it than a created event.
25
- *
26
- * ── WHAT THIS PINS (a characterization test, deliberately) ───────────────────────
27
- * Under `claude -p` at `CHARACTERIZED_CLI` (`lib/agent-cli-version.mjs`, 2.1.227) the whole
28
- * `mcp__*` surface is ABSENT, and so is `ToolSearch`.
29
- *
30
- * 🔴 THAT NUMBER IS A VALUE, NOT PROSE, and it was prose until issue #7. The probe below used
31
- * to run with `stdio: "ignore"`, so the version the CLI printed was never read — this tier
32
- * characterized whatever the runner installed that day while this paragraph kept saying 2.1.227.
33
- * Measured: 2.1.273 on the reporter's machine and on this one, forty-six patch releases of drift
34
- * that nothing in a green log could show. The version is now OBSERVED, reported in the summary
35
- * below, and named in every failure message — so a red says which version it characterized.
36
- * That means:
37
- * 1. §3 of this skill is UNEXECUTABLE in any headless / CI context. It is an
38
- * interactive-session-only step. Nothing in the file says so.
39
- * 2. 🔴 THE RECORDED `ToolSearch` FINDING DOES NOT REPRODUCE HERE, AND THE CORRECTION
40
- * MATTERS. `.claude/lib/skill-effects-results/README.md` (2026-08-10, $0.82)
41
- * recorded "the model called ToolSearch … it isn't in allowed-tools," with the caveat
42
- * that it might be an artifact of the headless environment. It is the opposite:
43
- * headless has NO ToolSearch to call. Adding `ToolSearch` to `allowed-tools` would
44
- * therefore fix nothing here. The reachability of these four tools is a property of
45
- * the ENVIRONMENT, not of the declaration.
46
- *
47
- * If either fact changes, this file fails and says which one — that is its whole job.
48
- * It is not asserting that absence is good.
14
+ * ── WHY RUN A NO LONGER PROBES THE CALENDAR ─────────────────────────────────────
15
+ * It used to call `mcp__Google_Calendar__list_events` and `ToolSearch` and pin "No such
16
+ * tool available" as a finding about headless runs. It was a finding about the FIXTURE:
17
+ * the run happens in an empty `mkdtemp` with no MCP server configured, so the calendar
18
+ * tool is missing by construction, whatever the CLI can do. vigiles 38 refuses a
19
+ * scripted call to a tool the CLI does not have, for exactly this reason. Run B is
20
+ * where the calendar step is tested, with the surface supplied.
49
21
  *
50
22
  * Cost: $0 (scripted mock model, no API key). Wall clock ~30-60s for one CLI spawn.
51
23
  */
@@ -114,37 +86,19 @@ const r = await runHarnessTest({
114
86
  transcript: true,
115
87
  timeoutMs: 180_000,
116
88
  prompt: "plan the AISec timeline",
117
- // The SESSION grant. Deliberately WIDER than the skill's declaration for `Read`, and
118
- // deliberately missing `Bash` — that asymmetry is what turns the two probes below into
119
- // a control and a measurement inside ONE permission configuration.
120
- allowedTools: ["Read", "Write", "Skill", "ToolSearch", ...declaredMcp],
89
+ // The SESSION grant: the tools the script below calls, and no others.
90
+ allowedTools: ["Read", "Skill"],
121
91
  files: { "cfp.md": "AISec 2026 — submission 24 Jul AoE\n" },
122
92
  model: [
123
93
  { tool: "Skill", input: { skill: "plan-paper-timeline" } },
124
- // CONTROL: a tool that is present and permitted. If this failed, every "absent"
125
- // verdict below would be a broken run, not a finding.
94
+ // CONTROL: a tool that is present and permitted.
126
95
  { tool: "Read", input: { file_path: "cfp.md" } },
127
- // READ-ONLY calendar probe. Never create_event / update_event — see header.
128
- {
129
- tool: "mcp__Google_Calendar__list_events",
130
- input: { calendarId: "primary" },
131
- },
132
- // The tool the 2026-08-10 sweep said was missing from the declaration.
133
- {
134
- tool: "ToolSearch",
135
- input: {
136
- query: "select:mcp__Google_Calendar__create_event",
137
- max_results: 1,
138
- },
139
- },
140
96
  { text: "done" },
141
97
  ],
142
98
  });
143
99
 
144
100
  try {
145
101
  const call = (name) => r.toolCalls.find((c) => c.name === name);
146
- const absent = (c) =>
147
- c !== undefined && c.isError && /No such tool available/.test(c.resultText);
148
102
 
149
103
  // ── 1. The run actually happened and the skill actually loaded ──────────────────
150
104
  // Asserted FIRST and separately from everything below. A confident "nothing was
@@ -172,48 +126,14 @@ try {
172
126
  throw new Error(
173
127
  `POSITIVE CONTROL FAILED: Read — a tool this skill declares and the session grants — ` +
174
128
  `did not succeed (${ctrl ? ctrl.resultText.slice(0, 160) : "never called"}). ` +
175
- `Until this passes, no "tool absent" result below can be believed.`,
176
- );
177
-
178
- // ── 2. The pinned environment fact: the declared MCP surface is not there ───────
179
- const cal = call("mcp__Google_Calendar__list_events");
180
- if (!absent(cal))
181
- throw new Error(
182
- `THE ENVIRONMENT CHANGED — and this is good news that must be read, not silenced. ` +
183
- `Observed \`claude\` ${cli.version} (characterized against ${cli.characterized}). ` +
184
- `\`mcp__Google_Calendar__list_events\` is now REACHABLE from \`claude -p\` ` +
185
- `(result: ${cal ? cal.resultText.slice(0, 200) : "no call recorded"}). ` +
186
- `Consequences: (a) §3 of this skill is now executable headlessly, so it can finally be ` +
187
- `tested for real instead of characterized; (b) 🔴 every future run of this tier can now ` +
188
- `WRITE A REAL CALENDAR — re-check the safety argument in this file's header BEFORE ` +
189
- `adding a create_event probe; (c) update this assertion deliberately, do not delete it.`,
190
- );
191
-
192
- const ts = call("ToolSearch");
193
- if (!absent(ts))
194
- throw new Error(
195
- `\`ToolSearch\` is now available under \`claude -p\` ${cli.version} ` +
196
- `(characterized against ${cli.characterized}) (result: ` +
197
- `${ts ? ts.resultText.slice(0, 200) : "no call recorded"}). The 2026-08-10 sweep recorded ` +
198
- `"ToolSearch missing from allowed-tools" as a finding about THIS SKILL; measured on ` +
199
- `2026-08-11 it was a fact about the ENVIRONMENT (headless had no ToolSearch at all). ` +
200
- `If it exists now, that finding becomes actionable again: add it to allowed-tools.`,
129
+ `The skill loaded but the run cannot use the tools it declares.`,
201
130
  );
202
131
 
203
- // ── 3. What that means, stated once, where a reader will see it ────────────────
132
+ // ── 2. The version goes in the REPORT, not only into failures ──────────────────
204
133
  console.log(
205
134
  `✓ plan-paper-timeline [effects]: skill activated and its body reached the model; ` +
206
135
  `Read (declared+granted) works — the run is live.\n` +
207
- // 🔴 The version goes in the REPORT, not only into failures. A characterization test that
208
- // silently still holds is indistinguishable from one nobody re-checked, so the run says
209
- // which version it held against — and says it whether or not the numbers agree.
210
- ` CLI: ${cli.note}\n` +
211
- ` PINNED: ${String(declaredMcp.length)} of its 10 declared entries are MCP calendar tools, ` +
212
- `and the whole mcp__* surface is ABSENT under \`claude -p\`; \`ToolSearch\` is absent too.\n` +
213
- ` → §3 ("Create the calendar events") is an INTERACTIVE-SESSION-ONLY step. Nothing in ` +
214
- `SKILL.md says so, and no static check can see it.\n` +
215
- ` → Corrects the 2026-08-10 sweep: the ToolSearch gap was environmental, not a defect ` +
216
- `in this declaration.`,
136
+ ` CLI: ${cli.note}`,
217
137
  );
218
138
  } finally {
219
139
  r.cleanup();
@@ -222,9 +142,8 @@ try {
222
142
  // ════════════════════════════════════════════════════════════════════════════════
223
143
  // RUN B — §3 EXECUTED, not characterized
224
144
  //
225
- // Run A above pins that the calendar surface is absent from a headless session, which
226
- // is true and worth knowing and also means the skill's CENTRAL STEP has never been
227
- // checked by anything. Run B supplies the surface with a stand-in
145
+ // A headless run in an empty work dir has no calendar surface, so without a stand-in
146
+ // the skill's CENTRAL STEP is checked by nothing. Run B supplies the surface with a stand-in
228
147
  // (`fixtures/fake-google-calendar.ts`) carrying the REAL API's required-field sets,
229
148
  // so the step becomes executable for the first time.
230
149
  //