@feigi/fleet-ctl 3.82.19 → 3.82.21
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/agents/fleet-merge-bot.agent.md +6 -6
- package/package.json +1 -1
- package/scripts/tier-outcomes.mjs +46 -6
- package/skills/run-team/SKILL.md +178 -579
- package/skills/run-team/references/ci-and-staleness.md +47 -0
- package/skills/run-team/references/ci-watcher.md +138 -3
- package/skills/run-team/references/close-out.md +34 -1
- package/skills/run-team/references/failure-handling.md +43 -0
- package/skills/run-team/references/finisher.md +185 -0
- package/skills/run-team/references/instruments.md +20 -0
- package/skills/run-team/references/isolation.md +10 -0
- package/skills/run-team/references/ledger.md +4 -0
- package/skills/run-team/references/member-lifecycle.md +18 -0
- package/skills/run-team/references/phase-0.md +50 -0
- package/skills/run-team/references/pull.md +36 -1
- package/skills/run-team/references/reviewers.md +81 -0
- package/skills/run-team/references/shell-traps.md +10 -9
- package/skills/run-team/references/tick-rows.md +35 -0
package/skills/run-team/SKILL.md
CHANGED
|
@@ -47,13 +47,8 @@ phase, or in any later one, asks the maintainer which tickets to take.
|
|
|
47
47
|
`$ARGUMENTS` gave one, `--plugin-checkout <dir>` when this workspace tracks
|
|
48
48
|
no `plugin/…` of its own, `--spend-dir <dir>` to name the directory the
|
|
49
49
|
cockpit's spend panel reads, and `--workflow <name>` or `--declare-no-ci`
|
|
50
|
-
when the repository has no workflow named CI.
|
|
51
|
-
|
|
52
|
-
tier-roles, metrics, cockpit, rotate, reclaim, recipe, fold-in, stranded,
|
|
53
|
-
shortlist — prints one `phase0: <step>: <status>` line per step with the
|
|
54
|
-
`NEED` lines last, and records each step in `.fleet/phase0.json`, which the
|
|
55
|
-
tick reads: it prints `PHASE0-INCOMPLETE` and holds every dispatching row
|
|
56
|
-
until the receipt is whole. Dispatch nothing before it exits 0. Exit 4 is a
|
|
50
|
+
when the repository has no workflow named CI. Its steps and receipt: See
|
|
51
|
+
references/phase-0.md. Dispatch nothing before it exits 0. Exit 4 is a
|
|
57
52
|
step that needs you: act on each `NEED` line below, then run it again. Once
|
|
58
53
|
`rotate` has run, a re-run skips every `ok` step and resumes where the last
|
|
59
54
|
one stopped; before it, a re-run runs every step again.
|
|
@@ -90,43 +85,12 @@ phase, or in any later one, asks the maintainer which tickets to take.
|
|
|
90
85
|
go on. See references/phase-0.md.
|
|
91
86
|
|
|
92
87
|
1. **Build the Shortlist** — `~/.fleet/bin/fleet-run shortlist.mjs`, then read
|
|
93
|
-
`.fleet/shortlist.json
|
|
94
|
-
|
|
95
|
-
the
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`scanned`, the supply; the rows its to-spec filter removed are
|
|
100
|
-
`dropped`, counted nowhere.
|
|
101
|
-
- **Dependency scan**, `next-ticket` step 2, on each row's `d` array. Open
|
|
102
|
-
blocker → drop.
|
|
103
|
-
- **Live Exclusions** — a ticket whose ledger row reads `excluded ·
|
|
104
|
-
behind-pr:#M` or `excluded · behind-issue:#M` stays out while its premise
|
|
105
|
-
holds (references/pull.md, **Exclusion**).
|
|
106
|
-
- **In-flight check** — `inflight.sh <N>` per survivor. Exit 1 is taken, and
|
|
107
|
-
any other non-zero exit is a check that could not answer, which is never
|
|
108
|
-
free. Both drop the ticket, logged by number.
|
|
109
|
-
|
|
110
|
-
It writes the survivors, in order, to `.fleet/shortlist.json` —
|
|
111
|
-
`{scanned, shortlist: [{n, t}], dropped: [{n, reason}]}` — resolved against the git common dir by
|
|
112
|
-
`fleet-dir.mjs`, the one locator `ledger.mjs` resolves `.fleet/ledger.md`
|
|
113
|
-
with too, so you, the tick and a member in its own worktree all name one
|
|
114
|
-
file. **Exit 0 is the file written,
|
|
115
|
-
an empty shortlist included** — the empty queue is an answer. **Exit 2 is a
|
|
116
|
-
refusal**: the scan, the ledger read or the write did not answer, and the
|
|
117
|
-
previous file stands untouched, because an empty file written then would
|
|
118
|
-
read as "no work". Never read 2 as empty.
|
|
119
|
-
|
|
120
|
-
Every probe fails CLOSED: an unreadable blocker counts as open, an
|
|
121
|
-
unreadable premise leaves its exclusion standing, an unanswered in-flight
|
|
122
|
-
check counts as taken. A ticket dropped wrongly waits one refresh; a ticket
|
|
123
|
-
admitted wrongly costs a Pull's full read at best and two members on one
|
|
124
|
-
ticket at worst.
|
|
125
|
-
|
|
126
|
-
**`--require-label ready-for-agent` mandatory, no fallback** — the script
|
|
127
|
-
fixes it, and you never run `candidates.mjs --allow-fallback` by hand to
|
|
128
|
-
widen the list: `ready-for-human` needs a human to brainstorm first and you
|
|
129
|
-
have no channel to one mid-flight.
|
|
88
|
+
`.fleet/shortlist.json` — `{scanned, shortlist: [{n, t}], dropped: [{n, reason}]}`:
|
|
89
|
+
the survivors of its cheap filters, every probe failing CLOSED
|
|
90
|
+
(references/phase-0.md, **Build the Shortlist**). Exit 0 is the file
|
|
91
|
+
written, an empty shortlist included; exit 2 is a refusal and the previous
|
|
92
|
+
file stands — never read 2 as empty. **`--require-label ready-for-agent`
|
|
93
|
+
mandatory, no fallback**: never run `candidates.mjs --allow-fallback` by hand.
|
|
130
94
|
|
|
131
95
|
`candidates.mjs`'s exit 3 reaches you as an empty shortlist whose `dropped`
|
|
132
96
|
names every ticket the to-spec filter took, by number and reason — and the
|
|
@@ -142,22 +106,8 @@ phase, or in any later one, asks the maintainer which tickets to take.
|
|
|
142
106
|
`--allow-fallback`, whose second pass is unfiltered and so reaches the
|
|
143
107
|
`ready-for-human` and untriaged work you have no channel for. A partial
|
|
144
108
|
drop, survivors and specs together, is named the same way.
|
|
145
|
-
2. **Refresh is the tick's from here on
|
|
146
|
-
|
|
147
|
-
bound, when the file is missing or empty, when an `excluded` premise has
|
|
148
|
-
lifted, and when a branch-named `behind-pr` exclusion whose PR is not yet
|
|
149
|
-
MERGED or CLOSED, or a `behind-issue` exclusion whose issue is not yet
|
|
150
|
-
closed, names a ticket the file still carries, and prints
|
|
151
|
-
`REFRESHED shortlist: <n> entries; <k> lifted`. A head whose Pull ended
|
|
152
|
-
this run is not counted, unless on a lifted exclusion. Phase 0 runs it by hand
|
|
153
|
-
once because a dead run's shortlist is stale by definition.
|
|
154
|
-
None of those triggers runs while `HOLD (in-flight <n>/<B>)` stands.
|
|
155
|
-
|
|
156
|
-
**No multi-select, no maintainer tick.** Selection and ordering are batched —
|
|
157
|
-
one scan, oldest first; admission never is. The invariant this replaces — one
|
|
158
|
-
human decision per batch of admissions, zero unilateral grabs — is retired
|
|
159
|
-
is retired: zero human decisions per admission, and every non-admission is a
|
|
160
|
-
label or a premise the maintainer can read.
|
|
109
|
+
2. **Refresh is the tick's from here on**: it prints `REFRESHED shortlist: <n>
|
|
110
|
+
entries; <k> lifted` (references/tick-rows.md, **Refresh is the tick's**).
|
|
161
111
|
|
|
162
112
|
## The wake table
|
|
163
113
|
|
|
@@ -180,13 +130,8 @@ evidence cannot settle**.
|
|
|
180
130
|
notification, a label, a CI run reaching a terminal state, a `CONFLICT` line, a
|
|
181
131
|
heartbeat, the maintainer's drain — is one `fleet-event.mjs` call, which makes
|
|
182
132
|
the wake's writes and ends in one `fleet-tick.mjs` invocation, and you do what
|
|
183
|
-
it prints. There are no hand-run
|
|
184
|
-
|
|
185
|
-
`.fleet/ledger.md` and nothing else of yours: a wake you have not written down is
|
|
186
|
-
one it cannot see, and it names the same dispatch again. Every record goes
|
|
187
|
-
through `ledger.mjs` (references/ledger.md), which `fleet-event.mjs` drives — never a
|
|
188
|
-
hand edit. What each kind writes is in the header comment of `fleet-event.mjs`.
|
|
189
|
-
See references/ci-and-staleness.md.
|
|
133
|
+
it prints. There are no hand-run refill or dispatch edges left, and no hand
|
|
134
|
+
edit of the ledger. See references/ci-and-staleness.md.
|
|
190
135
|
|
|
191
136
|
Run each as `~/.fleet/bin/fleet-run fleet-event.mjs <kind> <args>`, from the main checkout:
|
|
192
137
|
|
|
@@ -231,10 +176,7 @@ owes beyond its call:
|
|
|
231
176
|
Read the digest as **Reading a review result** says.
|
|
232
177
|
- **Monitor: `ready-to-merge` appears** → `fleet-event.mjs label <pr>`, and
|
|
233
178
|
on `DISPATCH merge-bot` dispatch the next `merge-bot-<n>` (**The merge bot**).
|
|
234
|
-
|
|
235
|
-
for that finisher's report: its rebase is server-side,
|
|
236
|
-
`gh pr update-branch --rebase`, so nothing on the finisher's worktree has to
|
|
237
|
-
happen first. **You own the watcher, not the bot.** Arm one yourself,
|
|
179
|
+
**You own the watcher, not the bot.** Arm one yourself,
|
|
238
180
|
`persistent: true`, seeded before the loop so handled PRs do not re-fire.
|
|
239
181
|
**One rebase per PR, at merge time.** Any behind-count you hand a bot is
|
|
240
182
|
expired on arrival; say so. See references/ci-and-staleness.md.
|
|
@@ -247,36 +189,31 @@ owes beyond its call:
|
|
|
247
189
|
pass. Read `worktreesRemoved` alongside `reaped`: a pass that
|
|
248
190
|
reaps branches and removes no worktrees is a finding, not a quiet success.
|
|
249
191
|
Update the reaped tickets' ledger rows in the same step.
|
|
250
|
-
**Do not invoke `commit-commands:clean_gone
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
a row without a terminal state means someone may still be in that worktree, and
|
|
254
|
-
the dirty check does not see a member that committed but has not pushed.
|
|
192
|
+
**Do not invoke `commit-commands:clean_gone`**: do the reap yourself. **Never
|
|
193
|
+
reap a branch a live member is on** — cross-check `.fleet/ledger.md` first
|
|
194
|
+
for a row without a terminal state.
|
|
255
195
|
See references/reaping.md.
|
|
256
|
-
- **The run ends, or the maintainer says drain** →
|
|
257
|
-
|
|
258
|
-
holds the implementer row at `HOLD (draining)` from then on — for a
|
|
259
|
-
replacement controller too — and keeps the review, fix-applier and merge rows
|
|
260
|
-
firing until the open PRs merge. The drain starts the end of the run; it does
|
|
261
|
-
not end it. **End of run**, in this order:
|
|
196
|
+
- **The run ends, or the maintainer says drain** → **End of run**, in this
|
|
197
|
+
order; the drain starts the end of the run, it does not end it:
|
|
262
198
|
1. `~/.fleet/bin/fleet-run fleet-event.mjs drain --reason "<reason>"` — runs
|
|
263
|
-
`closeout.mjs begin --reason "<reason>"` (references/reaping.md), then the
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
199
|
+
`closeout.mjs begin --reason "<reason>"` (references/reaping.md), then the
|
|
200
|
+
tick, which holds the implementer row at `HOLD (draining)` from then on. A
|
|
201
|
+
claim it refuses prints `NEED hand-release #<N> <blocker>`: that refusal is
|
|
202
|
+
the finding, so run `worktree-audit.sh` and decide by hand. `begin`
|
|
203
|
+
selects a claim with no PR and a live or `bailed` implementer token on
|
|
204
|
+
every run, so a claim you keep leaves `begin` at exit 4 on every run:
|
|
267
205
|
release the rest by hand and run `begin` again.
|
|
268
206
|
2. Act on the review, fix-applier and merge rows until the tick prints
|
|
269
207
|
`ACTION: run over → fleet-heartbeat.mjs --stop "<reason>"`, which it does
|
|
270
208
|
once the run is draining with no open PR and no live implementer.
|
|
271
|
-
3. `~/.fleet/bin/fleet-run closeout.mjs finish --reason "<reason>"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
fails is explained in references/close-out.md. Exit 0 is the run closed.
|
|
209
|
+
3. `~/.fleet/bin/fleet-run closeout.mjs finish --reason "<reason>"`, which
|
|
210
|
+
then records `fleet-heartbeat.mjs --stop` last, because every reader takes
|
|
211
|
+
a recorded stop as the end of the run. A `NEED tier-outcomes append
|
|
212
|
+
<pr>` is a PR the ledger marks reviewed with no verdict row: append it with
|
|
213
|
+
the verdict you rule (**Guard: accumulate per PR**) and run `finish` again.
|
|
214
|
+
A reviewed PR that closes no issue takes no row and is never a NEED.
|
|
215
|
+
A step it fails is explained in references/close-out.md. Exit 0 is the run
|
|
216
|
+
closed.
|
|
280
217
|
Drain only when a documented trigger fired, and name the trigger in `--reason`.
|
|
281
218
|
**Undrain** only when the maintainer asks, never a drain that budget or
|
|
282
219
|
context exhaustion caused (references/close-out.md).
|
|
@@ -286,7 +223,7 @@ Write each state change through `ledger.mjs` before you act on it, never by hand
|
|
|
286
223
|
|
|
287
224
|
Compaction parks every in-flight member and nothing reports it, so on resume take the registry census first (**Member unaccounted**, in **Failure handling**) before you trust a live token.
|
|
288
225
|
|
|
289
|
-
|
|
226
|
+
`ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row; re-typing the row with `row` drops the tokens you do not restate. Write member tokens and `review=` tokens only through `ledger.mjs`, never by hand.
|
|
290
227
|
|
|
291
228
|
Never rotate the ledger mid-run: it is phase 0's step alone.
|
|
292
229
|
|
|
@@ -296,11 +233,11 @@ Record each ruling with `ledger.mjs ruled <pr> <decision>`, one line with its re
|
|
|
296
233
|
|
|
297
234
|
### Dispatching the members a wake names
|
|
298
235
|
|
|
299
|
-
- **The reviewer.** **Dispatch the review, never wait on it, and act when its result lands. That is the default path.** `DISPATCH review PR#<M>`: a `task` member named `review-pr-<pr#>`, agent `fleet-review-runner`, its prompt the five args `pr`, `branch`, `worktree`, `testCmd` and `scratch`. Record `review=member:review-pr-<pr#>` on the PR's row before the call
|
|
236
|
+
- **The reviewer.** **Dispatch the review, never wait on it, and act when its result lands. That is the default path.** `DISPATCH review PR#<M>`: a `task` member named `review-pr-<pr#>`, agent `fleet-review-runner`, its prompt the five args `pr`, `branch`, `worktree`, `testCmd` and `scratch`. Record `review=member:review-pr-<pr#>` on the PR's row before the call, with `ledger.mjs review <pr> <token>`: the tick counts in-flight reviews off the ledger and nowhere else. `scratch` is the scratch root itself, never a `pr<N>` partition (`references/reviewers.md`). The runner writes the result file and reports its path and `reviewed=` token.
|
|
300
237
|
A row with `review=` and no `reviewed=` is a review in flight, and the tick counts it as a live review unit unless gh reports its PR MERGED or CLOSED.
|
|
301
|
-
- **The fix-applier.** `DISPATCH fix-pr PR#<M>` on a `reviewed=` count that survived or unverified findings, with no fix-applier live: name `fix-pr-<pr#>` (the next suffix if that name is on record), never the PR's implementer; render with `dispatch-prompt.mjs render fix-applier --kind review --member fix-pr-<pr#> …`, then record `ledger.mjs dispatch <pr#> fix-pr-<pr#>` before the call. Its brief carries the PR number, the worktree abs path, the PR's branch, the same `testCmd` you passed the review, and the **path** `<scratch>/pr<pr>/<run>/review.json`, `<run>` off the PR's latest `reviewed=` — never the findings. Every `<scratch>` in that brief is the absolute path `scratch-root.mjs` printed. Its rulings are the fix-applier body's (`agents/fleet-fix-applier.agent.md`); you hold none
|
|
302
|
-
- **The conflict hold.** `DISPATCH fix-pr PR#<M>` on a conflict hold: the row carries an unresolved `conflict-hold:#<M>`, the merge bot's or one you recorded off the tick's `CONFLICT` line. It is the same slot with a different job, and no review file to hand over. Name it `fix-pr-<M>`, or the next suffix (`-b`, `-c` …) when that name is on record; render with `dispatch-prompt.mjs render fix-applier --kind conflict --member <name> …`; dispatch with the `agent` `ledger.mjs dispatch` printed, in the PR's existing worktree. Read the definition off that output (`fleet-fix-applier` whatever the row's `tier=`), never recall it. Settle `applied:<head>`, or `no-op` if no conflict was left, which lifts the merge hold; a dispatched-but-live one does not, and `failed`/`killed` leave the PR fix-due for a replacement.
|
|
303
|
-
- **Then dispatch a red-check fixer** — on a `check`-job `failure` bound to the PR's current head that is not **solely** staleness
|
|
238
|
+
- **The fix-applier.** `DISPATCH fix-pr PR#<M>` on a `reviewed=` count that survived or unverified findings, with no fix-applier live: name `fix-pr-<pr#>` (the next suffix if that name is on record), never the PR's implementer; render with `dispatch-prompt.mjs render fix-applier --kind review --member fix-pr-<pr#> …`, then record `ledger.mjs dispatch <pr#> fix-pr-<pr#>` before the call. Its brief carries the PR number, the worktree abs path, the PR's branch, the same `testCmd` you passed the review, and the **path** `<scratch>/pr<pr>/<run>/review.json`, `<run>` off the PR's latest `reviewed=` — never the findings. Every `<scratch>` in that brief is the absolute path `scratch-root.mjs` printed. Its rulings are the fix-applier body's (`agents/fleet-fix-applier.agent.md`); you hold none, and a refuter report that surfaces to you is its to retrieve, never yours to scan or pass on. On a PR carrying `ready-to-merge`, its push moves the head after the label: a fresh finisher, per **Failure handling**.
|
|
239
|
+
- **The conflict hold.** `DISPATCH fix-pr PR#<M>` on a conflict hold: the row carries an unresolved `conflict-hold:#<M>`, the merge bot's or one you recorded off the tick's `CONFLICT` line. It is the same slot with a different job, and no review file to hand over. Name it `fix-pr-<M>`, or the next suffix (`-b`, `-c` …) when that name is on record; render with `dispatch-prompt.mjs render fix-applier --kind conflict --member <name> …`; dispatch with the `agent` `ledger.mjs dispatch` printed, in the PR's existing worktree. Read the definition off that output (`fleet-fix-applier` whatever the row's `tier=`), never recall it. Settle `applied:<head>`, or `no-op` if no conflict was left, which lifts the merge hold; a dispatched-but-live one does not, and `failed`/`killed` leave the PR fix-due for a replacement. It answers the hold and nothing else: review findings the row also carries get a review `DISPATCH fix-pr PR#<M>` once it lands.
|
|
240
|
+
- **Then dispatch a red-check fixer** — on a `check`-job `failure` bound to the PR's current head that is not **solely** staleness (the five-condition note in `agents/fleet-fix-applier.agent.md`). Name `fix-pr-<M>`, or the next free suffix; render with `dispatch-prompt.mjs render fix-applier --kind red-check --member <name> …`, record with `ledger.mjs dispatch <pr#> <name>` before the call, and dispatch with the printed `agent` in the PR's existing worktree. Never while a review or a review fix-applier on the PR is live or still owes its report — a dispositions mismatch awaiting its retry and a conflict hold awaiting its fix-applier included; a red before any review still gets its review first. The tick prints no `DISPATCH` for it: reconcile it yourself, always after the review. The finisher gate applies to it as to a fix-applier. Settle it `applied:<head>` once a fix is pushed; `no-op` only when the red is not its to fix — a flake that passes on rerun, or staleness — with nothing pushed, never for "could not fix"; `failed` when it cannot reach green (**Failure handling**). Its three dispatch edges are under **The finisher gate**; their rationale is in `references/member-lifecycle.md` (The red-check fixer's three edges).
|
|
304
241
|
- **The merge bot.** Named `merge-bot-<n>`; `ledger.mjs dispatch merge-bot` numbers it. Never two at once. Render `--member merge-bot-<n> --n <n> --scratch <scratch>`. Dispatch with the printed agent, omit `model`, and dispatch on the first `ready-to-merge` label without waiting for the finisher report.
|
|
305
242
|
- **The finisher.** Its render and receives are in `references/finisher.md`.
|
|
306
243
|
|
|
@@ -312,8 +249,7 @@ A row with `review=` and no `reviewed=` is a review in flight, and the tick coun
|
|
|
312
249
|
|
|
313
250
|
**Name every member.**
|
|
314
251
|
Names follow the unit of work: `impl-<issue#>`, `fix-pr-<pr#>`,
|
|
315
|
-
`review-pr-<pr#>`, `finisher-pr-<pr#>`, `merge-bot-<n>`.
|
|
316
|
-
references/member-lifecycle.md.
|
|
252
|
+
`review-pr-<pr#>`, `finisher-pr-<pr#>`, `merge-bot-<n>`.
|
|
317
253
|
|
|
318
254
|
**Fresh context per member.** One member, one unit of work, gone. Refill =
|
|
319
255
|
**new** member, **new** name. See references/member-lifecycle.md.
|
|
@@ -321,13 +257,10 @@ references/member-lifecycle.md.
|
|
|
321
257
|
**The scratch root is computed, never invented and never recorded.** You run
|
|
322
258
|
`~/.fleet/bin/fleet-run scratch-root.mjs`; a non-zero exit is an environment to
|
|
323
259
|
fix, not a cue to pick another root.
|
|
324
|
-
`<scratch>` in this file is the path it prints, resolved before you send
|
|
325
|
-
brief that carries one carries the absolute path
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
`<scratch>/pr<N>/…`, and a review runner's `scratch` argument, which is the
|
|
329
|
-
root itself. A member whose prompt names no path asks you by name and never
|
|
330
|
-
makes one up. See references/isolation.md.
|
|
260
|
+
`<scratch>` in this file is the path it prints, resolved before you send
|
|
261
|
+
anything: every brief that carries one carries the absolute path. A member
|
|
262
|
+
whose prompt names no path asks you by name and never makes one up. See
|
|
263
|
+
references/isolation.md.
|
|
331
264
|
|
|
332
265
|
### Reading a review result
|
|
333
266
|
|
|
@@ -337,74 +270,23 @@ is the whole of your read, and it is what you record: `reviewed=<head>:<survived
|
|
|
337
270
|
off `head`, `counts` and the run root's name — the runner's `ledger` field — on
|
|
338
271
|
the PR's row, with `ledger.mjs review <pr>`. Every later path to the review is built from that token, before a
|
|
339
272
|
restart or after one: `<scratch>/pr<pr>/<run>/review.json`, `<run>` off the PR's
|
|
340
|
-
latest `reviewed=`. Findings never enter your context
|
|
341
|
-
a result runs 26-61 KB, 7-15k tokens that used to land in yours and then again,
|
|
342
|
-
up to ~10k characters, in every fix-applier prompt you wrote. The fix-applier
|
|
273
|
+
latest `reviewed=`. Findings never enter your context. The fix-applier
|
|
343
274
|
reads every one off the file and makes every ruling on them
|
|
344
|
-
(`review-and-fix.md`'s **The review result file**)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
which records only how much a finding would matter if true. `resume` is
|
|
360
|
-
non-null exactly when a specialist or a refuter pair crashed again after the
|
|
361
|
-
review's own in-run retry, and **resume beats handing a crash-heavy review
|
|
362
|
-
on**: a deferred crash is a finding nobody ever looked at.
|
|
363
|
-
No replay exists for a review that ran in a runner's own kernel — `resume` is reported, not acted on, and the fix-applier defers what it names.
|
|
364
|
-
`refuted` comes back deliberately as well — a refutation is itself a claim, and
|
|
365
|
-
one has been reversed on new evidence. The fix-applier may reverse one; it
|
|
366
|
-
records each reversal as a `refuted` entry in its disposition record, the
|
|
367
|
-
evidence in `reason`, and you copy those entries to the ledger's `ruled` line by
|
|
368
|
-
running `fleet-event.mjs fix-report`, whose `settle --from-report` records each
|
|
369
|
-
refutation the report defers.
|
|
370
|
-
|
|
371
|
-
**`dimensionsRun` is the dispatch; `dimensionsUnrun` is what names a gap.** A
|
|
372
|
-
specialist that dies contributes zero findings while its key stays in
|
|
373
|
-
`dimensionsRun`. So read the two together. A key
|
|
374
|
-
in `dimensionsRun` and NOT in `dimensionsUnrun` returned a review; every
|
|
375
|
-
`dimensionsUnrun` entry is `{dimension, reason}` naming a specialist that
|
|
376
|
-
returned nothing, its `reason` the crash.
|
|
377
|
-
The review already re-dispatched each crashed specialist once, so what
|
|
378
|
-
`dimensionsUnrun` still names is residue: the fix-applier names it unrun in its
|
|
379
|
-
report and re-runs nothing — an absence of findings is not coverage.
|
|
380
|
-
|
|
381
|
-
**`suiteVerdict` is the review's one judgement on its shared test run:
|
|
382
|
-
`{usable, reason}`, always present.** The review runs the test command once,
|
|
383
|
-
and every specialist reads that run instead of running its own. `{usable: true,
|
|
384
|
-
reason: null}` says the run executed real tests and its exit status is
|
|
385
|
-
explained by its counts; usable is not green, so a usable run can have failing
|
|
386
|
-
tests. `usable: false` carries `reason`, including a run that never started
|
|
387
|
-
(install skipped, no suite slot). The verdict says nothing about whether a
|
|
388
|
-
dimension reviewed: a specialist that returned reviewed, its findings verified
|
|
389
|
-
as usual, whatever the suite verdict. With `usable: false` the
|
|
390
|
-
fix-applier names `shared test run not usable: <reason>` in its final verdict
|
|
391
|
-
and does nothing more — its own `<testCmd>` run and CI cover the suite.
|
|
392
|
-
**Exit 75 is not a test result.** In a review it makes
|
|
393
|
-
`suiteVerdict` not usable, `suiteVerdict.reason` beginning "no suite slot within
|
|
394
|
-
<n> s — the host is at its suite bound; not a test failure", with no retry
|
|
395
|
-
inside the run: read that `reason` off the digest and re-review it
|
|
396
|
-
under the next runner letter (`review-pr-<pr#>-b` after a first runner), as for
|
|
397
|
-
a runner that reported `failed`.
|
|
398
|
-
|
|
399
|
-
**`cwdAudit` is the specialist's own inherited-checkout audit, one entry per
|
|
400
|
-
dispatched dimension: `{dimension, state, line}`, `state` one of**
|
|
401
|
-
`clean`/`dirty`/`unrepo`/`missing`. The prompt asks every specialist to run
|
|
402
|
-
`git status --porcelain -uall` against the directory it inherited before
|
|
403
|
-
returning and fold the result into a `CWD-AUDIT:` line — a convention, not
|
|
404
|
-
schema, so a specialist that never emits it or misspells it still validates
|
|
405
|
-
clean. This field is the runtime backstop that reads for the line regardless:
|
|
406
|
-
`missing` is exactly as loud as `dirty`, and both name a checkout you should
|
|
407
|
-
treat as a live scratch write until you have checked it.
|
|
275
|
+
(`review-and-fix.md`'s **The review result file**); never rule on one
|
|
276
|
+
yourself. Each digest field in full: See references/reviewers.md. What the
|
|
277
|
+
digest asks of you:
|
|
278
|
+
|
|
279
|
+
- **`testEnvironment` says what the review's shared test run is evidence about**: read it so a degraded review never passes for a clean one.
|
|
280
|
+
- **`resume`** non-null: resume beats handing a crash-heavy review on, except that no replay exists for a review that ran in a runner's own kernel — `resume` is reported, not acted on, and the fix-applier defers what it names.
|
|
281
|
+
- **`unverified`** is *not* "checked and cleared", and **`refuted`** may be reversed: copy the fix-applier's reversals to the ledger's `ruled` line by running `fleet-event.mjs fix-report`.
|
|
282
|
+
- **`suiteVerdict`** — **Exit 75 is not a test result.** In a review it makes
|
|
283
|
+
`suiteVerdict` not usable, `suiteVerdict.reason` beginning "no suite slot within
|
|
284
|
+
<n> s — the host is at its suite bound; not a test failure", with no retry
|
|
285
|
+
inside the run: read that `reason` off the digest and re-review it
|
|
286
|
+
under the next runner letter (`review-pr-<pr#>-b` after a first runner), as for
|
|
287
|
+
a runner that reported `failed`.
|
|
288
|
+
- **`cwdAudit`**: `missing` is exactly as loud as `dirty`, and both name a
|
|
289
|
+
checkout you should treat as a live scratch write until you have checked it.
|
|
408
290
|
|
|
409
291
|
**Know the trim before you rely on it — it is wider than `docsOnly` suggests.** See references/reviewers.md.
|
|
410
292
|
|
|
@@ -421,25 +303,12 @@ its one member-outcomes row decides), and BLANK with a WARNING naming why
|
|
|
421
303
|
when neither settles
|
|
422
304
|
it; never type a tier in over that. That append is the whole duty. See references/pull.md.
|
|
423
305
|
|
|
424
|
-
The row's last four fields are the ticket's difficulty, and they are what lets a
|
|
425
|
-
tier comparison condition on the thing that swamps it. `sizing` is the
|
|
426
|
-
**member's** own `light`/`heavy` verdict from its dispatched `sizing-a-ticket`
|
|
427
|
-
run, which `append` reads itself off the member's report file
|
|
428
|
-
(`<scratch>/reports/impl-<ticket>[-x].json`, its `sizing.verdict`, the highest
|
|
429
|
-
retry letter's when there are several), so you pass no `--sizing`; the member
|
|
430
|
-
also puts it in the PR body's `Sizing:` line, with the signal it turned on;
|
|
431
|
-
`profile`, `loc` and `files` all come from `diff-stats.mjs` over the merged
|
|
432
|
-
diff.
|
|
433
|
-
|
|
434
306
|
**A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
|
|
435
307
|
|
|
436
308
|
### Nothing to do: arm the beat
|
|
437
309
|
|
|
438
310
|
- **Nothing to do right now** → **do not end your turn.** Arm the heartbeat
|
|
439
|
-
below
|
|
440
|
-
external will wake you, and the level-check you are one command away from
|
|
441
|
-
never runs. Listed on its own because it is the only wake with no trigger —
|
|
442
|
-
every other row above arrives; this one is the absence of one.
|
|
311
|
+
below: nothing external will wake you. See references/ci-and-staleness.md.
|
|
443
312
|
|
|
444
313
|
**Beat when there is nothing to do, and never end your turn on a drained queue.** See references/ci-and-staleness.md.
|
|
445
314
|
|
|
@@ -453,18 +322,14 @@ It blocks, then prints one line. Which line it is, is the whole protocol:
|
|
|
453
322
|
- `… interval elapsed (quiet=<n>) → run fleet-event.mjs heartbeat` — the level-check
|
|
454
323
|
is due. Run that command and act on what the tick it ends in prints.
|
|
455
324
|
Then arm the beat again.
|
|
456
|
-
- `… Ns of Ms remain → re-issue this command now, do not end your turn` —
|
|
457
|
-
|
|
458
|
-
why the CI gate holds its wait in one `eval` cell polling `gh run view`).
|
|
459
|
-
Issue it again. That is still one blocking call per turn, never an idle turn.
|
|
325
|
+
- `… Ns of Ms remain → re-issue this command now, do not end your turn` —
|
|
326
|
+
issue it again: still one blocking call per turn, never an idle turn.
|
|
460
327
|
|
|
461
328
|
**Do not raise `--ceiling` past 30 minutes.** See references/ci-and-staleness.md.
|
|
462
329
|
|
|
463
|
-
**The beat does not stop on idleness
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
context does — not because there was briefly nothing to do.
|
|
467
|
-
Zeroes on the cockpit are not yet proof the pipeline is idle.
|
|
330
|
+
**The beat does not stop on idleness**: the run ends when you drain it, when
|
|
331
|
+
the budget goes, or when this context does, and zeroes on the cockpit are not
|
|
332
|
+
proof the pipeline is idle. See references/ci-and-staleness.md.
|
|
468
333
|
|
|
469
334
|
**When you stop on purpose, say why — one command, and it is the last thing
|
|
470
335
|
you owe the next run.** See references/ci-and-staleness.md.
|
|
@@ -476,9 +341,7 @@ you owe the next run.** See references/ci-and-staleness.md.
|
|
|
476
341
|
It holds nothing and returns at once. Use it when the budget goes, or when
|
|
477
342
|
you can see the context ceiling coming. A drained run records it as the last
|
|
478
343
|
step of **End of run** (above), never at the drain itself.
|
|
479
|
-
Skipping it is not fatal
|
|
480
|
-
reports that the beat stopped *without a recorded reason*, which is all it can
|
|
481
|
-
honestly say about a death that named nothing.
|
|
344
|
+
Skipping it is not fatal: the next reader reports a stop *without a recorded reason*.
|
|
482
345
|
|
|
483
346
|
**A stall line names whose stall it is.** Its opening words come off the controller
|
|
484
347
|
record:
|
|
@@ -523,24 +386,11 @@ For the merge queue, see references/pull.md.
|
|
|
523
386
|
govern — refresh, and never Pull a ticket the shortlist does not hold.
|
|
524
387
|
See references/pull.md.
|
|
525
388
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
and the drain marker, all off `.fleet/ledger.md`; the unclaimed heads and the
|
|
532
|
-
supply come off `.fleet/shortlist.json`, which it refreshes itself when the
|
|
533
|
-
unclaimed count is below the In-flight bound, when the file is missing or
|
|
534
|
-
empty, when an Exclusion's premise has lifted, or when a branch-named
|
|
535
|
-
`behind-pr` or a `behind-issue` Exclusion still not lifted names a ticket the
|
|
536
|
-
file carries — never while the bound holds; the open PRs come off `gh`. A
|
|
537
|
-
head whose Pull ended this run is not counted, unless on a lifted exclusion. A
|
|
538
|
-
ledger token it
|
|
539
|
-
cannot read, it refuses rather than guessing — a member counted live or gone on
|
|
540
|
-
a guess is the over- or under-dispatch it exists to prevent — so correct that
|
|
541
|
-
row with `ledger.mjs row` and tick again. The In-flight bound defaults to 5
|
|
542
|
-
and is fixed for the run: pass `--in-flight` on every tick when `$ARGUMENTS`
|
|
543
|
-
set it, and never otherwise.
|
|
389
|
+
The tick reads the run itself, off `.fleet/ledger.md`, `.fleet/shortlist.json`
|
|
390
|
+
and `gh`; nobody states a count (references/tick-rows.md, **What the tick
|
|
391
|
+
reads**). A ledger token it cannot read, it refuses rather than guessing:
|
|
392
|
+
correct that row with `ledger.mjs row` and tick again. The In-flight bound is fixed for the run:
|
|
393
|
+
pass `--in-flight` on every tick when `$ARGUMENTS` set it, and never otherwise.
|
|
544
394
|
|
|
545
395
|
It prints `actual/target` and a named ACTION per role. Act on each line as it reads:
|
|
546
396
|
|
|
@@ -574,19 +424,15 @@ A Pull is done when `pull.mjs next` exits 0 with its `DISPATCH` line, which the
|
|
|
574
424
|
Serial, main checkout, one Pull at a time. Never parallel, never inside a member —
|
|
575
425
|
concurrent `worktree add` and label writes race.
|
|
576
426
|
|
|
577
|
-
A Pull's entry is `~/.fleet/bin/fleet-run pull.mjs next --head <N>`, run from
|
|
578
|
-
main checkout (without `--head`, the next shortlist entry). It
|
|
579
|
-
|
|
580
|
-
<qid
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
after a router refusal `next --head <N>` starts the ticket over; exit 3 is a
|
|
587
|
-
usage mistake, or an answer the question does not take, and records nothing.
|
|
588
|
-
The record is `<scratch>/impl-<N>/pull.json`. See references/pull.md for what
|
|
589
|
-
each step does, and why.
|
|
427
|
+
A Pull's entry is `~/.fleet/bin/fleet-run pull.mjs next --head <N>`, run from
|
|
428
|
+
the main checkout (without `--head`, the next shortlist entry). It stops at
|
|
429
|
+
each judgement with a `QUESTION <qid>:` block and exit 10; `pull.mjs answer <N>
|
|
430
|
+
<qid> <token> [--path <p>] [--] [<text>…]` records the answer and resumes it.
|
|
431
|
+
Exit 2 names what could not be looked at: `next --head <N>` again retries a
|
|
432
|
+
failed step, the same `answer` again one that failed while applying an answer,
|
|
433
|
+
and after a router refusal `next --head <N>` starts the ticket over. Every
|
|
434
|
+
other exit is in **The exit table**; the record is
|
|
435
|
+
`<scratch>/impl-<N>/pull.json`. See references/pull.md for what each step does, and why.
|
|
590
436
|
|
|
591
437
|
### The tick's PULL rows
|
|
592
438
|
|
|
@@ -693,12 +539,12 @@ A dispatch is done when its `task` call is made, in the background, with the `ag
|
|
|
693
539
|
|
|
694
540
|
- **One claim, one dispatch, no queue.** Make one claim and then one dispatch of a new member under a name no member has held; nothing holds a queue. Claim what you dispatch and dispatch at once. Record `ledger.mjs dispatch` before the call.
|
|
695
541
|
- **The implementer.** Dispatch with the printed `agent`, omit `model`, keep `name: impl-<N>`. **No per-call tier or effort anywhere on this path**: tier stays the agent definition's own frontmatter, whichever `fleet-implementer-<cell>` definition the Pull names, and a member's Resolved tier stays readable from its transcript. The prompt carries only what varies, and the Pull's own read of the ticket is pasted, never a request to re-fetch. A member settling with nothing is not a bail. Confirm death first, exactly as the Member-killed row does (Failure handling, below) — its mtime discriminator, never silence alone. Only the member's own bail report counts. A confirmed-dead member recovers exactly as the Member-killed row says — new member, new name, the SAME ticket — never a demotion.
|
|
696
|
-
- **The tier check.** **Run the check right after the dispatch, and never reinstall the plugin or change `modelRoles` while any member is unchecked.** `tick --apply`, or `tier-check.mjs --batch <path>`, runs it. Dispatch nothing on top of an unchecked or mismatched member. **The tick holds the next Pull until that verdict exists.** Clear a mismatch by fixing the definition or the dispatch and dispatching the replacement at the right tier. A mismatch on a ticket whose issue is already CLOSED holds nothing
|
|
542
|
+
- **The tier check.** **Run the check right after the dispatch, and never reinstall the plugin or change `modelRoles` while any member is unchecked.** `tick --apply`, or `tier-check.mjs --batch <path>`, runs it. Dispatch nothing on top of an unchecked or mismatched member. **The tick holds the next Pull until that verdict exists.** Clear a mismatch by fixing the definition or the dispatch and dispatching the replacement at the right tier. A mismatch on a ticket whose issue is already CLOSED holds nothing. **After `/resume`, a carried implementer can hold the whole tick** with `HOLD (tier unchecked impl-<N>)` until it settles; reconcile its carried token meanwhile. See references/member-lifecycle.md.
|
|
697
543
|
- **The exploration rule.** **Every 5th Pull by ledger count goes at an exploration cell.** See references/pull.md. The printed CELL line is the whole routing decision: never re-derive `CELL`, never label a cell by hand. **Do not label it anywhere — the dispatch record already carries it.** **The readout counts DELIBERATE comparisons.** See references/pull.md.
|
|
698
544
|
|
|
699
545
|
Pointers: the `/resume` rules are in `references/phase-0.md`; the tick HOLD tier rules are in `references/tick-rows.md`; shell traps are in `references/shell-traps.md`; the fix-applier's per-PR rulings are in `agents/fleet-fix-applier.agent.md`. Rationale for these rules: `references/member-lifecycle.md`.
|
|
700
546
|
|
|
701
|
-
**The test command every gate runs.** Pass the same command to the review, to the fix-applier, and to the finisher — whose duty-2 mutation gate runs it too — so every gate runs one command
|
|
547
|
+
**The test command every gate runs.** Pass the same command to the review, to the fix-applier, and to the finisher — whose duty-2 mutation gate runs it too — so every gate runs one command: the fix-applier and the finisher get the wrapped string as `<testCmd>` — `~/.fleet/bin/fleet-run suite-slot.mjs '<command>'`, each `'` inside the command written `'\''` — and the review's args the bare command. Never substitute `<testCmd>` with nothing.
|
|
702
548
|
|
|
703
549
|
**Where `<branch>` comes from:** the PR's head branch — `gh pr view <N> --json headRefName -q .headRefName`, or the same branch you cut the worktree from. See references/reviewers.md.
|
|
704
550
|
|
|
@@ -710,93 +556,55 @@ Dispatch a finisher only when every test here passes at once — the review retu
|
|
|
710
556
|
|
|
711
557
|
- **Monitor: CI run completes** → `fleet-event.mjs ci <pr>`, which binds the run
|
|
712
558
|
(`ci-state.mjs --pr <N>`) and **records it as
|
|
713
|
-
`ci=<run-id>:<attempt>:<conclusion>`, not just "CI green"** —
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
conclusion is not stable** (below), because a rerun rewrites the run in
|
|
718
|
-
place. The diff-validating `check` job green with no heavy job (the
|
|
719
|
-
diff-validating suites, not the `rebase-check` currency gate) in `failure`
|
|
720
|
-
→ a finisher CANDIDATE (gated below), a `check`
|
|
559
|
+
`ci=<run-id>:<attempt>:<conclusion>`, not just "CI green"** — the whole key,
|
|
560
|
+
never the conclusion alone, since **a conclusion is not stable** (below). The
|
|
561
|
+
diff-validating `check` job green with no heavy job (not the `rebase-check`
|
|
562
|
+
currency gate) in `failure` → a finisher CANDIDATE (gated below); a `check`
|
|
721
563
|
**failure** that is not solely staleness → the **red-check fixer** (**Then
|
|
722
|
-
dispatch a red-check fixer**, above), behind the same
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
is mid-work on a SHA the member may still move, which its own prompt tells it
|
|
735
|
-
not to hand over. A push is therefore not an exit, and a report that has not
|
|
736
|
-
arrived is asked for by name rather than read as one. Neither shape is caught
|
|
737
|
-
by an empty outbox: on the implementer's firing there is no fix-applier to owe
|
|
738
|
-
anything to, so the outbox is *vacuously* empty, and on a fix-applier's
|
|
739
|
-
mid-work push nothing has been sent since the dispatch, so it is *genuinely*
|
|
740
|
-
empty. **The outbox is the last test, not the only one, and it stands wherever
|
|
741
|
-
a fix-applier exists** — that is exactly where a ruling you still owe it is
|
|
742
|
-
outstanding, so never dispatch off it while you do. Empty your outbox to it
|
|
564
|
+
dispatch a red-check fixer**, above), behind the same gate. Heavy jobs merely
|
|
565
|
+
`skipped` still label. **This edge fires for every CI run the PR produces,
|
|
566
|
+
and the implementer's own firing comes first for every PR** — `ci.yml`
|
|
567
|
+
triggers on `pull_request`, so no review has run at all when it arrives.
|
|
568
|
+
**Green is therefore not the gate: dispatch a finisher only once the PR's
|
|
569
|
+
review has returned AND its fix-applier, if one was dispatched, has sent its
|
|
570
|
+
report — pushed is not reported**: a push before that report is mid-work on a
|
|
571
|
+
SHA the member may still move, and a report that has not arrived is asked for
|
|
572
|
+
by name. An empty outbox catches neither — *vacuously* empty on the
|
|
573
|
+
implementer's firing, *genuinely* empty on a mid-work push. **The outbox is
|
|
574
|
+
the last test, not the only one, and it stands wherever a fix-applier
|
|
575
|
+
exists**, so never dispatch off it while you do owe it a ruling: empty it
|
|
743
576
|
first — including any ruling you have withdrawn or reversed — then dispatch
|
|
744
577
|
(below); a final report is not proof it stopped. A PR that needs nothing fixed
|
|
745
578
|
reaches a finisher through the **fix-applier reports `no-op`** bullet below,
|
|
746
|
-
never by waiting for a second CI event
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
bullets are also the only routes to the red-check fixer there.
|
|
750
|
-
Then run the tick. These edges dispatch it, each the finisher's own: **Monitor: CI run completes** binding a red on a head whose review has returned and whose fix-applier, if any, has reported; **A fix-applier reports `no-op`**, or a SHA you have already bound, when that head's bound conclusion is red; and **A review result lands** with no finding and nothing to file, when that head's bound conclusion is red.
|
|
579
|
+
never by waiting for a second CI event; a red that fired before the review
|
|
580
|
+
reaches the red-check fixer only through that bullet or **A review result
|
|
581
|
+
lands**. Then run the tick. See references/finisher.md.
|
|
751
582
|
- **A fix-applier reports `no-op`, or a SHA you have already bound** → dispatch
|
|
752
583
|
the finisher **now**, against the existing head — when that head's bound
|
|
753
584
|
conclusion is red, the red-check fixer instead (**Then dispatch a red-check
|
|
754
|
-
fixer**, above), never a finisher and never a wait
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
for the CI event waits forever. This stays a common outcome on a clean PR:
|
|
758
|
-
`suggestion` is the band a clean diff produces, and every out-of-scope or
|
|
759
|
-
refuted one is filed as an issue rather than committed. An edge-only label path
|
|
760
|
-
therefore strands exactly the PRs with nothing wrong with them. **Reconcile, do
|
|
761
|
-
not wait for an event** governs here too, not only the implementer row. **This
|
|
585
|
+
fixer**, above), never a finisher and never a wait: the Monitor is edge-keyed
|
|
586
|
+
on `<run-id>:<attempt>:<conclusion>`, so no push means no CI event, ever.
|
|
587
|
+
**Reconcile, do not wait for an event** governs here too. **This
|
|
762
588
|
bullet and the one above are a pair, and on a clean PR this one carries the
|
|
763
589
|
whole route to a finisher**: a `no-op` report satisfies the gate above by
|
|
764
|
-
itself
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
complete for this repo, so the CI-run-completion edge above never fires and
|
|
769
|
-
waiting for it stalls the whole PR — the same silent-stall shape reported
|
|
770
|
-
before this verdict existed. Dispatch the finisher off the reviewer's final
|
|
771
|
-
verdict instead, the moment it lands — and, same as above, never while you still
|
|
772
|
-
owe it a ruling — or hold anything else it has not received, a ruling you have
|
|
773
|
-
withdrawn or reversed included. The finisher's gate is then the
|
|
590
|
+
itself, so nothing further is owed and no further event is coming. Edit either bullet without the other and what strands is exactly the PR that needed no fixes.
|
|
591
|
+
- **`ci-state.mjs --pr <N>` reads `verdict: "no-ci"`** → the CI edge never
|
|
592
|
+
fires: dispatch the finisher off the reviewer's final verdict the moment it
|
|
593
|
+
lands, never while you still owe it a ruling. Its gate is then the
|
|
774
594
|
`--declare-no-ci` declaration, not a `check` job: with it, label off the
|
|
775
595
|
reviewer's verified suite run; **without it, do not label** — report that this
|
|
776
|
-
repo has no CI configured and no declaration, and stop.
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
`--declare-no-ci` declaration is caller-side, and reading it back out of the
|
|
787
|
-
payload verifies nothing**: the flag is echoed into `reasons` and flips the exit
|
|
788
|
-
code, so re-running with it and finding it there confirms only that you passed
|
|
789
|
-
it. Passing it yourself is not a second opinion. Check the two facts it stands
|
|
790
|
-
for instead:
|
|
791
|
-
|
|
792
|
-
1. `ci-state.mjs` **without** the flag still reads `verdict: "no-ci"`. A repo
|
|
793
|
-
whose CI is merely misconfigured — workflow files present under other names,
|
|
794
|
-
or `.github/workflows/` not a directory — exits 2 there rather than reading no-ci,
|
|
795
|
-
so this is the check that separates real absence from misconfiguration, the
|
|
796
|
-
case that must never ship silently.
|
|
596
|
+
repo has no CI configured and no declaration, and stop. See `review-and-fix.md` step 6.
|
|
597
|
+
|
|
598
|
+
Gate on the `check` job, **not** on `ci-state --quiet` exit 0, which a behind
|
|
599
|
+
PR never reaches; the finisher reads per-job state, without `--quiet`.
|
|
600
|
+
|
|
601
|
+
**The `--declare-no-ci` declaration is caller-side, and reading it back out of
|
|
602
|
+
the payload verifies nothing.** Check the two facts it stands for instead:
|
|
603
|
+
|
|
604
|
+
1. `ci-state.mjs` **without** the flag still reads `verdict: "no-ci"`; a merely
|
|
605
|
+
misconfigured CI exits 2 there.
|
|
797
606
|
2. The reviewer's own final verdict reports a green `testCmd` run on the SHA it
|
|
798
|
-
pushed.
|
|
799
|
-
gate this repo actually has.
|
|
607
|
+
pushed.
|
|
800
608
|
|
|
801
609
|
Both → add the label, and say it rests on the reviewer's suite run, not on CI.
|
|
802
610
|
Either missing → halt here, same as a dirty worktree: report `no CI configured,
|
|
@@ -804,39 +612,23 @@ no verified suite run on record` and add no label.
|
|
|
804
612
|
|
|
805
613
|
### The dispatch and its refusals
|
|
806
614
|
|
|
807
|
-
**The fix-applier pushes and exits — it does not hold the CI wait
|
|
808
|
-
persistent Monitor
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
`rebase-check` currency gate; a `skipped` heavy job is behind-count staleness and
|
|
814
|
-
fine — **solely** off that count, which is a condition to establish rather than
|
|
815
|
-
infer, see the five-condition note in the fix-applier body) — or
|
|
816
|
-
`ci-state.mjs` reads `verdict: "no-ci"`, see above — **and once the PR's review
|
|
817
|
-
has returned and its fix-applier, if one was dispatched, has sent its report,
|
|
818
|
-
which the CI-completes edge above states in full** — dispatch a
|
|
615
|
+
**The fix-applier pushes and exits — it does not hold the CI wait**; you own the
|
|
616
|
+
persistent Monitor. When the `check` job is green **and no heavy job is in
|
|
617
|
+
`failure`** (a `skipped` one is fine only when **solely** off the behind-count,
|
|
618
|
+
per the fix-applier body's five-condition note) — or `ci-state.mjs` reads
|
|
619
|
+
`verdict: "no-ci"` — **and once the PR's review has returned and its
|
|
620
|
+
fix-applier, if one was dispatched, has sent its report**, dispatch a
|
|
819
621
|
**finisher** — a fresh small agent, not the fix-applier resumed. Render its brief
|
|
820
622
|
with `dispatch-prompt.mjs render finisher --kind fresh --member finisher-pr-<pr#> …`
|
|
821
623
|
(`--kind repair` once an earlier finisher reported `labelled` without the label landing), record it with
|
|
822
624
|
`ledger.mjs dispatch <pr#> finisher-pr-<pr#>` before the call, then **dispatch
|
|
823
|
-
it with the `agent` that command printed and omit `model` on the call**
|
|
824
|
-
|
|
825
|
-
as the `@smol:low` route, which resolves through the operator's `modelRoles`.
|
|
826
|
-
Its four duties are a checklist — audit the
|
|
827
|
-
worktree, confirm every deferral has a tracker home and re-run the acceptance
|
|
828
|
-
mutation, apply one release label, report — and the
|
|
829
|
-
merge gate downstream still catches whatever it gets wrong, the same argument
|
|
830
|
-
that puts the merge bot on the same tier above. **Never dispatch
|
|
831
|
-
one while you still owe the fix-applier a ruling**: the pinned SHA is only as good
|
|
832
|
-
as the guarantee nothing else is inbound. **The test is your OUTBOX, not the
|
|
625
|
+
it with the `agent` that command printed and omit `model` on the call**. **Never dispatch
|
|
626
|
+
one while you still owe the fix-applier a ruling. The test is your OUTBOX, not the
|
|
833
627
|
member's last message** — anything you have decided that it has not received,
|
|
834
|
-
including a ruling you have since **withdrawn or reversed
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
recorded only in the ledger is undelivered: the member still holds the original
|
|
839
|
-
and will act on it. Relay everything — reversals too — then dispatch.
|
|
628
|
+
including a ruling you have since **withdrawn or reversed**, a withdrawal
|
|
629
|
+
recorded only in the ledger included. **That is the test of THIS condition
|
|
630
|
+
only**: the report condition above is the other. Relay everything — reversals
|
|
631
|
+
too — then dispatch. See references/finisher.md.
|
|
840
632
|
|
|
841
633
|
**That `dispatch` refuses a finisher — exit 2, nothing written — on a PR whose
|
|
842
634
|
latest `reviewed=` counts a survived or unverified finding, unless the
|
|
@@ -882,17 +674,8 @@ finisher again.
|
|
|
882
674
|
|
|
883
675
|
**An unanswered question from the member is an outbox item, and it blocks dispatch with the same weight as a ruling you have already made.** See references/finisher.md.
|
|
884
676
|
|
|
885
|
-
**A final report is not proof the member stopped.**
|
|
886
|
-
|
|
887
|
-
reported "nothing outstanding" resumed on a ledger-only withdrawal and was
|
|
888
|
-
cycling mutants through the worktree when the finisher audited it. Both finishers
|
|
889
|
-
correctly halted. Note what that costs to detect: two reads of the same worktree
|
|
890
|
-
a minute apart showed *different* mutants, so a member's report and any single
|
|
891
|
-
`git status` are each valid only at their instant.
|
|
892
|
-
|
|
893
|
-
**The standing CI facts are in the fix-applier body, not in per-event messages** —
|
|
894
|
-
otherwise you send "your red is staleness, do not rebase" once per member per
|
|
895
|
-
merge. `review-and-fix.md` states them; the body says they apply.
|
|
677
|
+
**A final report is not proof the member stopped.** **The standing CI facts are
|
|
678
|
+
in the fix-applier body, not in per-event messages.**
|
|
896
679
|
|
|
897
680
|
**Once the label is on, take it off before you approve any push.** Write `label-off=<latest finisher attempt>` onto the PR's row first
|
|
898
681
|
(`ledger.mjs token <PR number> label-off=<latest finisher attempt>`, which keeps
|
|
@@ -907,17 +690,14 @@ See references/finisher.md.
|
|
|
907
690
|
|
|
908
691
|
### Own the CI waits
|
|
909
692
|
|
|
910
|
-
**Own the CI waits.** Arm a second persistent Monitor over open PRs'
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
without which the probe 404s and `behind` comes back `null`, never 0, so every
|
|
919
|
-
event reads as unknown-behind. Call it **without `--quiet`** — that flag drops
|
|
920
|
-
`jobs`, `missing` and `dropped`.
|
|
693
|
+
**Own the CI waits.** Arm a second persistent Monitor over open PRs' latest
|
|
694
|
+
runs from the block in references/ci-watcher.md, verbatim, keyed
|
|
695
|
+
`<run-id>:<attempt>:<conclusion>` so each terminal state fires once: one
|
|
696
|
+
`~/.fleet/bin/fleet-run ci-state.mjs --pr <N>` per open PR emits run id,
|
|
697
|
+
attempt, conclusion, behind-count and per-job conclusions. Call it **without
|
|
698
|
+
`--quiet`** — that flag drops `jobs`, `missing` and `dropped`. A `success` on a
|
|
699
|
+
branch 8 behind is not actionable, and `check`-green-with-heavy-skipped is the
|
|
700
|
+
staleness board you must not confuse with a red.
|
|
921
701
|
|
|
922
702
|
A monitor event is a wake-up, never a verdict; members re-query at labelling
|
|
923
703
|
time. See references/ci-and-staleness.md.
|
|
@@ -927,144 +707,12 @@ time. See references/ci-and-staleness.md.
|
|
|
927
707
|
**Every merge invalidates every other open PR, silently.** Behind-count is the
|
|
928
708
|
only honest signal. See references/ci-and-staleness.md.
|
|
929
709
|
|
|
930
|
-
**A probe that cannot read is a transition event, not silence.**
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
level down. The shape is **one outer tick loop with an inner pass over the open
|
|
934
|
-
PRs**, because the two causes live at different levels — the budget is one
|
|
935
|
-
account, the payload is one PR:
|
|
936
|
-
|
|
937
|
-
```sh
|
|
938
|
-
budget_out= hidden_out= list_out= cap_out= blind= # seed the latches: set -u reads each below before tick 1 ever assigns one
|
|
939
|
-
pr_cap=200 # the open-PR read's --limit; gh stops there with exit 0 and no warning
|
|
940
|
-
while :; do # one tick
|
|
941
|
-
rl=$(gh api rate_limit --jq '.resources.core.remaining' 2>/dev/null || echo ERR)
|
|
942
|
-
case "$rl" in ''|*[!0-9]*) rl=ERR;; esac # a non-numeric read is an outage, not a budget
|
|
943
|
-
if [ "$rl" = ERR ] || [ "$rl" -lt 200 ]; then
|
|
944
|
-
if [ -z "$budget_out" ]; then # ONE global latch: a quota outage hits every PR
|
|
945
|
-
echo "WATCHER DEGRADED: core REST budget=$rl — CI polling paused, silence is NOT green"
|
|
946
|
-
budget_out=1
|
|
947
|
-
fi
|
|
948
|
-
else # every RECOVERED waits for the pass gates below
|
|
949
|
-
prs=$(gh pr list --state open --limit "$pr_cap" --json number --jq '.[].number' 2>/dev/null) || prs=ERR
|
|
950
|
-
if [ "$prs" = ERR ]; then
|
|
951
|
-
if [ -z "$list_out" ]; then # global latch: this cause is account-level too
|
|
952
|
-
echo "WATCHER DEGRADED: cannot list open PRs — CI polling paused, silence is NOT green"
|
|
953
|
-
list_out=1
|
|
954
|
-
fi
|
|
955
|
-
else # a list or cap RECOVERED waits for the pass gates too
|
|
956
|
-
count=$(printf '%s\n' "$prs" | grep -c .) || : # grep -c exits 1 on zero matches; set -e must not take it
|
|
957
|
-
if [ "$count" -ge "$pr_cap" ] && [ -z "$cap_out" ]; then # global latch: a full page may have been cut there
|
|
958
|
-
echo "WATCHER DEGRADED: open-PR list hit its --limit $pr_cap — PRs past it are NOT watched, silence on them is NOT green"
|
|
959
|
-
cap_out=1
|
|
960
|
-
fi
|
|
961
|
-
# gh pr list is GraphQL, so $rl still holds. The pass is budgeted at 4 core
|
|
962
|
-
# REST units per listed PR on top of the 200 floor. See references/ci-watcher.md.
|
|
963
|
-
# A shortfall here, or at the Actions-bucket gate below, pauses this tick's pass.
|
|
964
|
-
if [ "$rl" -lt $((200 + 4 * count)) ]; then
|
|
965
|
-
if [ -z "$budget_out" ]; then
|
|
966
|
-
echo "WATCHER DEGRADED: core REST budget=$rl < $((200 + 4 * count)) needed for $count PRs — CI polling paused, silence is NOT green"
|
|
967
|
-
budget_out=1
|
|
968
|
-
fi
|
|
969
|
-
else
|
|
970
|
-
# The workflow and run lookups bill a second core bucket that
|
|
971
|
-
# `rate_limit` does not report. Its count is only in the response
|
|
972
|
-
# headers of a lookup that bills it: one 1-unit read per tick, 4 units
|
|
973
|
-
# per listed PR on the same 200 floor. Recovery for every global latch
|
|
974
|
-
# waits until this gate clears too, so no line says polling resumed
|
|
975
|
-
# while this one still pauses it.
|
|
976
|
-
hrl=$(gh api -i 'repos/{owner}/{repo}/actions/workflows?per_page=1' 2>/dev/null | awk 'tolower($1)=="x-ratelimit-remaining:" {sub(/\r$/, "", $2); print $2; exit}')
|
|
977
|
-
case "$hrl" in ''|*[!0-9]*) hrl=ERR;; esac # a missing header is an outage, not a budget
|
|
978
|
-
if [ "$hrl" = ERR ] || [ "$hrl" -lt $((200 + 4 * count)) ]; then
|
|
979
|
-
if [ -z "$hidden_out" ]; then
|
|
980
|
-
need=; [ "$hrl" = ERR ] || need=" < $((200 + 4 * count)) needed for $count PRs" # an unreadable header has no amount to compare
|
|
981
|
-
echo "WATCHER DEGRADED: unreported Actions core REST bucket=$hrl$need — CI polling paused, silence is NOT green"
|
|
982
|
-
hidden_out=1
|
|
983
|
-
fi
|
|
984
|
-
else
|
|
985
|
-
if [ -n "$list_out" ]; then
|
|
986
|
-
echo "WATCHER RECOVERED: open-PR list readable again — CI polling resumed"
|
|
987
|
-
list_out=
|
|
988
|
-
fi
|
|
989
|
-
if [ -n "$cap_out" ] && [ "$count" -lt "$pr_cap" ]; then
|
|
990
|
-
echo "WATCHER RECOVERED: open-PR list under its --limit $pr_cap — every open PR watched again"
|
|
991
|
-
cap_out=
|
|
992
|
-
fi
|
|
993
|
-
if [ -n "$budget_out" ]; then
|
|
994
|
-
echo "WATCHER RECOVERED: core budget=$rl — CI polling resumed"
|
|
995
|
-
budget_out=
|
|
996
|
-
fi
|
|
997
|
-
if [ -n "$hidden_out" ]; then
|
|
998
|
-
echo "WATCHER RECOVERED: unreported Actions core REST bucket=$hrl — CI polling resumed"
|
|
999
|
-
hidden_out=
|
|
1000
|
-
fi
|
|
1001
|
-
for pr in $(printf '%s\n' "$prs"); do # inline $(...): `for pr in $prs` is ONE iteration under zsh
|
|
1002
|
-
st=$(~/.fleet/bin/fleet-run ci-state.mjs --pr "$pr" 2>/dev/null) || : # not-green exits non-zero; the payload is the verdict
|
|
1003
|
-
if ! printf '%s' "$st" | jq -e '.verdict and .verdict != "rate-limited"' >/dev/null 2>&1; then
|
|
1004
|
-
case " $blind " in *" $pr "*) ;; *) # latch keyed BY PR: this cause is per-PR
|
|
1005
|
-
echo "WATCHER DEGRADED: no usable ci-state reading for #$pr — silence is NOT green"
|
|
1006
|
-
blind="$blind $pr" ;;
|
|
1007
|
-
esac
|
|
1008
|
-
continue # inner continue — still reaches the tick sleep
|
|
1009
|
-
fi
|
|
1010
|
-
case " $blind " in *" $pr "*)
|
|
1011
|
-
echo "WATCHER RECOVERED: ci-state reading #$pr again — CI polling resumed"
|
|
1012
|
-
keep=; for b in $(printf '%s\n' "$blind"); do [ "$b" = "$pr" ] || keep="$keep $b"; done; blind=$keep ;;
|
|
1013
|
-
esac
|
|
1014
|
-
: # your normal handling of $st for this PR; a run at a terminal state is the CI wake: fleet-event.mjs ci "$pr"
|
|
1015
|
-
done
|
|
1016
|
-
fi
|
|
1017
|
-
fi
|
|
1018
|
-
fi
|
|
1019
|
-
fi
|
|
1020
|
-
sleep 120 # the block's only pacing, once per tick
|
|
1021
|
-
done
|
|
1022
|
-
```
|
|
1023
|
-
|
|
1024
|
-
**One gate, not a gate plus a caveat.** See references/ci-watcher.md.
|
|
1025
|
-
|
|
1026
|
-
**It has to latch** — one line entering the degraded state, one leaving it,
|
|
1027
|
-
never one per tick. **Latch each cause at the level its cause lives
|
|
1028
|
-
at**: the budget, the unreported Actions bucket, the open-PR list and that
|
|
1029
|
-
list's cap are one global flag each — all account-level — but the payload latch is keyed by PR, and a
|
|
1030
|
-
shared scalar for those is an unlatched line wearing a latch's clothes — one
|
|
1031
|
-
persistently blind PR alongside one healthy PR re-clears the flag on every tick,
|
|
1032
|
-
emitting a DEGRADED and a false RECOVERED pair forever. The recovery line is
|
|
1033
|
-
what says the watch is alive again, so emit it on the first good pass even when
|
|
1034
|
-
nothing about the CI state changed; without it a reader cannot tell a recovered
|
|
1035
|
-
watcher from a dead one. See references/ci-watcher.md.
|
|
1036
|
-
|
|
710
|
+
**A probe that cannot read is a transition event, not silence.** A `WATCHER
|
|
711
|
+
DEGRADED` line is a paused poll or an unread PR, and silence there is NOT green
|
|
712
|
+
until its `WATCHER RECOVERED` line. **One gate, not a gate plus a caveat.** See references/ci-watcher.md.
|
|
1037
713
|
**All pacing in exactly one place — the tick loop's own tail `sleep`.** See references/ci-watcher.md.
|
|
1038
|
-
|
|
1039
|
-
**Guard every probe, not just the ones with a verdict field.** Capture its status (`prs=$(…) || prs=ERR`), latch it globally, and fall
|
|
1040
|
-
through to the same tail sleep. **Then iterate with the inline `$(printf '%s\n'
|
|
1041
|
-
"$prs")` form, never a bare `for pr in $prs`** — the assigned-variable trap in
|
|
1042
|
-
**Shell traps**, in references/shell-traps.md, which carries the mechanism and its
|
|
1043
|
-
measurements. It
|
|
1044
|
-
applies to **every** loop in this block, the `blind`-latch removal included:
|
|
1045
|
-
under a bare `for b in $blind` a recovered PR never leaves the list and the block
|
|
1046
|
-
emits a fresh RECOVERED every tick thereafter — the per-tick volume the latch
|
|
1047
|
-
exists to prevent, wearing a latch's clothes again. See references/ci-watcher.md.
|
|
1048
|
-
|
|
1049
|
-
**A readable list can still be a short one.** Pass an explicit `--limit`
|
|
1050
|
-
(`pr_cap`: one variable for the flag and the check, so the two cannot drift)
|
|
1051
|
-
and treat a full page as its own degraded cause — the "no silent caps" rule
|
|
1052
|
-
`candidates.mjs`'s `refuseIfCapped()` and `fleet-tick.mjs`'s `PR_LIMIT` already
|
|
1053
|
-
enforce. It latches globally like the list failure but does not pause polling:
|
|
1054
|
-
the PRs it did return are real, so the pass still watches them, and the
|
|
1055
|
-
DEGRADED line names the rest as unwatched until a pass comes back under the cap.
|
|
1056
|
-
See references/ci-watcher.md.
|
|
1057
|
-
|
|
1058
714
|
**The budget is gated at the floor and again at the pass, and only the pass gates announce recovery.** See references/ci-watcher.md.
|
|
1059
715
|
|
|
1060
|
-
**Judge `ci-state` on its payload, never its exit code** — the same rule as
|
|
1061
|
-
`merge-gate.mjs` applies on the merge path, applied to the watcher. Empty
|
|
1062
|
-
or unparseable output is the degraded case; parseable JSON reading
|
|
1063
|
-
`verdict: "not-green"` is normal. An exhausted quota may also name itself,
|
|
1064
|
-
`verdict: "rate-limited"` on stdout — likewise degraded, and likewise not a
|
|
1065
|
-
reading, which is why the gate above tests the verdict rather than mere
|
|
1066
|
-
parseability: that payload is perfectly good JSON. See references/ci-watcher.md.
|
|
1067
|
-
|
|
1068
716
|
**Do not back off and wait.** See references/ci-watcher.md.
|
|
1069
717
|
|
|
1070
718
|
**While the budget is exhausted, `ledger.mjs check` reads
|
|
@@ -1128,10 +776,9 @@ A member that bails demotes nothing itself; demotion by cause is yours
|
|
|
1128
776
|
|
|
1129
777
|
### Resolving a finisher halt
|
|
1130
778
|
|
|
1131
|
-
A
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
instead of asking anyone.
|
|
779
|
+
**A rebase never gets a `reviewed=`**, and never write a `reviewed=` you did
|
|
780
|
+
not read off a review result file. Why `past-pin` needs the tick: See
|
|
781
|
+
references/finisher.md.
|
|
1135
782
|
|
|
1136
783
|
**Resolving a finisher halt.** A halt is the finisher working correctly — it
|
|
1137
784
|
refused to label — so it is never `failed`, which stays for a finisher that
|
|
@@ -1150,34 +797,17 @@ decides the rest — two resolve automatically, every other one escalates:
|
|
|
1150
797
|
| `rebase` | **Escalate** — comment and flag, nothing more. The report's `evidence.replay` reads `pure` only when `git range-diff origin/main <pin> HEAD` pairs every pinned commit with an unchanged one; `changed` (a commit dropped, edited or added, or a stale `origin/main`) or `unknown` means read `evidence.rangeDiff` before acting, and `evidence.remote.state` says whether that head is on the PR. No automatic rule until a live halt shows the case recurring. |
|
|
1151
798
|
| `unreadable`, `missing`, `absent`, `other` | **Escalate** — comment and flag, no automatic retry. |
|
|
1152
799
|
|
|
1153
|
-
### Why a `past-pin` halt needs the tick
|
|
1154
|
-
|
|
1155
|
-
`past-pin` needs the tick because the dead-review path cannot reach it: once a
|
|
1156
|
-
review has **returned**, its `reviewed=` token holds the PR out of `review-due=`,
|
|
1157
|
-
and settling that review `=failed` re-queues nothing — that path covers only a
|
|
1158
|
-
review that died before returning. A head that moved past `reviewed=` with no
|
|
1159
|
-
such halt — a fix-applier's push — stays not due: duty 2 verifies what it
|
|
1160
|
-
applied. **A rebase never gets a `reviewed=`:** the earlier review still
|
|
1161
|
-
counts, because dispositions verdicts are tied to its head. Never write a
|
|
1162
|
-
`reviewed=` you did not read off a review result file.
|
|
1163
|
-
|
|
1164
800
|
### Resolving a finisher that died
|
|
1165
801
|
|
|
1166
|
-
**Resolving a finisher that died.** A finisher settled `failed` or `killed`
|
|
1167
|
-
its PR without `ready-to-merge
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
…` (`--kind repair` once an earlier finisher reported `labelled` without the
|
|
1176
|
-
label landing), then `ledger.mjs dispatch <M> finisher-pr-<M>-<x>`. The flag
|
|
1177
|
-
clears when a later attempt settles — `labelled`
|
|
1178
|
-
(then `unlabelled` takes over while the label is still off), `halted:<cause>`,
|
|
1179
|
-
or a live `-b` — or when the PR carries `ready-to-merge`, merges or closes.
|
|
1180
|
-
You never add the label yourself, and the tick does not retry.
|
|
802
|
+
**Resolving a finisher that died.** A finisher settled `failed` or `killed`
|
|
803
|
+
leaves its PR without `ready-to-merge`; the cockpit flags it
|
|
804
|
+
`finisher:failed` or `finisher:killed`, and the tick does not retry. Resolve
|
|
805
|
+
it by hand: investigate why the finisher died, or dispatch the next-suffix
|
|
806
|
+
finisher after the finisher gate: render its brief with `dispatch-prompt.mjs
|
|
807
|
+
render finisher --kind fresh --member finisher-pr-<M>-<x> …` (`--kind repair`
|
|
808
|
+
once an earlier finisher reported `labelled` without the label landing), then
|
|
809
|
+
`ledger.mjs dispatch <M> finisher-pr-<M>-<x>`. You never add the label
|
|
810
|
+
yourself. When the flag clears: See references/finisher.md.
|
|
1181
811
|
|
|
1182
812
|
### Dispositions escalation
|
|
1183
813
|
|
|
@@ -1225,7 +855,7 @@ Match the failure to its row in the table below and run that response; where the
|
|
|
1225
855
|
| Member idle with work outstanding | Read the PR first, *then* ping. Idle ≠ done |
|
|
1226
856
|
| Member **killed** (spend limit, API error, crash) | Confirm it is dead first — a frozen transcript does not establish that; see below — then run the **Member confirmed dead or never dispatched** wake, then new member, new name, prompt carries inherited state |
|
|
1227
857
|
| Member **unaccounted** (a live token the tick flags `CHECK-LIVENESS`, or any live token nothing has reported on) | Registry census first: `read history://` once. A row other than `running` → `read agent://<member>` and the artifacts (PR head, worktree, labels), settle the real outcome with the existing vocabulary, and resume a `parked` member with `write agent://<member>` when its work is worth continuing. A `running` row → the mtime discriminator below, two polls: moved means BLOCKED and alive; frozen at both polls **and** no artifact movement → `write proc://<member>/kill`, the **Member confirmed dead or never dispatched** wake, fresh name. Three independent alive-signals — the first poll, the second poll, the artifacts — must all be silent before a kill; the tick settles and kills nothing |
|
|
1228
|
-
| A claim abandoned mid-run (a collision found after the claim) | `~/.fleet/bin/fleet-run release-ticket.sh <N> impl-<N> implementer --apply`, the pair the claim took
|
|
858
|
+
| A claim abandoned mid-run (a collision found after the claim) | `~/.fleet/bin/fleet-run release-ticket.sh <N> impl-<N> implementer --apply`, the pair the claim took, on the spot — and over every claimed ticket with no PR when the run ends or the maintainer drains. **That refusal is the finding, never an obstacle**: a claim carrying commits or a pushed branch is not auto-released, ever — run `worktree-audit.sh` (it audits every worktree; find this claim's row in the output) and decide by hand. Update the released tickets' ledger rows in the same step. See references/reaping.md |
|
|
1229
859
|
| A second review launch refused for an open one | Wait for that one's `reviewed=`, or, once the **Member killed** row has confirmed its runner dead, settle it `=failed` in the same `ledger.mjs review` call as the new launch. |
|
|
1230
860
|
| `ledger.mjs settle <member> --from-report` exits 3 | A check failed: the member's claim and the world disagree, and nothing is written. You rule. |
|
|
1231
861
|
| `ledger.mjs settle <member> --from-report` exits 2 | The report is absent or invalid, or a check could not look; nothing is written. A check that could not look is run again once `gh` and `git` answer. |
|
|
@@ -1245,48 +875,29 @@ yet) whose prompt states what it inherits.
|
|
|
1245
875
|
Reviewers that went idle on CI recover as a **finisher, not a re-review** once
|
|
1246
876
|
commits are pushed. See references/member-lifecycle.md.
|
|
1247
877
|
|
|
1248
|
-
Audit every worktree before dispatching replacements with
|
|
1249
|
-
`~/.fleet/bin/fleet-run worktree-audit.sh
|
|
1250
|
-
ahead-count and uncommitted files, the committed-vs-uncommitted distinction that
|
|
1251
|
-
decides whether a replacement redoes or destroys work.
|
|
878
|
+
**Audit every worktree before dispatching replacements**, with
|
|
879
|
+
`~/.fleet/bin/fleet-run worktree-audit.sh`. See references/failure-handling.md.
|
|
1252
880
|
|
|
1253
881
|
When a report is missing, **ask by name — that is what the "unless the controller asks by name" clause in every brief is for — and do not attribute the gap to the member until it answers.** See references/member-lifecycle.md.
|
|
1254
882
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
**Verify every reported SHA.** `~/.fleet/bin/fleet-run verify-sha.sh
|
|
1267
|
-
<branch> <sha>` before enqueueing — a member can commit in a nested worktree,
|
|
1268
|
-
leaving the SHA on a stray branch while its report reads normally. Exit codes as
|
|
1269
|
-
`inflight.sh`: `# 0 reachable, 1 not reachable, 2 unanswerable`. Not reachable
|
|
1270
|
-
(exit 1) → flag, do not enqueue, do not return the ticket to the shortlist until the
|
|
1271
|
-
maintainer rules. **Exit 2 is not a verdict** — the question could not be
|
|
1272
|
-
answered, and every cause of it reaches you as one shape: no JSON on stdout at
|
|
1273
|
-
all, the cause on stderr alone. Read that stderr, fix what it names and re-run;
|
|
1274
|
-
escalate if it repeats. Never read exit 2 as `not reachable`, and never record it
|
|
1275
|
-
as a stray commit — the flag and the maintainer's ruling above are what exit 1
|
|
1276
|
-
earns, and spending them on a probe that never answered costs a member its
|
|
1277
|
-
ticket over a failure a re-run would have cleared.
|
|
883
|
+
A review that throws, returns empty or notifies a status not `completed` is a
|
|
884
|
+
failure event, not a clean review: retry it as the **Failure handling** row for
|
|
885
|
+
it says. See references/failure-handling.md.
|
|
886
|
+
|
|
887
|
+
**Verify every reported SHA** with `~/.fleet/bin/fleet-run verify-sha.sh
|
|
888
|
+
<branch> <sha>` before enqueueing. Exit 1, not reachable → flag, do not
|
|
889
|
+
enqueue, do not return the ticket to the shortlist until the maintainer rules.
|
|
890
|
+
**Exit 2 is not a verdict**: read its stderr, fix what it names and re-run;
|
|
891
|
+
escalate if it repeats, and never read it as `not reachable` or record it as a
|
|
892
|
+
stray commit. See references/failure-handling.md.
|
|
1278
893
|
|
|
1279
894
|
**Never `--delete-branch` — the merge bot deletes the head branch itself.**
|
|
1280
895
|
`gh pr merge <n> --merge` alone: the flag's local cleanup errors on a `main`
|
|
1281
896
|
held by another worktree, or strands the feature worktree on `main`.
|
|
1282
897
|
|
|
1283
898
|
**Never force a rebase to start.** No `git clean`, `git checkout .`,
|
|
1284
|
-
`git reset --hard`, `git stash`.
|
|
1285
|
-
|
|
1286
|
-
mode, never bare `--porcelain`, or `status.showUntrackedFiles = no` reads an
|
|
1287
|
-
empty answer here and licenses the rebase over work this check exists to
|
|
1288
|
-
protect. Stashing empties it either way, so that check and
|
|
1289
|
-
`no-undo-audit.sh` both go quiet on work nothing else holds.
|
|
899
|
+
`git reset --hard`, `git stash`. Non-empty `git status --porcelain -unormal` →
|
|
900
|
+
stop and report, never off bare `--porcelain`. See references/failure-handling.md.
|
|
1290
901
|
|
|
1291
902
|
**Members share one filesystem, one docker stack and one process table**, and the
|
|
1292
903
|
failures arrive as *wrong findings*, not errors:
|
|
@@ -1346,19 +957,12 @@ Read why that body carries the claim discipline, and its literal-path and `grep
|
|
|
1346
957
|
**Then.** **Re-pin first** — `~/.fleet/bin/fleet-run instruments.sh
|
|
1347
958
|
--pin`. See references/instruments.md. Then the ledger line, save the
|
|
1348
959
|
rationale, one line to the maintainer. **Then land it from the main checkout,
|
|
1349
|
-
if the fix was edited in it
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
Re-baseline only a fix you leave edited in the checkout: run
|
|
1356
|
-
`~/.fleet/bin/fleet-run main-checkout.mjs --check` first, and only when the
|
|
1357
|
-
paths it names are exactly the ones you just edited, run `--record` — never
|
|
1358
|
-
before the commit, because the switch back would then read your own paths
|
|
1359
|
-
dirty again. Any other path on that line is a stray write — leave the hold for
|
|
1360
|
-
the maintainer, because `--record` would certify it with yours. Live members
|
|
1361
|
-
hold the old text — re-brief only if it changes what they do *now*.
|
|
960
|
+
if the fix was edited in it**: commit it on a branch and switch back to `main`
|
|
961
|
+
before the next tick, never `--record` on that branch. Re-baseline only a fix
|
|
962
|
+
you leave edited in the checkout, after the commit, and only when
|
|
963
|
+
`main-checkout.mjs --check` names exactly your paths; any other path is a stray
|
|
964
|
+
write whose hold is the maintainer's. Re-brief live members only if the fix
|
|
965
|
+
changes what they do *now*. See references/instruments.md.
|
|
1362
966
|
|
|
1363
967
|
Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
|
|
1364
968
|
|
|
@@ -1410,13 +1014,8 @@ One running table, updated as events land:
|
|
|
1410
1014
|
|
|
1411
1015
|
Plus a queue-depth line: shortlist, supply, whether triage was suggested.
|
|
1412
1016
|
|
|
1413
|
-
**Do not read a cell early
|
|
1414
|
-
|
|
1415
|
-
check passed and its resolved effort is the cell's level — and one at
|
|
1416
|
-
`slow-high` whose resolved model and effort differ. Count them per cell, never
|
|
1417
|
-
pooled across cells, and stop until
|
|
1418
|
-
there are at least ten of them across five or more distinct `run_date`s; below
|
|
1419
|
-
that, a comparison count is a number, not evidence. **Report the count the per-cell readout prints,
|
|
1017
|
+
**Do not read a cell early** (references/pull.md, **Do not read a cell
|
|
1018
|
+
early**). **Report the count the per-cell readout prints,
|
|
1420
1019
|
never one from any other query:** `~/.fleet/bin/fleet-run cell-readout.mjs`
|
|
1421
1020
|
prints `<cell> <comparisons> <run_dates> <resolved models>` for each cell past
|
|
1422
1021
|
that floor and a cell below it only as a count on stderr; a `mixed (…)` models
|