paperlint 10.0.0 → 10.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CONTRIBUTING.md +16 -4
  2. package/dist/eslint-rules/js-needs-reason.d.ts +55 -0
  3. package/dist/eslint-rules/js-needs-reason.d.ts.map +1 -0
  4. package/dist/eslint-rules/js-needs-reason.js +35 -0
  5. package/dist/eslint-rules/js-needs-reason.js.map +1 -0
  6. package/dist/package-dirs.d.ts +6 -0
  7. package/dist/package-dirs.d.ts.map +1 -1
  8. package/dist/package-dirs.js +6 -0
  9. package/dist/package-dirs.js.map +1 -1
  10. package/dist/skills/label-agent-runs/scripts/labels-commands.d.ts +13 -0
  11. package/dist/skills/label-agent-runs/scripts/labels.d.ts +2 -0
  12. package/dist/skills/run-agent-grid/scripts/grid-guard-commands.d.ts +20 -0
  13. package/dist/skills/run-agent-grid/scripts/grid-guard.d.ts +2 -0
  14. package/docs/skills.md +2 -0
  15. package/docs/testing.md +13 -0
  16. package/eslint-rules/js-needs-reason.test.ts +141 -0
  17. package/eslint-rules/js-needs-reason.ts +77 -0
  18. package/eslint.config.mjs +22 -2
  19. package/lib/skill-trigger-cases.test.ts +211 -0
  20. package/package.json +8 -4
  21. package/scripts/coverage-config.test.ts +5 -3
  22. package/skills/build-benchmark/SKILL.md +4 -1
  23. package/skills/build-benchmark/SKILL.md.spec.ts +3 -0
  24. package/skills/build-label-page/SKILL.md +3 -1
  25. package/skills/build-label-page/SKILL.md.spec.ts +2 -0
  26. package/skills/label-agent-runs/SKILL.md +103 -0
  27. package/skills/label-agent-runs/SKILL.md.spec.ts +106 -0
  28. package/skills/label-agent-runs/label-agent-runs.eval.ts +55 -0
  29. package/skills/label-agent-runs/label-agent-runs.harness.ts +123 -0
  30. package/skills/label-agent-runs/references/formats.md +64 -0
  31. package/skills/label-agent-runs/scripts/labels-commands.js +13 -0
  32. package/skills/label-agent-runs/scripts/labels-commands.ts +13 -0
  33. package/skills/label-agent-runs/scripts/labels.js +396 -0
  34. package/skills/label-agent-runs/scripts/labels.test.ts +538 -0
  35. package/skills/label-agent-runs/scripts/labels.ts +501 -0
  36. package/skills/paper-pipeline/SKILL.md +8 -1
  37. package/skills/paper-pipeline/SKILL.md.spec.ts +7 -0
  38. package/skills/render-paper/check-render.sh +9 -0
  39. package/skills/render-paper/render-paper.harness.mjs +102 -0
  40. package/skills/run-agent-grid/SKILL.md +181 -0
  41. package/skills/run-agent-grid/SKILL.md.spec.ts +184 -0
  42. package/skills/run-agent-grid/references/failure-catalog.md +36 -0
  43. package/skills/run-agent-grid/references/registry-and-ledger.md +142 -0
  44. package/skills/run-agent-grid/run-agent-grid.eval.ts +59 -0
  45. package/skills/run-agent-grid/run-agent-grid.harness.ts +115 -0
  46. package/skills/run-agent-grid/scripts/grid-guard-commands.js +18 -0
  47. package/skills/run-agent-grid/scripts/grid-guard-commands.ts +21 -0
  48. package/skills/run-agent-grid/scripts/grid-guard.js +700 -0
  49. package/skills/run-agent-grid/scripts/grid-guard.test.ts +1090 -0
  50. package/skills/run-agent-grid/scripts/grid-guard.ts +869 -0
  51. package/src/package-dirs.ts +8 -0
  52. package/tsconfig.base.json +22 -0
  53. package/tsconfig.json +6 -24
  54. package/tsconfig.pkg.json +3 -1
  55. package/tsconfig.skills.json +14 -0
  56. package/tsconfig.src.json +12 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The shared trigger table is FROZEN while every skill moves its trigger cases into its own eval
3
+ * file (#218).
4
+ *
5
+ * A skill's trigger cases belong to the skill: `skills/<name>/<name>.eval.ts` default-exports
6
+ * `defineEval({ measureTriggerRate: { prompts, irrelevantPrompts, … } })`, and `vigiles eval`
7
+ * discovers and runs it. `lib/skill-trigger-cases.mjs` holds the skills that have not moved yet.
8
+ * This file keeps that table from growing (a new skill writes its own eval file, never a row),
9
+ * makes `FROZEN` shrink in step with the moves, and refuses a skill that is in both places.
10
+ *
11
+ * The table is read by RUNNING the module in a child process, as the evals do — not by importing
12
+ * it here, which would need types for a JavaScript module, and not by matching its text. An eval
13
+ * file is classified by PARSING it: importing one that still calls the paid kit would spend money.
14
+ *
15
+ * Each check is shown both ways: on the real files (passes) and on a planted input (fails).
16
+ */
17
+ import assert from "node:assert/strict";
18
+ import { spawnSync } from "node:child_process";
19
+ import { globSync, readFileSync } from "node:fs";
20
+ import { basename, dirname, join } from "node:path";
21
+ import { pathToFileURL } from "node:url";
22
+ import ts from "typescript";
23
+ import { assertPromptDiversity } from "vigiles";
24
+ import { test } from "vitest";
25
+ import { z } from "zod";
26
+ import { useTempDir, writeTree } from "../test/support.ts";
27
+ import grid from "../skills/run-agent-grid/run-agent-grid.eval.ts";
28
+ import labels from "../skills/label-agent-runs/label-agent-runs.eval.ts";
29
+
30
+ const ROOT = join(import.meta.dirname, "..");
31
+
32
+ /** The skills still in the shared table. It may only SHRINK: remove a name when its skill moves. */
33
+ const FROZEN: readonly string[] = [
34
+ "analyze-sibling-paper",
35
+ "argument-arc",
36
+ "build-benchmark",
37
+ "build-label-page",
38
+ "camera-ready",
39
+ "cold-read-diff",
40
+ "draft-paper",
41
+ "extend-paper",
42
+ "find-venue",
43
+ "grade-paper-writing",
44
+ "harden-paper",
45
+ "map-prior-work",
46
+ "osf-artifact-upload",
47
+ "paper-adversarial-review",
48
+ "paper-pipeline",
49
+ "paper-status",
50
+ "pc-panel-review",
51
+ "plan-paper-timeline",
52
+ "render-paper",
53
+ "research-ideate",
54
+ "study-accepted-papers",
55
+ "submission-portal",
56
+ "submit-paper",
57
+ "sweep-design-space",
58
+ "tighten-paper",
59
+ "verify-citations",
60
+ ];
61
+
62
+ const Row = z.object({ skill: z.string(), prompts: z.array(z.string()) });
63
+ type Row = z.infer<typeof Row>;
64
+
65
+ /** The shared table's rows, read by running the module. */
66
+ function sharedTable(): Row[] {
67
+ const url = pathToFileURL(join(ROOT, "lib", "skill-trigger-cases.mjs")).href;
68
+ const r = spawnSync(
69
+ process.execPath,
70
+ [
71
+ "--input-type=module",
72
+ "-e",
73
+ `const m = await import(${JSON.stringify(url)});` +
74
+ "console.log(JSON.stringify(m.CASES.map(({ skill, prompts }) => ({ skill, prompts }))));",
75
+ ],
76
+ { cwd: ROOT, encoding: "utf8" },
77
+ );
78
+ assert.equal(r.status, 0, r.stderr);
79
+ return z.array(Row).parse(JSON.parse(r.stdout));
80
+ }
81
+ const SHARED = sharedTable();
82
+
83
+ /** What is wrong with the shared table against the frozen set, as messages; [] when nothing. */
84
+ function frozenProblems(
85
+ table: readonly string[],
86
+ frozen: readonly string[],
87
+ ): string[] {
88
+ return [
89
+ ...table
90
+ .filter((n) => !frozen.includes(n))
91
+ .map(
92
+ (n) =>
93
+ `${n} is a new row in lib/skill-trigger-cases.mjs, which is frozen: declare its trigger ` +
94
+ `cases in skills/${n}/${n}.eval.ts with defineEval({ measureTriggerRate }) instead`,
95
+ ),
96
+ ...frozen
97
+ .filter((n) => !table.includes(n))
98
+ .map(
99
+ (n) =>
100
+ `${n} left the shared table: remove it from FROZEN in lib/skill-trigger-cases.test.ts — ` +
101
+ `the frozen set only shrinks`,
102
+ ),
103
+ ];
104
+ }
105
+
106
+ /** Is this the `measureTriggerRate` key of an object literal? */
107
+ const isTriggerKey = (p: ts.ObjectLiteralElementLike): boolean =>
108
+ p.name !== undefined &&
109
+ ts.isIdentifier(p.name) &&
110
+ p.name.text === "measureTriggerRate";
111
+
112
+ /** Does this eval file default-export `defineEval({ measureTriggerRate: … })`? Parsed, not run. */
113
+ function declaresTriggerRate(file: string): boolean {
114
+ const sf = ts.createSourceFile(
115
+ file,
116
+ readFileSync(file, "utf8"),
117
+ ts.ScriptTarget.Latest,
118
+ );
119
+ return sf.statements.some(
120
+ (s) =>
121
+ ts.isExportAssignment(s) &&
122
+ ts.isCallExpression(s.expression) &&
123
+ ts.isIdentifier(s.expression.expression) &&
124
+ s.expression.expression.text === "defineEval" &&
125
+ s.expression.arguments.some(
126
+ (a) =>
127
+ ts.isObjectLiteralExpression(a) && a.properties.some(isTriggerKey),
128
+ ),
129
+ );
130
+ }
131
+
132
+ /** Skills under `dir` whose own `<name>.eval.ts` declares their trigger cases. */
133
+ const colocated = (dir: string): string[] =>
134
+ globSync("*/*.eval.ts", { cwd: dir })
135
+ .filter((f) => basename(f) === `${dirname(f)}.eval.ts`)
136
+ .filter((f) => declaresTriggerRate(join(dir, f)))
137
+ .map((f) => dirname(f))
138
+ .sort();
139
+
140
+ test("the shared table names exactly the frozen skills", () => {
141
+ assert.deepEqual(
142
+ frozenProblems(
143
+ SHARED.map((r) => r.skill),
144
+ FROZEN,
145
+ ),
146
+ [],
147
+ );
148
+ });
149
+
150
+ test("a planted new row, and a frozen name that left the table, both fail", () => {
151
+ const names = SHARED.map((r) => r.skill);
152
+ assert.deepEqual(frozenProblems([...names, "new-skill"], FROZEN), [
153
+ "new-skill is a new row in lib/skill-trigger-cases.mjs, which is frozen: declare its trigger cases in skills/new-skill/new-skill.eval.ts with defineEval({ measureTriggerRate }) instead",
154
+ ]);
155
+ assert.deepEqual(
156
+ frozenProblems(
157
+ names.filter((n) => n !== "camera-ready"),
158
+ FROZEN,
159
+ ),
160
+ [
161
+ "camera-ready left the shared table: remove it from FROZEN in lib/skill-trigger-cases.test.ts — the frozen set only shrinks",
162
+ ],
163
+ );
164
+ });
165
+
166
+ test("no skill that declares its own trigger cases is also in the shared table", () => {
167
+ const mine = colocated(join(ROOT, "skills"));
168
+ assert.deepEqual(mine, ["label-agent-runs", "run-agent-grid"]);
169
+ assert.deepEqual(
170
+ mine.filter((s) => SHARED.some((r) => r.skill === s)),
171
+ [],
172
+ );
173
+ });
174
+
175
+ const WORK = useTempDir("trigger-cases-");
176
+
177
+ test("an eval file is classified by its default export, parsed: defineEval yes, the kit no", () => {
178
+ const dir = writeTree(join(WORK, "skills"), {
179
+ "own/own.eval.ts":
180
+ 'import { defineEval } from "vigiles";\nexport default defineEval({ measureTriggerRate: { prompts: [] } });\n',
181
+ "kit/kit.eval.ts":
182
+ 'import { runSkillTriggerEval } from "../../lib/skill-eval-kit.mjs";\nawait runSkillTriggerEval("kit");\n',
183
+ "other/other.eval.ts":
184
+ 'import { defineEval } from "vigiles";\nexport default defineEval({ measure: { task: "x" } });\n',
185
+ "named/elsewhere.eval.ts":
186
+ 'import { defineEval } from "vigiles";\nexport default defineEval({ measureTriggerRate: { prompts: [] } });\n',
187
+ });
188
+ assert.deepEqual(colocated(dir), ["own"]);
189
+ // A planted double source: a skill that declares its own cases and is also a table row.
190
+ assert.deepEqual(
191
+ colocated(dir).filter((s) => [...FROZEN, "own"].includes(s)),
192
+ ["own"],
193
+ );
194
+ });
195
+
196
+ test("each new eval declares diverse prompts on both sides, none of them a table row's", () => {
197
+ const tablePrompts = new Set(SHARED.flatMap((r) => r.prompts));
198
+ for (const def of [grid, labels]) {
199
+ const spec = def.measureTriggerRate;
200
+ assert.ok(spec, "the eval declares measureTriggerRate");
201
+ assertPromptDiversity(spec.prompts, { minPrompts: 4, label: "prompts" });
202
+ assertPromptDiversity(spec.irrelevantPrompts ?? [], {
203
+ minPrompts: 4,
204
+ label: "irrelevantPrompts",
205
+ });
206
+ assert.deepEqual(
207
+ spec.prompts.filter((p) => tablePrompts.has(p)),
208
+ [],
209
+ );
210
+ }
211
+ });
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "paperlint",
3
- "version": "10.0.0",
3
+ "version": "10.1.1",
4
4
  "description": "A linter for scientific papers written in LaTeX: catches mistakes before you submit to a conference or journal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
- "build": "tsc -p tsconfig.pkg.json && tsc",
9
- "prepare": "tsc -p tsconfig.pkg.json && tsc",
8
+ "build": "tsc -b",
9
+ "prepare": "tsc -b",
10
10
  "test": "node scripts/test.ts",
11
11
  "test:e2e": "vitest run --project 'e2e-*'",
12
12
  "test:e2e:install": "vitest run --project e2e-install",
@@ -101,6 +101,7 @@
101
101
  "action.yml",
102
102
  "bin",
103
103
  "dist",
104
+ "!dist/.tsbuildinfo",
104
105
  "docs",
105
106
  "eslint-rules",
106
107
  "eslint.config.mjs",
@@ -114,8 +115,11 @@
114
115
  "skills",
115
116
  "src",
116
117
  "templates",
118
+ "tsconfig.base.json",
117
119
  "tsconfig.json",
118
- "tsconfig.pkg.json"
120
+ "tsconfig.pkg.json",
121
+ "tsconfig.skills.json",
122
+ "tsconfig.src.json"
119
123
  ],
120
124
  "imports": {
121
125
  "#lib/*": {
@@ -165,7 +165,7 @@ test("every evalOnly exclusion is imported only by an eval or another evalOnly m
165
165
  : [];
166
166
  });
167
167
  const outsiders = importers.filter(
168
- (f) => !f.endsWith(".eval.mjs") && !evalOnly.has(join(ROOT, f)),
168
+ (f) => !/\.eval\.[cm]?[jt]s$/.test(f) && !evalOnly.has(join(ROOT, f)),
169
169
  );
170
170
  assert.deepEqual(
171
171
  { outsiders, anyImporter: importers.length > 0 },
@@ -398,8 +398,8 @@ test("the exclude list is exactly the justified set", () => {
398
398
  // tests themselves
399
399
  "**/*.harness.*",
400
400
  "**/*.test.*",
401
- // PAID LLM evaluations, never run by `npm test`
402
- "**/*.eval.mjs",
401
+ // PAID LLM evaluations, never run by `npm test` (`.mjs` or `.ts`)
402
+ "**/*.eval.*",
403
403
  // skill and CLAUDE.md sources vigiles compiles; a glob's `**` skips a dot directory
404
404
  "**/*.spec.ts",
405
405
  ".github/*.spec.ts",
@@ -423,6 +423,8 @@ test("the exclude list is exactly the justified set", () => {
423
423
  "skills/**/repro/**",
424
424
  "skills/**/references/**",
425
425
  "docs/prior-art/repro/**",
426
+ // a skill's scripts compiled beside their .ts (tsconfig.skills.json); the .ts is what is measured
427
+ "skills/*/scripts/*.js",
426
428
  // the test runner's own configuration, read by vitest, not code the package runs
427
429
  "vitest.config.ts",
428
430
  ]);
@@ -5,7 +5,7 @@ context: fork
5
5
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Agent]
6
6
  ---
7
7
 
8
- <!-- vigiles:sha256:5eb2ba9561e082d9 compiled from skills/build-benchmark/SKILL.md.spec.ts -->
8
+ <!-- vigiles:sha256:eed7d0afdfee87a6 compiled from skills/build-benchmark/SKILL.md.spec.ts -->
9
9
 
10
10
  # build-benchmark — the study + the artifact reviewers can run
11
11
 
@@ -167,6 +167,9 @@ Before submitting, verify the artifact you built with the reviewer-side reproduc
167
167
  - **argument-arc (frame mode)** — 🔴 **hard input.** Owns row `frame`, the stated claim. No `frame`, no study.
168
168
  - **research-ideate** (upstream) — supplies the contribution and the pitfall to invert; this skill turns
169
169
  it into evidence.
170
+ - **run-agent-grid** — when the study is a grid of agent runs: this skill designs it, that one runs
171
+ it (registry gate, sandbox probe, smoke, locked driver, overlap audit, committed runs).
172
+ - **label-agent-runs** — the outcome labels of those runs, before any statistic here is computed.
170
173
  - **build-label-page** — when the study needs a person to label items (gold labels, a blind review of
171
174
  outputs): it builds the page, publishes it and exports the answers into the study's `data/`.
172
175
  - **draft-paper** — the study's stats, bound, and artifact-number table feed Methods/Results/Availability.
@@ -173,6 +173,9 @@ Before submitting, verify the artifact you built with the reviewer-side reproduc
173
173
  - **argument-arc (frame mode)** — 🔴 **hard input.** Owns row \`frame\`, the stated claim. No \`frame\`, no study.
174
174
  - **research-ideate** (upstream) — supplies the contribution and the pitfall to invert; this skill turns
175
175
  it into evidence.
176
+ - **run-agent-grid** — when the study is a grid of agent runs: this skill designs it, that one runs
177
+ it (registry gate, sandbox probe, smoke, locked driver, overlap audit, committed runs).
178
+ - **label-agent-runs** — the outcome labels of those runs, before any statistic here is computed.
176
179
  - **build-label-page** — when the study needs a person to label items (gold labels, a blind review of
177
180
  outputs): it builds the page, publishes it and exports the answers into the study's \`data/\`.
178
181
  - **draft-paper** — the study's stats, bound, and artifact-number table feed Methods/Results/Availability.
@@ -4,7 +4,7 @@ description: Build a phone labelling page published as a claude.ai Artifact with
4
4
  allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Artifact, ArtifactData]
5
5
  ---
6
6
 
7
- <!-- vigiles:sha256:894d8a2df3f5c319 compiled from skills/build-label-page/SKILL.md.spec.ts -->
7
+ <!-- vigiles:sha256:592d7b4f8f27c4ce compiled from skills/build-label-page/SKILL.md.spec.ts -->
8
8
 
9
9
  # build-label-page — a page the person labels on a phone, and the labels back in the repo
10
10
 
@@ -112,6 +112,8 @@ Do not iterate on a page you cannot see.
112
112
  - **build-benchmark** — owns the study design and the inter-rater rule: agreement with the person who
113
113
  wrote the codebook measures trainability, not reliability, so the labeller for gold labels is
114
114
  someone who did not write the scheme.
115
+ - **label-agent-runs** — when the items are the runs of an agent grid: the codebook, which runs need
116
+ a hand label, the second rater and the label file this page's answers go into.
115
117
  - **draft-paper** — the exported labels, the familiarity filter and the complete-item count feed the
116
118
  Methods and the threats.
117
119
  - **submit-paper** — a published artifact page is not anonymous. Do not link it from an anonymized
@@ -122,6 +122,8 @@ Do not iterate on a page you cannot see.
122
122
  - **build-benchmark** — owns the study design and the inter-rater rule: agreement with the person who
123
123
  wrote the codebook measures trainability, not reliability, so the labeller for gold labels is
124
124
  someone who did not write the scheme.
125
+ - **label-agent-runs** — when the items are the runs of an agent grid: the codebook, which runs need
126
+ a hand label, the second rater and the label file this page's answers go into.
125
127
  - **draft-paper** — the exported labels, the familiarity filter and the complete-item count feed the
126
128
  Methods and the threats.
127
129
  - **submit-paper** — a published artifact page is not anonymous. Do not link it from an anonymized
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: label-agent-runs
3
+ description: "Label the outcomes of an agent experiment grid so the labels survive review: a codebook fixed before the first label (each label with a definition and a borderline example), the automatic classifier used only as a pre-sort, every run without artifact-level evidence read in full and labelled by hand, auto-vs-hand disagreements reported, a blind second rater on a different model with agreement (Cohen's kappa, percent with a CI), labels kept as data and never edited after the analysis without a dated note. Use after the runs of a grid are in and before any outcome number is computed: \"label the runs\", \"the classifier missed some outcomes\", \"second rater\", \"inter-rater agreement on the transcripts\". Ships labels.js (validate, agree). Composes with run-agent-grid (upstream), build-label-page (the hand-labelling page) and build-benchmark (the analysis)."
4
+ allowed-tools: [Read, Write, Edit, Grep, Glob, Bash, Agent]
5
+ ---
6
+
7
+ <!-- vigiles:sha256:91ebf75c83bbd845 compiled from skills/label-agent-runs/SKILL.md.spec.ts -->
8
+
9
+ # label-agent-runs — turning finished agent runs into outcome labels a reviewer can trust
10
+
11
+ After `run-agent-grid` has produced the runs, every outcome number in the paper comes from a label.
12
+ This skill is the procedure for producing those labels: what is fixed before labelling, what may be
13
+ decided by a script, what must be read by a person, how a second rater is run, and how the labels
14
+ are kept. `build-benchmark` turns the labels into statistics; `build-label-page` is the page a person
15
+ labels on.
16
+
17
+ Each rule names the failure behind it and what holds it — a refusal in `skills/label-agent-runs/scripts/labels.ts` (run as the compiled `scripts/labels.js`) or
18
+ **prose only**. Formats (codebook, label file, notes, rater instruction):
19
+ [`references/formats.md`](references/formats.md).
20
+
21
+ ```bash
22
+ node ${CLAUDE_SKILL_DIR}/scripts/labels.js validate labels.tsv --codebook codebook.json --ledger attempts.jsonl
23
+ node ${CLAUDE_SKILL_DIR}/scripts/labels.js agree auto.tsv hand.tsv
24
+ ```
25
+
26
+ Exit codes: 0 valid or reported · 1 a finding · 2 usage.
27
+
28
+ ## 1. The codebook comes first
29
+
30
+ - Before the first label: every outcome label with a **definition** and a **borderline example** (the
31
+ case nearest the line, and which side it falls on). A codebook written while labelling bends to the
32
+ runs already seen.
33
+ - **Held by:** `validate` refuses a codebook that is not committed, differs from its committed
34
+ version, lacks a definition or a borderline example for a label, or has ANY commit later than the
35
+ label file's first commit; and a label the codebook does not define. A codebook changed after
36
+ labelling began is refused rather than excused by a note: the labels already given were given
37
+ under the old definitions, so the change starts a NEW label file, committed after it, and every
38
+ run is labelled again under the new codebook.
39
+
40
+ ## 2. The automatic classifier is a pre-sort, not a label
41
+
42
+ - An automatic outcome detector misses classes. The case that produced this rule: a detector of
43
+ "the agent handed the decision back to the user" keyed on a question mark, and missed most of the
44
+ deferrals in one grid — they were phrased as statements ("I'll leave that decision to you.").
45
+ - **Mechanically certain** means artifact-level evidence decides the outcome: the task's check passes
46
+ or fails on the committed state, the required file exists or does not. Only those runs keep the
47
+ automatic label.
48
+ - **Every other run is read in full** — the final message AND the action sequence (the tool calls,
49
+ in order) — and labelled by hand. Use `build-label-page` when there are more than a handful.
50
+ - **The disagreements between the pre-sort and the hand labels are reported**, with counts per
51
+ label, not folded away: they are the measured error of the classifier.
52
+ - **Held by:** `validate --ledger` refuses a label file that misses a completed run, or labels a cell
53
+ with no completed run; `agree auto.tsv hand.tsv` lists every disagreement. **Prose only:** that
54
+ the uncertain runs were actually read.
55
+
56
+ ## 3. A finished run still gets a check of what changed outside the task
57
+
58
+ - A run that completed its task can also have changed what it was not asked to touch (deleted a
59
+ test, edited a fixture, rewrote a config). Before a "solved" label stands, list what the run
60
+ changed outside the task's paths:
61
+
62
+ ```bash
63
+ git -C <working-copy> diff --name-only <base> HEAD -- . ':(exclude)<task path>'
64
+ ```
65
+
66
+ Anything listed is read and either explained in the notes or reflected in the label.
67
+ - **Prose only.**
68
+
69
+ ## 4. A second rater, blind, on a different model
70
+
71
+ - A second rater labels **the same runs**: a different model from the first rater's, given **the same
72
+ instruction byte for byte** (one committed file; record its hash in the notes), and **blind** to the
73
+ first labels — it receives the runs' messages and actions, never another rater's label.
74
+ - Report agreement: Cohen's kappa and percent agreement with its 95% interval, over n, with the
75
+ disagreements. Resolve disagreements by re-reading the run, and record each resolution in the notes.
76
+ - A person may replace **one** rater, never all of them: at least one rater stays independent of the
77
+ person who wrote the codebook.
78
+ - **Held by:** `agree` refuses two files that label different cells, and computes n, percent with a
79
+ Wilson interval, kappa (or says it is undefined when only one label occurs), and every
80
+ disagreement. **Prose only:** blindness, the different model, the identical instruction.
81
+
82
+ ## 5. Labels are data, and frozen after the analysis
83
+
84
+ - The labels live in a TSV with the header `agent model cell label` (tab-separated), one row per
85
+ run, beside a notes file. Every relabel gets a note under a dated heading (`## YYYY-MM-DD`) naming
86
+ the cell and quoting the run verbatim (a `>` line) — the quote is the evidence for the label.
87
+ - Once an analysis has read the labels, they are not edited without such a note.
88
+ - **Held by:** `validate --analysed-at <rev> --notes notes.md` compares the cells at the revision the
89
+ analysis read with the cells now, and refuses every cell that was relabelled, ADDED or REMOVED
90
+ since and is not named in a dated section with a quote. Added cells count too: the analysis did
91
+ not see them. Tag the revision when the analysis runs (`git tag analysis-<date>`).
92
+
93
+ ## What to report
94
+
95
+ The codebook (and its revision), the share of runs labelled automatically vs by hand, the
96
+ pre-sort's disagreements with the hand labels, the second rater's agreement (kappa, percent with CI,
97
+ n), and every relabel after the analysis with its note.
98
+
99
+ ## Compose with
100
+
101
+ - **run-agent-grid** (upstream) — the runs, the attempt ledger and the cell ids the labels use.
102
+ - **build-label-page** — the page a person labels on, and the export into the study's `data/`.
103
+ - **build-benchmark** — the statistics computed from the labels.
@@ -0,0 +1,106 @@
1
+ import { experimental_skill, file, prose } from "vigiles/spec";
2
+ import { COMMANDS } from "./scripts/labels-commands.ts";
3
+
4
+ export default experimental_skill({
5
+ name: "label-agent-runs",
6
+ description:
7
+ 'Label the outcomes of an agent experiment grid so the labels survive review: a codebook fixed before the first label (each label with a definition and a borderline example), the automatic classifier used only as a pre-sort, every run without artifact-level evidence read in full and labelled by hand, auto-vs-hand disagreements reported, a blind second rater on a different model with agreement (Cohen\'s kappa, percent with a CI), labels kept as data and never edited after the analysis without a dated note. Use after the runs of a grid are in and before any outcome number is computed: "label the runs", "the classifier missed some outcomes", "second rater", "inter-rater agreement on the transcripts". Ships labels.js (validate, agree). Composes with run-agent-grid (upstream), build-label-page (the hand-labelling page) and build-benchmark (the analysis).',
8
+ tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash", "Agent"],
9
+ body: prose`
10
+ # label-agent-runs — turning finished agent runs into outcome labels a reviewer can trust
11
+
12
+ After \`run-agent-grid\` has produced the runs, every outcome number in the paper comes from a label.
13
+ This skill is the procedure for producing those labels: what is fixed before labelling, what may be
14
+ decided by a script, what must be read by a person, how a second rater is run, and how the labels
15
+ are kept. \`build-benchmark\` turns the labels into statistics; \`build-label-page\` is the page a person
16
+ labels on.
17
+
18
+ Each rule names the failure behind it and what holds it — a refusal in ${file("skills/label-agent-runs/scripts/labels.ts")} (run as the compiled \`scripts/labels.js\`) or
19
+ **prose only**. Formats (codebook, label file, notes, rater instruction):
20
+ [\`references/formats.md\`](references/formats.md).
21
+
22
+ \`\`\`bash
23
+ node \${CLAUDE_SKILL_DIR}/scripts/labels.js ${COMMANDS.validate} labels.tsv --codebook codebook.json --ledger attempts.jsonl
24
+ node \${CLAUDE_SKILL_DIR}/scripts/labels.js ${COMMANDS.agree} auto.tsv hand.tsv
25
+ \`\`\`
26
+
27
+ Exit codes: 0 valid or reported · 1 a finding · 2 usage.
28
+
29
+ ## 1. The codebook comes first
30
+
31
+ - Before the first label: every outcome label with a **definition** and a **borderline example** (the
32
+ case nearest the line, and which side it falls on). A codebook written while labelling bends to the
33
+ runs already seen.
34
+ - **Held by:** \`${COMMANDS.validate}\` refuses a codebook that is not committed, differs from its committed
35
+ version, lacks a definition or a borderline example for a label, or has ANY commit later than the
36
+ label file's first commit; and a label the codebook does not define. A codebook changed after
37
+ labelling began is refused rather than excused by a note: the labels already given were given
38
+ under the old definitions, so the change starts a NEW label file, committed after it, and every
39
+ run is labelled again under the new codebook.
40
+
41
+ ## 2. The automatic classifier is a pre-sort, not a label
42
+
43
+ - An automatic outcome detector misses classes. The case that produced this rule: a detector of
44
+ "the agent handed the decision back to the user" keyed on a question mark, and missed most of the
45
+ deferrals in one grid — they were phrased as statements ("I'll leave that decision to you.").
46
+ - **Mechanically certain** means artifact-level evidence decides the outcome: the task's check passes
47
+ or fails on the committed state, the required file exists or does not. Only those runs keep the
48
+ automatic label.
49
+ - **Every other run is read in full** — the final message AND the action sequence (the tool calls,
50
+ in order) — and labelled by hand. Use \`build-label-page\` when there are more than a handful.
51
+ - **The disagreements between the pre-sort and the hand labels are reported**, with counts per
52
+ label, not folded away: they are the measured error of the classifier.
53
+ - **Held by:** \`validate --ledger\` refuses a label file that misses a completed run, or labels a cell
54
+ with no completed run; \`agree auto.tsv hand.tsv\` lists every disagreement. **Prose only:** that
55
+ the uncertain runs were actually read.
56
+
57
+ ## 3. A finished run still gets a check of what changed outside the task
58
+
59
+ - A run that completed its task can also have changed what it was not asked to touch (deleted a
60
+ test, edited a fixture, rewrote a config). Before a "solved" label stands, list what the run
61
+ changed outside the task's paths:
62
+
63
+ \`\`\`bash
64
+ git -C <working-copy> diff --name-only <base> HEAD -- . ':(exclude)<task path>'
65
+ \`\`\`
66
+
67
+ Anything listed is read and either explained in the notes or reflected in the label.
68
+ - **Prose only.**
69
+
70
+ ## 4. A second rater, blind, on a different model
71
+
72
+ - A second rater labels **the same runs**: a different model from the first rater's, given **the same
73
+ instruction byte for byte** (one committed file; record its hash in the notes), and **blind** to the
74
+ first labels — it receives the runs' messages and actions, never another rater's label.
75
+ - Report agreement: Cohen's kappa and percent agreement with its 95% interval, over n, with the
76
+ disagreements. Resolve disagreements by re-reading the run, and record each resolution in the notes.
77
+ - A person may replace **one** rater, never all of them: at least one rater stays independent of the
78
+ person who wrote the codebook.
79
+ - **Held by:** \`${COMMANDS.agree}\` refuses two files that label different cells, and computes n, percent with a
80
+ Wilson interval, kappa (or says it is undefined when only one label occurs), and every
81
+ disagreement. **Prose only:** blindness, the different model, the identical instruction.
82
+
83
+ ## 5. Labels are data, and frozen after the analysis
84
+
85
+ - The labels live in a TSV with the header \`agent model cell label\` (tab-separated), one row per
86
+ run, beside a notes file. Every relabel gets a note under a dated heading (\`## YYYY-MM-DD\`) naming
87
+ the cell and quoting the run verbatim (a \`>\` line) — the quote is the evidence for the label.
88
+ - Once an analysis has read the labels, they are not edited without such a note.
89
+ - **Held by:** \`validate --analysed-at <rev> --notes notes.md\` compares the cells at the revision the
90
+ analysis read with the cells now, and refuses every cell that was relabelled, ADDED or REMOVED
91
+ since and is not named in a dated section with a quote. Added cells count too: the analysis did
92
+ not see them. Tag the revision when the analysis runs (\`git tag analysis-<date>\`).
93
+
94
+ ## What to report
95
+
96
+ The codebook (and its revision), the share of runs labelled automatically vs by hand, the
97
+ pre-sort's disagreements with the hand labels, the second rater's agreement (kappa, percent with CI,
98
+ n), and every relabel after the analysis with its note.
99
+
100
+ ## Compose with
101
+
102
+ - **run-agent-grid** (upstream) — the runs, the attempt ledger and the cell ids the labels use.
103
+ - **build-label-page** — the page a person labels on, and the export into the study's \`data/\`.
104
+ - **build-benchmark** — the statistics computed from the labels.
105
+ `,
106
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * label-agent-runs — the PAID tier: does this skill's description fire on its own requests, and
3
+ * stay quiet on a sibling's?
4
+ *
5
+ * Declared here, whole: the prompts it must fire on, the prompts it must not, and the
6
+ * measurement — no shared table, no kit. `vigiles eval` discovers this file by its name and runs
7
+ * it; nothing runs at import.
8
+ *
9
+ * The should-not-fire set is `run-agent-grid`'s own requests, imported from its eval file (the
10
+ * sibling it is most easily confused with), plus `build-label-page` (a page a person labels on)
11
+ * and `build-benchmark` (the statistics), written inline.
12
+ *
13
+ * The skills it competes with are every skill this package ships (the `skills/` directory this
14
+ * file sits in), the same set in a checkout and in `node_modules`.
15
+ *
16
+ * ⚠️ NEVER RUN. Written with the skill; its rates are UNKNOWN, not assumed good.
17
+ *
18
+ * Costs money; not CI.
19
+ * npx vigiles eval skills/label-agent-runs/label-agent-runs.eval.ts
20
+ */
21
+ import { dirname } from "node:path";
22
+ import { fileURLToPath } from "node:url";
23
+ import { defineEval, skillResolved } from "vigiles";
24
+ import { TRIGGER_PROMPTS as GRID } from "../run-agent-grid/run-agent-grid.eval.ts";
25
+
26
+ /** The namespace vigiles installs a loose skills directory under. */
27
+ const LOOSE_NS = "vigiles-loose-skills";
28
+
29
+ export default defineEval({
30
+ measureTriggerRate: {
31
+ name: "label-agent-runs-trigger",
32
+ skillsDir: dirname(dirname(fileURLToPath(import.meta.url))),
33
+ prompts: [
34
+ "прогоны закончились, теперь надо разметить исходы каждого запуска агента",
35
+ "the auto-classifier missed outcomes where the agent handed the decision back without asking a question",
36
+ "нужен второй разметчик на другой модели и каппа Коэна по меткам прогонов",
37
+ "we already ran the analysis, can I still fix a few run labels and how do I record it",
38
+ ],
39
+ irrelevantPrompts: [
40
+ ...GRID.slice(0, 2),
41
+ "make me a page to label these, and put the answers back into the study folder",
42
+ "нужны парные прогоны, доверительные интервалы и поправка на множественные сравнения",
43
+ ],
44
+ fired: (trace) => skillResolved(trace, `${LOOSE_NS}:label-agent-runs`),
45
+ // Four named phrasings per side, deliberately, as for every skill of this package.
46
+ minPrompts: 4,
47
+ // Finished runs and a first label file, so "label the runs" has runs to refer to.
48
+ fixture: {
49
+ "runs/a1/done.json": '{"status":"complete"}\n',
50
+ "runs/a1/final-message.txt":
51
+ "I'll leave the final call on this to you.\n",
52
+ "labels.tsv": "agent\tmodel\tcell\tlabel\n",
53
+ },
54
+ },
55
+ });