@feigi/fleet-ctl 3.23.13 → 3.23.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.23.13",
3
+ "version": "3.23.15",
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": {
@@ -499,7 +499,16 @@ const FILES = [
499
499
  // other number straight back in, and this file already carried the
500
500
  // identical stale number at two independent sites.
501
501
  path: ["scripts", "fleet-heartbeat.mjs"],
502
- stale: [/run-merge-bot\.md:\d+/],
502
+ // #2667. The header once said run-merge-bot.md's CI gate holds its wait
503
+ // "and re-issuing it", and the several-holds paragraph once called the
504
+ // heartbeat "the same remedy the CI gate reached for". The CI wait holds
505
+ // in one `eval` cell and re-issues nothing. Tolerant of a re-wrap: the
506
+ // comment is hard-wrapped, so either phrase can break across a `//` line.
507
+ stale: [
508
+ /run-merge-bot\.md:\d+/,
509
+ /re-issuing\s+(?:\/\/\s*)?it\b/,
510
+ /same remedy the\s+(?:\/\/\s*)?CI gate/,
511
+ ],
503
512
  // One needle per SITE — either can revert independently of the other.
504
513
  live: [
505
514
  "run-merge-bot.md's CI wait rule already states",
@@ -18,7 +18,7 @@
18
18
  // roughly two hours; neither ever woke it. run-merge-bot.md's CI gate states
19
19
  // the same rule as a rule — "whatever wakes you is external and may never
20
20
  // come" — and cures it the same way this file does, by holding the wait
21
- // inside one blocking command and re-issuing it.
21
+ // inside one blocking call.
22
22
  //
23
23
  // So the heartbeat is not a Monitor, not a cron entry and not a paragraph of
24
24
  // prose. A cron entry that ran the reconcile would print its rows to nobody:
@@ -66,9 +66,8 @@ export function interval({ quiet, base, ceiling, multiplier }) {
66
66
  // rule already states.
67
67
  //
68
68
  // So a long interval is served by SEVERAL holds, and the elapsed total is
69
- // persisted rather than counted in prose. That is the same remedy the CI gate
70
- // reached for, made mechanical: the script says how much remains, so the
71
- // controller re-issues without tracking arithmetic across turns.
69
+ // persisted rather than counted in prose: the script says how much remains, so
70
+ // the controller re-issues without tracking arithmetic across turns.
72
71
  export function heldThisCall({ elapsed, target, hold }) {
73
72
  return Math.min(hold, Math.max(0, target - elapsed));
74
73
  }
@@ -48,7 +48,7 @@ import { test } from "node:test";
48
48
  import assert from "node:assert/strict";
49
49
  import { readFileSync } from "node:fs";
50
50
  import { join } from "node:path";
51
- import { between, phrase, stripHashGutter } from "./prose-pin.mjs";
51
+ import { between, paragraph, phrase, stripHashGutter } from "./prose-pin.mjs";
52
52
 
53
53
  const REPO = join(import.meta.dirname, "..");
54
54
  const REAPING = readFileSync(
@@ -141,6 +141,58 @@ test("run-team/SKILL.md: the release deletes the branch with update-ref's compar
141
141
  );
142
142
  });
143
143
 
144
+ // The reap paragraph's twin of the release pin above. reap.sh deletes a
145
+ // `[gone]` branch with `git update-ref --no-deref -d refs/heads/<b> <tip>`,
146
+ // never `-D`, and keeps a branch the cherry check cleared whenever a fresh
147
+ // worktree check finds it held — so naming `-D`, or the cherry check as the
148
+ // delete's only authorizer, is false as written.
149
+ const reapParagraph = () =>
150
+ paragraph(RUN_TEAM, "`~/.fleet/bin/fleet-run reap.sh --apply` recomputes every precondition", "run-team/SKILL.md's reap paragraph");
151
+
152
+ test("run-team/SKILL.md: reap's cherry check authorizes the update-ref delete, with the fresh worktree check beside it", () => {
153
+ const p = reapParagraph();
154
+ assert.match(
155
+ p,
156
+ phrase("`git cherry origin/main` to authorize the `git update-ref -d` delete"),
157
+ "SKILL.md's reap paragraph no longer names the cherry check as authorizing the `git update-ref -d` delete",
158
+ );
159
+ assert.match(
160
+ p,
161
+ phrase("a fresh check that no worktree holds the branch"),
162
+ "SKILL.md's reap paragraph no longer names the fresh worktree check — reap.sh keeps a branch the cherry check cleared whenever a worktree holds it",
163
+ );
164
+ assert.doesNotMatch(p, /(?:^|[\s`])(?:git\s+branch\s+)?-D\b/, "SKILL.md's reap paragraph names `-D` — reap.sh deletes with `git update-ref -d`");
165
+ assert.doesNotMatch(
166
+ p,
167
+ /\bonly\s+by\b|\bnothing\s+else\b|\bsolely\b|\balone\b/i,
168
+ "SKILL.md's reap paragraph makes one check the delete's only authorizer — reap.sh's worktree check stops that delete too",
169
+ );
170
+ });
171
+
172
+ test("run-team/SKILL.md: the reap paragraph keeps the preconditions it already named", () => {
173
+ // Input the assertions above must ACCEPT: the corrected clause sits in a
174
+ // list whose other entries stay as they were.
175
+ const p = reapParagraph();
176
+ assert.match(p, phrase("`for-each-ref` for `[gone]`"));
177
+ assert.match(p, phrase("worktree removal without `--force`"));
178
+ });
179
+
180
+ test("reap.sh: the worktree check the reap paragraph names runs before the delete", () => {
181
+ // Live lines only: both names recur in comments, and a comment hit would
182
+ // read as the code.
183
+ const lines = readFileSync(join(REPO, "scripts", "reap.sh"), "utf8")
184
+ .split("\n")
185
+ .filter((l) => !/^\s*(?:#|echo\b)/.test(l));
186
+ const only = (re, what) => {
187
+ const hits = lines.flatMap((l, i) => (re.test(l) ? [i] : []));
188
+ assert.equal(hits.length, 1, `reap.sh: expected exactly one live line that ${what}, found ${hits.length}`);
189
+ return hits[0];
190
+ };
191
+ const check = only(/^\s*if\s+wt_holding\s+"refs\/heads\/\$b"/, "runs wt_holding on the branch");
192
+ const del = only(/\bgit\s+update-ref\s+--no-deref\s+-d\s+"refs\/heads\/\$b"\s+"\$tip"/, "runs `git update-ref -d` on the branch at its tip");
193
+ assert.ok(check < del, "reap.sh runs its worktree check after the delete, not before it");
194
+ });
195
+
144
196
  test("run-team/SKILL.md: the by-hand fallback names worktree-audit.sh's no-argument contract", () => {
145
197
  // #525. The by-hand fallback for a claim `release-ticket.sh` refused sends
146
198
  // the reader to `worktree-audit.sh`, which takes NO argument and audits
@@ -3101,9 +3101,10 @@ included (phase 3, **Merge-bot pass reports done**). See references/reaping.md.
3101
3101
 
3102
3102
  `~/.fleet/bin/fleet-run reap.sh --apply` recomputes every precondition
3103
3103
  inside the same invocation as the delete — `for-each-ref` for `[gone]`, `git
3104
- cherry origin/main` to authorize `-D`, worktree removal without `--force` — and
3105
- reports reaped and kept-with-reason counts. Update the reaped tickets' ledger rows
3106
- in the same step. See references/reaping.md.
3104
+ cherry origin/main` to authorize the `git update-ref -d` delete (plus a fresh
3105
+ check that no worktree holds the branch), worktree removal without `--force` —
3106
+ and reports reaped and kept-with-reason counts. Update the reaped tickets' ledger
3107
+ rows in the same step. See references/reaping.md.
3107
3108
 
3108
3109
  **It sweeps detached worktrees too — the shape a `[gone]` walk structurally
3109
3110
  cannot see.** Bounded to `.worktrees/`, so a checkout of yours outside it is