create-agent-rig 0.10.1 → 1.0.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 (44) hide show
  1. package/CHANGELOG.md +170 -4
  2. package/README.md +8 -8
  3. package/package.json +2 -2
  4. package/packages/cli/dist/commands/upgrade.js +22 -9
  5. package/packages/cli/dist/index.js +9 -2
  6. package/packages/cli/dist/integrations/doctor-guards.js +7 -3
  7. package/templates/agent-os/subagent-routing.json +9 -5
  8. package/templates/agent-os/universal/.agents/skills/diagnose/SKILL.md +43 -0
  9. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +65 -22
  10. package/templates/agent-os/universal/.agents/skills/plan-slices/SKILL.md +30 -0
  11. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
  12. package/templates/agent-os/universal/.agents/skills/release-propose/SKILL.md +74 -0
  13. package/templates/agent-os/universal/.agents/skills/skill-authoring/SKILL.md +39 -0
  14. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +5 -1
  15. package/templates/agent-os/universal/.claude/agents/failure-diagnostician.md +112 -0
  16. package/templates/agent-os/universal/.claude/agents/security-scanner.md +1 -1
  17. package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +14 -3
  18. package/templates/agent-os/universal/.claude/hooks/guard-rulebook.mjs +142 -14
  19. package/templates/agent-os/universal/.claude/hooks/guard-secret-file.mjs +5 -1
  20. package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +168 -17
  21. package/templates/agent-os/universal/.claude/rules/invariants.md +33 -0
  22. package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +63 -0
  23. package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +5 -6
  24. package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +71 -14
  25. package/templates/agent-os/universal/.claude/scripts/queue/propose.mjs +139 -0
  26. package/templates/agent-os/universal/.claude/scripts/release-evidence.mjs +188 -0
  27. package/templates/agent-os/universal/.claude/scripts/revalidation-report.mjs +4 -2
  28. package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +157 -10
  29. package/templates/agent-os/universal/.claude/skills/diagnose/SKILL.md +43 -0
  30. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +65 -22
  31. package/templates/agent-os/universal/.claude/skills/plan-slices/SKILL.md +30 -0
  32. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
  33. package/templates/agent-os/universal/.claude/skills/release-propose/SKILL.md +74 -0
  34. package/templates/agent-os/universal/.claude/skills/skill-authoring/SKILL.md +39 -0
  35. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
  36. package/templates/agent-os/universal/.codex/agents/failure-diagnostician.toml +6 -0
  37. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
  38. package/templates/agent-os/universal/AGENTS.md +11 -5
  39. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +1 -1
  40. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +5 -3
  41. package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +15 -3
  42. package/templates/agent-os/universal/layers.json +12 -0
  43. package/templates/hash-history.json +118 -22
  44. package/templates/release-ledger.json +3 -1
package/CHANGELOG.md CHANGED
@@ -14,12 +14,178 @@ second recorded departure; its own entry states the direction and the reason,
14
14
  and this paragraph deliberately does not restate them — a numbering rule with
15
15
  two copies of its exceptions is the shape 0.8.0 exists to remove.
16
16
 
17
+ ## 1.0.1
18
+
19
+ **1.0.1 is a patch on the 1.0 line.** It closes guard gaps an unattended run
20
+ could use to rewrite the rulebook, fixes a `doctor` side effect, and moves the
21
+ reviewer tier of both harnesses to newer models. The 1.0 contract is
22
+ unchanged: no flag, JSON shape, exit code, verdict or outcome word, and no
23
+ manifest key is added, removed or renamed.
24
+
25
+ ### Changed
26
+
27
+ - **The Claude reviewer tier runs on Opus 5.5.** `code-reviewer`,
28
+ `security-scanner` and `failure-diagnostician` route to `claude-opus-5-5`
29
+ (was `claude-opus-5`), effort `high` unchanged (RP-232).
30
+ - **The Codex reviewer tier runs on GPT-6 Sol.** The same three roles route to
31
+ `gpt-6-sol` (was `gpt-5.6-sol`), reasoning effort `high` unchanged. Under
32
+ ChatGPT-account auth, Codex CLI 0.156.1 dispatched it and 0.154.0 rejected
33
+ the model id; versions in between were not tested (RP-233).
34
+
35
+ ### Fixed
36
+
37
+ - **`guard-rulebook` judges the paths an `apply_patch` removes.** A
38
+ `*** Delete File:` section, and the source of a `*** Move to:`, were never
39
+ judged, so an armed unattended run could delete or move a rulebook file —
40
+ including the guard itself (RP-214).
41
+ - **A miscased rulebook path is recognised.** `.Codex/config.toml` or
42
+ `.AGENTS/…` passed where the lowercase spelling was refused, and on a
43
+ case-insensitive filesystem the write landed on the real file. A miscased
44
+ allow-list entry is refused like its canonical spelling (RP-215).
45
+ - **A Win32 trailing dot or space in a path component no longer hides a
46
+ rulebook path.** Windows strips them, so `.claude./settings.json` is
47
+ `.claude/settings.json` (RP-243).
48
+ - **A Win32 verbatim or device path no longer hides a rulebook path.**
49
+ `\\?\C:\…`, `\\.\C:\…` and a `..` run above the drive root are judged as the
50
+ plain path Windows writes to. A UNC or other device-namespace path that does
51
+ not resolve under the repository root is refused while unattended rather
52
+ than judged as outside the rulebook (RP-244). One case stays open: when the
53
+ repository root is itself spelled as a UNC admin share, the local drive
54
+ spelling of the same file is not caught (RP-246).
55
+ - **`doctor` no longer leaves an unattended flag in your home directory.** Its
56
+ guard fixture armed the flag in the invoking user's real `~/.claude` as well
57
+ as in its temporary home, and never cleared it (RP-238).
58
+ - **The workflow layer's seed declares the revalidation contract elevated.**
59
+ `.rig/revalidation.json` was already protected from an unattended edit but
60
+ was missing from the `elevated-paths` seed, so the gate sweep could not see a
61
+ merge that rewrote it (RP-217).
62
+
63
+ ### Generator repository (not a rig-facing change)
64
+
65
+ - The evidence-pointer check resolves a directory-qualified citation against
66
+ that exact path instead of any file with the same name (RP-216).
67
+ - The package-manager CLI-start test bounds its child process and reports a
68
+ timeout as one (RP-212); a credential-masking test no longer serialises 41
69
+ fixture rewrites, which made it time out under sibling disk load (RP-218).
70
+
71
+ ## 1.0.0
72
+
73
+ **The harness-configuration contract is frozen.** Nothing here changes what a
74
+ `create` or `init` installs in a way a 0.10.x rig would notice; what changes is
75
+ that the surface those commands present — their flags, their JSON shapes, their
76
+ exit codes, their verdict and outcome vocabularies, the manifest they write —
77
+ is now a contract with a stated deprecation policy, and a test suite that goes
78
+ red when the document and the code disagree. The major number says that, and
79
+ only that: from here, removing or renaming something in that surface is a major
80
+ bump, and adding to it is a minor.
81
+
82
+ The rest of this entry is what the rigs themselves gain, down to the last
83
+ section — which is the changes that stayed in the generator, named there so a
84
+ reader does not go looking for them in a rig.
85
+
86
+ ### The contract
87
+
88
+ What a rig gains here is the promise, not a file: the document and its tests
89
+ live in the generator, and the section at the end of this entry says so.
90
+
91
+ - **The 1.0 public surface is stated, and so is how it may change** — what is
92
+ promised, what is explicitly not, and what a deprecation costs (RP-184).
93
+ - **Seven correspondences keep it honest**, each red when either side drifts:
94
+ the `upgrade` verdict union, the closed set of seven `doctor` check ids, the
95
+ `setup` outcome values, the manifest keys a fresh `init` writes, the
96
+ integrations declaration schema against a real `setup add`, the packed tarball
97
+ carrying no third-party provider payload, and the CI Node matrix against
98
+ `engines.node` (RP-184).
99
+
100
+ ### Added
101
+
102
+ - **`failure-diagnostician`** — a read-only agent that reproduces a red check or
103
+ a claimed defect on the current default branch and answers with one of six
104
+ checked verdicts, so "is this still live?" is measured before work is planned
105
+ on it (RP-195).
106
+ - **`diagnose`** — the Core skill that dispatches it, for a failure whose cause
107
+ is not obvious (RP-195).
108
+ - **`skill-authoring`** — the Core skill for writing a new skill, with
109
+ structural checks over the ones a rig ships (RP-195).
110
+ - **`plan-slices`** — the workflow-layer skill that cuts a large item into
111
+ slices that can each be shipped on their own (RP-195).
112
+ - **`release-propose`**, and the `release-evidence.mjs` it reads through — the
113
+ workflow-layer skill that proposes a release only from repeated,
114
+ pointer-backed evidence, never from one bad run (RP-203).
115
+ - **`queue/propose.mjs`** — one root-safe entry point for filing a triage
116
+ proposal, so a session standing in a subdirectory files into the project's
117
+ real queue rather than a cwd-relative one that does not exist (RP-209).
118
+
119
+ ### Changed
120
+
121
+ - **The gate-round cap defaults to three**, not two — one round to find, one to
122
+ fix, one to confirm (RP-210).
123
+ - **The verdict vocabulary carries the diagnosis words** and an optional
124
+ `classification`, so a diagnostician's answer is checked the same way every
125
+ other gate's is (RP-195).
126
+ - **An escalation names the diagnostician's verdict.** The `loop` skill no
127
+ longer escalates a failure as a bare stop; it carries the checked verdict that
128
+ says whether the failure is live (RP-195).
129
+
130
+ ### Fixed
131
+
132
+ - **The Definition-of-Done gate no longer leaks the run directory into its own
133
+ checks.** `gate-stop-dod` passed `RIG_RUN_DIR` down to every configured
134
+ command, so a project whose tests spawn the queue CLI wrote fixture records
135
+ into the live run's trace (RP-207).
136
+ - **A closed issue can no longer push open work out of the GitHub queue.** The
137
+ adapter asks for open and closed items in separate windows and says so on
138
+ stderr when a window is capped (RP-208).
139
+ - **`upgrade --dry-run` exits 1 where the real run refuses** a symlinked
140
+ manifest, instead of 0 (RP-206).
141
+ - **`guard-rulebook` no longer loses the path an `apply_patch` actually wrote
142
+ to.** When a guarded prefix such as `.claude/hooks` is a junction or symlink
143
+ to a directory inside the same checkout, the resolved spelling no longer
144
+ starts with a protected prefix, and an unattended `apply_patch` through it was
145
+ allowed. Both spellings are now compared. The four Claude edit surfaces were
146
+ never affected, and a junction pointing outside the checkout already failed
147
+ closed (RP-60).
148
+
149
+ ### Rulebook
150
+
151
+ - **The revalidation detection contract is governance input.**
152
+ `.rig/revalidation.json` decides whether preflight stops and what the scope
153
+ fingerprint watches, and an unattended run could rewrite it. It joins the
154
+ protected rulebook set — as the exact file, because `.rig/claims/` must stay
155
+ writable for a SELECT to record its own baseline. The matching elevated-path
156
+ declaration landed in this repository's own rulebook, not in the payload's
157
+ seed (RP-61).
158
+ - **The independent-oracle invariant.** A test of a security, ownership or
159
+ governance mechanism must not derive its expected result from the same
160
+ production mechanism it checks; `code-reviewer` enforces it as a checklist
161
+ item, and the rule says plainly that no hook can (RP-187).
162
+
163
+ ### Generator repository (not a rig-facing change)
164
+
165
+ - `layers.json` is a declared elevated path of this repository. It is the
166
+ manifest `init` installs _from_ — it names the paths of each layer and never
167
+ names itself — so no rig receives it (RP-211).
168
+ - The Memory conformance probe, `scripts/memory-conformance.mjs`, reports a
169
+ buffer overflow as what it is — a killed child — rather than as a child that
170
+ could not start. The script is not packed (RP-206).
171
+ - The 1.0 contract document, `docs/command-contract.md`, and the seven
172
+ correspondence tests that pin it live in this repository; what a rig gains is
173
+ the promise they hold the commands to, not the files (RP-184).
174
+
17
175
  ## 0.10.1
18
176
 
19
- **A fixes-only patch on 0.10.0.** Every entry corrects existing behaviour or
20
- states a rule the rulebook already follows; nothing is added to what `create`,
21
- `init` or `upgrade` install beyond the corrected files, and no agent, skill,
22
- hook or routing row is new.
177
+ **A fixes-only patch on 0.10.0, plus one narrowing.** Every other entry
178
+ corrects existing behaviour or states a rule the rulebook already follows;
179
+ nothing is added to what `create`, `init` or `upgrade` install beyond the
180
+ corrected files and the Node floor below, and no agent, skill, hook or routing
181
+ row is new.
182
+
183
+ ### Changed
184
+
185
+ - **The Node floor is raised from `>=20` to `>=22`** (`package.json`'s
186
+ `engines.node`), matching the only runtime every CI lane still tests — Node
187
+ 20 has been end-of-life since April 2026. The CLI still runs unchanged on
188
+ any Node 22 or newer (RP-184).
23
189
 
24
190
  ### Fixed
25
191
 
package/README.md CHANGED
@@ -100,13 +100,13 @@ MCP wiring goes into Claude Code's `.mcp.json` entry by entry, but into Codex's
100
100
 
101
101
  ## What Rig installs
102
102
 
103
- | Area | What you get |
104
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
- | Rules | `AGENTS.md`, `CLAUDE.md` and `.claude/rules/`: autonomy tiers (what an agent may do alone), stop rules, TDD workflow and a Definition of Done |
106
- | Guards | Hooks that refuse bypassing pre-commit, force-pushing a shared branch, destructive `rm`, writing credentials, and ending a session while a configured check is red |
107
- | Agents | `test-writer` and `implementation-agent` for the TDD steps; `code-reviewer`, `security-scanner` and `prose-reviewer` for review — each pinned to a model and effort |
108
- | Skills | `worktree-task`, `new-invariant` and `check-premises` |
109
- | Lifecycle metadata | `.claude/.rig-manifest.json`: which bytes Rig installed |
103
+ | Area | What you get |
104
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
105
+ | Rules | `AGENTS.md`, `CLAUDE.md` and `.claude/rules/`: autonomy tiers (what an agent may do alone), stop rules, TDD workflow and a Definition of Done |
106
+ | Guards | Hooks that refuse bypassing pre-commit, force-pushing a shared branch, destructive `rm`, writing credentials, and ending a session while a configured check is red |
107
+ | Agents | `test-writer` and `implementation-agent` for the TDD steps; `code-reviewer`, `security-scanner` and `prose-reviewer` for review; `failure-diagnostician` to reproduce a failure or a claimed finding — each pinned to a model and effort |
108
+ | Skills | `worktree-task`, `new-invariant`, `check-premises`, `skill-authoring` and `diagnose` |
109
+ | Lifecycle metadata | `.claude/.rig-manifest.json`: which bytes Rig installed |
110
110
 
111
111
  A guard is a check on each edit or command before it runs, not a sandbox. Each
112
112
  one states what it does not catch in its own header — `guard-secret-file`, for
@@ -279,7 +279,7 @@ attempted pre-commit bypass.
279
279
 
280
280
  ## Requirements
281
281
 
282
- - Node ≥ 20. The CLI has no runtime dependencies.
282
+ - Node ≥ 22. The CLI has no runtime dependencies.
283
283
  - Git.
284
284
  - For Spec Kit only: `uv` and `uvx`.
285
285
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agent-rig",
3
- "version": "0.10.1",
3
+ "version": "1.0.1",
4
4
  "description": "Configure an agent operating system (rules, gates, hooks) for Claude Code and Codex in a new or existing repository — no application scaffolding",
5
5
  "keywords": [
6
6
  "agent",
@@ -29,7 +29,7 @@
29
29
  "CHANGELOG.md"
30
30
  ],
31
31
  "engines": {
32
- "node": ">=20"
32
+ "node": ">=22"
33
33
  },
34
34
  "packageManager": "pnpm@11.16.0",
35
35
  "scripts": {
@@ -719,6 +719,27 @@ export async function planUpgrade(repoDir, options = {}) {
719
719
  },
720
720
  };
721
721
  }
722
+ /**
723
+ * The complete write set `applyUpgrade` will touch — every `update`/`new`
724
+ * action plus the manifest itself — each resolved with {@link writableOnDisk}
725
+ * so a symlinked (or otherwise unsafe) destination is refused before any
726
+ * write happens. Exported so `--dry-run` (in `index.ts`) can run the exact
727
+ * same check and refuse with the exact same message wherever the real run
728
+ * would: one implementation, read by both call sites, rather than a second
729
+ * copy that could drift from it.
730
+ */
731
+ export async function preflightWritable(repoDir, plan) {
732
+ const destinations = new Map();
733
+ for (const rel of [
734
+ ...plan.actions
735
+ .filter(({ verdict }) => verdict === 'update' || verdict === 'new')
736
+ .map(({ rel }) => rel),
737
+ MANIFEST_REL,
738
+ ]) {
739
+ destinations.set(rel, await writableOnDisk(repoDir, rel));
740
+ }
741
+ return destinations;
742
+ }
722
743
  /**
723
744
  * Write the plan: the `update` and `new` files, then the manifest. Everything
724
745
  * else in the plan is a sentence for a human, not an edit.
@@ -742,15 +763,7 @@ export async function applyUpgrade(repoDir, plan, options = {}) {
742
763
  // any file. Then re-check each destination after mkdir and immediately before
743
764
  // writeFile, so both pre-existing and newly-visible symlink components are
744
765
  // refused.
745
- const destinations = new Map();
746
- for (const rel of [
747
- ...plan.actions
748
- .filter(({ verdict }) => verdict === 'update' || verdict === 'new')
749
- .map(({ rel }) => rel),
750
- MANIFEST_REL,
751
- ]) {
752
- destinations.set(rel, await writableOnDisk(repoDir, rel));
753
- }
766
+ const destinations = await preflightWritable(repoDir, plan);
754
767
  for (const action of plan.actions) {
755
768
  if (action.verdict !== 'update' && action.verdict !== 'new')
756
769
  continue;
@@ -7,7 +7,7 @@ import { InitError, initFileContents, initProject, planInit } from './commands/i
7
7
  import { INTEGRATIONS_VERBS, runIntegrationsCommand } from './commands/integrations.js';
8
8
  import { runSetupWizard } from './commands/setup-wizard.js';
9
9
  import { execFileRunner, setupSubsystems } from './commands/setup.js';
10
- import { AGENTS_MD_RESCUE, UpgradeError, applyUpgrade, planUpgrade } from './commands/upgrade.js';
10
+ import { AGENTS_MD_RESCUE, UpgradeError, applyUpgrade, planUpgrade, preflightWritable, } from './commands/upgrade.js';
11
11
  import { CHANGED_SINCE_PLANNING_REASON, UninstallError, applyUninstall, isUnverifiedReason, planUninstall, protectedFileReason, } from './commands/uninstall.js';
12
12
  import { makePalette } from './lib/colors.js';
13
13
  import { readManifest, sha256 } from './lib/manifest.js';
@@ -500,7 +500,14 @@ async function runUpgrade(rawArgs) {
500
500
  if (notice !== null)
501
501
  process.stdout.write(notice);
502
502
  // The same exit the real run gives: `applyUpgrade` refuses this status.
503
- return plan.agentsRescue.holdBack && plan.agentsRescue.status === 'unsafe' ? 1 : 0;
503
+ if (plan.agentsRescue.holdBack && plan.agentsRescue.status === 'unsafe')
504
+ return 1;
505
+ // Same preflight `applyUpgrade` runs before writing anything (a symlinked
506
+ // manifest or destination, RP-206): thrown here, an `UpgradeError`
507
+ // propagates to `main()`'s own catch below exactly as it does for the
508
+ // real run, so both modes report the identical message and exit 1.
509
+ await preflightWritable(cwd, plan);
510
+ return 0;
504
511
  }
505
512
  // The plan above is the review step, so it has to be answered before
506
513
  // anything is written. On a terminal that is a question; off one it is a
@@ -48,12 +48,13 @@ const root=fs.mkdtempSync(path.join(os.tmpdir(),'rig-guard-fixtures-'));
48
48
  const parent=path.resolve(os.tmpdir()),resolved=path.resolve(root);
49
49
  if(!resolved.startsWith(parent+path.sep)||!path.basename(resolved).startsWith('rig-guard-fixtures-'))throw new Error('unsafe fixture root');
50
50
  let code=1;
51
+ let flag,env;
51
52
  try {
52
53
  const home=path.join(root,'home'); fs.mkdirSync(path.join(root,'.claude','agents'),{recursive:true});
53
54
  fs.writeFileSync(path.join(root,'.claude','agents','code-reviewer.md'),'---\nmodel: gpt-5.6-sol\n---\n');
54
- const env={...process.env,HOME:home,APPDATA:home,CLAUDE_PROJECT_DIR:root};
55
+ env={...process.env,HOME:home,APPDATA:home,CLAUDE_PROJECT_DIR:root};
55
56
  const unattended=path.join(source,'.claude','scripts','unattended-flag.mjs');
56
- const flag=await import(url.pathToFileURL(unattended).href); flag.writeUnattended({item:'fixture',runDir:root,allow:[]},env);
57
+ flag=await import(url.pathToFileURL(unattended).href); flag.writeUnattended({item:'fixture',runDir:root,allow:[]},env);
57
58
  const run=(rel,input)=>cp.spawnSync(process.execPath,[path.join(source,...rel.split('/'))],{cwd:root,env,input:JSON.stringify(input),encoding:'utf8',timeout:5000,maxBuffer:8192}).status;
58
59
  const pre=(tool_input)=>({hook_event_name:'PreToolUse',tool_name:tool_input.command?'Bash':'Write',tool_input});
59
60
  const cases=[
@@ -69,7 +70,10 @@ try {
69
70
  ['.claude/hooks/guard-subagent-model.mjs',{hook_event_name:'PreToolUse',tool_name:'Agent',tool_input:{subagent_type:'code-reviewer'}},true]
70
71
  ];
71
72
  for(const [rel,input,allowed] of cases){const status=run(rel,input);if(allowed ? status!==0 : status!==2){code=1;break;} code=0;}
72
- } finally { fs.rmSync(resolved,{recursive:true,force:true}); }
73
+ } finally {
74
+ try { if(flag&&typeof flag.clearUnattended==='function') flag.clearUnattended(env); } catch {}
75
+ fs.rmSync(resolved,{recursive:true,force:true});
76
+ }
73
77
  process.exitCode=code;
74
78
  })().catch(()=>{process.exitCode=1;});
75
79
  `;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "claudeModels": {
3
- "claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
3
+ "claude-opus-5-5": ["low", "medium", "high", "xhigh", "max"],
4
4
  "claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"]
5
5
  },
6
6
  "unnamed": {
@@ -21,12 +21,16 @@
21
21
  "codex": { "model": "gpt-5.6-terra", "effort": "high" }
22
22
  },
23
23
  "code-reviewer": {
24
- "claude": { "model": "claude-opus-5", "effort": "high" },
25
- "codex": { "model": "gpt-5.6-sol", "effort": "high" }
24
+ "claude": { "model": "claude-opus-5-5", "effort": "high" },
25
+ "codex": { "model": "gpt-6-sol", "effort": "high" }
26
26
  },
27
27
  "security-scanner": {
28
- "claude": { "model": "claude-opus-5", "effort": "high" },
29
- "codex": { "model": "gpt-5.6-sol", "effort": "high" }
28
+ "claude": { "model": "claude-opus-5-5", "effort": "high" },
29
+ "codex": { "model": "gpt-6-sol", "effort": "high" }
30
+ },
31
+ "failure-diagnostician": {
32
+ "claude": { "model": "claude-opus-5-5", "effort": "high" },
33
+ "codex": { "model": "gpt-6-sol", "effort": "high" }
30
34
  }
31
35
  }
32
36
  }
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: diagnose
3
+ description: Use when a check is red or a run crashed and the cause is not obvious, or when a claimed defect or historical finding needs confirming before work is planned on it.
4
+ allowed-tools: Read, Grep, Glob, Bash, Task
5
+ ---
6
+
7
+ # Diagnose before you fix
8
+
9
+ Stop guessing. A red check or a crashed run is never a thing to retry until
10
+ it goes green — that stop rule is already stated in
11
+ `.claude/rules/autonomy.md` ("Flaky ≠ retry"); this skill does not restate
12
+ it.
13
+
14
+ ## Hand it to `failure-diagnostician`
15
+
16
+ Give the agent what it needs to reproduce, verbatim:
17
+
18
+ - for a failure — the exact failure output, the command that produced it,
19
+ and the commit or branch where it failed;
20
+ - for a claim — the claim's own text, and where it came from (a queue item,
21
+ a review comment, a prior finding).
22
+
23
+ Dispatch `failure-diagnostician`. Its method is its own —
24
+ `.claude/agents/failure-diagnostician.md` — not repeated here.
25
+
26
+ ## Check the answer, then act on the word
27
+
28
+ Save its report to a file and run exactly:
29
+
30
+ ```sh
31
+ node .claude/scripts/verdict.mjs check <report> failure-diagnostician
32
+ ```
33
+
34
+ Exit 1 means it did not answer — that is no diagnosis, not a word to act on.
35
+
36
+ | Verdict | Action |
37
+ | --- | --- |
38
+ | `ROOT_CAUSE` / `STILL_LIVE` | the failing test first, through `test-writer` — the Red step in `.claude/rules/workflow.md` |
39
+ | `INCONCLUSIVE` / `INSUFFICIENT_EVIDENCE` | stop; escalate in the format `.claude/rules/autonomy.md` ("Escalation format") sets, carrying the verdict's blockers as what would decide the question |
40
+ | `ALREADY_FIXED` / `OBSOLETE` | close the item, citing the verdict's `evidence` |
41
+
42
+ This is a Core skill: it dispatches no opt-in-workflow-layer machinery, and
43
+ routes on the word alone.
@@ -420,7 +420,11 @@ who was not reading the code at the time, and everything downstream — the fail
420
420
  test, the implementation, the reviewer comparing diff to item — inherits its
421
421
  claims rather than checking them. On `PREMISE FALSE` the item is escalated (§6),
422
422
  not repaired in place: a run that silently re-aims its own task has authored work
423
- for itself, which is the one thing this loop does not do (§8).
423
+ for itself, which is the one thing this loop does not do (§8). A claim
424
+ `check-premises` cannot settle by reading — a claimed defect or a historical
425
+ finding that needs reproducing on the current default branch, not just re-reading — goes to
426
+ `failure-diagnostician` in claim mode instead, never to an unnamed built-in
427
+ subagent.
424
428
 
425
429
  🔴 **And again at the other end, before `pr-ship`: `check-premises` on the prose the
426
430
  task itself wrote** — the rulebook prose the diff touches (the skill defines that set,
@@ -703,7 +707,23 @@ Both then follow the same three steps:
703
707
  its clauses: what was *observed* (verbatim errors, not summaries), and the
704
708
  single question whose answer unblocks the work. So: what fails, what was tried, the
705
709
  current hypothesis, and links to the PR and the failing run where they exist
706
- — a premise stop has neither, and its citation stands in for both. **Name the outcome
710
+ — a premise stop has neither, and its citation stands in for both.
711
+ **For a red check or an unexplained failure, the current hypothesis is the
712
+ diagnostician's parsed verdict**: dispatch `failure-diagnostician` with the
713
+ verbatim failure, save its answer to a file under the run directory, and
714
+ check it —
715
+
716
+ ```sh
717
+ node .claude/scripts/verdict.mjs check <report> failure-diagnostician
718
+ ```
719
+
720
+ — then carry the parsed verdict (word, `classification`, blockers,
721
+ evidence) as the hypothesis instead of writing the diagnosis from scratch,
722
+ with the verbatim failure output still in the comment beside it, covering
723
+ the "observed" clause the list above already names. A `PREMISE FALSE` or
724
+ exhausted-cap stop keeps the diagnosis the paragraph above already
725
+ describes (what the item claimed, or the round count).
726
+ **Name the outcome
707
727
  state in the same comment** — `incomplete` if the diagnosis cannot say **where** it
708
728
  stopped (§5: a thin diagnosis that still locates the wall is a `documented-stall`).
709
729
  Writing `incomplete` on your own task is uncomfortable and
@@ -847,29 +867,52 @@ four things, and a proposal missing any of them is not ready to file:
847
867
  3. the change, concretely enough to diff;
848
868
  4. how the next run would prove it worked — the observation that would differ.
849
869
 
850
- Filing is the adapter's `proposeTriage`, which the CLI deliberately does **not**
851
- expose — `index.mjs` never writes to the QUEUE (`next`, `list`, `hygiene` only), so
852
- that no accidental invocation can change what the next run is handed. Its one
853
- write is to the run journal above, and only into a directory the run declared —
854
- a trace of the selection, never a change to it. Call `proposeTriage` directly:
870
+ Filing is the adapter's `proposeTriage`, reached through the one root-safe entry
871
+ point `.claude/scripts/queue/propose.mjs` never a relative `import()` typed by
872
+ hand, which breaks the moment the session is standing in a subdirectory
873
+ (`ERR_MODULE_NOT_FOUND` from the import, then an `ENOENT` from a cwd-relative
874
+ `PLAN.md` that is not there). The CLI (`index.mjs`) still deliberately does
875
+ **not** expose this — it never writes to the QUEUE (`next`, `list`, `hygiene`
876
+ only), so that no accidental invocation can change what the next run is
877
+ handed. `propose.mjs` resolves its config from its own location, exactly as
878
+ `index.mjs` does, so the proposal lands in the project's real PLAN.md (or
879
+ tracker) and the active board's own options travel with it rather than being
880
+ typed by hand. Write the proposal to a file under the run directory, then run
881
+ the script:
855
882
 
856
883
  ```bash
857
884
  node --input-type=module -e '
858
- const a = await import("./.claude/scripts/queue/plan-md.mjs"); // or github-issues / jira
859
- console.log(await a.proposeTriage({
860
- finding: "<the journal line it came from>",
861
- part: "<skill | agent | hook | rule | AGENTS.md | CLAUDE.md | workflow>",
862
- change: "<concretely enough to diff>",
863
- proof: "<the observation that would differ next run>",
864
- // a pair: what the probe touched, and what is concluded from it. The
865
- // mechanism accepts a proposal without them; this procedure does not.
866
- measured: "<the paths the probe actually exercised>",
867
- inferred: "<the conclusion, citing only surfaces named in measured>",
868
- }, { project: "<KEY>" })); // jira only — the ACTIVE board's key: `queue/index.mjs board --json` → options.project;
869
- // plan-md and github-issues take no second argument
885
+ const fs = await import("node:fs/promises");
886
+ await fs.writeFile(
887
+ `${process.env.RIG_RUN_DIR}/proposal.json`,
888
+ JSON.stringify({
889
+ finding: "<the journal line it came from>",
890
+ part: "<skill | agent | hook | rule | AGENTS.md | CLAUDE.md | workflow>",
891
+ change: "<concretely enough to diff>",
892
+ proof: "<the observation that would differ next run>",
893
+ // a pair: what the probe touched, and what is concluded from it. The
894
+ // mechanism accepts a proposal without them; this procedure does not.
895
+ measured: "<the paths the probe actually exercised>",
896
+ inferred: "<the conclusion, citing only surfaces named in measured>",
897
+ }),
898
+ );
870
899
  '
900
+ # Root-anchored so the same command works whether the session is standing
901
+ # at the repo root or in a subdirectory. Pinned in the generator's
902
+ # test/template/loop-report-file.test.ts (absent in a generated rig) ›
903
+ # "files when the documented command line runs, unmodified, from a project
904
+ # subdirectory".
905
+ node "$(git rev-parse --show-toplevel)/.claude/scripts/queue/propose.mjs" --file "$RIG_RUN_DIR/proposal.json"
871
906
  ```
872
907
 
908
+ The result prints as one JSON line on stdout, and — because `RIG_RUN_DIR` is
909
+ declared — the same result is also recorded as a `proposal` event in the run
910
+ journal, so a failed filing is journalled as a failure instead of silently
911
+ going nowhere. Pinned in the generator's `test/template/queue-propose.test.ts`
912
+ (absent in a generated rig) › "files a proposal with a multiline finding from
913
+ a project subdirectory, into the project-root PLAN.md" and › "journals a
914
+ proposal event with ok: true on a successful filing under RIG_RUN_DIR".
915
+
873
916
  A proposal missing any of the four parts is refused rather than filed half-formed.
874
917
 
875
918
  **A finding can say what it measured and what it inferred, as two paired fields.**
@@ -920,9 +963,9 @@ rather than a step in the procedure: `plan-md` returns it when the plan file has
920
963
  no `## Operator queue` heading, because a proposal then has nowhere to land that
921
964
  the selection query cannot reach. Add the heading — never the Agent queue.
922
965
 
923
- One adapter needs the second argument the snippet above carries: `jira` requires
924
- `options.project` and throws rather than filing without itloudly, so nothing
925
- is lost, but a call that drops it files nothing.
966
+ `jira` still requires `options.project`, and still throws rather than filing
967
+ without it loudly, so nothing is lostand there is no
968
+ second argument left to hand-copy.
926
969
 
927
970
  🔴 **The loop proposes; the owner patches.** Self-applying a change to its own
928
971
  rulebook is how an unattended run drifts irreversibly, and it collides head-on
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: plan-slices
3
+ description: Use when a task cannot be verified as one reviewable PR and splits into slices that are each independently verifiable on their own. Ships only with the opt-in workflow layer.
4
+ allowed-tools: Read, Grep, Glob, Write, Edit
5
+ ---
6
+
7
+ # Splitting a task into slices
8
+
9
+ This applies when a task's change cannot be reviewed and verified as one PR,
10
+ and decomposes into slices that are each independently verifiable — each
11
+ slice stands on its own claim about behaviour, checked by its own test.
12
+
13
+ Do not trigger it mechanically based on file or module count: a change
14
+ that touches many files but makes one verifiable claim stays one PR, and a
15
+ change touching few files but making several independent claims still
16
+ splits.
17
+
18
+ There is no planner role dispatched for this — the session writes the slice
19
+ plan directly. For each slice, record:
20
+
21
+ - the outcome the slice delivers, stated as a claim a test can check;
22
+ - its own failing test, written by `test-writer` in the ordinary Red step;
23
+ - the elevated paths it touches, if any (`AGENTS.md`'s elevated-paths block);
24
+ - where it sits in the slice order, and what it depends on.
25
+
26
+ Each slice then ships as its own PR through the ordinary flow —
27
+ `.claude/rules/workflow.md` has the TDD cycle and the PR flow in full; this
28
+ skill does not restate them.
29
+
30
+ This skill ships only with the opt-in workflow layer.
@@ -39,7 +39,7 @@ blockers.
39
39
  `test/template/gate-rounds.test.ts` — absent in a generated rig — ›
40
40
  "refuses to count a round on a dirty tree, and counts nothing".
41
41
 
42
- The cap is **2 by default**, and no shipped `.claude/queue.json` carries the key
42
+ The cap is **3 by default**, and no shipped `.claude/queue.json` carries the key
43
43
  — the default lives in `core.mjs` as `DEFAULT_MAX_GATE_ROUNDS`. A project that
44
44
  wants a different cap sets `options.maxGateRounds` there, which in a rig whose
45
45
  `queue.json` is composed means changing what composes it, not editing the file.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: release-propose
3
+ description: Use to turn repeated evidence from `release-evidence.mjs` into a bounded candidate-release proposal for the owner to decide. Ships only with the opt-in workflow layer.
4
+ allowed-tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ # Proposing a release from repeated pain
8
+
9
+ ## 1. What this is not
10
+
11
+ The loop's own §7 improvement proposals are per-run fixes, filed and read one
12
+ run at a time. This skill is different: a release-level proposal, built from
13
+ evidence that recurred across more than one run, addressed to the owner —
14
+ never to the queue, and never approved by the skill itself.
15
+
16
+ ## 2. Gather
17
+
18
+ Run exactly:
19
+
20
+ ```sh
21
+ node "$(git rev-parse --show-toplevel)/.claude/scripts/release-evidence.mjs" --since <date> --json
22
+ ```
23
+
24
+ Read its `verdict`, `groups` and `why`. Optionally read
25
+ `revalidation-report.mjs --json` the same way, the triage proposals already
26
+ on file, and the tracker — cite every one of these by pointer (run id, file,
27
+ seq, or ticket id), never from memory.
28
+
29
+ ## 3. Measured vs. inferred
30
+
31
+ A number in the proposal is `measured` only when it came straight out of
32
+ `release-evidence.mjs`'s JSON or a cited line of a run/ticket. Every other
33
+ number or claim is labelled `inferred`, or `UNVERIFIED` when nothing backs it
34
+ at all — never stated as if it were measured.
35
+
36
+ ## 4. Routing
37
+
38
+ - `GATHER_MORE_EVIDENCE` — gather-more-evidence: name what evidence would decide it, file nothing, and stop — never build a candidate release out of anecdotes.
39
+ - `REPEATED_PAIN` — write the proposal (§5) and hand it off (§6).
40
+
41
+ ## 5. Proposal template
42
+
43
+ Write these headings, in order, into `$RIG_RUN_DIR/release-proposal.md`:
44
+
45
+ - **Observed repeated pain** — the repeated groups, each with its pointers
46
+ - **Candidate release** — the bounded scope this pain justifies
47
+ - **Why now**
48
+ - **Why not the alternatives** — including a required "Do nothing" row
49
+ - **Dependencies** — proven only; nothing inferred here
50
+ - **Scope / non-goals**
51
+ - **Complexity** — small, medium or large, plus the maintenance burden it adds
52
+ - **Evidence gaps** — what is still `inferred` or `UNVERIFIED`
53
+ - **Upstream capability check** — could a native plugin, connector, MCP
54
+ server, CLI or provider feature do this instead: sufficient, insufficient
55
+ or rejected, and why
56
+ - **Owner decision** — approve, reject or gather-more-evidence; left blank
57
+ for the owner to fill in, never pre-filled by this skill
58
+
59
+ ## 6. Hand-off
60
+
61
+ - Write the proposal to `$RIG_RUN_DIR/release-proposal.md`.
62
+ - File exactly ONE triage item, pointing at it:
63
+ `node "$(git rev-parse --show-toplevel)/.claude/scripts/queue/propose.mjs" --file <proposal.json>`
64
+ — finding = the repeated-pain groups by pointer; part = `"release"`;
65
+ change = `"candidate release: <one line>"`; proof = what the owner would
66
+ observe if the release lands.
67
+ - A triage item filed this way is unselectable by the queue on its own;
68
+ promotion out of triage into selectable work is the owner's act, never
69
+ this skill's.
70
+ - This skill never files a ticket in the selectable queue, never opens a
71
+ GitHub issue directly, and never edits PLAN.md's Agent queue: the Agent
72
+ queue is not something this skill touches, under any verdict.
73
+
74
+ This skill ships only with the opt-in workflow layer.