@feigi/fleet-ctl 3.23.14 → 3.23.16

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.14",
3
+ "version": "3.23.16",
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
  }
@@ -0,0 +1,63 @@
1
+ // release-ticket.sh's comment above its branch delete contrasts the release's
2
+ // pairing (the `ahead` and `git cherry` guards, the recount at the delete, the
3
+ // fresh worktree check) with reap.sh's. It said reap.sh authorizes its own
4
+ // [gone] deletes with `git cherry` ALONE, but reap.sh also re-reads the
5
+ // worktree listing and keeps any branch `wt_holding` reports held or cannot
6
+ // resolve, before the same kind of compare-and-swap delete. What reap.sh does
7
+ // lack is the `ahead` count and the recount: `git cherry` is the only check
8
+ // there that asks whether the branch is merged.
9
+ import { test } from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { phrase, runAbove, stripHashGutter } from "./prose-pin.mjs";
14
+
15
+ const RELEASE = readFileSync(join(import.meta.dirname, "release-ticket.sh"), "utf8");
16
+ // reap.sh's code lines only: a whole-line `#` comment that mentions a command
17
+ // is neither a call nor a guard, so it must satisfy no check below and fail none.
18
+ const REAP = readFileSync(join(import.meta.dirname, "reap.sh"), "utf8")
19
+ .split("\n")
20
+ .filter((line) => !/^\s*#/.test(line))
21
+ .join("\n");
22
+
23
+ const deleteComment = () => {
24
+ const comment = stripHashGutter(
25
+ runAbove(RELEASE, 'echo "\\$ git update-ref -d refs/heads/$branch $tip" >&2', "release-ticket.sh branch delete", "#"),
26
+ );
27
+ // runAbove returns "" rather than throwing when no comment run sits directly
28
+ // above the anchor; without this the negative pin would pass on nothing.
29
+ assert.notEqual(comment, "", "no comment run sits directly above release-ticket.sh's branch delete — the pins have nothing to read");
30
+ return comment;
31
+ };
32
+
33
+ test("release-ticket.sh: the delete comment does not say reap.sh deletes on `git cherry` alone", () => {
34
+ assert.doesNotMatch(
35
+ deleteComment(),
36
+ new RegExp(phrase("deletes with `git cherry` alone").source, "i"),
37
+ "release-ticket.sh's delete comment says reap.sh authorizes its [gone] deletes with `git cherry` alone — reap.sh also re-checks the worktree with `wt_holding` before its delete",
38
+ );
39
+ });
40
+
41
+ test("release-ticket.sh: the delete comment still says how reap.sh's pairing differs", () => {
42
+ // Input the pin above must ACCEPT: the contrast the sentence exists for —
43
+ // reap.sh has no `ahead` count and no recount — survives, beside the
44
+ // worktree check reap.sh shares with the release.
45
+ const comment = deleteComment();
46
+ assert.match(comment, phrase("with `git cherry` alone — no `ahead` count, no recount"));
47
+ assert.match(comment, phrase("re-checks the worktree with `wt_holding`"));
48
+ });
49
+
50
+ test("reap.sh: the branch delete still follows a `wt_holding` check and has no `ahead` recount", () => {
51
+ // The target side of the comment's claim, so a change to reap.sh that makes
52
+ // it false reds here rather than leaving release-ticket.sh's prose stale.
53
+ const held = REAP.indexOf('if wt_holding "refs/heads/$b"');
54
+ const del = REAP.indexOf('git update-ref --no-deref -d "refs/heads/$b" "$tip"');
55
+ assert.ok(held !== -1, "reap.sh no longer checks `wt_holding` on the [gone] branch");
56
+ assert.ok(del !== -1, "reap.sh no longer deletes the [gone] branch with a compare-and-swap `update-ref -d`");
57
+ assert.ok(held < del, "reap.sh's `wt_holding` check no longer runs before its branch delete");
58
+ assert.doesNotMatch(
59
+ REAP,
60
+ /rev-list\b[^\n]*--count|--count[^\n]*rev-list/,
61
+ "reap.sh now counts commits ahead — release-ticket.sh's comment says it has no `ahead` count",
62
+ );
63
+ });
@@ -1679,9 +1679,11 @@ else
1679
1679
 
1680
1680
  # update-ref, authorized by the `ahead` and `git cherry` guards above,
1681
1681
  # this recount, and the worktree check above — and carrying its own
1682
- # compare-and-swap on $tip on top, which `-D` never had. reap.sh
1683
- # authorizes its own [gone] deletes with `git cherry` ALONE — not this
1684
- # pairing. `-d` measures against HEAD and the branch's upstream, and a
1682
+ # compare-and-swap on $tip on top, which `-D` never had. reap.sh asks
1683
+ # whether its own [gone] branches are merged with `git cherry` alone — no
1684
+ # `ahead` count, no recount — so not this pairing, though it too
1685
+ # re-checks the worktree with `wt_holding` before its own compare-and-swap
1686
+ # delete. `-d` measures against HEAD and the branch's upstream, and a
1685
1687
  # claim has no upstream until its first push (claim-ticket.sh passes
1686
1688
  # --no-track), so `-d` falls back to local HEAD alone and refuses a
1687
1689
  # pristine claim whenever local main is behind origin/main —