@feigi/fleet-ctl 3.88.6 → 3.88.7

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.88.6",
3
+ "version": "3.88.7",
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": {
@@ -8,8 +8,10 @@
8
8
  // append <pr> --closed-own-ticket yes|no --minted-false-claim yes|no
9
9
  // --note <text> [--sizing light|heavy] [--profile <p>] [--loc <n>] [--files <n>]
10
10
  // [--scratch <dir>]
11
- // Fills `run_date` (today's UTC date, the ruling date), `pr`, `ticket` (off
12
- // `gh pr view --json closingIssuesReferences`), `tier` and, unless
11
+ // Fills `run_date` (today's UTC date, the ruling date), `pr`, `ticket` (the
12
+ // PR's one `gh pr view --json closingIssuesReferences` issue; when the PR
13
+ // closes zero or several, the N of its one settled `impl-<N>=PR#<pr>` token
14
+ // in the run ledger), `tier` and, unless
13
15
  // `--sizing` names it, `sizing` (see below). Idempotent by
14
16
  // PR: a row already present is named and nothing is written, exit 0.
15
17
  // check [--live]
@@ -20,12 +22,14 @@
20
22
  // lack a member row (nothing from that run was checked). `--live`
21
23
  // (a run in progress; CI never passes it) adds a WARNING, never a
22
24
  // failure, per PR whose run-ledger row carries `reviewed=` but which has
23
- // no row here, except a PR that closes no issue: `append` keys a row on
24
- // exactly one ticket and refuses it, so it is named in a `note` line and
25
- // never warned about. A read of that PR that does not answer keeps the
26
- // warning, after a line naming why the read failed. Both reads of a PR's
27
- // closing references ignore an ambient GH_REPO and give `gh` 60s.
28
- // `--ledger <path>` names another ledger than the run's own.
25
+ // no row here. A PR whose closing references are not exactly one, and that
26
+ // no settled `impl-` token names, is named in a `note` line and never
27
+ // warned about: `append` has no ticket to key a row on. Where such tokens
28
+ // do name it but `append` would still refuse it, its WARNING says why. A
29
+ // read of that PR that does not answer keeps the warning, after a line
30
+ // naming why the read failed. Both reads of a PR's closing references
31
+ // ignore an ambient GH_REPO and give `gh` 60s. `--ledger <path>` names
32
+ // another ledger than the run's own.
29
33
  //
30
34
  // `tier` resolution, in order (never tie-broken by `run_date`: the two files'
31
35
  // `run_date`s mean different things — this file's header measures 92 of 267
@@ -451,12 +455,13 @@ function readLedger(ledgerFile, requireFile) {
451
455
  }
452
456
  }
453
457
 
454
- // A PR's closingIssuesReferences array, or an Error naming why it could not be
455
- // read. GH_REPO is blanked so an ambient one naming another repository never
456
- // decides which PR is read; the timeout keeps a hung `gh` from stalling
457
- // `append` or a close-out's `check --live`.
458
+ // The unique issue numbers of a PR's closingIssuesReferences, in GitHub's
459
+ // order, or an Error naming why they could not be read — a reference with no
460
+ // integer issue number among the reasons. GH_REPO is blanked so an ambient one
461
+ // naming another repository never decides which PR is read; the timeout keeps
462
+ // a hung `gh` from stalling `append` or a close-out's `check --live`.
458
463
  const GH_TIMEOUT_MS = 60_000;
459
- function closingRefs(pr) {
464
+ function closingIssueNumbers(pr) {
460
465
  let raw;
461
466
  try {
462
467
  raw = execFileSync("gh", ["pr", "view", String(pr), "--json", "closingIssuesReferences"], {
@@ -472,21 +477,56 @@ function closingRefs(pr) {
472
477
  throw new Error(`gh pr view ${pr} returned unreadable JSON: ${e.message}`);
473
478
  }
474
479
  if (!Array.isArray(refs)) throw new Error(`gh pr view ${pr} returned no closingIssuesReferences array`);
475
- return refs;
480
+ const numbers = [...new Set(refs.map((r) => r?.number))];
481
+ const unreadable = numbers.filter((n) => !Number.isInteger(n));
482
+ if (unreadable.length > 0) {
483
+ throw new Error(`gh pr view ${pr} returned a closing reference with no issue number (${unreadable.map((n) => JSON.stringify(n) ?? "undefined").join(", ")})`);
484
+ }
485
+ return numbers;
476
486
  }
477
487
 
478
- function closingTicket(pr) {
479
- let refs;
488
+ function closingNumbers(pr) {
480
489
  try {
481
- refs = closingRefs(pr);
490
+ return closingIssueNumbers(pr);
482
491
  } catch (e) {
483
492
  die(e.message);
484
493
  }
485
- const numbers = [...new Set(refs.map((r) => r?.number))];
486
- if (numbers.length !== 1 || !Number.isInteger(numbers[0])) {
487
- die(`PR #${pr} closes ${numbers.length === 0 ? "no issue" : `${numbers.length} issues (${numbers.map((n) => `#${n}`).join(", ")})`} per GitHub's closingIssuesReferences — a tier-outcomes row keys on exactly one ticket`);
494
+ }
495
+
496
+ // Every settled `impl-<N>=PR#<pr>` token in the run ledger's rows, each with the
497
+ // row that carries it and its ticket N. The dispatched list is not read.
498
+ function implementerClaims(ledger, pr) {
499
+ const claims = [];
500
+ for (const text of ledger?.rows ?? []) {
501
+ const key = text.split(/\s/)[0];
502
+ for (const t of memberTokens(text)) {
503
+ if (t.family === "impl" && t.error === null && t.outcome === `PR#${pr}`) claims.push({ key, ticket: t.number });
504
+ }
505
+ }
506
+ return claims;
507
+ }
508
+
509
+ // The ticket a PR's row keys on: its one closing issue, else the N of its one
510
+ // settled impl token. Otherwise `refusal` says why there is none, and `claims`
511
+ // counts the settled impl tokens naming the PR — none is a PR no implementer
512
+ // names. Refused: no such token, several, or several closing issues that do not
513
+ // include the token's N.
514
+ function ticketFor(pr, numbers, ledger) {
515
+ if (numbers.length === 1) return { ticket: String(numbers[0]) };
516
+ const claims = implementerClaims(ledger, pr);
517
+ const closes = numbers.length === 0 ? "closes no issue" : `closes ${numbers.length} issues (${numbers.map((n) => `#${n}`).join(", ")})`;
518
+ const refused = (why) => ({ claims: claims.length, refusal: `PR #${pr} ${closes} per GitHub's closingIssuesReferences, and ${why}` });
519
+ if (claims.length === 0) {
520
+ return refused(`no run-ledger row carries a settled impl-N=PR#${pr} token to name its ticket — a tier-outcomes row keys on exactly one ticket`);
521
+ }
522
+ if (claims.length > 1) {
523
+ return refused(`${claims.length} run-ledger rows carry a settled impl-N=PR#${pr} token (${claims.map((c) => c.key).join(", ")}) — a tier-outcomes row keys on exactly one ticket`);
524
+ }
525
+ const [{ key, ticket }] = claims;
526
+ if (numbers.length > 1 && !numbers.includes(ticket)) {
527
+ return refused(`its settled impl-${ticket}=PR#${pr} token in ${key} names #${ticket}, which is not among them`);
488
528
  }
489
- return String(numbers[0]);
529
+ return { ticket: String(ticket) };
490
530
  }
491
531
 
492
532
  // The UTC date, the convention of a session id's date and so of every Pull's
@@ -525,8 +565,9 @@ function append(pr, paths) {
525
565
  return;
526
566
  }
527
567
 
528
- const ticket = closingTicket(pr);
529
568
  const ledger = readLedger(paths.ledger, false);
569
+ const { ticket, refusal } = ticketFor(pr, closingNumbers(pr), ledger);
570
+ if (refusal) die(refusal);
530
571
  const memberRows = readMemberRows(paths.members);
531
572
  const resolved = resolveTier({ ledger, memberRows, ticket, pr });
532
573
  if (resolved.warning) console.error(`${NAME}: WARNING tier left blank for PR #${pr} (ticket #${ticket}): ${resolved.warning}`);
@@ -557,17 +598,15 @@ function append(pr, paths) {
557
598
  console.log(line);
558
599
  }
559
600
 
560
- // True only when `gh` answered and the PR's closingIssuesReferences is an empty
561
- // array. Any other outcome (gh failing, unreadable JSON, a non-array) is false
562
- // after a line naming why: a PR whose closing references cannot be read keeps
563
- // its warning. That line never reads `WARNING PR #`, which closeout.mjs turns
564
- // into a NEED.
565
- function closesNoIssue(pr) {
601
+ // A PR's closing issue numbers, or null after a line naming why they could not
602
+ // be read. That line never reads `WARNING PR #`, which closeout.mjs turns into
603
+ // a NEED; a PR whose read failed keeps its warning.
604
+ function closingNumbersOrNull(pr) {
566
605
  try {
567
- return closingRefs(pr).length === 0;
606
+ return closingIssueNumbers(pr);
568
607
  } catch (e) {
569
608
  console.error(`${NAME}: could not read the closing references of PR #${pr}: ${e.message}`);
570
- return false;
609
+ return null;
571
610
  }
572
611
  }
573
612
 
@@ -583,11 +622,15 @@ function check(paths) {
583
622
  if (has("live")) {
584
623
  const ledger = readLedger(paths.ledger, true);
585
624
  for (const pr of unrecordedReviewedPrs(ledger, rows)) {
586
- if (closesNoIssue(pr)) {
587
- console.error(`${NAME}: note PR #${pr} carries reviewed= on the ledger and closes no issue, so it takes no row in ${paths.file}`);
625
+ const numbers = closingNumbersOrNull(pr);
626
+ const { refusal, claims } = numbers === null ? {} : ticketFor(pr, numbers, ledger);
627
+ if (refusal && claims === 0) {
628
+ const closes = numbers.length === 0 ? "closes no issue" : `closes ${numbers.length} issues`;
629
+ console.error(`${NAME}: note PR #${pr} carries reviewed= on the ledger and ${closes}, and no settled impl token names it, so it takes no row in ${paths.file}`);
588
630
  continue;
589
631
  }
590
- console.error(`${NAME}: WARNING PR #${pr} carries reviewed= on the ledger but has no row in ${paths.file} — run \`tier-outcomes.mjs append ${pr}\` if it is an implementer PR`);
632
+ const next = refusal ? `\`tier-outcomes.mjs append ${pr}\` refuses it: ${refusal}` : `run \`tier-outcomes.mjs append ${pr}\` if it is an implementer PR`;
633
+ console.error(`${NAME}: WARNING PR #${pr} carries reviewed= on the ledger but has no row in ${paths.file} — ${next}`);
591
634
  }
592
635
  }
593
636
 
@@ -211,7 +211,7 @@ owes beyond its call:
211
211
  a recorded stop as the end of the run. A `NEED tier-outcomes append
212
212
  <pr>` is a PR the ledger marks reviewed with no verdict row: append it with
213
213
  the verdict you rule (**Guard: accumulate per PR**) and run `finish` again.
214
- A reviewed PR that closes no issue takes no row and is never a NEED.
214
+ A PR closing none or several: a row only if an impl token names it.
215
215
  A step it fails is explained in references/close-out.md. Exit 0 is the run
216
216
  closed.
217
217
  Drain only when a documented trigger fired, and name the trigger in `--reason`.
@@ -25,8 +25,12 @@
25
25
  stop as the end of the run. A `NEED tier-outcomes append <pr>` is a PR
26
26
  the ledger marks reviewed with no verdict row: append it with the verdict
27
27
  you rule (**Guard: accumulate per PR**) and run `finish` again. A reviewed
28
- PR that closes no issue, a `Refs`-only step for instance, takes no row: the
29
- check names it in a `note` line and never asks for one. A step it fails is
28
+ PR that closes no issue, or several, with no settled `impl-<N>=PR#<pr>`
29
+ token naming it takes no row: the check names it in a `note` line and
30
+ never asks for one. With such a token it is a NEED: `append` takes the
31
+ ticket from the token when exactly one row carries it and, for several
32
+ closing issues, it is among them; otherwise the WARNING behind the NEED
33
+ says why `append` refuses it. A step it fails is
30
34
  explained in references/close-out.md. Exit 0 is the run closed.
31
35
  Drain only when a documented trigger fired, and name the trigger in `--reason`.
32
36
  **Undrain** only when the maintainer asks, never a drain that budget or
@@ -86,9 +90,11 @@ run's member rows if they are missing, or blank each named row's `tier` and say
86
90
  why in `note`.
87
91
  Each WARNING names a PR this run's ledger marks `reviewed=` that has no row yet
88
92
  — append it before committing if it is an implementer PR. A reviewed PR that
89
- closes no issue is named in a `note` line instead: it takes no row. A line
90
- saying the PR's closing references could not be read comes before that PR's
91
- WARNING and says why.
93
+ closes no issue, or several, and that no settled `impl-<N>=PR#<pr>` token
94
+ names, is named in a `note` line instead: it takes no row. A WARNING for a PR
95
+ that such tokens name but that `append` would refuse says why. A line
96
+ saying the PR's closing references could not be read — a reference with no
97
+ issue number among the reasons — comes before that PR's WARNING and says why.
92
98
 
93
99
  ## Drain the pending ticket-features rows and refresh the cost guard
94
100