@mutmutco/cli 4.3.20 → 4.3.21
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 +684 -487
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5227,7 +5227,7 @@ function unknownCommandDomainGuide(parentPath, token) {
|
|
|
5227
5227
|
|
|
5228
5228
|
// src/command-composition.ts
|
|
5229
5229
|
var import_node_os22 = require("node:os");
|
|
5230
|
-
var
|
|
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");
|
|
@@ -12902,6 +12902,29 @@ async function waitForPrChecks(deps) {
|
|
|
12902
12902
|
|
|
12903
12903
|
// src/bootstrap-ruleset.ts
|
|
12904
12904
|
var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
|
|
12905
|
+
var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
|
|
12906
|
+
function reseedProductRulesetStrictness(current, seed) {
|
|
12907
|
+
const parse = (raw) => {
|
|
12908
|
+
const policy2 = JSON.parse(raw);
|
|
12909
|
+
if (!policy2 || typeof policy2 !== "object" || Array.isArray(policy2) || policy2.name !== PRODUCT_RULESET_NAME || policy2.target !== "branch" || !Array.isArray(policy2.rules) || policy2.rules.some((r) => !r || typeof r !== "object" || Array.isArray(r) || typeof r.type !== "string")) {
|
|
12910
|
+
throw new Error("product ruleset must be a branch ruleset object with well-formed rules");
|
|
12911
|
+
}
|
|
12912
|
+
const required = policy2.rules.filter((r) => r.type === "required_status_checks");
|
|
12913
|
+
const parameters2 = required[0]?.parameters;
|
|
12914
|
+
if (required.length !== 1 || !parameters2 || typeof parameters2 !== "object" || Array.isArray(parameters2) || !Array.isArray(parameters2.required_status_checks) || !parameters2.required_status_checks.length || parameters2.required_status_checks.some((c) => !c || typeof c !== "object" || Array.isArray(c) || typeof c.context !== "string" || !c.context.trim()) || parameters2.strict_required_status_checks_policy !== void 0 && typeof parameters2.strict_required_status_checks_policy !== "boolean") {
|
|
12915
|
+
throw new Error("product ruleset must carry exactly one well-formed required_status_checks rule");
|
|
12916
|
+
}
|
|
12917
|
+
return { policy: policy2, parameters: parameters2 };
|
|
12918
|
+
};
|
|
12919
|
+
const strict = parse(seed).parameters.strict_required_status_checks_policy;
|
|
12920
|
+
if (typeof strict !== "boolean") throw new Error("canonical product ruleset must declare boolean strictness");
|
|
12921
|
+
if (current === null) return seed;
|
|
12922
|
+
const { policy, parameters } = parse(current);
|
|
12923
|
+
if (parameters.strict_required_status_checks_policy === strict) return current;
|
|
12924
|
+
parameters.strict_required_status_checks_policy = strict;
|
|
12925
|
+
return `${JSON.stringify(policy, null, 2)}
|
|
12926
|
+
`;
|
|
12927
|
+
}
|
|
12905
12928
|
function stripRulesetComment(raw) {
|
|
12906
12929
|
const parsed = JSON.parse(raw);
|
|
12907
12930
|
delete parsed._comment;
|
|
@@ -12921,14 +12944,6 @@ function rulesetStrictPolicy(ruleset) {
|
|
|
12921
12944
|
const rules = (ruleset.rules ?? []).filter((rule) => rule.type === "required_status_checks");
|
|
12922
12945
|
return rules.length > 0 && rules.every((rule) => rule.parameters?.strict_required_status_checks_policy === true);
|
|
12923
12946
|
}
|
|
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
|
-
}
|
|
12932
12947
|
function rulesetBranchIncludes(ruleset) {
|
|
12933
12948
|
const raw = ruleset.conditions?.ref_name?.include;
|
|
12934
12949
|
return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref.length > 0))].sort((a, b) => a.localeCompare(b)) : [];
|
|
@@ -12948,8 +12963,7 @@ function patchRulesetRequiredContexts(body, contexts) {
|
|
|
12948
12963
|
...r,
|
|
12949
12964
|
parameters: {
|
|
12950
12965
|
...r.parameters,
|
|
12951
|
-
strict_required_status_checks_policy:
|
|
12952
|
-
// #6263: org policy — see patchRulesetStrictPolicy
|
|
12966
|
+
strict_required_status_checks_policy: r.parameters?.strict_required_status_checks_policy ?? false,
|
|
12953
12967
|
required_status_checks: sorted.map((context) => ({ context }))
|
|
12954
12968
|
}
|
|
12955
12969
|
};
|
|
@@ -13353,7 +13367,7 @@ function extractForwardRefs(markdown) {
|
|
|
13353
13367
|
});
|
|
13354
13368
|
return refs;
|
|
13355
13369
|
}
|
|
13356
|
-
function checkPins(root,
|
|
13370
|
+
function checkPins(root, readFile9, docs) {
|
|
13357
13371
|
const findings = [];
|
|
13358
13372
|
for (const [doc, markdown] of Object.entries(docs)) {
|
|
13359
13373
|
for (const pin of extractPins(markdown)) {
|
|
@@ -13361,7 +13375,7 @@ function checkPins(root, readFile10, docs) {
|
|
|
13361
13375
|
findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
|
|
13362
13376
|
continue;
|
|
13363
13377
|
}
|
|
13364
|
-
const source =
|
|
13378
|
+
const source = readFile9((0, import_node_path12.join)(root, pin.file));
|
|
13365
13379
|
if (source == null) {
|
|
13366
13380
|
findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
|
|
13367
13381
|
continue;
|
|
@@ -13576,7 +13590,7 @@ function defaultTrackedFirstSegments(root, firstSegments, exec = import_node_chi
|
|
|
13576
13590
|
}
|
|
13577
13591
|
}
|
|
13578
13592
|
function runDocRefs(root, deps = {}) {
|
|
13579
|
-
const
|
|
13593
|
+
const readFile9 = deps.readFile ?? readFileOrNull;
|
|
13580
13594
|
const exists = deps.exists ?? import_node_fs14.existsSync;
|
|
13581
13595
|
const listDocs = deps.listDocs ?? defaultListDocs;
|
|
13582
13596
|
const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
|
|
@@ -13585,11 +13599,11 @@ function runDocRefs(root, deps = {}) {
|
|
|
13585
13599
|
const walked = listDocs(root);
|
|
13586
13600
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
13587
13601
|
const docs = Object.fromEntries(
|
|
13588
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel,
|
|
13602
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
|
|
13589
13603
|
);
|
|
13590
13604
|
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
|
|
13591
13605
|
const findings = [
|
|
13592
|
-
...checkPins(root,
|
|
13606
|
+
...checkPins(root, readFile9, docs).findings,
|
|
13593
13607
|
...refResult.findings
|
|
13594
13608
|
];
|
|
13595
13609
|
if (commandPaths == null) {
|
|
@@ -14186,22 +14200,22 @@ function labelsToPrune(orgLabelNames) {
|
|
|
14186
14200
|
const org = new Set(orgLabelNames);
|
|
14187
14201
|
return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
|
|
14188
14202
|
}
|
|
14189
|
-
function resolveSeedContent(seed, vars,
|
|
14190
|
-
if (seed.source === "self") return
|
|
14203
|
+
function resolveSeedContent(seed, vars, readFile9) {
|
|
14204
|
+
if (seed.source === "self") return readFile9(seed.target);
|
|
14191
14205
|
if (seed.source.startsWith("seed:")) {
|
|
14192
|
-
const tmpl =
|
|
14206
|
+
const tmpl = readFile9(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
|
|
14193
14207
|
return tmpl == null ? null : renderSeed(tmpl, vars);
|
|
14194
14208
|
}
|
|
14195
14209
|
return null;
|
|
14196
14210
|
}
|
|
14197
|
-
function resolveSeedWriteContent(seed, vars,
|
|
14211
|
+
function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
|
|
14198
14212
|
if (!seed.managedBlock) {
|
|
14199
|
-
return { ok: true, content: resolveSeedContent(seed, vars,
|
|
14213
|
+
return { ok: true, content: resolveSeedContent(seed, vars, readFile9), managed: seed.source === "managed-block" };
|
|
14200
14214
|
}
|
|
14201
|
-
const base = remoteContent ?? resolveSeedContent(seed, vars,
|
|
14215
|
+
const base = remoteContent ?? resolveSeedContent(seed, vars, readFile9);
|
|
14202
14216
|
if (base == null) return { ok: true, content: null, managed: true };
|
|
14203
14217
|
const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
|
|
14204
|
-
const desired = resolveSeedContent(blockSeed, vars,
|
|
14218
|
+
const desired = resolveSeedContent(blockSeed, vars, readFile9);
|
|
14205
14219
|
if (desired == null) return { ok: true, content: null, managed: true };
|
|
14206
14220
|
const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
|
|
14207
14221
|
return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
|
|
@@ -15502,10 +15516,10 @@ var rollout_plan_default = {
|
|
|
15502
15516
|
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)."
|
|
15503
15517
|
},
|
|
15504
15518
|
baseline: {
|
|
15505
|
-
version: "4.3.
|
|
15506
|
-
tag: "v4.3.
|
|
15507
|
-
commit: "
|
|
15508
|
-
npm: "@mutmutco/cli@4.3.
|
|
15519
|
+
version: "4.3.21",
|
|
15520
|
+
tag: "v4.3.21",
|
|
15521
|
+
commit: "f55fc2217cd3",
|
|
15522
|
+
npm: "@mutmutco/cli@4.3.21"
|
|
15509
15523
|
},
|
|
15510
15524
|
exitCriterion: "fleet-n-of-n",
|
|
15511
15525
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15522,14 +15536,14 @@ var rollout_plan_default = {
|
|
|
15522
15536
|
repo: "mutmutco/mmi-hub",
|
|
15523
15537
|
role: "canary",
|
|
15524
15538
|
schedule: "train",
|
|
15525
|
-
v3Target: "v4.3.
|
|
15539
|
+
v3Target: "v4.3.21"
|
|
15526
15540
|
}
|
|
15527
15541
|
],
|
|
15528
15542
|
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.",
|
|
15529
15543
|
rollback: {
|
|
15530
15544
|
independent: true,
|
|
15531
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15532
|
-
v3Target: "v4.3.
|
|
15545
|
+
mechanism: "npm dist-tag latest -> 4.3.21 and redeploy the Hub Lambda from tag v4.3.21 (f55fc2217cd3); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15546
|
+
v3Target: "v4.3.21 (@mutmutco/cli@4.3.21, tag commit f55fc2217cd3 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15533
15547
|
}
|
|
15534
15548
|
},
|
|
15535
15549
|
{
|
|
@@ -23527,12 +23541,12 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
|
|
|
23527
23541
|
const contexts = sortedUnique(registryContexts ?? committedContexts);
|
|
23528
23542
|
const explicitBranches = Array.isArray(meta?.requiredCheckBranches) && meta.requiredCheckBranches.length > 0 ? resolveRequiredCheckBranches(meta, repo) : null;
|
|
23529
23543
|
const committedStrict = rulesetStrictPolicy(committedPayload);
|
|
23530
|
-
let apiPayload =
|
|
23544
|
+
let apiPayload = registryContexts == null ? committedPayload : patchRulesetRequiredContexts(committedPayload, contexts);
|
|
23531
23545
|
if (explicitBranches) apiPayload = patchRulesetBranchIncludes(apiPayload, explicitBranches);
|
|
23532
23546
|
const branchIncludes = rulesetBranchIncludes(apiPayload);
|
|
23533
23547
|
const authoritativeFilePayload = {
|
|
23534
23548
|
...filePayload,
|
|
23535
|
-
...registryContexts == null
|
|
23549
|
+
...registryContexts == null ? {} : { rules: apiPayload.rules },
|
|
23536
23550
|
...explicitBranches == null ? {} : { conditions: apiPayload.conditions }
|
|
23537
23551
|
};
|
|
23538
23552
|
return {
|
|
@@ -23554,7 +23568,7 @@ function sameContexts(left, right) {
|
|
|
23554
23568
|
function resolveProductRulesetReconcilePlan(input) {
|
|
23555
23569
|
const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
|
|
23556
23570
|
const liveNeedsBranchConvergence = input.liveBranchIncludes !== void 0 && input.authorityBranchIncludes !== void 0 && !sameContexts(input.liveBranchIncludes, input.authorityBranchIncludes);
|
|
23557
|
-
const liveNeedsStrictConvergence = input.liveStrict
|
|
23571
|
+
const liveNeedsStrictConvergence = input.liveStrict !== void 0 && input.authorityStrict !== void 0 && input.liveStrict !== input.authorityStrict;
|
|
23558
23572
|
const liveIsActive = input.liveEnforcement === "active";
|
|
23559
23573
|
if (liveIsActive && !liveNeedsContextConvergence && !liveNeedsBranchConvergence && !liveNeedsStrictConvergence) {
|
|
23560
23574
|
return { shouldActivate: false, targetEnforcement: "active" };
|
|
@@ -23765,12 +23779,12 @@ async function auditRepoCi(repo, deps) {
|
|
|
23765
23779
|
const liveContextsAligned = sameContexts(liveContexts, authoritativeRuleset.contexts);
|
|
23766
23780
|
const fileBranchesAligned = sameContexts(authoritativeRuleset.committedBranchIncludes, authoritativeRuleset.branchIncludes);
|
|
23767
23781
|
const liveBranchesAligned = sameContexts(liveBranchIncludes, authoritativeRuleset.branchIncludes);
|
|
23768
|
-
const strictAligned = authoritativeRuleset.committedStrict
|
|
23782
|
+
const strictAligned = authoritativeRuleset.committedStrict === liveStrict;
|
|
23769
23783
|
const aligned = fileContextsAligned && liveContextsAligned && fileBranchesAligned && liveBranchesAligned && strictAligned;
|
|
23770
23784
|
checks.push({
|
|
23771
23785
|
ok: aligned,
|
|
23772
23786
|
label: RULESET_REFERENCE_MATCH_LABEL,
|
|
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
|
|
23787
|
+
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 differs from committed policy \u2014 ${PRODUCT_RULESET_REF} declares ${authoritativeRuleset.committedStrict}, live ${PRODUCT_RULESET_NAME} has ${liveStrict}`),
|
|
23774
23788
|
remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
|
|
23775
23789
|
});
|
|
23776
23790
|
}
|
|
@@ -24360,6 +24374,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24360
24374
|
liveBranchIncludes,
|
|
24361
24375
|
authorityBranchIncludes: authority.branchIncludes,
|
|
24362
24376
|
liveStrict: live == null ? void 0 : rulesetStrictPolicy(live),
|
|
24377
|
+
authorityStrict: authority.committedStrict,
|
|
24363
24378
|
gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
|
|
24364
24379
|
unsafeContexts: unsafe
|
|
24365
24380
|
});
|
|
@@ -27167,11 +27182,6 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
27167
27182
|
label: "product required-check ruleset enforcement active",
|
|
27168
27183
|
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
|
|
27169
27184
|
});
|
|
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
|
-
});
|
|
27175
27185
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
27176
27186
|
const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
|
|
27177
27187
|
checks.push({
|
|
@@ -27225,6 +27235,100 @@ function renderBootstrapVerifyReport(report) {
|
|
|
27225
27235
|
return lines2.join("\n");
|
|
27226
27236
|
}
|
|
27227
27237
|
|
|
27238
|
+
// src/bootstrap-seed-delivery.ts
|
|
27239
|
+
var import_node_crypto9 = require("node:crypto");
|
|
27240
|
+
var SHA = /^[a-f0-9]{40}$/;
|
|
27241
|
+
async function readSeedFile(repo, target, ref, client) {
|
|
27242
|
+
try {
|
|
27243
|
+
const file = await client.rest(
|
|
27244
|
+
"GET",
|
|
27245
|
+
`repos/${repo}/contents/${target.split("/").map(encodeURIComponent).join("/")}?ref=${encodeURIComponent(ref)}`
|
|
27246
|
+
);
|
|
27247
|
+
if (!file?.sha || !SHA.test(file.sha) || file.encoding !== "base64" || typeof file.content !== "string") {
|
|
27248
|
+
throw new Error("bootstrap apply: scoped seed file is unreadable");
|
|
27249
|
+
}
|
|
27250
|
+
return { sha: file.sha, content: decodeGitHubContents(file.content) };
|
|
27251
|
+
} catch (e) {
|
|
27252
|
+
if (e instanceof GitHubApiError && e.status === 404) return null;
|
|
27253
|
+
throw e;
|
|
27254
|
+
}
|
|
27255
|
+
}
|
|
27256
|
+
async function readTargetedSeedFiles(repo, plan, target, ref, client) {
|
|
27257
|
+
const base = await readSeedFile(repo, target, plan.baseSha, client);
|
|
27258
|
+
const candidate = ref === plan.baseSha ? base : await readSeedFile(repo, target, ref, client);
|
|
27259
|
+
return { base, candidate };
|
|
27260
|
+
}
|
|
27261
|
+
async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, client) {
|
|
27262
|
+
const [rules, base] = await Promise.all([
|
|
27263
|
+
client.rest("GET", `repos/${repo}/rules/branches/${encodeURIComponent(baseBranch)}`),
|
|
27264
|
+
client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(baseBranch)}`)
|
|
27265
|
+
]);
|
|
27266
|
+
if (!Array.isArray(rules) || rules.some((rule) => !rule || typeof rule.type !== "string" || !rule.type)) {
|
|
27267
|
+
throw new Error("bootstrap apply: base branch protection is unreadable");
|
|
27268
|
+
}
|
|
27269
|
+
const baseSha = base?.object?.sha;
|
|
27270
|
+
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
27271
|
+
const identity = (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
27272
|
+
return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
|
|
27273
|
+
}
|
|
27274
|
+
async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
|
|
27275
|
+
let ref;
|
|
27276
|
+
try {
|
|
27277
|
+
ref = await client.rest("GET", `repos/${repo}/git/ref/heads/${encodeURIComponent(plan.ref)}`);
|
|
27278
|
+
} catch (e) {
|
|
27279
|
+
if (allowMissing && e instanceof GitHubApiError && e.status === 404) return null;
|
|
27280
|
+
throw e;
|
|
27281
|
+
}
|
|
27282
|
+
const head = ref?.object?.sha;
|
|
27283
|
+
if (!head || !SHA.test(head)) throw new Error("bootstrap apply: scoped seed head is unreadable");
|
|
27284
|
+
if (head === plan.baseSha) return head;
|
|
27285
|
+
const diff = await client.rest("GET", `repos/${repo}/compare/${plan.baseSha}...${head}`);
|
|
27286
|
+
if (diff?.status !== "ahead" || diff.merge_base_commit?.sha !== plan.baseSha || !Array.isArray(diff.files) || diff.files.length !== 1 || diff.files[0].filename !== target || diff.files[0].previous_filename !== void 0 || !["added", "modified"].includes(diff.files[0].status ?? "")) {
|
|
27287
|
+
throw new Error("bootstrap apply: scoped seed branch contains changes outside the requested target or base");
|
|
27288
|
+
}
|
|
27289
|
+
return head;
|
|
27290
|
+
}
|
|
27291
|
+
async function prepareTargetedSeedBranch(repo, plan, target, client, gh) {
|
|
27292
|
+
const candidates = await client.rest(
|
|
27293
|
+
"GET",
|
|
27294
|
+
`repos/${repo}/pulls?state=open&head=${encodeURIComponent(`${repo.split("/")[0]}:${plan.branch}`)}&base=${encodeURIComponent(plan.baseBranch)}&per_page=2`
|
|
27295
|
+
);
|
|
27296
|
+
if (!Array.isArray(candidates) || candidates.length > 1) throw new Error("bootstrap apply: scoped seed PR identity is unreadable");
|
|
27297
|
+
for (const pr of candidates) {
|
|
27298
|
+
if (!Number.isInteger(pr?.number) || pr.number <= 0 || pr.head?.ref !== plan.branch || pr.base?.ref !== plan.baseBranch) {
|
|
27299
|
+
throw new Error("bootstrap apply: scoped seed PR identity is unreadable");
|
|
27300
|
+
}
|
|
27301
|
+
await gh(["pr", "merge", String(pr.number), "--repo", repo, "--disable-auto"]);
|
|
27302
|
+
const after = await client.rest("GET", `repos/${repo}/pulls/${pr.number}`);
|
|
27303
|
+
if (after?.auto_merge !== null) throw new Error("bootstrap apply: scoped seed PR auto-merge was not disabled");
|
|
27304
|
+
}
|
|
27305
|
+
const existing = await verifyTargetedSeedBranch(repo, plan, target, client, true);
|
|
27306
|
+
if (existing) return;
|
|
27307
|
+
try {
|
|
27308
|
+
await client.rest("POST", `repos/${repo}/git/refs`, { body: { ref: `refs/heads/${plan.branch}`, sha: plan.baseSha } });
|
|
27309
|
+
} catch (e) {
|
|
27310
|
+
if (!(e instanceof GitHubApiError) || e.status !== 422) throw e;
|
|
27311
|
+
}
|
|
27312
|
+
await verifyTargetedSeedBranch(repo, plan, target, client);
|
|
27313
|
+
}
|
|
27314
|
+
async function enableTargetedSeedAutoMerge(repo, number, plan, target, expected, client, gh) {
|
|
27315
|
+
const head = await verifyTargetedSeedBranch(repo, plan, target, client);
|
|
27316
|
+
const pr = await client.rest("GET", `repos/${repo}/pulls/${number}`);
|
|
27317
|
+
if (pr?.state !== "open" || pr.head?.ref !== plan.branch || pr.head?.sha !== head || pr.head.repo?.full_name?.toLowerCase() !== repo.toLowerCase() || pr.base?.ref !== plan.baseBranch || pr.base.sha !== plan.baseSha || pr.base.repo?.full_name?.toLowerCase() !== repo.toLowerCase() || pr.changed_files !== 1) throw new Error("bootstrap apply: scoped seed PR identity or diff changed");
|
|
27318
|
+
const files = await client.rest(
|
|
27319
|
+
"GET",
|
|
27320
|
+
`repos/${repo}/pulls/${number}/files?per_page=2`
|
|
27321
|
+
);
|
|
27322
|
+
if (!Array.isArray(files) || files.length !== 1 || files[0]?.filename !== target || files[0].previous_filename !== void 0) {
|
|
27323
|
+
throw new Error("bootstrap apply: scoped seed PR contains changes outside the requested target");
|
|
27324
|
+
}
|
|
27325
|
+
const file = await readSeedFile(repo, target, head, client);
|
|
27326
|
+
if (file?.content !== expected) {
|
|
27327
|
+
throw new Error("bootstrap apply: scoped seed content differs from this invocation");
|
|
27328
|
+
}
|
|
27329
|
+
await gh(["pr", "merge", String(number), "--repo", repo, "--auto", "--squash", "--match-head-commit", head]);
|
|
27330
|
+
}
|
|
27331
|
+
|
|
27228
27332
|
// src/bootstrap-commands.ts
|
|
27229
27333
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
27230
27334
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -27537,7 +27641,7 @@ function registerBootstrapCommands(program3) {
|
|
|
27537
27641
|
}
|
|
27538
27642
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
27539
27643
|
const gh = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
27540
|
-
const
|
|
27644
|
+
const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
|
|
27541
27645
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
27542
27646
|
const putSeed = async (target, content, ref, sha) => {
|
|
27543
27647
|
const tmp = (0, import_node_path25.join)((0, import_node_os14.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
@@ -27632,14 +27736,23 @@ function registerBootstrapCommands(program3) {
|
|
|
27632
27736
|
});
|
|
27633
27737
|
}
|
|
27634
27738
|
let seedPlan = { mode: "direct", ref: baseBranch, reason: "dry-run (no protection probe)" };
|
|
27739
|
+
let scopedPlan;
|
|
27740
|
+
let scopedContent;
|
|
27635
27741
|
let seededToBranch = 0;
|
|
27636
|
-
if (
|
|
27742
|
+
if (onlyTarget) {
|
|
27743
|
+
scopedPlan = await targetedSeedPlan(repo, slug, onlyTarget, baseBranch, seedSource.sha, controlClient);
|
|
27744
|
+
seedPlan = scopedPlan;
|
|
27745
|
+
if (o.execute && scopedPlan.branch) {
|
|
27746
|
+
await prepareTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient, gh);
|
|
27747
|
+
}
|
|
27748
|
+
} else if (o.execute) {
|
|
27637
27749
|
let branchRules = [];
|
|
27638
27750
|
try {
|
|
27639
27751
|
branchRules = JSON.parse((await gh(["api", `repos/${repo}/rules/branches/${baseBranch}`])).stdout || "[]");
|
|
27640
27752
|
} catch {
|
|
27641
|
-
|
|
27753
|
+
throw new Error("bootstrap apply: base branch protection is unreadable");
|
|
27642
27754
|
}
|
|
27755
|
+
if (!Array.isArray(branchRules)) throw new Error("bootstrap apply: base branch protection is unreadable");
|
|
27643
27756
|
seedPlan = planSeedDelivery(branchRules, slug, baseBranch);
|
|
27644
27757
|
if (seedPlan.branch) {
|
|
27645
27758
|
const headSha = (await gh(["api", `repos/${repo}/git/ref/heads/${baseBranch}`, "--jq", ".object.sha"])).stdout.trim();
|
|
@@ -27651,6 +27764,7 @@ function registerBootstrapCommands(program3) {
|
|
|
27651
27764
|
}
|
|
27652
27765
|
}
|
|
27653
27766
|
const docsForIndex = [];
|
|
27767
|
+
const seedReadRef = !o.execute && scopedPlan ? scopedPlan.baseSha : seedPlan.ref;
|
|
27654
27768
|
for (const seed of seedsToApply) {
|
|
27655
27769
|
if (!seed.classes.includes(o.class)) continue;
|
|
27656
27770
|
if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
|
|
@@ -27664,8 +27778,20 @@ function registerBootstrapCommands(program3) {
|
|
|
27664
27778
|
let exists = false;
|
|
27665
27779
|
let sha;
|
|
27666
27780
|
let remoteContent = null;
|
|
27667
|
-
|
|
27668
|
-
|
|
27781
|
+
let baseContent = null;
|
|
27782
|
+
const preserveRuleset = onlyTarget === PRODUCT_RULESET_PATH;
|
|
27783
|
+
if (scopedPlan) {
|
|
27784
|
+
try {
|
|
27785
|
+
const files = await readTargetedSeedFiles(repo, scopedPlan, resolved.target, seedReadRef, controlClient);
|
|
27786
|
+
baseContent = files.base?.content ?? null;
|
|
27787
|
+
exists = files.candidate !== null;
|
|
27788
|
+
sha = files.candidate?.sha;
|
|
27789
|
+
remoteContent = files.candidate?.content ?? null;
|
|
27790
|
+
} catch (e) {
|
|
27791
|
+
return failGraceful(`bootstrap apply: cannot read scoped seed file; no replacement made: ${e.message}`);
|
|
27792
|
+
}
|
|
27793
|
+
} else try {
|
|
27794
|
+
const r = await gh(["api", `repos/${repo}/contents/${enc(resolved.target)}?ref=${seedReadRef}`]);
|
|
27669
27795
|
exists = true;
|
|
27670
27796
|
try {
|
|
27671
27797
|
const parsed = JSON.parse(r.stdout);
|
|
@@ -27678,23 +27804,34 @@ function registerBootstrapCommands(program3) {
|
|
|
27678
27804
|
} catch {
|
|
27679
27805
|
exists = false;
|
|
27680
27806
|
}
|
|
27681
|
-
const planned = planSeedAction(resolved, exists);
|
|
27807
|
+
const planned = planSeedAction(resolved, scopedPlan ? baseContent !== null : exists);
|
|
27808
|
+
if (scopedPlan && planned.action !== "skip") planned.action = exists ? "update" : "create";
|
|
27682
27809
|
const isLegacyBlock = resolved.source === "managed-block";
|
|
27683
27810
|
let content = null;
|
|
27684
27811
|
let isManaged = isLegacyBlock || resolved.managedBlock != null;
|
|
27812
|
+
const preservedContent = scopedPlan ? baseContent : remoteContent;
|
|
27685
27813
|
if (planned.action === "create" || planned.action === "update") {
|
|
27686
27814
|
if (isLegacyBlock) {
|
|
27687
|
-
content = upsertManagedGitignoreBlock(
|
|
27815
|
+
content = upsertManagedGitignoreBlock(preservedContent).content;
|
|
27688
27816
|
} else {
|
|
27689
|
-
const writeContent = resolveSeedWriteContent(resolved, vars,
|
|
27817
|
+
const writeContent = resolveSeedWriteContent(resolved, vars, readFile9, preservedContent);
|
|
27690
27818
|
if (!writeContent.ok) {
|
|
27691
27819
|
return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
27692
27820
|
}
|
|
27693
27821
|
content = writeContent.content;
|
|
27822
|
+
if (preserveRuleset) {
|
|
27823
|
+
if (content === null) return fail("bootstrap apply: canonical product ruleset seed is unreadable");
|
|
27824
|
+
try {
|
|
27825
|
+
content = reseedProductRulesetStrictness(baseContent, content);
|
|
27826
|
+
} catch (e) {
|
|
27827
|
+
return fail(`bootstrap apply: product ruleset preservation refused: ${e.message}`);
|
|
27828
|
+
}
|
|
27829
|
+
}
|
|
27694
27830
|
isManaged = writeContent.managed;
|
|
27695
27831
|
}
|
|
27696
27832
|
}
|
|
27697
27833
|
const action = reconcileSeedAction(planned, content, isManaged, remoteContent);
|
|
27834
|
+
if (onlyTarget && content !== null) scopedContent = content;
|
|
27698
27835
|
actions.push(action);
|
|
27699
27836
|
const docBody = content ?? remoteContent;
|
|
27700
27837
|
if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
|
|
@@ -27729,7 +27866,8 @@ function registerBootstrapCommands(program3) {
|
|
|
27729
27866
|
}
|
|
27730
27867
|
}
|
|
27731
27868
|
let seedPrUrl;
|
|
27732
|
-
|
|
27869
|
+
const scopedHead = o.execute && scopedPlan?.branch ? await verifyTargetedSeedBranch(repo, scopedPlan, onlyTarget, controlClient) : null;
|
|
27870
|
+
if (o.execute && seedPlan.mode === "pr" && seedPlan.branch && (seededToBranch > 0 || scopedContent !== void 0 && scopedHead !== null && scopedHead !== scopedPlan?.baseSha)) {
|
|
27733
27871
|
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-F", `delete_branch_on_merge=${MANAGED_DELETE_BRANCH_ON_MERGE}`]).catch(() => {
|
|
27734
27872
|
});
|
|
27735
27873
|
const openPrs = await gh(["pr", "list", "--repo", repo, "--head", seedPlan.branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
|
|
@@ -27758,7 +27896,12 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
27758
27896
|
seedPrUrl = created.url;
|
|
27759
27897
|
}
|
|
27760
27898
|
let autoMergeEnabled = true;
|
|
27761
|
-
|
|
27899
|
+
if (scopedPlan) {
|
|
27900
|
+
if (scopedContent === void 0) throw new Error("bootstrap apply: scoped seed content is unresolved");
|
|
27901
|
+
const number = Number(seedPrUrl.split("/").pop());
|
|
27902
|
+
if (!Number.isInteger(number) || number <= 0) throw new Error("bootstrap apply: scoped seed PR number is unreadable");
|
|
27903
|
+
await enableTargetedSeedAutoMerge(repo, number, scopedPlan, onlyTarget, scopedContent, controlClient, gh);
|
|
27904
|
+
} else await gh(["pr", "merge", seedPrUrl, "--repo", repo, "--auto", "--squash"]).catch((e) => {
|
|
27762
27905
|
const message2 = String(e.message ?? "");
|
|
27763
27906
|
if (/already/i.test(message2)) return;
|
|
27764
27907
|
if (/clean status|enablePullRequestAutoMerge/i.test(message2)) {
|
|
@@ -27823,7 +27966,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
27823
27966
|
}
|
|
27824
27967
|
const rulesetSeed = repo.toLowerCase() === "mutmutco/mmi-hub" ? void 0 : manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
|
|
27825
27968
|
if (rulesetSeed) {
|
|
27826
|
-
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars,
|
|
27969
|
+
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile9);
|
|
27827
27970
|
if (rulesetContent) {
|
|
27828
27971
|
try {
|
|
27829
27972
|
const client = controlClient;
|
|
@@ -27941,7 +28084,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
27941
28084
|
deployFactsRead: controlDeployFacts !== null
|
|
27942
28085
|
});
|
|
27943
28086
|
}
|
|
27944
|
-
if (o.json) console.log(JSON.stringify({ repo, class: o.class, sourceSha: seedSource.sha, only: onlyTarget || null, execute: o.execute, seedDelivery: seedPlan.mode, seedPrUrl, actions, controlPlane: controlRows, applied, ddbWrites }, null, 2));
|
|
28087
|
+
if (o.json) console.log(JSON.stringify({ repo, class: o.class, sourceSha: seedSource.sha, only: onlyTarget || null, execute: o.execute, seedDelivery: seedPlan.mode, seedBranch: seedPlan.branch, seedBaseSha: scopedPlan?.baseSha, seedPrUrl, actions, controlPlane: controlRows, applied, ddbWrites }, null, 2));
|
|
27945
28088
|
else {
|
|
27946
28089
|
console.log(renderSeedPlan(actions));
|
|
27947
28090
|
if (controlRows.length) console.log(`
|
|
@@ -27968,7 +28111,7 @@ LIVE apply to ${repo}:
|
|
|
27968
28111
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
27969
28112
|
if (!seed.managedBlock && !(0, import_node_fs26.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
27970
28113
|
const hubContent = seed.managedBlock ? null : (0, import_node_fs26.readFileSync)(seed.target, "utf8");
|
|
27971
|
-
const
|
|
28114
|
+
const readSeedFile2 = (path2) => (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null;
|
|
27972
28115
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
27973
28116
|
const cfg = await loadConfig();
|
|
27974
28117
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -28045,14 +28188,14 @@ LIVE apply to ${repo}:
|
|
|
28045
28188
|
const project2 = projects.find((p) => (p.repos ?? []).some((repo) => repo.toLowerCase() === r.repo.toLowerCase()));
|
|
28046
28189
|
const track = resolveReleaseTrack(project2, void 0, r.repo);
|
|
28047
28190
|
const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass, track, project2?.requiredCheckBranches);
|
|
28048
|
-
const resolved = resolveSeedWriteContent(seed, vars,
|
|
28191
|
+
const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
|
|
28049
28192
|
if (!resolved.ok || resolved.content == null) {
|
|
28050
28193
|
return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.ok ? "rendered no content" : resolved.reason} \u2014 refusing an incomplete per-repo render`);
|
|
28051
28194
|
}
|
|
28052
28195
|
desired = resolved.content;
|
|
28053
28196
|
} else if (seed.managedBlock) {
|
|
28054
28197
|
const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
|
|
28055
|
-
const resolved = resolveSeedWriteContent(seed, vars,
|
|
28198
|
+
const resolved = resolveSeedWriteContent(seed, vars, readSeedFile2, content);
|
|
28056
28199
|
if (!resolved.ok) {
|
|
28057
28200
|
return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
28058
28201
|
}
|
|
@@ -31891,8 +32034,7 @@ var LOOP_PLAYBOOKS = {
|
|
|
31891
32034
|
{ label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
|
|
31892
32035
|
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
31893
32036
|
{ label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
31894
|
-
{ label: "
|
|
31895
|
-
{ label: "Land to development (waits for checks itself and demands the review verdict)", command: "mmi-cli devops pr land <PR-number>" },
|
|
32037
|
+
{ label: "Land to development (waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" },
|
|
31896
32038
|
{ label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
|
|
31897
32039
|
// #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
|
|
31898
32040
|
{ label: "Learning reports are fire-and-forget (file, then return to the current task \u2014 never claim/poll/duplicate the learning issue)", command: 'mmi-cli learning report --title "<one-line>" --body "<what hurt>"' }
|
|
@@ -31913,14 +32055,14 @@ var LOOP_PLAYBOOKS = {
|
|
|
31913
32055
|
steps: [
|
|
31914
32056
|
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
31915
32057
|
{ label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
31916
|
-
{ label: "Land the PR (merge to development \u2014 waits for checks itself
|
|
32058
|
+
{ label: "Land the PR (merge to development \u2014 waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" }
|
|
31917
32059
|
]
|
|
31918
32060
|
},
|
|
31919
32061
|
"hotfix": {
|
|
31920
32062
|
title: "Hotfix",
|
|
31921
32063
|
steps: [
|
|
31922
32064
|
{ label: "Create the main-base hotfix PR from every already-merged fix this cycle carries", command: "mmi-cli devops hotfix start --from <pr#|sha>[,<pr#|sha>...]" },
|
|
31923
|
-
{ label: "Merge the main-base PR (waits for checks itself
|
|
32065
|
+
{ label: "Merge the main-base PR (waits for checks itself)", command: "mmi-cli devops pr merge <PR-number> --squash" },
|
|
31924
32066
|
{ label: "After the PR is merged, run the gated release", command: "mmi-cli devops hotfix release <vX.Y.Z> --carries <pr#|sha>[,<pr#|sha>...]" }
|
|
31925
32067
|
]
|
|
31926
32068
|
}
|
|
@@ -32193,7 +32335,7 @@ function parseOriginRepo(remoteUrl) {
|
|
|
32193
32335
|
|
|
32194
32336
|
// src/issue-commands.ts
|
|
32195
32337
|
var import_node_fs31 = require("node:fs");
|
|
32196
|
-
var
|
|
32338
|
+
var import_node_crypto10 = require("node:crypto");
|
|
32197
32339
|
|
|
32198
32340
|
// src/issue-body.ts
|
|
32199
32341
|
var import_node_os16 = require("node:os");
|
|
@@ -32723,7 +32865,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
32723
32865
|
const identity = `${spec.type}
|
|
32724
32866
|
${spec.title.trim()}
|
|
32725
32867
|
${spec.body ?? ""}`;
|
|
32726
|
-
const hash = (0,
|
|
32868
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
32727
32869
|
return `${batchKey}:${hash}`;
|
|
32728
32870
|
}
|
|
32729
32871
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -35768,14 +35910,14 @@ function registerStageCommands(program3) {
|
|
|
35768
35910
|
}
|
|
35769
35911
|
|
|
35770
35912
|
// src/tenant-artifact.ts
|
|
35771
|
-
var
|
|
35913
|
+
var import_node_crypto11 = require("node:crypto");
|
|
35772
35914
|
var import_node_fs35 = require("node:fs");
|
|
35773
35915
|
var import_promises6 = require("node:fs/promises");
|
|
35774
35916
|
var import_node_path32 = require("node:path");
|
|
35775
35917
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
35776
35918
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
35777
35919
|
async function sha256File(path2) {
|
|
35778
|
-
const hash = (0,
|
|
35920
|
+
const hash = (0, import_node_crypto11.createHash)("sha256");
|
|
35779
35921
|
for await (const chunk of (0, import_node_fs35.createReadStream)(path2)) hash.update(chunk);
|
|
35780
35922
|
return hash.digest("hex");
|
|
35781
35923
|
}
|
|
@@ -35940,7 +36082,7 @@ function renderVerifySecrets(body) {
|
|
|
35940
36082
|
// src/command-register-collaboration.ts
|
|
35941
36083
|
var import_node_child_process16 = require("node:child_process");
|
|
35942
36084
|
var import_node_fs43 = require("node:fs");
|
|
35943
|
-
var
|
|
36085
|
+
var import_promises7 = require("node:fs/promises");
|
|
35944
36086
|
|
|
35945
36087
|
// src/session-runtime.ts
|
|
35946
36088
|
var WIN32_TRAMPOLINE = "const{spawn}=require('node:child_process');const a=JSON.parse(process.argv[1]);const c=spawn(a.cmd,a.args,{stdio:'ignore',windowsHide:true,cwd:a.cwd});c.on('exit',x=>process.exit(x??0));c.on('error',()=>process.exit(1));";
|
|
@@ -35959,7 +36101,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
35959
36101
|
}
|
|
35960
36102
|
|
|
35961
36103
|
// src/command-register-collaboration.ts
|
|
35962
|
-
var
|
|
36104
|
+
var import_node_path40 = require("node:path");
|
|
35963
36105
|
|
|
35964
36106
|
// src/attach-to-project.ts
|
|
35965
36107
|
function boardAttachRateLimitedReceipt(resetEpochSeconds) {
|
|
@@ -36145,6 +36287,14 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
36145
36287
|
};
|
|
36146
36288
|
}
|
|
36147
36289
|
}
|
|
36290
|
+
const queue = await deps.queueMerge?.(prNumber, repo);
|
|
36291
|
+
if (queue) return {
|
|
36292
|
+
...base,
|
|
36293
|
+
queue,
|
|
36294
|
+
status: queue.state === "merged" ? "merged" : "failed",
|
|
36295
|
+
mergeStatus: queue.state === "merged" ? "merged" : "failed",
|
|
36296
|
+
...queue.state === "merged" ? {} : { error: queue.detail }
|
|
36297
|
+
};
|
|
36148
36298
|
const ciPolicy = await deps.resolveCiPolicy(repo);
|
|
36149
36299
|
base.ciPolicy = ciPolicy;
|
|
36150
36300
|
const checksWaitError = (checksWait) => {
|
|
@@ -36762,8 +36912,8 @@ function annotateChangeMeaning(changed, policy, read) {
|
|
|
36762
36912
|
function isMeaningfulRow(file) {
|
|
36763
36913
|
return file.meaningful !== false;
|
|
36764
36914
|
}
|
|
36765
|
-
function loadPolicy(root,
|
|
36766
|
-
const raw =
|
|
36915
|
+
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
36916
|
+
const raw = readFile9((0, import_node_path33.join)(root, POLICY_FILE));
|
|
36767
36917
|
if (raw == null) return { mandatory: [], declared: false };
|
|
36768
36918
|
return parsePolicy(raw, POLICY_FILE);
|
|
36769
36919
|
}
|
|
@@ -37454,9 +37604,287 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
37454
37604
|
};
|
|
37455
37605
|
}
|
|
37456
37606
|
|
|
37457
|
-
// src/
|
|
37607
|
+
// src/review-verdict.ts
|
|
37608
|
+
var import_node_child_process14 = require("node:child_process");
|
|
37458
37609
|
var import_node_fs38 = require("node:fs");
|
|
37610
|
+
var import_node_os19 = require("node:os");
|
|
37459
37611
|
var import_node_path35 = require("node:path");
|
|
37612
|
+
var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
|
|
37613
|
+
var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
|
|
37614
|
+
function isReviewVerdict(value) {
|
|
37615
|
+
return typeof value === "string" && REVIEW_VERDICTS.includes(value);
|
|
37616
|
+
}
|
|
37617
|
+
function renderReviewVerdictComment(input) {
|
|
37618
|
+
const payload = {
|
|
37619
|
+
v: 1,
|
|
37620
|
+
patch: input.patch,
|
|
37621
|
+
head: input.head,
|
|
37622
|
+
verdict: input.verdict,
|
|
37623
|
+
scope: input.scope,
|
|
37624
|
+
risk: input.risk,
|
|
37625
|
+
unverified: input.unverified,
|
|
37626
|
+
reviewer: input.reviewer
|
|
37627
|
+
};
|
|
37628
|
+
const findings = input.findings?.trim();
|
|
37629
|
+
return `${REVIEW_VERDICT_MARKER}
|
|
37630
|
+
\`\`\`json
|
|
37631
|
+
${JSON.stringify(payload, null, 2)}
|
|
37632
|
+
\`\`\`
|
|
37633
|
+
${findings ? `
|
|
37634
|
+
${findings}
|
|
37635
|
+
` : ""}`;
|
|
37636
|
+
}
|
|
37637
|
+
function isReviewVerdictComment(body) {
|
|
37638
|
+
return body.trimStart().startsWith(REVIEW_VERDICT_MARKER);
|
|
37639
|
+
}
|
|
37640
|
+
var FENCE_RE = /^(?:`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n(?:`{3,}|~{3,})\s*$/m;
|
|
37641
|
+
function parseReviewVerdictComment(body) {
|
|
37642
|
+
if (!isReviewVerdictComment(body)) return void 0;
|
|
37643
|
+
const rest = body.trimStart().slice(REVIEW_VERDICT_MARKER.length);
|
|
37644
|
+
const fence = FENCE_RE.exec(rest);
|
|
37645
|
+
if (!fence) return void 0;
|
|
37646
|
+
let parsed;
|
|
37647
|
+
try {
|
|
37648
|
+
parsed = JSON.parse(fence[1]);
|
|
37649
|
+
} catch {
|
|
37650
|
+
return void 0;
|
|
37651
|
+
}
|
|
37652
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
37653
|
+
const p = parsed;
|
|
37654
|
+
if (p.v !== 1 || !isReviewVerdict(p.verdict)) return void 0;
|
|
37655
|
+
if (typeof p.patch !== "string" || !/^[0-9a-f]{40}$/.test(p.patch)) return void 0;
|
|
37656
|
+
if (typeof p.head !== "string" || typeof p.scope !== "string" || typeof p.risk !== "string" || typeof p.reviewer !== "string") return void 0;
|
|
37657
|
+
const unverified = Array.isArray(p.unverified) ? p.unverified.filter((u) => typeof u === "string") : [];
|
|
37658
|
+
return { v: 1, patch: p.patch, head: p.head, verdict: p.verdict, scope: p.scope, risk: p.risk, unverified, reviewer: p.reviewer };
|
|
37659
|
+
}
|
|
37660
|
+
function latestReviewComment(comments) {
|
|
37661
|
+
let latest;
|
|
37662
|
+
for (const c of comments) {
|
|
37663
|
+
if (!isReviewVerdictComment(c.body)) continue;
|
|
37664
|
+
if (!latest || c.createdAt > latest.createdAt || c.createdAt === latest.createdAt && (c.id ?? 0) >= (latest.id ?? 0)) latest = c;
|
|
37665
|
+
}
|
|
37666
|
+
return latest;
|
|
37667
|
+
}
|
|
37668
|
+
function evaluateReviewVerdict(comments, currentPatchId) {
|
|
37669
|
+
const latest = latestReviewComment(comments);
|
|
37670
|
+
if (!latest) return { ok: false, reason: "none" };
|
|
37671
|
+
const payload = parseReviewVerdictComment(latest.body);
|
|
37672
|
+
if (!payload) return { ok: false, reason: "malformed" };
|
|
37673
|
+
if (payload.verdict !== "PROCEED") return { ok: false, reason: "not-proceed", verdict: payload.verdict, patch: payload.patch };
|
|
37674
|
+
if (payload.patch !== currentPatchId) return { ok: false, reason: "stale", verdict: payload.verdict, patch: payload.patch };
|
|
37675
|
+
return { ok: true, reason: "proceed", verdict: payload.verdict, patch: payload.patch };
|
|
37676
|
+
}
|
|
37677
|
+
function evaluateTrustedReviewVerdict(comments, patch, head) {
|
|
37678
|
+
const latest = latestReviewComment(comments);
|
|
37679
|
+
if (!latest) return { ok: false, reason: "none" };
|
|
37680
|
+
const identity = { commentId: latest.id, commentCreatedAt: latest.createdAt };
|
|
37681
|
+
if (latest.author?.toLowerCase() !== "jervaise") return { ...identity, ok: false, reason: "untrusted-author" };
|
|
37682
|
+
const evaluation = evaluateReviewVerdict([latest], patch);
|
|
37683
|
+
if (!evaluation.ok) return { ...identity, ok: false, reason: evaluation.reason };
|
|
37684
|
+
if (parseReviewVerdictComment(latest.body)?.head !== head) return { ...identity, ok: false, reason: "stale-head" };
|
|
37685
|
+
return { ...identity, ok: true, reason: "proceed" };
|
|
37686
|
+
}
|
|
37687
|
+
async function checkPrReview(number, repo, head, deps = {
|
|
37688
|
+
readHead: readPrHeadSha,
|
|
37689
|
+
readComments: readPrIssueComments,
|
|
37690
|
+
computePatch: computePrPatchId
|
|
37691
|
+
}) {
|
|
37692
|
+
const base = { repo, number, head };
|
|
37693
|
+
if (!/^[1-9][0-9]*$/.test(number) || !/^[\w.-]+\/[\w.-]+$/.test(repo) || !/^[0-9a-f]{40}$/.test(head)) {
|
|
37694
|
+
return { ...base, ok: false, reason: "invalid-input", detail: "expected a PR number, owner/repo and exact lowercase 40-character head SHA" };
|
|
37695
|
+
}
|
|
37696
|
+
try {
|
|
37697
|
+
if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
|
|
37698
|
+
const [comments, patch] = await Promise.all([deps.readComments(number, repo), deps.computePatch(number, repo)]);
|
|
37699
|
+
if (await deps.readHead(number, repo) !== head) return { ...base, ok: false, reason: "stale-head" };
|
|
37700
|
+
return { ...base, patch, ...evaluateTrustedReviewVerdict(comments, patch, head) };
|
|
37701
|
+
} catch (e) {
|
|
37702
|
+
return { ...base, ok: false, reason: "unreadable", detail: e.message };
|
|
37703
|
+
}
|
|
37704
|
+
}
|
|
37705
|
+
function computePrPatchId(number, repo) {
|
|
37706
|
+
return new Promise((resolve7, reject) => {
|
|
37707
|
+
const gh = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
37708
|
+
const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
37709
|
+
let out = "";
|
|
37710
|
+
let ghErr = "";
|
|
37711
|
+
let gitErr = "";
|
|
37712
|
+
const timer = setTimeout(() => {
|
|
37713
|
+
gh.kill();
|
|
37714
|
+
git3.kill();
|
|
37715
|
+
reject(new Error(`patch-id: timed out after ${GC_GH_TIMEOUT_MS4}ms`));
|
|
37716
|
+
}, GC_GH_TIMEOUT_MS4);
|
|
37717
|
+
gh.stdout.pipe(git3.stdin);
|
|
37718
|
+
gh.stderr.on("data", (d) => {
|
|
37719
|
+
ghErr += d.toString();
|
|
37720
|
+
});
|
|
37721
|
+
git3.stderr.on("data", (d) => {
|
|
37722
|
+
gitErr += d.toString();
|
|
37723
|
+
});
|
|
37724
|
+
git3.stdout.on("data", (d) => {
|
|
37725
|
+
out += d.toString();
|
|
37726
|
+
});
|
|
37727
|
+
gh.on("error", (e) => {
|
|
37728
|
+
clearTimeout(timer);
|
|
37729
|
+
reject(e);
|
|
37730
|
+
});
|
|
37731
|
+
git3.on("error", (e) => {
|
|
37732
|
+
clearTimeout(timer);
|
|
37733
|
+
reject(e);
|
|
37734
|
+
});
|
|
37735
|
+
let ghCode;
|
|
37736
|
+
let gitCode;
|
|
37737
|
+
const finish = () => {
|
|
37738
|
+
if (ghCode === void 0 || gitCode === void 0) return;
|
|
37739
|
+
clearTimeout(timer);
|
|
37740
|
+
if (ghCode !== 0) return reject(Object.assign(new Error(`gh pr diff ${number} --repo ${repo} exited ${ghCode}: ${ghErr.trim()}`), { stderr: ghErr }));
|
|
37741
|
+
if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
|
|
37742
|
+
const id = out.trim().split(/\s+/)[0];
|
|
37743
|
+
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"})`));
|
|
37744
|
+
resolve7(id);
|
|
37745
|
+
};
|
|
37746
|
+
gh.on("close", (code) => {
|
|
37747
|
+
ghCode = code;
|
|
37748
|
+
finish();
|
|
37749
|
+
});
|
|
37750
|
+
git3.on("close", (code) => {
|
|
37751
|
+
gitCode = code;
|
|
37752
|
+
finish();
|
|
37753
|
+
});
|
|
37754
|
+
git3.stdin.on("error", (e) => {
|
|
37755
|
+
clearTimeout(timer);
|
|
37756
|
+
gh.kill();
|
|
37757
|
+
git3.kill();
|
|
37758
|
+
reject(e);
|
|
37759
|
+
});
|
|
37760
|
+
});
|
|
37761
|
+
}
|
|
37762
|
+
async function readPrHeadSha(number, repo) {
|
|
37763
|
+
const { stdout } = await execFileP("gh", ["api", `repos/${repo}/pulls/${number}`, "--jq", ".head.sha"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
37764
|
+
const sha = stdout.trim();
|
|
37765
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error(`could not read PR #${number} head sha`);
|
|
37766
|
+
return sha;
|
|
37767
|
+
}
|
|
37768
|
+
async function readPrIssueComments(number, repo) {
|
|
37769
|
+
const { stdout } = await execFileP("gh", ["api", "--paginate", `repos/${repo}/issues/${number}/comments?per_page=100`, "--jq", ".[] | {id, body, createdAt: .created_at, author: .user.login}"], { timeout: GC_GH_TIMEOUT_MS4 });
|
|
37770
|
+
const comments = parseNdjsonLines(stdout);
|
|
37771
|
+
if (comments.some((c) => !c || !Number.isSafeInteger(c.id) || c.id <= 0 || typeof c.body !== "string" || typeof c.createdAt !== "string" || !Number.isFinite(Date.parse(c.createdAt)) || c.author !== null && typeof c.author !== "string")) throw new Error("unreadable PR comment identity");
|
|
37772
|
+
return comments;
|
|
37773
|
+
}
|
|
37774
|
+
async function postPrCommentFromFile(number, repo, body) {
|
|
37775
|
+
const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os19.tmpdir)(), "mmi-review-verdict-"));
|
|
37776
|
+
const path2 = (0, import_node_path35.join)(dir, "body.md");
|
|
37777
|
+
try {
|
|
37778
|
+
(0, import_node_fs38.writeFileSync)(path2, body, "utf8");
|
|
37779
|
+
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
37780
|
+
return stdout.trim();
|
|
37781
|
+
} finally {
|
|
37782
|
+
try {
|
|
37783
|
+
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
37784
|
+
} catch {
|
|
37785
|
+
}
|
|
37786
|
+
}
|
|
37787
|
+
}
|
|
37788
|
+
|
|
37789
|
+
// src/pr-mergify-queue.ts
|
|
37790
|
+
var MERGIFY_PILOT_REPO = "mutmutco/Jerv-JervCode";
|
|
37791
|
+
var MERGIFY_PILOT_VARIABLE = "JERV_BATCH_QUEUE_PILOT";
|
|
37792
|
+
var MERGIFY_READY_LABEL = "ready-to-merge";
|
|
37793
|
+
async function ghJson(args) {
|
|
37794
|
+
return JSON.parse((await execFileP("gh", ["api", ...args], { timeout: GC_GH_TIMEOUT_MS4 })).stdout);
|
|
37795
|
+
}
|
|
37796
|
+
async function usesMergifyPilot(repo, base, readVariable = async () => ghJson([`repos/${repo}/actions/variables/${MERGIFY_PILOT_VARIABLE}`])) {
|
|
37797
|
+
if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase() || base !== "development") return false;
|
|
37798
|
+
const variable = await readVariable();
|
|
37799
|
+
if (variable?.value === "true") return true;
|
|
37800
|
+
if (variable?.value === "false") return false;
|
|
37801
|
+
throw new Error(`Mergify pilot activation is missing or malformed on ${repo}; refusing native merge`);
|
|
37802
|
+
}
|
|
37803
|
+
function queueCheckState(checks, head) {
|
|
37804
|
+
const check = checks.filter((c) => c.name === "Mergify Merge Queue" && c.head_sha === head && c.app?.id === 10562 && c.app.slug === "mergify").sort((a, b) => b.id - a.id)[0];
|
|
37805
|
+
if (!check) return "refused";
|
|
37806
|
+
if (check.conclusion && !["success", "neutral"].includes(check.conclusion)) return "refused";
|
|
37807
|
+
if (["In merge queue", "Running merge queue checks"].includes(check.output?.title ?? "")) return "pending";
|
|
37808
|
+
return "requested";
|
|
37809
|
+
}
|
|
37810
|
+
async function requestMergifyMerge(number, repo, guardedText, expectedHead, deps = {
|
|
37811
|
+
readPull: () => ghJson([`repos/${repo}/pulls/${number}`]),
|
|
37812
|
+
readChecks: async (head) => parseNdjsonLines((await execFileP("gh", [
|
|
37813
|
+
"api",
|
|
37814
|
+
"--paginate",
|
|
37815
|
+
`repos/${repo}/commits/${head}/check-runs?per_page=100`,
|
|
37816
|
+
"--jq",
|
|
37817
|
+
".check_runs[]"
|
|
37818
|
+
], { timeout: GC_GH_TIMEOUT_MS4 })).stdout),
|
|
37819
|
+
addLabel: async () => {
|
|
37820
|
+
await ghJson([`repos/${repo}/issues/${number}/labels`, "--method", "POST", "-f", `labels[]=${MERGIFY_READY_LABEL}`]);
|
|
37821
|
+
},
|
|
37822
|
+
review: (head) => checkPrReview(number, repo, head),
|
|
37823
|
+
now: () => Date.now(),
|
|
37824
|
+
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
|
|
37825
|
+
}, timeoutMs = 6e5) {
|
|
37826
|
+
let head = expectedHead ?? "";
|
|
37827
|
+
let state = "refused";
|
|
37828
|
+
const receipt = (detail) => ({ provider: "mergify", state, head, detail });
|
|
37829
|
+
try {
|
|
37830
|
+
if (repo.toLowerCase() !== MERGIFY_PILOT_REPO.toLowerCase()) return receipt("repository is outside the Mergify pilot");
|
|
37831
|
+
const initial = await deps.readPull();
|
|
37832
|
+
head = expectedHead ?? initial.head.sha;
|
|
37833
|
+
const sameTarget = (pr) => /^[0-9a-f]{40}$/.test(head) && pr.head.sha === head && pr.base.ref === "development";
|
|
37834
|
+
if (!sameTarget(initial)) return receipt("PR head or target base changed");
|
|
37835
|
+
if (initial.merged === true) {
|
|
37836
|
+
state = "merged";
|
|
37837
|
+
return receipt("GitHub confirms the PR merged");
|
|
37838
|
+
}
|
|
37839
|
+
const validate = (pr) => pr.state === "open" && sameTarget(pr) && `${pr.title}
|
|
37840
|
+
${pr.body ?? ""}` === guardedText;
|
|
37841
|
+
if (!validate(initial)) return receipt("PR state, head, base or guarded title/body changed");
|
|
37842
|
+
const initialState = queueCheckState(await deps.readChecks(head), head);
|
|
37843
|
+
if (initialState === "refused") return receipt("trusted current-head Mergify check is absent or failed");
|
|
37844
|
+
if (!(await deps.review(head)).ok) return receipt("current trusted review verdict does not permit queue admission");
|
|
37845
|
+
if (!validate(await deps.readPull())) return receipt("PR changed before queue request");
|
|
37846
|
+
let mutationDetail = "";
|
|
37847
|
+
if (!initial.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
|
|
37848
|
+
try {
|
|
37849
|
+
await deps.addLabel();
|
|
37850
|
+
} catch {
|
|
37851
|
+
mutationDetail = "Label response was ambiguous; no replay was attempted. ";
|
|
37852
|
+
}
|
|
37853
|
+
}
|
|
37854
|
+
state = "requested";
|
|
37855
|
+
const deadline = deps.now() + timeoutMs;
|
|
37856
|
+
while (true) {
|
|
37857
|
+
const pr = await deps.readPull();
|
|
37858
|
+
if (!sameTarget(pr)) {
|
|
37859
|
+
state = "refused";
|
|
37860
|
+
return receipt("PR head or target base changed while queued");
|
|
37861
|
+
}
|
|
37862
|
+
if (pr.merged === true) {
|
|
37863
|
+
state = "merged";
|
|
37864
|
+
return receipt("GitHub confirms the PR merged");
|
|
37865
|
+
}
|
|
37866
|
+
if (!validate(pr)) {
|
|
37867
|
+
state = "refused";
|
|
37868
|
+
return receipt("PR state, head, base or guarded title/body changed while queued");
|
|
37869
|
+
}
|
|
37870
|
+
state = queueCheckState(await deps.readChecks(head), head);
|
|
37871
|
+
if (state === "refused") return receipt("trusted current-head Mergify check is absent or failed");
|
|
37872
|
+
if (!pr.labels.some((label) => label.name === MERGIFY_READY_LABEL)) {
|
|
37873
|
+
state = "refused";
|
|
37874
|
+
return receipt(`${mutationDetail}Queue request label is absent`);
|
|
37875
|
+
}
|
|
37876
|
+
if (deps.now() >= deadline) return receipt(`${mutationDetail}${state === "pending" ? "Mergify confirms queue admission" : "Queue requested; Mergify has not confirmed admission"}; PR has not merged within the wait window`);
|
|
37877
|
+
await deps.sleep(3e4);
|
|
37878
|
+
}
|
|
37879
|
+
} catch {
|
|
37880
|
+
state = "refused";
|
|
37881
|
+
return receipt("Mergify or GitHub state is unreadable; no native merge was attempted");
|
|
37882
|
+
}
|
|
37883
|
+
}
|
|
37884
|
+
|
|
37885
|
+
// src/post-merge-recon.ts
|
|
37886
|
+
var import_node_fs39 = require("node:fs");
|
|
37887
|
+
var import_node_path36 = require("node:path");
|
|
37460
37888
|
|
|
37461
37889
|
// src/cross-repo-filing-issue.ts
|
|
37462
37890
|
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
@@ -37622,16 +38050,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
37622
38050
|
}
|
|
37623
38051
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
37624
38052
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
37625
|
-
(0,
|
|
37626
|
-
(0,
|
|
38053
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
|
|
38054
|
+
(0, import_node_fs39.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
37627
38055
|
`, "utf8");
|
|
37628
38056
|
return path2;
|
|
37629
38057
|
}
|
|
37630
38058
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
37631
38059
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
37632
|
-
if (!(0,
|
|
38060
|
+
if (!(0, import_node_fs39.existsSync)(path2)) return;
|
|
37633
38061
|
try {
|
|
37634
|
-
(0,
|
|
38062
|
+
(0, import_node_fs39.unlinkSync)(path2);
|
|
37635
38063
|
} catch {
|
|
37636
38064
|
}
|
|
37637
38065
|
}
|
|
@@ -37661,184 +38089,6 @@ function postMergeReconWarnings(input) {
|
|
|
37661
38089
|
return lines2;
|
|
37662
38090
|
}
|
|
37663
38091
|
|
|
37664
|
-
// src/review-verdict.ts
|
|
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
38092
|
// src/pr-create-docs-check.ts
|
|
37843
38093
|
var import_node_child_process15 = require("node:child_process");
|
|
37844
38094
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
@@ -37960,120 +38210,6 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
37960
38210
|
};
|
|
37961
38211
|
}
|
|
37962
38212
|
|
|
37963
|
-
// src/pr-create-zeroci.ts
|
|
37964
|
-
var import_promises7 = require("node:fs/promises");
|
|
37965
|
-
var import_node_path38 = require("node:path");
|
|
37966
|
-
|
|
37967
|
-
// src/jervcode-node-modules-cleanup.ts
|
|
37968
|
-
var import_node_fs40 = require("node:fs");
|
|
37969
|
-
var import_node_os20 = require("node:os");
|
|
37970
|
-
var import_node_path37 = require("node:path");
|
|
37971
|
-
var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
|
|
37972
|
-
var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
|
|
37973
|
-
var POSIX_NAMES2 = ["jervcode"];
|
|
37974
|
-
var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
|
|
37975
|
-
function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
|
|
37976
|
-
const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
|
|
37977
|
-
const out = [];
|
|
37978
|
-
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
37979
|
-
for (const name of names) out.push((0, import_node_path37.join)(dir, name));
|
|
37980
|
-
}
|
|
37981
|
-
return out;
|
|
37982
|
-
}
|
|
37983
|
-
function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
|
|
37984
|
-
for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
|
|
37985
|
-
if (exists(candidate)) return candidate;
|
|
37986
|
-
}
|
|
37987
|
-
return void 0;
|
|
37988
|
-
}
|
|
37989
|
-
function jervcodeExecFileArgs(args, opts = {}) {
|
|
37990
|
-
const platform2 = opts.platform ?? process.platform;
|
|
37991
|
-
const exists = opts.exists ?? import_node_fs40.existsSync;
|
|
37992
|
-
const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
|
|
37993
|
-
if (resolved) {
|
|
37994
|
-
const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
|
|
37995
|
-
if (exists(entry)) {
|
|
37996
|
-
return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
|
|
37997
|
-
}
|
|
37998
|
-
}
|
|
37999
|
-
const bin = resolved ?? "jervcode";
|
|
38000
|
-
if (platform2 === "win32") {
|
|
38001
|
-
return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
|
|
38002
|
-
}
|
|
38003
|
-
return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
|
|
38004
|
-
}
|
|
38005
|
-
async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
|
|
38006
|
-
const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
|
|
38007
|
-
const env = opts.env ?? process.env;
|
|
38008
|
-
const exec = opts.exec ?? execFileP;
|
|
38009
|
-
const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
|
|
38010
|
-
const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
|
|
38011
|
-
const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
|
|
38012
|
-
try {
|
|
38013
|
-
await exec(plan.file, plan.args, execOptions);
|
|
38014
|
-
return { ok: true };
|
|
38015
|
-
} catch (e) {
|
|
38016
|
-
const err = e;
|
|
38017
|
-
const base = formatJervCliSpawnFailure(err, plan, candidates);
|
|
38018
|
-
const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
|
|
38019
|
-
const detail = stderr.split("\n")[0] || base;
|
|
38020
|
-
return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
|
|
38021
|
-
}
|
|
38022
|
-
}
|
|
38023
|
-
|
|
38024
|
-
// src/pr-create-zeroci.ts
|
|
38025
|
-
var GATE_WORKFLOW_PATH = ".github/workflows/gate.yml";
|
|
38026
|
-
var ZEROCI_MINT_MARKER = "zeroci-mint: seat";
|
|
38027
|
-
var ZEROCI_MINT_TIMEOUT_MS = 18e5;
|
|
38028
|
-
var ZEROCI_MINT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
38029
|
-
function createPrCreateZeroCiDeps() {
|
|
38030
|
-
const git3 = createPrCreateDocsIndexDeps();
|
|
38031
|
-
return {
|
|
38032
|
-
worktreeRoot: git3.worktreeRoot,
|
|
38033
|
-
originRepo: git3.originRepo,
|
|
38034
|
-
readGateWorkflow: async (root) => {
|
|
38035
|
-
try {
|
|
38036
|
-
return await (0, import_promises7.readFile)((0, import_node_path38.join)(root, GATE_WORKFLOW_PATH), "utf8");
|
|
38037
|
-
} catch {
|
|
38038
|
-
return void 0;
|
|
38039
|
-
}
|
|
38040
|
-
},
|
|
38041
|
-
mint: async (root) => {
|
|
38042
|
-
const plan = jervcodeExecFileArgs(["zeroci-receipt", "--from-gate"]);
|
|
38043
|
-
try {
|
|
38044
|
-
await execFileHard(plan.file, plan.args, {
|
|
38045
|
-
cwd: root,
|
|
38046
|
-
timeout: ZEROCI_MINT_TIMEOUT_MS,
|
|
38047
|
-
maxBuffer: ZEROCI_MINT_MAX_BUFFER,
|
|
38048
|
-
step: "jervcode zeroci-receipt --from-gate"
|
|
38049
|
-
});
|
|
38050
|
-
} catch (e) {
|
|
38051
|
-
const err = e;
|
|
38052
|
-
const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
|
|
38053
|
-
throw new Error(stderr.split("\n").filter(Boolean).pop() || formatJervCliSpawnFailure(err, plan, jervcodeCandidatePaths()));
|
|
38054
|
-
}
|
|
38055
|
-
}
|
|
38056
|
-
};
|
|
38057
|
-
}
|
|
38058
|
-
async function mintZeroCiReceipt(opts, deps) {
|
|
38059
|
-
if (opts.zeroci === false) return void 0;
|
|
38060
|
-
const root = await deps.worktreeRoot();
|
|
38061
|
-
if (!root) return void 0;
|
|
38062
|
-
if (opts.repo) {
|
|
38063
|
-
const origin = await deps.originRepo(root);
|
|
38064
|
-
if (!origin || origin.toLowerCase() !== opts.repo.toLowerCase()) return void 0;
|
|
38065
|
-
}
|
|
38066
|
-
const gate = await deps.readGateWorkflow(root);
|
|
38067
|
-
if (!gate || !gate.includes("zeroci-verify")) return void 0;
|
|
38068
|
-
if (!gate.includes(ZEROCI_MINT_MARKER)) return void 0;
|
|
38069
|
-
try {
|
|
38070
|
-
await deps.mint(root);
|
|
38071
|
-
return void 0;
|
|
38072
|
-
} catch (e) {
|
|
38073
|
-
return `pr create: WARNING \u2014 ZeroCI receipt not minted, opening the PR anyway (the gate runs in full): ${e.message}`;
|
|
38074
|
-
}
|
|
38075
|
-
}
|
|
38076
|
-
|
|
38077
38213
|
// src/pr-create-claim-guard.ts
|
|
38078
38214
|
var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
|
|
38079
38215
|
function withRateLimitRetry(client, seams = {}) {
|
|
@@ -38132,11 +38268,68 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
38132
38268
|
|
|
38133
38269
|
// src/worktree-merge-cleanup.ts
|
|
38134
38270
|
var import_node_fs42 = require("node:fs");
|
|
38135
|
-
var
|
|
38271
|
+
var import_node_path39 = require("node:path");
|
|
38272
|
+
|
|
38273
|
+
// src/jervcode-node-modules-cleanup.ts
|
|
38274
|
+
var import_node_fs40 = require("node:fs");
|
|
38275
|
+
var import_node_os20 = require("node:os");
|
|
38276
|
+
var import_node_path37 = require("node:path");
|
|
38277
|
+
var JERVCODE_PACKAGE_ENTRY = (0, import_node_path37.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
|
|
38278
|
+
var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
|
|
38279
|
+
var POSIX_NAMES2 = ["jervcode"];
|
|
38280
|
+
var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
|
|
38281
|
+
function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform) {
|
|
38282
|
+
const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
|
|
38283
|
+
const out = [];
|
|
38284
|
+
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
38285
|
+
for (const name of names) out.push((0, import_node_path37.join)(dir, name));
|
|
38286
|
+
}
|
|
38287
|
+
return out;
|
|
38288
|
+
}
|
|
38289
|
+
function resolveJervcodePath(env = process.env, home = (0, import_node_os20.homedir)(), platform2 = process.platform, exists = import_node_fs40.existsSync) {
|
|
38290
|
+
for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
|
|
38291
|
+
if (exists(candidate)) return candidate;
|
|
38292
|
+
}
|
|
38293
|
+
return void 0;
|
|
38294
|
+
}
|
|
38295
|
+
function jervcodeExecFileArgs(args, opts = {}) {
|
|
38296
|
+
const platform2 = opts.platform ?? process.platform;
|
|
38297
|
+
const exists = opts.exists ?? import_node_fs40.existsSync;
|
|
38298
|
+
const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os20.homedir)(), platform2, exists);
|
|
38299
|
+
if (resolved) {
|
|
38300
|
+
const entry = (0, import_node_path37.join)((0, import_node_path37.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
|
|
38301
|
+
if (exists(entry)) {
|
|
38302
|
+
return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
|
|
38303
|
+
}
|
|
38304
|
+
}
|
|
38305
|
+
const bin = resolved ?? "jervcode";
|
|
38306
|
+
if (platform2 === "win32") {
|
|
38307
|
+
return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
|
|
38308
|
+
}
|
|
38309
|
+
return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
|
|
38310
|
+
}
|
|
38311
|
+
async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
|
|
38312
|
+
const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
|
|
38313
|
+
const env = opts.env ?? process.env;
|
|
38314
|
+
const exec = opts.exec ?? execFileP;
|
|
38315
|
+
const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
|
|
38316
|
+
const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
|
|
38317
|
+
const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
|
|
38318
|
+
try {
|
|
38319
|
+
await exec(plan.file, plan.args, execOptions);
|
|
38320
|
+
return { ok: true };
|
|
38321
|
+
} catch (e) {
|
|
38322
|
+
const err = e;
|
|
38323
|
+
const base = formatJervCliSpawnFailure(err, plan, candidates);
|
|
38324
|
+
const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
|
|
38325
|
+
const detail = stderr.split("\n")[0] || base;
|
|
38326
|
+
return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
|
|
38327
|
+
}
|
|
38328
|
+
}
|
|
38136
38329
|
|
|
38137
38330
|
// src/worktree-evidence-archive.ts
|
|
38138
38331
|
var import_node_fs41 = require("node:fs");
|
|
38139
|
-
var
|
|
38332
|
+
var import_node_path38 = require("node:path");
|
|
38140
38333
|
var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
|
|
38141
38334
|
function sanitizeArchiveSegment(value, max = 80) {
|
|
38142
38335
|
const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -38168,7 +38361,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
38168
38361
|
const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
|
|
38169
38362
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
38170
38363
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
38171
|
-
const source = (0,
|
|
38364
|
+
const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
|
|
38172
38365
|
if (!exists(source)) return { status: "absent" };
|
|
38173
38366
|
if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
|
|
38174
38367
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -38176,7 +38369,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
38176
38369
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
38177
38370
|
const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
|
|
38178
38371
|
try {
|
|
38179
|
-
mkdirp((0,
|
|
38372
|
+
mkdirp((0, import_node_path38.dirname)(dest));
|
|
38180
38373
|
copyDir(source, dest);
|
|
38181
38374
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
38182
38375
|
return { status: "archived", path: dest, runScope };
|
|
@@ -38192,7 +38385,7 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
38192
38385
|
const exists = deps.exists ?? import_node_fs41.existsSync;
|
|
38193
38386
|
const stat4 = deps.stat ?? defaultStat;
|
|
38194
38387
|
const readdir2 = deps.readdir ?? import_node_fs41.readdirSync;
|
|
38195
|
-
const tmpRoot = (0,
|
|
38388
|
+
const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
|
|
38196
38389
|
if (!exists(tmpRoot)) return [];
|
|
38197
38390
|
const entries = [];
|
|
38198
38391
|
const walk2 = (dir) => {
|
|
@@ -38203,14 +38396,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
38203
38396
|
return;
|
|
38204
38397
|
}
|
|
38205
38398
|
for (const name of names) {
|
|
38206
|
-
const full = (0,
|
|
38399
|
+
const full = (0, import_node_path38.join)(dir, name);
|
|
38207
38400
|
let st;
|
|
38208
38401
|
try {
|
|
38209
38402
|
st = stat4(full);
|
|
38210
38403
|
} catch {
|
|
38211
38404
|
continue;
|
|
38212
38405
|
}
|
|
38213
|
-
const relPath = (0,
|
|
38406
|
+
const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
|
|
38214
38407
|
if (st.isDirectory()) {
|
|
38215
38408
|
if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
|
|
38216
38409
|
walk2(full);
|
|
@@ -38235,7 +38428,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
38235
38428
|
if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
|
|
38236
38429
|
if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
|
|
38237
38430
|
const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
|
|
38238
|
-
const source = (0,
|
|
38431
|
+
const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
|
|
38239
38432
|
if (!scanned.length) return { status: "absent" };
|
|
38240
38433
|
if (!exists(source)) return { status: "absent" };
|
|
38241
38434
|
const runScope = resolveArtifactRunScope(env);
|
|
@@ -38243,7 +38436,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
|
38243
38436
|
const stamp = now().toISOString().replace(/[:.]/g, "-");
|
|
38244
38437
|
const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
|
|
38245
38438
|
try {
|
|
38246
|
-
mkdirp((0,
|
|
38439
|
+
mkdirp((0, import_node_path38.dirname)(dest));
|
|
38247
38440
|
copyDir(source, dest);
|
|
38248
38441
|
if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
|
|
38249
38442
|
const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
|
|
@@ -38312,7 +38505,7 @@ function normPath2(p) {
|
|
|
38312
38505
|
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
38313
38506
|
}
|
|
38314
38507
|
function unlinkNodeModulesJunction(wtPath) {
|
|
38315
|
-
const nm = (0,
|
|
38508
|
+
const nm = (0, import_node_path39.join)(wtPath, "node_modules");
|
|
38316
38509
|
try {
|
|
38317
38510
|
if ((0, import_node_fs42.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs42.rmdirSync)(nm);
|
|
38318
38511
|
return { ok: true };
|
|
@@ -38449,7 +38642,7 @@ async function preCleanWorktreeForRemoval(wtPath, execGit) {
|
|
|
38449
38642
|
}
|
|
38450
38643
|
async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
38451
38644
|
const out = await execGit(["-C", wtPath, "ls-files", "--others", "--ignored", "--exclude-standard", "--directory"]).catch(() => "");
|
|
38452
|
-
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0,
|
|
38645
|
+
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)));
|
|
38453
38646
|
}
|
|
38454
38647
|
function safeRemoveTree(path2) {
|
|
38455
38648
|
const stat4 = (0, import_node_fs42.lstatSync)(path2);
|
|
@@ -38462,7 +38655,7 @@ function safeRemoveTree(path2) {
|
|
|
38462
38655
|
return;
|
|
38463
38656
|
}
|
|
38464
38657
|
if (stat4.isDirectory()) {
|
|
38465
|
-
for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0,
|
|
38658
|
+
for (const entry of (0, import_node_fs42.readdirSync)(path2)) safeRemoveTree((0, import_node_path39.join)(path2, entry));
|
|
38466
38659
|
(0, import_node_fs42.rmdirSync)(path2);
|
|
38467
38660
|
return;
|
|
38468
38661
|
}
|
|
@@ -38505,7 +38698,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
38505
38698
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
38506
38699
|
}
|
|
38507
38700
|
for (const entry of entries) {
|
|
38508
|
-
const child2 = (0,
|
|
38701
|
+
const child2 = (0, import_node_path39.join)(dir, entry.name);
|
|
38509
38702
|
if (entry.isSymbolicLink()) {
|
|
38510
38703
|
let target = "";
|
|
38511
38704
|
try {
|
|
@@ -38539,7 +38732,7 @@ async function describePreCleanFailure(wtPath, execGit, error) {
|
|
|
38539
38732
|
const more = remaining.length > 10 ? ` (+${remaining.length - 10} more)` : "";
|
|
38540
38733
|
const quote = (path2) => path2.replace(/'/g, "''");
|
|
38541
38734
|
const nested = remaining.find((path2) => path2.endsWith("node_modules/") && path2 !== "node_modules/");
|
|
38542
|
-
const remediation = remaining.includes("node_modules/") ? `jervcode worktree-node-modules-cleanup --worktree '${quote(wtPath)}'` : nested ? `Remove-Item -LiteralPath '${quote((0,
|
|
38735
|
+
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;
|
|
38543
38736
|
return { ok: false, error: `${error}; remaining ignored paths: ${shown}${more}`, ...remediation ? { remediation } : {} };
|
|
38544
38737
|
}
|
|
38545
38738
|
function formatWorktreeRemovalFailureDetail(options) {
|
|
@@ -38833,7 +39026,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
38833
39026
|
const preHelperGuard = unlinkEscapingReparsePoints(wtPath, options.primaryRoot);
|
|
38834
39027
|
if (!preHelperGuard.ok) return refuseReparseEscape(preHelperGuard.error);
|
|
38835
39028
|
unlinkedReparsePoints.push(...preHelperGuard.unlinked);
|
|
38836
|
-
if (pathExists((0,
|
|
39029
|
+
if (pathExists((0, import_node_path39.join)(wtPath, "node_modules"))) {
|
|
38837
39030
|
const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
|
|
38838
39031
|
if (!nmRemoved.ok) {
|
|
38839
39032
|
report.worktree = {
|
|
@@ -39000,10 +39193,10 @@ function argvWantsJson2() {
|
|
|
39000
39193
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
39001
39194
|
}
|
|
39002
39195
|
function hubRoot() {
|
|
39003
|
-
const fromPkg = (0,
|
|
39196
|
+
const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
|
|
39004
39197
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
39005
|
-
if ((0, import_node_fs43.existsSync)((0,
|
|
39006
|
-
if ((0, import_node_fs43.existsSync)((0,
|
|
39198
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
|
|
39199
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
|
|
39007
39200
|
return null;
|
|
39008
39201
|
}
|
|
39009
39202
|
function ciAuditDeps() {
|
|
@@ -39015,12 +39208,12 @@ function ciAuditDeps() {
|
|
|
39015
39208
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
39016
39209
|
readSeedFile: (path2) => {
|
|
39017
39210
|
if (!root) return null;
|
|
39018
|
-
const fullPath = (0,
|
|
39211
|
+
const fullPath = (0, import_node_path40.join)(root, path2);
|
|
39019
39212
|
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
39020
39213
|
}
|
|
39021
39214
|
};
|
|
39022
39215
|
}
|
|
39023
|
-
async function
|
|
39216
|
+
async function ghJson2(args, timeout = 1e4) {
|
|
39024
39217
|
const { stdout } = await execFileP("gh", args, { timeout });
|
|
39025
39218
|
return JSON.parse(stdout);
|
|
39026
39219
|
}
|
|
@@ -39166,7 +39359,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39166
39359
|
try {
|
|
39167
39360
|
title = await resolveIssueTitle(
|
|
39168
39361
|
{ title: opts.title, titleFile: opts.titleFile },
|
|
39169
|
-
{ readFile:
|
|
39362
|
+
{ readFile: import_promises7.readFile, readStdin }
|
|
39170
39363
|
);
|
|
39171
39364
|
} catch (e) {
|
|
39172
39365
|
return fail(
|
|
@@ -39208,8 +39401,8 @@ function registerCollaborationCommands(program3) {
|
|
|
39208
39401
|
let surfaceFlagLabel;
|
|
39209
39402
|
try {
|
|
39210
39403
|
issueType = resolveCreateType(o.type, "issue create", o.label);
|
|
39211
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
39212
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
39404
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39405
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39213
39406
|
if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
|
|
39214
39407
|
priority = resolveCreatePriority(o.priority, "issue create");
|
|
39215
39408
|
extraLabels = [...o.label ?? []];
|
|
@@ -39307,7 +39500,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39307
39500
|
async function readParentField(number, repo) {
|
|
39308
39501
|
let payload;
|
|
39309
39502
|
try {
|
|
39310
|
-
payload = await
|
|
39503
|
+
payload = await ghJson2(["api", `repos/${repo}/issues/${number}`]);
|
|
39311
39504
|
} catch (e) {
|
|
39312
39505
|
const err = e;
|
|
39313
39506
|
return { parentReadError: (err.stderr || err.message || String(e)).trim() };
|
|
@@ -39339,7 +39532,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39339
39532
|
emit(data2, await readParentField(n, repo));
|
|
39340
39533
|
return;
|
|
39341
39534
|
}
|
|
39342
|
-
const data = await
|
|
39535
|
+
const data = await ghJson2(["issue", "view", String(n), "--repo", repo, "--json", gh.ghFields]);
|
|
39343
39536
|
emit(data, await readParentField(n, repo));
|
|
39344
39537
|
} catch (e) {
|
|
39345
39538
|
const err = e;
|
|
@@ -39354,7 +39547,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39354
39547
|
const repo = await resolveRepo(o.repo);
|
|
39355
39548
|
if (!repo) return fail("issue discover-related: could not resolve repo");
|
|
39356
39549
|
try {
|
|
39357
|
-
const issues = await
|
|
39550
|
+
const issues = await ghJson2([
|
|
39358
39551
|
"issue",
|
|
39359
39552
|
"list",
|
|
39360
39553
|
"--repo",
|
|
@@ -39369,7 +39562,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39369
39562
|
const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
|
|
39370
39563
|
if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
|
|
39371
39564
|
if (!candidates.length) return;
|
|
39372
|
-
const viewed = await
|
|
39565
|
+
const viewed = await ghJson2([
|
|
39373
39566
|
"issue",
|
|
39374
39567
|
"view",
|
|
39375
39568
|
String(number),
|
|
@@ -39417,7 +39610,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39417
39610
|
}
|
|
39418
39611
|
let body;
|
|
39419
39612
|
try {
|
|
39420
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
39613
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39421
39614
|
} catch (e) {
|
|
39422
39615
|
return fail(`issue comment: ${e.message}`);
|
|
39423
39616
|
}
|
|
@@ -39443,7 +39636,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39443
39636
|
const checked = o.off !== true;
|
|
39444
39637
|
let body;
|
|
39445
39638
|
try {
|
|
39446
|
-
const viewed = await
|
|
39639
|
+
const viewed = await ghJson2(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
39447
39640
|
body = viewed.body ?? "";
|
|
39448
39641
|
} catch (e) {
|
|
39449
39642
|
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
@@ -39477,8 +39670,8 @@ ${list}`);
|
|
|
39477
39670
|
let title;
|
|
39478
39671
|
const sourceRepo = o.repo ?? await resolveRepo(void 0);
|
|
39479
39672
|
try {
|
|
39480
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
39481
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
39673
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39674
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39482
39675
|
priority = resolveCreatePriority(o.priority, "report");
|
|
39483
39676
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
39484
39677
|
throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
@@ -39533,8 +39726,8 @@ ${list}`);
|
|
|
39533
39726
|
let args;
|
|
39534
39727
|
try {
|
|
39535
39728
|
skill = assertSkillName(o.skill);
|
|
39536
|
-
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
39537
|
-
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
39729
|
+
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39730
|
+
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39538
39731
|
title = buildSkillLessonTitle(skill, rawTitle);
|
|
39539
39732
|
priority = resolveCreatePriority(o.priority, "skill-lesson");
|
|
39540
39733
|
body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
|
|
@@ -39545,7 +39738,7 @@ ${list}`);
|
|
|
39545
39738
|
if (!o.force) {
|
|
39546
39739
|
let openLessons = [];
|
|
39547
39740
|
try {
|
|
39548
|
-
openLessons = await
|
|
39741
|
+
openLessons = await ghJson2([
|
|
39549
39742
|
"issue",
|
|
39550
39743
|
"list",
|
|
39551
39744
|
"--repo",
|
|
@@ -39591,12 +39784,12 @@ ${list}`);
|
|
|
39591
39784
|
console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
|
|
39592
39785
|
});
|
|
39593
39786
|
const pr = program3.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
39594
|
-
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--no-zeroci", "
|
|
39787
|
+
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--no-zeroci", "compatibility option; pr create does not mint receipts").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
|
|
39595
39788
|
let body;
|
|
39596
39789
|
let title;
|
|
39597
39790
|
try {
|
|
39598
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
39599
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
39791
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39792
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
39600
39793
|
} catch (e) {
|
|
39601
39794
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
39602
39795
|
}
|
|
@@ -39616,9 +39809,6 @@ ${list}`);
|
|
|
39616
39809
|
}
|
|
39617
39810
|
const claimRefusal = await prCreateClaimRefusal(body, o.repo);
|
|
39618
39811
|
if (claimRefusal) return fail(claimRefusal);
|
|
39619
|
-
const zeroCiWarn = await mintZeroCiReceipt({ repo: o.repo, zeroci: o.zeroci }, createPrCreateZeroCiDeps());
|
|
39620
|
-
if (zeroCiWarn) process.stderr.write(`${zeroCiWarn}
|
|
39621
|
-
`);
|
|
39622
39812
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
39623
39813
|
if (isGhCreateRateLimited(created)) {
|
|
39624
39814
|
console.log(JSON.stringify(created));
|
|
@@ -39654,7 +39844,7 @@ ${list}`);
|
|
|
39654
39844
|
console.log(JSON.stringify(data2));
|
|
39655
39845
|
return;
|
|
39656
39846
|
}
|
|
39657
|
-
const data = await
|
|
39847
|
+
const data = await ghJson2(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
39658
39848
|
console.log(JSON.stringify(data));
|
|
39659
39849
|
} catch (e) {
|
|
39660
39850
|
const err = e;
|
|
@@ -39662,11 +39852,11 @@ ${list}`);
|
|
|
39662
39852
|
}
|
|
39663
39853
|
});
|
|
39664
39854
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
39665
|
-
const wfDir = (0,
|
|
39855
|
+
const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
|
|
39666
39856
|
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
39667
39857
|
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
39668
39858
|
try {
|
|
39669
|
-
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0,
|
|
39859
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
|
|
39670
39860
|
} catch {
|
|
39671
39861
|
return true;
|
|
39672
39862
|
}
|
|
@@ -39854,7 +40044,12 @@ ${list}`);
|
|
|
39854
40044
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
39855
40045
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
39856
40046
|
});
|
|
39857
|
-
|
|
40047
|
+
pr.command("review-check <number>").description("read-only trusted review verdict check for an exact PR head").requiredOption("--repo <owner/repo>", "target repository").requiredOption("--head <sha>", "exact expected head SHA").option("--json", "machine-readable output").action(async (number, o) => {
|
|
40048
|
+
const result = await checkPrReview(number, o.repo, o.head);
|
|
40049
|
+
console.log(o.json ? JSON.stringify(result) : `pr review-check: ${result.reason}`);
|
|
40050
|
+
if (!result.ok) process.exitCode = 1;
|
|
40051
|
+
});
|
|
40052
|
+
jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").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) => {
|
|
39858
40053
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
39859
40054
|
const verdict = o.verdict;
|
|
39860
40055
|
const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
@@ -39896,7 +40091,7 @@ ${list}`);
|
|
|
39896
40091
|
}
|
|
39897
40092
|
class PrHeadBehindBaseError extends Error {
|
|
39898
40093
|
}
|
|
39899
|
-
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>", "
|
|
40094
|
+
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>", "compatibility option; shared merge helpers do not require a review verdict").action(async (number, o) => {
|
|
39900
40095
|
if (/^(?:[^/]+\/[^/]+)?#\d+$/.test(number.trim())) {
|
|
39901
40096
|
try {
|
|
39902
40097
|
const parsed = parseIssueRef(number, o.repo);
|
|
@@ -39914,7 +40109,7 @@ ${list}`);
|
|
|
39914
40109
|
await readClosingGuardInput(number, repoArgs, landRepoForGuard, "pr land"),
|
|
39915
40110
|
async (n) => {
|
|
39916
40111
|
if (!landRepoForGuard) return void 0;
|
|
39917
|
-
const viewed = await
|
|
40112
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", landRepoForGuard, "--json", "state"]);
|
|
39918
40113
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
39919
40114
|
}
|
|
39920
40115
|
);
|
|
@@ -39931,16 +40126,15 @@ ${list}`);
|
|
|
39931
40126
|
return;
|
|
39932
40127
|
}
|
|
39933
40128
|
if (landClosingGuardVerdict.message) console.warn(landClosingGuardVerdict.message);
|
|
39934
|
-
{
|
|
39935
|
-
const gateRepo = landRepoForGuard ?? await requireRepo(o.repo);
|
|
39936
|
-
const refusal = await requireReviewVerdict("pr land", number, gateRepo, { withoutReview: o.withoutReview });
|
|
39937
|
-
if (refusal) {
|
|
39938
|
-
console.error(refusal);
|
|
39939
|
-
process.exitCode = 1;
|
|
39940
|
-
return;
|
|
39941
|
-
}
|
|
39942
|
-
}
|
|
39943
40129
|
const result = await runPrLand(number, { repo: o.repo, requireTrain: o.requireTrain !== false }, {
|
|
40130
|
+
queueMerge: async (prNumber, repo) => {
|
|
40131
|
+
const meta = await ghJson2(["pr", "view", prNumber, "--repo", repo, "--json", "baseRefName,headRefOid"]);
|
|
40132
|
+
if (!await usesMergifyPilot(repo, meta.baseRefName)) return void 0;
|
|
40133
|
+
if (!landClosingGuardInput) throw new Error("pr land: Mergify requires a readable closing-keyword guard");
|
|
40134
|
+
const guard = evaluateClosingGuard(landClosingGuardInput, { force: o.force, context: "pr land", squashBodyText: landClosingGuardInput.text });
|
|
40135
|
+
if (guard.blocked) throw new Error(guard.message);
|
|
40136
|
+
return requestMergifyMerge(prNumber, repo, landClosingGuardInput.text, meta.headRefOid);
|
|
40137
|
+
},
|
|
39944
40138
|
resolveRepo: async (prNumber, repoOpt) => {
|
|
39945
40139
|
const args = repoOpt ? ["--repo", repoOpt] : repoArgs;
|
|
39946
40140
|
const viewed = (await execFileP("gh", ["pr", "view", prNumber, ...args, "--json", "headRepository,baseRefName", "--jq", '.headRepository.nameWithOwner + " " + .baseRefName'], { timeout: GC_GH_TIMEOUT_MS4 })).stdout.trim();
|
|
@@ -40075,7 +40269,7 @@ ${list}`);
|
|
|
40075
40269
|
else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
|
|
40076
40270
|
if (result.status === "failed") process.exitCode = 1;
|
|
40077
40271
|
});
|
|
40078
|
-
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>", "
|
|
40272
|
+
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>", "compatibility option; shared merge helpers do not require a review verdict")).action(async (number, o) => {
|
|
40079
40273
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
40080
40274
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
40081
40275
|
if (o.disableAuto) {
|
|
@@ -40097,20 +40291,24 @@ ${list}`);
|
|
|
40097
40291
|
const headRef = prMeta.head;
|
|
40098
40292
|
const baseRef = prMeta.base;
|
|
40099
40293
|
const headRefOid = (prMeta.oid ?? "").trim() || void 0;
|
|
40294
|
+
if (!repoForPostCleanup) throw new Error("pr merge: cannot resolve target repository");
|
|
40295
|
+
const mergifyPilot = await usesMergifyPilot(repoForPostCleanup, baseRef);
|
|
40296
|
+
if (mergifyPilot && (method !== "--squash" || o.squashBodyFile)) throw new Error("pr merge: Mergify pilot uses the protected squash title/body policy; custom merge methods and body files are unsupported");
|
|
40100
40297
|
const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
|
|
40101
40298
|
const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
|
|
40102
40299
|
const closingGuardInput = await withAlreadyClosedCommitTargets(
|
|
40103
40300
|
await readClosingGuardInput(number, repoArgs, repoForPostCleanup, "pr merge"),
|
|
40104
40301
|
async (n) => {
|
|
40105
40302
|
if (!repoForPostCleanup) return void 0;
|
|
40106
|
-
const viewed = await
|
|
40303
|
+
const viewed = await ghJson2(["issue", "view", String(n), "--repo", repoForPostCleanup, "--json", "state"]);
|
|
40107
40304
|
return typeof viewed.state === "string" ? viewed.state : void 0;
|
|
40108
40305
|
}
|
|
40109
40306
|
);
|
|
40110
40307
|
if (o.squashBodyFile && method !== "--squash") {
|
|
40111
40308
|
throw new Error("pr merge: --squash-body-file applies only to squash merges");
|
|
40112
40309
|
}
|
|
40113
|
-
|
|
40310
|
+
if (mergifyPilot && !closingGuardInput) throw new Error("pr merge: Mergify requires a readable closing-keyword guard");
|
|
40311
|
+
const mergeSquashBody = mergifyPilot ? closingGuardInput.text : squashBodyTextForMerge(
|
|
40114
40312
|
closingGuardInput,
|
|
40115
40313
|
method === "--squash",
|
|
40116
40314
|
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
@@ -40127,15 +40325,6 @@ ${list}`);
|
|
|
40127
40325
|
return;
|
|
40128
40326
|
}
|
|
40129
40327
|
if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
|
|
40130
|
-
if (prMeta.state !== "MERGED") {
|
|
40131
|
-
const gateRepo = repoForPostCleanup ?? await requireRepo(o.repo);
|
|
40132
|
-
const refusal = await requireReviewVerdict("pr merge", number, gateRepo, { withoutReview: o.withoutReview });
|
|
40133
|
-
if (refusal) {
|
|
40134
|
-
console.error(refusal);
|
|
40135
|
-
process.exitCode = 1;
|
|
40136
|
-
return;
|
|
40137
|
-
}
|
|
40138
|
-
}
|
|
40139
40328
|
if (prMeta.state !== "MERGED" && !o.preserveWorktree) {
|
|
40140
40329
|
if (!repoForPostCleanup) throw new Error("pr merge: repository is unreadable; cannot prove remote branch preservation");
|
|
40141
40330
|
const repoSettings = await fetchRestRepoMergeSettings(repoForPostCleanup);
|
|
@@ -40154,7 +40343,7 @@ ${list}`);
|
|
|
40154
40343
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
40155
40344
|
let foreignCheckout;
|
|
40156
40345
|
if (foreignCwd) {
|
|
40157
|
-
const sibling = (0,
|
|
40346
|
+
const sibling = (0, import_node_path40.join)((0, import_node_path40.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
40158
40347
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
40159
40348
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
40160
40349
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -40217,7 +40406,7 @@ ${list}`);
|
|
|
40217
40406
|
}
|
|
40218
40407
|
return true;
|
|
40219
40408
|
};
|
|
40220
|
-
if (o.wait && !await runWaitGate()) return;
|
|
40409
|
+
if (!mergifyPilot && o.wait && !await runWaitGate()) return;
|
|
40221
40410
|
if (ciPolicy.policy === "no-ci") {
|
|
40222
40411
|
const guard = decidePrMergeNoCiGuard(await pollGhPrChecks(number, repoArgs), ciPolicy.reason);
|
|
40223
40412
|
if (guard.action === "refuse") throw new Error(`gh pr merge ${number}: ${guard.message}`);
|
|
@@ -40226,7 +40415,7 @@ ${list}`);
|
|
|
40226
40415
|
const remoteBefore = await remoteBranchExists2(headRef, { remote });
|
|
40227
40416
|
let upgradedToAuto = false;
|
|
40228
40417
|
let remoteNotAttemptedReason = "preserved-delayed-cleanup";
|
|
40229
|
-
const overrideBody = mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
|
|
40418
|
+
const overrideBody = mergifyPilot ? void 0 : mergeSquashBody ? { ...writeSquashBodyFile(mergeSquashBody), text: mergeSquashBody } : await composeOverrideBodyFile(
|
|
40230
40419
|
number,
|
|
40231
40420
|
repoArgs,
|
|
40232
40421
|
async (a, t) => (await execFileP("gh", a, { timeout: t })).stdout,
|
|
@@ -40306,7 +40495,15 @@ ${list}`);
|
|
|
40306
40495
|
});
|
|
40307
40496
|
try {
|
|
40308
40497
|
try {
|
|
40309
|
-
|
|
40498
|
+
if (mergifyPilot) {
|
|
40499
|
+
const queue = await requestMergifyMerge(number, repoForPostCleanup, closingGuardInput.text, headRefOid);
|
|
40500
|
+
if (queue.state !== "merged") {
|
|
40501
|
+
console.log(JSON.stringify({ mergeStatus: "failed", pr: number, repo: repoForPostCleanup, queue }));
|
|
40502
|
+
process.exitCode = queue.state === "refused" ? 1 : PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
40503
|
+
return;
|
|
40504
|
+
}
|
|
40505
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
40506
|
+
} else await mergeOnce();
|
|
40310
40507
|
} catch (e) {
|
|
40311
40508
|
if (!(e instanceof PrHeadBehindBaseError)) throw e;
|
|
40312
40509
|
const localCheckedOut = !foreignCwd && await prHeadCheckedOutHere(headRef, targetRepo2, Boolean(o.repo));
|
|
@@ -40524,7 +40721,7 @@ async function resolveWhoami(deps) {
|
|
|
40524
40721
|
}
|
|
40525
40722
|
|
|
40526
40723
|
// src/command-register-developer.ts
|
|
40527
|
-
var
|
|
40724
|
+
var import_node_path45 = require("node:path");
|
|
40528
40725
|
|
|
40529
40726
|
// src/wave-land.ts
|
|
40530
40727
|
function planWaveLand(prs) {
|
|
@@ -40778,26 +40975,26 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
40778
40975
|
|
|
40779
40976
|
// src/dist-drift.ts
|
|
40780
40977
|
var import_node_child_process17 = require("node:child_process");
|
|
40781
|
-
var
|
|
40978
|
+
var import_node_crypto13 = require("node:crypto");
|
|
40782
40979
|
var import_node_fs46 = require("node:fs");
|
|
40783
40980
|
var import_node_os21 = require("node:os");
|
|
40784
|
-
var
|
|
40981
|
+
var import_node_path42 = require("node:path");
|
|
40785
40982
|
|
|
40786
40983
|
// ../scripts/distribution-digest.mjs
|
|
40787
|
-
var
|
|
40984
|
+
var import_node_crypto12 = require("node:crypto");
|
|
40788
40985
|
var import_node_fs45 = require("node:fs");
|
|
40789
|
-
var
|
|
40986
|
+
var import_node_path41 = require("node:path");
|
|
40790
40987
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
40791
40988
|
function repoPath(root, declaredPath, label) {
|
|
40792
|
-
const absoluteRoot = (0,
|
|
40793
|
-
const target = (0,
|
|
40794
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
40989
|
+
const absoluteRoot = (0, import_node_path41.resolve)(root);
|
|
40990
|
+
const target = (0, import_node_path41.resolve)(root, declaredPath);
|
|
40991
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path41.sep}`)) {
|
|
40795
40992
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
40796
40993
|
}
|
|
40797
40994
|
return target;
|
|
40798
40995
|
}
|
|
40799
40996
|
function digestFiles(files) {
|
|
40800
|
-
const hash = (0,
|
|
40997
|
+
const hash = (0, import_node_crypto12.createHash)("sha256");
|
|
40801
40998
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
40802
40999
|
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
|
|
40803
41000
|
hash.update(file.relative, "utf8");
|
|
@@ -40827,7 +41024,7 @@ var DIST_ARTIFACTS = [
|
|
|
40827
41024
|
];
|
|
40828
41025
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
40829
41026
|
var ABSENT = "absent";
|
|
40830
|
-
var sha256 = (bytes) => `sha256:${(0,
|
|
41027
|
+
var sha256 = (bytes) => `sha256:${(0, import_node_crypto13.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
40831
41028
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
40832
41029
|
const committed = committedBytes ? sha256(committedBytes) : ABSENT;
|
|
40833
41030
|
const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
|
|
@@ -40905,7 +41102,7 @@ function walkFiles(root) {
|
|
|
40905
41102
|
const files = [];
|
|
40906
41103
|
const walk2 = (directory) => {
|
|
40907
41104
|
for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
40908
|
-
const child2 = (0,
|
|
41105
|
+
const child2 = (0, import_node_path42.join)(directory, entry.name);
|
|
40909
41106
|
if (entry.isDirectory()) walk2(child2);
|
|
40910
41107
|
else files.push(child2);
|
|
40911
41108
|
}
|
|
@@ -40915,10 +41112,10 @@ function walkFiles(root) {
|
|
|
40915
41112
|
}
|
|
40916
41113
|
function bomPathFor(root) {
|
|
40917
41114
|
try {
|
|
40918
|
-
const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0,
|
|
40919
|
-
return (0,
|
|
41115
|
+
const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(root, "surfaces.json"), "utf8"));
|
|
41116
|
+
return (0, import_node_path42.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
40920
41117
|
} catch {
|
|
40921
|
-
return (0,
|
|
41118
|
+
return (0, import_node_path42.join)(root, "distribution-bom.json");
|
|
40922
41119
|
}
|
|
40923
41120
|
}
|
|
40924
41121
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -40931,28 +41128,28 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
40931
41128
|
});
|
|
40932
41129
|
}
|
|
40933
41130
|
function runDistStatus(root) {
|
|
40934
|
-
const stage = (0, import_node_fs46.mkdtempSync)((0,
|
|
41131
|
+
const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path42.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
|
|
40935
41132
|
let overlayCount = 0;
|
|
40936
41133
|
try {
|
|
40937
|
-
const cliOut = (0,
|
|
40938
|
-
const hubOut = (0,
|
|
40939
|
-
rebuildTo((0,
|
|
40940
|
-
rebuildTo((0,
|
|
41134
|
+
const cliOut = (0, import_node_path42.join)(stage, "cli-dist");
|
|
41135
|
+
const hubOut = (0, import_node_path42.join)(stage, "hub-dist");
|
|
41136
|
+
rebuildTo((0, import_node_path42.join)(root, "cli"), cliOut);
|
|
41137
|
+
rebuildTo((0, import_node_path42.join)(root, "updater"), hubOut);
|
|
40941
41138
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
40942
41139
|
const rebuilt = (path2) => {
|
|
40943
41140
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
40944
|
-
return spec ? readOrNull((0,
|
|
41141
|
+
return spec ? readOrNull((0, import_node_path42.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
40945
41142
|
};
|
|
40946
|
-
const committed = (path2) => readOrNull((0,
|
|
40947
|
-
const tree = (path2) => readOrNull((0,
|
|
40948
|
-
const distRoot = (0,
|
|
40949
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
41143
|
+
const committed = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
|
|
41144
|
+
const tree = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
|
|
41145
|
+
const distRoot = (0, import_node_path42.join)(root, "cli", "dist");
|
|
41146
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path42.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
40950
41147
|
const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
|
|
40951
41148
|
const digest = (entries) => {
|
|
40952
|
-
const overlay = (0,
|
|
41149
|
+
const overlay = (0, import_node_path42.join)(stage, `overlay-${overlayCount++}`);
|
|
40953
41150
|
for (const entry of entries) {
|
|
40954
|
-
const target = (0,
|
|
40955
|
-
(0, import_node_fs46.mkdirSync)((0,
|
|
41151
|
+
const target = (0, import_node_path42.join)(overlay, entry.path);
|
|
41152
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path42.dirname)(target), { recursive: true });
|
|
40956
41153
|
(0, import_node_fs46.writeFileSync)(target, entry.bytes);
|
|
40957
41154
|
}
|
|
40958
41155
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
@@ -41030,8 +41227,8 @@ function registerEdgeCommands(program3) {
|
|
|
41030
41227
|
}
|
|
41031
41228
|
|
|
41032
41229
|
// src/schedules-lift-command.ts
|
|
41033
|
-
var
|
|
41034
|
-
var
|
|
41230
|
+
var import_promises8 = require("node:fs/promises");
|
|
41231
|
+
var import_node_path43 = require("node:path");
|
|
41035
41232
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
41036
41233
|
var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
|
|
41037
41234
|
var SchedulesLiftUsageError = class extends Error {
|
|
@@ -41051,14 +41248,14 @@ var RegistryUnreachableError = class extends Error {
|
|
|
41051
41248
|
async function readWorkflowFiles(dir) {
|
|
41052
41249
|
let names;
|
|
41053
41250
|
try {
|
|
41054
|
-
names = await (0,
|
|
41251
|
+
names = await (0, import_promises8.readdir)(dir);
|
|
41055
41252
|
} catch {
|
|
41056
41253
|
return [];
|
|
41057
41254
|
}
|
|
41058
41255
|
const files = [];
|
|
41059
41256
|
for (const name of names.sort()) {
|
|
41060
41257
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
41061
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0,
|
|
41258
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path43.join)(dir, name), "utf8") });
|
|
41062
41259
|
}
|
|
41063
41260
|
return files;
|
|
41064
41261
|
}
|
|
@@ -41142,7 +41339,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
41142
41339
|
// src/spawn-policy-core.ts
|
|
41143
41340
|
var import_node_child_process18 = require("node:child_process");
|
|
41144
41341
|
var import_node_fs47 = require("node:fs");
|
|
41145
|
-
var
|
|
41342
|
+
var import_node_path44 = require("node:path");
|
|
41146
41343
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
41147
41344
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
41148
41345
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -41228,7 +41425,7 @@ function runSpawnPolicy(root) {
|
|
|
41228
41425
|
for (const file of files) {
|
|
41229
41426
|
let raw;
|
|
41230
41427
|
try {
|
|
41231
|
-
raw = (0, import_node_fs47.readFileSync)((0,
|
|
41428
|
+
raw = (0, import_node_fs47.readFileSync)((0, import_node_path44.join)(root, file), "utf8");
|
|
41232
41429
|
} catch {
|
|
41233
41430
|
continue;
|
|
41234
41431
|
}
|
|
@@ -41248,7 +41445,7 @@ function runSpawnPolicy(root) {
|
|
|
41248
41445
|
function registerDeveloperCommands(program3) {
|
|
41249
41446
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
41250
41447
|
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) => {
|
|
41251
|
-
const path2 = (0,
|
|
41448
|
+
const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
|
|
41252
41449
|
const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
|
|
41253
41450
|
const plan = planManagedGitignore(current);
|
|
41254
41451
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
@@ -41508,10 +41705,10 @@ function registerDeveloperCommands(program3) {
|
|
|
41508
41705
|
}
|
|
41509
41706
|
|
|
41510
41707
|
// src/command-register-train-operations.ts
|
|
41511
|
-
var
|
|
41708
|
+
var import_promises10 = require("node:fs/promises");
|
|
41512
41709
|
|
|
41513
41710
|
// src/hotfix-apply.ts
|
|
41514
|
-
var
|
|
41711
|
+
var import_promises9 = require("node:fs/promises");
|
|
41515
41712
|
|
|
41516
41713
|
// src/slack-alert.ts
|
|
41517
41714
|
var SSM_REGION = "eu-central-1";
|
|
@@ -42295,7 +42492,7 @@ async function runHotfixRelease(deps, versionInput, options = {}, doctor = runTr
|
|
|
42295
42492
|
}
|
|
42296
42493
|
let lines2;
|
|
42297
42494
|
try {
|
|
42298
|
-
lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0,
|
|
42495
|
+
lines2 = summaryFileLines(await (deps.readFile ?? ((p) => (0, import_promises9.readFile)(p, "utf8")))(options.announceSummaryFile));
|
|
42299
42496
|
} catch (e) {
|
|
42300
42497
|
throw new Error(`could not read --announce-summary-file ${options.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
|
|
42301
42498
|
}
|
|
@@ -42854,7 +43051,7 @@ function checkHotfixCarries(options) {
|
|
|
42854
43051
|
|
|
42855
43052
|
// src/train-commands.ts
|
|
42856
43053
|
var import_node_fs49 = require("node:fs");
|
|
42857
|
-
var
|
|
43054
|
+
var import_node_path46 = require("node:path");
|
|
42858
43055
|
var INVOKED_ARGV = process.argv.slice(2);
|
|
42859
43056
|
var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
42860
43057
|
function resolveReleaseBumpIntent(raw) {
|
|
@@ -42866,7 +43063,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
42866
43063
|
}
|
|
42867
43064
|
function readRepoVersion() {
|
|
42868
43065
|
try {
|
|
42869
|
-
return JSON.parse((0, import_node_fs49.readFileSync)((0,
|
|
43066
|
+
return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path46.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
42870
43067
|
} catch {
|
|
42871
43068
|
return void 0;
|
|
42872
43069
|
}
|
|
@@ -43029,8 +43226,8 @@ function trainApplyDeps() {
|
|
|
43029
43226
|
// Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
|
|
43030
43227
|
announce: (args) => announceRelease({
|
|
43031
43228
|
run: async (file, cmdArgs) => (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
|
|
43032
|
-
readFile: (path2) => (0,
|
|
43033
|
-
removeFile: (path2) => (0,
|
|
43229
|
+
readFile: (path2) => (0, import_promises10.readFile)(path2, "utf8"),
|
|
43230
|
+
removeFile: (path2) => (0, import_promises10.unlink)(path2)
|
|
43034
43231
|
}, args),
|
|
43035
43232
|
// #4713 (I/O-boundary census): `null` used to mean BOTH "this project configures no edge domains"
|
|
43036
43233
|
// (a real answer) and "the registry read missed" — so a release verdict printed an environments block
|
|
@@ -43416,7 +43613,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
43416
43613
|
}
|
|
43417
43614
|
let summaryLines;
|
|
43418
43615
|
try {
|
|
43419
|
-
summaryLines = summaryFileLines(await (0,
|
|
43616
|
+
summaryLines = summaryFileLines(await (0, import_promises10.readFile)(o.announceSummaryFile, "utf8"));
|
|
43420
43617
|
} catch (e) {
|
|
43421
43618
|
return fail(`release: could not read --announce-summary-file ${o.announceSummaryFile}: ${e.message} \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
|
|
43422
43619
|
}
|
|
@@ -43610,7 +43807,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
43610
43807
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
43611
43808
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
43612
43809
|
function envHealLockPath(home) {
|
|
43613
|
-
return (0,
|
|
43810
|
+
return (0, import_node_path47.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
43614
43811
|
}
|
|
43615
43812
|
async function withEnvHealLock(what, run) {
|
|
43616
43813
|
try {
|
|
@@ -43781,7 +43978,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
43781
43978
|
// has no generated routing index would
|
|
43782
43979
|
// get a permanent — demanding an artifact it never asked for.
|
|
43783
43980
|
docsIndexState: (root) => {
|
|
43784
|
-
if (!(0, import_node_fs50.existsSync)((0,
|
|
43981
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
43785
43982
|
const real = createDocsIndexDeps(root);
|
|
43786
43983
|
let docs;
|
|
43787
43984
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -43790,7 +43987,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
43790
43987
|
},
|
|
43791
43988
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
43792
43989
|
healDocsIndex: (root) => {
|
|
43793
|
-
if (!(0, import_node_fs50.existsSync)((0,
|
|
43990
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
43794
43991
|
const real = createDocsIndexDeps(root);
|
|
43795
43992
|
let docs;
|
|
43796
43993
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -44669,16 +44866,16 @@ function ciAuditDeps2() {
|
|
|
44669
44866
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
44670
44867
|
readSeedFile: (path2) => {
|
|
44671
44868
|
if (!root) return null;
|
|
44672
|
-
const fullPath = (0,
|
|
44869
|
+
const fullPath = (0, import_node_path47.join)(root, path2);
|
|
44673
44870
|
return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
|
|
44674
44871
|
}
|
|
44675
44872
|
};
|
|
44676
44873
|
}
|
|
44677
44874
|
function hubRoot2() {
|
|
44678
|
-
const fromPkg = (0,
|
|
44875
|
+
const fromPkg = (0, import_node_path47.join)(__dirname, "..", "..");
|
|
44679
44876
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
44680
|
-
if ((0, import_node_fs50.existsSync)((0,
|
|
44681
|
-
if ((0, import_node_fs50.existsSync)((0,
|
|
44877
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(fromPkg, marker))) return fromPkg;
|
|
44878
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path47.join)(process.cwd(), marker))) return process.cwd();
|
|
44682
44879
|
return null;
|
|
44683
44880
|
}
|
|
44684
44881
|
registerQueryCommands(program2);
|
|
@@ -44817,7 +45014,7 @@ function directoryBytes(path2) {
|
|
|
44817
45014
|
return 0;
|
|
44818
45015
|
}
|
|
44819
45016
|
for (const entry of entries) {
|
|
44820
|
-
const child2 = (0,
|
|
45017
|
+
const child2 = (0, import_node_path47.join)(path2, entry.name);
|
|
44821
45018
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
44822
45019
|
else {
|
|
44823
45020
|
try {
|
|
@@ -44847,7 +45044,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
|
44847
45044
|
dirBytes,
|
|
44848
45045
|
listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
44849
45046
|
try {
|
|
44850
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
45047
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path47.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
|
|
44851
45048
|
} catch {
|
|
44852
45049
|
return { name: d.name, mtimeMs: Date.now() };
|
|
44853
45050
|
}
|
|
@@ -44861,7 +45058,7 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
44861
45058
|
return {
|
|
44862
45059
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
44863
45060
|
mtimeMs: (name) => {
|
|
44864
|
-
const p = (0,
|
|
45061
|
+
const p = (0, import_node_path47.join)(stagingRoot, name);
|
|
44865
45062
|
if (!(0, import_node_fs50.existsSync)(p)) return null;
|
|
44866
45063
|
try {
|
|
44867
45064
|
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
|