@feigi/fleet-ctl 3.79.3 → 3.79.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/ledger.mjs +49 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feigi/fleet-ctl",
3
- "version": "3.79.3",
3
+ "version": "3.79.5",
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": {
@@ -134,10 +134,14 @@ function cause(...candidates) {
134
134
  // one stall can EXCEED it — up to GIT_TIMEOUT_MS + 20 s — when `gh` still
135
135
  // runs (measured). `check`'s worst case is the sum of whichever of its
136
136
  // children actually stall, not a single fixed term. That is three children
137
- // normally; in those layouts it is three git children, and `gh` is a fourth
138
- // only where the ledger's repository still has a work tree (a submodule) —
139
- // in the `.bare`, bare and `--separate-git-dir` layouts repoCheck() fails
140
- // and `gh` never runs (measured).
137
+ // normally; in those layouts it is three git children and `gh` is a fourth.
138
+ // Where the ledger's repository has no work tree (the `.bare` layout, a bare
139
+ // repository, a `--separate-git-dir` clone; not a submodule) repoCheck()'s
140
+ // `--show-toplevel` is refused and it asks the same directory for
141
+ // `--git-common-dir` to bind `gh` to, so there it is four git children and
142
+ // `gh` a fifth. `gh` runs in every one of those layouts. With `--file` the
143
+ // fleetFile() children drop out: two children normally, three where the
144
+ // repository has no work tree.
141
145
  //
142
146
  // `LEDGER_GIT_TIMEOUT` is this script's OWN override, in seconds, and it can
143
147
  // only ever SHORTEN. The rule and the reasons for it are net_budget's, in
@@ -156,6 +160,13 @@ function gitBudget(defaultSeconds, override) {
156
160
  }
157
161
  const GIT_TIMEOUT_MS = gitBudget(10, process.env.LEDGER_GIT_TIMEOUT);
158
162
 
163
+ // What `git rev-parse --show-toplevel` says (exit 128, under LC_ALL=C) in a
164
+ // repository that has no work tree to name. NOT fleet-dir.mjs's
165
+ // NOT_A_WORK_TREE, which also matches "not a git repository": that one is
166
+ // "there is nothing here", this one is "there is a repository, and it has no
167
+ // work tree", and only the second leaves a repository for `gh` to bind to.
168
+ const NO_WORK_TREE = /must be run in a work tree/;
169
+
159
170
  // There is ONE ledger per run, and it lives in the main checkout. Members run
160
171
  // from their own worktrees, where a cwd-relative `.fleet/ledger.md` does not
161
172
  // exist — `check` then warns and reports every subject as safe to file, which
@@ -1144,10 +1155,12 @@ function runCheck() {
1144
1155
  // the repo) the two agree, but an explicit --file naming a ledger
1145
1156
  // outside the caller's repo diverges silently: the query searches the
1146
1157
  // wrong tracker and reports a confident, empty result. Bind gh's
1147
- // cwd to the ledger's OWN repository instead of leaving it implicit. No
1148
- // --repo flag needed — gh's remote-based resolution does the rest once it
1149
- // is pointed at the right directory, and a worktree ledger (shared .git,
1150
- // own working tree) still resolves to the same repo either way.
1158
+ // cwd to the ledger's OWN repository instead of leaving it implicit —
1159
+ // its work tree's root, or its common git dir where it has no work tree
1160
+ // (see the probe below). No --repo flag needed — gh's remote-based
1161
+ // resolution does the rest once it is pointed at the right directory,
1162
+ // and a worktree ledger (shared .git, own working tree) still resolves
1163
+ // to the same repo either way.
1151
1164
  //
1152
1165
  // cwd is not the whole story: inherited git vars outrank it, so an
1153
1166
  // ambient GIT_DIR/GIT_WORK_TREE retargets BOTH the probe below and gh's
@@ -1181,7 +1194,27 @@ function runCheck() {
1181
1194
  // the missing directory instead reported every first `check` as not being
1182
1195
  // in a repository and dropped the tracker query outright.
1183
1196
  while (!existsSync(ledgerDir) && dirname(ledgerDir) !== ledgerDir) ledgerDir = dirname(ledgerDir);
1184
- const repoCheck = spawnSync("git", ["-C", ledgerDir, "rev-parse", "--show-toplevel"], { encoding: "utf8", env: queryEnv, timeout: GIT_TIMEOUT_MS });
1197
+ // LC_ALL=C on this probe alone: the no-work-tree refusal below is told
1198
+ // apart from every other failure by git's own wording, which a translated
1199
+ // git would otherwise spell differently. gh keeps `queryEnv` untouched.
1200
+ const probeEnv = { ...queryEnv, LC_ALL: "C" };
1201
+ let repoCheck = spawnSync("git", ["-C", ledgerDir, "rev-parse", "--show-toplevel"], { encoding: "utf8", env: probeEnv, timeout: GIT_TIMEOUT_MS });
1202
+ // A repository with no work tree — the `.bare` layout and its linked
1203
+ // worktrees, a bare repository, a `--separate-git-dir` clone — is where
1204
+ // fleetFile() puts the ledger INSIDE the git dir or beside it, and git
1205
+ // refuses `--show-toplevel` there ("must be run in a work tree", exit
1206
+ // 128). That is not "no repository": the same directory answers
1207
+ // `--git-common-dir`, and `gh` resolves its remote from a git dir just as
1208
+ // it does from a checkout (measured on a bare repository with a remote).
1209
+ // Bind to the common git dir — the very repository fleetFile() resolved
1210
+ // the ledger against — never to this process's cwd. ONLY that refusal
1211
+ // gets this: "not a git repository", git missing, a dubious-ownership
1212
+ // refusal, an unreadable gitfile and a probe killed at its bound all stay
1213
+ // on the failure branch below, and so does a second probe that fails.
1214
+ const noWorkTree = repoCheck.status === 128 && NO_WORK_TREE.test(repoCheck.stderr);
1215
+ if (noWorkTree) {
1216
+ repoCheck = spawnSync("git", ["-C", ledgerDir, "rev-parse", "--git-common-dir"], { encoding: "utf8", env: probeEnv, timeout: GIT_TIMEOUT_MS });
1217
+ }
1185
1218
  if (repoCheck.status !== 0) {
1186
1219
  // More than one cause lands here: a ledger path genuinely outside any
1187
1220
  // repository, but also git missing entirely (spawn ENOENT, so `status`
@@ -1225,7 +1258,13 @@ function runCheck() {
1225
1258
  const why = cause(repoCheck.error && repoCheck.error.message, repoCheck.stderr);
1226
1259
  tracker = { ok: false, query, error: `cannot resolve the ledger's repository (${ledgerDir})${why ? `: ${why}` : ""}` };
1227
1260
  } else {
1228
- const ghCwd = repoCheck.stdout.trim();
1261
+ // The work tree's root, or the common git dir where the repository has
1262
+ // no work tree. `--git-common-dir` may answer relative to the directory
1263
+ // it ran in (`.` when that is the git dir itself) or absolute, and
1264
+ // resolve() takes either. Read only past the status check above: a
1265
+ // probe that never spawned (git missing) leaves `stdout` undefined.
1266
+ const probed = repoCheck.stdout.trim();
1267
+ const ghCwd = noWorkTree ? resolve(ledgerDir, probed) : probed;
1229
1268
  try {
1230
1269
  const out = execFileSync(
1231
1270
  "gh",