@feigi/fleet-ctl 3.88.7 → 3.88.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.88.7",
3
+ "version": "3.88.8",
4
4
  "description": "Agent fleet: run-team controller, merge bot, PR reviewer, and the ticket pipeline they share",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -9,8 +9,7 @@ Run `next-ticket`, `review-and-fix`, `run-merge-bot` as one fleet. You are the
9
9
  **controller**, in the main thread, never a member.
10
10
 
11
11
  `$ARGUMENTS` = `[in-flight]`, optional, one positive integer, default 5: the
12
- In-flight bound, the most tickets between Claim and merge at once. It becomes
13
- the tick's `--in-flight` and `tier-roles.mjs --check --in-flight`.
12
+ In-flight bound, the most tickets between Claim and merge at once.
14
13
  Merge bot is at most one, not configurable.
15
14
 
16
15
  The war story behind a rule ending `See references/<file>` lives in that file; load
@@ -35,10 +34,8 @@ The reasoning behind each line is in references/invariants.md.
35
34
 
36
35
  This phase is done when `phase0.mjs` exits 0: run it again after an exit 3 (a step failed) or an exit 4 (a `NEED` line asks you to act), and dispatch nothing before.
37
36
 
38
- Once per run, at start. Supply is automatic after that: the tick
39
- refreshes the Shortlist itself, and every admission is a **Pull** — one ticket
40
- into one free implementer slot, the moment it frees (phase 1). Nothing in this
41
- phase, or in any later one, asks the maintainer which tickets to take.
37
+ Once per run, at start; after it, every admission is a **Pull** (phase 1).
38
+ Nothing in this phase, or in any later one, asks the maintainer which tickets to take.
42
39
 
43
40
  0. **Once per run, before anything else.** Run phase 0 from a Python eval
44
41
  cell in this main checkout, without `--session`:
@@ -93,25 +90,19 @@ phase, or in any later one, asks the maintainer which tickets to take.
93
90
  mandatory, no fallback**: never run `candidates.mjs --allow-fallback` by hand.
94
91
 
95
92
  `candidates.mjs`'s exit 3 reaches you as an empty shortlist whose `dropped`
96
- names every ticket the to-spec filter took, by number and reason — and the
97
- tick prints the same list on every tick, `specs-dropped=#<N> (to-spec: User
98
- Stories heading)` on the implementer row. **Exit 3 is not that empty queue, and never a reason to
99
- widen the net.** It says the labeled query returned rows and the to-spec
100
- filter took every one: what is queued is to-tickets' input rather than
101
- claimable tickets, and a member handed one implements an entire spec as a
102
- single ticket. Log it as
103
- specs awaiting to-tickets, not as no work — read the numbers off
104
- `dropped` or `specs-dropped=`, not off stderr, which the tick's own
105
- refresh discards — and still do not pass
106
- `--allow-fallback`, whose second pass is unfiltered and so reaches the
107
- `ready-for-human` and untriaged work you have no channel for. A partial
108
- drop, survivors and specs together, is named the same way.
93
+ names every ticket the to-spec filter took, by number and reason, and as
94
+ `specs-dropped=#<N> (to-spec: User Stories heading)` on the implementer row.
95
+ **Exit 3 is not that empty queue, and never a reason to widen the net**: the
96
+ to-spec filter took every one, so what is queued is to-tickets' input rather
97
+ than claimable tickets. Log it as specs awaiting to-tickets, not as no work —
98
+ read the numbers off `dropped` or `specs-dropped=`, not off stderr — and
99
+ still do not pass `--allow-fallback`. See references/phase-0.md.
109
100
  2. **Refresh is the tick's from here on**: it prints `REFRESHED shortlist: <n>
110
101
  entries; <k> lifted` (references/tick-rows.md, **Refresh is the tick's**).
111
102
 
112
103
  ## The wake table
113
104
 
114
- A wake is done when its one `fleet-event.mjs` call has run and you have acted on every line it printed, the tick's last; with nothing left to act on, mark the beat and end your turn. What a wake owes beyond its call is under **What a wake owes beyond its call**, below. The loop has no exit but the run's own end: drain the run when it ends (**End of run**).
105
+ A wake is done when its one `fleet-event.mjs` call has run and you have acted on every line it printed, the tick's last; with nothing left to act on, mark the beat and end your turn. The loop has no exit but the run's own end: drain the run when it ends (**End of run**).
115
106
 
116
107
  React to events; never block on one.
117
108
 
@@ -126,12 +117,10 @@ merge bot, Pulling a ticket, re-verifying a SHA, filing a follow-up — all proc
126
117
  unconfirmed. Invoking the command was the opt-in. One exception: **a judgement the
127
118
  evidence cannot settle**.
128
119
 
129
- **Rule: record, tick, act, beat.** Every wake — a member report, a review
130
- notification, a label, a CI run reaching a terminal state, a `CONFLICT` line, a
131
- heartbeat, the maintainer's drain — is one `fleet-event.mjs` call, which makes
132
- the wake's writes and ends in one `fleet-tick.mjs` invocation, and you do what
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.
120
+ **Rule: record, tick, act, beat.** Every wake below is one `fleet-event.mjs`
121
+ call, which makes the wake's writes and ends in one `fleet-tick.mjs`
122
+ invocation; do what it prints, and never hand-edit the ledger.
123
+ See references/ci-and-staleness.md.
135
124
 
136
125
  Run each as `~/.fleet/bin/fleet-run fleet-event.mjs <kind> <args>`, from the main checkout:
137
126
 
@@ -182,10 +171,8 @@ owes beyond its call:
182
171
  expired on arrival; say so. See references/ci-and-staleness.md.
183
172
  - **Merge-bot pass reports done** → `fleet-event.mjs merge-bot-report merge-bot-<n>`. A label that
184
173
  landed at the last moment reads `DISPATCH
185
- merge-bot` again: dispatch the next bot straight away. A `rebase-fallback-#<pr>`
186
- on a conflict needs no record from you — the bot already appended
187
- `conflict-hold:#<pr>` to that PR's row, and the tick turns it into `DISPATCH
188
- fix-pr PR#<pr>` (**The fix-applier**). The tick's `--apply` reaps after the
174
+ merge-bot` again: dispatch the next bot straight away. A conflict needs no
175
+ record from you (**Failure handling**). The tick's `--apply` reaps after the
189
176
  pass. Read `worktreesRemoved` alongside `reaped`: a pass that
190
177
  reaps branches and removes no worktrees is a finding, not a quiet success.
191
178
  Update the reaped tickets' ledger rows in the same step.
@@ -238,7 +225,7 @@ A row with `review=` and no `reviewed=` is a review in flight, and the tick coun
238
225
  - **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
226
  - **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
227
  - **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).
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.
228
+ - **The merge bot.** Named `merge-bot-<n>`; `ledger.mjs dispatch merge-bot` numbers it. 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.
242
229
  - **The finisher.** Its render and receives are in `references/finisher.md`.
243
230
 
244
231
  **A fleet member is never dispatched with omp's `isolated: true`.** See references/isolation.md.
@@ -277,7 +264,7 @@ yourself. Each digest field in full: See references/reviewers.md. What the
277
264
  digest asks of you:
278
265
 
279
266
  - **`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.
267
+ - **`resume`** non-null is reported, not acted on: the fix-applier defers what it names.
281
268
  - **`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
269
  - **`suiteVerdict`** — **Exit 75 is not a test result.** In a review it makes
283
270
  `suiteVerdict` not usable, `suiteVerdict.reason` beginning "no suite slot within
@@ -296,12 +283,7 @@ digest asks of you:
296
283
  `docs/metrics/tier-outcomes.tsv` when you rule each PR's review, with
297
284
  `~/.fleet/bin/fleet-run tier-outcomes.mjs append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no --note "<text>"`,
298
285
  never by hand** (that file's header carries the column meanings). The script
299
- fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
300
- ran, off the implementer's `tier-ok=` ledger verdict or else its one
301
- member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
302
- its one member-outcomes row decides), and BLANK with a WARNING naming why
303
- when neither settles
304
- it; never type a tier in over that. That append is the whole duty. See references/pull.md.
286
+ fills `tier` itself; never type one in. That append is the whole duty. See references/pull.md.
305
287
 
306
288
  **A verdict is a measurement only if it post-dates the run that produced it.** See references/reviewers.md.
307
289
 
@@ -377,10 +359,7 @@ Act on each line the tick prints as its row reads here, then stop: the next wake
377
359
  Read the depth off the tick and never derive it by hand: act on the row it prints, and when it prints `SUGGEST /triage, hold idle`, hold the implementer row idle and suggest `/triage` in the report. See references/tick-rows.md.
378
360
 
379
361
  **The review backlog never gates, and the merge-queue depth is still no
380
- signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
381
- `DISPATCH review` on the same tick, never a reason to idle an implementer.
382
- A starved implementer queue never stalls the review or merge side.
383
- For the merge queue, see references/pull.md.
362
+ signal.** The only gate on a Pull is the In-flight count. See references/pull.md.
384
363
 
385
364
  **Reconcile, do not wait for an event.** With the shortlist empty the tick's rows
386
365
  govern — refresh, and never Pull a ticket the shortlist does not hold.
@@ -456,15 +435,10 @@ Pull, and each head a free slot Pulls gets its own row, read off that head's
456
435
  <string>`, the string it must add: each runs `staleness.mjs` and asks again.
457
436
  `none <why>` (no string settles it) and `done` end the question.
458
437
 
459
- **Grep for the ticket's ASKED-FOR CHANGE, not only its subject construct** —
460
- the construct check passes on every stale pin ticket, because the construct
461
- is what the pin is *about* and never went anywhere. So for a ticket asking
438
+ **Grep for the ticket's ASKED-FOR CHANGE, not only its subject construct**:
462
439
  for a pin, test or assertion, grep the **test file** for the assertion it
463
- wants; for one asking for exact wording, grep the **old** wording the ticket
464
- quotes as wrong, never the new wording it proposes. A proposed replacement is
465
- a suggestion the implementer is free to reword — an equivalent rewording
466
- satisfies the ticket while failing a grep for its literal text — and the
467
- quoted defect is the fact.
440
+ wants; for exact wording, grep the **old** wording the ticket quotes as
441
+ wrong. See references/pull.md.
468
442
 
469
443
  `staleness.mjs` answers each `gone` and `present` in three values:
470
444
 
@@ -494,16 +468,12 @@ Pull, and each head a free slot Pulls gets its own row, read off that head's
494
468
 
495
469
  Any undecided item → not decided. Name it; that name is the relabel comment's
496
470
  missing decision or open fork. **Torn → surface, never guess**: relabel it
497
- `ready-for-human` as a fork naming the open decision — the label is how a
498
- Pull surfaces it to the maintainer. Opposite of
499
- `sizing-a-ticket`'s *torn → take the heavier row*, deliberately — see that skill.
471
+ `ready-for-human` as a fork naming the open decision. See references/pull.md.
500
472
 
501
- 4. **`collision` (Q3)** — asked after the scan prints one line per open PR or
502
- live worktree branch sharing a path with the brief (`no overlap` when none,
503
- `could not look: <which>` for one it could not read) and the brief's `Out of
504
- scope` `#N` refs as candidates: `clear`; `behind-pr:#M`, an open PR or a
505
- branch with no PR yet by its name; `behind-issue:#M`, an open issue that
506
- section sequences this one after; or `paths <p>…`, which scans again.
473
+ 4. **`collision` (Q3)** — asked after the collision scan (references/pull.md):
474
+ `clear`; `behind-pr:#M`, an open PR or a branch with no PR yet by its name;
475
+ `behind-issue:#M`, an open issue the brief's `Out of scope` sequences this
476
+ one after; or `paths <p>…`, which scans again.
507
477
 
508
478
  ### How a Pull ends
509
479
 
@@ -537,12 +507,12 @@ worktree re-created for an already-open PR is in references/isolation.md.
537
507
 
538
508
  A dispatch is done when its `task` call is made, in the background, with the `agent` and the name `ledger.mjs dispatch` printed, and — for an implementer — the tier check has written its verdict: dispatch nothing on top of an unchecked one.
539
509
 
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.
510
+ - **One claim, one dispatch, no queue.** Claim one ticket, then dispatch one new member at once, under a name no member has held; nothing holds a queue. Record `ledger.mjs dispatch` before the call.
541
511
  - **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.
542
512
  - **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.
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.
513
+ - **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`, and never label a cell anywhere — the dispatch record already carries it. **The readout counts DELIBERATE comparisons.** See references/pull.md.
544
514
 
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`.
515
+ 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`. Rationale for these rules: `references/member-lifecycle.md`.
546
516
 
547
517
  **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.
548
518
 
@@ -682,11 +652,7 @@ in the fix-applier body, not in per-event messages.**
682
652
  every token the row already has, see references/ledger.md), then
683
653
  remove `ready-to-merge`, then approve. Then
684
654
  dispatch a **fresh** finisher against the new head; the first audit does not
685
- transfer, since it verified a different tree. The merge bot refuses that head on
686
- its own (`run-merge-bot.md`, **The labelled head**), so this is not the only
687
- guard — but its refusal costs a pass and leaves the label lying, which is yours
688
- to clear either way, the same way: `label-off=` first, then the removal.
689
- See references/finisher.md.
655
+ transfer, since it verified a different tree. See references/finisher.md.
690
656
 
691
657
  ### Own the CI waits
692
658
 
@@ -695,9 +661,7 @@ runs from the block in references/ci-watcher.md, verbatim, keyed
695
661
  `<run-id>:<attempt>:<conclusion>` so each terminal state fires once: one
696
662
  `~/.fleet/bin/fleet-run ci-state.mjs --pr <N>` per open PR emits run id,
697
663
  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.
664
+ `--quiet`** — that flag drops `jobs`, `missing` and `dropped`.
701
665
 
702
666
  A monitor event is a wake-up, never a verdict; members re-query at labelling
703
667
  time. See references/ci-and-staleness.md.
@@ -715,12 +679,9 @@ until its `WATCHER RECOVERED` line. **One gate, not a gate plus a caveat.** See
715
679
 
716
680
  **Do not back off and wait.** See references/ci-watcher.md.
717
681
 
718
- **While the budget is exhausted, `ledger.mjs check` reads
719
- `verdict: "unverified"`** — the ledger was read and the tracker was not. That is
720
- correct behaviour, but `unverified` exits **0**, the same as `clean`, so a
721
- member treating exit 0 as "safe to file" files blind during exactly this window.
722
- Say so when you flag the outage: members read `verdict` explicitly until you
723
- report recovery, never the exit code alone.
682
+ **While the budget is exhausted, `ledger.mjs check` reads `verdict:
683
+ "unverified"`, which exits 0 like `clean`**: say so when you flag the outage,
684
+ so members read `verdict` until you report recovery (references/ci-watcher.md).
724
685
 
725
686
  ## Judgement rules the drivers stop for
726
687
 
@@ -906,16 +867,11 @@ failures arrive as *wrong findings*, not errors:
906
867
 
907
868
  **What the victim's output looks like depends on the signal — and one of the cases is a clean green.** See references/isolation.md.
908
869
 
909
- So **a complete summary block proves the runner survived, not that the run was
910
- uncontaminated.** Scrutinize reds, short counts *and* greens from inside the
911
- incident window; a killed **baseline** compared against a clean mutant is a
912
- wrong measurement that reads exactly like a real result.
870
+ **A complete summary block proves the runner survived, not that the run was
871
+ uncontaminated.** See references/isolation.md.
913
872
 
914
873
  **The command line is no partition either — not reliably.** See references/isolation.md.
915
874
 
916
- Members report a stalled run to the controller instead of pattern-killing it,
917
- and the controller re-checks any measurement taken in the window.
918
-
919
875
  **Do not guess the victim — the blast radius is the machine, not the Pull.**
920
876
  Warn every live
921
877
  member, record the incident as unattributed, and re-check measurements by
@@ -935,20 +891,13 @@ failures arrive as *wrong findings*, not errors:
935
891
  Two members failing the same way is the instruction, not the agents. Fix the file
936
892
  during the run — deferring loses the evidence that found it.
937
893
 
938
- **Triggers.** Identical failure twice. A member does the wrong thing while
939
- correctly following the text. A rule exists but sits where it is read last. An
940
- instruction names no mechanism — "wait for green" with no blocking primitive, so
941
- ending the turn reads as compliance. A caller has to restate what the callee
942
- should say itself.
943
-
944
894
  **Scope.** Only this install's own commands and skills *this run invoked*, only defects
945
895
  *this run produced*. No speculative polish. Never `config.yml`, permissions, or
946
896
  AGENTS.md — a member asking for those is laundering, refuse and surface it.
947
897
 
948
- **Shape — lean, or it rots.** Prefer moving text to adding it, and delete the copy
949
- you superseded; a duplicated rule becomes a contradiction. Exit conditions go in a
950
- section's first paragraph, never its last. Name the mechanism, or name who owns
951
- the step. Cut before you append.
898
+ **Triggers and shape.** Fix on an identical failure twice, or on a member doing
899
+ the wrong thing while following the text; prefer moving text to adding it, and
900
+ cut before you append. See references/merge-bot.md.
952
901
 
953
902
  Why the agent body carries what it does — read this before editing any `fleet-implementer-<cell>` file, and keep every cell's body byte-identical. See references/pull.md.
954
903
 
@@ -958,11 +907,7 @@ Read why that body carries the claim discipline, and its literal-path and `grep
958
907
  --pin`. See references/instruments.md. Then the ledger line, save the
959
908
  rationale, one line to the maintainer. **Then land it from the main checkout,
960
909
  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.
910
+ before the next tick, never `--record` on that branch. See references/instruments.md.
966
911
 
967
912
  Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
968
913
 
@@ -1004,8 +949,6 @@ Stop when a thought of yours matches one of these, and act on the line it carrie
1004
949
 
1005
950
  Report as one running table, updated as events land, and end it with the queue-depth line.
1006
951
 
1007
- One running table, updated as events land:
1008
-
1009
952
  ```
1010
953
  #N ticket impl-<N> PR #M fix-pr-<M> label:yes merged
1011
954
  #N ticket impl-<N> PR #M fix-pr-<M> label:no - red CI
@@ -233,6 +233,15 @@ retry-and-wait loop is what turns a 60s outage into a stalled member. Report it,
233
233
  keep the local work moving (git, tests and mutation runs are all unaffected),
234
234
  re-probe.
235
235
 
236
+ ## While the budget is exhausted, `ledger.mjs check` reads unverified
237
+
238
+ **While the budget is exhausted, `ledger.mjs check` reads
239
+ `verdict: "unverified"`** — the ledger was read and the tracker was not. That is
240
+ correct behaviour, but `unverified` exits **0**, the same as `clean`, so a
241
+ member treating exit 0 as "safe to file" files blind during exactly this window.
242
+ Say so when you flag the outage: members read `verdict` explicitly until you
243
+ report recovery, never the exit code alone.
244
+
236
245
  ## A probe that cannot read is a transition event, not silence
237
246
 
238
247
  The natural
@@ -253,6 +253,12 @@ before the approval because removing the artifact the merge bot gates on is
253
253
  the reliable stop, where messaging the bot races it, and a label has been
254
254
  observed holding until after an abort message arrived.
255
255
 
256
+ When a push moves the PR off its labelled head, the merge bot refuses the new
257
+ head on its own (`run-merge-bot.md`, **The labelled head**), so your take-off
258
+ is not the only guard — but its refusal costs a pass and leaves the label
259
+ lying, which is yours to clear either way, the same way: `label-off=` first,
260
+ then the removal.
261
+
256
262
  ## A final report is not proof the member stopped
257
263
 
258
264
  **A final report is not proof the member stopped.** Observed twice: a ruling
@@ -253,6 +253,9 @@ where the controller itself runs from.
253
253
  killed a `node --test` over two test files that was not
254
254
  its own.
255
255
 
256
+ Members report a stalled run to the controller instead of pattern-killing it,
257
+ and the controller re-checks any measurement taken in the window.
258
+
256
259
  ## What a killed run's output looks like depends on the signal
257
260
 
258
261
  **What the victim's output looks like depends on the signal — and one of the
@@ -272,6 +275,11 @@ asserting only that the process it spawned exited non-zero —
272
275
  `assert.notEqual(r.status, 0)`, a form used across this suite, in files that
273
276
  spawn the shell scripts they cover — passes vacuously on `null`.
274
277
 
278
+ So **a complete summary block proves the runner survived, not that the run was
279
+ uncontaminated.** Scrutinize reds, short counts *and* greens from inside the
280
+ incident window; a killed **baseline** compared against a clean mutant is a
281
+ wrong measurement that reads exactly like a real result.
282
+
275
283
  ## The command line is no partition either
276
284
 
277
285
  **The command line is no partition either — not reliably.** Ports are derived
@@ -2,6 +2,19 @@
2
2
 
3
3
  Evidence for the rules SKILL.md states in **A mid-run tooling fix** and in the `ready-to-merge` wake, keyed by the rule each justifies.
4
4
 
5
+ ## When to fix the tooling mid-run, and its shape
6
+
7
+ **Triggers.** Identical failure twice. A member does the wrong thing while
8
+ correctly following the text. A rule exists but sits where it is read last. An
9
+ instruction names no mechanism — "wait for green" with no blocking primitive, so
10
+ ending the turn reads as compliance. A caller has to restate what the callee
11
+ should say itself.
12
+
13
+ **Shape — lean, or it rots.** Prefer moving text to adding it, and delete the copy
14
+ you superseded; a duplicated rule becomes a contradiction. Exit conditions go in a
15
+ section's first paragraph, never its last. Name the mechanism, or name who owns
16
+ the step. Cut before you append.
17
+
5
18
  ## A mid-run tooling fix lands through a PR
6
19
 
7
20
  **Landing it.** The file being right is not the end of it. `main` carries a
@@ -12,6 +12,10 @@ child's stderr. `--session <dir>` overrides the variable.
12
12
  Exit 3 is a step that failed: its line carries the refusal, so fix the cause
13
13
  and run it again.
14
14
 
15
+ Once per run, at start. Supply is automatic after that: the tick
16
+ refreshes the Shortlist itself, and every admission is a **Pull** — one ticket
17
+ into one free implementer slot, the moment it frees (phase 1).
18
+
15
19
  `phase0.mjs` runs the start sequence in
16
20
  its fixed order — alarm, branch, fast-forward, pin, baseline, preflight, ruleset,
17
21
  tier-roles, metrics, cockpit, rotate, reclaim, recipe, fold-in, stranded,
@@ -495,3 +499,18 @@ stranded.
495
499
  widen the list: `ready-for-human` needs a human to brainstorm first and you
496
500
  have no channel to one mid-flight.
497
501
 
502
+ `candidates.mjs`'s exit 3 reaches you as an empty shortlist whose `dropped`
503
+ names every ticket the to-spec filter took, by number and reason — and the
504
+ tick prints the same list on every tick, `specs-dropped=#<N> (to-spec: User
505
+ Stories heading)` on the implementer row. **Exit 3 is not that empty queue, and never a reason to
506
+ widen the net.** It says the labeled query returned rows and the to-spec
507
+ filter took every one: what is queued is to-tickets' input rather than
508
+ claimable tickets, and a member handed one implements an entire spec as a
509
+ single ticket. Log it as
510
+ specs awaiting to-tickets, not as no work — read the numbers off
511
+ `dropped` or `specs-dropped=`, not off stderr, which the tick's own
512
+ refresh discards — and still do not pass
513
+ `--allow-fallback`, whose second pass is unfiltered and so reaches the
514
+ `ready-for-human` and untriaged work you have no channel for. A partial
515
+ drop, survivors and specs together, is named the same way.
516
+
@@ -52,6 +52,11 @@ terminating in exactly one dispatch or an empty shortlist:
52
52
  scope` sequencing wherever that section is found. Then judge **decided?**
53
53
  — never size.
54
54
 
55
+ **Torn → surface, never guess**: relabel it
56
+ `ready-for-human` as a fork naming the open decision — the label is how a
57
+ Pull surfaces it to the maintainer. Opposite of
58
+ `sizing-a-ticket`'s *torn → take the heavier row*, deliberately — see that skill.
59
+
55
60
  **A ruling the brief does not carry stops the Pull first.** The brief is
56
61
  the last `## Agent Brief` comment, else the issue body, so a decision posted
57
62
  under another heading never reaches any question the Pull asks. A comment
@@ -79,6 +84,16 @@ terminating in exactly one dispatch or an empty shortlist:
79
84
  commit, do not claim it. Partly fixed → say which acceptance criteria the
80
85
  tree already meets, and which the tree now **contradicts**.
81
86
 
87
+ **Grep for the ticket's ASKED-FOR CHANGE, not only its subject construct** —
88
+ the construct check passes on every stale pin ticket, because the construct
89
+ is what the pin is *about* and never went anywhere. So for a ticket asking
90
+ for a pin, test or assertion, grep the **test file** for the assertion it
91
+ wants; for one asking for exact wording, grep the **old** wording the ticket
92
+ quotes as wrong, never the new wording it proposes. A proposed replacement is
93
+ a suggestion the implementer is free to reword — an equivalent rewording
94
+ satisfies the ticket while failing a grep for its literal text — and the
95
+ quoted defect is the fact.
96
+
82
97
  Old string gone from `git show origin/main:<file>`
83
98
  ⇒ the fix landed, and
84
99
  `git log -S '<old string>' --oneline origin/main -- <file> | head -1` names
@@ -222,6 +237,13 @@ terminating in exactly one dispatch or an empty shortlist:
222
237
  printed beside Q3 as the candidates: one naming an open ticket this one
223
238
  sequences after → `behind-issue:#M`. No overlap and no such ticket →
224
239
  `clear`.
240
+
241
+ **`collision` (Q3)** — asked after the scan prints one line per open PR or
242
+ live worktree branch sharing a path with the brief (`no overlap` when none,
243
+ `could not look: <which>` for one it could not read) and the brief's `Out of
244
+ scope` `#N` refs as candidates: `clear`; `behind-pr:#M`, an open PR or a
245
+ branch with no PR yet by its name; `behind-issue:#M`, an open issue that
246
+ section sequences this one after; or `paths <p>…`, which scans again.
225
247
  6. **Claim** — `~/.fleet/bin/fleet-run claim-ticket.sh <N> impl-<N> implementer
226
248
  --apply` (below). Exit 2 → treat it as taken, next entry.
227
249
  7. **Route, row, then dispatch** — `~/.fleet/bin/fleet-run ticket-router.mjs route
@@ -418,6 +440,9 @@ it — a ticket the rest of the run
418
440
  depends on; `class` is not read. Do not tell the member it is a control: a member that
419
441
  knows it is being measured is not measuring the same thing.
420
442
 
443
+ The cell is the one the router's printed CELL line names, the whole routing decision.
444
+ **Do not label it anywhere — the dispatch record already carries it.**
445
+
421
446
  ## The readout counts deliberate comparisons
422
447
 
423
448
  **The readout counts DELIBERATE comparisons.** An earlier rule asked
@@ -438,6 +463,14 @@ supply is one Pull per free slot, so there are no implementer batches. The
438
463
  guard this section speaks of is SKILL.md's **Guard: accumulate per PR, never
439
464
  conclude inside one run**, under **Reading a review result**.
440
465
 
466
+ `tier-outcomes.mjs append`
467
+ fills `run_date`, `pr`, `ticket` and `tier` itself — `tier` from what actually
468
+ ran, off the implementer's `tier-ok=` ledger verdict or else its one
469
+ member-outcomes row (a `tier-unverifiable=` member has no ledger verdict, so
470
+ its one member-outcomes row decides), and BLANK with a WARNING naming why
471
+ when neither settles
472
+ it; never type a tier in over that.
473
+
441
474
  **Why not decide inside one run.** A run holds 2-3 implementer PRs, and ticket
442
475
  difficulty swamps the tier effect — a gojq parity harness and a two-statement
443
476
  shell reorder are not comparable units. Finding-counts are not comparable either:
@@ -537,6 +570,9 @@ one run.
537
570
 
538
571
  ## Default: In-flight bound 5
539
572
 
573
+ `$ARGUMENTS` sets B. It becomes
574
+ the tick's `--in-flight` and `tier-roles.mjs --check --in-flight`.
575
+
540
576
  **Default: In-flight bound 5, fixed for the run.** The level is set by load, not
541
577
  throughput: B caps live review units (≤ open PRs ≤ B), and the review side is
542
578
  about 84 % of cache-write tokens, fanning out to 1 snapshot + up to 6
@@ -554,6 +590,10 @@ A deep `ready-to-merge` queue adds no rebases per PR — each PR rebases exactly
554
590
  once, when it becomes the candidate — and those PRs count as In flight because
555
591
  they sit between Claim and merge, not because the queue is deep.
556
592
 
593
+ A deep backlog is a
594
+ `DISPATCH review` on the same tick, never a reason to idle an implementer.
595
+ A starved implementer queue never stalls the review or merge side.
596
+
557
597
  ## Reconcile, do not wait for an event
558
598
 
559
599
  "A slot is free and the shortlist is
@@ -78,6 +78,13 @@ So nothing in the run cleans it up. Next run, the in-flight probe's local-worktr
78
78
 
79
79
  Check: the script's own test suite — real throwaway repos, no mocked git history, one case per precondition proving it blocks on its own.
80
80
 
81
+ ## A conflict on the merge pass needs no record from you
82
+
83
+ A `rebase-fallback-#<pr>`
84
+ on a conflict needs no record from you — the bot already appended
85
+ `conflict-hold:#<pr>` to that PR's row, and the tick turns it into `DISPATCH
86
+ fix-pr PR#<pr>` (**The fix-applier**).
87
+
81
88
  ## Never reap a branch a live member is on
82
89
 
83
90
  Cross-check `.fleet/ledger.md` before running: row without terminal state means someone may still be in that worktree — merged PR can still have reviewer filing follow-ups. Dirty check does not see member that committed but not pushed. Update reaped tickets' ledger rows in same step, report reaped and kept counts. Kept-with-reason is half worth reading.