@feigi/fleet-ctl 3.76.5 → 3.76.6
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
package/scripts/fleet-tick.mjs
CHANGED
|
@@ -104,16 +104,20 @@
|
|
|
104
104
|
// up; a member with no schema is never checked against one.
|
|
105
105
|
// pulls `<scratch>/impl-<N>/pull.json`, pull.mjs's record of a Pull, for
|
|
106
106
|
// every head on the shortlist, `<scratch>` as for the report
|
|
107
|
-
// files
|
|
108
|
-
//
|
|
109
|
-
// ` (
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
107
|
+
// files: read before the refresh is judged, and again for the
|
|
108
|
+
// heads a refresh replaced them with. A question standing prints
|
|
109
|
+
// `PULL #<N> (Q<n> pending — pull.mjs answer <N> <qid> …)` in
|
|
110
|
+
// place of the fresh row, with ` (stale)` once the file is older
|
|
111
|
+
// than `--pull-stale-minutes`; a Pull that dispatched with the
|
|
112
|
+
// head still holding no `impl-` row prints `(dispatched, row
|
|
113
|
+
// missing — ledger.mjs row …)`. A Pull that ended in this run's
|
|
114
|
+
// session (the ticket left, or its claim was released) names no
|
|
115
|
+
// row: `pull.mjs next` skips it, `next --head` would start the
|
|
116
|
+
// ticket over, so the next head fills the slot. Nor is it counted
|
|
117
|
+
// in `unclaimed=` or toward the short-queue refresh, so a
|
|
118
|
+
// shortlist of ended heads refreshes. A record from another
|
|
119
|
+
// phase-0 session, or one unreadable or absent, is the fresh row:
|
|
120
|
+
// `pull.mjs next` starts or resumes it.
|
|
117
121
|
// session The listing of the session directory omp writes one
|
|
118
122
|
// `<member>.jsonl` transcript into per dispatched member: this
|
|
119
123
|
// run's phase-0 receipt's `session`, else `--session`; with
|
|
@@ -278,12 +282,23 @@ function pullAction(n, p) {
|
|
|
278
282
|
return `PULL #${n} — pull.mjs next --head ${n}`;
|
|
279
283
|
}
|
|
280
284
|
|
|
285
|
+
// The heads a PULL row could name: a head whose Pull ended in this run takes
|
|
286
|
+
// no slot, and the next head takes it. Order kept, so the slots fill oldest first.
|
|
287
|
+
function pullable(heads, pulls) {
|
|
288
|
+
return heads.filter((n) => pulls?.get(n)?.kind !== "ended");
|
|
289
|
+
}
|
|
290
|
+
|
|
281
291
|
// The implementer rows: one per head a free slot Pulls, else the one row
|
|
282
|
-
// saying why none is pulled.
|
|
292
|
+
// saying why none is pulled. `unclaimed=` counts the heads a PULL row could
|
|
293
|
+
// name. A refresh whose trigger text names a count carries it as `unclaimed`,
|
|
294
|
+
// and the row prints that count, not the refreshed file's: one tick never
|
|
295
|
+
// prints two different counts.
|
|
283
296
|
function implementers(s) {
|
|
284
297
|
const dropped = specsDropped(s.dropped);
|
|
298
|
+
const heads = pullable(s.heads, s.pulls);
|
|
299
|
+
const counted = s.refresh?.unclaimed ?? heads.length;
|
|
285
300
|
const one = mkRow("implementers", s.inFlight, s.bound,
|
|
286
|
-
`implementers=${s.inFlightImpl} prs=${s.inFlightPrs} unclaimed=${
|
|
301
|
+
`implementers=${s.inFlightImpl} prs=${s.inFlightPrs} unclaimed=${counted} supply=${s.supply ?? "?"}`
|
|
287
302
|
+ (s.shortlistStatus === "ok" ? "" : ` shortlist=${s.shortlistStatus}`)
|
|
288
303
|
+ (dropped ? ` ${dropped}` : ""));
|
|
289
304
|
const row = (...a) => [one(...a)];
|
|
@@ -323,9 +338,7 @@ function implementers(s) {
|
|
|
323
338
|
return s.owedCounted.length ? row(hold, { acts: true, extra: replaceStep(s.owedCounted, s.implNames) }) : row(hold);
|
|
324
339
|
}
|
|
325
340
|
|
|
326
|
-
|
|
327
|
-
const pullable = s.heads.filter((n) => s.pulls?.get(n)?.kind !== "ended");
|
|
328
|
-
if (pullable.length) return pullable.slice(0, s.bound - s.inFlight).map((n) => one(pullAction(n, s.pulls?.get(n)), { acts: true }));
|
|
341
|
+
if (heads.length) return heads.slice(0, s.bound - s.inFlight).map((n) => one(pullAction(n, s.pulls?.get(n)), { acts: true }));
|
|
329
342
|
|
|
330
343
|
// Nothing to Pull even after a refresh: no unclaimed head, or only heads
|
|
331
344
|
// whose Pull ended in this run, which `pull.mjs next` skips. `SUGGEST /triage` asks a
|
|
@@ -1138,7 +1151,9 @@ export function parseShortlist(text) {
|
|
|
1138
1151
|
};
|
|
1139
1152
|
}
|
|
1140
1153
|
|
|
1141
|
-
// Why this tick refreshes the shortlist, or null.
|
|
1154
|
+
// Why this tick refreshes the shortlist, or null. `unclaimed` counts the
|
|
1155
|
+
// heads a PULL row could name, so a head whose Pull ended in this run is not
|
|
1156
|
+
// in it. The premise-lifted
|
|
1142
1157
|
// trigger costs gh calls, so main() asks it only when these answer null.
|
|
1143
1158
|
// Nothing refreshes while the bound holds (`held`, n ≥ B, whichever HOLD row
|
|
1144
1159
|
// prints): a refresh then cannot feed a Pull, and the first tick back under
|
|
@@ -1147,10 +1162,15 @@ export function refreshWhy({ draining, held, status, entries, unclaimed: k, boun
|
|
|
1147
1162
|
if (draining !== null || held) return null;
|
|
1148
1163
|
if (status !== "ok") return `shortlist ${status}`;
|
|
1149
1164
|
if (entries === 0) return "shortlist empty";
|
|
1150
|
-
if (k < bound) return
|
|
1165
|
+
if (k < bound) return shortQueue(k, bound);
|
|
1151
1166
|
return null;
|
|
1152
1167
|
}
|
|
1153
1168
|
|
|
1169
|
+
// The short-queue trigger's text, the one trigger that names a count.
|
|
1170
|
+
function shortQueue(k, bound) {
|
|
1171
|
+
return `unclaimed ${k} < in-flight bound ${bound}`;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1154
1174
|
// How long, in minutes, a live token of each family may run before the tick
|
|
1155
1175
|
// asks the controller to look at it. Each default is that family's p99
|
|
1156
1176
|
// run time: a token past it is rare among members that finished, so a line
|
|
@@ -2046,49 +2066,58 @@ function main() {
|
|
|
2046
2066
|
if (unchecked.length && applyTierChecks(unchecked, phase0.session)) run = readRun(prs);
|
|
2047
2067
|
}
|
|
2048
2068
|
|
|
2069
|
+
// The scratch root of the report files and the Pull records alike.
|
|
2070
|
+
const scratch = reportsScratch(receipt, scratchFlag);
|
|
2071
|
+
if (scratch === null) console.error(`${NAME}: report files and Pull records not checked — no scratch in the phase-0 receipt and no --scratch`);
|
|
2072
|
+
// Each head's Pull record read, not only the heads a free slot names: a head
|
|
2073
|
+
// whose Pull ended gives its slot to the next, so the heads past the slots
|
|
2074
|
+
// can fill one. The session it is judged against is the receipt's, as
|
|
2075
|
+
// pull.mjs judges it.
|
|
2076
|
+
const runSession = receipt.read.state === "ok" && receipt.read.receipt.session !== "" ? receipt.read.receipt.session : null;
|
|
2077
|
+
const pullsOf = (hs) => scratch === null ? new Map()
|
|
2078
|
+
: readPulls(scratch.root, hs, { session: runSession, now: Date.now(), staleMinutes: pullStaleMinutes });
|
|
2079
|
+
|
|
2049
2080
|
// Pull from the current file first; refresh when it is short, absent, an
|
|
2050
2081
|
// exclusion has lifted, or a branch-named or issue one still names a ticket on it.
|
|
2051
2082
|
// A refresh that succeeds replaces the heads outright: the PULL is read off
|
|
2052
2083
|
// what shortlist.mjs just probed, never merged with the file's own heads,
|
|
2053
2084
|
// or a ticket the refresh dropped would be pulled anyway.
|
|
2054
2085
|
let heads = unclaimed(current.entries, run);
|
|
2086
|
+
let pulls = pullsOf(heads);
|
|
2055
2087
|
let supply = current.scanned;
|
|
2056
2088
|
let dropped = current.dropped;
|
|
2057
2089
|
let fresh = null;
|
|
2090
|
+
// Short means fewer heads than B that a PULL row could name: `pull.mjs next`
|
|
2091
|
+
// skips a head whose Pull ended in this run, so counting it would hold back
|
|
2092
|
+
// the refresh that finds the head to take its slot.
|
|
2093
|
+
const counted = pullable(heads, pulls).length;
|
|
2058
2094
|
// While the bound holds a refresh cannot feed a Pull, so none of its
|
|
2059
2095
|
// triggers runs: the first tick back under the bound refreshes and Pulls in
|
|
2060
2096
|
// the same tick.
|
|
2061
2097
|
const held = run.inFlight >= caps.bound;
|
|
2062
2098
|
const trigger = refreshWhy({ draining: run.draining, held, status: current.status, entries: current.entries.length,
|
|
2063
|
-
unclaimed:
|
|
2099
|
+
unclaimed: counted, bound: caps.bound })
|
|
2064
2100
|
?? (run.draining === null && !held ? liftedPremise(run.excluded, current.entries, prs) : null);
|
|
2065
2101
|
if (trigger) {
|
|
2066
2102
|
const r = refresh();
|
|
2067
2103
|
if (r.ok) {
|
|
2068
2104
|
const excludedSet = new Set(run.excluded.map((x) => x.n));
|
|
2069
2105
|
heads = unclaimed(r.entries, run);
|
|
2106
|
+
pulls = pullsOf(heads);
|
|
2070
2107
|
supply = r.scanned;
|
|
2071
2108
|
dropped = r.dropped;
|
|
2072
2109
|
fresh = {
|
|
2073
2110
|
ok: true, trigger, entries: r.entries.length,
|
|
2074
2111
|
lifted: r.entries.filter((e) => excludedSet.has(e.n)).length,
|
|
2075
2112
|
changed: r.entries.map((e) => e.n).join(",") !== current.entries.map((e) => e.n).join(","),
|
|
2113
|
+
// The count this trigger's text names, which the implementer row prints in place of the refreshed file's.
|
|
2114
|
+
...(trigger === shortQueue(counted, caps.bound) ? { unclaimed: counted } : {}),
|
|
2076
2115
|
};
|
|
2077
2116
|
} else {
|
|
2078
2117
|
fresh = { ok: false, trigger, why: r.why };
|
|
2079
2118
|
}
|
|
2080
2119
|
}
|
|
2081
2120
|
|
|
2082
|
-
// The scratch root of the report files and the Pull records alike.
|
|
2083
|
-
const scratch = reportsScratch(receipt, scratchFlag);
|
|
2084
|
-
if (scratch === null) console.error(`${NAME}: report files and Pull records not checked — no scratch in the phase-0 receipt and no --scratch`);
|
|
2085
|
-
// Each head's Pull record read, not only the heads a free slot names: a head
|
|
2086
|
-
// whose Pull ended gives its slot to the next, so the heads past the slots
|
|
2087
|
-
// can fill one. The session it is judged against is the receipt's, as
|
|
2088
|
-
// pull.mjs judges it.
|
|
2089
|
-
const runSession = receipt.read.state === "ok" && receipt.read.receipt.session !== "" ? receipt.read.receipt.session : null;
|
|
2090
|
-
const pulls = scratch === null ? new Map()
|
|
2091
|
-
: readPulls(scratch.root, heads, { session: runSession, now: Date.now(), staleMinutes: pullStaleMinutes });
|
|
2092
2121
|
const rows = reconcile({
|
|
2093
2122
|
...caps, ...run, heads, supply, dropped, shortlistStatus: current.status, refresh: fresh, mainCheckout, phase0, router, pulls,
|
|
2094
2123
|
});
|
package/skills/run-team/SKILL.md
CHANGED
|
@@ -147,7 +147,8 @@ phase, or in any later one, asks the maintainer which tickets to take.
|
|
|
147
147
|
lifted, and when a branch-named `behind-pr` exclusion whose PR is not yet
|
|
148
148
|
MERGED or CLOSED, or a `behind-issue` exclusion whose issue is not yet
|
|
149
149
|
closed, names a ticket the file still carries, and prints
|
|
150
|
-
`REFRESHED shortlist: <n> entries; <k> lifted`.
|
|
150
|
+
`REFRESHED shortlist: <n> entries; <k> lifted`. A head whose Pull ended
|
|
151
|
+
this run is not counted. Phase 0 runs it by hand
|
|
151
152
|
once because a dead run's shortlist is stale by definition.
|
|
152
153
|
None of those triggers runs while `HOLD (in-flight <n>/<B>)` stands
|
|
153
154
|
(**Queue depth**).
|
|
@@ -249,6 +250,7 @@ unclaimed count is below the In-flight bound, when the file is missing or
|
|
|
249
250
|
empty, when an Exclusion's premise has lifted, or when a branch-named
|
|
250
251
|
`behind-pr` or a `behind-issue` Exclusion still not lifted names a ticket the
|
|
251
252
|
file carries — never while the bound holds; the open PRs come off `gh`. A
|
|
253
|
+
head whose Pull ended this run is not counted. A
|
|
252
254
|
ledger token it
|
|
253
255
|
cannot read, it refuses rather than guessing — a member counted live or gone on
|
|
254
256
|
a guess is the over- or under-dispatch it exists to prevent — so correct that
|
|
@@ -2110,6 +2112,8 @@ Read the depth off the tick and never derive it by hand: act on the row it print
|
|
|
2110
2112
|
|
|
2111
2113
|
- **shortlist** — `.fleet/shortlist.json`'s entries with no `impl-` row and no
|
|
2112
2114
|
live `excluded` row: admissible, not yet pulled. The tick's `unclaimed=`.
|
|
2115
|
+
Not after a refresh at unclaimed < B (below). A head whose Pull ended this
|
|
2116
|
+
run is not counted.
|
|
2113
2117
|
- **supply** — `scanned`, the open `ready-for-agent` tickets `shortlist.mjs`'s
|
|
2114
2118
|
candidate scan returned, before any filter. Only a Pull's full read judges
|
|
2115
2119
|
decided?, so a shortlist of undecided tickets is supply that every Pull
|
|
@@ -2149,18 +2153,12 @@ Read the depth off the tick and never derive it by hand: act on the row it print
|
|
|
2149
2153
|
|
|
2150
2154
|
**The review backlog never gates, and the merge-queue depth is still no
|
|
2151
2155
|
signal.** The only gate on a Pull is the In-flight count. A deep backlog is a
|
|
2152
|
-
`DISPATCH review` on the same tick, never a reason to idle an implementer.
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
non-empty" is a *level* condition — re-derive the deficit on **every** tick,
|
|
2159
|
-
whatever woke you: a member finishing, a member bailing before implementing, a
|
|
2160
|
-
merge landing. An edge-triggered loop that only Pulls on completion stalls
|
|
2161
|
-
silently the moment the shortlist empties, because 0 implementers emit no
|
|
2162
|
-
completion event. With the shortlist empty the table below governs — refresh,
|
|
2163
|
-
and never Pull a ticket the shortlist does not hold.
|
|
2156
|
+
`DISPATCH review` on the same tick, never a reason to idle an implementer.
|
|
2157
|
+
For the merge queue, see references/pull.md.
|
|
2158
|
+
|
|
2159
|
+
**Reconcile, do not wait for an event.** With the shortlist empty the table
|
|
2160
|
+
below governs — refresh, and never Pull a ticket the shortlist does not hold.
|
|
2161
|
+
See references/pull.md.
|
|
2164
2162
|
|
|
2165
2163
|
**Do not re-derive this by hand — `fleet-tick.mjs` computes it.** One invocation
|
|
2166
2164
|
prints per-role `actual/target` and an explicit ACTION with the backlog gate
|
|
@@ -2170,7 +2168,9 @@ the whole point: a table you must remember to consult is one you will not
|
|
|
2170
2168
|
consult under a merge-side event storm, and that is how implementers reached
|
|
2171
2169
|
0/target with one approved ticket waiting and ~57 `ready-for-agent` in supply while nobody
|
|
2172
2170
|
noticed (#3). What follows stays here as the explanation of what the script decides —
|
|
2173
|
-
never as a second, hand-run copy of it.
|
|
2171
|
+
never as a second, hand-run copy of it. The table's shortlist counts no head
|
|
2172
|
+
whose Pull ended this run. A refresh at unclaimed < B leaves `unclaimed=` at
|
|
2173
|
+
that count, so `SUGGEST /triage` can print it above 0.
|
|
2174
2174
|
|
|
2175
2175
|
| shortlist (unclaimed) | implementer row prints |
|
|
2176
2176
|
|---|---|
|
|
@@ -2186,7 +2186,8 @@ awaiting to-tickets, so an empty shortlist carrying one is not "no work".
|
|
|
2186
2186
|
|
|
2187
2187
|
The refresh runs at unclaimed < B and, with every other refresh trigger, never
|
|
2188
2188
|
while the bound holds: a refresh then cannot feed a Pull, and the first tick
|
|
2189
|
-
back under the bound refreshes and Pulls in the same tick.
|
|
2189
|
+
back under the bound refreshes and Pulls in the same tick. Unclaimed there
|
|
2190
|
+
counts no head whose Pull ended this run.
|
|
2190
2191
|
|
|
2191
2192
|
Four holds outrank every row: `HOLD (draining)` once `ledger.mjs drain` has
|
|
2192
2193
|
recorded the drain, `HOLD (tier mismatch impl-<N>)` until a replacement is
|
|
@@ -435,3 +435,16 @@ raising B past that buys nothing. 8 was rejected: peak review concurrency above
|
|
|
435
435
|
the former reviewer count, for a throughput gain the backlog does not show it
|
|
436
436
|
needs. Re-derive it from the map's guard once 20 or more PRs have merged under
|
|
437
437
|
it, never from one run's rows.
|
|
438
|
+
|
|
439
|
+
A deep `ready-to-merge` queue adds no rebases per PR — each PR rebases exactly
|
|
440
|
+
once, when it becomes the candidate — and those PRs count as In flight because
|
|
441
|
+
they sit between Claim and merge, not because the queue is deep.
|
|
442
|
+
|
|
443
|
+
## Reconcile, do not wait for an event
|
|
444
|
+
|
|
445
|
+
"A slot is free and the shortlist is
|
|
446
|
+
non-empty" is a *level* condition — re-derive the deficit on **every** tick,
|
|
447
|
+
whatever woke you: a member finishing, a member bailing before implementing, a
|
|
448
|
+
merge landing. An edge-triggered loop that only Pulls on completion stalls
|
|
449
|
+
silently the moment the shortlist empties, because 0 implementers emit no
|
|
450
|
+
completion event.
|
|
@@ -8,9 +8,14 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
|
|
|
8
8
|
- `REFRESHED shortlist: <n> entries; <k> lifted` — the tick refreshed the
|
|
9
9
|
shortlist itself, and its `PULL` row already reads the result. `REFRESH
|
|
10
10
|
FAILED` is yours: `shortlist.mjs` could not run, and the implementer row is
|
|
11
|
-
blind until it can.
|
|
11
|
+
blind until it can. When the refresh ran because fewer than B heads were
|
|
12
|
+
unclaimed (`unclaimed <u> < in-flight bound <B>`), a head whose Pull ended
|
|
13
|
+
this run not counted, the implementer row's `unclaimed=` prints that same
|
|
14
|
+
`<u>`, not the refreshed file's count.
|
|
12
15
|
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh.
|
|
13
|
-
Suggest, never run (**Queue depth**).
|
|
16
|
+
Suggest, never run (**Queue depth**). A head whose Pull already ended in
|
|
17
|
+
this run is not counted toward the refresh, so a shortlist of only such
|
|
18
|
+
heads triggers the refresh before this row prints.
|
|
14
19
|
- `specs-dropped=#<N> … (to-spec: User Stories heading)` at the end of the
|
|
15
20
|
implementer row's counts, whatever its ACTION — the tickets the to-spec
|
|
16
21
|
filter kept out of `supply`, read off the shortlist's `dropped`. Never
|