@feigi/fleet-ctl 3.82.11 → 3.82.13
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
|
@@ -118,7 +118,10 @@
|
|
|
118
118
|
// row: `pull.mjs next` skips it, `next --head` would start the
|
|
119
119
|
// ticket over, so the next head fills the slot. Nor is it counted
|
|
120
120
|
// in `unclaimed=` or toward the short-queue refresh, so a
|
|
121
|
-
// shortlist of ended heads refreshes. A
|
|
121
|
+
// shortlist of ended heads refreshes. A Pull that left on an
|
|
122
|
+
// exclusion (its reason starts `excluded ·`) is no such end: its
|
|
123
|
+
// head is counted and Pulled again once the exclusion lifts. A
|
|
124
|
+
// record from another
|
|
122
125
|
// phase-0 session, or one unreadable or absent, is the fresh row:
|
|
123
126
|
// `pull.mjs next` starts or resumes it.
|
|
124
127
|
// session The listing of the session directory omp writes one
|
|
@@ -139,7 +142,7 @@
|
|
|
139
142
|
// the ledger; main() does the I/O. Split so the guard table and the reading are
|
|
140
143
|
// both unit-testable without a network.
|
|
141
144
|
|
|
142
|
-
import { parseMember, parseToken, premisesOf, rowNums, nextRetry, readRetired, REVIEW, REVIEWED } from "./ledger-grammar.mjs";
|
|
145
|
+
import { parseMember, parseToken, premisesOf, rowNums, nextRetry, readRetired, pullEnded, REVIEW, REVIEWED } from "./ledger-grammar.mjs";
|
|
143
146
|
|
|
144
147
|
// The implementer and reviewer rows' TARGET is the In-flight bound; the merge
|
|
145
148
|
// bot's is 1. Availability of work belongs in the ACTION, not the target: a
|
|
@@ -273,8 +276,10 @@ const QUESTION_NUMBERS = { staleness: "Q1", close: "Q1b", decided: "Q2", collisi
|
|
|
273
276
|
* PULL row: `{kind: "pending", qid, stale}`, `{kind: "dispatched", text}` with
|
|
274
277
|
* the text the Pull's own `ledger.mjs row` wrote (null when it recorded none),
|
|
275
278
|
* `{kind: "ended"}` for a Pull that left or was released, or null for the
|
|
276
|
-
* fresh row.
|
|
277
|
-
*
|
|
279
|
+
* fresh row. A Pull that left on an Exclusion (its reason starts `excluded ·`)
|
|
280
|
+
* is the fresh row too: it is no end, since the head claims a PULL row again
|
|
281
|
+
* once the tick's own lift logic admits it. `since` is the file's mtime and
|
|
282
|
+
* `session` this run's phase-0 session, null when the receipt names none.
|
|
278
283
|
*/
|
|
279
284
|
export function pullOf(record, { session, since, now, staleMinutes }) {
|
|
280
285
|
if (record === null || typeof record !== "object" || Array.isArray(record)) return null;
|
|
@@ -283,7 +288,14 @@ export function pullOf(record, { session, since, now, staleMinutes }) {
|
|
|
283
288
|
if (session !== null && record.session !== session) return null;
|
|
284
289
|
// pull.mjs's `next` skips a Pull that ended in this run, and `next --head`
|
|
285
290
|
// would start the ticket over: the head is no one for a PULL row to name.
|
|
286
|
-
|
|
291
|
+
// `pullEnded` is the one test both read. A Pull that left on an Exclusion is
|
|
292
|
+
// no such end, so it falls through to the fresh row. While a `behind-pr:#M`
|
|
293
|
+
// premise stands, the ledger claims the head and it never reaches here; a
|
|
294
|
+
// `behind-issue` or branch-named premise only the refreshed shortlist keeps
|
|
295
|
+
// out, by dropping its ticket (unclaimed), so after a failed refresh the
|
|
296
|
+
// head can get a PULL row that `pull.mjs` leaves again. Once the premise
|
|
297
|
+
// lifts, `next` takes it again.
|
|
298
|
+
if (pullEnded(record)) return { kind: "ended" };
|
|
287
299
|
if (record.status === "running" && typeof record.pending?.qid === "string") {
|
|
288
300
|
return { kind: "pending", qid: record.pending.qid, stale: now - since > staleMinutes * 60_000 };
|
|
289
301
|
}
|
|
@@ -308,7 +320,8 @@ function pullAction(n, p) {
|
|
|
308
320
|
}
|
|
309
321
|
|
|
310
322
|
// The heads a PULL row could name: a head whose Pull ended in this run takes
|
|
311
|
-
// no slot, and the next head takes it.
|
|
323
|
+
// no slot, and the next head takes it. A Pull that left on an Exclusion is no
|
|
324
|
+
// such end (pullOf). Order kept, so the slots fill oldest first.
|
|
312
325
|
function pullable(heads, pulls) {
|
|
313
326
|
return heads.filter((n) => pulls?.get(n)?.kind !== "ended");
|
|
314
327
|
}
|
|
@@ -366,7 +379,8 @@ function implementers(s) {
|
|
|
366
379
|
if (heads.length) return heads.slice(0, s.bound - s.inFlight).map((n) => one(pullAction(n, s.pulls?.get(n)), { acts: true }));
|
|
367
380
|
|
|
368
381
|
// Nothing to Pull even after a refresh: no unclaimed head, or only heads
|
|
369
|
-
// whose Pull ended in this run, which `pull.mjs next` skips
|
|
382
|
+
// whose Pull ended in this run, which `pull.mjs next` skips — a Pull that
|
|
383
|
+
// left on an Exclusion is no such end (pullOf). `SUGGEST /triage` asks a
|
|
370
384
|
// MAINTAINER to tick tickets, and on the unattended night the heartbeat
|
|
371
385
|
// exists for there is nobody to ask — so it prints (the fold shows it once)
|
|
372
386
|
// but is not actionable, or it would pin the interval at the base all night.
|
|
@@ -1195,10 +1209,12 @@ export function deriveRun({ rows, dispatched, drain, retired = [] }, prs, closed
|
|
|
1195
1209
|
// `behind-issue` or branch-named premise) is different: its row is rewritten
|
|
1196
1210
|
// only when a Pull dispatches the ticket (shortlist.mjs's own header, §3),
|
|
1197
1211
|
// never on its own, so once shortlist.mjs's live probe has re-admitted the
|
|
1198
|
-
// ticket to these entries, that stale row no longer blocks it.
|
|
1212
|
+
// ticket to these entries, that stale row no longer blocks it. A row naming no
|
|
1213
|
+
// premise has nothing to lift: it claims its ticket for good, as `pull.mjs
|
|
1214
|
+
// next` and shortlist.mjs read it.
|
|
1199
1215
|
export function unclaimed(entries, run) {
|
|
1200
1216
|
const unverifiable = new Set(run.excluded
|
|
1201
|
-
.filter((e) => e.premises.every(({ kind, target }) => kind !== "pr" || !isDigits(target)))
|
|
1217
|
+
.filter((e) => e.premises.length > 0 && e.premises.every(({ kind, target }) => kind !== "pr" || !isDigits(target)))
|
|
1202
1218
|
.map((e) => e.n));
|
|
1203
1219
|
return entries.map((e) => e.n).filter((n) => !run.claimed.has(n) || unverifiable.has(n));
|
|
1204
1220
|
}
|
|
@@ -1226,8 +1242,9 @@ export function parseShortlist(text) {
|
|
|
1226
1242
|
|
|
1227
1243
|
// Why this tick refreshes the shortlist, or null. `unclaimed` counts the
|
|
1228
1244
|
// heads a PULL row could name, so a head whose Pull ended in this run is not
|
|
1229
|
-
// in it. The
|
|
1230
|
-
// trigger costs gh calls, so main() asks it only when these
|
|
1245
|
+
// in it — a Pull that left on an Exclusion is no such end (pullOf). The
|
|
1246
|
+
// premise-lifted trigger costs gh calls, so main() asks it only when these
|
|
1247
|
+
// answer null.
|
|
1231
1248
|
// Nothing refreshes while the bound holds (`held`, n ≥ B, whichever HOLD row
|
|
1232
1249
|
// prints): a refresh then cannot feed a Pull, and the first tick back under
|
|
1233
1250
|
// the bound refreshes and Pulls in the same tick, so nothing is lost.
|
|
@@ -2168,8 +2185,8 @@ function main() {
|
|
|
2168
2185
|
if (scratch === null) console.error(`${NAME}: report files and Pull records not checked — no scratch in the phase-0 receipt and no --scratch`);
|
|
2169
2186
|
// Each head's Pull record read, not only the heads a free slot names: a head
|
|
2170
2187
|
// whose Pull ended gives its slot to the next, so the heads past the slots
|
|
2171
|
-
// can fill one.
|
|
2172
|
-
// pull.mjs judges it.
|
|
2188
|
+
// can fill one. A Pull that left on an Exclusion is no such end (pullOf). The
|
|
2189
|
+
// session it is judged against is the receipt's, as pull.mjs judges it.
|
|
2173
2190
|
const runSession = receipt.read.state === "ok" && receipt.read.receipt.session !== "" ? receipt.read.receipt.session : null;
|
|
2174
2191
|
const pullsOf = (hs) => scratch === null ? new Map()
|
|
2175
2192
|
: readPulls(scratch.root, hs, { session: runSession, now: Date.now(), staleMinutes: pullStaleMinutes });
|
|
@@ -2186,7 +2203,8 @@ function main() {
|
|
|
2186
2203
|
let fresh = null;
|
|
2187
2204
|
// Short means fewer heads than B that a PULL row could name: `pull.mjs next`
|
|
2188
2205
|
// skips a head whose Pull ended in this run, so counting it would hold back
|
|
2189
|
-
// the refresh that finds the head to take its slot.
|
|
2206
|
+
// the refresh that finds the head to take its slot. A Pull that left on an
|
|
2207
|
+
// Exclusion is no such end (pullOf).
|
|
2190
2208
|
const counted = pullable(heads, pulls).length;
|
|
2191
2209
|
// While the bound holds a refresh cannot feed a Pull, so none of its
|
|
2192
2210
|
// triggers runs: the first tick back under the bound refreshes and Pulls in
|
|
@@ -609,6 +609,20 @@ export function premisesOf(row) {
|
|
|
609
609
|
return [...m[1].matchAll(PREMISE)].map(([, kind, target]) => ({ kind, target }));
|
|
610
610
|
}
|
|
611
611
|
|
|
612
|
+
/** The prefix of the `outcome.reason` of a Pull that ended on an Exclusion. pull.mjs writes it on every path that
|
|
613
|
+
* ends a Pull on an Exclusion naming a premise; a row naming no premise leaves the row's own text as the reason,
|
|
614
|
+
* which carries the prefix only when the row does — that Exclusion never lifts, so neither reader takes its head
|
|
615
|
+
* again whatever the reason says. The fleet tick and `pull.mjs next` read it to tell such a Pull from one that ended
|
|
616
|
+
* for good: taken, relabelled, closed or released. */
|
|
617
|
+
export const EXCLUDED_REASON = "excluded ·";
|
|
618
|
+
|
|
619
|
+
/** Whether a Pull's `outcome.reason` says it ended on an Exclusion. */
|
|
620
|
+
const exclusionEnded = (reason) => typeof reason === "string" && reason.startsWith(EXCLUDED_REASON);
|
|
621
|
+
|
|
622
|
+
/** Whether a Pull record ended for good: released, or left for any reason but an Exclusion. The fleet tick's
|
|
623
|
+
* pullOf and `pull.mjs next` both read a Pull's end through it, so the two cannot disagree. */
|
|
624
|
+
export const pullEnded = (record) => record.status === "released" || (record.status === "left" && !exclusionEnded(record.outcome?.reason));
|
|
625
|
+
|
|
612
626
|
export const ROWS = "## Rows";
|
|
613
627
|
export const DISPATCHED = "## Dispatched";
|
|
614
628
|
export const FILED = "## Filed";
|
package/scripts/pull.mjs
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
// Reached through the Resolver and run from the MAIN checkout (a linked
|
|
9
9
|
// worktree refuses). `next` starts a Pull on the shortlist head — `--head`, else
|
|
10
10
|
// the first `.fleet/shortlist.json` entry with no `impl-` row, no live
|
|
11
|
-
// `excluded` row and no Pull of its own already ended in this phase-0 session
|
|
11
|
+
// `excluded` row and no Pull of its own already ended in this phase-0 session
|
|
12
|
+
// (a Pull that ended on an exclusion whose premise has since lifted is not
|
|
13
|
+
// ended in this sense, and its head is taken again) — or resumes the
|
|
12
14
|
// one in progress for `--head`; `answer` records one answer and resumes. Both
|
|
13
15
|
// run until the next question or the end of the Pull, and both are safe to
|
|
14
16
|
// run again: the state is on disk after every step.
|
|
@@ -132,7 +134,7 @@ import { isDigits, makeDie } from "./arg.mjs";
|
|
|
132
134
|
import { FleetDirUnresolvable, fleetFile } from "./fleet-dir.mjs";
|
|
133
135
|
import { gitEnv } from "./git-env.mjs";
|
|
134
136
|
import { isCLI } from "./is-cli.mjs";
|
|
135
|
-
import { memberTokens, premisesOf, tierValues } from "./ledger-grammar.mjs";
|
|
137
|
+
import { EXCLUDED_REASON, memberTokens, premisesOf, pullEnded, tierValues } from "./ledger-grammar.mjs";
|
|
136
138
|
import { sessionFromReceipt } from "./phase0-receipt.mjs";
|
|
137
139
|
import { briefPaths, briefText, outOfScope, strandedRuling } from "./ticket-router.mjs";
|
|
138
140
|
|
|
@@ -1133,7 +1135,7 @@ class Pull {
|
|
|
1133
1135
|
}
|
|
1134
1136
|
const r = this.script("exclude", "ledger.mjs", ["row", String(st.ticket), `excluded · ${token}`]);
|
|
1135
1137
|
if (r.status !== 0) return this.stop(withText(`ledger.mjs row failed (${r.how})`, r));
|
|
1136
|
-
return this.leave(
|
|
1138
|
+
return this.leave(`${EXCLUDED_REASON} ${token}`);
|
|
1137
1139
|
}
|
|
1138
1140
|
}
|
|
1139
1141
|
|
|
@@ -1196,7 +1198,7 @@ function heldOut(p, n) {
|
|
|
1196
1198
|
throw new Refusal(`#${n}: the premise of its exclusion could not be read, so the exclusion stands: ${standing.unread} — run again once gh answers`);
|
|
1197
1199
|
}
|
|
1198
1200
|
if (standing.token === null) return row.slice(rowKey(row).length).trim();
|
|
1199
|
-
return
|
|
1201
|
+
return `${EXCLUDED_REASON} ${standing.token}${standing.state === null ? "" : ` ${standing.state}`}`;
|
|
1200
1202
|
}
|
|
1201
1203
|
|
|
1202
1204
|
// The phase-0 session names the run a Pull belongs to: the persistent scratch
|
|
@@ -1235,7 +1237,8 @@ function shortlistHead(p, run) {
|
|
|
1235
1237
|
continue;
|
|
1236
1238
|
}
|
|
1237
1239
|
const ended = readPull(p.statePath(n));
|
|
1238
|
-
|
|
1240
|
+
// A Pull that left on an exclusion whose premise has lifted — checked above — is not an end: the head is taken again.
|
|
1241
|
+
if (ended !== null && pullEnded(ended) && (run === null || ended.session === run)) continue;
|
|
1239
1242
|
return n;
|
|
1240
1243
|
}
|
|
1241
1244
|
if (unreadable.length > 0) {
|
package/skills/run-team/SKILL.md
CHANGED
|
@@ -149,7 +149,7 @@ phase, or in any later one, asks the maintainer which tickets to take.
|
|
|
149
149
|
MERGED or CLOSED, or a `behind-issue` exclusion whose issue is not yet
|
|
150
150
|
closed, names a ticket the file still carries, and prints
|
|
151
151
|
`REFRESHED shortlist: <n> entries; <k> lifted`. A head whose Pull ended
|
|
152
|
-
this run is not counted. Phase 0 runs it by hand
|
|
152
|
+
this run is not counted, unless on a lifted exclusion. Phase 0 runs it by hand
|
|
153
153
|
once because a dead run's shortlist is stale by definition.
|
|
154
154
|
None of those triggers runs while `HOLD (in-flight <n>/<B>)` stands
|
|
155
155
|
(**Queue depth**).
|
|
@@ -452,7 +452,7 @@ unclaimed count is below the In-flight bound, when the file is missing or
|
|
|
452
452
|
empty, when an Exclusion's premise has lifted, or when a branch-named
|
|
453
453
|
`behind-pr` or a `behind-issue` Exclusion still not lifted names a ticket the
|
|
454
454
|
file carries — never while the bound holds; the open PRs come off `gh`. A
|
|
455
|
-
head whose Pull ended this run is not counted. A
|
|
455
|
+
head whose Pull ended this run is not counted, unless on a lifted exclusion. A
|
|
456
456
|
ledger token it
|
|
457
457
|
cannot read, it refuses rather than guessing — a member counted live or gone on
|
|
458
458
|
a guess is the over- or under-dispatch it exists to prevent — so correct that
|
|
@@ -583,7 +583,7 @@ Pull, and each head a free slot Pulls gets its own row, read off that head's
|
|
|
583
583
|
A head whose Pull already ended in this run — the ticket left, or the router
|
|
584
584
|
refused it and its claim was released — gets no row: `next` without `--head`
|
|
585
585
|
skips it, `next --head <N>` would start the ticket over, and the next head
|
|
586
|
-
fills the slot.
|
|
586
|
+
fills the slot. A lifted exclusion's head (reason `excluded ·`) is the exception: it is counted and Pulled again.
|
|
587
587
|
|
|
588
588
|
**A Pull never re-admits a ticket already relabelled**: the issue step reads the
|
|
589
589
|
labels fresh and exits 1 `relabelled elsewhere` once `ready-for-agent` is gone.
|
|
@@ -1718,7 +1718,7 @@ Read the depth off the tick and never derive it by hand: act on the row it print
|
|
|
1718
1718
|
- **shortlist** — `.fleet/shortlist.json`'s entries with no `impl-` row and no
|
|
1719
1719
|
live `excluded` row: admissible, not yet pulled. The tick's `unclaimed=`.
|
|
1720
1720
|
Not after a refresh at unclaimed < B (below). A head whose Pull ended this
|
|
1721
|
-
run is not counted.
|
|
1721
|
+
run is not counted, unless on a lifted exclusion.
|
|
1722
1722
|
- **supply** — `scanned`, the open `ready-for-agent` tickets `shortlist.mjs`'s
|
|
1723
1723
|
candidate scan returned, before any filter. Only a Pull's full read judges
|
|
1724
1724
|
decided?, so a shortlist of undecided tickets is supply that every Pull
|
|
@@ -1774,7 +1774,7 @@ consult under a merge-side event storm, and that is how implementers reached
|
|
|
1774
1774
|
0/target with ~57 `ready-for-agent` in supply while nobody
|
|
1775
1775
|
noticed (#3). What follows stays here as the explanation of what the script decides —
|
|
1776
1776
|
never as a second, hand-run copy of it. The table's shortlist counts no head
|
|
1777
|
-
whose Pull ended this run. A refresh at unclaimed < B leaves `unclaimed=` at
|
|
1777
|
+
whose Pull ended this run, unless on a lifted exclusion. A refresh at unclaimed < B leaves `unclaimed=` at
|
|
1778
1778
|
that count, so `SUGGEST /triage` can print it above 0.
|
|
1779
1779
|
|
|
1780
1780
|
| shortlist (unclaimed) | implementer row prints |
|
|
@@ -1792,7 +1792,7 @@ awaiting to-tickets, so an empty shortlist carrying one is not "no work".
|
|
|
1792
1792
|
The refresh runs at unclaimed < B and, with every other refresh trigger, never
|
|
1793
1793
|
while the bound holds: a refresh then cannot feed a Pull, and the first tick
|
|
1794
1794
|
back under the bound refreshes and Pulls in the same tick. Unclaimed there
|
|
1795
|
-
counts no head whose Pull ended this run.
|
|
1795
|
+
counts no head whose Pull ended this run, unless on a lifted exclusion.
|
|
1796
1796
|
|
|
1797
1797
|
Four holds outrank every row: `HOLD (draining)` once `ledger.mjs drain` has
|
|
1798
1798
|
recorded the drain, `HOLD (tier mismatch impl-<N>)` until a replacement is
|
|
@@ -10,12 +10,15 @@ The full text of each row `fleet-tick.mjs` prints and what it asks of the contro
|
|
|
10
10
|
FAILED` is yours: `shortlist.mjs` could not run, and the implementer row is
|
|
11
11
|
blind until it can. When the refresh ran because fewer than B heads were
|
|
12
12
|
unclaimed (`unclaimed <u> < in-flight bound <B>`), a head whose Pull ended
|
|
13
|
-
this run not counted
|
|
13
|
+
this run not counted (unless it ended on an exclusion whose premise has
|
|
14
|
+
lifted), the implementer row's `unclaimed=` prints that same
|
|
14
15
|
`<u>`, not the refreshed file's count.
|
|
15
16
|
- `SUGGEST /triage, hold idle` — nothing is admissible after the refresh.
|
|
16
17
|
Suggest, never run (**Queue depth**). A head whose Pull already ended in
|
|
17
18
|
this run is not counted toward the refresh, so a shortlist of only such
|
|
18
|
-
heads triggers the refresh before this row prints.
|
|
19
|
+
heads triggers the refresh before this row prints. It is not printed while the
|
|
20
|
+
refreshed shortlist holds a head whose Pull ended only on an exclusion whose
|
|
21
|
+
premise has lifted and a slot is free: that head gets its `PULL` row.
|
|
19
22
|
- `specs-dropped=#<N> … (to-spec: User Stories heading)` at the end of the
|
|
20
23
|
implementer row's counts, whatever its ACTION — the tickets the to-spec
|
|
21
24
|
filter kept out of `supply`, read off the shortlist's `dropped`. Never
|