create-agent-rig 0.10.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +155 -0
- package/README.md +8 -8
- package/package.json +2 -2
- package/packages/cli/dist/commands/upgrade.js +69 -31
- package/packages/cli/dist/index.js +15 -2
- package/templates/agent-os/subagent-routing.json +4 -0
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +32 -3
- package/templates/agent-os/universal/.agents/skills/diagnose/SKILL.md +43 -0
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +65 -22
- package/templates/agent-os/universal/.agents/skills/plan-slices/SKILL.md +30 -0
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.agents/skills/release-propose/SKILL.md +74 -0
- package/templates/agent-os/universal/.agents/skills/skill-authoring/SKILL.md +39 -0
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +4 -0
- package/templates/agent-os/universal/.claude/agents/failure-diagnostician.md +112 -0
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +18 -5
- package/templates/agent-os/universal/.claude/hooks/guard-rulebook.mjs +21 -6
- package/templates/agent-os/universal/.claude/hooks/lib/edit-input.mjs +13 -3
- package/templates/agent-os/universal/.claude/rules/invariants.md +33 -0
- package/templates/agent-os/universal/.claude/rules/workflow.md +14 -2
- package/templates/agent-os/universal/.claude/scripts/lib/verdict.mjs +63 -0
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +5 -6
- package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +71 -14
- package/templates/agent-os/universal/.claude/scripts/queue/propose.mjs +139 -0
- package/templates/agent-os/universal/.claude/scripts/release-evidence.mjs +188 -0
- package/templates/agent-os/universal/.claude/scripts/revalidation-report.mjs +4 -2
- package/templates/agent-os/universal/.claude/scripts/unattended-flag.mjs +8 -0
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +32 -3
- package/templates/agent-os/universal/.claude/skills/diagnose/SKILL.md +43 -0
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +65 -22
- package/templates/agent-os/universal/.claude/skills/plan-slices/SKILL.md +30 -0
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.claude/skills/release-propose/SKILL.md +74 -0
- package/templates/agent-os/universal/.claude/skills/skill-authoring/SKILL.md +39 -0
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +1 -1
- package/templates/agent-os/universal/.codex/agents/failure-diagnostician.toml +6 -0
- package/templates/agent-os/universal/AGENTS.md +6 -4
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +5 -3
- package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +15 -3
- package/templates/agent-os/universal/layers.json +12 -0
- package/templates/hash-history.json +112 -39
- package/templates/release-ledger.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,161 @@ 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.0
|
|
18
|
+
|
|
19
|
+
**The harness-configuration contract is frozen.** Nothing here changes what a
|
|
20
|
+
`create` or `init` installs in a way a 0.10.x rig would notice; what changes is
|
|
21
|
+
that the surface those commands present — their flags, their JSON shapes, their
|
|
22
|
+
exit codes, their verdict and outcome vocabularies, the manifest they write —
|
|
23
|
+
is now a contract with a stated deprecation policy, and a test suite that goes
|
|
24
|
+
red when the document and the code disagree. The major number says that, and
|
|
25
|
+
only that: from here, removing or renaming something in that surface is a major
|
|
26
|
+
bump, and adding to it is a minor.
|
|
27
|
+
|
|
28
|
+
The rest of this entry is what the rigs themselves gain, down to the last
|
|
29
|
+
section — which is the changes that stayed in the generator, named there so a
|
|
30
|
+
reader does not go looking for them in a rig.
|
|
31
|
+
|
|
32
|
+
### The contract
|
|
33
|
+
|
|
34
|
+
What a rig gains here is the promise, not a file: the document and its tests
|
|
35
|
+
live in the generator, and the section at the end of this entry says so.
|
|
36
|
+
|
|
37
|
+
- **The 1.0 public surface is stated, and so is how it may change** — what is
|
|
38
|
+
promised, what is explicitly not, and what a deprecation costs (RP-184).
|
|
39
|
+
- **Seven correspondences keep it honest**, each red when either side drifts:
|
|
40
|
+
the `upgrade` verdict union, the closed set of seven `doctor` check ids, the
|
|
41
|
+
`setup` outcome values, the manifest keys a fresh `init` writes, the
|
|
42
|
+
integrations declaration schema against a real `setup add`, the packed tarball
|
|
43
|
+
carrying no third-party provider payload, and the CI Node matrix against
|
|
44
|
+
`engines.node` (RP-184).
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **`failure-diagnostician`** — a read-only agent that reproduces a red check or
|
|
49
|
+
a claimed defect on the current default branch and answers with one of six
|
|
50
|
+
checked verdicts, so "is this still live?" is measured before work is planned
|
|
51
|
+
on it (RP-195).
|
|
52
|
+
- **`diagnose`** — the Core skill that dispatches it, for a failure whose cause
|
|
53
|
+
is not obvious (RP-195).
|
|
54
|
+
- **`skill-authoring`** — the Core skill for writing a new skill, with
|
|
55
|
+
structural checks over the ones a rig ships (RP-195).
|
|
56
|
+
- **`plan-slices`** — the workflow-layer skill that cuts a large item into
|
|
57
|
+
slices that can each be shipped on their own (RP-195).
|
|
58
|
+
- **`release-propose`**, and the `release-evidence.mjs` it reads through — the
|
|
59
|
+
workflow-layer skill that proposes a release only from repeated,
|
|
60
|
+
pointer-backed evidence, never from one bad run (RP-203).
|
|
61
|
+
- **`queue/propose.mjs`** — one root-safe entry point for filing a triage
|
|
62
|
+
proposal, so a session standing in a subdirectory files into the project's
|
|
63
|
+
real queue rather than a cwd-relative one that does not exist (RP-209).
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- **The gate-round cap defaults to three**, not two — one round to find, one to
|
|
68
|
+
fix, one to confirm (RP-210).
|
|
69
|
+
- **The verdict vocabulary carries the diagnosis words** and an optional
|
|
70
|
+
`classification`, so a diagnostician's answer is checked the same way every
|
|
71
|
+
other gate's is (RP-195).
|
|
72
|
+
- **An escalation names the diagnostician's verdict.** The `loop` skill no
|
|
73
|
+
longer escalates a failure as a bare stop; it carries the checked verdict that
|
|
74
|
+
says whether the failure is live (RP-195).
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **The Definition-of-Done gate no longer leaks the run directory into its own
|
|
79
|
+
checks.** `gate-stop-dod` passed `RIG_RUN_DIR` down to every configured
|
|
80
|
+
command, so a project whose tests spawn the queue CLI wrote fixture records
|
|
81
|
+
into the live run's trace (RP-207).
|
|
82
|
+
- **A closed issue can no longer push open work out of the GitHub queue.** The
|
|
83
|
+
adapter asks for open and closed items in separate windows and says so on
|
|
84
|
+
stderr when a window is capped (RP-208).
|
|
85
|
+
- **`upgrade --dry-run` exits 1 where the real run refuses** a symlinked
|
|
86
|
+
manifest, instead of 0 (RP-206).
|
|
87
|
+
- **`guard-rulebook` no longer loses the path an `apply_patch` actually wrote
|
|
88
|
+
to.** When a guarded prefix such as `.claude/hooks` is a junction or symlink
|
|
89
|
+
to a directory inside the same checkout, the resolved spelling no longer
|
|
90
|
+
starts with a protected prefix, and an unattended `apply_patch` through it was
|
|
91
|
+
allowed. Both spellings are now compared. The four Claude edit surfaces were
|
|
92
|
+
never affected, and a junction pointing outside the checkout already failed
|
|
93
|
+
closed (RP-60).
|
|
94
|
+
|
|
95
|
+
### Rulebook
|
|
96
|
+
|
|
97
|
+
- **The revalidation detection contract is governance input.**
|
|
98
|
+
`.rig/revalidation.json` decides whether preflight stops and what the scope
|
|
99
|
+
fingerprint watches, and an unattended run could rewrite it. It joins the
|
|
100
|
+
protected rulebook set — as the exact file, because `.rig/claims/` must stay
|
|
101
|
+
writable for a SELECT to record its own baseline. The matching elevated-path
|
|
102
|
+
declaration landed in this repository's own rulebook, not in the payload's
|
|
103
|
+
seed (RP-61).
|
|
104
|
+
- **The independent-oracle invariant.** A test of a security, ownership or
|
|
105
|
+
governance mechanism must not derive its expected result from the same
|
|
106
|
+
production mechanism it checks; `code-reviewer` enforces it as a checklist
|
|
107
|
+
item, and the rule says plainly that no hook can (RP-187).
|
|
108
|
+
|
|
109
|
+
### Generator repository (not a rig-facing change)
|
|
110
|
+
|
|
111
|
+
- `layers.json` is a declared elevated path of this repository. It is the
|
|
112
|
+
manifest `init` installs _from_ — it names the paths of each layer and never
|
|
113
|
+
names itself — so no rig receives it (RP-211).
|
|
114
|
+
- The Memory conformance probe, `scripts/memory-conformance.mjs`, reports a
|
|
115
|
+
buffer overflow as what it is — a killed child — rather than as a child that
|
|
116
|
+
could not start. The script is not packed (RP-206).
|
|
117
|
+
- The 1.0 contract document, `docs/command-contract.md`, and the seven
|
|
118
|
+
correspondence tests that pin it live in this repository; what a rig gains is
|
|
119
|
+
the promise they hold the commands to, not the files (RP-184).
|
|
120
|
+
|
|
121
|
+
## 0.10.1
|
|
122
|
+
|
|
123
|
+
**A fixes-only patch on 0.10.0, plus one narrowing.** Every other entry
|
|
124
|
+
corrects existing behaviour or states a rule the rulebook already follows;
|
|
125
|
+
nothing is added to what `create`, `init` or `upgrade` install beyond the
|
|
126
|
+
corrected files and the Node floor below, and no agent, skill, hook or routing
|
|
127
|
+
row is new.
|
|
128
|
+
|
|
129
|
+
### Changed
|
|
130
|
+
|
|
131
|
+
- **The Node floor is raised from `>=20` to `>=22`** (`package.json`'s
|
|
132
|
+
`engines.node`), matching the only runtime every CI lane still tests — Node
|
|
133
|
+
20 has been end-of-life since April 2026. The CLI still runs unchanged on
|
|
134
|
+
any Node 22 or newer (RP-184).
|
|
135
|
+
|
|
136
|
+
### Fixed
|
|
137
|
+
|
|
138
|
+
- **`upgrade` no longer crashes on a directory where a rig-owned file
|
|
139
|
+
belongs.** A tracked path that is a directory or another non-regular entry
|
|
140
|
+
is reported as a conflict with a reason and left untouched, in `--dry-run`
|
|
141
|
+
and `--yes` alike, instead of ending in an `EISDIR` stack trace (RP-189).
|
|
142
|
+
- **`upgrade` re-checks a leftover `AGENTS.md.rig-new` before removing it.**
|
|
143
|
+
The file is removed only while it still holds exactly the rendered
|
|
144
|
+
`AGENTS.md` bytes, so one edited after the plan was shown is kept, one
|
|
145
|
+
removed in the meantime no longer crashes the run, and the closing notice
|
|
146
|
+
reports a removal only when one happened (RP-202).
|
|
147
|
+
- **`upgrade --dry-run` exits 1 where the real run refuses** a non-file at
|
|
148
|
+
`AGENTS.md.rig-new`, instead of 0 (RP-202).
|
|
149
|
+
- **The Stop gate's budget is measured on the monotonic clock.**
|
|
150
|
+
`gate-stop-dod` computed its shared deadline from the wall clock, so a clock
|
|
151
|
+
step lengthened or shortened it (RP-204).
|
|
152
|
+
|
|
153
|
+
### Rulebook
|
|
154
|
+
|
|
155
|
+
- `workflow.md` states the review-findings rule as a Core section of its own:
|
|
156
|
+
a blocking finding closes by a fix the reviewer reads again, or by evidence
|
|
157
|
+
the reviewer can re-check that its premise is false — with or without the
|
|
158
|
+
opt-in workflow layer (RP-194).
|
|
159
|
+
- `check-premises` treats a claim about an external API, CLI or library as a
|
|
160
|
+
premise: backed by a four-part record (version, authoritative source, date,
|
|
161
|
+
quote or pointer), it holds or is `PREMISE FALSE`; without one it stays
|
|
162
|
+
`UNVERIFIABLE` (RP-194).
|
|
163
|
+
|
|
164
|
+
### Generator repository (not a rig-facing change)
|
|
165
|
+
|
|
166
|
+
- `packages/cli/src/commands/integrations.ts` is a declared elevated path.
|
|
167
|
+
- `scripts/memory-conformance.mjs` names why a rig spawn failed — exit code or
|
|
168
|
+
signal plus the child's error code — instead of a bare "exit 1" (RP-188).
|
|
169
|
+
- `docs/decisions/agent-roles-1.0.md` records the 1.0 agent-role targets; it
|
|
170
|
+
is repository-local and not shipped.
|
|
171
|
+
|
|
17
172
|
## 0.10.0
|
|
18
173
|
|
|
19
174
|
Platforms: the packed release candidate is accepted on Linux, Windows and
|
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
|
|
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 ≥
|
|
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.
|
|
3
|
+
"version": "1.0.0",
|
|
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": ">=
|
|
32
|
+
"node": ">=22"
|
|
33
33
|
},
|
|
34
34
|
"packageManager": "pnpm@11.16.0",
|
|
35
35
|
"scripts": {
|
|
@@ -149,23 +149,20 @@ async function writableOnDisk(repoDir, rel) {
|
|
|
149
149
|
}
|
|
150
150
|
return dest;
|
|
151
151
|
}
|
|
152
|
-
/**
|
|
153
|
-
* The file's bytes, or `null` when it is genuinely **absent**.
|
|
154
|
-
*
|
|
155
|
-
* Only "not there" is absence. Any other failure — a permission, a directory
|
|
156
|
-
* where a file should be, a path this command refuses to touch — is rethrown,
|
|
157
|
-
* because "I could not read your file" must never become "so I wrote mine over
|
|
158
|
-
* it": every caller of this treats `null` as grounds to install.
|
|
159
|
-
*/
|
|
160
152
|
async function readIfPresent(repoDir, rel) {
|
|
153
|
+
const dest = await writableOnDisk(repoDir, rel);
|
|
154
|
+
let stat;
|
|
161
155
|
try {
|
|
162
|
-
|
|
156
|
+
stat = await lstat(dest);
|
|
163
157
|
}
|
|
164
158
|
catch (error) {
|
|
165
159
|
if (error.code === 'ENOENT')
|
|
166
|
-
return
|
|
160
|
+
return { kind: 'absent' };
|
|
167
161
|
throw error;
|
|
168
162
|
}
|
|
163
|
+
if (!stat.isFile())
|
|
164
|
+
return { kind: 'non-file' };
|
|
165
|
+
return { kind: 'file', bytes: await readFile(dest) };
|
|
169
166
|
}
|
|
170
167
|
/**
|
|
171
168
|
* The bytes THIS release would render for AGENTS.md, for THIS project,
|
|
@@ -394,7 +391,18 @@ export async function planUpgrade(repoDir, options = {}) {
|
|
|
394
391
|
// CLAUDE.md's CURRENT content when it holds the shim back.
|
|
395
392
|
const currentBytesByRel = new Map();
|
|
396
393
|
for (const file of files) {
|
|
397
|
-
const
|
|
394
|
+
const currentFile = await readIfPresent(repoDir, file.rel);
|
|
395
|
+
if (currentFile.kind === 'non-file') {
|
|
396
|
+
actions.push({
|
|
397
|
+
rel: file.rel,
|
|
398
|
+
verdict: 'conflict',
|
|
399
|
+
reason: 'a directory or other non-regular entry exists where this rig-owned file belongs — ' +
|
|
400
|
+
'move or remove it by hand; upgrade will leave it untouched',
|
|
401
|
+
templatePath: file.source,
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const currentBytes = currentFile.kind === 'file' ? currentFile.bytes : null;
|
|
398
406
|
if (currentBytes !== null)
|
|
399
407
|
currentBytesByRel.set(file.rel, currentBytes);
|
|
400
408
|
const recordedInManifest = manifest?.files[file.rel];
|
|
@@ -620,7 +628,8 @@ export async function planUpgrade(repoDir, options = {}) {
|
|
|
620
628
|
nextFiles['CLAUDE.md'] = sha256(heldBytes);
|
|
621
629
|
}
|
|
622
630
|
// Round 5, blocker 3: the rescue file's status is decided HERE, once, at
|
|
623
|
-
// plan time — never re-probed by `applyUpgrade
|
|
631
|
+
// plan time — never re-probed by `applyUpgrade` except to re-verify a
|
|
632
|
+
// `cleanup` before deleting (RP-192 item 4), so a dry run and a real
|
|
624
633
|
// run report the identical status and every refusal is known before a
|
|
625
634
|
// single byte is written. Round 4 cycle 4's blocker 3 was exactly the
|
|
626
635
|
// alternative: probing the filesystem again inside `applyUpgrade`, AFTER
|
|
@@ -710,14 +719,36 @@ export async function planUpgrade(repoDir, options = {}) {
|
|
|
710
719
|
},
|
|
711
720
|
};
|
|
712
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
|
+
}
|
|
713
743
|
/**
|
|
714
744
|
* Write the plan: the `update` and `new` files, then the manifest. Everything
|
|
715
745
|
* else in the plan is a sentence for a human, not an edit.
|
|
716
746
|
*/
|
|
717
747
|
export async function applyUpgrade(repoDir, plan, options = {}) {
|
|
718
748
|
const written = [];
|
|
749
|
+
let removedRescue = false;
|
|
719
750
|
if (options.dryRun === true)
|
|
720
|
-
return { written };
|
|
751
|
+
return { written, removedRescue };
|
|
721
752
|
// Round 5, blocker 3: refused BEFORE any write at all — not merely before
|
|
722
753
|
// the manifest write. Round 4 cycle 4 measured that probing the rescue
|
|
723
754
|
// path AFTER the ordinary payload writes left files rewritten and the
|
|
@@ -732,15 +763,7 @@ export async function applyUpgrade(repoDir, plan, options = {}) {
|
|
|
732
763
|
// any file. Then re-check each destination after mkdir and immediately before
|
|
733
764
|
// writeFile, so both pre-existing and newly-visible symlink components are
|
|
734
765
|
// refused.
|
|
735
|
-
const destinations =
|
|
736
|
-
for (const rel of [
|
|
737
|
-
...plan.actions
|
|
738
|
-
.filter(({ verdict }) => verdict === 'update' || verdict === 'new')
|
|
739
|
-
.map(({ rel }) => rel),
|
|
740
|
-
MANIFEST_REL,
|
|
741
|
-
]) {
|
|
742
|
-
destinations.set(rel, await writableOnDisk(repoDir, rel));
|
|
743
|
-
}
|
|
766
|
+
const destinations = await preflightWritable(repoDir, plan);
|
|
744
767
|
for (const action of plan.actions) {
|
|
745
768
|
if (action.verdict !== 'update' && action.verdict !== 'new')
|
|
746
769
|
continue;
|
|
@@ -755,12 +778,12 @@ export async function applyUpgrade(repoDir, plan, options = {}) {
|
|
|
755
778
|
await writeFile(await writableOnDisk(repoDir, action.rel), content);
|
|
756
779
|
written.push(action.rel);
|
|
757
780
|
}
|
|
758
|
-
// Round 5:
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
// nothing at all — round 4 cycle 4's "must not affect upgrade" case.
|
|
781
|
+
// Round 5: `plan.agentsRescue` says what (if anything) this run may do,
|
|
782
|
+
// decided before any write above ever ran. `would-write` is the only status
|
|
783
|
+
// that writes; `identical` and `differs` are both "leave it exactly as it
|
|
784
|
+
// is"; `cleanup` is the only status that deletes, and the one that reads the
|
|
785
|
+
// file again before it does (below); `unsafe` already returned above and
|
|
786
|
+
// `none` does nothing at all — round 4 cycle 4's "must not affect upgrade" case.
|
|
764
787
|
if (plan.agentsRescue.holdBack) {
|
|
765
788
|
if (plan.agentsRescue.status === 'would-write') {
|
|
766
789
|
const rendered = plan.contents.get('AGENTS.md');
|
|
@@ -774,9 +797,24 @@ export async function applyUpgrade(repoDir, plan, options = {}) {
|
|
|
774
797
|
}
|
|
775
798
|
}
|
|
776
799
|
else if (plan.agentsRescue.status === 'cleanup') {
|
|
777
|
-
|
|
778
|
-
|
|
800
|
+
// The one deletion here is re-verified at apply time, the way uninstall
|
|
801
|
+
// re-checks `recordedHash`: an interactive run applies after its prompt,
|
|
802
|
+
// so the leftover may have been edited or removed since it was matched.
|
|
803
|
+
const rendered = plan.contents.get('AGENTS.md');
|
|
804
|
+
const current = await readRescueFile(repoDir);
|
|
805
|
+
if (rendered !== undefined &&
|
|
806
|
+
current.kind === 'file' &&
|
|
807
|
+
current.bytes.equals(Buffer.from(rendered, 'utf8'))) {
|
|
808
|
+
try {
|
|
809
|
+
await unlink(await writableOnDisk(repoDir, AGENTS_MD_RESCUE));
|
|
810
|
+
removedRescue = true;
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
if (error.code !== 'ENOENT')
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
779
817
|
}
|
|
780
818
|
await writeManifest(repoDir, plan.manifest);
|
|
781
|
-
return { written };
|
|
819
|
+
return { written, removedRescue };
|
|
782
820
|
}
|
|
@@ -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';
|
|
@@ -499,6 +499,14 @@ async function runUpgrade(rawArgs) {
|
|
|
499
499
|
const notice = renderAgentsRescueNotice(plan.agentsRescue, true);
|
|
500
500
|
if (notice !== null)
|
|
501
501
|
process.stdout.write(notice);
|
|
502
|
+
// The same exit the real run gives: `applyUpgrade` refuses this status.
|
|
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);
|
|
502
510
|
return 0;
|
|
503
511
|
}
|
|
504
512
|
// The plan above is the review step, so it has to be answered before
|
|
@@ -562,7 +570,12 @@ async function runUpgrade(rawArgs) {
|
|
|
562
570
|
// blockers 1/2) means genuinely nothing to add: an ordinary, readable,
|
|
563
571
|
// customised AGENTS.md conflict, or unrelated clutter at the rescue path,
|
|
564
572
|
// both print nothing here at all.
|
|
565
|
-
|
|
573
|
+
// A `cleanup` the apply step did not carry out (the leftover changed or went
|
|
574
|
+
// away after the plan) reports nothing rather than a removal.
|
|
575
|
+
const rescue = plan.agentsRescue.status === 'cleanup' && !result.removedRescue
|
|
576
|
+
? { holdBack: false, status: 'none' }
|
|
577
|
+
: plan.agentsRescue;
|
|
578
|
+
const notice = renderAgentsRescueNotice(rescue, false);
|
|
566
579
|
if (notice !== null)
|
|
567
580
|
process.stdout.write(notice);
|
|
568
581
|
return 0;
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"security-scanner": {
|
|
28
28
|
"claude": { "model": "claude-opus-5", "effort": "high" },
|
|
29
29
|
"codex": { "model": "gpt-5.6-sol", "effort": "high" }
|
|
30
|
+
},
|
|
31
|
+
"failure-diagnostician": {
|
|
32
|
+
"claude": { "model": "claude-opus-5", "effort": "high" },
|
|
33
|
+
"codex": { "model": "gpt-5.6-sol", "effort": "high" }
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -91,13 +91,40 @@ At the **second** entry point this inverts for one case: a test is exactly what
|
|
|
91
91
|
a behaviour claim, so reading it is the point. The rule above is about not letting a
|
|
92
92
|
test's *name* stand in for what the code does; §4 says which artifacts count.
|
|
93
93
|
|
|
94
|
+
### External premises
|
|
95
|
+
|
|
96
|
+
A claim about something this repository does not contain — an external API, a
|
|
97
|
+
CLI, a library — is a premise like any other, and §2 decides whether it is
|
|
98
|
+
load-bearing. "The provider accepts a `--json` flag" or "the SDK retries on 429"
|
|
99
|
+
changes what gets built if it is false. The code here cannot settle it, so this
|
|
100
|
+
is the one case where documentation is the evidence, and it is recorded in four
|
|
101
|
+
parts:
|
|
102
|
+
|
|
103
|
+
- **version** — the exact version this project uses (the lockfile, the installed
|
|
104
|
+
binary's own version output), not "latest";
|
|
105
|
+
- **source** — an authoritative one for that version: the vendor's reference
|
|
106
|
+
documentation, its changelog, or the tool's own `--help`;
|
|
107
|
+
- **date** — when you read it, because documentation changes under a fixed URL;
|
|
108
|
+
- **quote** or pointer — the sentence that says it, short enough to re-check, or
|
|
109
|
+
the exact section it sits in.
|
|
110
|
+
|
|
111
|
+
A complete record that supports the claim lets it hold. One that contradicts it
|
|
112
|
+
is `PREMISE FALSE`, and the work stops exactly as it does for a claim the code
|
|
113
|
+
contradicts. The four parts go in the report's `evidence`, and in the blocker's
|
|
114
|
+
`note` when there is one.
|
|
115
|
+
|
|
116
|
+
A claim missing any of the four stays `UNVERIFIABLE`: being widely believed, or
|
|
117
|
+
true of an earlier version, does not promote it to a fact. This skill adds no
|
|
118
|
+
network tooling — where the session cannot reach the source, the claim is
|
|
119
|
+
`UNVERIFIABLE` and travels as a labelled assumption, as §4 says.
|
|
120
|
+
|
|
94
121
|
## 4. The verdict
|
|
95
122
|
|
|
96
123
|
| Verdict | When | What happens next |
|
|
97
124
|
| --- | --- | --- |
|
|
98
125
|
| `PREMISES HOLD` | every load-bearing claim checked out, or there were none | proceed to the Red step |
|
|
99
|
-
| `PREMISE FALSE` | a load-bearing claim is contradicted by the code | **stop and report** |
|
|
100
|
-
| `UNVERIFIABLE` | a load-bearing claim could not be decided from the code | report it as unverifiable, name what would decide it, and proceed only under a **labelled assumption** |
|
|
126
|
+
| `PREMISE FALSE` | a load-bearing claim is contradicted by the code — or, for an external premise, by its four-part record | **stop and report** |
|
|
127
|
+
| `UNVERIFIABLE` | a load-bearing claim could not be decided from the code — or, for an external premise, has no complete four-part record | report it as unverifiable, name what would decide it, and proceed only under a **labelled assumption** |
|
|
101
128
|
| `UNMEASURED` | **second entry point only:** a sentence you wrote asserts behaviour, and nothing you can point at backs it | **delete the sentence, or turn it into a pointer to the test that proves it** — before the gate |
|
|
102
129
|
|
|
103
130
|
🔴 **The edit belongs to the calling session, not to this skill.** It reports; the
|
|
@@ -223,7 +250,9 @@ is invisible to every gate downstream.
|
|
|
223
250
|
- **It reads the code, so it only catches what the code can contradict.** A claim
|
|
224
251
|
about runtime behaviour ("this times out in production"), about intent, or
|
|
225
252
|
about a system this repository does not contain is `UNVERIFIABLE` here, not
|
|
226
|
-
false — say so rather than guessing.
|
|
253
|
+
false — say so rather than guessing. For an external API, CLI or library the
|
|
254
|
+
way out is the four-part record "External premises" asks for, which can make
|
|
255
|
+
the claim hold or prove it false.
|
|
227
256
|
- **Each entry point is one pass, at its own end of the task.** A premise that goes
|
|
228
257
|
false *between* them — a merge lands, a dependency moves — is a staleness stop rule
|
|
229
258
|
(`.claude/rules/autonomy.md`), not this skill. Neither pass watches the other's
|
|
@@ -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.
|
|
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`,
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
966
|
+
`jira` still requires `options.project`, and still throws rather than filing
|
|
967
|
+
without it — loudly, so nothing is lost — and 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
|