@mutmutco/cli 4.1.20 → 4.2.1
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 +522 -263
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3415,8 +3415,8 @@ var program = new Command();
|
|
|
3415
3415
|
|
|
3416
3416
|
// src/index.ts
|
|
3417
3417
|
var import_promises8 = require("node:fs/promises");
|
|
3418
|
-
var
|
|
3419
|
-
var
|
|
3418
|
+
var import_node_fs43 = require("node:fs");
|
|
3419
|
+
var import_node_child_process19 = require("node:child_process");
|
|
3420
3420
|
|
|
3421
3421
|
// src/cli-shared.ts
|
|
3422
3422
|
var import_node_child_process3 = require("node:child_process");
|
|
@@ -5538,7 +5538,7 @@ function commandLadderHint() {
|
|
|
5538
5538
|
}
|
|
5539
5539
|
|
|
5540
5540
|
// src/index.ts
|
|
5541
|
-
var
|
|
5541
|
+
var import_node_path40 = require("node:path");
|
|
5542
5542
|
|
|
5543
5543
|
// src/merge-ci-policy.ts
|
|
5544
5544
|
function resolveMergeCiPolicy(input) {
|
|
@@ -7287,6 +7287,7 @@ function parseOwnerRepo(repo) {
|
|
|
7287
7287
|
if (owner.includes("\\") || name.includes("\\")) throw new Error("repo must be owner/repo");
|
|
7288
7288
|
return { owner, name, slug: name.toLowerCase(), fullName: `${owner}/${name}` };
|
|
7289
7289
|
}
|
|
7290
|
+
var MANAGED_DELETE_BRANCH_ON_MERGE = false;
|
|
7290
7291
|
var DEFAULT_GATE_CMD = "npm run check";
|
|
7291
7292
|
var DEFAULT_GATE_MAX_SECONDS = "300";
|
|
7292
7293
|
var DEFAULT_GATE_PY_VERSION = "3.11";
|
|
@@ -11761,10 +11762,10 @@ var rollout_plan_default = {
|
|
|
11761
11762
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
11762
11763
|
},
|
|
11763
11764
|
baseline: {
|
|
11764
|
-
version: "4.1
|
|
11765
|
-
tag: "v4.1
|
|
11766
|
-
commit: "
|
|
11767
|
-
npm: "@mutmutco/cli@4.1
|
|
11765
|
+
version: "4.2.1",
|
|
11766
|
+
tag: "v4.2.1",
|
|
11767
|
+
commit: "66e67b6905a1",
|
|
11768
|
+
npm: "@mutmutco/cli@4.2.1"
|
|
11768
11769
|
},
|
|
11769
11770
|
exitCriterion: "fleet-n-of-n",
|
|
11770
11771
|
hubOnlyShortcut: "forbidden",
|
|
@@ -11781,14 +11782,14 @@ var rollout_plan_default = {
|
|
|
11781
11782
|
repo: "mutmutco/mmi-hub",
|
|
11782
11783
|
role: "canary",
|
|
11783
11784
|
schedule: "train",
|
|
11784
|
-
v3Target: "v4.1
|
|
11785
|
+
v3Target: "v4.2.1"
|
|
11785
11786
|
}
|
|
11786
11787
|
],
|
|
11787
11788
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
11788
11789
|
rollback: {
|
|
11789
11790
|
independent: true,
|
|
11790
|
-
mechanism: "npm dist-tag latest -> 4.1
|
|
11791
|
-
v3Target: "v4.1
|
|
11791
|
+
mechanism: "npm dist-tag latest -> 4.2.1 and redeploy the Hub Lambda from tag v4.2.1 (66e67b6905a1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
11792
|
+
v3Target: "v4.2.1 (@mutmutco/cli@4.2.1, tag commit 66e67b6905a1 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
11792
11793
|
}
|
|
11793
11794
|
},
|
|
11794
11795
|
{
|
|
@@ -14738,10 +14739,16 @@ async function correlateWorkflowRun(deps, args) {
|
|
|
14738
14739
|
async function watchTenantRun(deps, runId, repo = HUB_REPO3) {
|
|
14739
14740
|
if (runId == null) return "pending";
|
|
14740
14741
|
let watchSaidSuccess = true;
|
|
14742
|
+
const startedAt = Date.now();
|
|
14743
|
+
deps.warn?.(`watching workflow run ${runId} (${repo})`);
|
|
14744
|
+
const heartbeat = deps.warn ? setInterval(() => deps.warn?.(`workflow run ${runId} (${repo}) still running \u2014 ${Math.floor((Date.now() - startedAt) / 6e4)}m elapsed`), 6e4) : void 0;
|
|
14745
|
+
heartbeat?.unref();
|
|
14741
14746
|
try {
|
|
14742
14747
|
await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
|
|
14743
14748
|
} catch {
|
|
14744
14749
|
watchSaidSuccess = false;
|
|
14750
|
+
} finally {
|
|
14751
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
14745
14752
|
}
|
|
14746
14753
|
const sleep2 = resolveSleep(deps);
|
|
14747
14754
|
let confirmError;
|
|
@@ -15824,7 +15831,7 @@ function assertNpmMajorPreflight(opts) {
|
|
|
15824
15831
|
if (localMajor === void 0 || expectedMajor === void 0) return;
|
|
15825
15832
|
if (localMajor === expectedMajor) return;
|
|
15826
15833
|
throw new Error(
|
|
15827
|
-
`${opts.repo}: Step 0a npm-major preflight failed \u2014 local npm ${localNpm} (major ${localMajor}) \u2260 CI npm ${expectedNpm} (major ${expectedMajor}).
|
|
15834
|
+
`${opts.repo}: Step 0a npm-major preflight failed \u2014 local npm ${localNpm} (major ${localMajor}) \u2260 CI npm ${expectedNpm} (major ${expectedMajor}). Supported repair (preserves Node): \`npm install -g npm@${expectedNpm}\`; then verify with \`node -v && npm -v\` and rerun the same release command. This is an npm-major mismatch (#5666/#5616/#4578), not a lockfile or publish-surface problem. When gate.yml has no bundled npm pin, read the publish workflow npm pin; otherwise compare \`npm -v\` with the green gate log's toolchain-preflight line (#3446).`
|
|
15828
15835
|
);
|
|
15829
15836
|
}
|
|
15830
15837
|
async function assertNpmMajorPreflightFromWorkflows(deps, repo) {
|
|
@@ -16108,15 +16115,9 @@ async function runRcandResume(deps, options = {}) {
|
|
|
16108
16115
|
note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
|
|
16109
16116
|
};
|
|
16110
16117
|
}
|
|
16111
|
-
async function
|
|
16118
|
+
async function runReleaseResumePipeline(deps, options, prepared) {
|
|
16112
16119
|
const watch = options.watch ?? false;
|
|
16113
|
-
const ctx =
|
|
16114
|
-
await requireCleanTree(deps);
|
|
16115
|
-
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
16116
|
-
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
16117
|
-
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
16118
|
-
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
16119
|
-
const deployModel = await preflight(deps, ctx, "main", meta);
|
|
16120
|
+
const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
|
|
16120
16121
|
const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((t) => t.trim()).filter((t) => /^v\d+\.\d+\.\d+$/.test(t));
|
|
16121
16122
|
const tag = tags[0];
|
|
16122
16123
|
if (!tag) throw new Error("release --resume: no final v* release tag found \u2014 there is no release to resume");
|
|
@@ -16224,6 +16225,36 @@ async function runReleaseResume(deps, options = {}) {
|
|
|
16224
16225
|
note: followUp === "failed" ? `resumed ${tag} (version preserved, not re-cut) \u2014 but a watched deploy/publish run FAILED; the Release stands, re-run the failed job (do NOT re-tag)` : followUp === "pending" ? `resumed ${tag} (version preserved, not re-cut) \u2014 a deploy/publish or alignment follow-up is unresolved; watch it to completion` : `resumed and completed ${tag} \u2014 the original version was preserved, not re-cut`
|
|
16225
16226
|
};
|
|
16226
16227
|
}
|
|
16228
|
+
async function withCheckoutRestoredAfterReleaseResume(deps, startBranch, operation) {
|
|
16229
|
+
let result;
|
|
16230
|
+
try {
|
|
16231
|
+
result = await operation();
|
|
16232
|
+
} catch (error) {
|
|
16233
|
+
let checkoutNote;
|
|
16234
|
+
try {
|
|
16235
|
+
checkoutNote = (await restoreCheckoutAfterRelease(deps, startBranch)).note;
|
|
16236
|
+
} catch (restoreError) {
|
|
16237
|
+
checkoutNote = `checkout restoration failed while returning to ${startBranch}: ${restoreError instanceof Error ? restoreError.message : String(restoreError)}`;
|
|
16238
|
+
}
|
|
16239
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; ${checkoutNote}`);
|
|
16240
|
+
}
|
|
16241
|
+
return withCheckoutRestoredAfterRelease(deps, result, startBranch);
|
|
16242
|
+
}
|
|
16243
|
+
async function runReleaseResume(deps, options = {}) {
|
|
16244
|
+
const ctx = await buildTrainApplyContext(deps);
|
|
16245
|
+
await requireCleanTree(deps);
|
|
16246
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
16247
|
+
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
16248
|
+
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
16249
|
+
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
16250
|
+
const deployModel = await preflight(deps, ctx, "main", meta);
|
|
16251
|
+
const startBranch = directTrack ? "development" : "rc";
|
|
16252
|
+
return withCheckoutRestoredAfterReleaseResume(
|
|
16253
|
+
deps,
|
|
16254
|
+
startBranch,
|
|
16255
|
+
() => runReleaseResumePipeline(deps, options, { ctx, meta, branchHints, directTrack, deployModel })
|
|
16256
|
+
);
|
|
16257
|
+
}
|
|
16227
16258
|
async function runReleaseAbort(deps, options = {}) {
|
|
16228
16259
|
if (!options.approved) {
|
|
16229
16260
|
throw new Error("release --abort requires --apply after explicit approval; nothing was written");
|
|
@@ -16313,8 +16344,9 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
|
16313
16344
|
}
|
|
16314
16345
|
};
|
|
16315
16346
|
const run = await readRun();
|
|
16316
|
-
|
|
16317
|
-
|
|
16347
|
+
const retryableConclusion = run.conclusion === "failure" || run.conclusion === "cancelled";
|
|
16348
|
+
if (run.databaseId !== runId || run.workflowName !== "publish" || run.event !== "release" || run.status !== "completed" || !retryableConclusion) {
|
|
16349
|
+
throw new Error(`run ${runId} is not a completed failed or cancelled Hub publish release run; nothing was written`);
|
|
16318
16350
|
}
|
|
16319
16351
|
const tag = run.headBranch ?? "";
|
|
16320
16352
|
const tagSha = run.headSha ?? "";
|
|
@@ -16325,13 +16357,16 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
|
16325
16357
|
throw new Error(`run ${runId} does not match origin ${tag}; nothing was written`);
|
|
16326
16358
|
}
|
|
16327
16359
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
|
|
16328
|
-
await deps.run("gh", [
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
|
|
16360
|
+
await deps.run("gh", [
|
|
16361
|
+
"run",
|
|
16362
|
+
"rerun",
|
|
16363
|
+
String(runId),
|
|
16364
|
+
"--repo",
|
|
16365
|
+
ctx.repo,
|
|
16366
|
+
...run.conclusion === "failure" ? ["--failed"] : []
|
|
16367
|
+
]);
|
|
16368
|
+
if (options.watch && await watchTenantRun(deps, runId, ctx.repo) !== "success") {
|
|
16369
|
+
throw new Error(`publish run ${runId} did not finish successfully after retry`);
|
|
16335
16370
|
}
|
|
16336
16371
|
return {
|
|
16337
16372
|
command: "release-retry-publish",
|
|
@@ -16341,7 +16376,7 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
|
16341
16376
|
runId,
|
|
16342
16377
|
runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
|
|
16343
16378
|
status: options.watch ? "success" : "pending",
|
|
16344
|
-
note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId}
|
|
16379
|
+
note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} queued for retry`
|
|
16345
16380
|
};
|
|
16346
16381
|
}
|
|
16347
16382
|
async function runTrainApplyPipeline(mode, input) {
|
|
@@ -17130,7 +17165,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
17130
17165
|
ok: info.allow_auto_merge === true,
|
|
17131
17166
|
label: "allow_auto_merge enabled",
|
|
17132
17167
|
detail: info.allow_auto_merge === true ? void 0 : "false or unavailable",
|
|
17133
|
-
remediation: `gh api -X PATCH repos/${repo} -f allow_auto_merge=true -f allow_squash_merge=true -
|
|
17168
|
+
remediation: `gh api -X PATCH repos/${repo} -f allow_auto_merge=true -f allow_squash_merge=true -F delete_branch_on_merge=${MANAGED_DELETE_BRANCH_ON_MERGE}`
|
|
17134
17169
|
});
|
|
17135
17170
|
checks.push({
|
|
17136
17171
|
ok: info.allow_squash_merge === true,
|
|
@@ -17138,9 +17173,9 @@ async function auditRepoCi(repo, deps) {
|
|
|
17138
17173
|
detail: info.allow_squash_merge === true ? void 0 : "false or unavailable"
|
|
17139
17174
|
});
|
|
17140
17175
|
checks.push({
|
|
17141
|
-
ok: info.delete_branch_on_merge ===
|
|
17142
|
-
label: "delete_branch_on_merge
|
|
17143
|
-
detail: info.delete_branch_on_merge ===
|
|
17176
|
+
ok: info.delete_branch_on_merge === MANAGED_DELETE_BRANCH_ON_MERGE,
|
|
17177
|
+
label: "delete_branch_on_merge disabled",
|
|
17178
|
+
detail: info.delete_branch_on_merge === MANAGED_DELETE_BRANCH_ON_MERGE ? void 0 : "merged branches must survive until delayed cleanup proves no open PR uses them as a base (#5789)"
|
|
17144
17179
|
});
|
|
17145
17180
|
checks.push({
|
|
17146
17181
|
ok: info.has_wiki === false,
|
|
@@ -17447,11 +17482,11 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
|
|
|
17447
17482
|
body: {
|
|
17448
17483
|
allow_auto_merge: true,
|
|
17449
17484
|
allow_squash_merge: true,
|
|
17450
|
-
delete_branch_on_merge:
|
|
17485
|
+
delete_branch_on_merge: MANAGED_DELETE_BRANCH_ON_MERGE,
|
|
17451
17486
|
has_wiki: false
|
|
17452
17487
|
}
|
|
17453
17488
|
});
|
|
17454
|
-
applied.push("allow_auto_merge, allow_squash_merge,
|
|
17489
|
+
applied.push("allow_auto_merge, allow_squash_merge, preserve merged remote branches, has_wiki=false");
|
|
17455
17490
|
} catch (e) {
|
|
17456
17491
|
errors.push(e.message);
|
|
17457
17492
|
}
|
|
@@ -17682,6 +17717,12 @@ async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
|
|
|
17682
17717
|
return result;
|
|
17683
17718
|
}
|
|
17684
17719
|
}
|
|
17720
|
+
function ciReconcileExitFailed(input) {
|
|
17721
|
+
if (!input.apply || input.applyResults.length === 0) {
|
|
17722
|
+
return !input.audit.ok || input.applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
|
|
17723
|
+
}
|
|
17724
|
+
return input.applyResults.some((result) => result.errors.length > 0 || result.postApply == null || result.postApply.state !== "clean" && result.postApply.state !== "repaired");
|
|
17725
|
+
}
|
|
17685
17726
|
async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
|
|
17686
17727
|
if (authority.coversReleaseBranches) {
|
|
17687
17728
|
const prOnly = authority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context));
|
|
@@ -20892,6 +20933,11 @@ async function readBoard(options, deps = {}) {
|
|
|
20892
20933
|
report[scope].claimable = filtered.claimable;
|
|
20893
20934
|
report.warnings.push(...filtered.warnings);
|
|
20894
20935
|
}
|
|
20936
|
+
for (const scope of ["primary", "secondary"]) {
|
|
20937
|
+
const demoted = await demoteLiveClaimedClaimables(report[scope], client);
|
|
20938
|
+
report.warnings.push(...demoted.warnings);
|
|
20939
|
+
report.partial = report.partial || demoted.partial;
|
|
20940
|
+
}
|
|
20895
20941
|
return report;
|
|
20896
20942
|
}
|
|
20897
20943
|
function findBoardItem(items, selector, board) {
|
|
@@ -20903,11 +20949,11 @@ function findBoardItem(items, selector, board) {
|
|
|
20903
20949
|
}
|
|
20904
20950
|
async function resolveCurrentRepo(options, deps) {
|
|
20905
20951
|
if (options.repo) return options.repo;
|
|
20906
|
-
const
|
|
20952
|
+
const git3 = deps.git ?? defaultGit;
|
|
20907
20953
|
let gitFailure;
|
|
20908
20954
|
let originUrl = "";
|
|
20909
20955
|
try {
|
|
20910
|
-
originUrl = await
|
|
20956
|
+
originUrl = await git3(["remote", "get-url", "origin"]);
|
|
20911
20957
|
} catch (e) {
|
|
20912
20958
|
const read = gitReadState(e);
|
|
20913
20959
|
if (read.state === "failed") gitFailure = read.detail;
|
|
@@ -21753,6 +21799,66 @@ function boardItemClaim(item) {
|
|
|
21753
21799
|
currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress"
|
|
21754
21800
|
};
|
|
21755
21801
|
}
|
|
21802
|
+
function classifyClaimableMarker(comments, actor, now = Date.now()) {
|
|
21803
|
+
const marker = comments ? latestClaimMarker(comments) : void 0;
|
|
21804
|
+
if (!marker) return { verdict: "claimable" };
|
|
21805
|
+
const ownership = laneOwnership(marker, actor);
|
|
21806
|
+
if (ownership === "mine") return { verdict: "owned", holder: describeClaimMarker(marker) };
|
|
21807
|
+
const age = now - Date.parse(marker.ts);
|
|
21808
|
+
if (Number.isFinite(age) && age >= 0 && age < CLAIM_MARKER_LIVE_MS) {
|
|
21809
|
+
return { verdict: "taken", holder: describeClaimMarker(marker) };
|
|
21810
|
+
}
|
|
21811
|
+
return { verdict: "claimable" };
|
|
21812
|
+
}
|
|
21813
|
+
async function demoteLiveClaimedClaimables(scope, client, actor = describeSessionIdentity(), deps = {}) {
|
|
21814
|
+
const warnings = [];
|
|
21815
|
+
let partial = false;
|
|
21816
|
+
const fetchComments = deps.fetchComments ?? (async (item) => {
|
|
21817
|
+
if (item.details) return item.details.comments;
|
|
21818
|
+
const comments = await client.restPaginate(`repos/${item.repository}/issues/${item.number}/comments`);
|
|
21819
|
+
return comments.map((comment) => ({ body: comment.body ?? "" }));
|
|
21820
|
+
});
|
|
21821
|
+
const stillClaimable = [];
|
|
21822
|
+
const movedTaken = [];
|
|
21823
|
+
const movedOwned = [];
|
|
21824
|
+
for (const item of scope.claimable) {
|
|
21825
|
+
if (item.contentType !== "Issue") {
|
|
21826
|
+
stillClaimable.push(item);
|
|
21827
|
+
continue;
|
|
21828
|
+
}
|
|
21829
|
+
let comments;
|
|
21830
|
+
try {
|
|
21831
|
+
comments = await fetchComments(item);
|
|
21832
|
+
} catch {
|
|
21833
|
+
comments = void 0;
|
|
21834
|
+
}
|
|
21835
|
+
if (comments === void 0) {
|
|
21836
|
+
partial = true;
|
|
21837
|
+
warnings.push(`${item.ref} left claimable: claim-marker evidence could not be read`);
|
|
21838
|
+
continue;
|
|
21839
|
+
}
|
|
21840
|
+
const verdict = classifyClaimableMarker(comments, actor, deps.now);
|
|
21841
|
+
if (verdict.verdict === "claimable") {
|
|
21842
|
+
stillClaimable.push(item);
|
|
21843
|
+
} else if (verdict.verdict === "owned") {
|
|
21844
|
+
movedOwned.push(item);
|
|
21845
|
+
warnings.push(`${item.ref} is held by your own lane (${verdict.holder}) \u2014 moved to user-owned`);
|
|
21846
|
+
} else {
|
|
21847
|
+
movedTaken.push(item);
|
|
21848
|
+
warnings.push(`${item.ref} is held by a live claim marker (lane ${verdict.holder}) \u2014 no longer claimable`);
|
|
21849
|
+
}
|
|
21850
|
+
}
|
|
21851
|
+
scope.claimable = stillClaimable;
|
|
21852
|
+
if (movedTaken.length) {
|
|
21853
|
+
scope.taken.push(...movedTaken);
|
|
21854
|
+
scope.taken.sort(compareItems);
|
|
21855
|
+
}
|
|
21856
|
+
if (movedOwned.length) {
|
|
21857
|
+
scope.userOwned.push(...movedOwned);
|
|
21858
|
+
scope.userOwned.sort(compareItems);
|
|
21859
|
+
}
|
|
21860
|
+
return { warnings, partial };
|
|
21861
|
+
}
|
|
21756
21862
|
async function postClaimMarkerComment(client, item, actor = describeSessionIdentity()) {
|
|
21757
21863
|
try {
|
|
21758
21864
|
const marker = {
|
|
@@ -23374,11 +23480,69 @@ function renderPluginCachePlan(plan, applied) {
|
|
|
23374
23480
|
return lines.join("\n");
|
|
23375
23481
|
}
|
|
23376
23482
|
|
|
23377
|
-
// src/
|
|
23483
|
+
// src/train-self-converge.ts
|
|
23378
23484
|
var import_node_child_process10 = require("node:child_process");
|
|
23485
|
+
var SELF_CONVERGE_INSTALL_TIMEOUT_MS = 12e4;
|
|
23486
|
+
var SELF_CONVERGE_VERSION_TIMEOUT_MS = 3e4;
|
|
23487
|
+
var isWin2 = process.platform === "win32";
|
|
23488
|
+
var defaultDeps = {
|
|
23489
|
+
// Pin the exact released version (never `@latest` — a cached packument resolves the tag to the
|
|
23490
|
+
// PREVIOUS version in exactly this post-publish window, #3422). execFileP rejects on nonzero exit.
|
|
23491
|
+
update: async (target) => {
|
|
23492
|
+
const args = ["install", "-g", `@mutmutco/cli@${target}`];
|
|
23493
|
+
if (isWin2) {
|
|
23494
|
+
await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout: SELF_CONVERGE_INSTALL_TIMEOUT_MS });
|
|
23495
|
+
} else {
|
|
23496
|
+
await execFileP2("npm", args, { timeout: SELF_CONVERGE_INSTALL_TIMEOUT_MS });
|
|
23497
|
+
}
|
|
23498
|
+
},
|
|
23499
|
+
// Read the version the freshly installed `mmi-cli` on PATH actually resolves to. Unparseable or
|
|
23500
|
+
// failed read answers undefined so the flow reports not-taken-effect instead of guessing green.
|
|
23501
|
+
readInstalledVersion: async () => {
|
|
23502
|
+
try {
|
|
23503
|
+
const { stdout } = isWin2 ? await execFileP2("cmd.exe", ["/c", "mmi-cli", "--version"], { timeout: SELF_CONVERGE_VERSION_TIMEOUT_MS }) : await execFileP2("mmi-cli", ["--version"], { timeout: SELF_CONVERGE_VERSION_TIMEOUT_MS });
|
|
23504
|
+
return parseNpmVersion(stdout);
|
|
23505
|
+
} catch {
|
|
23506
|
+
return void 0;
|
|
23507
|
+
}
|
|
23508
|
+
},
|
|
23509
|
+
// Re-exec the SAME command line in the fresh CLI with inherited stdio (spawn, not execFile, so
|
|
23510
|
+
// output streams live) and hand its exit code back — the stale parent exits with it (#5762).
|
|
23511
|
+
reexec: () => new Promise((resolve6) => {
|
|
23512
|
+
const args = process.argv.slice(2);
|
|
23513
|
+
const child2 = isWin2 ? (0, import_node_child_process10.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process10.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
|
|
23514
|
+
child2.on("exit", (code) => resolve6(code ?? 1));
|
|
23515
|
+
child2.on("error", () => resolve6(1));
|
|
23516
|
+
}),
|
|
23517
|
+
// stderr only — stdout may be carrying --json output.
|
|
23518
|
+
warn: (line) => console.error(line)
|
|
23519
|
+
};
|
|
23520
|
+
async function selfConvergeTrainCli(input) {
|
|
23521
|
+
const deps = { ...defaultDeps, ...input.deps };
|
|
23522
|
+
try {
|
|
23523
|
+
deps.warn(
|
|
23524
|
+
`train: running mmi-cli ${input.currentVersion} is stale against released ${input.releasedVersion} \u2014 self-converging via npm install -g @mutmutco/cli@${input.releasedVersion} before resuming ${input.commandName}`
|
|
23525
|
+
);
|
|
23526
|
+
await deps.update(input.releasedVersion);
|
|
23527
|
+
const installed = await deps.readInstalledVersion();
|
|
23528
|
+
if (!installed || compareVersions(installed, input.releasedVersion) < 0) {
|
|
23529
|
+
return {
|
|
23530
|
+
kind: "failed",
|
|
23531
|
+
error: `self-converge did not take effect: mmi-cli on PATH still resolves to ${installed ?? "unknown"}, expected >= ${input.releasedVersion}`
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23534
|
+
const reexecCode = await deps.reexec();
|
|
23535
|
+
return { kind: "converged", reexecCode };
|
|
23536
|
+
} catch (e) {
|
|
23537
|
+
return { kind: "failed", error: e instanceof Error ? e.message : String(e) };
|
|
23538
|
+
}
|
|
23539
|
+
}
|
|
23540
|
+
|
|
23541
|
+
// src/statusline-invalidate.ts
|
|
23542
|
+
var import_node_child_process11 = require("node:child_process");
|
|
23379
23543
|
function invalidateStatuslineBoardCache() {
|
|
23380
23544
|
try {
|
|
23381
|
-
const child2 = (0,
|
|
23545
|
+
const child2 = (0, import_node_child_process11.spawn)("jerv-cli", ["lane", "ops", "invalidate-board"], {
|
|
23382
23546
|
detached: true,
|
|
23383
23547
|
stdio: "ignore",
|
|
23384
23548
|
windowsHide: true
|
|
@@ -23651,36 +23815,36 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
23651
23815
|
}
|
|
23652
23816
|
|
|
23653
23817
|
// src/hotfix-coverage.ts
|
|
23654
|
-
var
|
|
23818
|
+
var import_node_child_process12 = require("node:child_process");
|
|
23655
23819
|
var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
23656
23820
|
function checkHotfixCoverage(options = {}) {
|
|
23657
23821
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
23658
23822
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
23659
|
-
const
|
|
23823
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
23660
23824
|
const revList = (range) => {
|
|
23661
|
-
const out =
|
|
23825
|
+
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
23662
23826
|
return out ? out.split("\n") : [];
|
|
23663
23827
|
};
|
|
23664
23828
|
const isAncestor = (sha, ref) => {
|
|
23665
23829
|
try {
|
|
23666
|
-
|
|
23830
|
+
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
23667
23831
|
return true;
|
|
23668
23832
|
} catch {
|
|
23669
23833
|
return false;
|
|
23670
23834
|
}
|
|
23671
23835
|
};
|
|
23672
23836
|
const patchId = (sha) => {
|
|
23673
|
-
const diff =
|
|
23837
|
+
const diff = git3(["show", "--no-color", "--pretty=format:", sha]);
|
|
23674
23838
|
if (!diff.trim()) return null;
|
|
23675
|
-
const out =
|
|
23839
|
+
const out = git3(["patch-id", "--stable"], { input: diff }).trim();
|
|
23676
23840
|
return out ? out.split(" ")[0] : null;
|
|
23677
23841
|
};
|
|
23678
23842
|
const changedPaths = (sha) => {
|
|
23679
|
-
const out =
|
|
23843
|
+
const out = git3(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
|
|
23680
23844
|
return out ? out.split("\n") : [];
|
|
23681
23845
|
};
|
|
23682
23846
|
const cherrySources = (sha) => {
|
|
23683
|
-
const message =
|
|
23847
|
+
const message = git3(["log", "-1", "--format=%B", sha]);
|
|
23684
23848
|
return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
|
|
23685
23849
|
};
|
|
23686
23850
|
const mainOnly = revList(`${rcRef}..${mainRef}`);
|
|
@@ -23695,7 +23859,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
23695
23859
|
}
|
|
23696
23860
|
};
|
|
23697
23861
|
const commits = mainOnly.map((sha) => {
|
|
23698
|
-
const subject =
|
|
23862
|
+
const subject = git3(["log", "-1", "--format=%s", sha]).trim();
|
|
23699
23863
|
const paths = changedPaths(sha);
|
|
23700
23864
|
if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
|
|
23701
23865
|
return { sha, subject, coverage: "exempt-distribution" };
|
|
@@ -23724,21 +23888,21 @@ function checkHotfixCoverage(options = {}) {
|
|
|
23724
23888
|
}
|
|
23725
23889
|
function checkHotfixCarries(options) {
|
|
23726
23890
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
23727
|
-
const
|
|
23891
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process12.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
23728
23892
|
const isAncestor = (sha, ref) => {
|
|
23729
23893
|
try {
|
|
23730
|
-
|
|
23894
|
+
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
23731
23895
|
return true;
|
|
23732
23896
|
} catch {
|
|
23733
23897
|
return false;
|
|
23734
23898
|
}
|
|
23735
23899
|
};
|
|
23736
23900
|
const revList = (range) => {
|
|
23737
|
-
const out =
|
|
23901
|
+
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
23738
23902
|
return out ? out.split("\n") : [];
|
|
23739
23903
|
};
|
|
23740
23904
|
const cherrySources = (sha) => {
|
|
23741
|
-
const message =
|
|
23905
|
+
const message = git3(["log", "-1", "--format=%B", sha]);
|
|
23742
23906
|
return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
|
|
23743
23907
|
};
|
|
23744
23908
|
const carried = /* @__PURE__ */ new Set();
|
|
@@ -24821,7 +24985,7 @@ function renderDeployPortDoctor(report) {
|
|
|
24821
24985
|
}
|
|
24822
24986
|
|
|
24823
24987
|
// src/spawn-policy-core.ts
|
|
24824
|
-
var
|
|
24988
|
+
var import_node_child_process13 = require("node:child_process");
|
|
24825
24989
|
var import_node_fs24 = require("node:fs");
|
|
24826
24990
|
var import_node_path21 = require("node:path");
|
|
24827
24991
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
@@ -24892,7 +25056,7 @@ function findViolationsInSource(raw) {
|
|
|
24892
25056
|
return found;
|
|
24893
25057
|
}
|
|
24894
25058
|
function policedFiles(root) {
|
|
24895
|
-
const r = (0,
|
|
25059
|
+
const r = (0, import_node_child_process13.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
24896
25060
|
cwd: root,
|
|
24897
25061
|
encoding: "utf8",
|
|
24898
25062
|
windowsHide: true,
|
|
@@ -24926,12 +25090,25 @@ function runSpawnPolicy(root) {
|
|
|
24926
25090
|
}
|
|
24927
25091
|
|
|
24928
25092
|
// src/test-policy-core.ts
|
|
24929
|
-
var
|
|
25093
|
+
var import_node_child_process15 = require("node:child_process");
|
|
24930
25094
|
var import_node_fs25 = require("node:fs");
|
|
24931
25095
|
var import_node_path22 = require("node:path");
|
|
24932
25096
|
|
|
24933
25097
|
// ../scripts/test-command-policy-core.mjs
|
|
25098
|
+
var import_node_child_process14 = require("node:child_process");
|
|
24934
25099
|
var TEST_COMMAND_CLASS = "test";
|
|
25100
|
+
var TRAILER_KEY = "Test-Policy-Override";
|
|
25101
|
+
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
25102
|
+
var REC = "";
|
|
25103
|
+
var FLD = "";
|
|
25104
|
+
var WAIVABLE_KINDS = [
|
|
25105
|
+
"mandatory-zone-untested",
|
|
25106
|
+
"unrequested-test-file",
|
|
25107
|
+
"protected-removed",
|
|
25108
|
+
"stale-protected-entry",
|
|
25109
|
+
"stale-satisfied-by"
|
|
25110
|
+
];
|
|
25111
|
+
var TEST_WORK_KIND = "unrequested-test-file";
|
|
24935
25112
|
function translateGlob(glob) {
|
|
24936
25113
|
let out = "";
|
|
24937
25114
|
for (let i = 0; i < glob.length; i += 1) {
|
|
@@ -24974,7 +25151,7 @@ function matchedMandatoryGlobs(paths, mandatory) {
|
|
|
24974
25151
|
return list.some((path2) => re.test(path2));
|
|
24975
25152
|
});
|
|
24976
25153
|
}
|
|
24977
|
-
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {}) {
|
|
25154
|
+
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null } = {}) {
|
|
24978
25155
|
const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
|
|
24979
25156
|
if (!regulated) {
|
|
24980
25157
|
return {
|
|
@@ -24987,7 +25164,8 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {})
|
|
|
24987
25164
|
};
|
|
24988
25165
|
}
|
|
24989
25166
|
const matched = matchedMandatoryGlobs(paths, mandatory);
|
|
24990
|
-
const
|
|
25167
|
+
const overrideAuthorizes = Array.isArray(override?.kinds) && override.kinds.includes(TEST_WORK_KIND);
|
|
25168
|
+
const testCommandsAllowed = matched.length > 0 || overrideAuthorizes;
|
|
24991
25169
|
return {
|
|
24992
25170
|
configuredMandatoryCount,
|
|
24993
25171
|
matchedMandatoryGlobs: matched,
|
|
@@ -25000,22 +25178,78 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {})
|
|
|
25000
25178
|
}
|
|
25001
25179
|
};
|
|
25002
25180
|
}
|
|
25181
|
+
function git(args, cwd) {
|
|
25182
|
+
return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
25183
|
+
}
|
|
25184
|
+
function parseScope(value) {
|
|
25185
|
+
const scoped = /^\[([^\]]*)\]\s*([\s\S]*)$/.exec(value);
|
|
25186
|
+
if (!scoped) return { kinds: [...WAIVABLE_KINDS], reason: value, unknown: [] };
|
|
25187
|
+
const named = scoped[1].split(",").map((k) => k.trim()).filter(Boolean);
|
|
25188
|
+
return {
|
|
25189
|
+
kinds: named,
|
|
25190
|
+
reason: scoped[2].trim(),
|
|
25191
|
+
unknown: named.filter((k) => !WAIVABLE_KINDS.includes(k))
|
|
25192
|
+
};
|
|
25193
|
+
}
|
|
25194
|
+
function invisibleTrailer(sha, line) {
|
|
25195
|
+
return {
|
|
25196
|
+
kind: "malformed-override-trailer",
|
|
25197
|
+
paths: [],
|
|
25198
|
+
detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)} carries
|
|
25199
|
+
${line}
|
|
25200
|
+
but \`git log --format='%(trailers:key=${TRAILER_KEY})'\` reports nothing for it, so the waiver would
|
|
25201
|
+
exist only to the regex that granted it. The trailer was chosen over a flag because it can be
|
|
25202
|
+
AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
|
|
25203
|
+
Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
|
|
25204
|
+
trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
|
|
25205
|
+
block. Indent the continuation lines, and leave nothing but trailers in that paragraph.
|
|
25206
|
+
This is fixable forward: push a LATER commit on this branch carrying a well-formed trailer.
|
|
25207
|
+
The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
|
|
25208
|
+
};
|
|
25209
|
+
}
|
|
25210
|
+
function unknownScope(sha, unknown) {
|
|
25211
|
+
return {
|
|
25212
|
+
kind: "malformed-override-trailer",
|
|
25213
|
+
paths: [],
|
|
25214
|
+
detail: `UNKNOWN OVERRIDE SCOPE \u2014 commit ${sha.slice(0, 8)} scopes its waiver to ${unknown.join(", ")}, which this
|
|
25215
|
+
gate cannot report.
|
|
25216
|
+
Waivable kinds: ${WAIVABLE_KINDS.join(", ")}.
|
|
25217
|
+
Refused rather than widened: treating a typo as "waive everything" is how a scoped waiver turns
|
|
25218
|
+
into a blanket exemption without anyone deciding it should.`
|
|
25219
|
+
};
|
|
25220
|
+
}
|
|
25221
|
+
function isShallowRepository(cwd) {
|
|
25222
|
+
try {
|
|
25223
|
+
return git(["rev-parse", "--is-shallow-repository"], cwd).trim() !== "false";
|
|
25224
|
+
} catch {
|
|
25225
|
+
return true;
|
|
25226
|
+
}
|
|
25227
|
+
}
|
|
25228
|
+
function readOverride(base, cwd) {
|
|
25229
|
+
const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
|
|
25230
|
+
const refusals = [];
|
|
25231
|
+
let override = null;
|
|
25232
|
+
for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
|
|
25233
|
+
const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
|
|
25234
|
+
if (!sha) continue;
|
|
25235
|
+
if (override) break;
|
|
25236
|
+
const value = (trailer ?? "").trim();
|
|
25237
|
+
if (!value) {
|
|
25238
|
+
const shaped = OVERRIDE_RE.exec(body ?? "");
|
|
25239
|
+
if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
|
|
25240
|
+
continue;
|
|
25241
|
+
}
|
|
25242
|
+
const { kinds, reason, unknown } = parseScope(value);
|
|
25243
|
+
if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
|
|
25244
|
+
else override = { sha, reason, kinds };
|
|
25245
|
+
}
|
|
25246
|
+
return { override, refusals };
|
|
25247
|
+
}
|
|
25003
25248
|
|
|
25004
25249
|
// src/test-policy-core.ts
|
|
25005
25250
|
var POLICY_FILE = "test-policy.json";
|
|
25006
25251
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
25007
25252
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
25008
|
-
var TRAILER_KEY = "Test-Policy-Override";
|
|
25009
|
-
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
25010
|
-
var REC = "";
|
|
25011
|
-
var FLD = "";
|
|
25012
|
-
var WAIVABLE_KINDS = [
|
|
25013
|
-
"mandatory-zone-untested",
|
|
25014
|
-
"unrequested-test-file",
|
|
25015
|
-
"protected-removed",
|
|
25016
|
-
"stale-protected-entry",
|
|
25017
|
-
"stale-satisfied-by"
|
|
25018
|
-
];
|
|
25019
25253
|
function translate(glob) {
|
|
25020
25254
|
let out = "";
|
|
25021
25255
|
for (let i = 0; i < glob.length; i++) {
|
|
@@ -25373,15 +25607,15 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
25373
25607
|
}
|
|
25374
25608
|
return findings;
|
|
25375
25609
|
}
|
|
25376
|
-
function
|
|
25377
|
-
return (0,
|
|
25610
|
+
function git2(args, cwd) {
|
|
25611
|
+
return (0, import_node_child_process15.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
25378
25612
|
}
|
|
25379
25613
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
25380
25614
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
25381
25615
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
25382
25616
|
function parseTrailers(message, cwd) {
|
|
25383
25617
|
try {
|
|
25384
|
-
return (0,
|
|
25618
|
+
return (0, import_node_child_process15.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
25385
25619
|
windowsHide: true,
|
|
25386
25620
|
cwd,
|
|
25387
25621
|
input: message,
|
|
@@ -25418,19 +25652,12 @@ function resolveBase(cwd, explicit) {
|
|
|
25418
25652
|
const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
|
|
25419
25653
|
for (const ref of candidates) {
|
|
25420
25654
|
try {
|
|
25421
|
-
return
|
|
25655
|
+
return git2(["merge-base", "HEAD", ref], cwd).trim();
|
|
25422
25656
|
} catch {
|
|
25423
25657
|
}
|
|
25424
25658
|
}
|
|
25425
25659
|
return null;
|
|
25426
25660
|
}
|
|
25427
|
-
function isShallowRepository(cwd) {
|
|
25428
|
-
try {
|
|
25429
|
-
return git(["rev-parse", "--is-shallow-repository"], cwd).trim() !== "false";
|
|
25430
|
-
} catch {
|
|
25431
|
-
return true;
|
|
25432
|
-
}
|
|
25433
|
-
}
|
|
25434
25661
|
function untrustworthyRange(cwd, base) {
|
|
25435
25662
|
if (!base) {
|
|
25436
25663
|
return {
|
|
@@ -25452,65 +25679,11 @@ function untrustworthyRange(cwd, base) {
|
|
|
25452
25679
|
Fetch full history \u2014 actions/checkout with \`fetch-depth: 0\`, or \`git fetch --unshallow\`.`
|
|
25453
25680
|
};
|
|
25454
25681
|
}
|
|
25455
|
-
function
|
|
25456
|
-
|
|
25457
|
-
if (!scoped) return { kinds: [...WAIVABLE_KINDS], reason: value, unknown: [] };
|
|
25458
|
-
const named = scoped[1].split(",").map((k) => k.trim()).filter(Boolean);
|
|
25459
|
-
return {
|
|
25460
|
-
kinds: named,
|
|
25461
|
-
reason: scoped[2].trim(),
|
|
25462
|
-
unknown: named.filter((k) => !WAIVABLE_KINDS.includes(k))
|
|
25463
|
-
};
|
|
25464
|
-
}
|
|
25465
|
-
function invisibleTrailer(sha, line) {
|
|
25466
|
-
return {
|
|
25467
|
-
kind: "malformed-override-trailer",
|
|
25468
|
-
paths: [],
|
|
25469
|
-
detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)} carries
|
|
25470
|
-
${line}
|
|
25471
|
-
but \`git log --format='%(trailers:key=${TRAILER_KEY})'\` reports nothing for it, so the waiver would
|
|
25472
|
-
exist only to the regex that granted it. The trailer was chosen over a flag because it can be
|
|
25473
|
-
AUDITED, and a reason the auditor cannot see is not a reason this gate accepts (#3628).
|
|
25474
|
-
Git reads trailers from the LAST paragraph only, and every line of that paragraph must be a
|
|
25475
|
-
trailer or an indented continuation \u2014 one bare line such as \`Closes #123\` disqualifies the whole
|
|
25476
|
-
block. Indent the continuation lines, and leave nothing but trailers in that paragraph.
|
|
25477
|
-
This is fixable forward: push a LATER commit on this branch carrying a well-formed trailer.
|
|
25478
|
-
The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
|
|
25479
|
-
};
|
|
25480
|
-
}
|
|
25481
|
-
function unknownScope(sha, unknown) {
|
|
25482
|
-
return {
|
|
25483
|
-
kind: "malformed-override-trailer",
|
|
25484
|
-
paths: [],
|
|
25485
|
-
detail: `UNKNOWN OVERRIDE SCOPE \u2014 commit ${sha.slice(0, 8)} scopes its waiver to ${unknown.join(", ")}, which this
|
|
25486
|
-
gate cannot report.
|
|
25487
|
-
Waivable kinds: ${WAIVABLE_KINDS.join(", ")}.
|
|
25488
|
-
Refused rather than widened: treating a typo as "waive everything" is how a scoped waiver turns
|
|
25489
|
-
into a blanket exemption without anyone deciding it should.`
|
|
25490
|
-
};
|
|
25491
|
-
}
|
|
25492
|
-
function readOverride(base, cwd) {
|
|
25493
|
-
const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
|
|
25494
|
-
const refusals = [];
|
|
25495
|
-
let override = null;
|
|
25496
|
-
for (const record of git(["log", `${base}..HEAD`, `--format=${format}`], cwd).split(REC)) {
|
|
25497
|
-
const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
|
|
25498
|
-
if (!sha) continue;
|
|
25499
|
-
if (override) break;
|
|
25500
|
-
const value = (trailer ?? "").trim();
|
|
25501
|
-
if (!value) {
|
|
25502
|
-
const shaped = OVERRIDE_RE.exec(body ?? "");
|
|
25503
|
-
if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
|
|
25504
|
-
continue;
|
|
25505
|
-
}
|
|
25506
|
-
const { kinds, reason, unknown } = parseScope(value);
|
|
25507
|
-
if (unknown.length > 0) refusals.push(unknownScope(sha, unknown));
|
|
25508
|
-
else override = { sha, reason, kinds };
|
|
25509
|
-
}
|
|
25510
|
-
return { override, refusals };
|
|
25682
|
+
function readOverride2(base, cwd) {
|
|
25683
|
+
return readOverride(base, cwd);
|
|
25511
25684
|
}
|
|
25512
25685
|
function changedFilesSince(base, cwd) {
|
|
25513
|
-
const raw =
|
|
25686
|
+
const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
|
|
25514
25687
|
const changed = raw ? raw.split("\n").map((line) => {
|
|
25515
25688
|
const parts = line.split(" ");
|
|
25516
25689
|
const norm = (p) => p.replace(/\\/g, "/");
|
|
@@ -25518,12 +25691,12 @@ function changedFilesSince(base, cwd) {
|
|
|
25518
25691
|
if (parts.length > 2) row.from = norm(parts[1]);
|
|
25519
25692
|
return row;
|
|
25520
25693
|
}) : [];
|
|
25521
|
-
const untracked =
|
|
25694
|
+
const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
|
|
25522
25695
|
return [...changed, ...untracked];
|
|
25523
25696
|
}
|
|
25524
25697
|
function blobAt(base, path2, cwd) {
|
|
25525
25698
|
try {
|
|
25526
|
-
return
|
|
25699
|
+
return git2(["show", `${base}:${path2}`], cwd);
|
|
25527
25700
|
} catch {
|
|
25528
25701
|
return null;
|
|
25529
25702
|
}
|
|
@@ -25539,7 +25712,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
25539
25712
|
before: blobAt(base, path2, root),
|
|
25540
25713
|
after: readFileOrNull2((0, import_node_path22.join)(root, path2))
|
|
25541
25714
|
}));
|
|
25542
|
-
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ?
|
|
25715
|
+
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
|
|
25543
25716
|
const present = (path2) => exists((0, import_node_path22.join)(root, path2));
|
|
25544
25717
|
const removedByThisDiff = removedPaths(changed);
|
|
25545
25718
|
const staleFindings = [];
|
|
@@ -25572,7 +25745,8 @@ function runTestPolicy(root, deps = {}) {
|
|
|
25572
25745
|
const commandPolicy = evaluateTestCommandPolicy({
|
|
25573
25746
|
paths: changed.map((f) => f.path),
|
|
25574
25747
|
mandatory: policy.mandatory,
|
|
25575
|
-
regulated: policy.declared !== false
|
|
25748
|
+
regulated: policy.declared !== false,
|
|
25749
|
+
override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null
|
|
25576
25750
|
});
|
|
25577
25751
|
const result = {
|
|
25578
25752
|
ok: findings.length === 0,
|
|
@@ -25594,7 +25768,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
25594
25768
|
}
|
|
25595
25769
|
|
|
25596
25770
|
// src/dist-drift.ts
|
|
25597
|
-
var
|
|
25771
|
+
var import_node_child_process16 = require("node:child_process");
|
|
25598
25772
|
var import_node_crypto7 = require("node:crypto");
|
|
25599
25773
|
var import_node_fs27 = require("node:fs");
|
|
25600
25774
|
var import_node_os13 = require("node:os");
|
|
@@ -25739,7 +25913,7 @@ function bomPathFor(root) {
|
|
|
25739
25913
|
}
|
|
25740
25914
|
}
|
|
25741
25915
|
function rebuildTo(packageRoot, outDir) {
|
|
25742
|
-
(0,
|
|
25916
|
+
(0, import_node_child_process16.execFileSync)(process.execPath, ["build.mjs"], {
|
|
25743
25917
|
cwd: packageRoot,
|
|
25744
25918
|
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
25745
25919
|
windowsHide: true,
|
|
@@ -27456,9 +27630,9 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
27456
27630
|
|
|
27457
27631
|
// src/schedules-commands.ts
|
|
27458
27632
|
var import_promises4 = require("node:fs/promises");
|
|
27459
|
-
var
|
|
27633
|
+
var import_node_child_process17 = require("node:child_process");
|
|
27460
27634
|
var import_node_util7 = require("node:util");
|
|
27461
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
27635
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process17.execFile);
|
|
27462
27636
|
var AWS_REGION = "eu-central-1";
|
|
27463
27637
|
var AWS_TIMEOUT_MS = 3e4;
|
|
27464
27638
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -29551,7 +29725,7 @@ function registerBootstrapCommands(program3) {
|
|
|
29551
29725
|
}
|
|
29552
29726
|
let seedPrUrl;
|
|
29553
29727
|
if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && seededToBranch > 0) {
|
|
29554
|
-
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-
|
|
29728
|
+
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-F", `delete_branch_on_merge=${MANAGED_DELETE_BRANCH_ON_MERGE}`]).catch(() => {
|
|
29555
29729
|
});
|
|
29556
29730
|
const openPrs = await gh(["pr", "list", "--repo", repo, "--head", seedPlan.branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
|
|
29557
29731
|
const prDecision = decideSeedPrAction(JSON.parse(openPrs.stdout || "[]"));
|
|
@@ -29600,8 +29774,8 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
29600
29774
|
}
|
|
29601
29775
|
if (o.execute && !onlyTarget && o.class === "deployable") {
|
|
29602
29776
|
try {
|
|
29603
|
-
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-
|
|
29604
|
-
applied.push("merge settings: allow_auto_merge, squash,
|
|
29777
|
+
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-F", `delete_branch_on_merge=${MANAGED_DELETE_BRANCH_ON_MERGE}`]);
|
|
29778
|
+
applied.push("merge settings: allow_auto_merge, squash, preserve merged remote branches");
|
|
29605
29779
|
} catch (e) {
|
|
29606
29780
|
applied.push(`merge settings (failed: ${e.message})`);
|
|
29607
29781
|
}
|
|
@@ -30784,6 +30958,9 @@ function buildPrMergeArgs(input) {
|
|
|
30784
30958
|
if (input.auto) args.push("--auto");
|
|
30785
30959
|
return args;
|
|
30786
30960
|
}
|
|
30961
|
+
function buildPrDisableAutoArgs(input) {
|
|
30962
|
+
return ["pr", "merge", input.number, ...input.repoArgs, "--disable-auto"];
|
|
30963
|
+
}
|
|
30787
30964
|
function basePolicyBlocksImmediateMerge(message) {
|
|
30788
30965
|
return /base branch policy prohibits|protected branch|required status check|merge queue/i.test(message);
|
|
30789
30966
|
}
|
|
@@ -30794,6 +30971,12 @@ function ghPrMergeLocalBranchDeleteWarning(message) {
|
|
|
30794
30971
|
function mergeAutoRejectedPrAlreadyClean(message) {
|
|
30795
30972
|
return /clean status \(enablePullRequestAutoMerge\)|is in clean status/i.test(message);
|
|
30796
30973
|
}
|
|
30974
|
+
function assertRemoteBranchPreservedOnMerge(repo, deleteBranchOnMerge) {
|
|
30975
|
+
if (deleteBranchOnMerge === false) return;
|
|
30976
|
+
throw new Error(
|
|
30977
|
+
`pr merge: ${repo} does not prove delete_branch_on_merge=false. Automatic deletion can irreversibly close stacked PRs. Run \`mmi-cli devops ci reconcile --repo ${repo} --apply\`, then retry; remote branch cleanup is delayed.`
|
|
30978
|
+
);
|
|
30979
|
+
}
|
|
30797
30980
|
function mergeMethodFromParentCount(parentCount) {
|
|
30798
30981
|
if (parentCount >= 2) return "merge";
|
|
30799
30982
|
return void 0;
|
|
@@ -31043,6 +31226,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
31043
31226
|
|
|
31044
31227
|
// src/worktree-merge-cleanup.ts
|
|
31045
31228
|
var import_node_fs36 = require("node:fs");
|
|
31229
|
+
var import_node_path34 = require("node:path");
|
|
31046
31230
|
|
|
31047
31231
|
// src/worktree-evidence-archive.ts
|
|
31048
31232
|
var import_node_fs35 = require("node:fs");
|
|
@@ -31221,6 +31405,18 @@ function formatArtifactsArchiveReceipt(archive) {
|
|
|
31221
31405
|
function normPath2(p) {
|
|
31222
31406
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
31223
31407
|
}
|
|
31408
|
+
function removeResidueDirectory(wtPath) {
|
|
31409
|
+
try {
|
|
31410
|
+
try {
|
|
31411
|
+
if ((0, import_node_fs36.lstatSync)((0, import_node_path34.join)(wtPath, "node_modules")).isSymbolicLink()) (0, import_node_fs36.rmdirSync)((0, import_node_path34.join)(wtPath, "node_modules"));
|
|
31412
|
+
} catch {
|
|
31413
|
+
}
|
|
31414
|
+
(0, import_node_fs36.rmSync)(wtPath, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
31415
|
+
return { ok: true };
|
|
31416
|
+
} catch (e) {
|
|
31417
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
31418
|
+
}
|
|
31419
|
+
}
|
|
31224
31420
|
function samePath(a, b) {
|
|
31225
31421
|
const na = normPath2(a);
|
|
31226
31422
|
const nb = normPath2(b);
|
|
@@ -31261,19 +31457,19 @@ function formatWorktreeRemovalFailureDetail(options) {
|
|
|
31261
31457
|
const detail = options.removeError.trim() || "git worktree remove exited nonzero";
|
|
31262
31458
|
return `${detail}; registered=${options.registered ? "yes" : "no"}; pathExists=${options.pathExists ? "yes" : "no"}; path=${options.wtPath}`;
|
|
31263
31459
|
}
|
|
31264
|
-
async function resolveMergeBaseTimestampMs(
|
|
31460
|
+
async function resolveMergeBaseTimestampMs(git3, baseRef, branch) {
|
|
31265
31461
|
try {
|
|
31266
|
-
const mergeBase = (await
|
|
31462
|
+
const mergeBase = (await git3(["merge-base", baseRef, branch])).trim();
|
|
31267
31463
|
if (!mergeBase) return 0;
|
|
31268
|
-
const sec = parseCommitTimestampSec(await
|
|
31464
|
+
const sec = parseCommitTimestampSec(await git3(["show", "-s", "--format=%ct", mergeBase]));
|
|
31269
31465
|
return sec ? sec * 1e3 : 0;
|
|
31270
31466
|
} catch {
|
|
31271
31467
|
return 0;
|
|
31272
31468
|
}
|
|
31273
31469
|
}
|
|
31274
|
-
async function verifyBranchHead(
|
|
31470
|
+
async function verifyBranchHead(git3, branch, expectedHeadOid) {
|
|
31275
31471
|
if (!expectedHeadOid) return { ok: false, reason: "branch-head-unverified" };
|
|
31276
|
-
const currentHead = (await
|
|
31472
|
+
const currentHead = (await git3(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
|
|
31277
31473
|
if (!currentHead) return { ok: false, reason: "branch-head-unverified" };
|
|
31278
31474
|
if (currentHead !== expectedHeadOid) {
|
|
31279
31475
|
return { ok: false, reason: "unpushed-branch", error: `${currentHead} != ${expectedHeadOid}` };
|
|
@@ -31288,17 +31484,17 @@ async function teardownWorktreeStage(worktreePath) {
|
|
|
31288
31484
|
return { status: "failed", error: e instanceof Error ? e.message : String(e) };
|
|
31289
31485
|
}
|
|
31290
31486
|
}
|
|
31291
|
-
async function removeWorktreeWithReconcile(wtPath,
|
|
31487
|
+
async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExists) {
|
|
31292
31488
|
const attemptRemove = async () => {
|
|
31293
31489
|
try {
|
|
31294
|
-
await
|
|
31490
|
+
await git3(["worktree", "remove", "--force", wtPath]);
|
|
31295
31491
|
return void 0;
|
|
31296
31492
|
} catch (e) {
|
|
31297
31493
|
return formatGitCommandError(e);
|
|
31298
31494
|
}
|
|
31299
31495
|
};
|
|
31300
31496
|
const pruneAndVerify = async (reason) => {
|
|
31301
|
-
await
|
|
31497
|
+
await git3(["worktree", "prune"]).catch(() => "");
|
|
31302
31498
|
let worktrees2;
|
|
31303
31499
|
try {
|
|
31304
31500
|
worktrees2 = await listWorktrees();
|
|
@@ -31328,6 +31524,16 @@ async function removeWorktreeWithReconcile(wtPath, git2, listWorktrees, pathExis
|
|
|
31328
31524
|
};
|
|
31329
31525
|
}
|
|
31330
31526
|
if (!isWorktreePathRegistered(worktrees, wtPath)) return pruneAndVerify("reconciled-unregistered");
|
|
31527
|
+
await git3(["worktree", "prune"]).catch(() => "");
|
|
31528
|
+
try {
|
|
31529
|
+
worktrees = await listWorktrees();
|
|
31530
|
+
} catch (e) {
|
|
31531
|
+
return {
|
|
31532
|
+
status: "failed",
|
|
31533
|
+
error: `could not verify worktree registration after prune: ${formatGitCommandError(e)}; path=${wtPath}`
|
|
31534
|
+
};
|
|
31535
|
+
}
|
|
31536
|
+
if (!isWorktreePathRegistered(worktrees, wtPath)) return pruneAndVerify("reconciled-pruned");
|
|
31331
31537
|
const retryError = await attemptRemove();
|
|
31332
31538
|
if (!retryError) return pruneAndVerify("reconciled-after-retry");
|
|
31333
31539
|
if (isNotAWorkingTreeMessage(retryError) || isNotAWorkingTreeMessage(firstError)) {
|
|
@@ -31405,8 +31611,8 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
31405
31611
|
return report;
|
|
31406
31612
|
}
|
|
31407
31613
|
const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, { pathExists });
|
|
31408
|
-
const
|
|
31409
|
-
const headCheck = await verifyBranchHead(
|
|
31614
|
+
const git3 = (args) => safeCwd ? execGit(["-C", safeCwd, ...args]) : execGit(args);
|
|
31615
|
+
const headCheck = await verifyBranchHead(git3, branch, options.expectedHeadOid);
|
|
31410
31616
|
if (!headCheck.ok) {
|
|
31411
31617
|
report.localBranch = {
|
|
31412
31618
|
name: branch,
|
|
@@ -31469,8 +31675,8 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
31469
31675
|
}
|
|
31470
31676
|
const removal = await removeWorktreeWithReconcile(
|
|
31471
31677
|
wtPath,
|
|
31472
|
-
|
|
31473
|
-
async () => parseGitWorktreePorcelain(await
|
|
31678
|
+
git3,
|
|
31679
|
+
async () => parseGitWorktreePorcelain(await git3(["worktree", "list", "--porcelain"])),
|
|
31474
31680
|
pathExists
|
|
31475
31681
|
);
|
|
31476
31682
|
if (removal.status === "failed") {
|
|
@@ -31493,11 +31699,20 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
31493
31699
|
stageTeardown,
|
|
31494
31700
|
tmpEvidenceCount: tmpEvidence.length
|
|
31495
31701
|
};
|
|
31702
|
+
if (pathExists(wtPath)) {
|
|
31703
|
+
const residue = options.removeResidueDir?.(wtPath) ?? removeResidueDirectory(wtPath);
|
|
31704
|
+
if (residue.ok) {
|
|
31705
|
+
report.worktree.residue = "swept";
|
|
31706
|
+
} else {
|
|
31707
|
+
report.worktree.residue = "left";
|
|
31708
|
+
report.worktree.residueError = residue.error;
|
|
31709
|
+
}
|
|
31710
|
+
}
|
|
31496
31711
|
try {
|
|
31497
|
-
await
|
|
31712
|
+
await git3(["branch", "-D", branch]);
|
|
31498
31713
|
report.localBranch = { name: branch, status: "deleted" };
|
|
31499
31714
|
} catch (e) {
|
|
31500
|
-
const remaining = await
|
|
31715
|
+
const remaining = await git3(["branch", "--list", branch]).catch(() => "");
|
|
31501
31716
|
report.localBranch = remaining.trim() ? { name: branch, status: "failed", error: e instanceof Error ? e.message : String(e) } : { name: branch, status: "already-gone" };
|
|
31502
31717
|
}
|
|
31503
31718
|
return report;
|
|
@@ -31520,6 +31735,9 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
31520
31735
|
} else if (wt.status === "preserved") {
|
|
31521
31736
|
lines.push(`pr merge: preserved worktree ${wt.path} (--preserve-worktree)`);
|
|
31522
31737
|
}
|
|
31738
|
+
if (wt.residue === "left") {
|
|
31739
|
+
lines.push(`pr merge: worktree ${wt.path} registration is gone but residue remains \u2014 delete by hand: ${wt.residueError ?? wt.path}`);
|
|
31740
|
+
}
|
|
31523
31741
|
if (wt.artifactsArchive?.status === "archived" && wt.artifactsArchive.path) {
|
|
31524
31742
|
lines.push(`pr merge: archived worktree evidence to ${wt.artifactsArchive.path}`);
|
|
31525
31743
|
} else if (wt.artifactsArchive?.status === "blocked" && wt.artifactsArchive.error) {
|
|
@@ -31528,6 +31746,17 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
31528
31746
|
return lines;
|
|
31529
31747
|
}
|
|
31530
31748
|
|
|
31749
|
+
// src/utf8-receipt.ts
|
|
31750
|
+
var import_node_fs37 = require("node:fs");
|
|
31751
|
+
var import_node_path35 = require("node:path");
|
|
31752
|
+
function writeUtf8Receipt(path2, text) {
|
|
31753
|
+
(0, import_node_fs37.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
|
|
31754
|
+
const body = text.endsWith("\n") ? text : `${text}
|
|
31755
|
+
`;
|
|
31756
|
+
(0, import_node_fs37.writeFileSync)(path2, body, "utf8");
|
|
31757
|
+
return Buffer.byteLength(body, "utf8");
|
|
31758
|
+
}
|
|
31759
|
+
|
|
31531
31760
|
// src/board-commands.ts
|
|
31532
31761
|
function refuseRateLimited(e, json) {
|
|
31533
31762
|
if (!isRateLimitedError(e)) return false;
|
|
@@ -31555,7 +31784,9 @@ function registerBoardCommands(program3) {
|
|
|
31555
31784
|
includeAllBodies: o.bodies,
|
|
31556
31785
|
allowPartial: o.allowPartial
|
|
31557
31786
|
}, o.direct ? {} : { snapshot: registryClientDeps(config) });
|
|
31558
|
-
|
|
31787
|
+
const output = o.json ? JSON.stringify(report) : renderBoardReport(report);
|
|
31788
|
+
if (!o.out) console.log(output);
|
|
31789
|
+
else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
|
|
31559
31790
|
} catch (e) {
|
|
31560
31791
|
return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
|
|
31561
31792
|
}
|
|
@@ -31564,7 +31795,7 @@ function registerBoardCommands(program3) {
|
|
|
31564
31795
|
return alreadyClaimed ? `Check ${ref}: claimed and In Progress, no live contest - claim would renew the lease (nothing written)` : `Check ${ref}: free - claim would proceed (nothing written)`;
|
|
31565
31796
|
}
|
|
31566
31797
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
31567
|
-
board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n").action((o) => runBoardRead(o));
|
|
31798
|
+
board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nNever capture the JSON with a shell redirect on Windows: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
31568
31799
|
withExamples(mutating(
|
|
31569
31800
|
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").addHelpText("after", "\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
31570
31801
|
(_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
|
|
@@ -32227,7 +32458,7 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
32227
32458
|
}
|
|
32228
32459
|
|
|
32229
32460
|
// src/issue-commands.ts
|
|
32230
|
-
var
|
|
32461
|
+
var import_node_fs38 = require("node:fs");
|
|
32231
32462
|
var import_node_crypto11 = require("node:crypto");
|
|
32232
32463
|
|
|
32233
32464
|
// src/learning-closure-rate.ts
|
|
@@ -32414,7 +32645,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
32414
32645
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
32415
32646
|
const patch = {};
|
|
32416
32647
|
let bodyChanged = false;
|
|
32417
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
32648
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs38.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
32418
32649
|
if (options.titleFile !== void 0) {
|
|
32419
32650
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
32420
32651
|
} else if (options.title !== void 0) {
|
|
@@ -33031,7 +33262,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
33031
33262
|
if (opts.batch) {
|
|
33032
33263
|
let specs;
|
|
33033
33264
|
try {
|
|
33034
|
-
const raw = (0,
|
|
33265
|
+
const raw = (0, import_node_fs38.readFileSync)(opts.batch, "utf8");
|
|
33035
33266
|
specs = JSON.parse(raw);
|
|
33036
33267
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
33037
33268
|
} catch (e) {
|
|
@@ -33106,8 +33337,8 @@ ${lines}`, {
|
|
|
33106
33337
|
}
|
|
33107
33338
|
|
|
33108
33339
|
// src/train-commands.ts
|
|
33109
|
-
var
|
|
33110
|
-
var
|
|
33340
|
+
var import_node_fs39 = require("node:fs");
|
|
33341
|
+
var import_node_path36 = require("node:path");
|
|
33111
33342
|
var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
33112
33343
|
function resolveReleaseBumpIntent(raw) {
|
|
33113
33344
|
const intent = typeof raw === "string" ? raw.trim() : "";
|
|
@@ -33118,7 +33349,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
33118
33349
|
}
|
|
33119
33350
|
function readRepoVersion() {
|
|
33120
33351
|
try {
|
|
33121
|
-
return JSON.parse((0,
|
|
33352
|
+
return JSON.parse((0, import_node_fs39.readFileSync)((0, import_node_path36.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
33122
33353
|
} catch {
|
|
33123
33354
|
return void 0;
|
|
33124
33355
|
}
|
|
@@ -33275,9 +33506,9 @@ function registerDeployCommands(program3) {
|
|
|
33275
33506
|
}
|
|
33276
33507
|
|
|
33277
33508
|
// src/discovery-commands.ts
|
|
33278
|
-
var
|
|
33509
|
+
var import_node_fs40 = require("node:fs");
|
|
33279
33510
|
var import_node_os17 = require("node:os");
|
|
33280
|
-
var
|
|
33511
|
+
var import_node_path37 = require("node:path");
|
|
33281
33512
|
var GC_GH_TIMEOUT_MS2 = 2e4;
|
|
33282
33513
|
async function collectStatus() {
|
|
33283
33514
|
const repo = await resolveRepo();
|
|
@@ -33467,8 +33698,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
33467
33698
|
}
|
|
33468
33699
|
const home = (0, import_node_os17.homedir)();
|
|
33469
33700
|
const plugin = onboardPluginGate({
|
|
33470
|
-
readKnown: () => readFileSyncSafe((0,
|
|
33471
|
-
readSettings: () => readFileSyncSafe((0,
|
|
33701
|
+
readKnown: () => readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
|
|
33702
|
+
readSettings: () => readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync)
|
|
33472
33703
|
});
|
|
33473
33704
|
return { track, board, registry: registry2, secrets, plugin, doors: opts.doors ?? [], nextCommand };
|
|
33474
33705
|
}
|
|
@@ -33777,7 +34008,10 @@ function findCommandInManifest(manifest, commandPath3) {
|
|
|
33777
34008
|
return house ? houseRootAsParent(manifest, house) : void 0;
|
|
33778
34009
|
}
|
|
33779
34010
|
const visit = (command) => {
|
|
33780
|
-
|
|
34011
|
+
const aliasMatches = command.aliases?.some(
|
|
34012
|
+
(alias) => [command.path, command.flat_path].some((path2) => `${path2.slice(0, path2.lastIndexOf(" ") + 1)}${alias}` === commandPath3)
|
|
34013
|
+
);
|
|
34014
|
+
if (command.path === commandPath3 || command.flat_path === commandPath3 || aliasMatches) return command;
|
|
33781
34015
|
for (const child2 of command.subcommands) {
|
|
33782
34016
|
const found = visit(child2);
|
|
33783
34017
|
if (found) return found;
|
|
@@ -34228,8 +34462,8 @@ function registerPrLifecycleCommands(program3) {
|
|
|
34228
34462
|
}
|
|
34229
34463
|
|
|
34230
34464
|
// src/post-merge-recon.ts
|
|
34231
|
-
var
|
|
34232
|
-
var
|
|
34465
|
+
var import_node_fs41 = require("node:fs");
|
|
34466
|
+
var import_node_path38 = require("node:path");
|
|
34233
34467
|
|
|
34234
34468
|
// src/cross-repo-filing-issue.ts
|
|
34235
34469
|
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
@@ -34395,16 +34629,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
34395
34629
|
}
|
|
34396
34630
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
34397
34631
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
34398
|
-
(0,
|
|
34399
|
-
(0,
|
|
34632
|
+
(0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
|
|
34633
|
+
(0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
34400
34634
|
`, "utf8");
|
|
34401
34635
|
return path2;
|
|
34402
34636
|
}
|
|
34403
34637
|
function clearPostMergeReconRecovery(cwd, repo, pr2) {
|
|
34404
34638
|
const path2 = postMergeReconStatePath(cwd, repo, pr2);
|
|
34405
|
-
if (!(0,
|
|
34639
|
+
if (!(0, import_node_fs41.existsSync)(path2)) return;
|
|
34406
34640
|
try {
|
|
34407
|
-
(0,
|
|
34641
|
+
(0, import_node_fs41.unlinkSync)(path2);
|
|
34408
34642
|
} catch {
|
|
34409
34643
|
}
|
|
34410
34644
|
}
|
|
@@ -37461,17 +37695,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
37461
37695
|
}
|
|
37462
37696
|
function ghHostsConfigPath(env, platform2) {
|
|
37463
37697
|
const sep3 = platform2 === "win32" ? "\\" : "/";
|
|
37464
|
-
const
|
|
37698
|
+
const join33 = (...parts) => parts.join(sep3);
|
|
37465
37699
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
37466
|
-
if (explicit) return
|
|
37700
|
+
if (explicit) return join33(explicit, "hosts.yml");
|
|
37467
37701
|
if (platform2 === "win32") {
|
|
37468
37702
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
37469
|
-
return appData ?
|
|
37703
|
+
return appData ? join33(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
37470
37704
|
}
|
|
37471
37705
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
37472
|
-
if (xdg) return
|
|
37706
|
+
if (xdg) return join33(xdg, "gh", "hosts.yml");
|
|
37473
37707
|
const home = env.HOME?.trim();
|
|
37474
|
-
return home ?
|
|
37708
|
+
return home ? join33(home, ".config", "gh", "hosts.yml") : void 0;
|
|
37475
37709
|
}
|
|
37476
37710
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
37477
37711
|
let hostIndent = null;
|
|
@@ -37521,10 +37755,10 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
37521
37755
|
}
|
|
37522
37756
|
|
|
37523
37757
|
// src/doctor-io.ts
|
|
37524
|
-
var
|
|
37758
|
+
var import_node_fs42 = require("node:fs");
|
|
37525
37759
|
var import_node_os18 = require("node:os");
|
|
37526
|
-
var
|
|
37527
|
-
var
|
|
37760
|
+
var import_node_path39 = require("node:path");
|
|
37761
|
+
var import_node_child_process18 = require("node:child_process");
|
|
37528
37762
|
var import_node_util8 = require("node:util");
|
|
37529
37763
|
|
|
37530
37764
|
// src/discard-sink.ts
|
|
@@ -37533,27 +37767,27 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
|
|
|
37533
37767
|
}
|
|
37534
37768
|
|
|
37535
37769
|
// src/doctor-io.ts
|
|
37536
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
37770
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process18.execFile);
|
|
37537
37771
|
function execFileCapture(file, args, opts = {}) {
|
|
37538
37772
|
const sink = nodeDiscardSinkPath();
|
|
37539
|
-
const inFd = (0,
|
|
37540
|
-
const errFd = (0,
|
|
37773
|
+
const inFd = (0, import_node_fs42.openSync)(sink, "r");
|
|
37774
|
+
const errFd = (0, import_node_fs42.openSync)(sink, "w");
|
|
37541
37775
|
try {
|
|
37542
|
-
return (0,
|
|
37776
|
+
return (0, import_node_child_process18.execFileSync)(file, args, {
|
|
37543
37777
|
...opts,
|
|
37544
37778
|
encoding: "utf8",
|
|
37545
37779
|
stdio: [inFd, "pipe", errFd]
|
|
37546
37780
|
});
|
|
37547
37781
|
} finally {
|
|
37548
|
-
(0,
|
|
37549
|
-
(0,
|
|
37782
|
+
(0, import_node_fs42.closeSync)(inFd);
|
|
37783
|
+
(0, import_node_fs42.closeSync)(errFd);
|
|
37550
37784
|
}
|
|
37551
37785
|
}
|
|
37552
37786
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
37553
37787
|
function installedClaudePluginVersion() {
|
|
37554
37788
|
try {
|
|
37555
37789
|
const file = JSON.parse(
|
|
37556
|
-
(0,
|
|
37790
|
+
(0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
37557
37791
|
);
|
|
37558
37792
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
37559
37793
|
if (versions.length === 0) return void 0;
|
|
@@ -37564,7 +37798,7 @@ function installedClaudePluginVersion() {
|
|
|
37564
37798
|
}
|
|
37565
37799
|
function manifestVersion(path2) {
|
|
37566
37800
|
try {
|
|
37567
|
-
const manifest = JSON.parse((0,
|
|
37801
|
+
const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
|
|
37568
37802
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
37569
37803
|
} catch {
|
|
37570
37804
|
return void 0;
|
|
@@ -37573,12 +37807,12 @@ function manifestVersion(path2) {
|
|
|
37573
37807
|
function readHermesPluginEvidence(env = process.env) {
|
|
37574
37808
|
const host = hermesConfigRoot(env);
|
|
37575
37809
|
const root = hermesPluginRoot(env);
|
|
37576
|
-
const installRecordPresent = (0,
|
|
37577
|
-
const manifestPath = (0,
|
|
37810
|
+
const installRecordPresent = (0, import_node_fs42.existsSync)(root);
|
|
37811
|
+
const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
|
|
37578
37812
|
let installedVersion;
|
|
37579
37813
|
let manifest = "missing";
|
|
37580
37814
|
try {
|
|
37581
|
-
const text = (0,
|
|
37815
|
+
const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
|
|
37582
37816
|
let version;
|
|
37583
37817
|
try {
|
|
37584
37818
|
const parsed = JSON.parse(text).version;
|
|
@@ -37592,20 +37826,20 @@ function readHermesPluginEvidence(env = process.env) {
|
|
|
37592
37826
|
if (version) {
|
|
37593
37827
|
installedVersion = version;
|
|
37594
37828
|
manifest = "valid";
|
|
37595
|
-
} else if ((0,
|
|
37829
|
+
} else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
|
|
37596
37830
|
} catch {
|
|
37597
|
-
if ((0,
|
|
37831
|
+
if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
|
|
37598
37832
|
}
|
|
37599
37833
|
let skills = false;
|
|
37600
37834
|
try {
|
|
37601
|
-
skills = (0,
|
|
37835
|
+
skills = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "skills")) && (0, import_node_fs42.statSync)((0, import_node_path39.join)(root, "skills")).isDirectory();
|
|
37602
37836
|
} catch {
|
|
37603
37837
|
}
|
|
37604
37838
|
return {
|
|
37605
|
-
hostPresent: (0,
|
|
37839
|
+
hostPresent: (0, import_node_fs42.existsSync)(host),
|
|
37606
37840
|
installRecordPresent,
|
|
37607
37841
|
manifest,
|
|
37608
|
-
payloadPresent: (0,
|
|
37842
|
+
payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
|
|
37609
37843
|
...installedVersion ? { installedVersion } : {}
|
|
37610
37844
|
};
|
|
37611
37845
|
}
|
|
@@ -37613,7 +37847,7 @@ function installedSurfacePluginVersion(surface) {
|
|
|
37613
37847
|
const token = surfaceToken(surface);
|
|
37614
37848
|
if (token === "kilo") {
|
|
37615
37849
|
try {
|
|
37616
|
-
const stamp = (0,
|
|
37850
|
+
const stamp = (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
37617
37851
|
return stamp || void 0;
|
|
37618
37852
|
} catch {
|
|
37619
37853
|
return void 0;
|
|
@@ -37621,13 +37855,13 @@ function installedSurfacePluginVersion(surface) {
|
|
|
37621
37855
|
}
|
|
37622
37856
|
if (token === "hermes") return readHermesPluginEvidence().installedVersion;
|
|
37623
37857
|
if (token === "cursor") {
|
|
37624
|
-
return manifestVersion((0,
|
|
37858
|
+
return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
37625
37859
|
}
|
|
37626
37860
|
if (token === "jervcode") {
|
|
37627
37861
|
return installedJervCodePackageVersion();
|
|
37628
37862
|
}
|
|
37629
37863
|
if (token === "kimi") {
|
|
37630
|
-
return manifestVersion((0,
|
|
37864
|
+
return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
37631
37865
|
}
|
|
37632
37866
|
if (token === "claude") return installedClaudePluginVersion();
|
|
37633
37867
|
if (token !== "codex") return void 0;
|
|
@@ -37661,13 +37895,13 @@ function worktreeRootSync() {
|
|
|
37661
37895
|
}
|
|
37662
37896
|
var gitignorePath = () => {
|
|
37663
37897
|
const root = worktreeRootSync();
|
|
37664
|
-
return root === null ? null : (0,
|
|
37898
|
+
return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
|
|
37665
37899
|
};
|
|
37666
37900
|
function readGitignore() {
|
|
37667
37901
|
const path2 = gitignorePath();
|
|
37668
37902
|
if (path2 === null) return null;
|
|
37669
37903
|
try {
|
|
37670
|
-
return (0,
|
|
37904
|
+
return (0, import_node_fs42.readFileSync)(path2, "utf8");
|
|
37671
37905
|
} catch {
|
|
37672
37906
|
return null;
|
|
37673
37907
|
}
|
|
@@ -37676,14 +37910,14 @@ function writeGitignore(content) {
|
|
|
37676
37910
|
const path2 = gitignorePath();
|
|
37677
37911
|
if (path2 === null) return false;
|
|
37678
37912
|
try {
|
|
37679
|
-
(0,
|
|
37913
|
+
(0, import_node_fs42.writeFileSync)(path2, content, "utf8");
|
|
37680
37914
|
return true;
|
|
37681
37915
|
} catch {
|
|
37682
37916
|
return false;
|
|
37683
37917
|
}
|
|
37684
37918
|
}
|
|
37685
37919
|
function lineEndingState(root) {
|
|
37686
|
-
const attributesPresent = (0,
|
|
37920
|
+
const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
|
|
37687
37921
|
try {
|
|
37688
37922
|
const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
|
|
37689
37923
|
windowsHide: true
|
|
@@ -37750,8 +37984,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
37750
37984
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
37751
37985
|
try {
|
|
37752
37986
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
37753
|
-
if (!hostsPath || !(0,
|
|
37754
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
37987
|
+
if (!hostsPath || !(0, import_node_fs43.existsSync)(hostsPath)) return void 0;
|
|
37988
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs43.readFileSync)(hostsPath, "utf8")));
|
|
37755
37989
|
} catch {
|
|
37756
37990
|
return void 0;
|
|
37757
37991
|
}
|
|
@@ -37759,7 +37993,7 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
37759
37993
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
37760
37994
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
37761
37995
|
function envHealLockPath(home) {
|
|
37762
|
-
return (0,
|
|
37996
|
+
return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
37763
37997
|
}
|
|
37764
37998
|
async function withEnvHealLock(what, run) {
|
|
37765
37999
|
try {
|
|
@@ -37896,7 +38130,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
37896
38130
|
);
|
|
37897
38131
|
const result = applyPluginCachePlan(
|
|
37898
38132
|
plan,
|
|
37899
|
-
(p) => (0,
|
|
38133
|
+
(p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
|
|
37900
38134
|
stagingApplyFsGuard(configRoot)
|
|
37901
38135
|
);
|
|
37902
38136
|
return {
|
|
@@ -37934,7 +38168,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
37934
38168
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
37935
38169
|
// get a permanent — demanding an artifact it never asked for.
|
|
37936
38170
|
docsIndexState: (root) => {
|
|
37937
|
-
if (!(0,
|
|
38171
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
37938
38172
|
const real = createDocsIndexDeps(root);
|
|
37939
38173
|
let docs2;
|
|
37940
38174
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -37943,7 +38177,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
37943
38177
|
},
|
|
37944
38178
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
37945
38179
|
healDocsIndex: (root) => {
|
|
37946
|
-
if (!(0,
|
|
38180
|
+
if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
37947
38181
|
const real = createDocsIndexDeps(root);
|
|
37948
38182
|
let docs2;
|
|
37949
38183
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -38040,7 +38274,18 @@ async function requireFreshTrainCli(commandName) {
|
|
|
38040
38274
|
releasedVersion
|
|
38041
38275
|
});
|
|
38042
38276
|
if (report.ok) return;
|
|
38043
|
-
|
|
38277
|
+
if (process.env.MMI_TRAIN_NO_SELF_UPDATE === "1") {
|
|
38278
|
+
throw new Error(staleTrainCliMessage(report, commandName));
|
|
38279
|
+
}
|
|
38280
|
+
const converge = await selfConvergeTrainCli({
|
|
38281
|
+
currentVersion: resolveClientVersion(),
|
|
38282
|
+
releasedVersion: report.releasedVersion,
|
|
38283
|
+
commandName
|
|
38284
|
+
});
|
|
38285
|
+
if (converge.kind === "failed") {
|
|
38286
|
+
throw new Error(`${commandName}: ${converge.error}. ${staleTrainCliMessage(report, commandName)}`);
|
|
38287
|
+
}
|
|
38288
|
+
process.exit(converge.reexecCode);
|
|
38044
38289
|
}
|
|
38045
38290
|
var cachedCommandPaths;
|
|
38046
38291
|
function allCommandPaths() {
|
|
@@ -38253,19 +38498,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
38253
38498
|
});
|
|
38254
38499
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
38255
38500
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
38256
|
-
const path2 = (0,
|
|
38257
|
-
const current = (0,
|
|
38501
|
+
const path2 = (0, import_node_path40.join)(process.cwd(), ".gitignore");
|
|
38502
|
+
const current = (0, import_node_fs43.existsSync)(path2) ? (0, import_node_fs43.readFileSync)(path2, "utf8") : null;
|
|
38258
38503
|
const plan = planManagedGitignore(current);
|
|
38259
38504
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
38260
38505
|
if (opts.json) {
|
|
38261
|
-
if (opts.write && plan.changed) (0,
|
|
38506
|
+
if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
|
|
38262
38507
|
console.log(JSON.stringify(plan, null, 2));
|
|
38263
38508
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
38264
38509
|
return;
|
|
38265
38510
|
}
|
|
38266
38511
|
if (opts.write) {
|
|
38267
38512
|
if (plan.changed) {
|
|
38268
|
-
(0,
|
|
38513
|
+
(0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
|
|
38269
38514
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
38270
38515
|
} else {
|
|
38271
38516
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -38451,7 +38696,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
38451
38696
|
try {
|
|
38452
38697
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
38453
38698
|
if (o.repo) args.push("--repo", o.repo);
|
|
38454
|
-
spawnDetachedSelf(args, { spawn:
|
|
38699
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process19.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
38455
38700
|
} catch {
|
|
38456
38701
|
}
|
|
38457
38702
|
}
|
|
@@ -38841,7 +39086,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
38841
39086
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
38842
39087
|
if (o.secretsFile) {
|
|
38843
39088
|
try {
|
|
38844
|
-
vars.push(`secrets=${(0,
|
|
39089
|
+
vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
|
|
38845
39090
|
} catch (e) {
|
|
38846
39091
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
38847
39092
|
}
|
|
@@ -39666,11 +39911,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
39666
39911
|
}
|
|
39667
39912
|
});
|
|
39668
39913
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
39669
|
-
const wfDir = (0,
|
|
39670
|
-
if (!(0,
|
|
39671
|
-
return (0,
|
|
39914
|
+
const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
|
|
39915
|
+
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
39916
|
+
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
39672
39917
|
try {
|
|
39673
|
-
return workflowReportsPrChecks((0,
|
|
39918
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
|
|
39674
39919
|
} catch {
|
|
39675
39920
|
return true;
|
|
39676
39921
|
}
|
|
@@ -39736,16 +39981,16 @@ function ciAuditDeps() {
|
|
|
39736
39981
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
39737
39982
|
readSeedFile: (path2) => {
|
|
39738
39983
|
if (!root) return null;
|
|
39739
|
-
const fullPath = (0,
|
|
39740
|
-
return (0,
|
|
39984
|
+
const fullPath = (0, import_node_path40.join)(root, path2);
|
|
39985
|
+
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
39741
39986
|
}
|
|
39742
39987
|
};
|
|
39743
39988
|
}
|
|
39744
39989
|
function hubRoot() {
|
|
39745
|
-
const fromPkg = (0,
|
|
39990
|
+
const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
|
|
39746
39991
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
39747
|
-
if ((0,
|
|
39748
|
-
if ((0,
|
|
39992
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
|
|
39993
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
|
|
39749
39994
|
return null;
|
|
39750
39995
|
}
|
|
39751
39996
|
async function waitLoopCorePool(label) {
|
|
@@ -40017,15 +40262,24 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
40017
40262
|
else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
|
|
40018
40263
|
if (result.status === "failed") process.exitCode = 1;
|
|
40019
40264
|
});
|
|
40020
|
-
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
|
|
40265
|
+
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").addOption(new Option("--disable-auto", "disable a queued auto-merge without merging").conflicts(["auto", "wait", "squash", "merge", "rebase", "preserveWorktree", "gc", "squashBodyFile", "force"])).option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--squash-body-file <path>", "squash commit body (overrides GitHub COMMIT_MESSAGES); use when a pushed commit mentions close/fix/resolve + #N that must not close (#5723)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
|
|
40021
40266
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
40022
40267
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
40268
|
+
if (o.disableAuto) {
|
|
40269
|
+
await execFileP2("gh", buildPrDisableAutoArgs({ number, repoArgs }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e) => {
|
|
40270
|
+
const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
|
|
40271
|
+
if (note) throw new Error(`gh pr merge ${number} --disable-auto: ${note}`);
|
|
40272
|
+
throw e;
|
|
40273
|
+
});
|
|
40274
|
+
console.log(JSON.stringify({ mergeStatus: "auto-merge-disabled", pr: number, ...o.repo ? { repo: o.repo } : {} }));
|
|
40275
|
+
return;
|
|
40276
|
+
}
|
|
40023
40277
|
const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
|
|
40024
|
-
const prMeta = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName,headRefOid", "--jq", "{head: .headRefName, base: .baseRefName, oid: .headRefOid}"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => JSON.parse(r.stdout)).catch(async (e) => {
|
|
40278
|
+
const prMeta = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName,headRefOid,state", "--jq", "{head: .headRefName, base: .baseRefName, oid: .headRefOid, state: .state}"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => JSON.parse(r.stdout)).catch(async (e) => {
|
|
40025
40279
|
if (!isGitHubRateLimitError(e) || !repoForPostCleanup) throw e;
|
|
40026
40280
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 reading PR #${number} via REST instead (#4588).`);
|
|
40027
40281
|
const snapshot = await fetchRestPrSnapshot(number, repoForPostCleanup);
|
|
40028
|
-
return { head: snapshot.headRef, base: snapshot.baseRef, oid: snapshot.headSha };
|
|
40282
|
+
return { head: snapshot.headRef, base: snapshot.baseRef, oid: snapshot.headSha, state: snapshot.merged ? "MERGED" : snapshot.state.toUpperCase() };
|
|
40029
40283
|
});
|
|
40030
40284
|
const headRef = prMeta.head;
|
|
40031
40285
|
const baseRef = prMeta.base;
|
|
@@ -40046,7 +40300,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40046
40300
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
40047
40301
|
closingGuardInput,
|
|
40048
40302
|
method === "--squash",
|
|
40049
|
-
o.squashBodyFile ? (0,
|
|
40303
|
+
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
40050
40304
|
);
|
|
40051
40305
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
40052
40306
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -40060,6 +40314,11 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40060
40314
|
return;
|
|
40061
40315
|
}
|
|
40062
40316
|
if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
|
|
40317
|
+
if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
|
|
40318
|
+
if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
|
|
40319
|
+
const repoSettings = await defaultGitHubClient().rest("GET", `repos/${repoForPostCleanup}`);
|
|
40320
|
+
assertRemoteBranchPreservedOnMerge(repoForPostCleanup, repoSettings.delete_branch_on_merge);
|
|
40321
|
+
}
|
|
40063
40322
|
const startingPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
40064
40323
|
const housekeeping = assertPrMergeHousekeepingClean(startingPath || process.cwd(), "pr merge", { force: o.force });
|
|
40065
40324
|
const beforeWorktreesRead = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => ({ state: "ok", stdout: r.stdout })).catch((e) => ({ state: "failed", error: e.message || "git worktree list failed" }));
|
|
@@ -40107,7 +40366,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40107
40366
|
}
|
|
40108
40367
|
const remoteBefore = await remoteBranchExists2(headRef);
|
|
40109
40368
|
let upgradedToAuto = false;
|
|
40110
|
-
let remoteNotAttemptedReason = "
|
|
40369
|
+
let remoteNotAttemptedReason = "preserved-delayed-cleanup";
|
|
40111
40370
|
const overrideBody = mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
|
|
40112
40371
|
number,
|
|
40113
40372
|
repoArgs,
|
|
@@ -40130,7 +40389,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40130
40389
|
}
|
|
40131
40390
|
if (!repoForPostCleanup) throw e;
|
|
40132
40391
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
40133
|
-
const commitMessage = bodyFile ? (0,
|
|
40392
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
40134
40393
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
40135
40394
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
40136
40395
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -40219,7 +40478,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
40219
40478
|
preserveWorktree: o.preserveWorktree,
|
|
40220
40479
|
gcAcknowledged: o.gc,
|
|
40221
40480
|
expectedHeadOid: headRefOid,
|
|
40222
|
-
pathExists: (p) => (0,
|
|
40481
|
+
pathExists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
40223
40482
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout
|
|
40224
40483
|
});
|
|
40225
40484
|
} catch (e) {
|
|
@@ -40326,7 +40585,7 @@ function trainApplyDeps() {
|
|
|
40326
40585
|
run: async (file, args) => {
|
|
40327
40586
|
const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && args[0] === "scripts/jerv-gateway-release-deploy.sh" ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
|
|
40328
40587
|
try {
|
|
40329
|
-
return
|
|
40588
|
+
return isWin3 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
|
|
40330
40589
|
} catch (e) {
|
|
40331
40590
|
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
40332
40591
|
}
|
|
@@ -40422,6 +40681,7 @@ function renderReleaseResume(r) {
|
|
|
40422
40681
|
if (r.dispatch) lines.push(` deploy: ${r.dispatch.note}`);
|
|
40423
40682
|
if (r.devRollForward) lines.push(` development: ${r.devRollForward.note}`);
|
|
40424
40683
|
if (r.rcAlignment) lines.push(` rc: ${r.rcAlignment.note}`);
|
|
40684
|
+
if (r.checkout) lines.push(` checkout: ${r.checkout.note}`);
|
|
40425
40685
|
return lines.join("\n");
|
|
40426
40686
|
}
|
|
40427
40687
|
function renderReleaseAbort(r) {
|
|
@@ -40506,7 +40766,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
40506
40766
|
{ flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
|
|
40507
40767
|
{ flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
|
|
40508
40768
|
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
|
|
40509
|
-
{ flags: "--retry-publish <run-id>", description: "with --apply, retry
|
|
40769
|
+
{ flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
|
|
40510
40770
|
];
|
|
40511
40771
|
for (const f of RELEASE_ONLY_FLAGS) {
|
|
40512
40772
|
if (commandName === "release") {
|
|
@@ -40762,7 +41022,7 @@ function hotfixStatusDeps() {
|
|
|
40762
41022
|
run: async (file, args) => {
|
|
40763
41023
|
const timeout = file === "npm" ? STATUS_NPM_MS : file === "gh" ? STATUS_GH_MS : STATUS_GIT_MS;
|
|
40764
41024
|
try {
|
|
40765
|
-
return
|
|
41025
|
+
return isWin3 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
|
|
40766
41026
|
} catch (e) {
|
|
40767
41027
|
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
40768
41028
|
}
|
|
@@ -40805,8 +41065,7 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
|
|
|
40805
41065
|
console.log("\nDry-run \u2014 re-run with --apply to patch merge settings and activate product rulesets (master-admin).");
|
|
40806
41066
|
}
|
|
40807
41067
|
}
|
|
40808
|
-
|
|
40809
|
-
if (!audit.ok || applyFailed) process.exitCode = 1;
|
|
41068
|
+
if (ciReconcileExitFailed({ apply: o.apply === true, audit, applyResults })) process.exitCode = 1;
|
|
40810
41069
|
});
|
|
40811
41070
|
registerBootstrapCommands(program2);
|
|
40812
41071
|
var access = program2.command("access").description("org access audit (read-only)");
|
|
@@ -40855,18 +41114,18 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
40855
41114
|
targets = resolution.targets;
|
|
40856
41115
|
}
|
|
40857
41116
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
40858
|
-
const fileMatrix = (0,
|
|
41117
|
+
const fileMatrix = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
40859
41118
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
40860
41119
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
40861
|
-
const fileContracts = (0,
|
|
41120
|
+
const fileContracts = (0, import_node_fs43.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs43.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
40862
41121
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
40863
|
-
const sanctioned = (0,
|
|
41122
|
+
const sanctioned = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
40864
41123
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
40865
41124
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
40866
41125
|
if (!report.ok) process.exitCode = 1;
|
|
40867
41126
|
});
|
|
40868
41127
|
access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
40869
|
-
var
|
|
41128
|
+
var isWin3 = process.platform === "win32";
|
|
40870
41129
|
program2.command("doctor").description("safely heal active plugin wiring and report hygiene; repository cleanup requires explicit --apply (#5023), while version convergence belongs to `mmi-hub update`").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the canonical MMI Agentic Onboarding URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "advanced explicit lane: also apply guarded repository maintenance (gitignore, docs index, board mechanics, merged branches, aged scratch); the host owns workspace cleanup").option("--no-repo-writes", "compatibility spelling for the safe default: env/plugin repairs only, never mutate the repository").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run may heal safe machine-global plugin wiring but never mutates the repository. Run\n`mmi-cli doctor --apply` only when you explicitly want the guarded full repository cleanup lane.\nCLI and host version convergence belongs to `mmi-hub update`; doctor reports lag and last-run evidence.\n--banner/--fast/--self/--preflight are read-only lanes.\n").action(async (opts) => {
|
|
40871
41130
|
if (opts.guide) {
|
|
40872
41131
|
consoleIo.log(`MMI Agentic Onboarding: ${CANONICAL_ONBOARDING_URL}`);
|
|
@@ -40892,16 +41151,16 @@ function directoryBytes(path2) {
|
|
|
40892
41151
|
let total = 0;
|
|
40893
41152
|
let entries;
|
|
40894
41153
|
try {
|
|
40895
|
-
entries = (0,
|
|
41154
|
+
entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
|
|
40896
41155
|
} catch {
|
|
40897
41156
|
return 0;
|
|
40898
41157
|
}
|
|
40899
41158
|
for (const entry of entries) {
|
|
40900
|
-
const child2 = (0,
|
|
41159
|
+
const child2 = (0, import_node_path40.join)(path2, entry.name);
|
|
40901
41160
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
40902
41161
|
else {
|
|
40903
41162
|
try {
|
|
40904
|
-
total += (0,
|
|
41163
|
+
total += (0, import_node_fs43.statSync)(child2).size;
|
|
40905
41164
|
} catch {
|
|
40906
41165
|
}
|
|
40907
41166
|
}
|
|
@@ -40909,25 +41168,25 @@ function directoryBytes(path2) {
|
|
|
40909
41168
|
return total;
|
|
40910
41169
|
}
|
|
40911
41170
|
function listDirEntries(dir) {
|
|
40912
|
-
return (0,
|
|
41171
|
+
return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
40913
41172
|
}
|
|
40914
41173
|
function readInstalledPluginRefs(configRoot) {
|
|
40915
41174
|
const p = installedPluginsPathForConfig(configRoot);
|
|
40916
|
-
if (!(0,
|
|
41175
|
+
if (!(0, import_node_fs43.existsSync)(p)) return [];
|
|
40917
41176
|
try {
|
|
40918
|
-
return installedPluginPaths((0,
|
|
41177
|
+
return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
|
|
40919
41178
|
} catch {
|
|
40920
41179
|
return null;
|
|
40921
41180
|
}
|
|
40922
41181
|
}
|
|
40923
41182
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
40924
41183
|
return {
|
|
40925
|
-
exists: (p) => (0,
|
|
40926
|
-
listVersionDirs: (root) => (0,
|
|
41184
|
+
exists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
41185
|
+
listVersionDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
40927
41186
|
dirBytes,
|
|
40928
|
-
listStagingDirs: (root) => (0,
|
|
41187
|
+
listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
40929
41188
|
try {
|
|
40930
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
41189
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path40.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs43.statSync)(p).mtimeMs) };
|
|
40931
41190
|
} catch {
|
|
40932
41191
|
return { name: d.name, mtimeMs: Date.now() };
|
|
40933
41192
|
}
|
|
@@ -40941,10 +41200,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
40941
41200
|
return {
|
|
40942
41201
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
40943
41202
|
mtimeMs: (name) => {
|
|
40944
|
-
const p = (0,
|
|
40945
|
-
if (!(0,
|
|
41203
|
+
const p = (0, import_node_path40.join)(stagingRoot, name);
|
|
41204
|
+
if (!(0, import_node_fs43.existsSync)(p)) return null;
|
|
40946
41205
|
try {
|
|
40947
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
41206
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
|
|
40948
41207
|
} catch {
|
|
40949
41208
|
return null;
|
|
40950
41209
|
}
|
|
@@ -40970,7 +41229,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
40970
41229
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
40971
41230
|
);
|
|
40972
41231
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
40973
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
41232
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs43.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
40974
41233
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
40975
41234
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
40976
41235
|
else console.log(renderPluginCachePlan(plan, result));
|