@mutmutco/cli 3.84.0 → 3.86.0
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/dist/main.cjs +218 -110
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3415,7 +3415,7 @@ function useColor() {
|
|
|
3415
3415
|
var program = new Command();
|
|
3416
3416
|
|
|
3417
3417
|
// src/index.ts
|
|
3418
|
-
var
|
|
3418
|
+
var import_promises11 = require("node:fs/promises");
|
|
3419
3419
|
var import_node_fs36 = require("node:fs");
|
|
3420
3420
|
var import_node_child_process16 = require("node:child_process");
|
|
3421
3421
|
|
|
@@ -3982,10 +3982,10 @@ var import_node_fs4 = require("node:fs");
|
|
|
3982
3982
|
var injectedStdin;
|
|
3983
3983
|
function stdinHasPipedInput(statFd = () => (0, import_node_fs4.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
|
|
3984
3984
|
try {
|
|
3985
|
-
const
|
|
3986
|
-
if (
|
|
3987
|
-
if (
|
|
3988
|
-
if (
|
|
3985
|
+
const stat3 = statFd();
|
|
3986
|
+
if (stat3.isFIFO() || stat3.isFile()) return true;
|
|
3987
|
+
if (stat3.isCharacterDevice()) return false;
|
|
3988
|
+
if (stat3.isSocket()) return false;
|
|
3989
3989
|
if (getIsTTY() === true) return false;
|
|
3990
3990
|
return true;
|
|
3991
3991
|
} catch {
|
|
@@ -4502,9 +4502,9 @@ function trackedPathStatus(c, realPath, repoAnchor) {
|
|
|
4502
4502
|
return null;
|
|
4503
4503
|
}
|
|
4504
4504
|
}
|
|
4505
|
-
function rootScratchDirSnapshot(root, readdir2,
|
|
4505
|
+
function rootScratchDirSnapshot(root, readdir2, stat3) {
|
|
4506
4506
|
try {
|
|
4507
|
-
const rootStat =
|
|
4507
|
+
const rootStat = stat3(root);
|
|
4508
4508
|
let mtimeMs = rootStat.mtimeMs;
|
|
4509
4509
|
let bytes = 0;
|
|
4510
4510
|
const stack = [root];
|
|
@@ -4518,7 +4518,7 @@ function rootScratchDirSnapshot(root, readdir2, stat2) {
|
|
|
4518
4518
|
return null;
|
|
4519
4519
|
} catch {
|
|
4520
4520
|
}
|
|
4521
|
-
const st =
|
|
4521
|
+
const st = stat3(child2);
|
|
4522
4522
|
mtimeMs = Math.max(mtimeMs, st.mtimeMs);
|
|
4523
4523
|
if (ent.isDirectory()) stack.push(child2);
|
|
4524
4524
|
else if (ent.isFile()) bytes += st.size;
|
|
@@ -4531,7 +4531,7 @@ function rootScratchDirSnapshot(root, readdir2, stat2) {
|
|
|
4531
4531
|
}
|
|
4532
4532
|
function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
4533
4533
|
const readdir2 = deps.readdir ?? import_node_fs6.readdirSync;
|
|
4534
|
-
const
|
|
4534
|
+
const stat3 = deps.stat ?? import_node_fs6.statSync;
|
|
4535
4535
|
const plansRoot = (0, import_node_path4.join)(repoRoot2, "plans");
|
|
4536
4536
|
const rootScratchFiles = [];
|
|
4537
4537
|
try {
|
|
@@ -4544,10 +4544,10 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
|
4544
4544
|
const full = (0, import_node_path4.join)(repoRoot2, ent.name);
|
|
4545
4545
|
try {
|
|
4546
4546
|
if (isDir) {
|
|
4547
|
-
const snap = rootScratchDirSnapshot(full, readdir2,
|
|
4547
|
+
const snap = rootScratchDirSnapshot(full, readdir2, stat3);
|
|
4548
4548
|
if (snap) rootScratchFiles.push({ path: full, dir: repoRoot2, name: ent.name, mtimeMs: snap.mtimeMs, bytes: snap.bytes, kind: "dir" });
|
|
4549
4549
|
} else {
|
|
4550
|
-
const st =
|
|
4550
|
+
const st = stat3(full);
|
|
4551
4551
|
rootScratchFiles.push({ path: full, dir: repoRoot2, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size, kind: "file" });
|
|
4552
4552
|
}
|
|
4553
4553
|
} catch {
|
|
@@ -4561,7 +4561,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
|
4561
4561
|
if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
|
|
4562
4562
|
const full = (0, import_node_path4.join)(plansRoot, ent.name);
|
|
4563
4563
|
try {
|
|
4564
|
-
const st =
|
|
4564
|
+
const st = stat3(full);
|
|
4565
4565
|
planMdFiles.push({ path: full, dir: plansRoot, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size, kind: "file" });
|
|
4566
4566
|
} catch {
|
|
4567
4567
|
}
|
|
@@ -6371,6 +6371,14 @@ var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
|
|
|
6371
6371
|
var PKG = "package.json";
|
|
6372
6372
|
var LOCKFILE = "package-lock.json";
|
|
6373
6373
|
var NODE_MODULES = "node_modules";
|
|
6374
|
+
var LOCKFILE_INSTALLS = [
|
|
6375
|
+
{ lockfile: "bun.lock", command: "bun install --frozen-lockfile" },
|
|
6376
|
+
{ lockfile: "bun.lockb", command: "bun install --frozen-lockfile" },
|
|
6377
|
+
{ lockfile: "pnpm-lock.yaml", command: "pnpm install --frozen-lockfile" },
|
|
6378
|
+
{ lockfile: "yarn.lock", command: "yarn install --immutable" },
|
|
6379
|
+
{ lockfile: LOCKFILE, command: "npm ci" }
|
|
6380
|
+
];
|
|
6381
|
+
var NO_LOCKFILE_INSTALL = "npm install --no-package-lock";
|
|
6374
6382
|
var realFsProbe = {
|
|
6375
6383
|
isDir: (p) => {
|
|
6376
6384
|
try {
|
|
@@ -6397,18 +6405,19 @@ var realFsProbe = {
|
|
|
6397
6405
|
function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
6398
6406
|
const factsFor = (dir) => {
|
|
6399
6407
|
const abs = dir ? (0, import_node_path10.join)(root, dir) : root;
|
|
6408
|
+
const match = LOCKFILE_INSTALLS.find((c) => fs2.isFile((0, import_node_path10.join)(abs, c.lockfile)));
|
|
6400
6409
|
return {
|
|
6401
6410
|
dir,
|
|
6402
6411
|
hasPackageJson: fs2.isFile((0, import_node_path10.join)(abs, PKG)),
|
|
6403
|
-
|
|
6404
|
-
|
|
6412
|
+
hasNodeModules: fs2.isDir((0, import_node_path10.join)(abs, NODE_MODULES)),
|
|
6413
|
+
install: match?.command
|
|
6405
6414
|
};
|
|
6406
6415
|
};
|
|
6407
6416
|
const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
|
|
6408
6417
|
return [factsFor(""), ...children.map(factsFor).filter((f) => f.hasPackageJson)];
|
|
6409
6418
|
}
|
|
6410
6419
|
function npmInstallTargets(dirs) {
|
|
6411
|
-
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.
|
|
6420
|
+
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.install ?? NO_LOCKFILE_INSTALL }));
|
|
6412
6421
|
}
|
|
6413
6422
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
6414
6423
|
return fs2.isFile((0, import_node_path10.join)(root, ".git"));
|
|
@@ -6432,9 +6441,9 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
6432
6441
|
const allDirs = scanInstallDirs(worktreeRoot, fs2);
|
|
6433
6442
|
const targets = npmInstallTargets(allDirs);
|
|
6434
6443
|
if (deps.validateInstall) {
|
|
6435
|
-
for (const dir of allDirs.filter((d) => d.hasPackageJson && d.
|
|
6444
|
+
for (const dir of allDirs.filter((d) => d.hasPackageJson && d.install && d.hasNodeModules)) {
|
|
6436
6445
|
const cwd = dir.dir ? (0, import_node_path10.join)(worktreeRoot, dir.dir) : worktreeRoot;
|
|
6437
|
-
if (!await deps.validateInstall(cwd)) targets.push({ dir: dir.dir, command:
|
|
6446
|
+
if (!await deps.validateInstall(cwd)) targets.push({ dir: dir.dir, command: dir.install });
|
|
6438
6447
|
}
|
|
6439
6448
|
}
|
|
6440
6449
|
const targetDirs = new Set(targets.map((target) => target.dir));
|
|
@@ -7978,6 +7987,7 @@ function parseVerifyBroker(stdout) {
|
|
|
7978
7987
|
|
|
7979
7988
|
// src/train-apply.ts
|
|
7980
7989
|
var import_node_fs13 = require("node:fs");
|
|
7990
|
+
var import_promises2 = require("node:fs/promises");
|
|
7981
7991
|
var import_node_path12 = require("node:path");
|
|
7982
7992
|
function resolveDeployModel2(meta, repo) {
|
|
7983
7993
|
const m = meta?.deployModel;
|
|
@@ -8484,20 +8494,30 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
8484
8494
|
const number = parsePrNumber(url);
|
|
8485
8495
|
return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `development requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}`);
|
|
8486
8496
|
}
|
|
8497
|
+
var ALIGNMENT_ARM_ATTEMPTS = 3;
|
|
8498
|
+
var ALIGNMENT_ARM_BACKOFF_MS = 2e3;
|
|
8487
8499
|
async function enqueueAlignmentAutoMerge(deps, ctx, prNumber, prUrl, openedNote) {
|
|
8488
8500
|
const base = { status: "pr-pending", prNumber, prUrl };
|
|
8489
8501
|
const retryCommand = `mmi-cli pr merge ${prNumber ?? "<number>"} --auto --merge`;
|
|
8490
8502
|
const manual = `${openedNote} \u2014 land it with \`${retryCommand}\``;
|
|
8491
8503
|
if (deps.mergeAuto && prNumber !== void 0) {
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8504
|
+
const sleep3 = resolveSleep(deps);
|
|
8505
|
+
let lastError;
|
|
8506
|
+
for (let attempt = 1; attempt <= ALIGNMENT_ARM_ATTEMPTS; attempt += 1) {
|
|
8507
|
+
try {
|
|
8508
|
+
const res = await deps.mergeAuto(String(prNumber), ctx.repo);
|
|
8509
|
+
if (res.mergeStatus !== "failed") {
|
|
8510
|
+
return { ...base, autoMergeEnqueued: true, note: `${openedNote} \u2014 auto-merge enqueued (merges when checks pass)` };
|
|
8511
|
+
}
|
|
8512
|
+
lastError = res.error ?? "unknown error";
|
|
8513
|
+
} catch (e) {
|
|
8514
|
+
lastError = `enqueue threw (${e instanceof Error ? e.message : String(e)})`;
|
|
8496
8515
|
}
|
|
8497
|
-
|
|
8498
|
-
} catch (e) {
|
|
8499
|
-
deps.warn?.(`alignment PR #${prNumber} auto-merge enqueue threw (${e instanceof Error ? e.message : String(e)}) \u2014 re-run: ${retryCommand}`);
|
|
8516
|
+
if (attempt < ALIGNMENT_ARM_ATTEMPTS) await sleep3(ALIGNMENT_ARM_BACKOFF_MS * attempt);
|
|
8500
8517
|
}
|
|
8518
|
+
deps.warn?.(
|
|
8519
|
+
`alignment PR #${prNumber} auto-merge not armed after ${ALIGNMENT_ARM_ATTEMPTS} attempts (${lastError ?? "unknown error"}) \u2014 re-run: ${retryCommand}`
|
|
8520
|
+
);
|
|
8501
8521
|
}
|
|
8502
8522
|
return { ...base, note: manual };
|
|
8503
8523
|
}
|
|
@@ -8835,6 +8855,10 @@ async function enumerateOwnWorkflowRuns(deps, repo, headSha) {
|
|
|
8835
8855
|
const runs = await discoverShaWorkflowRuns(deps, repo, headSha, /* @__PURE__ */ new Set());
|
|
8836
8856
|
return runs.length ? runs : [{ workflow: `sha-enumeration(${headSha.slice(0, 7)}) no runs yet`, conclusion: "pending" }];
|
|
8837
8857
|
}
|
|
8858
|
+
function withReleaseRunObligation(runs) {
|
|
8859
|
+
if (runs.some((r) => r.event === "release")) return runs;
|
|
8860
|
+
return [...runs, { workflow: "release-event deploy run not created yet", conclusion: "pending" }];
|
|
8861
|
+
}
|
|
8838
8862
|
var NON_DEPLOY_EVENTS = /* @__PURE__ */ new Set([
|
|
8839
8863
|
"pull_request",
|
|
8840
8864
|
"pull_request_target",
|
|
@@ -8884,6 +8908,7 @@ async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
|
|
|
8884
8908
|
workflow: row.workflowName ?? `run:${row.databaseId}`,
|
|
8885
8909
|
runId: row.databaseId,
|
|
8886
8910
|
runUrl: row.url,
|
|
8911
|
+
event: row.event,
|
|
8887
8912
|
// An in-flight run is reported UNVERIFIED, never watched. Blocking on an arbitrary discovered run
|
|
8888
8913
|
// would let one long push workflow hang the release command and mask the failed rows behind it, and
|
|
8889
8914
|
// `gh run watch` has no timeout. The NAMED targets are the ones worth blocking on (also #3382 review).
|
|
@@ -8913,7 +8938,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
8913
8938
|
const note = ref === "rc" ? "no dispatch on rc: registry-publish repos have no rc-stage Release to publish from" : "no central dispatch: this repo's own publish.yml auto-fires on the published Release (#2428)";
|
|
8914
8939
|
if (ref === "rc" || !autoRunHeadSha) return { note, deployStatus: "pending" };
|
|
8915
8940
|
if (!watch) {
|
|
8916
|
-
const listed = await enumerateOwnWorkflowRuns(deps, ctx.repo, autoRunHeadSha);
|
|
8941
|
+
const listed = withReleaseRunObligation(await enumerateOwnWorkflowRuns(deps, ctx.repo, autoRunHeadSha));
|
|
8917
8942
|
return { note, workflowRuns: listed, deployStatus: aggregateWorkflowRuns(listed) };
|
|
8918
8943
|
}
|
|
8919
8944
|
const since = autoRunSince ?? (deps.now ?? Date.now)();
|
|
@@ -8933,7 +8958,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
8933
8958
|
if (!autoRunHeadSha) return { note, deployStatus: "pending" };
|
|
8934
8959
|
if (!watch) {
|
|
8935
8960
|
if (ref === "rc") return { note, deployStatus: "pending" };
|
|
8936
|
-
const listed = await enumerateOwnWorkflowRuns(deps, HUB_REPO, autoRunHeadSha);
|
|
8961
|
+
const listed = withReleaseRunObligation(await enumerateOwnWorkflowRuns(deps, HUB_REPO, autoRunHeadSha));
|
|
8937
8962
|
return { note, workflowRuns: listed, deployStatus: aggregateWorkflowRuns(listed) };
|
|
8938
8963
|
}
|
|
8939
8964
|
const since = autoRunSince ?? (deps.now ?? Date.now)();
|
|
@@ -9015,35 +9040,41 @@ async function executeMergeToMain(deps, sourceRef, mergeLabel, tolerated, predic
|
|
|
9015
9040
|
}
|
|
9016
9041
|
}
|
|
9017
9042
|
async function probeFoldFailureState(deps) {
|
|
9018
|
-
const branch = await currentBranch(deps);
|
|
9043
|
+
const branch = await currentBranch(deps).catch(() => "");
|
|
9019
9044
|
const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
|
|
9020
|
-
const
|
|
9045
|
+
const headSha = await deps.run("git", ["rev-parse", "HEAD"]).then(clean2).catch(() => "");
|
|
9046
|
+
const status = await deps.run("git", ["status", "--porcelain=v1"]).then(clean2).catch((e) => `(unavailable: ${e instanceof Error ? e.message : String(e)})`);
|
|
9021
9047
|
const mainSha = await deps.run("git", ["rev-parse", "main"]).then(clean2).catch(() => "");
|
|
9022
9048
|
const originMainSha = await deps.run("git", ["rev-parse", "origin/main"]).then(clean2).catch(() => "");
|
|
9023
9049
|
const originIsAncestorOfMain = Boolean(mainSha) && Boolean(originMainSha) ? await deps.run("git", ["merge-base", "--is-ancestor", "origin/main", "main"]).then(() => true).catch(() => false) : false;
|
|
9024
|
-
return { branch, mergeInProgress,
|
|
9050
|
+
return { branch, mergeInProgress, headSha, status, mainSha, originMainSha, originIsAncestorOfMain };
|
|
9025
9051
|
}
|
|
9026
9052
|
function shaLabel(sha) {
|
|
9027
9053
|
return sha ? sha.slice(0, 7) : "(unknown)";
|
|
9028
9054
|
}
|
|
9029
|
-
async function finishFoldAutoRestore(deps, causeMessage, startBranch, what) {
|
|
9055
|
+
async function finishFoldAutoRestore(deps, causeMessage, startBranch, preFoldMainSha, resumeCommand, what) {
|
|
9030
9056
|
try {
|
|
9031
9057
|
await deps.run("git", ["checkout", startBranch]);
|
|
9032
9058
|
} catch (e) {
|
|
9033
|
-
return
|
|
9059
|
+
return foldFailureGuidance(
|
|
9034
9060
|
`${causeMessage}
|
|
9035
9061
|
|
|
9036
|
-
fold failed; ${what}, but returning to ${startBranch} afterwards failed: ${e instanceof Error ? e.message : String(e)}
|
|
9062
|
+
fold failed; ${what}, but returning to ${startBranch} afterwards failed: ${e instanceof Error ? e.message : String(e)}.`,
|
|
9063
|
+
await probeFoldFailureState(deps),
|
|
9064
|
+
startBranch,
|
|
9065
|
+
preFoldMainSha,
|
|
9066
|
+
`git checkout ${startBranch}`,
|
|
9067
|
+
resumeCommand
|
|
9037
9068
|
);
|
|
9038
9069
|
}
|
|
9039
9070
|
return new Error(
|
|
9040
9071
|
`${causeMessage}
|
|
9041
9072
|
|
|
9042
|
-
fold failed; ${what} and the checkout was returned to ${startBranch}. Nothing was pushed to origin and no tag was created \u2014 rerun
|
|
9073
|
+
fold failed; ${what} and the checkout was returned to ${startBranch}. Nothing was pushed to origin and no tag was created \u2014 rerun with \`${resumeCommand}\` once the fold issue above is fixed.`
|
|
9043
9074
|
);
|
|
9044
9075
|
}
|
|
9045
|
-
function foldFailureGuidance(causeMessage, probe, startBranch, preFoldMainSha) {
|
|
9046
|
-
const steps = preFoldMainSha ? [
|
|
9076
|
+
function foldFailureGuidance(causeMessage, probe, startBranch, preFoldMainSha, nextCommand, resumeCommand = "mmi-cli release --apply", foldStarted = true) {
|
|
9077
|
+
const steps = !foldStarted ? [` 1. Resolve or wait out the git index lock`, ` 2. ${resumeCommand}`] : nextCommand === "git merge --abort" ? [` 1. git merge --abort`, ` 2. git checkout ${startBranch}`, ` 3. ${resumeCommand}`] : nextCommand === `git checkout ${startBranch}` ? [` 1. git checkout ${startBranch}`, ` 2. ${resumeCommand}`] : preFoldMainSha ? [
|
|
9047
9078
|
` 1. git status # inspect what is actually there`,
|
|
9048
9079
|
` 2. git log --oneline ${preFoldMainSha}..main # commits beyond the pre-fold main \u2014 confirm they are only this run's merge/fold attempt`,
|
|
9049
9080
|
` 3. git checkout main && git reset --hard ${preFoldMainSha} # back to the exact commit the fold built on`,
|
|
@@ -9053,14 +9084,60 @@ function foldFailureGuidance(causeMessage, probe, startBranch, preFoldMainSha) {
|
|
|
9053
9084
|
` 2. git log --oneline origin/main..main # this run never committed to main (it failed before the merge) \u2014 any commits here predate this run; preserve them`,
|
|
9054
9085
|
` 3. git checkout ${startBranch}`
|
|
9055
9086
|
];
|
|
9087
|
+
const exactNextCommand = nextCommand ?? "git status";
|
|
9056
9088
|
return new Error(
|
|
9057
9089
|
`${causeMessage}
|
|
9058
9090
|
|
|
9059
|
-
fold failed leaving the checkout in a state train --apply will not auto-restore (branch=${probe.branch || "(unknown)"},
|
|
9091
|
+
fold failed leaving the checkout in a state train --apply will not auto-restore (branch=${probe.branch || "(unknown)"}, main=${shaLabel(probe.mainSha)}, origin/main=${shaLabel(probe.originMainSha)}, pre-fold main=${preFoldMainSha ? shaLabel(preFoldMainSha) : "not captured"}, origin/main is${probe.originIsAncestorOfMain ? "" : " NOT"} an ancestor of main). Nothing was pushed to origin and no tag was created.
|
|
9092
|
+
Resumable state:
|
|
9093
|
+
current branch: ${probe.branch || "(unknown)"}
|
|
9094
|
+
HEAD: ${probe.headSha || "(unknown)"}
|
|
9095
|
+
pre-fold main: ${preFoldMainSha || "(not captured)"}
|
|
9096
|
+
index/worktree status:
|
|
9097
|
+
${probe.status || "(clean)"}
|
|
9098
|
+
Next command: ${exactNextCommand}
|
|
9099
|
+
Resume command after the checkout is clean: ${resumeCommand}
|
|
9060
9100
|
Recovery sequence:
|
|
9061
9101
|
${steps.join("\n")}`
|
|
9062
9102
|
);
|
|
9063
9103
|
}
|
|
9104
|
+
var GIT_INDEX_LOCK_WAIT_MS = 5e3;
|
|
9105
|
+
var GIT_INDEX_LOCK_POLL_MS = 100;
|
|
9106
|
+
async function waitForFoldIndexLock(deps, startBranch, resumeCommand) {
|
|
9107
|
+
const preFoldMainSha = await deps.run("git", ["rev-parse", "main"]).then(clean2).catch(() => void 0);
|
|
9108
|
+
const lockPath = requireValue(clean2(await deps.run("git", ["rev-parse", "--git-path", "index.lock"])), "git index lock path");
|
|
9109
|
+
const now = deps.now ?? Date.now;
|
|
9110
|
+
const sleep3 = resolveSleep(deps);
|
|
9111
|
+
const deadline = now() + GIT_INDEX_LOCK_WAIT_MS;
|
|
9112
|
+
for (; ; ) {
|
|
9113
|
+
try {
|
|
9114
|
+
await (0, import_promises2.stat)(lockPath);
|
|
9115
|
+
} catch (e) {
|
|
9116
|
+
if (e.code === "ENOENT") return;
|
|
9117
|
+
throw foldFailureGuidance(
|
|
9118
|
+
`release fold could not inspect git index lock ${lockPath}: ${e instanceof Error ? e.message : String(e)}`,
|
|
9119
|
+
await probeFoldFailureState(deps),
|
|
9120
|
+
startBranch,
|
|
9121
|
+
preFoldMainSha,
|
|
9122
|
+
resumeCommand,
|
|
9123
|
+
resumeCommand,
|
|
9124
|
+
false
|
|
9125
|
+
);
|
|
9126
|
+
}
|
|
9127
|
+
if (now() >= deadline) {
|
|
9128
|
+
throw foldFailureGuidance(
|
|
9129
|
+
`release fold waited ${GIT_INDEX_LOCK_WAIT_MS}ms for active git index lock ${lockPath}; it is still held. The fold did not start. Do not delete the lock; let its owning git process finish.`,
|
|
9130
|
+
await probeFoldFailureState(deps),
|
|
9131
|
+
startBranch,
|
|
9132
|
+
preFoldMainSha,
|
|
9133
|
+
resumeCommand,
|
|
9134
|
+
resumeCommand,
|
|
9135
|
+
false
|
|
9136
|
+
);
|
|
9137
|
+
}
|
|
9138
|
+
await sleep3(Math.min(GIT_INDEX_LOCK_POLL_MS, Math.max(1, deadline - now())));
|
|
9139
|
+
}
|
|
9140
|
+
}
|
|
9064
9141
|
async function recoverFailedRcand(deps, cause, preRcSha) {
|
|
9065
9142
|
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
9066
9143
|
const branch = await currentBranch(deps).catch(() => "");
|
|
@@ -9104,23 +9181,25 @@ Recovery sequence:
|
|
|
9104
9181
|
3. mmi-cli rcand --apply`
|
|
9105
9182
|
);
|
|
9106
9183
|
}
|
|
9107
|
-
async function recoverFailedFold(deps, cause, startBranch, preFoldMainSha) {
|
|
9184
|
+
async function recoverFailedFold(deps, cause, startBranch, preFoldMainSha, resumeCommand) {
|
|
9108
9185
|
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
9109
9186
|
const probe = await probeFoldFailureState(deps);
|
|
9110
9187
|
if (probe.mergeInProgress) {
|
|
9111
9188
|
try {
|
|
9112
9189
|
await deps.run("git", ["merge", "--abort"]);
|
|
9113
9190
|
} catch (e) {
|
|
9114
|
-
return
|
|
9191
|
+
return foldFailureGuidance(
|
|
9115
9192
|
`${causeMessage}
|
|
9116
9193
|
|
|
9117
|
-
fold failed with an in-progress merge
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9194
|
+
fold failed with an in-progress merge; automatic \`git merge --abort\` failed: ${e instanceof Error ? e.message : String(e)}.`,
|
|
9195
|
+
await probeFoldFailureState(deps),
|
|
9196
|
+
startBranch,
|
|
9197
|
+
preFoldMainSha,
|
|
9198
|
+
"git merge --abort",
|
|
9199
|
+
resumeCommand
|
|
9121
9200
|
);
|
|
9122
9201
|
}
|
|
9123
|
-
return finishFoldAutoRestore(deps, causeMessage, startBranch, "the in-progress merge was aborted");
|
|
9202
|
+
return finishFoldAutoRestore(deps, causeMessage, startBranch, preFoldMainSha, resumeCommand, "the in-progress merge was aborted");
|
|
9124
9203
|
}
|
|
9125
9204
|
if (probe.branch === "main" && preFoldMainSha) {
|
|
9126
9205
|
const mainDescendsFromPreFold = await deps.run("git", ["merge-base", "--is-ancestor", preFoldMainSha, "main"]).then(() => true).catch(() => false);
|
|
@@ -9128,13 +9207,15 @@ Recovery sequence:
|
|
|
9128
9207
|
try {
|
|
9129
9208
|
await deps.run("git", ["reset", "--hard", preFoldMainSha]);
|
|
9130
9209
|
} catch (e) {
|
|
9131
|
-
return
|
|
9210
|
+
return foldFailureGuidance(
|
|
9132
9211
|
`${causeMessage}
|
|
9133
9212
|
|
|
9134
|
-
fold failed after this run's merge/fold committed locally on main
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9213
|
+
fold failed after this run's merge/fold committed locally on main; automatic \`git reset --hard ${shaLabel(preFoldMainSha)}\` failed: ${e instanceof Error ? e.message : String(e)}.`,
|
|
9214
|
+
await probeFoldFailureState(deps),
|
|
9215
|
+
startBranch,
|
|
9216
|
+
preFoldMainSha,
|
|
9217
|
+
`git reset --hard ${preFoldMainSha}`,
|
|
9218
|
+
resumeCommand
|
|
9138
9219
|
);
|
|
9139
9220
|
}
|
|
9140
9221
|
const aheadNote = probe.originMainSha && preFoldMainSha !== probe.originMainSha ? `; local main carried commit(s) origin/main (${shaLabel(probe.originMainSha)}) does not have \u2014 they were preserved` : "";
|
|
@@ -9142,17 +9223,20 @@ Recovery sequence:
|
|
|
9142
9223
|
deps,
|
|
9143
9224
|
causeMessage,
|
|
9144
9225
|
startBranch,
|
|
9226
|
+
preFoldMainSha,
|
|
9227
|
+
resumeCommand,
|
|
9145
9228
|
`local main was reset to its pre-fold state ${shaLabel(preFoldMainSha)} (discarding only this run's merge/fold commit(s) at ${shaLabel(probe.mainSha)})${aheadNote}`
|
|
9146
9229
|
);
|
|
9147
9230
|
}
|
|
9148
9231
|
}
|
|
9149
|
-
return foldFailureGuidance(causeMessage, probe, startBranch, preFoldMainSha);
|
|
9232
|
+
return foldFailureGuidance(causeMessage, probe, startBranch, preFoldMainSha, void 0, resumeCommand);
|
|
9150
9233
|
}
|
|
9151
|
-
async function runFoldStage(deps, startBranch, preFold, fn) {
|
|
9234
|
+
async function runFoldStage(deps, startBranch, preFold, resumeCommand, fn) {
|
|
9235
|
+
await waitForFoldIndexLock(deps, startBranch, resumeCommand);
|
|
9152
9236
|
try {
|
|
9153
9237
|
return await fn();
|
|
9154
9238
|
} catch (e) {
|
|
9155
|
-
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
9239
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
9156
9240
|
}
|
|
9157
9241
|
}
|
|
9158
9242
|
async function verifyPublishDryRun(deps, ctx, meta, deployModel) {
|
|
@@ -9211,30 +9295,31 @@ async function gateTrueMergeCommit(deps, ctx, tag, releaseSha, requiredChecks) {
|
|
|
9211
9295
|
}
|
|
9212
9296
|
}
|
|
9213
9297
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe) {
|
|
9298
|
+
const resumeCommand = options.dev ? "mmi-cli release --dev --apply" : "mmi-cli release --apply";
|
|
9214
9299
|
try {
|
|
9215
9300
|
await verifyPublishDryRun(deps, ctx, meta, deployModel);
|
|
9216
9301
|
} catch (e) {
|
|
9217
|
-
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
9302
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
9218
9303
|
}
|
|
9219
9304
|
let requiredChecks;
|
|
9220
9305
|
try {
|
|
9221
9306
|
requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
9222
9307
|
} catch (e) {
|
|
9223
|
-
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
9308
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
9224
9309
|
}
|
|
9225
9310
|
let trueMergeGateNote;
|
|
9226
9311
|
if (preFold.isTrueMerge) {
|
|
9227
9312
|
try {
|
|
9228
9313
|
trueMergeGateNote = await gateTrueMergeCommit(deps, ctx, tag, releaseSha, requiredChecks);
|
|
9229
9314
|
} catch (e) {
|
|
9230
|
-
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
9315
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
9231
9316
|
}
|
|
9232
9317
|
}
|
|
9233
9318
|
let tagPush;
|
|
9234
9319
|
try {
|
|
9235
9320
|
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
|
|
9236
9321
|
} catch (e) {
|
|
9237
|
-
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
9322
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
9238
9323
|
}
|
|
9239
9324
|
const tagPushSince = (deps.now ?? Date.now)();
|
|
9240
9325
|
let checks;
|
|
@@ -9640,7 +9725,8 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
9640
9725
|
);
|
|
9641
9726
|
const tagProbe2 = { remoteSha: await probeRemoteTag(deps, tag2) };
|
|
9642
9727
|
const preFold2 = {};
|
|
9643
|
-
const
|
|
9728
|
+
const resumeCommand = options.dev ? "mmi-cli release --dev --apply" : "mmi-cli release --apply";
|
|
9729
|
+
const { versionFold: versionFold2, releaseSha: releaseSha2 } = await runFoldStage(deps, "development", preFold2, resumeCommand, async () => {
|
|
9644
9730
|
await executeMergeToMain(deps, "development", "development -> main", tolerated2, predicted2, preFold2);
|
|
9645
9731
|
const versionFold3 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
|
|
9646
9732
|
const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
@@ -9725,7 +9811,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
9725
9811
|
const tag = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
|
|
9726
9812
|
const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
|
|
9727
9813
|
const preFold = {};
|
|
9728
|
-
const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
|
|
9814
|
+
const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, "mmi-cli release --apply", async () => {
|
|
9729
9815
|
await executeMergeToMain(deps, "rc", "rc -> main", tolerated, predicted, preFold);
|
|
9730
9816
|
const versionFold2 = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
|
|
9731
9817
|
const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
@@ -12203,7 +12289,7 @@ var import_node_child_process6 = require("node:child_process");
|
|
|
12203
12289
|
var import_node_os4 = require("node:os");
|
|
12204
12290
|
|
|
12205
12291
|
// src/gh-create.ts
|
|
12206
|
-
var
|
|
12292
|
+
var import_promises3 = require("node:fs/promises");
|
|
12207
12293
|
var import_node_os3 = require("node:os");
|
|
12208
12294
|
var import_node_path14 = require("node:path");
|
|
12209
12295
|
var import_node_crypto3 = require("node:crypto");
|
|
@@ -12255,9 +12341,9 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
12255
12341
|
const i = args.indexOf("--body");
|
|
12256
12342
|
if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
|
|
12257
12343
|
} };
|
|
12258
|
-
const write = deps.write ??
|
|
12259
|
-
const remove2 = deps.remove ??
|
|
12260
|
-
const ensureDir = deps.ensureDir ??
|
|
12344
|
+
const write = deps.write ?? import_promises3.writeFile;
|
|
12345
|
+
const remove2 = deps.remove ?? import_promises3.unlink;
|
|
12346
|
+
const ensureDir = deps.ensureDir ?? import_promises3.mkdir;
|
|
12261
12347
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
12262
12348
|
const file = (0, import_node_path14.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
12263
12349
|
await ensureDir((0, import_node_path14.dirname)(file), { recursive: true }).catch(() => {
|
|
@@ -21004,7 +21090,7 @@ function formatBoxTable(boxes) {
|
|
|
21004
21090
|
const addrW = w((b) => b.address || "(no public ipv4)", "ADDRESS");
|
|
21005
21091
|
const projW = w((b) => b.project, "PROJECT");
|
|
21006
21092
|
const statW = w((b) => b.status, "STATUS");
|
|
21007
|
-
const row = (name, addr, proj,
|
|
21093
|
+
const row = (name, addr, proj, stat3, key) => `${name.padEnd(nameW)} ${addr.padEnd(addrW)} ${proj.padEnd(projW)} ${stat3.padEnd(statW)} ${key}`;
|
|
21008
21094
|
const lines = [row("BOX", "ADDRESS", "PROJECT", "STATUS", "SSH KEY (vault path)")];
|
|
21009
21095
|
for (const b of boxes) {
|
|
21010
21096
|
lines.push(row(b.name, b.address || "(no public ipv4)", b.project, b.status, b.sshKeyPath));
|
|
@@ -21142,7 +21228,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
21142
21228
|
}
|
|
21143
21229
|
|
|
21144
21230
|
// src/schedules-commands.ts
|
|
21145
|
-
var
|
|
21231
|
+
var import_promises4 = require("node:fs/promises");
|
|
21146
21232
|
var import_node_child_process13 = require("node:child_process");
|
|
21147
21233
|
var import_node_util7 = require("node:util");
|
|
21148
21234
|
|
|
@@ -21752,9 +21838,9 @@ function registerSchedulesCommands(program3) {
|
|
|
21752
21838
|
await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
|
|
21753
21839
|
return;
|
|
21754
21840
|
}
|
|
21755
|
-
const docText = await (0,
|
|
21841
|
+
const docText = await (0, import_promises4.readFile)(o.doc, "utf8");
|
|
21756
21842
|
const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
|
|
21757
|
-
await (0,
|
|
21843
|
+
await (0, import_promises4.writeFile)(o.doc, spliced, "utf8");
|
|
21758
21844
|
reportDrift(drift);
|
|
21759
21845
|
console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
21760
21846
|
return;
|
|
@@ -21813,7 +21899,7 @@ function registerSchedulesCommands(program3) {
|
|
|
21813
21899
|
}
|
|
21814
21900
|
|
|
21815
21901
|
// src/file-lock.ts
|
|
21816
|
-
var
|
|
21902
|
+
var import_promises5 = require("node:fs/promises");
|
|
21817
21903
|
var import_node_path24 = require("node:path");
|
|
21818
21904
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21819
21905
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
@@ -21838,7 +21924,7 @@ async function acquireFileLock(lockPath, opts, deadline) {
|
|
|
21838
21924
|
for (; ; ) {
|
|
21839
21925
|
let handle;
|
|
21840
21926
|
try {
|
|
21841
|
-
handle = await (0,
|
|
21927
|
+
handle = await (0, import_promises5.open)(lockPath, "wx");
|
|
21842
21928
|
} catch (e) {
|
|
21843
21929
|
const code = e.code;
|
|
21844
21930
|
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
@@ -21847,10 +21933,10 @@ async function acquireFileLock(lockPath, opts, deadline) {
|
|
|
21847
21933
|
if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
|
|
21848
21934
|
};
|
|
21849
21935
|
try {
|
|
21850
|
-
const age = Date.now() - (await (0,
|
|
21936
|
+
const age = Date.now() - (await (0, import_promises5.stat)(lockPath)).mtimeMs;
|
|
21851
21937
|
if (age > opts.staleMs) {
|
|
21852
21938
|
try {
|
|
21853
|
-
await (0,
|
|
21939
|
+
await (0, import_promises5.unlink)(lockPath);
|
|
21854
21940
|
await retryNow();
|
|
21855
21941
|
continue;
|
|
21856
21942
|
} catch (unlinkError) {
|
|
@@ -21886,7 +21972,7 @@ async function acquireFileLock(lockPath, opts, deadline) {
|
|
|
21886
21972
|
}
|
|
21887
21973
|
async function fileLockHeldBy(lockPath, token) {
|
|
21888
21974
|
try {
|
|
21889
|
-
return await (0,
|
|
21975
|
+
return await (0, import_promises5.readFile)(lockPath, "utf8") === token;
|
|
21890
21976
|
} catch {
|
|
21891
21977
|
return false;
|
|
21892
21978
|
}
|
|
@@ -21894,12 +21980,12 @@ async function fileLockHeldBy(lockPath, token) {
|
|
|
21894
21980
|
async function releaseFileLock(lockPath, guard) {
|
|
21895
21981
|
await guard.handle.close().catch(() => void 0);
|
|
21896
21982
|
if (await fileLockHeldBy(lockPath, guard.token)) {
|
|
21897
|
-
await (0,
|
|
21983
|
+
await (0, import_promises5.unlink)(lockPath).catch(() => void 0);
|
|
21898
21984
|
}
|
|
21899
21985
|
}
|
|
21900
21986
|
async function withFileLock(lockPath, opts, fn) {
|
|
21901
21987
|
const resolved = resolveFileLockOpts(opts);
|
|
21902
|
-
await (0,
|
|
21988
|
+
await (0, import_promises5.mkdir)((0, import_node_path24.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
21903
21989
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
21904
21990
|
try {
|
|
21905
21991
|
return await fn();
|
|
@@ -21909,7 +21995,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
21909
21995
|
}
|
|
21910
21996
|
|
|
21911
21997
|
// src/schedules-lift-command.ts
|
|
21912
|
-
var
|
|
21998
|
+
var import_promises6 = require("node:fs/promises");
|
|
21913
21999
|
var import_node_path25 = require("node:path");
|
|
21914
22000
|
|
|
21915
22001
|
// src/schedules-lift.ts
|
|
@@ -22009,14 +22095,14 @@ var RegistryUnreachableError = class extends Error {
|
|
|
22009
22095
|
async function readWorkflowFiles(dir) {
|
|
22010
22096
|
let names;
|
|
22011
22097
|
try {
|
|
22012
|
-
names = await (0,
|
|
22098
|
+
names = await (0, import_promises6.readdir)(dir);
|
|
22013
22099
|
} catch {
|
|
22014
22100
|
return [];
|
|
22015
22101
|
}
|
|
22016
22102
|
const files = [];
|
|
22017
22103
|
for (const name of names.sort()) {
|
|
22018
22104
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
22019
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0,
|
|
22105
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path25.join)(dir, name), "utf8") });
|
|
22020
22106
|
}
|
|
22021
22107
|
return files;
|
|
22022
22108
|
}
|
|
@@ -24298,7 +24384,7 @@ function registerBoardCommands(program3) {
|
|
|
24298
24384
|
|
|
24299
24385
|
// src/merge-cleanup.ts
|
|
24300
24386
|
var import_node_fs30 = require("node:fs");
|
|
24301
|
-
var
|
|
24387
|
+
var import_promises8 = require("node:fs/promises");
|
|
24302
24388
|
var import_node_path30 = require("node:path");
|
|
24303
24389
|
var import_node_os11 = require("node:os");
|
|
24304
24390
|
var import_node_child_process14 = require("node:child_process");
|
|
@@ -24386,23 +24472,23 @@ function boardAdvanceFailureMessage(result) {
|
|
|
24386
24472
|
}
|
|
24387
24473
|
|
|
24388
24474
|
// src/deferred-registry-store.ts
|
|
24389
|
-
var
|
|
24475
|
+
var import_promises7 = require("node:fs/promises");
|
|
24390
24476
|
var import_node_path29 = require("node:path");
|
|
24391
24477
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
24392
24478
|
async function atomicWrite(target, contents) {
|
|
24393
24479
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
24394
|
-
await (0,
|
|
24480
|
+
await (0, import_promises7.writeFile)(tmp, contents, "utf8");
|
|
24395
24481
|
try {
|
|
24396
24482
|
await renameWithRetry(tmp, target);
|
|
24397
24483
|
} catch (e) {
|
|
24398
|
-
await (0,
|
|
24484
|
+
await (0, import_promises7.unlink)(tmp).catch(() => void 0);
|
|
24399
24485
|
throw e;
|
|
24400
24486
|
}
|
|
24401
24487
|
}
|
|
24402
24488
|
async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
|
|
24403
24489
|
for (let i = 0; ; i++) {
|
|
24404
24490
|
try {
|
|
24405
|
-
await (0,
|
|
24491
|
+
await (0, import_promises7.rename)(from, to);
|
|
24406
24492
|
return;
|
|
24407
24493
|
} catch (e) {
|
|
24408
24494
|
const code = e.code;
|
|
@@ -24414,7 +24500,7 @@ async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
|
|
|
24414
24500
|
async function readStrict(registryPath) {
|
|
24415
24501
|
let text;
|
|
24416
24502
|
try {
|
|
24417
|
-
text = await (0,
|
|
24503
|
+
text = await (0, import_promises7.readFile)(registryPath, "utf8");
|
|
24418
24504
|
} catch (e) {
|
|
24419
24505
|
if (e.code === "ENOENT") return [];
|
|
24420
24506
|
throw e;
|
|
@@ -24431,19 +24517,19 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
24431
24517
|
// Lenient read for the sweep's initial fetch: any error (missing/corrupt) yields an empty queue.
|
|
24432
24518
|
read: async () => {
|
|
24433
24519
|
try {
|
|
24434
|
-
return parseDeferredWorktreesFile(await (0,
|
|
24520
|
+
return parseDeferredWorktreesFile(await (0, import_promises7.readFile)(registryPath, "utf8"));
|
|
24435
24521
|
} catch {
|
|
24436
24522
|
return [];
|
|
24437
24523
|
}
|
|
24438
24524
|
},
|
|
24439
24525
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
24440
24526
|
write: async (entries) => {
|
|
24441
|
-
await (0,
|
|
24527
|
+
await (0, import_promises7.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
|
|
24442
24528
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
24443
24529
|
},
|
|
24444
24530
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
24445
24531
|
update: async (mutate) => {
|
|
24446
|
-
await (0,
|
|
24532
|
+
await (0, import_promises7.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
|
|
24447
24533
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
24448
24534
|
for (; ; ) {
|
|
24449
24535
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -24884,7 +24970,7 @@ var realWorktreeDirRemover = {
|
|
|
24884
24970
|
(0, import_node_fs30.unlinkSync)(p);
|
|
24885
24971
|
}
|
|
24886
24972
|
},
|
|
24887
|
-
removeTree: (p) => (0,
|
|
24973
|
+
removeTree: (p) => (0, import_promises8.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
24888
24974
|
};
|
|
24889
24975
|
async function resolvePrimaryCheckout(execGit) {
|
|
24890
24976
|
try {
|
|
@@ -25141,7 +25227,7 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
25141
25227
|
|
|
25142
25228
|
// src/worktree-lifecycle-commands.ts
|
|
25143
25229
|
var import_node_fs31 = require("node:fs");
|
|
25144
|
-
var
|
|
25230
|
+
var import_promises9 = require("node:fs/promises");
|
|
25145
25231
|
var import_node_path31 = require("node:path");
|
|
25146
25232
|
var GH_TIMEOUT_MS = 2e4;
|
|
25147
25233
|
var DEFAULT_BASE = "origin/development";
|
|
@@ -25485,13 +25571,13 @@ function registerWorktreeCommands(program3) {
|
|
|
25485
25571
|
}
|
|
25486
25572
|
const landStatus = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "unreadable" }))).stdout || "";
|
|
25487
25573
|
const gitInWorktree = async (args) => (await execFileP2("git", ["-C", wtPath, ...args], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
25488
|
-
const orphanDirt = headBorn ? void 0 : await classifyOrphanWorktreeDirt(landStatus, { git: gitInWorktree, readTextFile: (p) => (0,
|
|
25574
|
+
const orphanDirt = headBorn ? void 0 : await classifyOrphanWorktreeDirt(landStatus, { git: gitInWorktree, readTextFile: (p) => (0, import_promises9.readFile)(p, "utf8") });
|
|
25489
25575
|
const landDirty = headBorn ? landStatus.trim().length > 0 : orphanDirt.dirt !== "clean";
|
|
25490
25576
|
if (landDirty) {
|
|
25491
25577
|
return fail(`worktree land: refusing to land '${branch}' \u2014 uncommitted changes in ${wtPath}; commit, stash, or remove the worktree manually`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
25492
25578
|
}
|
|
25493
25579
|
const orphanTip = orphan.orphan ? orphanDirt?.tip ?? await readOrphanWorktreeTip(
|
|
25494
|
-
{ git: gitInWorktree, readTextFile: (p) => (0,
|
|
25580
|
+
{ git: gitInWorktree, readTextFile: (p) => (0, import_promises9.readFile)(p, "utf8") },
|
|
25495
25581
|
orphan.kind
|
|
25496
25582
|
) : void 0;
|
|
25497
25583
|
const lastCommit = orphanTip ? {
|
|
@@ -26917,7 +27003,7 @@ function registerExplainCommand(program3) {
|
|
|
26917
27003
|
}
|
|
26918
27004
|
|
|
26919
27005
|
// src/pr-commands.ts
|
|
26920
|
-
var
|
|
27006
|
+
var import_promises10 = require("node:fs/promises");
|
|
26921
27007
|
var GC_GH_TIMEOUT_MS4 = 2e4;
|
|
26922
27008
|
var CHECKS_WATCH_POLL_MS = 15e3;
|
|
26923
27009
|
var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -27061,10 +27147,10 @@ function registerPrLifecycleCommands(program3) {
|
|
|
27061
27147
|
let body;
|
|
27062
27148
|
try {
|
|
27063
27149
|
if (o.title || o.titleFile) {
|
|
27064
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
27150
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises10.readFile, readStdin });
|
|
27065
27151
|
}
|
|
27066
27152
|
if (o.body || o.bodyFile) {
|
|
27067
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
27153
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises10.readFile, readStdin });
|
|
27068
27154
|
}
|
|
27069
27155
|
} catch (e) {
|
|
27070
27156
|
return fail(`pr edit: ${e.message}`);
|
|
@@ -27094,7 +27180,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
27094
27180
|
}
|
|
27095
27181
|
let body;
|
|
27096
27182
|
try {
|
|
27097
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
27183
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises10.readFile, readStdin });
|
|
27098
27184
|
} catch (e) {
|
|
27099
27185
|
return fail(`pr comment: ${e.message}`);
|
|
27100
27186
|
}
|
|
@@ -27382,9 +27468,17 @@ function findNegatedClosings(text, closingNumbers) {
|
|
|
27382
27468
|
return forIssue.length > 0 && forIssue.every((m) => m.negated);
|
|
27383
27469
|
});
|
|
27384
27470
|
}
|
|
27471
|
+
function commitMessagesText(commits) {
|
|
27472
|
+
if (!Array.isArray(commits)) return "";
|
|
27473
|
+
return commits.map((commit) => {
|
|
27474
|
+
const { messageHeadline, messageBody } = commit ?? {};
|
|
27475
|
+
return `${typeof messageHeadline === "string" ? messageHeadline : ""}
|
|
27476
|
+
${typeof messageBody === "string" ? messageBody : ""}`;
|
|
27477
|
+
}).join("\n");
|
|
27478
|
+
}
|
|
27385
27479
|
function parseClosingGuardInput(raw) {
|
|
27386
27480
|
if (!raw || typeof raw !== "object") return void 0;
|
|
27387
|
-
const { title, body, state, closingIssuesReferences } = raw;
|
|
27481
|
+
const { title, body, state, closingIssuesReferences, commits } = raw;
|
|
27388
27482
|
if (typeof state !== "string" || !Array.isArray(closingIssuesReferences)) return void 0;
|
|
27389
27483
|
const closing = [];
|
|
27390
27484
|
for (const ref of closingIssuesReferences) {
|
|
@@ -27392,15 +27486,20 @@ function parseClosingGuardInput(raw) {
|
|
|
27392
27486
|
if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) return void 0;
|
|
27393
27487
|
closing.push(n);
|
|
27394
27488
|
}
|
|
27489
|
+
const commitClosing = [...new Set(findClosingMentions(commitMessagesText(commits)).map((m) => m.issue))];
|
|
27395
27490
|
const text = `${typeof title === "string" ? title : ""}
|
|
27396
27491
|
${typeof body === "string" ? body : ""}`;
|
|
27397
|
-
return { state, text, closing };
|
|
27492
|
+
return { state, text, closing, commitClosing };
|
|
27398
27493
|
}
|
|
27399
27494
|
function negatedClosingRefusalMessage(negated, context = "pr merge") {
|
|
27400
27495
|
const named = negated.map((n) => `#${n}`).join(", ");
|
|
27401
27496
|
const first = `#${negated[0] ?? "N"}`;
|
|
27402
27497
|
return `${context}: REFUSED \u2014 GitHub will close ${named} on merge, but the PR body says it does not (GitHub's closing-keyword parser is negation-blind: "does not close ${first}" still closes it). Reword the body so the keyword is gone (e.g. "leaves ${first} open"), or re-run with --force to merge anyway and let ${named} close.`;
|
|
27403
27498
|
}
|
|
27499
|
+
function commitClosingRefusalMessage(closing, context = "pr merge") {
|
|
27500
|
+
const named = closing.map((n) => `#${n}`).join(", ");
|
|
27501
|
+
return `${context}: REFUSED \u2014 commit messages will close ${named} through the squash body, but GitHub omitted ${named} from closingIssuesReferences. Remove the closing keyword from the commit message, or re-run with --force to merge anyway and let ${named} close.`;
|
|
27502
|
+
}
|
|
27404
27503
|
function evaluateClosingGuard(input, opts) {
|
|
27405
27504
|
if (!input) {
|
|
27406
27505
|
return {
|
|
@@ -27408,7 +27507,16 @@ function evaluateClosingGuard(input, opts) {
|
|
|
27408
27507
|
message: `${opts.context}: could not read the PR's closing references \u2014 proceeding WITHOUT the negated-closing check (#3718).`
|
|
27409
27508
|
};
|
|
27410
27509
|
}
|
|
27411
|
-
if (input.state !== "OPEN"
|
|
27510
|
+
if (input.state !== "OPEN") return { blocked: false };
|
|
27511
|
+
const commitClosing = (input.commitClosing ?? []).filter((n) => !input.closing.includes(n));
|
|
27512
|
+
if (commitClosing.length) {
|
|
27513
|
+
if (!opts.force) return { blocked: true, message: commitClosingRefusalMessage(commitClosing, opts.context) };
|
|
27514
|
+
return {
|
|
27515
|
+
blocked: false,
|
|
27516
|
+
message: `${opts.context}: --force past the commit-message closing guard \u2014 the squash body will close ${commitClosing.map((n) => `#${n}`).join(", ")}.`
|
|
27517
|
+
};
|
|
27518
|
+
}
|
|
27519
|
+
if (input.closing.length === 0) return { blocked: false };
|
|
27412
27520
|
const negated = findNegatedClosings(input.text, input.closing);
|
|
27413
27521
|
if (!negated.length) return { blocked: false };
|
|
27414
27522
|
if (!opts.force) return { blocked: true, message: negatedClosingRefusalMessage(negated, opts.context) };
|
|
@@ -30669,7 +30777,7 @@ withExamples(mutating(
|
|
|
30669
30777
|
try {
|
|
30670
30778
|
title = await resolveIssueTitle(
|
|
30671
30779
|
{ title: opts.title, titleFile: opts.titleFile },
|
|
30672
|
-
{ readFile:
|
|
30780
|
+
{ readFile: import_promises11.readFile, readStdin }
|
|
30673
30781
|
);
|
|
30674
30782
|
} catch (e) {
|
|
30675
30783
|
return fail(
|
|
@@ -30711,8 +30819,8 @@ withExamples(mutating(
|
|
|
30711
30819
|
let surfaceFlagLabel;
|
|
30712
30820
|
try {
|
|
30713
30821
|
issueType = resolveCreateType(o.type, "issue create", o.label);
|
|
30714
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
30715
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
30822
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises11.readFile, readStdin });
|
|
30823
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
30716
30824
|
if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
|
|
30717
30825
|
priority = resolveCreatePriority(o.priority, "issue create");
|
|
30718
30826
|
extraLabels = [...o.label ?? []];
|
|
@@ -30892,7 +31000,7 @@ jsonParity(issue.command("comment <ref>").description("post a Markdown comment t
|
|
|
30892
31000
|
}
|
|
30893
31001
|
let body;
|
|
30894
31002
|
try {
|
|
30895
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
31003
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
30896
31004
|
} catch (e) {
|
|
30897
31005
|
return fail(`issue comment: ${e.message}`);
|
|
30898
31006
|
}
|
|
@@ -30952,8 +31060,8 @@ program2.command("report").description("file a friction report on the Hub board
|
|
|
30952
31060
|
let title;
|
|
30953
31061
|
const sourceRepo = o.repo ?? await resolveRepo(void 0);
|
|
30954
31062
|
try {
|
|
30955
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
30956
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
31063
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises11.readFile, readStdin });
|
|
31064
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
30957
31065
|
priority = resolveCreatePriority(o.priority, "report");
|
|
30958
31066
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
30959
31067
|
throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
@@ -31005,8 +31113,8 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
31005
31113
|
let args;
|
|
31006
31114
|
try {
|
|
31007
31115
|
skill = assertSkillName(o.skill);
|
|
31008
|
-
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
31009
|
-
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
31116
|
+
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
31117
|
+
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises11.readFile, readStdin });
|
|
31010
31118
|
title = buildSkillLessonTitle(skill, rawTitle);
|
|
31011
31119
|
priority = resolveCreatePriority(o.priority, "skill-lesson");
|
|
31012
31120
|
body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
|
|
@@ -31065,8 +31173,8 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
31065
31173
|
let body;
|
|
31066
31174
|
let title;
|
|
31067
31175
|
try {
|
|
31068
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
31069
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
31176
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises11.readFile, readStdin });
|
|
31177
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
31070
31178
|
} catch (e) {
|
|
31071
31179
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
31072
31180
|
}
|
|
@@ -31200,7 +31308,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
31200
31308
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
31201
31309
|
const startingPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
31202
31310
|
assertPrMergeHousekeepingClean(startingPath || process.cwd(), "pr land", { force: o.force });
|
|
31203
|
-
const landClosingGuardRaw = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "title,body,state,closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS2 }).then((r) => JSON.parse(r.stdout)).catch(() => void 0);
|
|
31311
|
+
const landClosingGuardRaw = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "title,body,state,closingIssuesReferences,commits"], { timeout: GC_GH_TIMEOUT_MS2 }).then((r) => JSON.parse(r.stdout)).catch(() => void 0);
|
|
31204
31312
|
const landClosingGuardVerdict = evaluateClosingGuard(parseClosingGuardInput(landClosingGuardRaw), { force: o.force, context: "pr land" });
|
|
31205
31313
|
if (landClosingGuardVerdict.blocked) {
|
|
31206
31314
|
console.error(landClosingGuardVerdict.message);
|
|
@@ -31318,7 +31426,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
31318
31426
|
const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
|
|
31319
31427
|
const [headRef, baseRef, headRefOid] = (await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName,headRefOid", "--jq", '.headRefName + " " + .baseRefName + " " + (.headRefOid // "")'], { timeout: GC_GH_TIMEOUT_MS2 })).stdout.trim().split(/\s+/);
|
|
31320
31428
|
const headIsProtected = isProtectedBranch(headRef);
|
|
31321
|
-
const closingGuardRaw = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "title,body,state,closingIssuesReferences"], { timeout: GC_GH_TIMEOUT_MS2 }).then((r) => JSON.parse(r.stdout)).catch(() => void 0);
|
|
31429
|
+
const closingGuardRaw = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "title,body,state,closingIssuesReferences,commits"], { timeout: GC_GH_TIMEOUT_MS2 }).then((r) => JSON.parse(r.stdout)).catch(() => void 0);
|
|
31322
31430
|
const closingGuardVerdict = evaluateClosingGuard(parseClosingGuardInput(closingGuardRaw), { force: o.force, context: "pr merge" });
|
|
31323
31431
|
if (closingGuardVerdict.blocked) {
|
|
31324
31432
|
console.error(closingGuardVerdict.message);
|
|
@@ -31563,8 +31671,8 @@ function trainApplyDeps() {
|
|
|
31563
31671
|
// Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
|
|
31564
31672
|
announce: (args) => announceRelease({
|
|
31565
31673
|
run: async (file, cmdArgs) => (await execFileP2(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
|
|
31566
|
-
readFile: (path2) => (0,
|
|
31567
|
-
removeFile: (path2) => (0,
|
|
31674
|
+
readFile: (path2) => (0, import_promises11.readFile)(path2, "utf8"),
|
|
31675
|
+
removeFile: (path2) => (0, import_promises11.unlink)(path2)
|
|
31568
31676
|
}, args),
|
|
31569
31677
|
fetchEdgeDomains: async (slug) => {
|
|
31570
31678
|
const proj = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig()));
|
|
@@ -31747,7 +31855,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
31747
31855
|
}
|
|
31748
31856
|
let summaryLines;
|
|
31749
31857
|
try {
|
|
31750
|
-
summaryLines = summaryFileLines(await (0,
|
|
31858
|
+
summaryLines = summaryFileLines(await (0, import_promises11.readFile)(o.announceSummaryFile, "utf8"));
|
|
31751
31859
|
} catch (e) {
|
|
31752
31860
|
return fail(`release: could not read --announce-summary-file ${o.announceSummaryFile}: ${e.message}`);
|
|
31753
31861
|
}
|
package/package.json
CHANGED