@feigi/fleet-ctl 3.79.1 → 3.79.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.79.1",
3
+ "version": "3.79.2",
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": {
@@ -131,8 +131,8 @@ export function computeStats(files, changedFiles) {
131
131
  // --- CLI (runs only when executed directly, never on import) ---------------
132
132
 
133
133
  // die()/numArg() shared with the other fleet scripts — see arg.mjs for the
134
- // fail-open and pipe-safety rationale. `--pr` is
135
- // the only flag read here, and numArg() rather than arg() because this file
134
+ // fail-open and pipe-safety rationale. `--pr` is the numeric flag read here
135
+ // (`--repo` is the other, a plain string for gh's own `-R`), and numArg() rather than arg() because this file
136
136
  // was the live half of a defect: the `if (!pr)` below caught a trailing
137
137
  // `--pr` — `undefined` is falsy — but never a non-numeric one, so `--pr abc`
138
138
  // reached `gh pr view abc` and printed `{"pr":null,…}` at exit 0. Measured
@@ -140,7 +140,7 @@ export function computeStats(files, changedFiles) {
140
140
  // it resolves as a BRANCH, so the stats underneath were genuine and about
141
141
  // whatever PR that branch heads, reported under a null id.
142
142
  const die = makeDie(NAME);
143
- const { numArg, sweep, stray } = defineFlags(die, { flags: { pr: "value" } });
143
+ const { arg, numArg, sweep, stray } = defineFlags(die, { flags: { pr: "value", repo: "value" } });
144
144
 
145
145
  // The one truncation rule for the raw gh value this script quotes into a
146
146
  // refusal: clip behind a visible marker, and only when the value was really
@@ -211,7 +211,17 @@ function main() {
211
211
  // required, where gh answers it truthfully as no such PR. Absent is this
212
212
  // file's own refusal and malformed is numArg()'s, per arg.mjs.
213
213
  const pr = numArg("pr");
214
- if (pr === null) die("usage: diff-stats.mjs --pr <number>");
214
+ if (pr === null) die("usage: diff-stats.mjs --pr <number> [--repo <[HOST/]OWNER/REPO | url>]");
215
+ // Which repository the PR is in. Absent, `gh` resolves it from this process's
216
+ // working directory — the right answer for a human at their own checkout and
217
+ // the wrong one for review-core.mjs's snapshot agent, which starts in the
218
+ // controller's checkout while the PR belongs to the worktree's repository: a
219
+ // PR in the cwd's repository that shares the number answers instead, exit 0,
220
+ // a well-formed `files` array and a profile that sizes another change.
221
+ // `arg()` refuses an empty or flag-shaped value, so a caller whose repository
222
+ // came back empty gets a refusal here, never the cwd fallback an empty `-R ""`
223
+ // would be.
224
+ const repo = arg("repo");
215
225
  // The misspelled-flag sweep, weaker here for the same reason as pr-overlap.mjs: `--pr` is
216
226
  // required, so a misspelled `--prr 5` already fell through to the usage die
217
227
  // above. What was silently ignored at exit 0 is a stray riding along with a
@@ -234,7 +244,7 @@ function main() {
234
244
  // fail-closed rule as run() above: a broken response is not an empty diff.
235
245
  // `changedFiles` rides the same query for free and is the only thing that
236
246
  // reveals the 100-file cap on `files` — see computeStats.
237
- const raw = run("gh", ["pr", "view", String(pr), "--json", "files,changedFiles"]);
247
+ const raw = run("gh", ["pr", "view", String(pr), "--json", "files,changedFiles", ...(repo === null ? [] : ["-R", repo])]);
238
248
  let info;
239
249
  try {
240
250
  info = JSON.parse(raw);
@@ -115,6 +115,11 @@ export const SNAPSHOT_SCHEMA = {
115
115
  // printed instead of SNAPSHOT_TREE_MATCH. Optional, because a verified
116
116
  // snapshot has nothing to say here.
117
117
  repoError: { type: "string" },
118
+ // Only when the PR-capture block printed SNAPSHOT_REPO_UNRESOLVED: the
119
+ // error `gh` gave for the worktree's repository, verbatim. Optional, so a
120
+ // resolved repository has nothing to say here; present, it is the one
121
+ // record of why the review then runs with no diff and no sizing.
122
+ repoUnresolved: { type: "string" },
118
123
  diffStats: { type: "string" },
119
124
  diffPath: { type: "string" },
120
125
  diffLines: { type: "integer" },
@@ -1071,17 +1076,41 @@ bare \`git\` — reads or writes THERE. Add nothing relative to it, and
1071
1076
  chain a \`cd\` into "$RUN" or "$SNAP" for anything you run beyond it.
1072
1077
 
1073
1078
  Then capture the PR's diff for the specialists, plus the three facts the caller
1074
- needs to judge whether it is usable:
1075
-
1076
- gh pr diff ${pr} > "$RUN"/pr.diff
1077
- crossRepo=$(gh pr view ${pr} --json isCrossRepository -q .isCrossRepository)
1078
- branch=$(gh pr view ${pr} --json headRefName -q .headRefName)
1079
- ref=""
1080
- [ "$crossRepo" = "true" ] || ref=$(git -C ${worktree} ls-remote origin "refs/heads/$branch" | cut -f1)
1081
- [ -n "$ref" ] || ref=$(git -C ${worktree} ls-remote origin "refs/pull/${pr}/head" | cut -f1)
1082
- echo "$ref"
1083
- gh pr view ${pr} --json headRefOid -q .headRefOid
1084
- wc -l < "$RUN"/pr.diff
1079
+ needs to judge whether it is usable. Every 'gh' call here names the repository
1080
+ with -R, and that repository is the one 'gh repo view' resolves from inside the
1081
+ worktree: this dispatch starts in the controller's checkout, which may be a
1082
+ different repository from the worktree's, and a 'gh' with no -R reads whichever
1083
+ PR in THAT repository shares the number — a diff, a branch and a head that
1084
+ belong to another PR entirely, with nothing in the output saying so. It is
1085
+ gh's answer rather than the raw 'origin' URL because gh resolves a checkout's
1086
+ remotes through ssh-config host aliases ('git@my-alias:owner/repo.git') and -R
1087
+ does not: handed that URL raw, every read below fails. It is also the remote gh
1088
+ would pick for any command run in that checkout — 'origin', unless an
1089
+ 'upstream' remote or 'gh repo set-default' names another:
1090
+
1091
+ REPO=$( { cd ${worktree} && gh repo view --json url -q .url; } 2>"$RUN"/repo.err )
1092
+ if [ -n "$REPO" ]; then
1093
+ gh pr diff ${pr} -R "$REPO" > "$RUN"/pr.diff
1094
+ crossRepo=$(gh pr view ${pr} -R "$REPO" --json isCrossRepository -q .isCrossRepository)
1095
+ branch=$(gh pr view ${pr} -R "$REPO" --json headRefName -q .headRefName)
1096
+ ref=""
1097
+ [ "$crossRepo" = "true" ] || ref=$(git -C ${worktree} ls-remote origin "refs/heads/$branch" | cut -f1)
1098
+ [ -n "$ref" ] || ref=$(git -C ${worktree} ls-remote origin "refs/pull/${pr}/head" | cut -f1)
1099
+ echo "$ref"
1100
+ gh pr view ${pr} -R "$REPO" --json headRefOid -q .headRefOid
1101
+ wc -l < "$RUN"/pr.diff
1102
+ else
1103
+ echo SNAPSHOT_REPO_UNRESOLVED
1104
+ cat "$RUN"/repo.err
1105
+ fi
1106
+
1107
+ If the block printed SNAPSHOT_REPO_UNRESOLVED, no 'gh pr' read ran: gh could not
1108
+ name the worktree's repository, and the controller's own repository is never a
1109
+ substitute. Report \`repoUnresolved\` = every line the block printed after
1110
+ SNAPSHOT_REPO_UNRESOLVED, verbatim — gh's own reason, such as no remote, or no
1111
+ remote on a GitHub host it is logged in to — or 'no error output' if it printed
1112
+ none. Omit \`diffPath\`, \`diffLines\`, \`refHead\` and \`prHead\`, and run
1113
+ nothing else against the PR yourself.
1085
1114
 
1086
1115
  Report \`diffPath\` = the SNAPSHOT_RUN_ROOT value with '/pr.diff' appended, ONLY
1087
1116
  if 'gh pr diff' exited 0. Report \`refHead\` = the sha the 'echo "$ref"' line
@@ -1120,9 +1149,15 @@ Report \`uncovered\` = its stdout, one line of JSON copied verbatim, ONLY if it
1120
1149
  exited 0. If it exited non-zero, report \`uncoveredError\` = its stderr and omit
1121
1150
  \`uncovered\`. Run none of the steps it lists.
1122
1151
 
1123
- Then size the diff:
1152
+ Then size the diff, naming the same repository for the same reason — diff-stats.mjs
1153
+ given no --repo reads the PR from the controller's checkout. This block resolves
1154
+ it again because it is a separate command; the capture block above already
1155
+ reported why when it could not:
1124
1156
 
1125
- ~/.fleet/bin/fleet-run diff-stats.mjs --pr ${pr}
1157
+ REPO=$(cd ${worktree} && gh repo view --json url -q .url 2>/dev/null)
1158
+ if [ -n "$REPO" ]; then ~/.fleet/bin/fleet-run diff-stats.mjs --pr ${pr} --repo "$REPO"; else echo SNAPSHOT_REPO_UNRESOLVED; fi
1159
+
1160
+ If that printed SNAPSHOT_REPO_UNRESOLVED, omit \`diffStats\`.
1126
1161
 
1127
1162
  Report \`runRoot\` = the SNAPSHOT_RUN_ROOT value and \`path\` = the SNAPSHOT_DEST
1128
1163
  value, both copied verbatim. Each ends in a component the shell substituted —
@@ -1135,8 +1170,9 @@ specialists into another run's tree. Report the HEAD sha, and — in
1135
1170
  STDOUT, copied verbatim. Only runRoot, path, head, pathVerified and repoVerified
1136
1171
  are ever required — diffStats, diffPath, diffLines, refHead and prHead are each
1137
1172
  omitted independently when their command failed, repoError only accompanies
1138
- a false repoVerified, installError only replaces installCmd and installExit,
1139
- and uncoveredError only replaces uncovered.`,
1173
+ a false repoVerified, repoUnresolved only accompanies SNAPSHOT_REPO_UNRESOLVED,
1174
+ installError only replaces installCmd and installExit, and uncoveredError only
1175
+ replaces uncovered.`,
1140
1176
  { label: `snapshot${forPr}`, phase: "Snapshot", agentType: SNAPSHOT_AGENT_TYPE, schema: SNAPSHOT_SCHEMA },
1141
1177
  );
1142
1178
 
@@ -1164,7 +1200,7 @@ and uncoveredError only replaces uncovered.`,
1164
1200
  log(
1165
1201
  usable
1166
1202
  ? `diff ${usable} (${snap.diffLines} lines)`
1167
- : `no diff — diffPath=${snap.diffPath ?? "(absent)"} diffLines=${snap.diffLines ?? "(absent)"} refHead=${snap.refHead ?? "(absent)"} prHead=${snap.prHead ?? "(absent)"} head=${snap.head} — specialists get the fallback read rules`,
1203
+ : `no diff — diffPath=${snap.diffPath ?? "(absent)"} diffLines=${snap.diffLines ?? "(absent)"} refHead=${snap.refHead ?? "(absent)"} prHead=${snap.prHead ?? "(absent)"} head=${snap.head}${typeof snap.repoUnresolved === "string" ? ` — repository unresolved: ${oneLine(snap.repoUnresolved) || "(no reason given)"}` : ""} — specialists get the fallback read rules`,
1168
1204
  );
1169
1205
 
1170
1206
  let stats = null;