@feigi/fleet-ctl 3.82.14 → 3.82.15
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.
|
@@ -15,7 +15,7 @@ argument-hint: [pr-number]
|
|
|
15
15
|
3. **Run `checkCmd` and `testCmd` before you commit**, from the worktree, each copied verbatim — not a runner you picked. `checkCmd` is the Recipe's Check step and `testCmd` is the command your dispatcher handed you; **standalone, with no dispatcher to hand you them, they are the repo's own Check step and Test entrypoint**, read off the repo's Recipe cache (`derive-testcmd.sh <repo> check` prints the first, `derive-testcmd.sh <repo> test` the second). Read them off the repo, never invent one: "not a runner you picked" is what this step forbids, and on the standalone path nothing else defines the referent. With no usable Recipe cache (`derive-testcmd.sh` refuses), run the Recipe derivation step first — dispatch a `task` member, agent `fleet-recipe-deriver`, its prompt naming the main checkout's absolute path as `<repo>` and a scratch directory of its own; it proves all three commands in a throwaway worktree and writes the cache only on proof, and its `RECIPE NOT PROVEN` is a stop, reported with its reason. Never fall back to a runner of your own choosing. `tests 0` is a FAILED run, not a pass: a glob matching nothing still exits 0. **Zero is not the only no-work count.** A run reporting **0 passes with no failures** skipped everything it collected, and a count **materially below the whole suite** means you ran a partial tree — this suite reported `tests 937` when this line was written, so compare what you get against a full run rather than against zero. The number drifts; that is the safe direction, because a stale one produces a loud false red while its absence produces a silent false green. Red, zero-test, or either no-work count → fix it or move the finding to defer; never commit over it. Then commit and push — `~/.fleet/bin/fleet-run push-sync.mjs --branch <branch>`, which pushes the explicit refspec `HEAD:refs/heads/<branch>` and then moves the worktree holding `<branch>` to the pushed SHA, or names why it did not and exits 3 with the push landed: this step also runs from a `--detach` worktree (`run-team/SKILL.md`'s worktree-recreation form), where a bare `git push` exits 128 (`fatal: You are not currently on a branch`), any push whose destination is `HEAD` rather than a full or branch refname (`-u` or not, forced or not) exits 1 (`error: The destination you provided is not a full refname`), and a refspec push alone leaves the claim worktree's `refs/heads/<branch>` at its old tip — and report the test result alongside the SHA. **Then reconcile the PR body with what you actually did.** It merges into the permanent record and nothing downstream re-derives it: your commit messages are yours to fix and usually get fixed, the body is neither. Removing a change, or reversing one, leaves the body asserting work the diff no longer contains. Measured twice in one run — one body kept asserting a calibration its own fix had just deleted as false, another kept claiming a hunk the fix had removed as "this diff owns it" — both caught by a finisher's reading rather than by any rule, and both would otherwise have merged intact. `gh pr edit <pr> --body-file <path>`, and re-read the body against the final diff rather than against your memory of it. **Never `--no-verify` on a commit you push, or on the push itself.** The fleet runs in repos it knows nothing about: it must not assume a pre-commit hook exists, and must never bypass one that does on what reaches the branch — a hook that fails is a finding to report, not an obstacle. Nothing re-reviews what you commit here, so this run is the only gate between your edit and a merge.
|
|
16
16
|
**Under the fleet, run the two through `recipe-check.mjs`, not one by one:** from the worktree, after `git add` and before the commit, `~/.fleet/bin/fleet-run recipe-check.mjs --member <your name> --testCmd "<testCmd>" --reports <scratch>/reports` — `<your name>` the `fix-pr-<pr>[-x]` you were dispatched as, and `<testCmd>` the whole command you were handed, one quoted argument. It runs the Recipe's Check step, which is `checkCmd`, then `<testCmd>` exactly as handed, against the staged tree, and records both results beside your report. The record is keyed to the tree, so run it again after any content change or rebase — a message-only amend keeps it valid — and the controller refuses an `applied` report whose head's tree has no green record. A non-zero exit is red; exit 75 is no test result — no slot was free and nothing ran — so run it again. **Standalone, with no report to settle against,** run `checkCmd` and `testCmd` yourself, each copied verbatim.
|
|
17
17
|
4. **Under the fleet, do not hold this wait.** The controller owns a persistent CI Monitor and dispatches a finisher once your report has arrived and CI is green — so push, then report to the controller a **final verdict** (every specialist you dispatched + its outcome + your apply/defer adjudication + how many refuters you dispatched against out-of-scope suggestions + the pushed SHA) and stop. A specialist that has not reported to you is not a verdict line — `hub send` it by name for its report, then ask the controller, before ruling; see **Specialists**. The controller keys "review complete" on that verdict, **never on you merely going idle** — a slow specialist can still be running when your turn ends, and finishing off idle-alone ships a premature label (observed: a PR labelled while its contract-test specialist was still running and about to surface a vacuous pin). A turn-based reviewer re-reading `gh pr checks` each idle cycle rebuilds its whole context for nothing the Monitor lacks. **Standalone (no controller): watch** the run until the diff-validating jobs are green (bind via `ci-state.mjs` below); fix a **genuine** failure and repeat from 3. **Staleness is not a failure:** a `rebase-check` red or heavy jobs `skipped` **solely from a non-zero behind-count** never clears by waiting and must not be rebased away here — see **`skipped` is not `passed`** and **Do NOT rebase to label** below for what it looks like and why. Stop watching and go to 6. A turn-based reviewer that "waits for green" on a behind PR stalls forever and ends its turn without labelling. **`ci-state.mjs` returns `verdict: "no-ci"`** → there is no run to watch, so don't: go straight to 6 and let that step's `--declare-no-ci` branch decide.
|
|
18
|
-
5. File each deferred finding: `gh issue create --label needs-triage` with the finding, `file:line`, why deferred, `Deferred from PR #<pr> review`, and **the finding's `dimension` in the issue body** — the review returns it on every finding, and a backlog that does not carry it cannot be attributed to the specialist that generated it. **The body also carries the evidence triage needs: the refuter's verdict — the `verdictPath` reason, or the review's votes — and the candidate remedy.** **Every issue you create is labelled `needs-triage`; only triage sets `ready-for-agent`** — and the label is never omitted; an unlabelled issue is invisible to both `candidates.mjs --require-label` and the cockpit's pool query, reachable only by a human. **A confirmed defect does not settle its remedy**, so confirming one earns it no other label: a confirmed defect with the remedy still open files `needs-triage`, and so does a finding you are unsure is broken at all. **A finding a refuter CHECKED and did not survive is neither** — it is not a confirmed defect, and you are not unsure whether anything is broken: you measured that nothing is. Refutations go as **one issue per review, titled `PR #<pr> review: the suggestion band, checked`, labelled `wontfix`, and CLOSED** — a run that files them open instead has to be corrected afterwards, as one was on 2026-08-27. Left open they read as queued agent work and rot into filing artifacts; a measured triage pass found a quarter of its `needs-triage` backlog was refuted records, bulk-closable by title shape. Closed is still a durable home — `check` reads the tracker at `--state all`, so a future run re-deriving the finding lands on the evidence rather than re-litigating it. Split the band only where an entry is **not** a refutation: a residual the refuter surfaced, or an out-of-scope finding its refuter let survive or never decided, is real deferred work — and real deferred work earns its own open issue only past a second bar. **Worth a claim: a finding whose own claim is that correct code could be shaped better — style, naming, layout, redundancy, a micro-simplification — never gets its own open issue.** **Neither does a gap in a prose guard — a test holding prose to its wording — unless something acts on the guarded text: an agent follows it as an instruction (a skill, a command, an agent definition, a script's header or usage text, `AGENTS.md`), or a program other than its own guard reads it.** Whether anything does is your judgement, as `claimKind` is, and a gap below this bar is recorded `claimKind: shape`. A gap in a guard's own code — a slow pattern, a dead branch, an unchecked spawn result — takes the stakes of the prose it guards, and a helper several guards share takes the highest of theirs. A missed wording already in the tree is no guard gap — that prose is wrong — and neither is an input already in the tree that trips a guard's code; each files on its own merits. Once the prose is corrected, the observed wording may join the guard's literal ban, never a reader built to catch wordings nobody has written. A below-bar finding's only open question is "is this worth doing?", which answers itself under the repriced triage bar: measured 2026-08-15→31, 133 issues were triage-closed `wontfix` in sixteen days, 121 of them review deferrals, mostly this shape. Append it to the same closed record issue under a `Below the claim bar` heading — `file:line`, dimension, the claim, and a re-open trigger per entry. Closed is a durable home for these too: `check` reads `--state all`, so a run re-deriving the finding lands on the record instead of re-filing — and a later review returning the same finding as `survived`, or a maintainer hitting it, is the promotion signal: file it open then, citing the record. **An in-scope rediscovery step 2 applied is that same promotion signal, and it files nothing** — so the record carries no mark unless you write one: run `check` over each finding step 2 applied as well, and where its tracker rows put one on a `Below the claim bar` entry, comment on that record issue, opening `Promoted — applied, not filed`, and name the entry, that its refuter let it through, and the commit carrying the fix. That comment is the promotion mark this path leaves, and it is **not** a cross-reference — measured on this tracker, a comment mints `cross-referenced` on the issue its own text *names*, never on the issue it sits under (a comment citing a second issue landed on that issue's timeline and nowhere else), so the fixed opening is what carries the signal instead. **The bar reads the finding's claim, never its review band** — a finding that alleges wrong behavior, whatever band it sat in, `unverified`-with-crashed-refuters included, is above this bar and files open, `needs-triage`, which is why a band cannot carry this decision. Do **not** reach for phase 0's **decided?** test here. That is a *consumption* gate, and whether a filed issue is decided enough to reach it is triage's call, never the filer's. **The cost of this rule is watched:** open `needs-triage` issues citing `Deferred from PR` above 40 on 3 consecutive run dates send the filing rule back to the maintainer — batch triage, a fleet triage role, or a different bar. **Dedupe through `~/.fleet/bin/fleet-run ledger.mjs check "<subject>"`, never a bare `gh issue list --search`** — the duplicate class that guard exists to catch was filed from *here*: the ledger row for the finding rediscovered five times in one measured run carries a reviewer's own source tag, `(review-pr-108)`. `check` reads this run's filed list *and* the tracker at `--state all`, so a closed duplicate still counts. Exit **1** is already filed in this run; exit **3** is a clean ledger with matching tracker rows that score against the subject — read the ones it prints and comment on the existing follow-up rather than filing a second issue. **Exit 0 is not automatically "safe to file"**: it covers `clean`, `soft-hit` and `unverified` alike, and an unreachable `gh` degrades to a ledger-only answer that says `TRACKER NOT CHECKED`, so read the stdout `verdict` to tell a tracker that was searched and clean from one nobody read — under `unverified`, search it yourself before filing — or, for the applied-promotion comment above, before treating a tracker nobody could read as a reason to skip it. **`soft-hit` is the answer that costs most to misread**: it means related rows were found and none of them was established as a duplicate — a near-miss above the floor, or tracker rows the scorer rates 0.00 — so read the rows it printed and decide, and never treat that exit 0 as permission. Exit **2** is a usage error, meaning nothing was checked at all: fix the invocation and re-run, never read it as a duplicate. Full exit-code and `verdict` semantics live in `$(~/.fleet/bin/fleet-run --root)/skills/run-team/references/ledger.md`, which `SKILL.md`'s
|
|
18
|
+
5. File each deferred finding: `gh issue create --label needs-triage` with the finding, `file:line`, why deferred, `Deferred from PR #<pr> review`, and **the finding's `dimension` in the issue body** — the review returns it on every finding, and a backlog that does not carry it cannot be attributed to the specialist that generated it. **The body also carries the evidence triage needs: the refuter's verdict — the `verdictPath` reason, or the review's votes — and the candidate remedy.** **Every issue you create is labelled `needs-triage`; only triage sets `ready-for-agent`** — and the label is never omitted; an unlabelled issue is invisible to both `candidates.mjs --require-label` and the cockpit's pool query, reachable only by a human. **A confirmed defect does not settle its remedy**, so confirming one earns it no other label: a confirmed defect with the remedy still open files `needs-triage`, and so does a finding you are unsure is broken at all. **A finding a refuter CHECKED and did not survive is neither** — it is not a confirmed defect, and you are not unsure whether anything is broken: you measured that nothing is. Refutations go as **one issue per review, titled `PR #<pr> review: the suggestion band, checked`, labelled `wontfix`, and CLOSED** — a run that files them open instead has to be corrected afterwards, as one was on 2026-08-27. Left open they read as queued agent work and rot into filing artifacts; a measured triage pass found a quarter of its `needs-triage` backlog was refuted records, bulk-closable by title shape. Closed is still a durable home — `check` reads the tracker at `--state all`, so a future run re-deriving the finding lands on the evidence rather than re-litigating it. Split the band only where an entry is **not** a refutation: a residual the refuter surfaced, or an out-of-scope finding its refuter let survive or never decided, is real deferred work — and real deferred work earns its own open issue only past a second bar. **Worth a claim: a finding whose own claim is that correct code could be shaped better — style, naming, layout, redundancy, a micro-simplification — never gets its own open issue.** **Neither does a gap in a prose guard — a test holding prose to its wording — unless something acts on the guarded text: an agent follows it as an instruction (a skill, a command, an agent definition, a script's header or usage text, `AGENTS.md`), or a program other than its own guard reads it.** Whether anything does is your judgement, as `claimKind` is, and a gap below this bar is recorded `claimKind: shape`. A gap in a guard's own code — a slow pattern, a dead branch, an unchecked spawn result — takes the stakes of the prose it guards, and a helper several guards share takes the highest of theirs. A missed wording already in the tree is no guard gap — that prose is wrong — and neither is an input already in the tree that trips a guard's code; each files on its own merits. Once the prose is corrected, the observed wording may join the guard's literal ban, never a reader built to catch wordings nobody has written. A below-bar finding's only open question is "is this worth doing?", which answers itself under the repriced triage bar: measured 2026-08-15→31, 133 issues were triage-closed `wontfix` in sixteen days, 121 of them review deferrals, mostly this shape. Append it to the same closed record issue under a `Below the claim bar` heading — `file:line`, dimension, the claim, and a re-open trigger per entry. Closed is a durable home for these too: `check` reads `--state all`, so a run re-deriving the finding lands on the record instead of re-filing — and a later review returning the same finding as `survived`, or a maintainer hitting it, is the promotion signal: file it open then, citing the record. **An in-scope rediscovery step 2 applied is that same promotion signal, and it files nothing** — so the record carries no mark unless you write one: run `check` over each finding step 2 applied as well, and where its tracker rows put one on a `Below the claim bar` entry, comment on that record issue, opening `Promoted — applied, not filed`, and name the entry, that its refuter let it through, and the commit carrying the fix. That comment is the promotion mark this path leaves, and it is **not** a cross-reference — measured on this tracker, a comment mints `cross-referenced` on the issue its own text *names*, never on the issue it sits under (a comment citing a second issue landed on that issue's timeline and nowhere else), so the fixed opening is what carries the signal instead. **The bar reads the finding's claim, never its review band** — a finding that alleges wrong behavior, whatever band it sat in, `unverified`-with-crashed-refuters included, is above this bar and files open, `needs-triage`, which is why a band cannot carry this decision. Do **not** reach for phase 0's **decided?** test here. That is a *consumption* gate, and whether a filed issue is decided enough to reach it is triage's call, never the filer's. **The cost of this rule is watched:** open `needs-triage` issues citing `Deferred from PR` above 40 on 3 consecutive run dates send the filing rule back to the maintainer — batch triage, a fleet triage role, or a different bar. **Dedupe through `~/.fleet/bin/fleet-run ledger.mjs check "<subject>"`, never a bare `gh issue list --search`** — the duplicate class that guard exists to catch was filed from *here*: the ledger row for the finding rediscovered five times in one measured run carries a reviewer's own source tag, `(review-pr-108)`. `check` reads this run's filed list *and* the tracker at `--state all`, so a closed duplicate still counts. Exit **1** is already filed in this run; exit **3** is a clean ledger with matching tracker rows that score against the subject — read the ones it prints and comment on the existing follow-up rather than filing a second issue. **Exit 0 is not automatically "safe to file"**: it covers `clean`, `soft-hit` and `unverified` alike, and an unreachable `gh` degrades to a ledger-only answer that says `TRACKER NOT CHECKED`, so read the stdout `verdict` to tell a tracker that was searched and clean from one nobody read — under `unverified`, search it yourself before filing — or, for the applied-promotion comment above, before treating a tracker nobody could read as a reason to skip it. **`soft-hit` is the answer that costs most to misread**: it means related rows were found and none of them was established as a duplicate — a near-miss above the floor, or tracker rows the scorer rates 0.00 — so read the rows it printed and decide, and never treat that exit 0 as permission. Exit **2** is a usage error, meaning nothing was checked at all: fix the invocation and re-run, never read it as a duplicate. Full exit-code and `verdict` semantics live in `$(~/.fleet/bin/fleet-run --root)/skills/run-team/references/ledger.md`, which `SKILL.md`'s wake section points at — read them there rather than restating them here. **The guard dedupes, it never drops:** a duplicate becomes a comment on the issue that already covers it, never a dropped finding. **Record every issue you create, at once:** immediately after each `gh issue create`, run `~/.fleet/bin/fleet-run ledger.mjs filed <N> "<subject>"` — `<N>` the number `gh issue create` printed, and **the same subject string you passed to `check`**, so the next `check` for that finding meets it in the filed list instead of scoring a rewording. Without it the guard's ledger half never holds a row, and its exit **1** can never fire. If `filed` fails, retry it once; if it fails again, leave the issue alone — it exists and is correct — and report it as `unrecorded: #N <subject>`, so the controller runs `filed` for it. Report `filed: #N <subject>` for every issue you created — under the fleet, in your report to the controller. **The one window the guard cannot see:** `check` → `gh issue create` → `filed` is three commands, and the ledger's lock covers each alone, so two filers can both read `clean` in the seconds before either records. That gap is accepted, never a reason to skip `filed`. Post the numbers as one PR comment.
|
|
19
19
|
**A duplicate that should hold an open deferral is a comment, never a relabel.** Where the guard lands a finding whose home is an open issue on an existing one that is open, cite the refuters' verdict — or the review's votes — in the comment you leave there, and change no label. Never relabel `ready-for-agent` down to `needs-triage`: a `ready-for-agent` issue already holds a `needs-triage` finding, because triage already promoted it, and a later run's crashed refuters are no reason to send it back. A closed one cannot hold it: file a new open issue citing it, as the promotion rule above does for the closed record.
|
|
20
20
|
**Name where each deferral went, in the disposition record.** Every entry you defer carries `issue`: the number of the issue you filed it to, commented it onto, or appended it to — the closed `PR #<pr> review: the suggestion band, checked` record included. A `suggestion`'s entry — an `unverified` finding with `refutersDispatched` of zero — also carries `verdictPath`, applied or deferred, in scope and, above the claim bar, out of scope: the absolute path of a file holding its refuter's verdict as JSON, `{"refuted": <boolean>, "reason": "<string>"}`, under your own `<scratch>/pr<N>/fix-XXXXXXXX/<finding>/` run root. An out-of-scope one whose refuter crashed before any verdict reached you still names the path its verdict would have taken there; out of scope, that file missing or holding no decision files the finding open `needs-triage`, while in scope it is a mismatch. `dispositions-check.mjs` reads each named issue back through `gh issue view --json labels,state,title` and each verdict file off disk; a mismatch names the row of its filing table the entry broke, so move or relabel that filing to the home the row names and run the check again. With `gh` unreachable, or an issue it cannot read, that filing goes unchecked: with nothing else broken the verdict is `unchecked` — never `ok`, and it exits 1 — so run the check again once `gh` answers.
|
|
21
21
|
6. Diff-check green (run-bound per `ci-state.mjs`; under staleness see **`skipped` is not `passed`** for what the label then rests on) **and** deferrals filed **and** the PR carrying exactly one release label (`patch`/`minor`/`major`, counting only those three; **required on every PR** — `release-label.yml` auto-adds `patch` when none is present and hard-fails on more than one, so this never needs to check whether the repo defines the gate at all, and `gh label list` settles nothing here any more) **and** `dispositions-check.mjs --member fix-pr-<pr> --scratch <scratch>` run over your own record exiting 0 (standalone that is yours to run, from the worktree, after step 2 writes `<scratch>/dispositions-fix-pr-<pr>.json`; `--review <path>` names the review file, which you write yourself from the findings step 1 collected, in the shape **The review result file** describes — `pr`, `head`, `snapshot`, `counts` and the `survived`, `refuted` and `unverified` buckets; standalone you pass `--no-ledger` with it, so it writes no token and the exit status is the verdict — a `.fleet/ledger.md` the repository already holds is a previous run's, and without the flag it exits 2 for a ledger with no row for you; under the fleet `ledger.mjs dispatch` has already refused a finisher without it) → `gh pr comment <pr> --body "ready-to-merge-head: <prHead>"`, `prHead` being the full SHA your green `ci-state.mjs` reading reported, which the merge bot reads as the head the label was applied to, then `gh pr edit <pr> --add-label ready-to-merge`. Do not merge. A non-zero dispositions exit halts you before the label, and you report the violations it printed. Zero or more than one release label halts you before the label, naming which you found — a timed-out `gh pr create` opens the PR with its `--label` unapplied and no exit status to react to, and nothing after this step re-derives the release label. **Under the fleet a finisher does this** — a fresh small agent the controller dispatches (after your final verdict, never on your bare idle). Gate it on the diff-validating **`check`** job, **not** on `ci-state --quiet` exit 0, which a behind PR — the normal case once any sibling merges — never reaches: a `rebase-check` red or heavy jobs `skipped` off the behind-count hold it short of full green, so an exit-0 gate strands every behind PR unlabelled. Label when `check` is present and green **and no heavy job (the diff-validating suites — *not* the `rebase-check` currency gate) is in `failure`** — a `skipped` heavy job is behind-count staleness and fine — **solely** off that count, which is a condition to establish rather than infer, see the five-condition note in `$(~/.fleet/bin/fleet-run --root)/skills/run-team/SKILL.md`'s fix-applier prompt block. A `failure` is the diff's own fault and blocks the label, else a genuinely broken PR gets labelled off a green `check` and burns a merge-bot rebase+CI cycle before the failure resurfaces. Read per-job state for this: run `ci-state.mjs` **without** `--quiet`, or read its `jobs` — the `--quiet` payload drops the per-job `jobs`/`missing` and the `dropped` list, keeping `reasons` (which still distinguishes `is failure` from `is skipped`). You reach step 6 yourself only when green is already in hand at push time.
|
package/package.json
CHANGED
package/skills/run-team/SKILL.md
CHANGED
|
@@ -185,7 +185,7 @@ it prints. There are no hand-run
|
|
|
185
185
|
refill or dispatch edges left. Record first, because the tick reads the run off
|
|
186
186
|
`.fleet/ledger.md` and nothing else of yours: a wake you have not written down is
|
|
187
187
|
one it cannot see, and it names the same dispatch again. Every record goes
|
|
188
|
-
through `ledger.mjs` (
|
|
188
|
+
through `ledger.mjs` (references/ledger.md), which `fleet-event.mjs` drives — never a
|
|
189
189
|
hand edit. What each kind writes is in the header comment of `fleet-event.mjs`.
|
|
190
190
|
See references/ci-and-staleness.md.
|
|
191
191
|
|
|
@@ -283,9 +283,22 @@ owes beyond its call:
|
|
|
283
283
|
context exhaustion caused (references/close-out.md).
|
|
284
284
|
- **The finisher-gate wakes** — a CI run reaching a terminal state, a fix-applier reporting `no-op`, a `no-ci` verdict → each is a bullet of **The finisher gate**.
|
|
285
285
|
|
|
286
|
+
Write each state change through `ledger.mjs` before you act on it, never by hand: a wake is done when no row, filing or ruling lives only in your context.
|
|
287
|
+
|
|
288
|
+
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.
|
|
289
|
+
|
|
290
|
+
Token additions go through `ledger.mjs token`. `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row. Re-typing the row with `row` replaces the whole line, so it drops the tokens you do not restate. Write member tokens and `review=` tokens only through `ledger.mjs`, never by hand.
|
|
291
|
+
|
|
292
|
+
Never rotate the ledger mid-run: it is phase 0's step alone.
|
|
293
|
+
|
|
294
|
+
Record each ruling with `ledger.mjs ruled <pr> <decision>`, one line with its reason. Gate on the `ledger.ok` field of `check`, not on the stderr warning. The writer policy, the gap the lock does not close, and `check`'s exits: See references/ledger.md.
|
|
295
|
+
|
|
296
|
+
**Write the ledger line before dispatching, not after.** Record the member with `ledger.mjs dispatch`, or `review` for a review, before its `task` call.
|
|
297
|
+
|
|
286
298
|
### Dispatching the members a wake names
|
|
287
299
|
|
|
288
300
|
- **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. `scratch` is the scratch root itself, never a `pr<N>` partition (`references/reviewers.md`). Write the `review=` token with `ledger.mjs review <pr> <token>`; `ledger.mjs` refuses a bad grammar. The tick names every PR owed one, oldest first, and each gets one review, once per PR. The tick counts in-flight reviews off the ledger and nowhere else, so a review you launched and never recorded is one it names again. The runner writes the result file and reports its path and `reviewed=` token.
|
|
301
|
+
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.
|
|
289
302
|
- **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. It holds every finding and you hold none, so there is nothing for you to rank, relay or pre-rule — and a report from a refuter it spawns that surfaces to you is its to retrieve, never yours to scan or pass on. It answers the hold and nothing else: survived or unverified findings of a returned review the row also carries stay unanswered through its settle, so the tick prints `DISPATCH fix-pr PR#<M>` again once it lands, and that one is a review fix-applier, dispatched as above. On a PR carrying `ready-to-merge`, the push moves the head after the label, so the next merge bot refuses it `head-moved-after-label-#<M>`: a fresh finisher, per **Failure handling**.
|
|
290
303
|
- **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.
|
|
291
304
|
- **Then dispatch a red-check fixer** — on a `check`-job `failure` bound to the PR's current head that is not **solely** staleness: a `rebase-check` red, or heavy jobs `skipped`, that comes only off the behind-count gets no fixer (the five-condition note in `agents/fleet-fix-applier.agent.md` establishes that). 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. 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 — the red-check fixer does not fire: their push starts a new run, and a fixer racing them would work on a SHA that is still moving. The common case, the implementer's own first CI firing turning red before any review exists, still dispatches the review against that head as normal. The tick prints no `DISPATCH` for it, so you reconcile it yourself, and it always goes after the review, never before. It is told outright not to watch, poll or wait for CI: your persistent Monitor owns the next run on the pushed head, and the finisher gate — review returned AND fix-applier report sent — applies to this member the same way. Settle it `applied:<head>` once a fix is pushed. Settle it `no-op` only when the red turns out not to be this member's to fix — a flake that passes on rerun, or staleness — with nothing pushed; `no-op` counts as landed, so it never stands for "could not fix". Settle it `failed` when it cannot reach green, which the **Failure handling** row "Reviewer, fix-applier or red-check fixer cannot reach green" covers. Its three dispatch edges are under **The finisher gate**; their rationale is in `references/member-lifecycle.md` (The red-check fixer's three edges).
|
|
@@ -824,7 +837,7 @@ merge. `review-and-fix.md` states them; the body says they apply.
|
|
|
824
837
|
|
|
825
838
|
**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
|
|
826
839
|
(`ledger.mjs token <PR number> label-off=<latest finisher attempt>`, which keeps
|
|
827
|
-
every token the row already has
|
|
840
|
+
every token the row already has, see references/ledger.md), then
|
|
828
841
|
remove `ready-to-merge`, then approve. Then
|
|
829
842
|
dispatch a **fresh** finisher against the new head; the first audit does not
|
|
830
843
|
transfer, since it verified a different tree. The merge bot refuses that head on
|
|
@@ -1508,213 +1521,6 @@ hold the old text — re-brief only if it changes what they do *now*.
|
|
|
1508
1521
|
|
|
1509
1522
|
Plan it as a PR the run's own merge queue lands, never as an edit-and-continue. See references/merge-bot.md.
|
|
1510
1523
|
|
|
1511
|
-
## Run ledger
|
|
1512
|
-
|
|
1513
|
-
Write each state change through `ledger.mjs` before you act on it, never by hand: this section is done when no row, filing or ruling lives only in your context.
|
|
1514
|
-
|
|
1515
|
-
One git-ignored `.fleet/ledger.md` per run, updated at **every** state change via
|
|
1516
|
-
`~/.fleet/bin/fleet-run ledger.mjs` subcommands (`row`, `filed`, `ruled`,
|
|
1517
|
-
`check`, `read`, `dispatch`, `settle`, `review`, `drain`, `rotate`). Your context is the least
|
|
1518
|
-
durable thing in the run: it compacts, and a controller that loses the dispatch
|
|
1519
|
-
map or the filed list redoes finished work. Two duplicate tickets shipped in one
|
|
1520
|
-
run from exactly that.
|
|
1521
|
-
Compaction parks every in-flight member and nothing reports it, so on resume
|
|
1522
|
-
take the registry census first (**Member unaccounted**, in **Failure
|
|
1523
|
-
handling**) before you trust a live token.
|
|
1524
|
-
|
|
1525
|
-
**The file lives one run.** Phase 0 rotates it before anything reads it, so
|
|
1526
|
-
no exclusion, filing, ruling or drain marker outlives the run that wrote it,
|
|
1527
|
-
and no row does either — except, when the previous run was this session's
|
|
1528
|
-
own, a row still carrying a live token, which rotate carries forward with
|
|
1529
|
-
its live `## Dispatched` entries (phase 0). Never rotate mid-run: it is
|
|
1530
|
-
phase 0's step alone.
|
|
1531
|
-
|
|
1532
|
-
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`; `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row):
|
|
1533
|
-
|
|
1534
|
-
```
|
|
1535
|
-
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
1536
|
-
#<t2> impl-<t2>=PR#<p2> → PR#<p2> · fix-pr-<p2> · ports=16<t2> · ruled:6-applies · held-behind:#<t3>
|
|
1537
|
-
#<t3> impl-<t3> · tier=task-high
|
|
1538
|
-
#<t4> excluded · behind-pr:#<p2>
|
|
1539
|
-
#<t5> excluded · behind-issue:#<t3>
|
|
1540
|
-
```
|
|
1541
|
-
|
|
1542
|
-
**A member's token is `<member>` while it is live and `<member>=<outcome>` once
|
|
1543
|
-
it settles** — the grammar is `ledger-grammar.mjs`'s, and the tick derives every
|
|
1544
|
-
live count from it, so nobody states one. Write neither by hand:
|
|
1545
|
-
|
|
1546
|
-
- **`ledger.mjs dispatch <ticket|pr> <member>`** — before the dispatch call,
|
|
1547
|
-
marks the member live on its row and appends it to `## Dispatched` with
|
|
1548
|
-
`brief=<first 8 hex of its brief's sha256>`. `dispatch
|
|
1549
|
-
merge-bot` names the next bot itself. It prints `{member, agent, ticket,
|
|
1550
|
-
line, created, total, brief}`, `brief` the `{path, sha256}` of the rendered
|
|
1551
|
-
brief the `task` call is handed; `agent` is the definition the `task` call names — an
|
|
1552
|
-
implementer's comes off its row's `tier=` (the tier check under **Dispatching the Pull's member** states the
|
|
1553
|
-
mapping), a finisher's and a merge bot's are their own definitions, and a
|
|
1554
|
-
fix-applier's is `fleet-fix-applier` for every kind of work. It
|
|
1555
|
-
refuses a member whose `briefs/<member>.json` beside the ledger is missing,
|
|
1556
|
-
names another role or carries no sha256, a brief file beside it that is missing or does not hash to that sha256, a name this session already used — on the ledger or in its
|
|
1557
|
-
`## Retired` — and names the replacement (`-b`, `-c` …), a
|
|
1558
|
-
second live member on one ticket or PR, any implementer once the run is
|
|
1559
|
-
draining, an implementer whose row's `tier=` names no single definition
|
|
1560
|
-
or one with no file under `agents/`, and a finisher on a ledger the tick
|
|
1561
|
-
itself refuses.
|
|
1562
|
-
- **`ledger.mjs settle <member> <outcome>`** — rewrites the token to
|
|
1563
|
-
`<member>=<outcome>` in both places. Outcomes: `impl-N` = `PR#M | bailed |
|
|
1564
|
-
released | killed | tier-mismatch | blocked`; `fix-pr-M` = `applied:<head> | no-op |
|
|
1565
|
-
failed | killed`; `finisher-pr-M` = `labelled | failed | killed |
|
|
1566
|
-
halted:<cause>`, `<cause>` one of `live-editor | rebase | past-pin |
|
|
1567
|
-
unreadable | missing | absent | other` (**Resolving a finisher halt**);
|
|
1568
|
-
`merge-bot-n` = `done | killed`. A settled member stays settled. `settle
|
|
1569
|
-
impl-N=PR#M` also folds a `#M` row a PR-bound dispatch or a `review` made
|
|
1570
|
-
before the settle into `#N`'s row, so the PR's row is the one row naming
|
|
1571
|
-
each PR from then on — but only when `#N`'s row named no PR before this
|
|
1572
|
-
write (a second `PR#` on it would misattribute the fold, or bury `#N`'s own
|
|
1573
|
-
fresher `review=` state under an older one) and no implementer claims `#M`'s
|
|
1574
|
-
row as its own. `settle impl-N=PR#M` refuses a settle after which a PR maps
|
|
1575
|
-
to more than one row when it did not before, judged after the fold — a PR
|
|
1576
|
-
has one row, the PR's row; a split already on disk is carried.
|
|
1577
|
-
- **`ledger.mjs settle <member> --from-report [--reports <dir>]`** — the
|
|
1578
|
-
settle for a member that wrote a report: reads `<dir>/<member>.json`
|
|
1579
|
-
(`<dir>` defaults to `<scratch>/reports`), derives the outcome from it and,
|
|
1580
|
-
before writing, checks what that outcome claims — `PR#M`: `verify-sha.sh`
|
|
1581
|
-
on the report's branch and head, and PR M OPEN and closing N; `bailed`: a
|
|
1582
|
-
`bailCause`, which the receipt names; `applied:<head>`: origin's branch at
|
|
1583
|
-
that head; `no-op`: origin's branch at the head the PR's latest
|
|
1584
|
-
`reviewed=` names; `labelled`: `ready-to-merge` on the PR and in the
|
|
1585
|
-
read-back; `halted:<cause>`: a cause and non-empty evidence; `done`: every
|
|
1586
|
-
PR the bot merged reads MERGED. `PR#M` and `applied:<head>` also need a
|
|
1587
|
-
green Check-step record (`SETTLE`). It then records each `unrecorded` filing in
|
|
1588
|
-
**filed** and, for a fix-applier, each refutation the report defers in
|
|
1589
|
-
**ruled**, once however often it runs, and prints `{member, outcome,
|
|
1590
|
-
checks, filed, ruled}`. Exit 3: a check failed — the member's claim and the
|
|
1591
|
-
world disagree — and nothing is written; you rule. Exit 2: the report is
|
|
1592
|
-
absent or invalid, or a check could not look, and nothing is written; a
|
|
1593
|
-
check that could not look is run again once `gh` and `git` answer. The bare
|
|
1594
|
-
form above, for an outcome a report carries, warns `settled without
|
|
1595
|
-
--from-report` on stderr.
|
|
1596
|
-
- **`ledger.mjs review <pr> <token>…`** — the only writer of `review=` and
|
|
1597
|
-
`reviewed=` (below): appends each to the PR's row — the ticket row once
|
|
1598
|
-
`settle impl-<N>=PR#M` has run, else `#M`, created when neither exists —
|
|
1599
|
-
and prints `{pr, row, line, created, next}`, `row` the key written and
|
|
1600
|
-
`next` the runner name the PR's next launch takes, null with every letter
|
|
1601
|
-
taken. A
|
|
1602
|
-
`review=<launch>=failed` whose `review=<launch>` the row holds settles that
|
|
1603
|
-
launch in place, alone or with the next launch in the same call. Any other
|
|
1604
|
-
token is refused.
|
|
1605
|
-
- **`ledger.mjs drain "<reason>"`** — at most one standing drain marker; supply
|
|
1606
|
-
stops, the review and merge sides keep going. It lives in this run's
|
|
1607
|
-
ledger, so the next run's rotation retires it with everything else.
|
|
1608
|
-
- **`ledger.mjs undrain "<reason>"`** — clears the drain marker and refuses when
|
|
1609
|
-
no marker is set. `closeout.mjs reopen` runs it.
|
|
1610
|
-
- **`ledger.mjs rotate`** — phase 0's first ledger write, every run: moves
|
|
1611
|
-
the file as-is to `ledger.<UTC YYYY-MM-DDTHHMMSSZ>.md` beside it under the
|
|
1612
|
-
same `<file>.lock`, and the next write starts the empty skeleton — unless
|
|
1613
|
-
the previous run was this session's own, when it first writes the rows and
|
|
1614
|
-
`## Dispatched` entries still live into a new ledger, and every other
|
|
1615
|
-
settled name, or name given an out-of-vocabulary outcome, into its
|
|
1616
|
-
`## Retired` (references/phase-0.md, "Rotate the ledger", says which). Refused
|
|
1617
|
-
while the recorded controller is alive and not this run's ancestor, ledger
|
|
1618
|
-
or none — or, with no record and a ledger to move, while the heartbeat mark
|
|
1619
|
-
is still beating, or a tick or ledger write is inside its window with no
|
|
1620
|
-
beat — or when the archive name already exists, the process
|
|
1621
|
-
table cannot be read, or `.fleet/heartbeat.json` exists but cannot be used
|
|
1622
|
-
(unreadable, not JSON, not an object, or a `controller` it cannot judge);
|
|
1623
|
-
with no ledger and no such refusal it moves nothing and exits 0. Then it
|
|
1624
|
-
records this run's controller in `.fleet/heartbeat.json`. The guard is
|
|
1625
|
-
stated in references/phase-0.md, "Rotate the ledger".
|
|
1626
|
-
|
|
1627
|
-
`row` refuses a malformed member token anywhere in its text — one whose outcome
|
|
1628
|
-
is outside the vocabulary above, e.g. `merge-bot-1=dispatched` — at exit 2,
|
|
1629
|
-
naming the token and writing nothing, because every reader would take it as a
|
|
1630
|
-
permanent settle. It refuses a write that adds a `review=`/`reviewed=` token
|
|
1631
|
-
(one the row already holds is carried), and one after which a PR maps to more
|
|
1632
|
-
than one row when it did not before — `row <M>` creating `#M` for a PR a ticket
|
|
1633
|
-
row maps, or a rewrite adding a `PR#M` mention to another row — naming the
|
|
1634
|
-
PR's row (`settle impl-N=PR#M` applies the same rule after its fold).
|
|
1635
|
-
|
|
1636
|
-
`→ PR#M` stays as the human-readable arrow; the tick reads only the `=`
|
|
1637
|
-
tokens. `tier=<cell>` marks an Exploration Pull's cell and `route=<REASON>` a router fallback (the dispatch's CELL line), and `excluded ·
|
|
1638
|
-
behind-pr:#M | behind-issue:#M` is an Exclusion (phase 1) — a ticket row like
|
|
1639
|
-
any other, never a section of its own.
|
|
1640
|
-
|
|
1641
|
-
A PR's review is not a member, so it carries its own pair of row tokens,
|
|
1642
|
-
written with `review` onto the PR's row (**The reviewer**): `review=wf:<runId>` |
|
|
1643
|
-
`review=member:<name>` at launch, `<name>` being
|
|
1644
|
-
`review-pr-<n>` for the PR's first runner and the next letter its rows do not
|
|
1645
|
-
carry yet (`review-pr-<n>-b`, `-c`, …) for every later one,
|
|
1646
|
-
settled dead as `…=failed`, then `reviewed=<head>:<survived>/<refuted>/<unverified>:<run>`
|
|
1647
|
-
when the result lands, `<run>` the review's `run-XXXXXXXX` run root under
|
|
1648
|
-
`<scratch>/pr<n>/`, which holds its `review.json`. A row with `review=` and no `reviewed=` is a review in
|
|
1649
|
-
flight, and the tick counts it as a live review unit unless gh reports its PR
|
|
1650
|
-
MERGED or CLOSED. `ci=<run-id>:<attempt>:<conclusion>`
|
|
1651
|
-
and `held-behind:#<lower>` are row tokens the same way, and so is
|
|
1652
|
-
`conflict-hold:#<pr>` — written by the merge bot itself (`run-merge-bot.md`
|
|
1653
|
-
step 1), or by you off the tick's `CONFLICT` line, naming the row's own PR. It
|
|
1654
|
-
is not an Exclusion: that gates a ticket's claim, this a PR's merge. `label-off=<finisher-pr-M[-x]>`
|
|
1655
|
-
is yours, written before you take `ready-to-merge` off PR M on purpose (taking
|
|
1656
|
-
it off before you approve a push, or clearing one left on a moved head),
|
|
1657
|
-
naming that PR's latest finisher attempt: the tick reads a missing label as a
|
|
1658
|
-
finisher's miss only after the last attempt a `label-off=` names, so without
|
|
1659
|
-
it your own removal reads as one. `row` and `token` refuse a `label-off=` naming no
|
|
1660
|
-
`finisher-pr` member that `## Dispatched` or some row already carries.
|
|
1661
|
-
|
|
1662
|
-
`review` holds a PR's review record to these rules, read across every row that
|
|
1663
|
-
maps to the PR — its settled `impl-<N>=PR#<M>` token, else its first `PR#`
|
|
1664
|
-
mention, else its key — in ledger order. Every `reviewed=` answers the nearest
|
|
1665
|
-
earlier launch that is neither answered nor `=failed`. A launch identity (a
|
|
1666
|
-
workflow's runId, or a runner's name, `=failed` aside) appears once, and no launch takes a runner name the
|
|
1667
|
-
ledger's `## Retired` holds — an earlier run of this session launched it —
|
|
1668
|
-
naming the next free reviewer name instead. No launch appears
|
|
1669
|
-
while an earlier one is open — unanswered and not `=failed`. Every
|
|
1670
|
-
`review=`/`reviewed=` parses. `row` holds the rest: a launch open before the
|
|
1671
|
-
write is still on one of the PR's rows after it, `=failed` allowed, and a
|
|
1672
|
-
member name appears once on the line, whatever its outcome. A write that adds
|
|
1673
|
-
a violation is refused, exit 2, nothing written; one already on the ledger and
|
|
1674
|
-
carried forward is not. A second launch refused for an open one waits for
|
|
1675
|
-
that one's `reviewed=`, or — once the Member-killed row confirms its runner
|
|
1676
|
-
dead — settles it `=failed` in the same `review` call as the new launch.
|
|
1677
|
-
|
|
1678
|
-
Plus two append-only lists:
|
|
1679
|
-
|
|
1680
|
-
- **filed** (`ledger.mjs filed <issue> <subject>`, checked with `ledger.mjs check
|
|
1681
|
-
<subject>` before every `gh issue create`) — so a finding already recorded
|
|
1682
|
-
this run is not filed twice. That is not a guarantee against duplicates at
|
|
1683
|
-
large; the tracker query below is what covers those. The FILER writes this
|
|
1684
|
-
one, not you: `review-and-fix.md` step 5 and finisher duty 2 each run `filed`
|
|
1685
|
-
immediately after their own `gh issue create`, with the subject they passed
|
|
1686
|
-
to `check`, and report `filed: #N <subject>`. A filer whose `filed` failed
|
|
1687
|
-
twice reports `unrecorded: #N <subject>` instead — an `unrecorded` entry of
|
|
1688
|
-
its report file — and `settle <member> --from-report` runs `filed` for it.
|
|
1689
|
-
- **ruled** (`ledger.mjs ruled <pr> <decision>`) — PR + decision + one-line reason,
|
|
1690
|
-
so a replacement controller does not re-litigate a settled call.
|
|
1691
|
-
|
|
1692
|
-
**Writer policy: the controller owns run state (`row`/`token`/`settle`/`dispatch`/`review`/`drain`/`ruled`); filers append filings with `filed`; all writes are serialized by `<file>.lock`.** See references/ledger.md.
|
|
1693
|
-
|
|
1694
|
-
**The suite slots wedge the same ways, and clear the same way.**
|
|
1695
|
-
`~/.fleet/suite-slots/<i>.lock` is built on the ledger's lock primitive, so an empty or
|
|
1696
|
-
unparseable `<i>.lock` and a leftover `<i>.lock.reap` are permanent, a zombie
|
|
1697
|
-
holder is transient, and a dead holder is taken over. A wait that expired with
|
|
1698
|
-
exit 75 names each wedged file — remove it by hand once no suite run is
|
|
1699
|
-
running, the way references/ledger.md removes `<file>.lock`.
|
|
1700
|
-
|
|
1701
|
-
**The gap the lock does not close:** `check` → `gh issue create` → `filed` is three commands, and the lock covers each one alone, so two filers can both read `clean` in the seconds before either records. See references/ledger.md.
|
|
1702
|
-
|
|
1703
|
-
`check` exits **0** clean, **1** already in this run's filed list, **2** usage error (no JSON on stdout), **3** the ledger is clean but open or closed tracker issues match — read those and decide. See references/ledger.md.
|
|
1704
|
-
|
|
1705
|
-
The ledger half reports its own readability as its own field, `ledger.ok`, the
|
|
1706
|
-
peer of `tracker.ok`: false when the file did not exist, and false too when the
|
|
1707
|
-
path led to something that did not parse as a ledger — which is what tells a
|
|
1708
|
-
ledger that was read and held nothing filed from one there was nothing to read. The absent case is the normal one for a run's first `check` — `.fleet/`
|
|
1709
|
-
is git-ignored and created lazily by the first write — so it stays `clean` at
|
|
1710
|
-
exit 0, and `ledger.ok` is the field that says why rather than the verdict
|
|
1711
|
-
moving. The stderr warning is deliberately narrower than the field: it fires on
|
|
1712
|
-
a path with no file, so a `--file` that landed on the wrong existing file is a
|
|
1713
|
-
silent `ok:false` with nothing on stderr. Gate on the field, not on the warning.
|
|
1714
|
-
|
|
1715
|
-
**Write the ledger line before dispatching, not after.** A member that dies
|
|
1716
|
-
between spawn and write is invisible — and members die in batches.
|
|
1717
|
-
|
|
1718
1524
|
## Failure handling
|
|
1719
1525
|
|
|
1720
1526
|
Match the failure to its row in the table below and run that response; where the row says report or flag, stop there.
|
|
@@ -1737,6 +1543,10 @@ Match the failure to its row in the table below and run that response; where the
|
|
|
1737
1543
|
| 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 |
|
|
1738
1544
|
| 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 |
|
|
1739
1545
|
| 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. Run it over every claimed ticket with no PR when the run ends or the maintainer drains, and on the spot for a claim abandoned mid-run (a bail before implementing, a collision found after the claim). **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 |
|
|
1546
|
+
| 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. |
|
|
1547
|
+
| `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. |
|
|
1548
|
+
| `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. |
|
|
1549
|
+
| A suite wait exits 75 naming a wedged `~/.fleet/suite-slots/<i>.lock` | Remove each named file by hand once no suite run is running, the way references/ledger.md removes `<file>.lock`, then run the suite again. |
|
|
1740
1550
|
|
|
1741
1551
|
### Recovery
|
|
1742
1552
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The run ledger: the evidence behind its rules
|
|
2
2
|
|
|
3
|
-
Evidence for the rules SKILL.md
|
|
3
|
+
Evidence for the rules SKILL.md's wake and dispatch sections state, keyed by the rule each justifies.
|
|
4
4
|
|
|
5
5
|
## Writer policy: every write is serialized by `<file>.lock`
|
|
6
6
|
|
|
@@ -55,3 +55,49 @@ them before filing. The stdout JSON names all of that in one field:
|
|
|
55
55
|
`unverified`. Only `already-filed` and `tracker-hit` carry a non-zero code, so
|
|
56
56
|
`verdict` is the only thing that tells a searched-and-clean tracker from one
|
|
57
57
|
that was never read and from one whose rows you have to read yourself.
|
|
58
|
+
|
|
59
|
+
## Why the ledger outlives your context
|
|
60
|
+
|
|
61
|
+
One git-ignored `.fleet/ledger.md` per run is updated at every state change through the `~/.fleet/bin/fleet-run ledger.mjs` subcommands (`row`, `filed`, `ruled`, `check`, `read`, `dispatch`, `settle`, `review`, `drain`, `rotate`). Your context is the least durable thing in the run: it compacts, and a controller that loses the dispatch map or the filed list redoes finished work. Two duplicate tickets shipped in one run from exactly that.
|
|
62
|
+
|
|
63
|
+
## One line per ticket
|
|
64
|
+
|
|
65
|
+
One line per ticket, rewritten in place (`ledger.mjs row <ticket> <text>`; `ledger.mjs token <key> <token>…` adds a token and keeps every token already on the row):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
#<t1> impl-<t1>=PR#<p1> → PR#<p1> → MERGED 73b356de
|
|
69
|
+
#<t2> impl-<t2>=PR#<p2> → PR#<p2> · fix-pr-<p2> · ports=16<t2> · ruled:6-applies · held-behind:#<t3>
|
|
70
|
+
#<t3> impl-<t3> · tier=task-high
|
|
71
|
+
#<t4> excluded · behind-pr:#<p2>
|
|
72
|
+
#<t5> excluded · behind-issue:#<t3>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Writing before dispatch
|
|
76
|
+
|
|
77
|
+
A member that dies between spawn and write is invisible — and members die in batches.
|
|
78
|
+
|
|
79
|
+
## filed and ruled
|
|
80
|
+
|
|
81
|
+
`filed` is checked with `ledger.mjs check <subject>` before every `gh issue create`, so a finding already recorded this run is not filed twice. That is not a guarantee against duplicates at large; the tracker query, described under `check`'s exits and its `verdict` above, is what covers those.
|
|
82
|
+
|
|
83
|
+
`ruled` records PR + decision + one-line reason, so a replacement controller does not re-litigate a settled call.
|
|
84
|
+
|
|
85
|
+
## Label-off and conflict holds
|
|
86
|
+
|
|
87
|
+
`label-off=<finisher-pr-M[-x]>` is written before you take `ready-to-merge` off PR M on purpose (taking it off before you approve a push, or clearing one left on a moved head), naming that PR's latest finisher attempt: the tick reads a missing label as a finisher's miss only after the last attempt a `label-off=` names, so without it your own removal reads as one.
|
|
88
|
+
|
|
89
|
+
`ci=<run-id>:<attempt>:<conclusion>` and `held-behind:#<lower>` are row tokens the same way, and so is `conflict-hold:#<pr>` — written by the merge bot itself (`run-merge-bot.md` step 1), or by you off the tick's `CONFLICT` line, naming the row's own PR. It is not an Exclusion: that gates a ticket's claim, this a PR's merge.
|
|
90
|
+
|
|
91
|
+
## The review pair on a PR's row
|
|
92
|
+
|
|
93
|
+
A PR's review is not a member, so it carries its own pair of row tokens, written with `review` onto the PR's row (**The reviewer**): `review=wf:<runId>` | `review=member:<name>` at launch, `<name>` being `review-pr-<n>` for the PR's first runner and the next letter its rows do not carry yet (`review-pr-<n>-b`, `-c`, …) for every later one, settled dead as `…=failed`, then `reviewed=<head>:<survived>/<refuted>/<unverified>:<run>` when the result lands, `<run>` the review's `run-XXXXXXXX` run root under `<scratch>/pr<n>/`, which holds its `review.json`.
|
|
94
|
+
|
|
95
|
+
## The ledger's readability field: `ledger.ok`
|
|
96
|
+
|
|
97
|
+
The ledger half reports its own readability as its own field, `ledger.ok`, the peer of `tracker.ok`: false when the file did not exist, and false too when the path led to something that did not parse as a ledger — which is what tells a ledger that was read and held nothing filed from one there was nothing to read. The absent case is the normal one for a run's first `check` — `.fleet/` is git-ignored and created lazily by the first write — so it stays `clean` at exit 0, and `ledger.ok` is the field that says why rather than the verdict moving. The stderr warning is deliberately narrower than the field: it fires on a path with no file, so a `--file` that landed on the wrong existing file is a silent `ok:false` with nothing on stderr.
|
|
98
|
+
|
|
99
|
+
## Suite slots
|
|
100
|
+
|
|
101
|
+
**The suite slots wedge the same ways, and clear the same way.**
|
|
102
|
+
|
|
103
|
+
`~/.fleet/suite-slots/<i>.lock` is built on the ledger's lock primitive, so an empty or unparseable `<i>.lock` and a leftover `<i>.lock.reap` are permanent, a zombie holder is transient, and a dead holder is taken over. A wait that expired with exit 75 names each wedged file — remove it by hand once no suite run is running, the way the Writer policy section above removes `<file>.lock`.
|
|
@@ -30,7 +30,7 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
|
|
|
30
30
|
(**The reviewer**).
|
|
31
31
|
- `DISPATCH merge-bot` — the next `merge-bot-<n>` (**The merge bot**). The `<scratch>` its brief takes is the absolute `scratch-root.mjs` output, and the bot writes only under `<scratch>/pr<N>/merge-bot-<n>/`.
|
|
32
32
|
- `DISPATCH finisher PR#<M> …` — a finisher settled `labelled`, yet the open
|
|
33
|
-
list shows PR M without `ready-to-merge`, and no `label-off=` (
|
|
33
|
+
list shows PR M without `ready-to-merge`, and no `label-off=` (references/ledger.md)
|
|
34
34
|
accounts for the removal. Apply the finisher gate (**The finisher gate**: the
|
|
35
35
|
`check` job green on the current head), then dispatch the next-suffix
|
|
36
36
|
finisher at that head, one letter past the highest the PR has used: render
|