@feigi/fleet-ctl 3.82.20 → 3.82.22

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.82.20",
3
+ "version": "3.82.22",
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": {
@@ -20,7 +20,12 @@
20
20
  // lack a member row (nothing from that run was checked). `--live`
21
21
  // (a run in progress; CI never passes it) adds a WARNING, never a
22
22
  // failure, per PR whose run-ledger row carries `reviewed=` but which has
23
- // no row here. `--ledger <path>` names another ledger than the run's own.
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.
24
29
  //
25
30
  // `tier` resolution, in order (never tie-broken by `run_date`: the two files'
26
31
  // `run_date`s mean different things — this file's header measures 92 of 267
@@ -445,20 +450,37 @@ function readLedger(ledgerFile, requireFile) {
445
450
  }
446
451
  }
447
452
 
448
- function closingTicket(pr) {
453
+ // A PR's closingIssuesReferences array, or an Error naming why it could not be
454
+ // read. GH_REPO is blanked so an ambient one naming another repository never
455
+ // decides which PR is read; the timeout keeps a hung `gh` from stalling
456
+ // `append` or a close-out's `check --live`.
457
+ const GH_TIMEOUT_MS = 60_000;
458
+ function closingRefs(pr) {
449
459
  let raw;
450
460
  try {
451
- raw = execFileSync("gh", ["pr", "view", pr, "--json", "closingIssuesReferences"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
461
+ raw = execFileSync("gh", ["pr", "view", String(pr), "--json", "closingIssuesReferences"], {
462
+ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, GH_REPO: "" }, timeout: GH_TIMEOUT_MS,
463
+ });
452
464
  } catch (e) {
453
- die(`gh pr view ${pr} failed: ${e.stderr?.trim() || e.message}`);
465
+ throw new Error(`gh pr view ${pr} failed: ${e.stderr?.trim() || e.message}`);
454
466
  }
455
467
  let refs;
456
468
  try {
457
469
  refs = JSON.parse(raw).closingIssuesReferences;
458
470
  } catch (e) {
459
- die(`gh pr view ${pr} returned unreadable JSON: ${e.message}`);
471
+ throw new Error(`gh pr view ${pr} returned unreadable JSON: ${e.message}`);
472
+ }
473
+ if (!Array.isArray(refs)) throw new Error(`gh pr view ${pr} returned no closingIssuesReferences array`);
474
+ return refs;
475
+ }
476
+
477
+ function closingTicket(pr) {
478
+ let refs;
479
+ try {
480
+ refs = closingRefs(pr);
481
+ } catch (e) {
482
+ die(e.message);
460
483
  }
461
- if (!Array.isArray(refs)) die(`gh pr view ${pr} returned no closingIssuesReferences array`);
462
484
  const numbers = [...new Set(refs.map((r) => r?.number))];
463
485
  if (numbers.length !== 1 || !Number.isInteger(numbers[0])) {
464
486
  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`);
@@ -534,6 +556,20 @@ function append(pr, paths) {
534
556
  console.log(line);
535
557
  }
536
558
 
559
+ // True only when `gh` answered and the PR's closingIssuesReferences is an empty
560
+ // array. Any other outcome (gh failing, unreadable JSON, a non-array) is false
561
+ // after a line naming why: a PR whose closing references cannot be read keeps
562
+ // its warning. That line never reads `WARNING PR #`, which closeout.mjs turns
563
+ // into a NEED.
564
+ function closesNoIssue(pr) {
565
+ try {
566
+ return closingRefs(pr).length === 0;
567
+ } catch (e) {
568
+ console.error(`${NAME}: could not read the closing references of PR #${pr}: ${e.message}`);
569
+ return false;
570
+ }
571
+ }
572
+
537
573
  function check(paths) {
538
574
  for (const flag of Object.keys(APPEND_FLAGS)) {
539
575
  if (arg(flag) !== null) die(`--${flag} is an append flag`);
@@ -546,6 +582,10 @@ function check(paths) {
546
582
  if (has("live")) {
547
583
  const ledger = readLedger(paths.ledger, true);
548
584
  for (const pr of unrecordedReviewedPrs(ledger, rows)) {
585
+ if (closesNoIssue(pr)) {
586
+ console.error(`${NAME}: note PR #${pr} carries reviewed= on the ledger and closes no issue, so it takes no row in ${paths.file}`);
587
+ continue;
588
+ }
549
589
  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`);
550
590
  }
551
591
  }
@@ -211,6 +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
215
  A step it fails is explained in references/close-out.md. Exit 0 is the run
215
216
  closed.
216
217
  Drain only when a documented trigger fired, and name the trigger in `--reason`.
@@ -24,8 +24,10 @@
24
24
  `fleet-heartbeat.mjs --stop` last, because every reader takes a recorded
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
- you rule (**Guard: accumulate per PR**) and run `finish` again. A step it
28
- fails is explained in references/close-out.md. Exit 0 is the run closed.
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
30
+ explained in references/close-out.md. Exit 0 is the run closed.
29
31
  Drain only when a documented trigger fired, and name the trigger in `--reason`.
30
32
  **Undrain** only when the maintainer asks, never a drain that budget or
31
33
  context exhaustion caused (references/close-out.md).
@@ -83,7 +85,10 @@ and none could be compared, so nothing from that run was checked: append the
83
85
  run's member rows if they are missing, or blank each named row's `tier` and say
84
86
  why in `note`.
85
87
  Each WARNING names a PR this run's ledger marks `reviewed=` that has no row yet
86
- — append it before committing if it is an implementer PR.
88
+ — 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.
87
92
 
88
93
  ## Drain the pending ticket-features rows and refresh the cost guard
89
94