@mutmutco/cli 4.3.15 → 4.3.17
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 +606 -266
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,7 +3416,7 @@ function useColor() {
|
|
|
3416
3416
|
var program = new Command();
|
|
3417
3417
|
|
|
3418
3418
|
// src/command-composition.ts
|
|
3419
|
-
var
|
|
3419
|
+
var import_node_fs50 = require("node:fs");
|
|
3420
3420
|
|
|
3421
3421
|
// src/clean-exit.ts
|
|
3422
3422
|
var UNDICI_GLOBAL_DISPATCHER_SYMBOL = Object.getOwnPropertySymbols(globalThis).find(
|
|
@@ -5226,8 +5226,8 @@ function unknownCommandDomainGuide(parentPath, token) {
|
|
|
5226
5226
|
}
|
|
5227
5227
|
|
|
5228
5228
|
// src/command-composition.ts
|
|
5229
|
-
var
|
|
5230
|
-
var
|
|
5229
|
+
var import_node_os22 = require("node:os");
|
|
5230
|
+
var import_node_path47 = require("node:path");
|
|
5231
5231
|
|
|
5232
5232
|
// src/board-read.ts
|
|
5233
5233
|
var import_node_fs13 = require("node:fs");
|
|
@@ -12917,6 +12917,18 @@ function rulesetRequiredContexts(ruleset) {
|
|
|
12917
12917
|
}
|
|
12918
12918
|
return contexts;
|
|
12919
12919
|
}
|
|
12920
|
+
function rulesetStrictPolicy(ruleset) {
|
|
12921
|
+
const rules = (ruleset.rules ?? []).filter((rule) => rule.type === "required_status_checks");
|
|
12922
|
+
return rules.length > 0 && rules.every((rule) => rule.parameters?.strict_required_status_checks_policy === true);
|
|
12923
|
+
}
|
|
12924
|
+
function patchRulesetStrictPolicy(body) {
|
|
12925
|
+
const rules = (body.rules ?? []).map((rule) => {
|
|
12926
|
+
const r = rule;
|
|
12927
|
+
if (r.type !== "required_status_checks") return rule;
|
|
12928
|
+
return { ...r, parameters: { ...r.parameters, strict_required_status_checks_policy: true } };
|
|
12929
|
+
});
|
|
12930
|
+
return { ...body, rules };
|
|
12931
|
+
}
|
|
12920
12932
|
function rulesetBranchIncludes(ruleset) {
|
|
12921
12933
|
const raw = ruleset.conditions?.ref_name?.include;
|
|
12922
12934
|
return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref.length > 0))].sort((a, b) => a.localeCompare(b)) : [];
|
|
@@ -12936,7 +12948,8 @@ function patchRulesetRequiredContexts(body, contexts) {
|
|
|
12936
12948
|
...r,
|
|
12937
12949
|
parameters: {
|
|
12938
12950
|
...r.parameters,
|
|
12939
|
-
strict_required_status_checks_policy:
|
|
12951
|
+
strict_required_status_checks_policy: true,
|
|
12952
|
+
// #6263: org policy — see patchRulesetStrictPolicy
|
|
12940
12953
|
required_status_checks: sorted.map((context) => ({ context }))
|
|
12941
12954
|
}
|
|
12942
12955
|
};
|
|
@@ -13023,6 +13036,7 @@ async function gateIsProvenGreen(repo, client, baseBranch, gateFiles = DEFAULT_G
|
|
|
13023
13036
|
}
|
|
13024
13037
|
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active") {
|
|
13025
13038
|
const want = new Set(rulesetRequiredContexts({ rules: rulesetBody.rules }));
|
|
13039
|
+
const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
|
|
13026
13040
|
const wantBranches = rulesetBranchIncludes(rulesetBody);
|
|
13027
13041
|
const list = await client.rest("GET", `repos/${repo}/rulesets`, { timeoutMs: 2e4 });
|
|
13028
13042
|
const existing = findProductRuleset(list ?? []);
|
|
@@ -13034,8 +13048,9 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
|
|
|
13034
13048
|
const have = new Set(rulesetRequiredContexts(detail));
|
|
13035
13049
|
const haveBranches = rulesetBranchIncludes(detail);
|
|
13036
13050
|
const branchesMatch = haveBranches.length === wantBranches.length && wantBranches.every((ref, index) => ref === haveBranches[index]);
|
|
13037
|
-
|
|
13038
|
-
|
|
13051
|
+
const strictMatch = rulesetStrictPolicy(detail) === wantStrict;
|
|
13052
|
+
if (detail.enforcement === effective && branchesMatch && strictMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
|
|
13053
|
+
return { action: "skipped", enforcement: effective, detail: `${effective} ruleset already matches required contexts, branches and up-to-date policy${kept}` };
|
|
13039
13054
|
}
|
|
13040
13055
|
await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body: { ...rulesetBody, enforcement: effective }, timeoutMs: 2e4 });
|
|
13041
13056
|
return { action: "updated", enforcement: effective, detail: `ruleset ${existing.id}${kept}` };
|
|
@@ -13706,7 +13721,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
13706
13721
|
}
|
|
13707
13722
|
|
|
13708
13723
|
// src/gate-budget.ts
|
|
13709
|
-
var BLESSED_RUN_WITH_BUDGET_SHA = "
|
|
13724
|
+
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
13710
13725
|
var BLESSED_RUNNER_GATE_SHA = "a6ee756d94fd95884451381e94408f0348eb11db";
|
|
13711
13726
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
13712
13727
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -14044,6 +14059,8 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
14044
14059
|
const runtimeVars = {
|
|
14045
14060
|
GATE_RUNTIME: runtime,
|
|
14046
14061
|
GATE_CMD: rt.cmd,
|
|
14062
|
+
// #6254: affected-only PR command; empty means "same as GATE_CMD" (withDerivedRepoVars fills it).
|
|
14063
|
+
GATE_AFFECTED_CMD: "",
|
|
14047
14064
|
GATE_INSTALL_CMD: rt.install,
|
|
14048
14065
|
GATE_WORKDIR: ".",
|
|
14049
14066
|
GATE_CACHE_DEP_PATH: "package-lock.json",
|
|
@@ -14091,6 +14108,7 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
|
|
|
14091
14108
|
for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
|
|
14092
14109
|
out[key] ??= value;
|
|
14093
14110
|
}
|
|
14111
|
+
if (!out.GATE_AFFECTED_CMD.trim()) out.GATE_AFFECTED_CMD = out.GATE_CMD;
|
|
14094
14112
|
return out;
|
|
14095
14113
|
}
|
|
14096
14114
|
function gateConfigToVars(gate) {
|
|
@@ -14098,6 +14116,7 @@ function gateConfigToVars(gate) {
|
|
|
14098
14116
|
if (!gate || typeof gate !== "object") return out;
|
|
14099
14117
|
if (gate.runtime === "node" || gate.runtime === "python") out.GATE_RUNTIME = gate.runtime;
|
|
14100
14118
|
if (typeof gate.cmd === "string" && gate.cmd.trim()) out.GATE_CMD = gate.cmd;
|
|
14119
|
+
if (typeof gate.affectedCmd === "string" && gate.affectedCmd.trim()) out.GATE_AFFECTED_CMD = gate.affectedCmd;
|
|
14101
14120
|
if (typeof gate.workdir === "string" && gate.workdir.trim()) out.GATE_WORKDIR = gate.workdir;
|
|
14102
14121
|
if (typeof gate.cacheDepPath === "string" && gate.cacheDepPath.trim()) out.GATE_CACHE_DEP_PATH = gate.cacheDepPath;
|
|
14103
14122
|
if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
|
|
@@ -15483,10 +15502,10 @@ var rollout_plan_default = {
|
|
|
15483
15502
|
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)."
|
|
15484
15503
|
},
|
|
15485
15504
|
baseline: {
|
|
15486
|
-
version: "4.3.
|
|
15487
|
-
tag: "v4.3.
|
|
15488
|
-
commit: "
|
|
15489
|
-
npm: "@mutmutco/cli@4.3.
|
|
15505
|
+
version: "4.3.17",
|
|
15506
|
+
tag: "v4.3.17",
|
|
15507
|
+
commit: "3b07c941910b",
|
|
15508
|
+
npm: "@mutmutco/cli@4.3.17"
|
|
15490
15509
|
},
|
|
15491
15510
|
exitCriterion: "fleet-n-of-n",
|
|
15492
15511
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15503,14 +15522,14 @@ var rollout_plan_default = {
|
|
|
15503
15522
|
repo: "mutmutco/mmi-hub",
|
|
15504
15523
|
role: "canary",
|
|
15505
15524
|
schedule: "train",
|
|
15506
|
-
v3Target: "v4.3.
|
|
15525
|
+
v3Target: "v4.3.17"
|
|
15507
15526
|
}
|
|
15508
15527
|
],
|
|
15509
15528
|
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.",
|
|
15510
15529
|
rollback: {
|
|
15511
15530
|
independent: true,
|
|
15512
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15513
|
-
v3Target: "v4.3.
|
|
15531
|
+
mechanism: "npm dist-tag latest -> 4.3.17 and redeploy the Hub Lambda from tag v4.3.17 (3b07c941910b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15532
|
+
v3Target: "v4.3.17 (@mutmutco/cli@4.3.17, tag commit 3b07c941910b \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15514
15533
|
}
|
|
15515
15534
|
},
|
|
15516
15535
|
{
|
|
@@ -23507,12 +23526,13 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
|
|
|
23507
23526
|
const registryContexts = registryRequiredContexts(meta);
|
|
23508
23527
|
const contexts = sortedUnique(registryContexts ?? committedContexts);
|
|
23509
23528
|
const explicitBranches = Array.isArray(meta?.requiredCheckBranches) && meta.requiredCheckBranches.length > 0 ? resolveRequiredCheckBranches(meta, repo) : null;
|
|
23510
|
-
|
|
23529
|
+
const committedStrict = rulesetStrictPolicy(committedPayload);
|
|
23530
|
+
let apiPayload = patchRulesetStrictPolicy(registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts));
|
|
23511
23531
|
if (explicitBranches) apiPayload = patchRulesetBranchIncludes(apiPayload, explicitBranches);
|
|
23512
23532
|
const branchIncludes = rulesetBranchIncludes(apiPayload);
|
|
23513
23533
|
const authoritativeFilePayload = {
|
|
23514
23534
|
...filePayload,
|
|
23515
|
-
...registryContexts == null ? {} : { rules: apiPayload.rules },
|
|
23535
|
+
...registryContexts == null && committedStrict ? {} : { rules: apiPayload.rules },
|
|
23516
23536
|
...explicitBranches == null ? {} : { conditions: apiPayload.conditions }
|
|
23517
23537
|
};
|
|
23518
23538
|
return {
|
|
@@ -23522,6 +23542,7 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
|
|
|
23522
23542
|
contexts,
|
|
23523
23543
|
branchIncludes,
|
|
23524
23544
|
committedBranchIncludes,
|
|
23545
|
+
committedStrict,
|
|
23525
23546
|
branchSource: explicitBranches ? "registry META requiredCheckBranches" : "committed ruleset reference",
|
|
23526
23547
|
source: registryContexts == null ? "committed ruleset reference" : "registry META requiredChecks",
|
|
23527
23548
|
coversReleaseBranches: rulesetCoversReleaseBranches(apiPayload, resolveReleaseTrack(meta, void 0, repo))
|
|
@@ -23533,8 +23554,9 @@ function sameContexts(left, right) {
|
|
|
23533
23554
|
function resolveProductRulesetReconcilePlan(input) {
|
|
23534
23555
|
const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
|
|
23535
23556
|
const liveNeedsBranchConvergence = input.liveBranchIncludes !== void 0 && input.authorityBranchIncludes !== void 0 && !sameContexts(input.liveBranchIncludes, input.authorityBranchIncludes);
|
|
23557
|
+
const liveNeedsStrictConvergence = input.liveStrict === false;
|
|
23536
23558
|
const liveIsActive = input.liveEnforcement === "active";
|
|
23537
|
-
if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence) {
|
|
23559
|
+
if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence && !liveNeedsStrictConvergence) {
|
|
23538
23560
|
return { shouldActivate: false, targetEnforcement: "active" };
|
|
23539
23561
|
}
|
|
23540
23562
|
if (input.unsafeContexts.length > 0) {
|
|
@@ -23722,6 +23744,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
23722
23744
|
const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
|
|
23723
23745
|
const liveContexts = productRuleset == null ? [] : sortedUnique(rulesetRequiredContexts(productRuleset));
|
|
23724
23746
|
const liveBranchIncludes = productRuleset == null ? [] : rulesetBranchIncludes(productRuleset);
|
|
23747
|
+
const liveStrict = productRuleset != null && rulesetStrictPolicy(productRuleset);
|
|
23725
23748
|
const hasRequiredChecks = productRuleset?.enforcement === "active" && liveContexts.length > 0;
|
|
23726
23749
|
checks.push({
|
|
23727
23750
|
ok: hasRequiredChecks,
|
|
@@ -23742,11 +23765,12 @@ async function auditRepoCi(repo, deps) {
|
|
|
23742
23765
|
const liveContextsAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
|
|
23743
23766
|
const fileBranchesAligned = sameContexts(authoritativeRuleset.committedBranchIncludes, authoritativeRuleset.branchIncludes);
|
|
23744
23767
|
const liveBranchesAligned = sameContexts(liveBranchIncludes, authoritativeRuleset.branchIncludes);
|
|
23745
|
-
const
|
|
23768
|
+
const strictAligned = authoritativeRuleset.committedStrict && liveStrict;
|
|
23769
|
+
const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned && strictAligned;
|
|
23746
23770
|
checks.push({
|
|
23747
23771
|
ok: aligned,
|
|
23748
23772
|
label: RULESET_REFERENCE_MATCH_LABEL,
|
|
23749
|
-
detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}]
|
|
23773
|
+
detail: aligned ? `contexts [${authoritativeRuleset.contexts.join(", ")}], branches [${authoritativeRuleset.branchIncludes.join(", ")}] and the up-to-date policy match declared authority` : `${authoritativeRuleset.source} requires contexts [${authoritativeRuleset.contexts.join(", ")}]; ${authoritativeRuleset.branchSource} requires branches [${authoritativeRuleset.branchIncludes.join(", ")}]; ${PRODUCT_RULESET_REF} declares contexts [${authoritativeRuleset.committedContexts.join(", ")}] / branches [${authoritativeRuleset.committedBranchIncludes.join(", ")}] and live ${PRODUCT_RULESET_NAME} has contexts [${liveContexts.join(", ")}] / branches [${liveBranchIncludes.join(", ")}]` + (strictAligned ? "" : `; strict_required_status_checks_policy must be true (require branches to be up to date before merging, #6263) \u2014 ${PRODUCT_RULESET_REF} declares ${authoritativeRuleset.committedStrict}, live ${PRODUCT_RULESET_NAME} has ${liveStrict}`),
|
|
23750
23774
|
remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
|
|
23751
23775
|
});
|
|
23752
23776
|
}
|
|
@@ -24280,7 +24304,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24280
24304
|
result.errors.push(`${authority.source} declares no required contexts`);
|
|
24281
24305
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
24282
24306
|
}
|
|
24283
|
-
const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes);
|
|
24307
|
+
const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
|
|
24284
24308
|
if (sourceNeedsConvergence) {
|
|
24285
24309
|
try {
|
|
24286
24310
|
const delivery = await deliverSeedFile(
|
|
@@ -24335,6 +24359,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24335
24359
|
authorityContexts: authority.contexts,
|
|
24336
24360
|
liveBranchIncludes,
|
|
24337
24361
|
authorityBranchIncludes: authority.branchIncludes,
|
|
24362
|
+
liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
|
|
24338
24363
|
gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
|
|
24339
24364
|
unsafeContexts: unsafe
|
|
24340
24365
|
});
|
|
@@ -24343,7 +24368,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24343
24368
|
result.skipped.push(plan.holdReason);
|
|
24344
24369
|
return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
|
|
24345
24370
|
}
|
|
24346
|
-
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts and
|
|
24371
|
+
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts, branches and up-to-date policy already match declared authority`);
|
|
24347
24372
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
24348
24373
|
}
|
|
24349
24374
|
try {
|
|
@@ -27142,6 +27167,11 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
27142
27167
|
label: "product required-check ruleset enforcement active",
|
|
27143
27168
|
detail: productRuleset?.enforcement !== "active" ? `${PRODUCT_RULESET_NAME} is ${productRuleset?.enforcement ?? "missing"} \u2014 run mmi-cli devops ci reconcile --apply --repo ${repo} once the gate is green` : void 0
|
|
27144
27169
|
});
|
|
27170
|
+
checks.push({
|
|
27171
|
+
ok: productRuleset != null && rulesetStrictPolicy(productRuleset),
|
|
27172
|
+
label: "product required-check ruleset requires an up-to-date branch",
|
|
27173
|
+
detail: productRuleset != null && rulesetStrictPolicy(productRuleset) ? void 0 : `${PRODUCT_RULESET_NAME} ${productRuleset == null ? "is missing" : "has strict_required_status_checks_policy=false"} \u2014 run mmi-cli devops ci reconcile --apply --repo ${repo} (#6263)`
|
|
27174
|
+
});
|
|
27145
27175
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
27146
27176
|
const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
|
|
27147
27177
|
checks.push({
|
|
@@ -28778,7 +28808,7 @@ function parseGateVar(raw) {
|
|
|
28778
28808
|
throw new Error('org project set: gate must be JSON, e.g. {"runtime":"python","cmd":"pytest","workdir":"app"}');
|
|
28779
28809
|
}
|
|
28780
28810
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
28781
|
-
throw new Error("org project set: gate must be a {runtime,cmd,workdir,cacheDepPath,pyVersion} object");
|
|
28811
|
+
throw new Error("org project set: gate must be a {runtime,cmd,affectedCmd,workdir,cacheDepPath,pyVersion} object");
|
|
28782
28812
|
}
|
|
28783
28813
|
const map = parsed;
|
|
28784
28814
|
const out = {};
|
|
@@ -28786,11 +28816,11 @@ function parseGateVar(raw) {
|
|
|
28786
28816
|
if (key === "runtime") {
|
|
28787
28817
|
if (value !== "node" && value !== "python") throw new Error('org project set: gate.runtime must be "node" or "python"');
|
|
28788
28818
|
out.runtime = value;
|
|
28789
|
-
} else if (key === "cmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
|
|
28819
|
+
} else if (key === "cmd" || key === "affectedCmd" || key === "workdir" || key === "cacheDepPath" || key === "pyVersion") {
|
|
28790
28820
|
if (typeof value !== "string" || !value.trim()) throw new Error(`org project set: gate.${key} must be a non-empty string`);
|
|
28791
28821
|
out[key] = value.trim();
|
|
28792
28822
|
} else {
|
|
28793
|
-
throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/workdir/cacheDepPath/pyVersion`);
|
|
28823
|
+
throw new Error(`org project set: gate key "${key}" \u2014 expected only runtime/cmd/affectedCmd/workdir/cacheDepPath/pyVersion`);
|
|
28794
28824
|
}
|
|
28795
28825
|
}
|
|
28796
28826
|
return out;
|
|
@@ -28891,7 +28921,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
28891
28921
|
requiredChecks: 'JSON array, e.g. ["gate"] or [] for no-ci',
|
|
28892
28922
|
requiredCheckBranches: "non-empty JSON branch-name array; exact product-ruleset scope (#5210)",
|
|
28893
28923
|
ciExemptReason: "non-empty string \u2014 declares this deployable repo has no CI surface (#4265)",
|
|
28894
|
-
gate: "JSON {runtime,cmd,workdir,cacheDepPath,pyVersion}"
|
|
28924
|
+
gate: "JSON {runtime,cmd,affectedCmd,workdir,cacheDepPath,pyVersion}"
|
|
28895
28925
|
};
|
|
28896
28926
|
function settableVarHelp() {
|
|
28897
28927
|
const keys = SETTABLE_VAR_KEYS.map((k) => {
|
|
@@ -35908,8 +35938,8 @@ function renderVerifySecrets(body) {
|
|
|
35908
35938
|
}
|
|
35909
35939
|
|
|
35910
35940
|
// src/command-register-collaboration.ts
|
|
35911
|
-
var
|
|
35912
|
-
var
|
|
35941
|
+
var import_node_child_process16 = require("node:child_process");
|
|
35942
|
+
var import_node_fs43 = require("node:fs");
|
|
35913
35943
|
var import_promises7 = require("node:fs/promises");
|
|
35914
35944
|
|
|
35915
35945
|
// src/session-runtime.ts
|
|
@@ -35929,7 +35959,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
35929
35959
|
}
|
|
35930
35960
|
|
|
35931
35961
|
// src/command-register-collaboration.ts
|
|
35932
|
-
var
|
|
35962
|
+
var import_node_path40 = require("node:path");
|
|
35933
35963
|
|
|
35934
35964
|
// src/attach-to-project.ts
|
|
35935
35965
|
function boardAttachRateLimitedReceipt(resetEpochSeconds) {
|
|
@@ -36006,6 +36036,47 @@ function applyChecklistCheck(body, query, checked) {
|
|
|
36006
36036
|
}
|
|
36007
36037
|
|
|
36008
36038
|
// src/pr-land.ts
|
|
36039
|
+
function prHeadBehindBase(input) {
|
|
36040
|
+
if ((input.mergeStateStatus ?? "").toUpperCase() === "BEHIND") return true;
|
|
36041
|
+
return /out[- ]of[- ]date|not up to date|up to date with the base|update (?:the|your) branch/i.test(input.message ?? "");
|
|
36042
|
+
}
|
|
36043
|
+
function prHeadUpdateRemedy(head, base) {
|
|
36044
|
+
return `git fetch origin ${base} && git merge origin/${base} (on ${head}), resolve the conflicts, push, then rerun \u2014 docs/Guides/train-troubleshooting.md#head-behind-base`;
|
|
36045
|
+
}
|
|
36046
|
+
var PR_HEAD_UPDATE_API_READ_RETRIES = 5;
|
|
36047
|
+
var PR_HEAD_UPDATE_API_READ_DELAY_MS = 2e3;
|
|
36048
|
+
async function updatePrHeadFromBase(input) {
|
|
36049
|
+
const remedy = prHeadUpdateRemedy(input.head, input.base);
|
|
36050
|
+
if (input.localCheckedOut) {
|
|
36051
|
+
const from2 = (await input.git(["rev-parse", "HEAD"])).trim();
|
|
36052
|
+
await input.git(["fetch", "origin", input.base]);
|
|
36053
|
+
try {
|
|
36054
|
+
await input.git(["merge", "--no-ff", "--no-edit", `origin/${input.base}`]);
|
|
36055
|
+
} catch (e) {
|
|
36056
|
+
await input.git(["merge", "--abort"]).catch(() => {
|
|
36057
|
+
});
|
|
36058
|
+
throw new Error(`updating ${input.head} from ${input.base} conflicted (${e.message.trim()}) \u2014 ${remedy}`);
|
|
36059
|
+
}
|
|
36060
|
+
await input.git(["push", "origin", input.head]);
|
|
36061
|
+
const to = (await input.git(["rev-parse", "HEAD"])).trim();
|
|
36062
|
+
return { from: from2, to, base: input.base, via: "local-merge" };
|
|
36063
|
+
}
|
|
36064
|
+
const pullPath = `repos/${input.repo}/pulls/${input.prNumber}`;
|
|
36065
|
+
const readHead2 = async () => (await input.rest("GET", pullPath)).head?.sha ?? "";
|
|
36066
|
+
const from = await readHead2();
|
|
36067
|
+
try {
|
|
36068
|
+
await input.rest("PUT", `${pullPath}/update-branch`, from ? { expected_head_sha: from } : void 0);
|
|
36069
|
+
} catch (e) {
|
|
36070
|
+
throw new Error(`GitHub could not update ${input.head} from ${input.base} (${e.message.trim()}) \u2014 ${remedy}`);
|
|
36071
|
+
}
|
|
36072
|
+
const sleep2 = input.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
36073
|
+
for (let attempt = 0; attempt < PR_HEAD_UPDATE_API_READ_RETRIES; attempt++) {
|
|
36074
|
+
const to = await readHead2();
|
|
36075
|
+
if (to && to !== from) return { from, to, base: input.base, via: "update-branch-api" };
|
|
36076
|
+
await sleep2(PR_HEAD_UPDATE_API_READ_DELAY_MS);
|
|
36077
|
+
}
|
|
36078
|
+
throw new Error(`GitHub accepted the update of ${input.head} from ${input.base} but the head did not move \u2014 check the PR, then rerun`);
|
|
36079
|
+
}
|
|
36009
36080
|
var PR_LAND_POLL_MS = 3e4;
|
|
36010
36081
|
var PR_LAND_ENQUEUE_TIMEOUT_MS = 10 * 6e4;
|
|
36011
36082
|
var PR_LAND_STATE_READ_RETRIES = 3;
|
|
@@ -36076,21 +36147,30 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
36076
36147
|
}
|
|
36077
36148
|
const ciPolicy = await deps.resolveCiPolicy(repo);
|
|
36078
36149
|
base.ciPolicy = ciPolicy;
|
|
36079
|
-
const
|
|
36080
|
-
|
|
36081
|
-
|
|
36082
|
-
|
|
36083
|
-
|
|
36084
|
-
|
|
36085
|
-
}
|
|
36086
|
-
|
|
36087
|
-
|
|
36088
|
-
|
|
36089
|
-
|
|
36090
|
-
|
|
36091
|
-
|
|
36150
|
+
const checksWaitError = (checksWait) => {
|
|
36151
|
+
if (checksWait.status === "conflicting") {
|
|
36152
|
+
return `checks-wait conflicting: ${checksWait.reason ?? "PR is conflicting with the base branch"}`;
|
|
36153
|
+
}
|
|
36154
|
+
if (checksWait.status === "failure" || checksWait.status === "timeout" || checksWait.status === "rate-limited") {
|
|
36155
|
+
return `checks-wait ${checksWait.status}${checksWait.detail ? `: ${checksWait.detail}` : ""}`;
|
|
36156
|
+
}
|
|
36157
|
+
return void 0;
|
|
36158
|
+
};
|
|
36159
|
+
base.checksWait = await deps.waitForChecks(prNumber, repo);
|
|
36160
|
+
const waitError = checksWaitError(base.checksWait);
|
|
36161
|
+
if (waitError) return { ...base, error: waitError };
|
|
36162
|
+
let prState = await deps.readPrState(prNumber, repo);
|
|
36163
|
+
if (prState.mergeStateStatus === "BEHIND" && deps.updateBranch) {
|
|
36164
|
+
try {
|
|
36165
|
+
base.branchUpdated = await deps.updateBranch(prNumber, repo);
|
|
36166
|
+
} catch (e) {
|
|
36167
|
+
return { ...base, error: `pr land #${prNumber}: ${e.message}` };
|
|
36168
|
+
}
|
|
36169
|
+
base.checksWait = await deps.waitForChecks(prNumber, repo);
|
|
36170
|
+
const reWaitError = checksWaitError(base.checksWait);
|
|
36171
|
+
if (reWaitError) return { ...base, error: reWaitError };
|
|
36172
|
+
prState = await deps.readPrState(prNumber, repo);
|
|
36092
36173
|
}
|
|
36093
|
-
const prState = await deps.readPrState(prNumber, repo);
|
|
36094
36174
|
if (prState.mergeStateStatus === "DIRTY" || prState.mergeable === "CONFLICTING") {
|
|
36095
36175
|
return {
|
|
36096
36176
|
...base,
|
|
@@ -37581,15 +37661,193 @@ function postMergeReconWarnings(input) {
|
|
|
37581
37661
|
return lines2;
|
|
37582
37662
|
}
|
|
37583
37663
|
|
|
37584
|
-
// src/
|
|
37664
|
+
// src/review-verdict.ts
|
|
37585
37665
|
var import_node_child_process14 = require("node:child_process");
|
|
37666
|
+
var import_node_fs39 = require("node:fs");
|
|
37667
|
+
var import_node_os19 = require("node:os");
|
|
37668
|
+
var import_node_path36 = require("node:path");
|
|
37669
|
+
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
37670
|
+
var REVIEW_WAIVER_MARKER = "<!-- zeroci-review-waived v1 -->";
|
|
37671
|
+
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
37672
|
+
function isReviewVerdict(value) {
|
|
37673
|
+
return typeof value === "string" && REVIEW_VERDICTS.includes(value);
|
|
37674
|
+
}
|
|
37675
|
+
function renderReviewVerdictComment(input) {
|
|
37676
|
+
const payload = {
|
|
37677
|
+
v: 1,
|
|
37678
|
+
patch: input.patch,
|
|
37679
|
+
head: input.head,
|
|
37680
|
+
verdict: input.verdict,
|
|
37681
|
+
scope: input.scope,
|
|
37682
|
+
risk: input.risk,
|
|
37683
|
+
unverified: input.unverified,
|
|
37684
|
+
reviewer: input.reviewer
|
|
37685
|
+
};
|
|
37686
|
+
const findings = input.findings?.trim();
|
|
37687
|
+
return `${REVIEW_VERDICT_MARKER}
|
|
37688
|
+
\`\`\`json
|
|
37689
|
+
${JSON.stringify(payload, null, 2)}
|
|
37690
|
+
\`\`\`
|
|
37691
|
+
${findings ? `
|
|
37692
|
+
${findings}
|
|
37693
|
+
` : ""}`;
|
|
37694
|
+
}
|
|
37695
|
+
function isReviewVerdictComment(body) {
|
|
37696
|
+
return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
|
|
37697
|
+
}
|
|
37698
|
+
var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
|
|
37699
|
+
function parseReviewVerdictComment(body) {
|
|
37700
|
+
if (!isReviewVerdictComment(body)) return void 0;
|
|
37701
|
+
const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
|
|
37702
|
+
const fence = FENCE_RE.exec(rest);
|
|
37703
|
+
if (!fence) return void 0;
|
|
37704
|
+
let parsed;
|
|
37705
|
+
try {
|
|
37706
|
+
parsed = JSON.parse(fence[1]);
|
|
37707
|
+
} catch {
|
|
37708
|
+
return void 0;
|
|
37709
|
+
}
|
|
37710
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
37711
|
+
const p = parsed;
|
|
37712
|
+
if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
|
|
37713
|
+
if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
|
|
37714
|
+
if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
|
|
37715
|
+
const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
|
|
37716
|
+
return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
|
|
37717
|
+
}
|
|
37718
|
+
function evaluateReviewVerdict(comments, currentPatchId) {
|
|
37719
|
+
let latest;
|
|
37720
|
+
for (const c of comments) {
|
|
37721
|
+
if (!isReviewVerdictComment(c.body)) continue;
|
|
37722
|
+
if (!latest || c.createdAt >= latest.createdAt) latest = c;
|
|
37723
|
+
}
|
|
37724
|
+
if (!latest) return { ok: false, reason: "none" };
|
|
37725
|
+
const payload = parseReviewVerdictComment(latest.body);
|
|
37726
|
+
if (!payload) return { ok: false, reason: "malformed" };
|
|
37727
|
+
if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
|
|
37728
|
+
if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
|
|
37729
|
+
return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
|
|
37730
|
+
}
|
|
37731
|
+
function reviewVerdictRemedyCommand(number, repo) {
|
|
37732
|
+
return `mmi-cli devops pr review-verdict ${number} --repo ${repo} --verdict PROCEED --scope "<one line>" --risk "<one line>" --reviewer <seat-or-model-id>`;
|
|
37733
|
+
}
|
|
37734
|
+
function reviewVerdictRefusalMessage(context, number, repo, evaluation, currentPatchId) {
|
|
37735
|
+
const why = {
|
|
37736
|
+
none: "no review verdict comment on the PR",
|
|
37737
|
+
stale: `the latest verdict is bound to patch ${evaluation.patch} but the PR's current diff is ${currentPatchId} \u2014 the content changed since the review`,
|
|
37738
|
+
"not-proceed": `the latest verdict is ${evaluation.verdict}, not PROCEED`,
|
|
37739
|
+
malformed: "the latest verdict comment is malformed (marker present, JSON block unreadable or wrong shape)",
|
|
37740
|
+
proceed: "proceed"
|
|
37741
|
+
}[evaluation.reason];
|
|
37742
|
+
return `${context}: REVIEW VERDICT REQUIRED (#6255) \u2014 ${why}. Review PR #${number} and post the verdict, then rerun:
|
|
37743
|
+
${reviewVerdictRemedyCommand(number, repo)}
|
|
37744
|
+
To land without a review, pass --without-review "<reason>" \u2014 it posts a waiver comment on the PR.`;
|
|
37745
|
+
}
|
|
37746
|
+
function renderReviewWaiverComment(reason, actor) {
|
|
37747
|
+
return `${REVIEW_WAIVER_MARKER}
|
|
37748
|
+
Review gate waived by @${actor}: ${reason.trim()}
|
|
37749
|
+
`;
|
|
37750
|
+
}
|
|
37751
|
+
function computePrPatchId(number, repo) {
|
|
37752
|
+
return new Promise((resolve7, reject) => {
|
|
37753
|
+
const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
37754
|
+
const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
37755
|
+
let out = "";
|
|
37756
|
+
let ghErr = "";
|
|
37757
|
+
let gitErr = "";
|
|
37758
|
+
const timer = setTimeout(() => {
|
|
37759
|
+
gh.kill();
|
|
37760
|
+
git3.kill();
|
|
37761
|
+
reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
|
|
37762
|
+
}, GC_GH_TIMEOUT_MS4);
|
|
37763
|
+
gh.stdout.pipe(git3.stdin);
|
|
37764
|
+
gh.stderr.on("data", (d) => {
|
|
37765
|
+
ghErr += d.toString();
|
|
37766
|
+
});
|
|
37767
|
+
git3.stderr.on("data", (d) => {
|
|
37768
|
+
gitErr += d.toString();
|
|
37769
|
+
});
|
|
37770
|
+
git3.stdout.on("data", (d) => {
|
|
37771
|
+
out += d.toString();
|
|
37772
|
+
});
|
|
37773
|
+
gh.on("error", (e) => {
|
|
37774
|
+
clearTimeout(timer);
|
|
37775
|
+
reject(e);
|
|
37776
|
+
});
|
|
37777
|
+
git3.on("error", (e) => {
|
|
37778
|
+
clearTimeout(timer);
|
|
37779
|
+
reject(e);
|
|
37780
|
+
});
|
|
37781
|
+
let ghCode;
|
|
37782
|
+
gh.on("close", (code) => {
|
|
37783
|
+
ghCode = code;
|
|
37784
|
+
});
|
|
37785
|
+
git3.on("close", (code) => {
|
|
37786
|
+
clearTimeout(timer);
|
|
37787
|
+
if (ghCode) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
|
|
37788
|
+
if (code) return reject(new Error(`git patch-id --stable exited ${code}: ${gitErr.trim()}`));
|
|
37789
|
+
const id = out.trim().split(/\s+/)[0];
|
|
37790
|
+
if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
|
|
37791
|
+
resolve7(id);
|
|
37792
|
+
});
|
|
37793
|
+
});
|
|
37794
|
+
}
|
|
37795
|
+
async function readPrHeadSha(number, repo) {
|
|
37796
|
+
const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
37797
|
+
const sha = stdout.trim();
|
|
37798
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
|
|
37799
|
+
return sha;
|
|
37800
|
+
}
|
|
37801
|
+
async function readPrIssueComments(number, repo) {
|
|
37802
|
+
const { stdout } = await execFileP("gh", ["api", "--paginate", `repos/${repo}/issues/${number}/comments?per_page=100`, "--jq", ".[] | {body, createdAt: .created_at}"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
37803
|
+
return parseNdjsonLines(stdout);
|
|
37804
|
+
}
|
|
37805
|
+
async function postPrCommentFromFile(number, repo, body) {
|
|
37806
|
+
const dir = (0, import_node_fs39.mkdtempSync)((0, import_node_path36.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
|
|
37807
|
+
const path2 = (0, import_node_path36.join)(dir, "body.md");
|
|
37808
|
+
try {
|
|
37809
|
+
(0, import_node_fs39.writeFileSync)(path2, body, "utf8");
|
|
37810
|
+
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
37811
|
+
return stdout.trim();
|
|
37812
|
+
} finally {
|
|
37813
|
+
try {
|
|
37814
|
+
(0, import_node_fs39.rmSync)(dir, { recursive: true, force: true });
|
|
37815
|
+
} catch {
|
|
37816
|
+
}
|
|
37817
|
+
}
|
|
37818
|
+
}
|
|
37819
|
+
async function requireReviewVerdict(context, number, repo, opts) {
|
|
37820
|
+
if (opts.withoutReview !== void 0) {
|
|
37821
|
+
const reason = opts.withoutReview.trim();
|
|
37822
|
+
if (!reason) throw new Error(`${context}: --without-review needs a non-empty reason`);
|
|
37823
|
+
const actor = await execFileP("gh", ["api", "user", "--jq", ".login"], { timeout: GC_GH_TIMEOUT_MS4 }).then((r) => r.stdout.trim()).catch(() => "") || "unknown";
|
|
37824
|
+
await postPrCommentFromFile(number, repo, renderReviewWaiverComment(reason, actor));
|
|
37825
|
+
console.warn(`${context}: review gate WAIVED by @${actor} \u2014 ${reason} (waiver comment posted on PR #${number})`);
|
|
37826
|
+
return void 0;
|
|
37827
|
+
}
|
|
37828
|
+
let comments;
|
|
37829
|
+
let currentPatchId;
|
|
37830
|
+
try {
|
|
37831
|
+
[comments, currentPatchId] = await Promise.all([readPrIssueComments(number, repo), computePrPatchId(number, repo)]);
|
|
37832
|
+
} catch (e) {
|
|
37833
|
+
const detail = e.message || String(e);
|
|
37834
|
+
const limited = isGitHubRateLimitError(e) ? " (gh rate-limited \u2014 retry after the pool resets)" : "";
|
|
37835
|
+
throw new Error(`${context}: cannot read PR #${number}'s review verdict${limited} \u2014 ${detail}. Refusing to merge WITHOUT the review gate (#6255).`);
|
|
37836
|
+
}
|
|
37837
|
+
const evaluation = evaluateReviewVerdict(comments, currentPatchId);
|
|
37838
|
+
if (evaluation.ok) return void 0;
|
|
37839
|
+
return reviewVerdictRefusalMessage(context, number, repo, evaluation, currentPatchId);
|
|
37840
|
+
}
|
|
37841
|
+
|
|
37842
|
+
// src/pr-create-docs-check.ts
|
|
37843
|
+
var import_node_child_process15 = require("node:child_process");
|
|
37586
37844
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
37587
37845
|
function catFileBatch(root, ref, paths) {
|
|
37588
37846
|
if (paths.length === 0) return Promise.resolve([]);
|
|
37589
37847
|
return new Promise((resolve7) => {
|
|
37590
37848
|
const chunks = [];
|
|
37591
37849
|
let settled = false;
|
|
37592
|
-
const child2 = (0,
|
|
37850
|
+
const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
37593
37851
|
const finish = () => {
|
|
37594
37852
|
if (settled) return;
|
|
37595
37853
|
settled = true;
|
|
@@ -37759,26 +38017,26 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
37759
38017
|
}
|
|
37760
38018
|
|
|
37761
38019
|
// src/worktree-merge-cleanup.ts
|
|
37762
|
-
var
|
|
37763
|
-
var
|
|
38020
|
+
var import_node_fs42 = require("node:fs");
|
|
38021
|
+
var import_node_path39 = require("node:path");
|
|
37764
38022
|
|
|
37765
38023
|
// src/jervcode-node-modules-cleanup.ts
|
|
37766
|
-
var
|
|
37767
|
-
var
|
|
37768
|
-
var
|
|
37769
|
-
var JERVCODE_PACKAGE_ENTRY = (0,
|
|
38024
|
+
var import_node_fs40 = require("node:fs");
|
|
38025
|
+
var import_node_os20 = require("node:os");
|
|
38026
|
+
var import_node_path37 = require("node:path");
|
|
38027
|
+
var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
|
|
37770
38028
|
var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
|
|
37771
38029
|
var POSIX_NAMES2 = ["jervcode"];
|
|
37772
38030
|
var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
|
|
37773
|
-
function jervcodeCandidatePaths(env = process.env, home = (0,
|
|
38031
|
+
function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
|
|
37774
38032
|
const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
|
|
37775
38033
|
const out = [];
|
|
37776
38034
|
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
37777
|
-
for (const name of names) out.push((0,
|
|
38035
|
+
for (const name of names) out.push((0, import_node_path37.join)(dir, name));
|
|
37778
38036
|
}
|
|
37779
38037
|
return out;
|
|
37780
38038
|
}
|
|
37781
|
-
function resolveJervcodePath(env = process.env, home = (0,
|
|
38039
|
+
function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
|
|
37782
38040
|
for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
|
|
37783
38041
|
if (exists(candidate)) return candidate;
|
|
37784
38042
|
}
|
|
@@ -37786,10 +38044,10 @@ function resolveJervcodePath(env = process.env, home = (0, import_node_os19.home
|
|
|
37786
38044
|
}
|
|
37787
38045
|
function jervcodeExecFileArgs(args, opts = {}) {
|
|
37788
38046
|
const platform2 = opts.platform ?? process.platform;
|
|
37789
|
-
const exists = opts.exists ??
|
|
37790
|
-
const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0,
|
|
38047
|
+
const exists = opts.exists ?? import_node_fs40.existsSync;
|
|
38048
|
+
const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
|
|
37791
38049
|
if (resolved) {
|
|
37792
|
-
const entry = (0,
|
|
38050
|
+
const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
|
|
37793
38051
|
if (exists(entry)) {
|
|
37794
38052
|
return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
|
|
37795
38053
|
}
|
|
@@ -37820,8 +38078,8 @@ async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
|
|
|
37820
38078
|
}
|
|
37821
38079
|
|
|
37822
38080
|
// src/worktree-evidence-archive.ts
|
|
37823
|
-
var
|
|
37824
|
-
var
|
|
38081
|
+
var import_node_fs41 = require("node:fs");
|
|
38082
|
+
var import_node_path38 = require("node:path");
|
|
37825
38083
|
var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
|
|
37826
38084
|
function sanitizeArchiveSegment(value, max = 80) {
|
|
37827
38085
|
const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -37836,24 +38094,24 @@ function resolveArtifactRunScope(env = process.env) {
|
|
|
37836
38094
|
}
|
|
37837
38095
|
function defaultIsDirectory(path2) {
|
|
37838
38096
|
try {
|
|
37839
|
-
return (0,
|
|
38097
|
+
return (0, import_node_fs41.lstatSync)(path2).isDirectory();
|
|
37840
38098
|
} catch {
|
|
37841
38099
|
return false;
|
|
37842
38100
|
}
|
|
37843
38101
|
}
|
|
37844
38102
|
function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
37845
|
-
const exists = deps.exists ??
|
|
38103
|
+
const exists = deps.exists ?? import_node_fs41.existsSync;
|
|
37846
38104
|
const isDirectory = deps.isDirectory ?? defaultIsDirectory;
|
|
37847
|
-
const copyDir = deps.copyDir ?? ((from, to) => (0,
|
|
38105
|
+
const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }));
|
|
37848
38106
|
const mkdirp = deps.mkdirp ?? ((path2) => {
|
|
37849
|
-
(0,
|
|
38107
|
+
(0, import_node_fs41.mkdirSync)(path2, { recursive: true });
|
|
37850
38108
|
});
|
|
37851
38109
|
const env = deps.env ?? process.env;
|
|
37852
38110
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
37853
38111
|
const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
|
|
37854
38112
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
37855
38113
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
37856
|
-
const source = (0,
|
|
38114
|
+
const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
|
|
37857
38115
|
if (!exists(source)) return { status: "absent" };
|
|
37858
38116
|
if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
|
|
37859
38117
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -37861,7 +38119,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
37861
38119
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
37862
38120
|
const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
|
|
37863
38121
|
try {
|
|
37864
|
-
mkdirp((0,
|
|
38122
|
+
mkdirp((0, import_node_path38.dirname)(dest));
|
|
37865
38123
|
copyDir(source, dest);
|
|
37866
38124
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
37867
38125
|
return { status: "archived", path: dest, runScope };
|
|
@@ -37870,14 +38128,14 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
37870
38128
|
}
|
|
37871
38129
|
}
|
|
37872
38130
|
function defaultStat(path2) {
|
|
37873
|
-
const st = (0,
|
|
38131
|
+
const st = (0, import_node_fs41.statSync)(path2);
|
|
37874
38132
|
return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
|
|
37875
38133
|
}
|
|
37876
38134
|
function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
37877
|
-
const exists = deps.exists ??
|
|
38135
|
+
const exists = deps.exists ?? import_node_fs41.existsSync;
|
|
37878
38136
|
const stat4 = deps.stat ?? defaultStat;
|
|
37879
|
-
const readdir2 = deps.readdir ??
|
|
37880
|
-
const tmpRoot = (0,
|
|
38137
|
+
const readdir2 = deps.readdir ?? import_node_fs41.readdirSync;
|
|
38138
|
+
const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
|
|
37881
38139
|
if (!exists(tmpRoot)) return [];
|
|
37882
38140
|
const entries = [];
|
|
37883
38141
|
const walk2 = (dir) => {
|
|
@@ -37888,14 +38146,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
37888
38146
|
return;
|
|
37889
38147
|
}
|
|
37890
38148
|
for (const name of names) {
|
|
37891
|
-
const full = (0,
|
|
38149
|
+
const full = (0, import_node_path38.join)(dir, name);
|
|
37892
38150
|
let st;
|
|
37893
38151
|
try {
|
|
37894
38152
|
st = stat4(full);
|
|
37895
38153
|
} catch {
|
|
37896
38154
|
continue;
|
|
37897
38155
|
}
|
|
37898
|
-
const relPath = (0,
|
|
38156
|
+
const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
|
|
37899
38157
|
if (st.isDirectory()) {
|
|
37900
38158
|
if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
|
|
37901
38159
|
walk2(full);
|
|
@@ -37909,10 +38167,10 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
37909
38167
|
return entries;
|
|
37910
38168
|
}
|
|
37911
38169
|
function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
37912
|
-
const exists = deps.exists ??
|
|
37913
|
-
const copyDir = deps.copyDir ?? ((from, to) => (0,
|
|
38170
|
+
const exists = deps.exists ?? import_node_fs41.existsSync;
|
|
38171
|
+
const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }));
|
|
37914
38172
|
const mkdirp = deps.mkdirp ?? ((path2) => {
|
|
37915
|
-
(0,
|
|
38173
|
+
(0, import_node_fs41.mkdirSync)(path2, { recursive: true });
|
|
37916
38174
|
});
|
|
37917
38175
|
const env = deps.env ?? process.env;
|
|
37918
38176
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -37920,7 +38178,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
37920
38178
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
37921
38179
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
37922
38180
|
const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
|
|
37923
|
-
const source = (0,
|
|
38181
|
+
const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
|
|
37924
38182
|
if (!scanned.length) return { status: "absent" };
|
|
37925
38183
|
if (!exists(source)) return { status: "absent" };
|
|
37926
38184
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -37928,7 +38186,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
37928
38186
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
37929
38187
|
const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
|
|
37930
38188
|
try {
|
|
37931
|
-
mkdirp((0,
|
|
38189
|
+
mkdirp((0, import_node_path38.dirname)(dest));
|
|
37932
38190
|
copyDir(source, dest);
|
|
37933
38191
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
37934
38192
|
const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
|
|
@@ -37997,9 +38255,9 @@ function normPath2(p) {
|
|
|
37997
38255
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
37998
38256
|
}
|
|
37999
38257
|
function unlinkNodeModulesJunction(wtPath) {
|
|
38000
|
-
const nm = (0,
|
|
38258
|
+
const nm = (0, import_node_path39.join)(wtPath, "node_modules");
|
|
38001
38259
|
try {
|
|
38002
|
-
if ((0,
|
|
38260
|
+
if ((0, import_node_fs42.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs42.rmdirSync)(nm);
|
|
38003
38261
|
return { ok: true };
|
|
38004
38262
|
} catch (e) {
|
|
38005
38263
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -38010,8 +38268,8 @@ function defaultSleep2(ms) {
|
|
|
38010
38268
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
38011
38269
|
}
|
|
38012
38270
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
38013
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
38014
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
38271
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs42.rmdirSync, exists = import_node_fs42.existsSync } = options;
|
|
38272
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs42.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
38015
38273
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
38016
38274
|
if (!junction.ok) return junction;
|
|
38017
38275
|
try {
|
|
@@ -38134,31 +38392,31 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
38134
38392
|
}
|
|
38135
38393
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
38136
38394
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
38137
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0,
|
|
38395
|
+
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path39.join)(wtPath, line.slice(0, -1)));
|
|
38138
38396
|
}
|
|
38139
38397
|
function safeRemoveTree(path2) {
|
|
38140
|
-
const stat4 = (0,
|
|
38398
|
+
const stat4 = (0, import_node_fs42.lstatSync)(path2);
|
|
38141
38399
|
if (stat4.isSymbolicLink()) {
|
|
38142
38400
|
try {
|
|
38143
|
-
(0,
|
|
38401
|
+
(0, import_node_fs42.rmdirSync)(path2);
|
|
38144
38402
|
} catch {
|
|
38145
|
-
(0,
|
|
38403
|
+
(0, import_node_fs42.unlinkSync)(path2);
|
|
38146
38404
|
}
|
|
38147
38405
|
return;
|
|
38148
38406
|
}
|
|
38149
38407
|
if (stat4.isDirectory()) {
|
|
38150
|
-
for (const entry of (0,
|
|
38151
|
-
(0,
|
|
38408
|
+
for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path39.join)(path2, entry));
|
|
38409
|
+
(0, import_node_fs42.rmdirSync)(path2);
|
|
38152
38410
|
return;
|
|
38153
38411
|
}
|
|
38154
|
-
(0,
|
|
38412
|
+
(0, import_node_fs42.unlinkSync)(path2);
|
|
38155
38413
|
}
|
|
38156
38414
|
function errorMessage(e) {
|
|
38157
38415
|
return e instanceof Error ? e.message : String(e);
|
|
38158
38416
|
}
|
|
38159
38417
|
function resolvedOrRaw(path2) {
|
|
38160
38418
|
try {
|
|
38161
|
-
return normPath2((0,
|
|
38419
|
+
return normPath2((0, import_node_fs42.realpathSync)(path2));
|
|
38162
38420
|
} catch {
|
|
38163
38421
|
return normPath2(path2);
|
|
38164
38422
|
}
|
|
@@ -38166,10 +38424,10 @@ function resolvedOrRaw(path2) {
|
|
|
38166
38424
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
38167
38425
|
let realRoot;
|
|
38168
38426
|
try {
|
|
38169
|
-
if ((0,
|
|
38427
|
+
if ((0, import_node_fs42.lstatSync)(root).isSymbolicLink()) {
|
|
38170
38428
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
38171
38429
|
}
|
|
38172
|
-
realRoot = normPath2((0,
|
|
38430
|
+
realRoot = normPath2((0, import_node_fs42.realpathSync)(root));
|
|
38173
38431
|
} catch (e) {
|
|
38174
38432
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
38175
38433
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -38185,16 +38443,16 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
38185
38443
|
const dir = stack.pop();
|
|
38186
38444
|
let entries;
|
|
38187
38445
|
try {
|
|
38188
|
-
entries = (0,
|
|
38446
|
+
entries = (0, import_node_fs42.readdirSync)(dir, { withFileTypes: true });
|
|
38189
38447
|
} catch (e) {
|
|
38190
38448
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
38191
38449
|
}
|
|
38192
38450
|
for (const entry of entries) {
|
|
38193
|
-
const child2 = (0,
|
|
38451
|
+
const child2 = (0, import_node_path39.join)(dir, entry.name);
|
|
38194
38452
|
if (entry.isSymbolicLink()) {
|
|
38195
38453
|
let target = "";
|
|
38196
38454
|
try {
|
|
38197
|
-
target = normPath2((0,
|
|
38455
|
+
target = normPath2((0, import_node_fs42.realpathSync)(child2));
|
|
38198
38456
|
} catch {
|
|
38199
38457
|
target = "";
|
|
38200
38458
|
}
|
|
@@ -38224,7 +38482,7 @@ async function describePreCleanFailure(wtPath, execGit, error) {
|
|
|
38224
38482
|
const more = remaining.length > 10 ? ` (+${remaining.length - 10} more)` : "";
|
|
38225
38483
|
const quote = (path2) => path2.replace(/'/g, "''");
|
|
38226
38484
|
const nested = remaining.find((path2) => path2.endsWith("node_modules/") && path2 !== "node_modules/");
|
|
38227
|
-
const remediation = remaining.includes("node_modules/") ? `jervcode worktree-node-modules-cleanup --worktree '${quote(wtPath)}'` : nested ? `Remove-Item -LiteralPath '${quote((0,
|
|
38485
|
+
const remediation = remaining.includes("node_modules/") ? `jervcode worktree-node-modules-cleanup --worktree '${quote(wtPath)}'` : nested ? `Remove-Item -LiteralPath '${quote((0, import_node_path39.join)(wtPath, nested.slice(0, -1)))}' -Recurse -Force` : void 0;
|
|
38228
38486
|
return { ok: false, error: `${error}; remaining ignored paths: ${shown}${more}`, ...remediation ? { remediation } : {} };
|
|
38229
38487
|
}
|
|
38230
38488
|
function formatWorktreeRemovalFailureDetail(options) {
|
|
@@ -38378,7 +38636,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
38378
38636
|
return report;
|
|
38379
38637
|
}
|
|
38380
38638
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
38381
|
-
const pathExists = options.pathExists ??
|
|
38639
|
+
const pathExists = options.pathExists ?? import_node_fs42.existsSync;
|
|
38382
38640
|
let afterWorktrees = [];
|
|
38383
38641
|
try {
|
|
38384
38642
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -38518,7 +38776,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
38518
38776
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
38519
38777
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
38520
38778
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
38521
|
-
if (pathExists((0,
|
|
38779
|
+
if (pathExists((0, import_node_path39.join)(wtPath, "node_modules"))) {
|
|
38522
38780
|
const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
|
|
38523
38781
|
if (!nmRemoved.ok) {
|
|
38524
38782
|
report.worktree = {
|
|
@@ -38685,10 +38943,10 @@ function argvWantsJson2() {
|
|
|
38685
38943
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
38686
38944
|
}
|
|
38687
38945
|
function hubRoot() {
|
|
38688
|
-
const fromPkg = (0,
|
|
38946
|
+
const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
|
|
38689
38947
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
38690
|
-
if ((0,
|
|
38691
|
-
if ((0,
|
|
38948
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
|
|
38949
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
|
|
38692
38950
|
return null;
|
|
38693
38951
|
}
|
|
38694
38952
|
function ciAuditDeps() {
|
|
@@ -38700,8 +38958,8 @@ function ciAuditDeps() {
|
|
|
38700
38958
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
38701
38959
|
readSeedFile: (path2) => {
|
|
38702
38960
|
if (!root) return null;
|
|
38703
|
-
const fullPath = (0,
|
|
38704
|
-
return (0,
|
|
38961
|
+
const fullPath = (0, import_node_path40.join)(root, path2);
|
|
38962
|
+
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
38705
38963
|
}
|
|
38706
38964
|
};
|
|
38707
38965
|
}
|
|
@@ -38783,7 +39041,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
38783
39041
|
try {
|
|
38784
39042
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
38785
39043
|
if (o.repo) args.push("--repo", o.repo);
|
|
38786
|
-
spawnDetachedSelf(args, { spawn:
|
|
39044
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
38787
39045
|
} catch {
|
|
38788
39046
|
}
|
|
38789
39047
|
}
|
|
@@ -39344,11 +39602,11 @@ ${list}`);
|
|
|
39344
39602
|
}
|
|
39345
39603
|
});
|
|
39346
39604
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
39347
|
-
const wfDir = (0,
|
|
39348
|
-
if (!(0,
|
|
39349
|
-
return (0,
|
|
39605
|
+
const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
|
|
39606
|
+
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
39607
|
+
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
39350
39608
|
try {
|
|
39351
|
-
return workflowReportsPrChecks((0,
|
|
39609
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
|
|
39352
39610
|
} catch {
|
|
39353
39611
|
return true;
|
|
39354
39612
|
}
|
|
@@ -39536,7 +39794,49 @@ ${list}`);
|
|
|
39536
39794
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
39537
39795
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
39538
39796
|
});
|
|
39539
|
-
pr.command("
|
|
39797
|
+
jsonParity(pr.command("review-verdict <number>").description("post the ZeroCI review verdict comment the merge helper requires (#6255): computes the PR diff patch-id and head sha itself, renders the v1 contract body, posts it as a PR comment. Only a PROCEED bound to the CURRENT diff lets `pr merge` / `pr land` merge").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
|
|
39798
|
+
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
39799
|
+
const verdict = o.verdict;
|
|
39800
|
+
const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
39801
|
+
let patch;
|
|
39802
|
+
let head;
|
|
39803
|
+
try {
|
|
39804
|
+
[patch, head] = await Promise.all([computePrPatchId(number, o.repo), readPrHeadSha(number, o.repo)]);
|
|
39805
|
+
} catch (e) {
|
|
39806
|
+
return failGraceful(`pr review-verdict: ${e.message}`);
|
|
39807
|
+
}
|
|
39808
|
+
const body = renderReviewVerdictComment({ v: 1, patch, head, verdict, scope: o.scope, risk: o.risk, unverified: o.unverified, reviewer: o.reviewer, findings });
|
|
39809
|
+
let commentUrl;
|
|
39810
|
+
try {
|
|
39811
|
+
commentUrl = await postPrCommentFromFile(number, o.repo, body);
|
|
39812
|
+
} catch (e) {
|
|
39813
|
+
const err = e;
|
|
39814
|
+
return failGraceful(`pr review-verdict: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
39815
|
+
}
|
|
39816
|
+
printLine(JSON.stringify({ number: Number(number), repo: o.repo, verdict, patch, head, commentUrl }));
|
|
39817
|
+
});
|
|
39818
|
+
async function updatePrHeadForMerge(input) {
|
|
39819
|
+
return updatePrHeadFromBase({
|
|
39820
|
+
...input,
|
|
39821
|
+
git: async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
39822
|
+
rest: (method, path2, body) => defaultGitHubClient().rest(method, path2, { ...body ? { body } : {}, timeoutMs: GH_MUTATION_TIMEOUT_MS })
|
|
39823
|
+
});
|
|
39824
|
+
}
|
|
39825
|
+
async function prHeadCheckedOutHere(head, repo, explicitRepo) {
|
|
39826
|
+
if (await gitOut(["branch", "--show-current"]) !== head) return false;
|
|
39827
|
+
if (!explicitRepo || !repo) return true;
|
|
39828
|
+
const cwdRepo = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"]));
|
|
39829
|
+
return cwdRepo?.toLowerCase() === repo.split("/").slice(-2).join("/").toLowerCase();
|
|
39830
|
+
}
|
|
39831
|
+
async function prLandUpdateBranch(prNumber, repo, explicitRepo) {
|
|
39832
|
+
const viewed = JSON.parse((await execFileP("gh", ["pr", "view", prNumber, "--repo", repo, "--json", "headRefName,baseRefName"], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
|
|
39833
|
+
const localCheckedOut = await prHeadCheckedOutHere(viewed.headRefName, repo, explicitRepo);
|
|
39834
|
+
console.warn(`pr land: PR #${prNumber} is BEHIND ${viewed.baseRefName} \u2014 updating ${viewed.headRefName} from the base (${localCheckedOut ? "local merge commit + push" : "GitHub update-branch"}) and re-waiting the checks once (#6263).`);
|
|
39835
|
+
return updatePrHeadForMerge({ prNumber, repo, head: viewed.headRefName, base: viewed.baseRefName, localCheckedOut });
|
|
39836
|
+
}
|
|
39837
|
+
class PrHeadBehindBaseError extends Error {
|
|
39838
|
+
}
|
|
39839
|
+
pr.command("land <number>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "acknowledge and land 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").option("--without-review <reason>", "land without a review verdict (#6255): posts a `zeroci-review-waived` comment carrying the reason and actor, then proceeds").action(async (number, o) => {
|
|
39540
39840
|
if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
|
|
39541
39841
|
try {
|
|
39542
39842
|
const parsed = parseIssueRef(number, o.repo);
|
|
@@ -39571,6 +39871,15 @@ ${list}`);
|
|
|
39571
39871
|
return;
|
|
39572
39872
|
}
|
|
39573
39873
|
if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
|
|
39874
|
+
{
|
|
39875
|
+
const gateRepo = landRepoForGuard ?? await requireRepo(o.repo);
|
|
39876
|
+
const refusal = await requireReviewVerdict("pr land", number, gateRepo, { withoutReview: o.withoutReview });
|
|
39877
|
+
if (refusal) {
|
|
39878
|
+
console.error(refusal);
|
|
39879
|
+
process.exitCode = 1;
|
|
39880
|
+
return;
|
|
39881
|
+
}
|
|
39882
|
+
}
|
|
39574
39883
|
const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
|
|
39575
39884
|
resolveRepo: async (prNumber, repoOpt) => {
|
|
39576
39885
|
const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
|
|
@@ -39645,6 +39954,7 @@ ${list}`);
|
|
|
39645
39954
|
allowedClosing: landClosingGuardInput?.closing,
|
|
39646
39955
|
bodyText: landSquashBody
|
|
39647
39956
|
}),
|
|
39957
|
+
updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)),
|
|
39648
39958
|
pollMerged: async (prNumber, repo, deadlineMs) => {
|
|
39649
39959
|
let lastFailure;
|
|
39650
39960
|
while (Date.now() < deadlineMs) {
|
|
@@ -39705,7 +40015,7 @@ ${list}`);
|
|
|
39705
40015
|
else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
|
|
39706
40016
|
if (result.status === "failed") process.exitCode = 1;
|
|
39707
40017
|
});
|
|
39708
|
-
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); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").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) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888); also the merge settings-proof bypass when delete_branch_on_merge cannot be proven (#6210)").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) => {
|
|
40018
|
+
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); from a foreign checkout the remote probe/delete address this repo and local cleanup runs only in the verified sibling checkout ../<repo>, else localBranch reports skipped-foreign-cwd and the receipt carries foreignCwd: true (#6148)").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) \u2014 run as a background/monitor task or under a shell timeout above that budget; a short foreground timeout (e.g. 120s) kills it after checks pass and leaves the PR open (#6027)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888); also the merge settings-proof bypass when delete_branch_on_merge cannot be proven (#6210)").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").option("--without-review <reason>", "merge without a review verdict (#6255): posts a `zeroci-review-waived` comment carrying the reason and actor, then proceeds")).action(async (number, o) => {
|
|
39709
40019
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
39710
40020
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
39711
40021
|
if (o.disableAuto) {
|
|
@@ -39743,7 +40053,7 @@ ${list}`);
|
|
|
39743
40053
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
39744
40054
|
closingGuardInput,
|
|
39745
40055
|
method === "--squash",
|
|
39746
|
-
o.squashBodyFile ? (0,
|
|
40056
|
+
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
39747
40057
|
);
|
|
39748
40058
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
39749
40059
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -39757,6 +40067,15 @@ ${list}`);
|
|
|
39757
40067
|
return;
|
|
39758
40068
|
}
|
|
39759
40069
|
if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
|
|
40070
|
+
if (prMeta.state !== "MERGED") {
|
|
40071
|
+
const gateRepo = repoForPostCleanup ?? await requireRepo(o.repo);
|
|
40072
|
+
const refusal = await requireReviewVerdict("pr merge", number, gateRepo, { withoutReview: o.withoutReview });
|
|
40073
|
+
if (refusal) {
|
|
40074
|
+
console.error(refusal);
|
|
40075
|
+
process.exitCode = 1;
|
|
40076
|
+
return;
|
|
40077
|
+
}
|
|
40078
|
+
}
|
|
39760
40079
|
if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
|
|
39761
40080
|
if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
|
|
39762
40081
|
const repoSettings = await fetchRestRepoMergeSettings(repoForPostCleanup);
|
|
@@ -39775,7 +40094,7 @@ ${list}`);
|
|
|
39775
40094
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
39776
40095
|
let foreignCheckout;
|
|
39777
40096
|
if (foreignCwd) {
|
|
39778
|
-
const sibling = (0,
|
|
40097
|
+
const sibling = (0, import_node_path40.join)((0, import_node_path40.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
39779
40098
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
39780
40099
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
39781
40100
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -39787,7 +40106,7 @@ ${list}`);
|
|
|
39787
40106
|
}
|
|
39788
40107
|
const ciHeadRef = repoForPostCleanup ? await prHeadRefForCiProbe(number, repoForPostCleanup) : void 0;
|
|
39789
40108
|
const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
|
|
39790
|
-
|
|
40109
|
+
const runWaitGate = async () => {
|
|
39791
40110
|
console.warn(`pr merge: --wait can hold the shell up to the full ${PR_CHECKS_TIMEOUT_MS / 6e4}m budget plus the merge \u2014 run under a shell timeout above that budget or as a background/monitor task (#6027)`);
|
|
39792
40111
|
const repo = await requireRepo(o.repo);
|
|
39793
40112
|
const budgetMs = PR_CHECKS_TIMEOUT_MS;
|
|
@@ -39799,7 +40118,7 @@ ${list}`);
|
|
|
39799
40118
|
if (snapshotRead.state === "failed") {
|
|
39800
40119
|
console.error(`pr merge: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
|
|
39801
40120
|
process.exitCode = 1;
|
|
39802
|
-
return;
|
|
40121
|
+
return false;
|
|
39803
40122
|
}
|
|
39804
40123
|
const baseBranch = snapshotRead.snapshot.baseRef;
|
|
39805
40124
|
const requiredContexts = await fetchRequiredCheckContexts(repo, baseBranch).catch(() => null);
|
|
@@ -39834,9 +40153,11 @@ ${list}`);
|
|
|
39834
40153
|
console.warn(`pr merge: --wait stopped before merge \u2014 ${wait.status}${wait.reason ? `: ${wait.reason}` : ""}${wait.detail ? ` (${wait.detail})` : ""}`);
|
|
39835
40154
|
for (const line of failedChecksReceiptLines("pr merge", wait)) console.warn(line);
|
|
39836
40155
|
process.exitCode = wait.status === "timeout" || wait.status === "rate-limited" ? PR_CHECKS_TIMEOUT_EXIT_CODE : 1;
|
|
39837
|
-
return;
|
|
40156
|
+
return false;
|
|
39838
40157
|
}
|
|
39839
|
-
|
|
40158
|
+
return true;
|
|
40159
|
+
};
|
|
40160
|
+
if (o.wait && !await runWaitGate()) return;
|
|
39840
40161
|
if (ciPolicy.policy === "no-ci") {
|
|
39841
40162
|
const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
|
|
39842
40163
|
if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
|
|
@@ -39852,73 +40173,90 @@ ${list}`);
|
|
|
39852
40173
|
{ allowedClosing: method === "--squash" ? closingGuardInput?.closing : void 0 }
|
|
39853
40174
|
);
|
|
39854
40175
|
const bodyFile = overrideBody?.path;
|
|
39855
|
-
|
|
39856
|
-
|
|
39857
|
-
|
|
39858
|
-
|
|
39859
|
-
|
|
39860
|
-
|
|
40176
|
+
let branchUpdated;
|
|
40177
|
+
const mergeOnce = () => execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch(async (e) => {
|
|
40178
|
+
const message2 = String(e.message || "");
|
|
40179
|
+
if (/already been merged/i.test(message2)) {
|
|
40180
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
40181
|
+
return;
|
|
40182
|
+
}
|
|
40183
|
+
const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
|
|
40184
|
+
if (note) throw new Error(`gh pr merge ${number}: ${note}`);
|
|
40185
|
+
if (!o.auto && !branchUpdated) {
|
|
40186
|
+
const mergeState = prHeadBehindBase({ message: message2 }) ? "BEHIND" : (await execFileP("gh", ["pr", "view", number, ...repoArgs, "--json", "mergeStateStatus", "--jq", ".mergeStateStatus"], { timeout: GC_GH_TIMEOUT_MS4 }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
40187
|
+
if (prHeadBehindBase({ mergeStateStatus: mergeState })) throw new PrHeadBehindBaseError(message2);
|
|
40188
|
+
}
|
|
40189
|
+
if (isGitHubRateLimitError(e)) {
|
|
40190
|
+
if (o.auto) {
|
|
40191
|
+
throw new Error(`gh pr merge ${number}: GraphQL rate limit exhausted, and enabling auto-merge has no REST equivalent \u2014 retry without --auto to merge now via REST, or retry --auto after the pool resets. (${message2.trim()})`);
|
|
39861
40192
|
}
|
|
39862
|
-
|
|
39863
|
-
|
|
39864
|
-
|
|
39865
|
-
|
|
39866
|
-
|
|
40193
|
+
if (!repoForPostCleanup) throw e;
|
|
40194
|
+
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
40195
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
40196
|
+
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
40197
|
+
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
40198
|
+
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
40199
|
+
});
|
|
40200
|
+
return;
|
|
40201
|
+
}
|
|
40202
|
+
if (!o.auto && mergeRejectedBaseBranchModified(message2)) {
|
|
40203
|
+
console.warn(`pr merge: the base branch was modified by a concurrent merge \u2014 waiting ${PR_MERGE_BASE_RACE_RETRY_DELAY_MS / 1e3}s and retrying PR #${number}'s merge once (#6052).`);
|
|
40204
|
+
await new Promise((resolve7) => setTimeout(resolve7, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
|
|
40205
|
+
await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
40206
|
+
const m2 = String(e2.message || "");
|
|
40207
|
+
if (/already been merged/i.test(m2)) {
|
|
40208
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
40209
|
+
return;
|
|
39867
40210
|
}
|
|
39868
|
-
|
|
39869
|
-
|
|
39870
|
-
|
|
39871
|
-
|
|
39872
|
-
|
|
39873
|
-
|
|
39874
|
-
|
|
39875
|
-
|
|
39876
|
-
|
|
39877
|
-
|
|
39878
|
-
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
39882
|
-
|
|
39883
|
-
|
|
39884
|
-
|
|
39885
|
-
|
|
39886
|
-
|
|
39887
|
-
|
|
39888
|
-
|
|
39889
|
-
|
|
39890
|
-
|
|
39891
|
-
|
|
39892
|
-
|
|
39893
|
-
|
|
39894
|
-
|
|
39895
|
-
|
|
39896
|
-
|
|
39897
|
-
|
|
39898
|
-
|
|
39899
|
-
|
|
39900
|
-
|
|
39901
|
-
|
|
39902
|
-
|
|
39903
|
-
|
|
39904
|
-
|
|
39905
|
-
|
|
39906
|
-
|
|
39907
|
-
|
|
39908
|
-
|
|
39909
|
-
|
|
39910
|
-
|
|
39911
|
-
|
|
39912
|
-
|
|
39913
|
-
|
|
39914
|
-
|
|
39915
|
-
|
|
39916
|
-
|
|
39917
|
-
});
|
|
39918
|
-
return;
|
|
39919
|
-
}
|
|
39920
|
-
if (!ghPrMergeLocalBranchDeleteWarning(message2)) throw e;
|
|
39921
|
-
});
|
|
40211
|
+
const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
|
|
40212
|
+
if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
|
|
40213
|
+
if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
|
|
40214
|
+
});
|
|
40215
|
+
return;
|
|
40216
|
+
}
|
|
40217
|
+
if (o.auto && mergeAutoRejectedPrAlreadyClean(message2)) {
|
|
40218
|
+
await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
40219
|
+
const m2 = String(e2.message || "");
|
|
40220
|
+
if (/already been merged/i.test(m2)) {
|
|
40221
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
40222
|
+
return;
|
|
40223
|
+
}
|
|
40224
|
+
const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
|
|
40225
|
+
if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
|
|
40226
|
+
if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
|
|
40227
|
+
});
|
|
40228
|
+
return;
|
|
40229
|
+
}
|
|
40230
|
+
if (!o.auto && basePolicyBlocksImmediateMerge(message2)) {
|
|
40231
|
+
console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
|
|
40232
|
+
upgradedToAuto = true;
|
|
40233
|
+
await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
40234
|
+
const m2 = String(e2.message || "");
|
|
40235
|
+
if (/already been merged/i.test(m2)) {
|
|
40236
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
40237
|
+
return;
|
|
40238
|
+
}
|
|
40239
|
+
const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
|
|
40240
|
+
if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
|
|
40241
|
+
if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
|
|
40242
|
+
});
|
|
40243
|
+
return;
|
|
40244
|
+
}
|
|
40245
|
+
if (!ghPrMergeLocalBranchDeleteWarning(message2)) throw e;
|
|
40246
|
+
});
|
|
40247
|
+
try {
|
|
40248
|
+
try {
|
|
40249
|
+
await mergeOnce();
|
|
40250
|
+
} catch (e) {
|
|
40251
|
+
if (!(e instanceof PrHeadBehindBaseError)) throw e;
|
|
40252
|
+
const localCheckedOut = !foreignCwd && await prHeadCheckedOutHere(headRef, targetRepo2, Boolean(o.repo));
|
|
40253
|
+
console.warn(`pr merge: GitHub refused the merge \u2014 PR #${number}'s head is BEHIND ${baseRef} \u2014 updating ${headRef} from the base (${localCheckedOut ? "local merge commit + push" : "GitHub update-branch"}) and retrying once (#6263).`);
|
|
40254
|
+
branchUpdated = await updatePrHeadForMerge({ prNumber: number, repo: await requireRepo(o.repo), head: headRef, base: baseRef, localCheckedOut }).catch((e2) => {
|
|
40255
|
+
throw new Error(`pr merge: ${e2.message}`);
|
|
40256
|
+
});
|
|
40257
|
+
if (o.wait && !await runWaitGate()) return;
|
|
40258
|
+
await mergeOnce();
|
|
40259
|
+
}
|
|
39922
40260
|
} finally {
|
|
39923
40261
|
overrideBody?.cleanup();
|
|
39924
40262
|
}
|
|
@@ -39953,7 +40291,8 @@ ${list}`);
|
|
|
39953
40291
|
pr: number,
|
|
39954
40292
|
branch: headRef,
|
|
39955
40293
|
state: stateRead.ok ? stateRead.state : "unknown",
|
|
39956
|
-
cleanupStatus: "skipped"
|
|
40294
|
+
cleanupStatus: "skipped",
|
|
40295
|
+
...branchUpdated ? { branchUpdated } : {}
|
|
39957
40296
|
}));
|
|
39958
40297
|
console.error(`pr merge: ${gate.message}. Nothing was deleted \u2014 re-check the PR and retry.`);
|
|
39959
40298
|
process.exitCode = 1;
|
|
@@ -39973,7 +40312,7 @@ ${list}`);
|
|
|
39973
40312
|
preserveWorktree: o.preserveWorktree,
|
|
39974
40313
|
gcAcknowledged: o.gc,
|
|
39975
40314
|
expectedHeadOid: headRefOid,
|
|
39976
|
-
pathExists: (p) => (0,
|
|
40315
|
+
pathExists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
39977
40316
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
39978
40317
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
39979
40318
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -40064,6 +40403,7 @@ ${list}`);
|
|
|
40064
40403
|
...methodField ? { method: methodField } : {},
|
|
40065
40404
|
remoteBranch,
|
|
40066
40405
|
housekeeping,
|
|
40406
|
+
...branchUpdated ? { branchUpdated } : {},
|
|
40067
40407
|
...foreignCwd ? { foreignCwd: true, ...foreignCheckout ? { foreignCheckout } : {} } : {},
|
|
40068
40408
|
...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
|
|
40069
40409
|
...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
|
|
@@ -40094,7 +40434,7 @@ ${list}`);
|
|
|
40094
40434
|
}
|
|
40095
40435
|
|
|
40096
40436
|
// src/command-register-developer.ts
|
|
40097
|
-
var
|
|
40437
|
+
var import_node_fs48 = require("node:fs");
|
|
40098
40438
|
|
|
40099
40439
|
// src/whoami.ts
|
|
40100
40440
|
async function resolveWhoami(deps) {
|
|
@@ -40124,7 +40464,7 @@ async function resolveWhoami(deps) {
|
|
|
40124
40464
|
}
|
|
40125
40465
|
|
|
40126
40466
|
// src/command-register-developer.ts
|
|
40127
|
-
var
|
|
40467
|
+
var import_node_path45 = require("node:path");
|
|
40128
40468
|
|
|
40129
40469
|
// src/wave-land.ts
|
|
40130
40470
|
function planWaveLand(prs) {
|
|
@@ -40155,7 +40495,7 @@ async function executeWaveLand(plan, deps) {
|
|
|
40155
40495
|
}
|
|
40156
40496
|
|
|
40157
40497
|
// src/box-commands.ts
|
|
40158
|
-
var
|
|
40498
|
+
var import_node_fs44 = require("node:fs");
|
|
40159
40499
|
|
|
40160
40500
|
// src/box.ts
|
|
40161
40501
|
var BOX_KEYS = {
|
|
@@ -40357,7 +40697,7 @@ function registerBoxCommands(program3) {
|
|
|
40357
40697
|
return;
|
|
40358
40698
|
}
|
|
40359
40699
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
40360
|
-
if (wroteScript) (0,
|
|
40700
|
+
if (wroteScript) (0, import_node_fs44.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
|
|
40361
40701
|
if (o.json) {
|
|
40362
40702
|
console.log(JSON.stringify({
|
|
40363
40703
|
box: found,
|
|
@@ -40377,21 +40717,21 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
40377
40717
|
}
|
|
40378
40718
|
|
|
40379
40719
|
// src/dist-drift.ts
|
|
40380
|
-
var
|
|
40720
|
+
var import_node_child_process17 = require("node:child_process");
|
|
40381
40721
|
var import_node_crypto12 = require("node:crypto");
|
|
40382
|
-
var
|
|
40383
|
-
var
|
|
40384
|
-
var
|
|
40722
|
+
var import_node_fs46 = require("node:fs");
|
|
40723
|
+
var import_node_os21 = require("node:os");
|
|
40724
|
+
var import_node_path42 = require("node:path");
|
|
40385
40725
|
|
|
40386
40726
|
// ../scripts/distribution-digest.mjs
|
|
40387
40727
|
var import_node_crypto11 = require("node:crypto");
|
|
40388
|
-
var
|
|
40389
|
-
var
|
|
40728
|
+
var import_node_fs45 = require("node:fs");
|
|
40729
|
+
var import_node_path41 = require("node:path");
|
|
40390
40730
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
40391
40731
|
function repoPath(root, declaredPath, label) {
|
|
40392
|
-
const absoluteRoot = (0,
|
|
40393
|
-
const target = (0,
|
|
40394
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
40732
|
+
const absoluteRoot = (0, import_node_path41.resolve)(root);
|
|
40733
|
+
const target = (0, import_node_path41.resolve)(root, declaredPath);
|
|
40734
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path41.sep}`)) {
|
|
40395
40735
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
40396
40736
|
}
|
|
40397
40737
|
return target;
|
|
@@ -40399,7 +40739,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
40399
40739
|
function digestFiles(files) {
|
|
40400
40740
|
const hash = (0, import_node_crypto11.createHash)("sha256");
|
|
40401
40741
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
40402
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
40742
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
|
|
40403
40743
|
hash.update(file.relative, "utf8");
|
|
40404
40744
|
hash.update("\0");
|
|
40405
40745
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -40414,8 +40754,8 @@ function digestFiles(files) {
|
|
|
40414
40754
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
40415
40755
|
return digestFiles(packedFiles.map((path2) => {
|
|
40416
40756
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
40417
|
-
if (!(0,
|
|
40418
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
40757
|
+
if (!(0, import_node_fs45.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
40758
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs45.lstatSync)(absolute) };
|
|
40419
40759
|
}));
|
|
40420
40760
|
}
|
|
40421
40761
|
|
|
@@ -40499,13 +40839,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
40499
40839
|
return lines2;
|
|
40500
40840
|
}
|
|
40501
40841
|
function readOrNull(path2) {
|
|
40502
|
-
return (0,
|
|
40842
|
+
return (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2) : null;
|
|
40503
40843
|
}
|
|
40504
40844
|
function walkFiles(root) {
|
|
40505
40845
|
const files = [];
|
|
40506
40846
|
const walk2 = (directory) => {
|
|
40507
|
-
for (const entry of (0,
|
|
40508
|
-
const child2 = (0,
|
|
40847
|
+
for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
40848
|
+
const child2 = (0, import_node_path42.join)(directory, entry.name);
|
|
40509
40849
|
if (entry.isDirectory()) walk2(child2);
|
|
40510
40850
|
else files.push(child2);
|
|
40511
40851
|
}
|
|
@@ -40515,14 +40855,14 @@ function walkFiles(root) {
|
|
|
40515
40855
|
}
|
|
40516
40856
|
function bomPathFor(root) {
|
|
40517
40857
|
try {
|
|
40518
|
-
const registry2 = JSON.parse((0,
|
|
40519
|
-
return (0,
|
|
40858
|
+
const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(root, "surfaces.json"), "utf8"));
|
|
40859
|
+
return (0, import_node_path42.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
40520
40860
|
} catch {
|
|
40521
|
-
return (0,
|
|
40861
|
+
return (0, import_node_path42.join)(root, "distribution-bom.json");
|
|
40522
40862
|
}
|
|
40523
40863
|
}
|
|
40524
40864
|
function rebuildTo(packageRoot, outDir) {
|
|
40525
|
-
(0,
|
|
40865
|
+
(0, import_node_child_process17.execFileSync)(process.execPath, ["build.mjs"], {
|
|
40526
40866
|
cwd: packageRoot,
|
|
40527
40867
|
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
40528
40868
|
windowsHide: true,
|
|
@@ -40531,35 +40871,35 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
40531
40871
|
});
|
|
40532
40872
|
}
|
|
40533
40873
|
function runDistStatus(root) {
|
|
40534
|
-
const stage = (0,
|
|
40874
|
+
const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path42.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
|
|
40535
40875
|
let overlayCount = 0;
|
|
40536
40876
|
try {
|
|
40537
|
-
const cliOut = (0,
|
|
40538
|
-
const hubOut = (0,
|
|
40539
|
-
rebuildTo((0,
|
|
40540
|
-
rebuildTo((0,
|
|
40877
|
+
const cliOut = (0, import_node_path42.join)(stage, "cli-dist");
|
|
40878
|
+
const hubOut = (0, import_node_path42.join)(stage, "hub-dist");
|
|
40879
|
+
rebuildTo((0, import_node_path42.join)(root, "cli"), cliOut);
|
|
40880
|
+
rebuildTo((0, import_node_path42.join)(root, "updater"), hubOut);
|
|
40541
40881
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
40542
40882
|
const rebuilt = (path2) => {
|
|
40543
40883
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
40544
|
-
return spec ? readOrNull((0,
|
|
40884
|
+
return spec ? readOrNull((0, import_node_path42.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
40545
40885
|
};
|
|
40546
|
-
const committed = (path2) => readOrNull((0,
|
|
40547
|
-
const tree = (path2) => readOrNull((0,
|
|
40548
|
-
const distRoot = (0,
|
|
40549
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
40550
|
-
const bom = JSON.parse((0,
|
|
40886
|
+
const committed = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
|
|
40887
|
+
const tree = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
|
|
40888
|
+
const distRoot = (0, import_node_path42.join)(root, "cli", "dist");
|
|
40889
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path42.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
40890
|
+
const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
|
|
40551
40891
|
const digest = (entries) => {
|
|
40552
|
-
const overlay = (0,
|
|
40892
|
+
const overlay = (0, import_node_path42.join)(stage, `overlay-${overlayCount++}`);
|
|
40553
40893
|
for (const entry of entries) {
|
|
40554
|
-
const target = (0,
|
|
40555
|
-
(0,
|
|
40556
|
-
(0,
|
|
40894
|
+
const target = (0, import_node_path42.join)(overlay, entry.path);
|
|
40895
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path42.dirname)(target), { recursive: true });
|
|
40896
|
+
(0, import_node_fs46.writeFileSync)(target, entry.bytes);
|
|
40557
40897
|
}
|
|
40558
40898
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
40559
40899
|
};
|
|
40560
40900
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
40561
40901
|
} finally {
|
|
40562
|
-
(0,
|
|
40902
|
+
(0, import_node_fs46.rmSync)(stage, { recursive: true, force: true });
|
|
40563
40903
|
}
|
|
40564
40904
|
}
|
|
40565
40905
|
|
|
@@ -40631,7 +40971,7 @@ function registerEdgeCommands(program3) {
|
|
|
40631
40971
|
|
|
40632
40972
|
// src/schedules-lift-command.ts
|
|
40633
40973
|
var import_promises8 = require("node:fs/promises");
|
|
40634
|
-
var
|
|
40974
|
+
var import_node_path43 = require("node:path");
|
|
40635
40975
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
40636
40976
|
var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
|
|
40637
40977
|
var SchedulesLiftUsageError = class extends Error {
|
|
@@ -40658,7 +40998,7 @@ async function readWorkflowFiles(dir) {
|
|
|
40658
40998
|
const files = [];
|
|
40659
40999
|
for (const name of names.sort()) {
|
|
40660
41000
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
40661
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0,
|
|
41001
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path43.join)(dir, name), "utf8") });
|
|
40662
41002
|
}
|
|
40663
41003
|
return files;
|
|
40664
41004
|
}
|
|
@@ -40740,9 +41080,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
40740
41080
|
}
|
|
40741
41081
|
|
|
40742
41082
|
// src/spawn-policy-core.ts
|
|
40743
|
-
var
|
|
40744
|
-
var
|
|
40745
|
-
var
|
|
41083
|
+
var import_node_child_process18 = require("node:child_process");
|
|
41084
|
+
var import_node_fs47 = require("node:fs");
|
|
41085
|
+
var import_node_path44 = require("node:path");
|
|
40746
41086
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
40747
41087
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
40748
41088
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -40811,7 +41151,7 @@ function findViolationsInSource(raw) {
|
|
|
40811
41151
|
return found;
|
|
40812
41152
|
}
|
|
40813
41153
|
function policedFiles(root) {
|
|
40814
|
-
const r = (0,
|
|
41154
|
+
const r = (0, import_node_child_process18.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
40815
41155
|
cwd: root,
|
|
40816
41156
|
encoding: "utf8",
|
|
40817
41157
|
windowsHide: true,
|
|
@@ -40828,7 +41168,7 @@ function runSpawnPolicy(root) {
|
|
|
40828
41168
|
for (const file of files) {
|
|
40829
41169
|
let raw;
|
|
40830
41170
|
try {
|
|
40831
|
-
raw = (0,
|
|
41171
|
+
raw = (0, import_node_fs47.readFileSync)((0, import_node_path44.join)(root, file), "utf8");
|
|
40832
41172
|
} catch {
|
|
40833
41173
|
continue;
|
|
40834
41174
|
}
|
|
@@ -40848,19 +41188,19 @@ function runSpawnPolicy(root) {
|
|
|
40848
41188
|
function registerDeveloperCommands(program3) {
|
|
40849
41189
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
40850
41190
|
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) => {
|
|
40851
|
-
const path2 = (0,
|
|
40852
|
-
const current = (0,
|
|
41191
|
+
const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
|
|
41192
|
+
const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
|
|
40853
41193
|
const plan = planManagedGitignore(current);
|
|
40854
41194
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
40855
41195
|
if (opts.json) {
|
|
40856
|
-
if (opts.write && plan.changed) (0,
|
|
41196
|
+
if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
40857
41197
|
console.log(JSON.stringify(plan, null, 2));
|
|
40858
41198
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
40859
41199
|
return;
|
|
40860
41200
|
}
|
|
40861
41201
|
if (opts.write) {
|
|
40862
41202
|
if (plan.changed) {
|
|
40863
|
-
(0,
|
|
41203
|
+
(0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
40864
41204
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
40865
41205
|
} else {
|
|
40866
41206
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -42348,12 +42688,12 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = H
|
|
|
42348
42688
|
}
|
|
42349
42689
|
|
|
42350
42690
|
// src/hotfix-coverage.ts
|
|
42351
|
-
var
|
|
42691
|
+
var import_node_child_process19 = require("node:child_process");
|
|
42352
42692
|
var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
42353
42693
|
function checkHotfixCoverage(options = {}) {
|
|
42354
42694
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
42355
42695
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
42356
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
42696
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process19.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
42357
42697
|
const revList = (range) => {
|
|
42358
42698
|
const out = git3(["rev-list", "--no-merges", range]).trim();
|
|
42359
42699
|
return out ? out.split("\n") : [];
|
|
@@ -42421,7 +42761,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
42421
42761
|
}
|
|
42422
42762
|
function checkHotfixCarries(options) {
|
|
42423
42763
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
42424
|
-
const git3 = options.git ?? ((args, opts) => (0,
|
|
42764
|
+
const git3 = options.git ?? ((args, opts) => (0, import_node_child_process19.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
42425
42765
|
const isAncestor = (sha, ref) => {
|
|
42426
42766
|
try {
|
|
42427
42767
|
git3(["merge-base", "--is-ancestor", sha, ref]);
|
|
@@ -42453,8 +42793,8 @@ function checkHotfixCarries(options) {
|
|
|
42453
42793
|
}
|
|
42454
42794
|
|
|
42455
42795
|
// src/train-commands.ts
|
|
42456
|
-
var
|
|
42457
|
-
var
|
|
42796
|
+
var import_node_fs49 = require("node:fs");
|
|
42797
|
+
var import_node_path46 = require("node:path");
|
|
42458
42798
|
var INVOKED_ARGV = process.argv.slice(2);
|
|
42459
42799
|
var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
42460
42800
|
function resolveReleaseBumpIntent(raw) {
|
|
@@ -42466,7 +42806,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
42466
42806
|
}
|
|
42467
42807
|
function readRepoVersion() {
|
|
42468
42808
|
try {
|
|
42469
|
-
return JSON.parse((0,
|
|
42809
|
+
return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path46.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
42470
42810
|
} catch {
|
|
42471
42811
|
return void 0;
|
|
42472
42812
|
}
|
|
@@ -43210,12 +43550,12 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
43210
43550
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
43211
43551
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
43212
43552
|
function envHealLockPath(home) {
|
|
43213
|
-
return (0,
|
|
43553
|
+
return (0, import_node_path47.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
43214
43554
|
}
|
|
43215
43555
|
async function withEnvHealLock(what, run) {
|
|
43216
43556
|
try {
|
|
43217
43557
|
return await withFileLock(
|
|
43218
|
-
envHealLockPath((0,
|
|
43558
|
+
envHealLockPath((0, import_node_os22.homedir)()),
|
|
43219
43559
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
43220
43560
|
run
|
|
43221
43561
|
);
|
|
@@ -43311,7 +43651,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
43311
43651
|
const configRoot = surfaceConfigRoot(surface);
|
|
43312
43652
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
43313
43653
|
const plan = buildPluginCachePlan(
|
|
43314
|
-
(0,
|
|
43654
|
+
(0, import_node_os22.homedir)(),
|
|
43315
43655
|
running,
|
|
43316
43656
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
43317
43657
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -43339,14 +43679,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
43339
43679
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
43340
43680
|
const installed = installedActivePluginVersion(surface);
|
|
43341
43681
|
const plan = buildPluginCachePlan(
|
|
43342
|
-
(0,
|
|
43682
|
+
(0, import_node_os22.homedir)(),
|
|
43343
43683
|
running,
|
|
43344
43684
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
43345
43685
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
43346
43686
|
);
|
|
43347
43687
|
const result = applyPluginCachePlan(
|
|
43348
43688
|
plan,
|
|
43349
|
-
(p) => (0,
|
|
43689
|
+
(p) => (0, import_node_fs50.rmSync)(p, { recursive: true }),
|
|
43350
43690
|
stagingApplyFsGuard(configRoot)
|
|
43351
43691
|
);
|
|
43352
43692
|
return {
|
|
@@ -43381,7 +43721,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
43381
43721
|
// has no generated routing index would
|
|
43382
43722
|
// get a permanent — demanding an artifact it never asked for.
|
|
43383
43723
|
docsIndexState: (root) => {
|
|
43384
|
-
if (!(0,
|
|
43724
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
43385
43725
|
const real = createDocsIndexDeps(root);
|
|
43386
43726
|
let docs;
|
|
43387
43727
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -43390,7 +43730,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
43390
43730
|
},
|
|
43391
43731
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
43392
43732
|
healDocsIndex: (root) => {
|
|
43393
|
-
if (!(0,
|
|
43733
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
43394
43734
|
const real = createDocsIndexDeps(root);
|
|
43395
43735
|
let docs;
|
|
43396
43736
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -43949,7 +44289,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
43949
44289
|
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`);
|
|
43950
44290
|
if (o.secretsFile) {
|
|
43951
44291
|
try {
|
|
43952
|
-
vars.push(`secrets=${(0,
|
|
44292
|
+
vars.push(`secrets=${(0, import_node_fs50.readFileSync)(o.secretsFile, "utf8")}`);
|
|
43953
44293
|
} catch (e) {
|
|
43954
44294
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
43955
44295
|
}
|
|
@@ -44262,16 +44602,16 @@ function ciAuditDeps2() {
|
|
|
44262
44602
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
44263
44603
|
readSeedFile: (path2) => {
|
|
44264
44604
|
if (!root) return null;
|
|
44265
|
-
const fullPath = (0,
|
|
44266
|
-
return (0,
|
|
44605
|
+
const fullPath = (0, import_node_path47.join)(root, path2);
|
|
44606
|
+
return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
|
|
44267
44607
|
}
|
|
44268
44608
|
};
|
|
44269
44609
|
}
|
|
44270
44610
|
function hubRoot2() {
|
|
44271
|
-
const fromPkg = (0,
|
|
44611
|
+
const fromPkg = (0, import_node_path47.join)(__dirname, "..", "..");
|
|
44272
44612
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
44273
|
-
if ((0,
|
|
44274
|
-
if ((0,
|
|
44613
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(fromPkg, marker))) return fromPkg;
|
|
44614
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(process.cwd(), marker))) return process.cwd();
|
|
44275
44615
|
return null;
|
|
44276
44616
|
}
|
|
44277
44617
|
registerQueryCommands(program2);
|
|
@@ -44371,10 +44711,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
44371
44711
|
targets = resolution.targets;
|
|
44372
44712
|
}
|
|
44373
44713
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
44374
|
-
const fileMatrix = (0,
|
|
44714
|
+
const fileMatrix = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
44375
44715
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
44376
44716
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
44377
|
-
const sanctioned = (0,
|
|
44717
|
+
const sanctioned = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
44378
44718
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
44379
44719
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
44380
44720
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -44405,16 +44745,16 @@ function directoryBytes(path2) {
|
|
|
44405
44745
|
let total = 0;
|
|
44406
44746
|
let entries;
|
|
44407
44747
|
try {
|
|
44408
|
-
entries = (0,
|
|
44748
|
+
entries = (0, import_node_fs50.readdirSync)(path2, { withFileTypes: true });
|
|
44409
44749
|
} catch {
|
|
44410
44750
|
return 0;
|
|
44411
44751
|
}
|
|
44412
44752
|
for (const entry of entries) {
|
|
44413
|
-
const child2 = (0,
|
|
44753
|
+
const child2 = (0, import_node_path47.join)(path2, entry.name);
|
|
44414
44754
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
44415
44755
|
else {
|
|
44416
44756
|
try {
|
|
44417
|
-
total += (0,
|
|
44757
|
+
total += (0, import_node_fs50.statSync)(child2).size;
|
|
44418
44758
|
} catch {
|
|
44419
44759
|
}
|
|
44420
44760
|
}
|
|
@@ -44422,25 +44762,25 @@ function directoryBytes(path2) {
|
|
|
44422
44762
|
return total;
|
|
44423
44763
|
}
|
|
44424
44764
|
function listDirEntries(dir) {
|
|
44425
|
-
return (0,
|
|
44765
|
+
return (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
44426
44766
|
}
|
|
44427
44767
|
function readInstalledPluginRefs(configRoot) {
|
|
44428
44768
|
const p = installedPluginsPathForConfig(configRoot);
|
|
44429
|
-
if (!(0,
|
|
44769
|
+
if (!(0, import_node_fs50.existsSync)(p)) return [];
|
|
44430
44770
|
try {
|
|
44431
|
-
return installedPluginPaths((0,
|
|
44771
|
+
return installedPluginPaths((0, import_node_fs50.readFileSync)(p, "utf8"));
|
|
44432
44772
|
} catch {
|
|
44433
44773
|
return null;
|
|
44434
44774
|
}
|
|
44435
44775
|
}
|
|
44436
44776
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
44437
44777
|
return {
|
|
44438
|
-
exists: (p) => (0,
|
|
44439
|
-
listVersionDirs: (root) => (0,
|
|
44778
|
+
exists: (p) => (0, import_node_fs50.existsSync)(p),
|
|
44779
|
+
listVersionDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
44440
44780
|
dirBytes,
|
|
44441
|
-
listStagingDirs: (root) => (0,
|
|
44781
|
+
listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
44442
44782
|
try {
|
|
44443
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
44783
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path47.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
|
|
44444
44784
|
} catch {
|
|
44445
44785
|
return { name: d.name, mtimeMs: Date.now() };
|
|
44446
44786
|
}
|
|
@@ -44454,10 +44794,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
44454
44794
|
return {
|
|
44455
44795
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
44456
44796
|
mtimeMs: (name) => {
|
|
44457
|
-
const p = (0,
|
|
44458
|
-
if (!(0,
|
|
44797
|
+
const p = (0, import_node_path47.join)(stagingRoot, name);
|
|
44798
|
+
if (!(0, import_node_fs50.existsSync)(p)) return null;
|
|
44459
44799
|
try {
|
|
44460
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
44800
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
|
|
44461
44801
|
} catch {
|
|
44462
44802
|
return null;
|
|
44463
44803
|
}
|
|
@@ -44477,13 +44817,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
44477
44817
|
return;
|
|
44478
44818
|
}
|
|
44479
44819
|
const plan = buildPluginCachePlan(
|
|
44480
|
-
(0,
|
|
44820
|
+
(0, import_node_os22.homedir)(),
|
|
44481
44821
|
running,
|
|
44482
44822
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
44483
44823
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
44484
44824
|
);
|
|
44485
44825
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
44486
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
44826
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs50.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
44487
44827
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
44488
44828
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
44489
44829
|
else console.log(renderPluginCachePlan(plan, result));
|