create-agent-rig 0.3.1 → 0.4.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 +192 -6
- package/README.md +40 -2
- package/package.json +1 -1
- package/packages/cli/dist/commands/create.js +40 -10
- package/packages/cli/dist/commands/init.js +41 -3
- package/packages/cli/dist/commands/upgrade.js +300 -0
- package/packages/cli/dist/index.js +100 -13
- package/packages/cli/dist/lib/copy-tree.js +9 -1
- package/packages/cli/dist/lib/git-env.js +48 -0
- package/packages/cli/dist/lib/history.js +49 -0
- package/packages/cli/dist/lib/install-set.js +46 -0
- package/packages/cli/dist/lib/manifest.js +99 -0
- package/packages/cli/dist/lib/prompts.js +20 -0
- package/packages/cli/dist/lib/safe-path.js +41 -0
- package/packages/cli/dist/lib/substitute.js +32 -0
- package/packages/cli/dist/lib/targets.js +15 -1
- package/packages/cli/dist/lib/version.js +15 -0
- package/scripts/prepare.mjs +54 -17
- package/templates/agent-os/init/CLAUDE.md +11 -5
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +15 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +104 -0
- package/templates/agent-os/universal/.claude/hooks/gate-stop-dod.mjs +20 -0
- package/templates/agent-os/universal/.claude/rules/workflow.md +4 -0
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +32 -4
- package/templates/agent-os/universal/.claude/scripts/preflight.mjs +34 -1
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +125 -0
- package/templates/agent-os/universal/.claude/scripts/queue/github-issues.mjs +6 -0
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +3 -0
- package/templates/agent-os/universal/.claude/scripts/queue/plan-md.mjs +6 -0
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +125 -0
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +36 -9
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +12 -2
- package/templates/agent-os/universal/CLAUDE.md +12 -3
- package/templates/agent-os/universal/PLAN.md +14 -3
- package/templates/agent-os/universal/layers.json +2 -0
- package/templates/hash-history.json +263 -0
|
@@ -22,7 +22,32 @@
|
|
|
22
22
|
// createdAt: ISO string | null,
|
|
23
23
|
// triage: boolean, // a proposal — never selectable
|
|
24
24
|
// trigger: 'auto' | 'human' | null, // null means unconditional
|
|
25
|
+
// body: string | null, // the item's text — see below
|
|
26
|
+
// raw: string | undefined, // adapter-private; not read here
|
|
25
27
|
// }
|
|
28
|
+
//
|
|
29
|
+
// 🔴 **Why `body` is on the neutral shape, decided rather than drifted into.**
|
|
30
|
+
// Two hygiene checks need the item's text: a body that claims a blocker the
|
|
31
|
+
// links do not carry, and a document link that is broken on its face. The
|
|
32
|
+
// alternative was to implement them inside each adapter — the same invariant in
|
|
33
|
+
// three places, which `.claude/rules/invariants.md` says will disagree, with the
|
|
34
|
+
// copy nobody is looking at being the wrong one. Here they are one function,
|
|
35
|
+
// testable on fixtures, and the adapters stay thin.
|
|
36
|
+
//
|
|
37
|
+
// The item that asked for these called one of them "body vs labels". It is
|
|
38
|
+
// **body vs links**, deliberately: invariant 1 in this same file says a label is
|
|
39
|
+
// never decisive, so a check that compared the body against labels would be
|
|
40
|
+
// asking the one source the rest of the module refuses to trust. Recorded here
|
|
41
|
+
// rather than silently substituted.
|
|
42
|
+
//
|
|
43
|
+
// **`null` is a real answer and it is not `''`.** `plan-md` is a flat list with
|
|
44
|
+
// no per-item body; it must say "I cannot answer" rather than "checked, found
|
|
45
|
+
// nothing", because the second one silently converts a blind spot into a pass.
|
|
46
|
+
// Every check below therefore returns `null` — no finding — when `body` is not
|
|
47
|
+
// a non-empty string.
|
|
48
|
+
//
|
|
49
|
+
// `raw` is the adapter's own record of the line or record it parsed. It is
|
|
50
|
+
// deliberately NOT read by this file: it exists for the adapter's writes.
|
|
26
51
|
|
|
27
52
|
/**
|
|
28
53
|
* The operations every adapter provides. A second tracker is an adapter, not a
|
|
@@ -127,9 +152,109 @@ export const hygieneOf = (ticket) => {
|
|
|
127
152
|
why: `labelled ready while ${open.map((b) => b.id).join(', ')} still blocks it`,
|
|
128
153
|
};
|
|
129
154
|
}
|
|
155
|
+
|
|
156
|
+
const links = ticket.blockedBy ?? [];
|
|
157
|
+
const body = typeof ticket.body === 'string' ? ticket.body : '';
|
|
158
|
+
|
|
159
|
+
// Everything below needs the item's text. `null`/'' means the adapter has none
|
|
160
|
+
// (plan-md), which is "cannot answer" and never a pass — see the shape note at
|
|
161
|
+
// the top of this file.
|
|
162
|
+
if (body.trim() === '') return null;
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
SPLIT_IN_BODY.test(body) &&
|
|
166
|
+
links.length >= 2 &&
|
|
167
|
+
open.length === 0 &&
|
|
168
|
+
ticket.state !== 'closed'
|
|
169
|
+
) {
|
|
170
|
+
return {
|
|
171
|
+
kind: 'split-parent-left-open',
|
|
172
|
+
id: ticket.id,
|
|
173
|
+
why:
|
|
174
|
+
'its body says it was split up, every part it links to is resolved, and it ' +
|
|
175
|
+
'is still open — either it wants closing, or the work it kept is written ' +
|
|
176
|
+
'down nowhere',
|
|
177
|
+
// 🔴 Limit, and the reason this reads the body at all: "every dependency
|
|
178
|
+
// resolved and still open" describes EVERY healthy multi-dependency item
|
|
179
|
+
// from the moment its last blocker lands — including one the queue is about
|
|
180
|
+
// to hand out, and one the loop is working right now. A check that fires on
|
|
181
|
+
// those gets muted, and a muted check reports nothing about anything. The
|
|
182
|
+
// body is the only place the neutral shape carries the word "split", so an
|
|
183
|
+
// adapter without one (plan-md) cannot raise this finding at all.
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (BLOCKER_IN_BODY.test(body) && links.length === 0) {
|
|
188
|
+
return {
|
|
189
|
+
kind: 'body-claims-unlinked-blocker',
|
|
190
|
+
id: ticket.id,
|
|
191
|
+
why:
|
|
192
|
+
'a dependency line in the body names a blocker the item carries no link ' +
|
|
193
|
+
'for, so selection sees it as unblocked. Either the link is missing or the ' +
|
|
194
|
+
'adapter failed to parse it — worse than a stale label, because this one ' +
|
|
195
|
+
'takes work whose blocker may still be open',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const broken = brokenLinkIn(body);
|
|
200
|
+
if (broken) {
|
|
201
|
+
return {
|
|
202
|
+
kind: 'broken-document-link',
|
|
203
|
+
id: ticket.id,
|
|
204
|
+
why:
|
|
205
|
+
`the body links to a document with no destination (${broken}) — the item ` +
|
|
206
|
+
'points at context nobody can reach',
|
|
207
|
+
// 🔴 Limit: this core is pure, so it cannot fetch or stat anything. It
|
|
208
|
+
// catches a link that is broken ON ITS FACE — empty, or a placeholder.
|
|
209
|
+
// A link that is well-formed and dead is invisible here, by design.
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
130
213
|
return null;
|
|
131
214
|
};
|
|
132
215
|
|
|
216
|
+
/**
|
|
217
|
+
* A dependency **line**, matching the convention `github-issues.mjs` parses.
|
|
218
|
+
*
|
|
219
|
+
* Anchoring to the line start is what makes it honest rather than merely narrow.
|
|
220
|
+
* Unanchored, it fired on "this WAS blocked by #7 last week, and #7 landed" and
|
|
221
|
+
* on "nothing is blocked by this item" — then printed a finding asserting a live
|
|
222
|
+
* blocker the body had just denied. A check that reports the opposite of what the
|
|
223
|
+
* text says is worse than no check.
|
|
224
|
+
*
|
|
225
|
+
* Linear: the bounded classes on either side of each boundary are disjoint, so
|
|
226
|
+
* there is no ambiguous split to backtrack over.
|
|
227
|
+
*/
|
|
228
|
+
const BLOCKER_IN_BODY = /^[-*\t ]{0,4}(?:blocked by|depends on|blocker)[ \t:]{0,8}[#A-Za-z0-9]/im;
|
|
229
|
+
|
|
230
|
+
/** The item saying, in its own words, that it was broken into other items. */
|
|
231
|
+
const SPLIT_IN_BODY = /\b(?:split into|split up into|broken into|broken up into|superseded by|subtasks?:)/i;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* A markdown link, destination captured for a plain-string test afterwards.
|
|
235
|
+
*
|
|
236
|
+
* 🔴 The destination is ONE bounded quantifier on purpose. The obvious regex —
|
|
237
|
+
* `\(\s*(?:TODO|TBD)?\s*\)` — puts two unbounded quantifiers around an optional
|
|
238
|
+
* group, which is `\s*\s*`: a whitespace run with no closing paren is re-split at
|
|
239
|
+
* every position. Measured on this module at 1.7s for 32k spaces and ~7s at the
|
|
240
|
+
* 64k body cap, in a function the loop runs for every item in the queue. That is
|
|
241
|
+
* the same defect, in the same shape, that `github-issues.mjs` records fixing —
|
|
242
|
+
* written out here because remembering it once evidently was not enough.
|
|
243
|
+
*/
|
|
244
|
+
const LINK = /\[[^\]]{0,120}\]\(([^)]{0,40})\)/;
|
|
245
|
+
const PLACEHOLDER = /^(?:TODO|TBD|link|url)$/i;
|
|
246
|
+
|
|
247
|
+
/** Control bytes stripped: this string is printed to a terminal. */
|
|
248
|
+
const printable = (text) => text.replace(/[^\x20-\x7E]/g, '').slice(0, 40);
|
|
249
|
+
|
|
250
|
+
const brokenLinkIn = (body) => {
|
|
251
|
+
const match = LINK.exec(body);
|
|
252
|
+
if (!match) return null;
|
|
253
|
+
const destination = String(match[1] ?? '').trim();
|
|
254
|
+
if (destination !== '' && !PLACEHOLDER.test(destination)) return null;
|
|
255
|
+
return printable(match[0]);
|
|
256
|
+
};
|
|
257
|
+
|
|
133
258
|
/**
|
|
134
259
|
* The sort among survivors.
|
|
135
260
|
*
|
|
@@ -74,6 +74,12 @@ export const toTicket = (issue, states = {}) => {
|
|
|
74
74
|
blocks: [],
|
|
75
75
|
priority: priorityLabel ? Number(priorityLabel[1]) : 999,
|
|
76
76
|
createdAt: issue.createdAt ?? null,
|
|
77
|
+
// The body travels on the neutral shape so the hygiene checks live in one
|
|
78
|
+
// place (core.mjs) instead of once per adapter. This adapter also parses it
|
|
79
|
+
// internally for blocker links — the two readings are independent on
|
|
80
|
+
// purpose: that is exactly the disagreement `body-claims-unlinked-blocker`
|
|
81
|
+
// exists to surface.
|
|
82
|
+
body: typeof issue.body === 'string' ? issue.body : null,
|
|
77
83
|
triage: labels.includes('triage'),
|
|
78
84
|
trigger: labels.includes('trigger-auto')
|
|
79
85
|
? 'auto'
|
|
@@ -96,6 +96,9 @@ export const toTicket = (issue) => {
|
|
|
96
96
|
blocks,
|
|
97
97
|
priority: PRIORITY[String(fields.priority?.name ?? '').toLowerCase()] ?? 999,
|
|
98
98
|
createdAt: toIso(fields.created),
|
|
99
|
+
// Flattened from the document description — the same text this adapter
|
|
100
|
+
// already reads internally, now visible to the shared hygiene checks.
|
|
101
|
+
body: descriptionTextOf(issue) || null,
|
|
99
102
|
triage: labels.includes('triage'),
|
|
100
103
|
trigger: labels.includes('trigger-auto')
|
|
101
104
|
? 'auto'
|
|
@@ -91,6 +91,12 @@ export const parsePlan = (plan) => {
|
|
|
91
91
|
raw,
|
|
92
92
|
line: index, // the identity a write uses — never the text
|
|
93
93
|
url: null,
|
|
94
|
+
// 🔴 `null`, not `''`: a flat list has no per-item body, and the hygiene
|
|
95
|
+
// checks must read that as "this adapter cannot answer" rather than
|
|
96
|
+
// "checked, found nothing". An empty string would silently turn a blind
|
|
97
|
+
// spot into a clean bill of health. `raw` above is the line itself, kept
|
|
98
|
+
// for writes — it is not a body and core does not read it as one.
|
|
99
|
+
body: null,
|
|
94
100
|
state: 'open',
|
|
95
101
|
labels: [],
|
|
96
102
|
tier: MARKERS.elevated.test(raw) ? 'elevated' : 'normal',
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: check-premises
|
|
3
|
+
description: Check a queue item's claims about the code before building on them. Use immediately after taking an item and before the failing test — whenever the item asserts that something exists, is missing, is broken, or works a particular way.
|
|
4
|
+
context: fork
|
|
5
|
+
allowed-tools: Read, Grep, Glob, Bash
|
|
6
|
+
argument-hint: <the queue item's text>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
A queue item is a **claim about the code**, written by someone who was not
|
|
10
|
+
reading the code at the time. "The retry path swallows the error", "there is no
|
|
11
|
+
validation on that field", "the worker never gets the second message" — each of
|
|
12
|
+
those is a premise, and the work that follows is only worth doing if it is true.
|
|
13
|
+
|
|
14
|
+
This skill checks the premises. It runs **after selection, before the Red step**,
|
|
15
|
+
and it produces one of three verdicts. It writes nothing.
|
|
16
|
+
|
|
17
|
+
## Why it sits here and not in review
|
|
18
|
+
|
|
19
|
+
A false premise is not caught later. Review reads the diff against the item, and
|
|
20
|
+
both are wrong in the same direction: the item said the validation was missing,
|
|
21
|
+
the diff adds validation, the reviewer sees a diff that does what the item asked.
|
|
22
|
+
Nobody re-reads the file that had the validation all along. The cost lands as a
|
|
23
|
+
duplicate implementation, a "fix" for a bug that was somewhere else entirely, or
|
|
24
|
+
a refactor of a path that no caller reaches — all of it green, reviewed, merged.
|
|
25
|
+
|
|
26
|
+
The check is cheap because it is narrow, and the next section is that narrowness.
|
|
27
|
+
|
|
28
|
+
## 1. Write out the claims — as claims
|
|
29
|
+
|
|
30
|
+
List what the item asserts about the code as it exists **now**. Two to five
|
|
31
|
+
lines. Keep them in the item's own terms; do not repair them while transcribing
|
|
32
|
+
— a claim you have already improved is one you will not test.
|
|
33
|
+
|
|
34
|
+
Separate the claims from the request. "Add a `GET /notes/:id` route" asserts
|
|
35
|
+
nothing; "the route handler bypasses the usecase layer" does.
|
|
36
|
+
|
|
37
|
+
An item that asserts nothing is done here: verdict `PREMISES HOLD`, one line
|
|
38
|
+
saying there were none. That is a common and perfectly good outcome.
|
|
39
|
+
|
|
40
|
+
## 2. Mark the load-bearing ones
|
|
41
|
+
|
|
42
|
+
🔴 **A claim is load-bearing when its falsity changes what gets built.** Only
|
|
43
|
+
those get verified. **This is not an audit** of the item, the file, or the
|
|
44
|
+
codebase — the moment it becomes one, it stops being cheap, gets skipped under
|
|
45
|
+
time pressure, and the whole step is lost.
|
|
46
|
+
|
|
47
|
+
| Load-bearing | Not |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| "there is no X" — if X exists, the task is already done | a stale line number in the item's description |
|
|
50
|
+
| "X is called from Y" — if it is not, the fix goes in the wrong place | a misspelled symbol you can resolve at a glance |
|
|
51
|
+
| "X handles the empty case by Z" — the fix is designed against Z | a claim about a file this task will not touch |
|
|
52
|
+
| "nothing enforces X" — the whole task is the enforcement | a claim the task's own failing test would immediately expose |
|
|
53
|
+
|
|
54
|
+
That last row is the one worth internalising: a premise the Red step would
|
|
55
|
+
falsify in the next five minutes does not need checking here. This step exists
|
|
56
|
+
for the premises a passing test **would not** catch — the ones about code the
|
|
57
|
+
task never touches.
|
|
58
|
+
|
|
59
|
+
## 3. Verify each, against the code, with a citation
|
|
60
|
+
|
|
61
|
+
Read the code. Not the tests, not the docs, not another queue item — those are
|
|
62
|
+
claims too. Each verified premise gets a `file:line` citation; a premise you
|
|
63
|
+
believe but cannot cite is not verified, it is remembered.
|
|
64
|
+
|
|
65
|
+
## 4. The verdict
|
|
66
|
+
|
|
67
|
+
| Verdict | When | What happens next |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `PREMISES HOLD` | every load-bearing claim checked out, or there were none | proceed to the Red step |
|
|
70
|
+
| `PREMISE FALSE` | a load-bearing claim is contradicted by the code | **stop and report** |
|
|
71
|
+
| `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** |
|
|
72
|
+
|
|
73
|
+
🔴 **On `PREMISE FALSE` the answer is stop and report — never quietly work around
|
|
74
|
+
the false premise by building something adjacent that seems useful.** Write what
|
|
75
|
+
the item claimed, what the code actually says with its citation, and what the
|
|
76
|
+
task might become instead. Then let a human re-aim it. The item is wrong, and an
|
|
77
|
+
agent that silently repairs a wrong item produces work nobody asked for, in a
|
|
78
|
+
branch named after a task that does not exist.
|
|
79
|
+
|
|
80
|
+
`UNVERIFIABLE` is not a soft pass. A probe that could not run tells you nothing —
|
|
81
|
+
so the assumption travels in the open, in the item and in the PR description,
|
|
82
|
+
where the next reader can see which part of the work rests on it.
|
|
83
|
+
|
|
84
|
+
## Examples — the three shapes this actually catches
|
|
85
|
+
|
|
86
|
+
**The thing already exists.** Item: "the payload schema does not reject an empty
|
|
87
|
+
title". The schema does reject it, three lines into the validator; the reported
|
|
88
|
+
bug came from a caller that never invoked the validator. Building "the missing
|
|
89
|
+
check" would have added a second, divergent rule and left the real defect —
|
|
90
|
+
the caller — in place. Verdict `PREMISE FALSE`; the task becomes a caller fix.
|
|
91
|
+
|
|
92
|
+
**The thing is somewhere else.** Item: "the worker retries forever because the
|
|
93
|
+
retry budget is not applied". The budget is applied, and correctly; the message
|
|
94
|
+
returns to the queue from a path above it that never consumed the budget at all.
|
|
95
|
+
The fix designed against the item would have been written in a file that was not
|
|
96
|
+
the problem. Verdict `PREMISE FALSE`.
|
|
97
|
+
|
|
98
|
+
**Nothing enforces it — except something does.** Item: "nothing stops a handler
|
|
99
|
+
importing the storage layer directly". A hook does exactly that, and has since
|
|
100
|
+
before the item was filed. Two hours of building a second enforcement mechanism,
|
|
101
|
+
which would then have disagreed with the first. Verdict `PREMISE FALSE`.
|
|
102
|
+
|
|
103
|
+
Note what all three have in common: the resulting work would have been correct,
|
|
104
|
+
tested, reviewable, and useless. That is the failure mode this catches, and it
|
|
105
|
+
is invisible to every gate downstream.
|
|
106
|
+
|
|
107
|
+
## Limits — stated, because a check trusted past its reach is worse than none
|
|
108
|
+
|
|
109
|
+
- **It reads the code, so it only catches what the code can contradict.** A claim
|
|
110
|
+
about runtime behaviour ("this times out in production"), about intent, or
|
|
111
|
+
about a system this repository does not contain is `UNVERIFIABLE` here, not
|
|
112
|
+
false — say so rather than guessing.
|
|
113
|
+
- **It is one pass, before the work.** A premise that becomes false while the
|
|
114
|
+
task runs (a merge lands, a dependency moves) is a staleness stop rule
|
|
115
|
+
(`.claude/rules/autonomy.md`), not this skill.
|
|
116
|
+
- **It has no opinion on whether the task is worth doing.** True premises and a
|
|
117
|
+
pointless task is a perfectly consistent state, and it belongs to whoever fills
|
|
118
|
+
the queue.
|
|
119
|
+
- 🔴 **Nothing makes this run, and the verdict is a self-report.** No hook fires
|
|
120
|
+
when a task starts building on an unchecked claim, and no artifact outlives the
|
|
121
|
+
step — so a run that skipped it and a run that passed it look identical
|
|
122
|
+
afterwards. That is the honest description of every rule of this shape here
|
|
123
|
+
(the `loop` skill says the same about its own no-hand-feeding rule), and it is
|
|
124
|
+
why the citation matters: a `file:line` in the report is the one part of this a
|
|
125
|
+
later reader can re-check.
|
|
@@ -10,9 +10,9 @@ boundaries; the **queue** holds the work; `PLAN.md` holds state, standing
|
|
|
10
10
|
decisions and the journal. This skill is the driver in between: what gets picked,
|
|
11
11
|
what keeps the loop going, what stops it, and where the report goes.
|
|
12
12
|
|
|
13
|
-
Per-task procedure is unchanged: (worktree if another session may run) →
|
|
14
|
-
test first → implement → `pr-ship` → merge on the
|
|
15
|
-
deployed surface if one changed.
|
|
13
|
+
Per-task procedure is unchanged: (worktree if another session may run) →
|
|
14
|
+
`check-premises` → failing test first → implement → `pr-ship` → merge on the
|
|
15
|
+
named criterion → verify the deployed surface if one changed.
|
|
16
16
|
|
|
17
17
|
## 0. The queue is behind an adapter
|
|
18
18
|
|
|
@@ -102,11 +102,18 @@ and the work turns out to touch an elevated path (`CLAUDE.md` →
|
|
|
102
102
|
`elevated-paths`), run the gate anyway, record the verdict on the PR, and treat it
|
|
103
103
|
as this run's elevated item for spacing.
|
|
104
104
|
|
|
105
|
+
**Then, before the Red step: `check-premises`.** The item was written by someone
|
|
106
|
+
who was not reading the code at the time, and everything downstream — the failing
|
|
107
|
+
test, the implementation, the reviewer comparing diff to item — inherits its
|
|
108
|
+
claims rather than checking them. On `PREMISE FALSE` the item is escalated (§6),
|
|
109
|
+
not repaired in place: a run that silently re-aims its own task has authored work
|
|
110
|
+
for itself, which is the one thing this loop does not do (§8).
|
|
111
|
+
|
|
105
112
|
## 3. What keeps the loop running, and what stops it
|
|
106
113
|
|
|
107
114
|
Per-task stops (three strikes, attempt budget, invariant conflict, a blocking
|
|
108
|
-
reviewer verdict) **do not end the run**:
|
|
109
|
-
next one.
|
|
115
|
+
reviewer verdict, a false premise in the item itself) **do not end the run**:
|
|
116
|
+
escalate that item (§5) and take the next one.
|
|
110
117
|
|
|
111
118
|
The run-level conditions are in `stopConditionOf` in `core.mjs`, checked in
|
|
112
119
|
severity order: **queue unreadable** · **runtime regression** · **kill switch** ·
|
|
@@ -204,10 +211,14 @@ mechanises fully (`missed`, `.claude/rules/autonomy.md`) needs no self-report.
|
|
|
204
211
|
## 6. Escalation — two channels, by scope
|
|
205
212
|
|
|
206
213
|
**Task-scoped — the item is the home, and the loop continues.** Three strikes, the
|
|
207
|
-
attempt budget, an invariant conflict,
|
|
214
|
+
attempt budget, an invariant conflict, a blocking reviewer verdict, or a
|
|
215
|
+
`PREMISE FALSE` verdict from `check-premises` — the last one is a
|
|
216
|
+
`documented-stall` (§5), and its diagnosis is already written: what the item
|
|
217
|
+
claimed, what the code says, and the citation:
|
|
208
218
|
|
|
209
219
|
1. Comment the diagnosis on the queue item: what fails, what was tried, the
|
|
210
|
-
current hypothesis, links to the PR and the failing run
|
|
220
|
+
current hypothesis, and links to the PR and the failing run where they exist
|
|
221
|
+
— a premise stop has neither, and its citation stands in for both. **Name the outcome
|
|
211
222
|
state in the same comment** — `incomplete` if the diagnosis cannot say which
|
|
212
223
|
stage needed what. Writing `incomplete` on your own task is uncomfortable and
|
|
213
224
|
is the point: the run that produced it is the only witness.
|
|
@@ -274,6 +285,7 @@ three poisons the only channel by which this project learns.
|
|
|
274
285
|
| Does not | Why |
|
|
275
286
|
| --- | --- |
|
|
276
287
|
| **Create its own work items** | The queue is human-filled. Self-authored work drifts scope, and unattended it drifts unwatched |
|
|
288
|
+
| **Re-aim an item whose premise turned out false** | Same rule wearing a disguise: an item silently rewritten into "what it should have said" is a work item the agent authored. Escalate it (§6) |
|
|
277
289
|
| Take items needing a human decision | It cannot unblock itself; those wait in the Operator queue |
|
|
278
290
|
| Take a `trigger-human` item | It would build for scale that does not exist |
|
|
279
291
|
| Take two elevated items back to back | One unreviewed schema/permissions change is recoverable; a chain overnight is not |
|
|
@@ -291,8 +303,23 @@ three poisons the only channel by which this project learns.
|
|
|
291
303
|
the very next query.
|
|
292
304
|
- **Closing:** close it with the merged PR linked, immediately after the
|
|
293
305
|
post-merge verdict — not in a cleanup pass.
|
|
294
|
-
|
|
295
|
-
|
|
306
|
+
- **Write-back:** with the close, record what it **unblocked** — the items that
|
|
307
|
+
were waiting on this one, by name. It is the journal's `unblocked` field, and
|
|
308
|
+
it is **required, not a step for when it applies**: an absent line and an
|
|
309
|
+
unpaid debt are the same observation from outside, so the empty case has to
|
|
310
|
+
be written to mean anything. Which empty case matters — "nothing was waiting"
|
|
311
|
+
is an answer, "this queue has no dependency links" is the absence of one
|
|
312
|
+
(§0), and a queue that cannot be asked must never be reported as asked.
|
|
313
|
+
|
|
314
|
+
🔴 It is a **report, not an edit to those items.** Blocked state is
|
|
315
|
+
re-resolved from the blocker itself on every selection (§2), so nothing is
|
|
316
|
+
stuck waiting to be corrected — and a label fixed by hand is evidence
|
|
317
|
+
destroyed, which §2 forbids by name. What the write-back buys is the thing no
|
|
318
|
+
query can answer: whether anyone **looked**. Where the close changed a fact
|
|
319
|
+
rather than a state — an Operator-queue item it settles — the paragraph
|
|
320
|
+
closing this section applies instead, and that edit lands in the same PR.
|
|
321
|
+
|
|
322
|
+
Between the opening and the close the item keeps absorbing what happens **as it happens** —
|
|
296
323
|
a decision, a deviation, a defect found in passing, a tier discovered mid-work. A
|
|
297
324
|
run that dies mid-task leaves its whole trail on the item; a run that batches its
|
|
298
325
|
comments to the end leaves nothing.
|
|
@@ -19,10 +19,20 @@ blockers.
|
|
|
19
19
|
(see its README / package scripts). Any failure is an instant HOLD — never
|
|
20
20
|
argue with a red check, never rerun flakiness to green
|
|
21
21
|
(`.claude/rules/workflow.md`).
|
|
22
|
-
3. **Reviewer fan-out.** Launch the `code-reviewer` agent on the diff — always
|
|
22
|
+
3. **Reviewer fan-out.** Launch the `code-reviewer` agent on the diff — always,
|
|
23
|
+
and **pass it the text of the queue item this branch implements**. Its
|
|
24
|
+
checklist blocks on a change that contradicts its item, and a reviewer given
|
|
25
|
+
only a diff cannot run that check: a cold context has no way to know what was
|
|
26
|
+
asked, and reconstructing it from the PR description would mean trusting the
|
|
27
|
+
run under review. If there is no item — owner-directed work, a hotfix — say
|
|
28
|
+
so when launching, and the reviewer skips that item openly instead of
|
|
29
|
+
guessing at it.
|
|
23
30
|
Launch `security-scanner` as well when the diff touches its triggers: auth,
|
|
24
31
|
secrets or configuration, input parsing, file handling, new outbound calls,
|
|
25
|
-
dependency changes.
|
|
32
|
+
dependency changes. Launch `prose-reviewer` when the diff touches a rule
|
|
33
|
+
file, a skill, an agent spec, `CLAUDE.md` or the README — a rulebook that
|
|
34
|
+
overstates its own enforcement fails silently and in the direction of false
|
|
35
|
+
confidence. Run them as subagents, in parallel — a fresh context
|
|
26
36
|
reviews better than the session that wrote the code (see
|
|
27
37
|
`.claude/rules/workflow.md`, "Review-context isolation").
|
|
28
38
|
4. **DoD walk.** Check the Definition of Done list in
|
|
@@ -42,6 +42,10 @@ them all; they are one rulebook.
|
|
|
42
42
|
|
|
43
43
|
- **TDD, without exception.** The failing test comes first — use the
|
|
44
44
|
`test-writer` agent for it. See `.claude/rules/workflow.md`.
|
|
45
|
+
- **Check the task's premises before the test.** A queue item is a claim about
|
|
46
|
+
the code, and nothing downstream re-reads the file it was wrong about — the
|
|
47
|
+
`check-premises` skill runs between taking the item and the failing test, and
|
|
48
|
+
a false load-bearing claim stops the task instead of quietly re-aiming it.
|
|
45
49
|
- **One task, one branch — and merge via PR.** Every unit of work gets its own
|
|
46
50
|
short-lived branch; the default branch is never committed to directly. Once
|
|
47
51
|
the project has a remote and CI, changes reach it through the PR flow (local
|
|
@@ -49,9 +53,14 @@ them all; they are one rulebook.
|
|
|
49
53
|
`.claude/rules/workflow.md` ("Branches and commits", "PR flow"). When another
|
|
50
54
|
session may touch this repo at the same time, the branch lives in its own
|
|
51
55
|
worktree — the `worktree-task` skill has the lifecycle and the cleanup.
|
|
52
|
-
- **Gates.** `code-reviewer`
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
- **Gates.** `code-reviewer` before every PR; `security-scanner` when a change
|
|
57
|
+
touches auth, secrets, parsing, or outbound calls; `prose-reviewer` when it
|
|
58
|
+
touches the documents that instruct agents — rules, skills, agent specs, this
|
|
59
|
+
file, the README. Blocking findings are resolved, not argued with, and the
|
|
60
|
+
`pr-ship` skill drives the fan-out. **No hook launches them** — a gate here is
|
|
61
|
+
a session following a written rule, so "the gate ran" is a claim, not a
|
|
62
|
+
guarantee. The mechanical enforcement below is a different thing, and the
|
|
63
|
+
difference is worth keeping straight.
|
|
55
64
|
- **Enforcement is mechanical.** `guard-core-purity` catches an impure edit to
|
|
56
65
|
the core the moment it lands; `guard-web-boundary` keeps the frontend off the
|
|
57
66
|
backend; `block-no-verify` refuses pre-commit bypasses; `guard-bash` refuses
|
|
@@ -31,7 +31,8 @@ must not take its history with it.
|
|
|
31
31
|
The fields exist so an entry can be visibly **incomplete**. A journal with no
|
|
32
32
|
stated shape decays into a diary that reads fine and proves nothing.
|
|
33
33
|
|
|
34
|
-
<!-- Template — copy the block, drop the fields that do not apply
|
|
34
|
+
<!-- Template — copy the block, drop the fields that do not apply (`unblocked` is
|
|
35
|
+
the exception: it is stated even when the answer is "nothing"):
|
|
35
36
|
|
|
36
37
|
### <one-line summary of the session>
|
|
37
38
|
|
|
@@ -41,8 +42,18 @@ stated shape decays into a diary that reads fine and proves nothing.
|
|
|
41
42
|
- **reviewed** — changes that went through a reviewer gate, and what it returned
|
|
42
43
|
- **stopped at** — which stop condition ended the session (or "checkpoint,
|
|
43
44
|
still running")
|
|
44
|
-
- **
|
|
45
|
-
|
|
45
|
+
- **unblocked** — what the session's closes released. **The field that is never
|
|
46
|
+
dropped** — a missing line and an unpaid debt read identically from outside,
|
|
47
|
+
and this is the only record of whether anyone looked. It has **three**
|
|
48
|
+
answers and they do not substitute for each other: the items that were
|
|
49
|
+
waiting, by name; "nothing was waiting", where the queue carries dependency
|
|
50
|
+
links and none pointed here; and "this queue has no dependency links", where
|
|
51
|
+
it cannot answer at all — a flat-list queue is **absent**, not satisfied, and
|
|
52
|
+
writing "nothing was waiting" there claims a look that no query could perform
|
|
53
|
+
- **queue hygiene** — queue state the session found unreliable and **reported**:
|
|
54
|
+
a stale marker, a dependency already satisfied, an item that describes work
|
|
55
|
+
already done. Reported, never corrected in passing — quietly fixing the
|
|
56
|
+
metadata destroys the evidence that the metadata is unreliable
|
|
46
57
|
- **cost** — the counts the session actually observed: reviewer subagents run,
|
|
47
58
|
CI runs consumed (re-runs included — the cheapest signal that a task fought
|
|
48
59
|
its tests), deploys triggered
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
".claude/agents/test-writer.md",
|
|
10
10
|
".claude/agents/code-reviewer.md",
|
|
11
11
|
".claude/agents/security-scanner.md",
|
|
12
|
+
".claude/agents/prose-reviewer.md",
|
|
12
13
|
".claude/hooks/block-no-verify.mjs",
|
|
13
14
|
".claude/hooks/guard-bash.mjs",
|
|
14
15
|
".claude/hooks/gate-stop-dod.mjs",
|
|
15
16
|
".claude/hooks/inject-rules.mjs",
|
|
16
17
|
".claude/skills/pr-ship/SKILL.md",
|
|
17
18
|
".claude/skills/loop/SKILL.md",
|
|
19
|
+
".claude/skills/check-premises/SKILL.md",
|
|
18
20
|
".claude/skills/worktree-task/SKILL.md",
|
|
19
21
|
".claude/scripts/detect-missed-gate.mjs",
|
|
20
22
|
".claude/scripts/reconcile-external-prs.mjs",
|