@feigi/fleet-ctl 3.94.0 → 3.94.1

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.
@@ -240,6 +240,10 @@ including if the `Sizing:` line was written before the run and corrected after;
240
240
  `sizing.verdict` and `sizing.signal` are that line's verdict and signal.
241
241
  Nothing in the PR body separates a measured verdict from one typed in early;
242
242
  that field is the only thing that does, and an unreported ordering costs the
243
- covariate. A bail is `outcome` `bailed`, its cause in `bailCause`. Then send
243
+ covariate. A bail is `outcome` `bailed`, its cause in `bailCause`: the
244
+ pre-code outcome, before you touch code. A block or ambiguity you find after
245
+ implementation began — the work cannot finish without a ruling or human hands
246
+ you do not have — is `outcome` `blocked` instead, with `pr`, `head` and `bailCause` `null` and
247
+ the reason in `bailDetail`; report it rather than ending with no report. Then send
244
248
  the one line your brief gives, `<your name>: <outcome> <path>`, and exit. Never
245
249
  apply `ready-to-merge`, never merge.
@@ -240,6 +240,10 @@ including if the `Sizing:` line was written before the run and corrected after;
240
240
  `sizing.verdict` and `sizing.signal` are that line's verdict and signal.
241
241
  Nothing in the PR body separates a measured verdict from one typed in early;
242
242
  that field is the only thing that does, and an unreported ordering costs the
243
- covariate. A bail is `outcome` `bailed`, its cause in `bailCause`. Then send
243
+ covariate. A bail is `outcome` `bailed`, its cause in `bailCause`: the
244
+ pre-code outcome, before you touch code. A block or ambiguity you find after
245
+ implementation began — the work cannot finish without a ruling or human hands
246
+ you do not have — is `outcome` `blocked` instead, with `pr`, `head` and `bailCause` `null` and
247
+ the reason in `bailDetail`; report it rather than ending with no report. Then send
244
248
  the one line your brief gives, `<your name>: <outcome> <path>`, and exit. Never
245
249
  apply `ready-to-merge`, never merge.
@@ -240,6 +240,10 @@ including if the `Sizing:` line was written before the run and corrected after;
240
240
  `sizing.verdict` and `sizing.signal` are that line's verdict and signal.
241
241
  Nothing in the PR body separates a measured verdict from one typed in early;
242
242
  that field is the only thing that does, and an unreported ordering costs the
243
- covariate. A bail is `outcome` `bailed`, its cause in `bailCause`. Then send
243
+ covariate. A bail is `outcome` `bailed`, its cause in `bailCause`: the
244
+ pre-code outcome, before you touch code. A block or ambiguity you find after
245
+ implementation began — the work cannot finish without a ruling or human hands
246
+ you do not have — is `outcome` `blocked` instead, with `pr`, `head` and `bailCause` `null` and
247
+ the reason in `bailDetail`; report it rather than ending with no report. Then send
244
248
  the one line your brief gives, `<your name>: <outcome> <path>`, and exit. Never
245
249
  apply `ready-to-merge`, never merge.
@@ -240,6 +240,10 @@ including if the `Sizing:` line was written before the run and corrected after;
240
240
  `sizing.verdict` and `sizing.signal` are that line's verdict and signal.
241
241
  Nothing in the PR body separates a measured verdict from one typed in early;
242
242
  that field is the only thing that does, and an unreported ordering costs the
243
- covariate. A bail is `outcome` `bailed`, its cause in `bailCause`. Then send
243
+ covariate. A bail is `outcome` `bailed`, its cause in `bailCause`: the
244
+ pre-code outcome, before you touch code. A block or ambiguity you find after
245
+ implementation began — the work cannot finish without a ruling or human hands
246
+ you do not have — is `outcome` `blocked` instead, with `pr`, `head` and `bailCause` `null` and
247
+ the reason in `bailDetail`; report it rather than ending with no report. Then send
244
248
  the one line your brief gives, `<your name>: <outcome> <path>`, and exit. Never
245
249
  apply `ready-to-merge`, never merge.
@@ -240,6 +240,10 @@ including if the `Sizing:` line was written before the run and corrected after;
240
240
  `sizing.verdict` and `sizing.signal` are that line's verdict and signal.
241
241
  Nothing in the PR body separates a measured verdict from one typed in early;
242
242
  that field is the only thing that does, and an unreported ordering costs the
243
- covariate. A bail is `outcome` `bailed`, its cause in `bailCause`. Then send
243
+ covariate. A bail is `outcome` `bailed`, its cause in `bailCause`: the
244
+ pre-code outcome, before you touch code. A block or ambiguity you find after
245
+ implementation began — the work cannot finish without a ruling or human hands
246
+ you do not have — is `outcome` `blocked` instead, with `pr`, `head` and `bailCause` `null` and
247
+ the reason in `bailDetail`; report it rather than ending with no report. Then send
244
248
  the one line your brief gives, `<your name>: <outcome> <path>`, and exit. Never
245
249
  apply `ready-to-merge`, never merge.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.94.0",
3
+ "version": "3.94.1",
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": {
@@ -50,6 +50,7 @@
50
50
  // opens with no sha, is a NEED and N is left untouched, claim and all.
51
51
  // The PR a `covered` detail names is never written to.
52
52
  // A `PR#M` outcome also sets the claim branch's upstream when it has none: `reap.sh` keeps a branch with no upstream, and only an upstream's `[gone]` state makes it reapable. It writes `branch.<b>.remote` and `branch.<b>.merge` only while the merge key is unset, and prints `NEED upstream <b> — <why>` when a write fails.
53
+ // A `blocked` outcome takes no action beyond the settle: no label, comment, release or upstream, so the ticket keeps `in-progress`.
53
54
  // fix-report <fix-pr-M[-x]>
54
55
  // `settle --from-report`, which records the filings and rulings the
55
56
  // report carries. Settled `applied:`/`no-op`, on a PR whose rows carry
@@ -2508,6 +2508,9 @@ function recordFiled(id, subject) {
2508
2508
  // own repository name N;
2509
2509
  // the Check-step record (below) holds for <head>
2510
2510
  // impl-N=bailed bailCause is not null; the receipt names it
2511
+ // impl-N=blocked nothing to check: report-schema.mjs already
2512
+ // refuses a block that names a bailCause, a pr or
2513
+ // a head, or gives no reason in bailDetail
2511
2514
  // fix-pr-M=applied:<head> origin's refs/heads/<branch> is <head>, fetched
2512
2515
  // by verify-sha.sh; the Check-step record (below)
2513
2516
  // holds for <head>
@@ -2670,6 +2673,8 @@ async function runSettleFromReport() {
2670
2673
  const { view: pr, numbers } = prViewClosing(report.pr, "state,closingIssuesReferences,url");
2671
2674
  check(`PR #${report.pr} reads OPEN`, pr.state === "OPEN");
2672
2675
  check(`PR #${report.pr} closes #${live.number}`, numbers.includes(live.number));
2676
+ } else if (report.outcome === "blocked") {
2677
+ outcome = "blocked";
2673
2678
  } else {
2674
2679
  outcome = "bailed";
2675
2680
  check(`bailCause: ${report.bailCause}`, report.bailCause !== null);
@@ -146,6 +146,7 @@ export function withReports(rows, scratch) {
146
146
  // opened one (more than one leaves the pr column blank), or
147
147
  // the last report claims a PR an earlier same-ticket
148
148
  // implementer opened in this session;
149
+ // blocked the last report blocks mid-implementation;
149
150
  // bail:<cause> the last report bails (`fixed` is checked against main);
150
151
  // provider a model fell back, or the last turn errored;
151
152
  // budget a budget notice reached the member;
@@ -162,6 +163,7 @@ export function endingOf({ member, pr, signals = null, earlierPrs = new Set(), v
162
163
  if (signals.openedPrs.length > 0) return "pr";
163
164
  const last = lastReport(signals.reportWrites, member.split("/").pop());
164
165
  if (last?.outcome === "pr" && earlierPrs.has(String(last.pr))) return "pr";
166
+ if (last?.outcome === "blocked") return "blocked";
165
167
  // lastReport passed the impl schema, whose bailCause is a BAILS key or null.
166
168
  if (last?.bailCause) return bailEnding(last, verifyMain);
167
169
  if (signals.providerFallback || signals.lastStopReason === "error") return "provider";
@@ -38,8 +38,8 @@
38
38
  // counted every other Pull: the only ones in a cell's figures.
39
39
  //
40
40
  // ENDINGS come from member-outcomes' `ending` column. The cell's: `hollow`,
41
- // `budget`, `bail:fixed-refuted`, `unknown`. The ticket's: every other
42
- // `bail:<cause>`. Neither's: `provider`, `crash`, `abort`.
41
+ // `budget`, `bail:fixed-refuted`, `unknown`. The ticket's: `blocked` and every
42
+ // other `bail:<cause>`. Neither's: `provider`, `crash`, `abort`.
43
43
  //
44
44
  // CLAIMS. A merged Pull is one claim, false when its Ruling fails the floor
45
45
  // (`minted_false_claim=yes` or `closed_own_ticket=no`). Each `bail:fixed` or
@@ -125,7 +125,7 @@ function datedRows(features) {
125
125
 
126
126
  const CELL_ENDINGS = new Set(["hollow", "budget", "bail:fixed-refuted", "unknown"]);
127
127
  const NEITHER_ENDINGS = new Set(["provider", "crash", "abort"]);
128
- const ticketsEnding = (e) => e.startsWith("bail:") && !CELL_ENDINGS.has(e);
128
+ const ticketsEnding = (e) => e === "blocked" || (e.startsWith("bail:") && !CELL_ENDINGS.has(e));
129
129
  const failsFloor = (ruling) => ruling.minted_false_claim === "yes" || ruling.closed_own_ticket === "no";
130
130
  // An omp session id opens with its ISO start time, so two of them sort in time order.
131
131
  const STARTED = /^\d{4}-\d{2}-\d{2}T/;
@@ -6,7 +6,7 @@
6
6
  // (flat: no per-run directory), `<member>` being its ledger name (`impl-412`,
7
7
  // `impl-412-b`, `fix-pr-88`, `finisher-pr-88`, `merge-bot-3`). The file is
8
8
  // checked against the schema of the member's family, report-schema.mjs's
9
- // SCHEMAS, and the rule reportViolation() adds to it. The review runner and
9
+ // SCHEMAS, and the rules reportViolation() adds to them. The review runner and
10
10
  // every member outside those four families have no schema, so no file to
11
11
  // look up.
12
12
  //
@@ -18,9 +18,14 @@
18
18
  // 0 print: the schema is on stdout. validate: `valid` on stdout
19
19
  // 1 validate: `invalid: <violation>` on stdout, naming the FIRST violation;
20
20
  // a file that is not JSON is invalid too, as `$: not JSON (...)`, and so
21
- // is one that repeats a key, as `$.merged: duplicate key "merged"`, and
22
- // a merge-bot report giving one PR two verdicts, as
23
- // `$.refused[0].pr: PR #<pr> already has a verdict at $.merged[0].pr`
21
+ // is one that repeats a key, as `$.merged: duplicate key "merged"`, a
22
+ // merge-bot report giving one PR two verdicts, as
23
+ // `$.refused[0].pr: PR #<pr> already has a verdict at $.merged[0].pr`,
24
+ // and an impl report whose `blocked` outcome names a `bailCause`, as
25
+ // `$.bailCause: "<cause>" is not null, with outcome "blocked" — a block
26
+ // is no bail`, gives no reason in `bailDetail`, or names a `pr` or a
27
+ // `head`, as `$.pr: <pr> is not null, with outcome "blocked" — a block
28
+ // reports no PR`
24
29
  // 2 usage: no or unknown subcommand, a wrong argument count, an unknown
25
30
  // family, any flag; or <file> could not be read; or print could not
26
31
  // write the schema to stdout. Refused on stderr, nothing on stdout
@@ -112,7 +117,9 @@ const issues = listOf({ issue: id, subject: str });
112
117
  export const SCHEMAS = {
113
118
  impl: report({
114
119
  member: str,
115
- outcome: { type: "string", enum: ["pr", "bailed"] },
120
+ // `bailed` is the pre-code outcome; `blocked` a block or ambiguity found
121
+ // after implementation began, its reason in `bailDetail`.
122
+ outcome: { type: "string", enum: ["pr", "bailed", "blocked"] },
116
123
  pr: { ...id, type: ["integer", "null"] },
117
124
  head: { type: ["string", "null"], pattern: HEX },
118
125
  branch: str,
@@ -174,14 +181,25 @@ export function mergeBotVerdicts(report) {
174
181
  ];
175
182
  }
176
183
 
177
- // The first violation of `family`'s schema by `value`, or null; for a
178
- // merge-bot report then the rule no schema keyword states: each PR gets one
179
- // verdict — merged, held, conflict-held or refused — the last it reached in
180
- // the pass, so a PR named twice, in one list or in two, is a violation at
181
- // its second entry.
184
+ // The first violation of `family`'s schema by `value`, or null; then the rules
185
+ // no schema keyword states. For an impl report: a `blocked` outcome names no
186
+ // `bailCause`, since a block is no bail, gives its reason in `bailDetail`,
187
+ // text that is not only whitespace, and names no `pr` or `head`, since a block
188
+ // reports no PR — a PR it opened is outcome `pr`, which settle checks. For a
189
+ // merge-bot report: each PR gets one verdict — merged, held, conflict-held or
190
+ // refused — the last it reached in the pass, so a PR named twice, in one list
191
+ // or in two, is a violation at its second entry.
182
192
  export function reportViolation(family, value) {
183
193
  const found = validate(SCHEMAS[family], value);
184
- if (found !== null || family !== "merge-bot") return found;
194
+ if (found !== null) return found;
195
+ if (family === "impl" && value.outcome === "blocked") {
196
+ if (value.bailCause !== null) return `$.bailCause: ${show(value.bailCause)} is not null, with outcome "blocked" — a block is no bail`;
197
+ if (!/\S/.test(value.bailDetail ?? "")) return `$.bailDetail: ${show(value.bailDetail)} is not a reason, with outcome "blocked"`;
198
+ for (const field of ["pr", "head"]) {
199
+ if (value[field] !== null) return `$.${field}: ${show(value[field])} is not null, with outcome "blocked" — a block reports no PR`;
200
+ }
201
+ }
202
+ if (family !== "merge-bot") return null;
185
203
  const first = new Map();
186
204
  for (const { pr, path } of mergeBotVerdicts(value)) {
187
205
  if (first.has(pr)) return `${path}: PR #${pr} already has a verdict at ${first.get(pr)}`;
@@ -792,7 +792,7 @@ Match the failure to its row in the table below and run that response; where the
792
792
  |---|---|
793
793
  | SHA not on expected branch | Flag, do not enqueue, report |
794
794
  | Implementer bails before implementing | Act by the report's `bailCause` as the **Relabel causes** table says, then refill (**What a wake owes beyond its call**) |
795
- | Implementer blocked or ambiguous *mid-implementation* | Free the slot, `ledger.mjs settle impl-<N> blocked`, leave `in-progress`, report — the ticket is not In flight and waits for a human; the row above is the pre-code bail, not this one |
795
+ | Implementer blocked or ambiguous *mid-implementation* | Free the slot. A `blocked` report settles on the **Implementer report** wake, no label moved; with no report, `ledger.mjs settle impl-<N> blocked`, its Ending still `hollow`. Leave `in-progress`, report — the ticket is not In flight and waits for a human; the row above is the pre-code bail |
796
796
  | `review-pr` workflow throws or returns no tree | Retry once under the next letter (`review-pr-<pr#>-b`); still failing, report and leave the PR at review-due. Never enqueue the PR as reviewed — the workflow is not a member, so no other row here covers it |
797
797
  | Reviewer, fix-applier or red-check fixer cannot reach green | Report, leave the PR unlabeled, free the slot — a fix-applier or red-check fixer settles `failed` |
798
798
  | Merge bot hits the hold rule | Report `held-behind-#<lower>`, PR stays queued |
@@ -841,9 +841,7 @@ enqueue, do not return the ticket to the shortlist until the maintainer rules.
841
841
  escalate if it repeats, and never read it as `not reachable` or record it as a
842
842
  stray commit. See references/failure-handling.md.
843
843
 
844
- **Never `--delete-branch` — the merge bot deletes the head branch itself.**
845
- `gh pr merge <n> --merge` alone: the flag's local cleanup errors on a `main`
846
- held by another worktree, or strands the feature worktree on `main`.
844
+ **Never `--delete-branch` — the merge bot deletes the head branch itself.** See references/failure-handling.md.
847
845
 
848
846
  **Never force a rebase to start.** No `git clean`, `git checkout .`,
849
847
  `git reset --hard`, `git stash`. Non-empty `git status --porcelain -unormal` →
@@ -66,6 +66,13 @@ escalate if it repeats. Never read exit 2 as `not reachable`, and never record i
66
66
  as a stray commit — the flag and the maintainer's ruling above are what exit 1
67
67
  earns, and spending them on a probe that never answered costs a member its
68
68
  ticket over a failure a re-run would have cleared.
69
+
70
+ ## Never `--delete-branch`
71
+
72
+ **Never `--delete-branch` — the merge bot deletes the head branch itself.**
73
+ `gh pr merge <n> --merge` alone: the flag's local cleanup errors on a `main`
74
+ held by another worktree, or strands the feature worktree on `main`.
75
+
69
76
  ## Never force a rebase to start
70
77
 
71
78
  **Never force a rebase to start.** No `git clean`, `git checkout .`,