@feigi/fleet-ctl 3.91.0 → 3.91.2
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/board-gather.mjs
CHANGED
|
@@ -412,10 +412,11 @@ export function mapCi(ciJson, pr) {
|
|
|
412
412
|
// A receipt with no session to give is `{ error }`, which the panel shows:
|
|
413
413
|
// the board names no session rather than guessing one. So is a recorded
|
|
414
414
|
// session whose NAME is not an omp `<ISO>_<uuid>` session directory:
|
|
415
|
-
// phase0.mjs
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
415
|
+
// phase0.mjs refuses a directory with no `<dir>.jsonl` beside it, but a
|
|
416
|
+
// receipt it wrote before that refusal, or one written by hand, can record the
|
|
417
|
+
// encoded-cwd project dir instead of its own session child, and readOmpSpend
|
|
418
|
+
// would book every session under it — the controller's own main-session
|
|
419
|
+
// transcript included — as this run's agents.
|
|
419
420
|
// A recorded directory that does not exist yet is not an error — that is
|
|
420
421
|
// gatherSpend()'s quiet `null`, below.
|
|
421
422
|
export function receiptSpendDir(stateDir) {
|
package/scripts/ledger.mjs
CHANGED
|
@@ -1984,7 +1984,8 @@ function checkReaders(where) {
|
|
|
1984
1984
|
// A `gh pr view --json <fields>` answer: an object whose every requested
|
|
1985
1985
|
// field has the type that field has. Anything else was not what was asked
|
|
1986
1986
|
// for, so it could not look.
|
|
1987
|
-
const
|
|
1987
|
+
const isString = (v) => typeof v === "string";
|
|
1988
|
+
const prFieldOk = { state: isString, labels: Array.isArray, closingIssuesReferences: Array.isArray, url: isString };
|
|
1988
1989
|
const prView = (pr, fields) => {
|
|
1989
1990
|
const what = `gh pr view ${pr} --json ${fields}`;
|
|
1990
1991
|
const { r, v } = lookJson(what, ["pr", "view", String(pr), "--json", fields]);
|
|
@@ -1997,6 +1998,34 @@ function checkReaders(where) {
|
|
|
1997
1998
|
}
|
|
1998
1999
|
return v;
|
|
1999
2000
|
};
|
|
2001
|
+
// PR `pr`'s prView() answer to `fields`, which name closingIssuesReferences
|
|
2002
|
+
// and url, as `view`, with the issue numbers, unique, of its closing
|
|
2003
|
+
// references that are in the PR's own repository: its url less
|
|
2004
|
+
// `/pull/<pr>`. A reference is in it when its url is that repository's
|
|
2005
|
+
// `/issues/<number>`, or, carrying no url, when its repository's
|
|
2006
|
+
// `<owner>/<name>` ends that repository's url. A reference with no integer
|
|
2007
|
+
// number, or with neither a url nor a repository to place it by, could not
|
|
2008
|
+
// look: it is never taken for the PR's own repository's.
|
|
2009
|
+
const prViewClosing = (pr, fields) => {
|
|
2010
|
+
const view = prView(pr, fields);
|
|
2011
|
+
const what = `gh pr view ${pr} --json ${fields}`;
|
|
2012
|
+
const suffix = `/pull/${pr}`;
|
|
2013
|
+
if (!view.url.endsWith(suffix)) die(`${where}: could not look — ${what} answered url ${view.url}, not PR #${pr}'s; nothing written`);
|
|
2014
|
+
const own = view.url.slice(0, -suffix.length);
|
|
2015
|
+
const str = (v) => typeof v === "string" && v !== "";
|
|
2016
|
+
const numbers = new Set();
|
|
2017
|
+
for (const ref of view.closingIssuesReferences) {
|
|
2018
|
+
const n = ref?.number;
|
|
2019
|
+
if (!Number.isInteger(n)) die(`${where}: could not look — ${what} answered a reference with no issue number; nothing written`);
|
|
2020
|
+
const repo = ref.repository;
|
|
2021
|
+
let isOwn;
|
|
2022
|
+
if (str(ref.url)) isOwn = ref.url === `${own}/issues/${n}`;
|
|
2023
|
+
else if (str(repo?.name) && str(repo?.owner?.login)) isOwn = own.endsWith(`/${repo.owner.login}/${repo.name}`);
|
|
2024
|
+
else die(`${where}: could not look — ${what} answered a reference to #${n} with neither a url nor a repository; nothing written`);
|
|
2025
|
+
if (isOwn) numbers.add(n);
|
|
2026
|
+
}
|
|
2027
|
+
return { view, numbers: [...numbers] };
|
|
2028
|
+
};
|
|
2000
2029
|
// The title of issue `n`, as `gh issue view <n> --json title` answers it.
|
|
2001
2030
|
const issueTitle = (n) => {
|
|
2002
2031
|
const what = `gh issue view ${n} --json title`;
|
|
@@ -2006,28 +2035,27 @@ function checkReaders(where) {
|
|
|
2006
2035
|
}
|
|
2007
2036
|
return v.title;
|
|
2008
2037
|
};
|
|
2009
|
-
return { repoDir, look, verifyShaOf, prView, issueTitle };
|
|
2038
|
+
return { repoDir, look, verifyShaOf, prView, prViewClosing, issueTitle };
|
|
2010
2039
|
}
|
|
2011
2040
|
|
|
2012
2041
|
// The finisher gate's closing-reference checks for the PR `member` finishes,
|
|
2013
|
-
// read off the PR's closingIssuesReferences
|
|
2014
|
-
//
|
|
2015
|
-
//
|
|
2016
|
-
//
|
|
2017
|
-
//
|
|
2018
|
-
//
|
|
2019
|
-
//
|
|
2020
|
-
//
|
|
2021
|
-
//
|
|
2042
|
+
// read off the PR's closingIssuesReferences in its own repository — a
|
|
2043
|
+
// reference into another repository closes no issue here, whatever its
|
|
2044
|
+
// number — and the titles of the issues they name; its second argument is
|
|
2045
|
+
// rulingOf()'s answer, which let the PR through. Check 1, when a settled
|
|
2046
|
+
// implementer's token names the PR: each ticket it names is closed by the
|
|
2047
|
+
// body when the PR's ruling is closed_own_ticket=yes, and is not when the
|
|
2048
|
+
// ruling is no. Check 2, for every PR: no reference may be the
|
|
2049
|
+
// suggestion-band record of this PR's review — an issue whose title, trimmed,
|
|
2050
|
+
// is recordTitle(), as dispositions-check.mjs finds the record. Returns one
|
|
2051
|
+
// line per failure. A read the checks need dies, naming the read, and writes
|
|
2052
|
+
// nothing.
|
|
2022
2053
|
async function closingRefusals(member, { tickets, ruling, tsv }) {
|
|
2023
2054
|
const pr = member.number;
|
|
2024
2055
|
const where = `dispatch ${member.name}`;
|
|
2025
2056
|
const { recordTitle } = await import("./dispositions-check.mjs");
|
|
2026
|
-
const {
|
|
2027
|
-
const refs =
|
|
2028
|
-
if (!refs.every(Number.isInteger)) {
|
|
2029
|
-
die(`${where}: could not look — gh pr view ${pr} --json closingIssuesReferences answered a reference with no issue number; nothing written`);
|
|
2030
|
-
}
|
|
2057
|
+
const { prViewClosing, issueTitle } = checkReaders(where);
|
|
2058
|
+
const refs = prViewClosing(pr, "closingIssuesReferences,url").numbers;
|
|
2031
2059
|
const failures = [];
|
|
2032
2060
|
if (tickets.length > 0) {
|
|
2033
2061
|
const verdict = ruling.closed_own_ticket;
|
|
@@ -2491,7 +2519,8 @@ function recordFiled(id, subject) {
|
|
|
2491
2519
|
// checked before anything is written:
|
|
2492
2520
|
//
|
|
2493
2521
|
// impl-N=PR#M verify-sha.sh <branch> <head> exits 0; PR M
|
|
2494
|
-
// reads OPEN and its closingIssuesReferences
|
|
2522
|
+
// reads OPEN and its closingIssuesReferences in its
|
|
2523
|
+
// own repository name N;
|
|
2495
2524
|
// the Check-step record (below) holds for <head>
|
|
2496
2525
|
// impl-N=bailed bailCause is not null; the receipt names it
|
|
2497
2526
|
// fix-pr-M=applied:<head> origin's refs/heads/<branch> is <head>, fetched
|
|
@@ -2603,7 +2632,7 @@ async function runSettleFromReport() {
|
|
|
2603
2632
|
if (report.member !== member) invalid(`$.member: ${JSON.stringify(report.member)} is not ${member}`);
|
|
2604
2633
|
|
|
2605
2634
|
// The gh and git reads this check makes, shared with the finisher gate.
|
|
2606
|
-
const { repoDir, look, verifyShaOf, prView } = checkReaders(where);
|
|
2635
|
+
const { repoDir, look, verifyShaOf, prView, prViewClosing } = checkReaders(where);
|
|
2607
2636
|
// origin's head of `branch`, or null when origin has no such branch.
|
|
2608
2637
|
const remoteHead = (branch) => {
|
|
2609
2638
|
const ref = `refs/heads/${branch}`;
|
|
@@ -2653,9 +2682,9 @@ async function runSettleFromReport() {
|
|
|
2653
2682
|
const sha = verifyShaOf(report.branch, report.head);
|
|
2654
2683
|
check(`verify-sha.sh ${report.branch} ${report.head} exits 0`, sha.status === 0);
|
|
2655
2684
|
recipeGate(report.head);
|
|
2656
|
-
const pr =
|
|
2685
|
+
const { view: pr, numbers } = prViewClosing(report.pr, "state,closingIssuesReferences,url");
|
|
2657
2686
|
check(`PR #${report.pr} reads OPEN`, pr.state === "OPEN");
|
|
2658
|
-
check(`PR #${report.pr} closes #${live.number}`,
|
|
2687
|
+
check(`PR #${report.pr} closes #${live.number}`, numbers.includes(live.number));
|
|
2659
2688
|
} else {
|
|
2660
2689
|
outcome = "bailed";
|
|
2661
2690
|
check(`bailCause: ${report.bailCause}`, report.bailCause !== null);
|
package/scripts/phase0.mjs
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
// The session directory is `--session`, else `PI_ARTIFACTS_DIR`, which omp
|
|
9
9
|
// exports to its Python eval kernel and the processes that kernel starts (the
|
|
10
10
|
// bash tool's environment does not carry it); with neither, exit 2. It is
|
|
11
|
-
// accepted when it is
|
|
12
|
-
//
|
|
13
|
-
// writes there.
|
|
11
|
+
// accepted when `<dir>.jsonl` exists beside it and the path is a directory or
|
|
12
|
+
// does not exist yet — omp creates the directory when the session's first
|
|
13
|
+
// subagent writes there. An existing directory with no `<dir>.jsonl` beside
|
|
14
|
+
// it names no session (the directory holding every session of a checkout is
|
|
15
|
+
// one): from either source it exits 2 naming the path, writing no receipt.
|
|
14
16
|
//
|
|
15
17
|
// Reached through the Resolver and run from the MAIN checkout (a linked
|
|
16
18
|
// worktree refuses: the checks below judge the checkout the controller reads
|
|
@@ -528,15 +530,19 @@ function main() {
|
|
|
528
530
|
}
|
|
529
531
|
const source = given === null ? "PI_ARTIFACTS_DIR" : "--session";
|
|
530
532
|
const session = resolve(given ?? fromEnv);
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
533
|
+
// A session is named by its transcript, `<session>.jsonl`, beside the
|
|
534
|
+
// directory: an existing directory with none beside it — the one holding
|
|
535
|
+
// every session of a checkout, say — names no session. The directory itself
|
|
536
|
+
// may not exist yet: omp creates it when the session's first subagent writes
|
|
537
|
+
// there.
|
|
538
|
+
const transcript = existsSync(`${session}.jsonl`);
|
|
534
539
|
try {
|
|
535
540
|
if (!statSync(session).isDirectory()) die(`${source} ${session} is not a directory`);
|
|
541
|
+
if (!transcript) die(`${source} ${session} has no ${session}.jsonl beside it, so it names no session`);
|
|
536
542
|
} catch (e) {
|
|
537
543
|
if (e.code === undefined) throw e;
|
|
538
544
|
if (e.code !== "ENOENT") die(`${source} ${session} is not readable (${e.code})`);
|
|
539
|
-
if (!
|
|
545
|
+
if (!transcript) die(`${source} ${session} does not exist, and no ${session}.jsonl beside it names a session`);
|
|
540
546
|
}
|
|
541
547
|
const positive = (name) => {
|
|
542
548
|
const n = F.numArg(name);
|
|
@@ -117,7 +117,7 @@ and will act on it. Relay everything — reversals too — then dispatch.
|
|
|
117
117
|
append` command to run. Any row for the PR passes, so a replacement finisher
|
|
118
118
|
is never held once the first passed; a PR no such token names is not gated.
|
|
119
119
|
No flag skips it: a PR `append` refuses is fixed on the ledger, as at close-out.
|
|
120
|
-
- **The PR's closing references match its ruling and exclude its own suggestion-band record** → `ledger.mjs dispatch` reads the PR's `closingIssuesReferences` from `gh pr view <M>` before it dispatches any finisher. When the ruled row says `closed_own_ticket=yes`, each ticket a settled `impl-<N>=PR#<M>` token names must be closed by the body (`Closes #<N>`); when it says `no`, none may be, and no reference may be the PR's own suggestion-band record. A blank verdict is refused until it is set. A refusal names the PR and the ticket. The controller repairs the body itself with `gh pr edit <M> --body-file <file>`, then dispatches the finisher again. A `gh` read that fails refuses the dispatch and writes nothing.
|
|
120
|
+
- **The PR's closing references match its ruling and exclude its own suggestion-band record** → `ledger.mjs dispatch` reads the PR's `closingIssuesReferences` from `gh pr view <M>` before it dispatches any finisher, counting only those in the PR's own repository: a reference into another repository closes none of this repository's tickets, whatever its number. When the ruled row says `closed_own_ticket=yes`, each ticket a settled `impl-<N>=PR#<M>` token names must be closed by the body (`Closes #<N>`); when it says `no`, none may be, and no reference may be the PR's own suggestion-band record. A blank verdict is refused until it is set. A refusal names the PR and the ticket. The controller repairs the body itself with `gh pr edit <M> --body-file <file>`, then dispatches the finisher again. A `gh` read that fails refuses the dispatch and writes nothing.
|
|
121
121
|
|
|
122
122
|
Gate on the `check` job, **not** on `ci-state --quiet` exit 0: a behind PR never
|
|
123
123
|
reaches full green, so an exit-0 gate strands it unlabelled. The finisher reads
|
|
@@ -7,7 +7,12 @@ environment carries `PI_ARTIFACTS_DIR`, this run's own session directory,
|
|
|
7
7
|
which phase 0 records in its receipt; the bash tool's does not, so there
|
|
8
8
|
phase 0 exits 2. `expanduser` is required: a literal `~/…` fails with
|
|
9
9
|
`FileNotFoundError`. So is the `stderr` redirect: the cell never shows a
|
|
10
|
-
child's stderr. `--session <dir>` overrides the variable.
|
|
10
|
+
child's stderr. `--session <dir>` overrides the variable. Either way the
|
|
11
|
+
directory must have its transcript `<dir>.jsonl` beside it; the directory
|
|
12
|
+
itself may not exist yet. An existing directory with no `<dir>.jsonl` beside
|
|
13
|
+
it — `~/.omp/agent/sessions/<encoded-cwd>`, which holds every session of the
|
|
14
|
+
checkout — names no session: phase 0 exits 2 naming the path and where it
|
|
15
|
+
came from, and writes no receipt.
|
|
11
16
|
|
|
12
17
|
Exit 3 is a step that failed: its line carries the refusal, so fix the cause
|
|
13
18
|
and run it again.
|