@mutmutco/codex-plugin 4.5.103 → 4.5.105
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/cli/dist/main.cjs
CHANGED
|
@@ -4747,7 +4747,7 @@ function useColor() {
|
|
|
4747
4747
|
var program = new Command();
|
|
4748
4748
|
|
|
4749
4749
|
// src/command-composition.ts
|
|
4750
|
-
var
|
|
4750
|
+
var import_node_fs55 = require("node:fs");
|
|
4751
4751
|
init_clean_exit();
|
|
4752
4752
|
init_cli_shared();
|
|
4753
4753
|
|
|
@@ -14908,7 +14908,7 @@ async function readDefaultBranchGate(repo, client, baseBranch, now = Date.now(),
|
|
|
14908
14908
|
async function gateIsProvenGreen(repo, client, baseBranch, gateFiles = DEFAULT_GATE_FILES) {
|
|
14909
14909
|
return (await readDefaultBranchGate(repo, client, baseBranch, Date.now(), gateFiles)).state === "green";
|
|
14910
14910
|
}
|
|
14911
|
-
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active") {
|
|
14911
|
+
async function activateProductRuleset(repo, rulesetBody, client, enforcement = "active", dryRun = false) {
|
|
14912
14912
|
const want = new Set(rulesetRequiredContexts({ rules: rulesetBody.rules }));
|
|
14913
14913
|
const wantStrict = rulesetStrictPolicy({ rules: rulesetBody.rules });
|
|
14914
14914
|
const wantBranches = rulesetBranchIncludes(rulesetBody);
|
|
@@ -14927,10 +14927,10 @@ async function activateProductRuleset(repo, rulesetBody, client, enforcement = "
|
|
|
14927
14927
|
if (detail.enforcement === effective && branchesMatch && strictMatch && have.size === want.size && [...want].every((c) => have.has(c))) {
|
|
14928
14928
|
return { action: "skipped", enforcement: effective, detail: `${effective} ruleset already matches required contexts, branches and up-to-date policy${kept}` };
|
|
14929
14929
|
}
|
|
14930
|
-
await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body: { ...rulesetBody, enforcement: effective }, timeoutMs: 2e4 });
|
|
14930
|
+
if (!dryRun) await client.rest("PUT", `repos/${repo}/rulesets/${existing.id}`, { body: { ...rulesetBody, enforcement: effective }, timeoutMs: 2e4 });
|
|
14931
14931
|
return { action: "updated", enforcement: effective, detail: `ruleset ${existing.id}${kept}` };
|
|
14932
14932
|
}
|
|
14933
|
-
await client.rest("POST", `repos/${repo}/rulesets`, { body: { ...rulesetBody, enforcement }, timeoutMs: 2e4 });
|
|
14933
|
+
if (!dryRun) await client.rest("POST", `repos/${repo}/rulesets`, { body: { ...rulesetBody, enforcement }, timeoutMs: 2e4 });
|
|
14934
14934
|
return { action: "created", enforcement };
|
|
14935
14935
|
}
|
|
14936
14936
|
async function gateContextEnforced(repo, rest) {
|
|
@@ -16383,7 +16383,7 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
16383
16383
|
suite,
|
|
16384
16384
|
...suite === "mm-work" && deployModel === "tenant-container" ? {
|
|
16385
16385
|
requiredBuildSecrets: ["NODE_AUTH_TOKEN=@github-packages-token"],
|
|
16386
|
-
requiredChecks: ["gate"
|
|
16386
|
+
requiredChecks: options.existing?.requiredChecks ?? ["gate"]
|
|
16387
16387
|
} : {},
|
|
16388
16388
|
repos: [`${parsedRepo.owner}/${slug}`],
|
|
16389
16389
|
branch: vars.BRANCH || (cls === "content" ? "main" : "development"),
|
|
@@ -17699,10 +17699,10 @@ var rollout_plan_default = {
|
|
|
17699
17699
|
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)."
|
|
17700
17700
|
},
|
|
17701
17701
|
baseline: {
|
|
17702
|
-
version: "4.5.
|
|
17703
|
-
tag: "v4.5.
|
|
17704
|
-
commit: "
|
|
17705
|
-
npm: "@mutmutco/cli@4.5.
|
|
17702
|
+
version: "4.5.105",
|
|
17703
|
+
tag: "v4.5.105",
|
|
17704
|
+
commit: "2edfdb644608",
|
|
17705
|
+
npm: "@mutmutco/cli@4.5.105"
|
|
17706
17706
|
},
|
|
17707
17707
|
exitCriterion: "fleet-n-of-n",
|
|
17708
17708
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17719,14 +17719,14 @@ var rollout_plan_default = {
|
|
|
17719
17719
|
repo: "mutmutco/mmi-hub",
|
|
17720
17720
|
role: "canary",
|
|
17721
17721
|
schedule: "train",
|
|
17722
|
-
v3Target: "v4.5.
|
|
17722
|
+
v3Target: "v4.5.105"
|
|
17723
17723
|
}
|
|
17724
17724
|
],
|
|
17725
17725
|
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.",
|
|
17726
17726
|
rollback: {
|
|
17727
17727
|
independent: true,
|
|
17728
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17729
|
-
v3Target: "v4.5.
|
|
17728
|
+
mechanism: "npm dist-tag latest -> 4.5.105 and redeploy the Hub Lambda from tag v4.5.105 (2edfdb644608); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17729
|
+
v3Target: "v4.5.105 (@mutmutco/cli@4.5.105, tag commit 2edfdb644608 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17730
17730
|
}
|
|
17731
17731
|
},
|
|
17732
17732
|
{
|
|
@@ -21992,7 +21992,7 @@ function renderTenantControl(r) {
|
|
|
21992
21992
|
}
|
|
21993
21993
|
|
|
21994
21994
|
// src/train-apply-phases.ts
|
|
21995
|
-
var
|
|
21995
|
+
var import_node_fs25 = require("node:fs");
|
|
21996
21996
|
var import_promises4 = require("node:fs/promises");
|
|
21997
21997
|
var import_node_path24 = require("node:path");
|
|
21998
21998
|
|
|
@@ -25765,6 +25765,7 @@ function formatTrainDoctor(v) {
|
|
|
25765
25765
|
|
|
25766
25766
|
// src/release-plain-words.ts
|
|
25767
25767
|
var import_promises3 = require("node:fs/promises");
|
|
25768
|
+
var import_node_fs24 = require("node:fs");
|
|
25768
25769
|
function releaseCutByNotes(cutBy) {
|
|
25769
25770
|
return `## Internal
|
|
25770
25771
|
Cut by ${formatSessionIdentity(cutBy)}
|
|
@@ -25779,8 +25780,44 @@ function buildPlainWordsSection(lines2, big, heading = PLAIN_WORDS_HEADING) {
|
|
|
25779
25780
|
if (big) body.push(PLAIN_WORDS_MARKER);
|
|
25780
25781
|
return body.join("\n");
|
|
25781
25782
|
}
|
|
25782
|
-
|
|
25783
|
-
|
|
25783
|
+
var SUMMARY_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing";
|
|
25784
|
+
function validatePlainWordsSummary(content) {
|
|
25785
|
+
const lines2 = content.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
25786
|
+
if (lines2[0]?.startsWith("No changes to this module")) throw new Error(`requires a real MM-Work title, not the no-change placeholder \u2014 see ${SUMMARY_GUIDE}`);
|
|
25787
|
+
if (lines2.length < 3 || lines2.length > 6) throw new Error(`requires 3\u20136 curated non-empty lines (title + 2\u20135 notes; found ${lines2.length}) \u2014 see ${SUMMARY_GUIDE}`);
|
|
25788
|
+
}
|
|
25789
|
+
function validateSuiteReleaseSummary(path2) {
|
|
25790
|
+
if (!path2) throw new Error(`requires --announce-summary-file <path> before promoting any member \u2014 see ${SUMMARY_GUIDE}`);
|
|
25791
|
+
let content;
|
|
25792
|
+
try {
|
|
25793
|
+
content = (0, import_node_fs24.readFileSync)(path2, "utf8");
|
|
25794
|
+
} catch (e) {
|
|
25795
|
+
throw new Error(`could not read summary file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
|
|
25796
|
+
}
|
|
25797
|
+
validatePlainWordsSummary(content);
|
|
25798
|
+
}
|
|
25799
|
+
async function validateReleaseSummaries(releases, files, releaseExists) {
|
|
25800
|
+
const refusals = [];
|
|
25801
|
+
for (const { repo, tag } of releases) {
|
|
25802
|
+
if (await releaseExists(repo, tag)) continue;
|
|
25803
|
+
try {
|
|
25804
|
+
validateSuiteReleaseSummary(files[repo]);
|
|
25805
|
+
} catch (e) {
|
|
25806
|
+
refusals.push(`${repo}: ${e.message}`);
|
|
25807
|
+
}
|
|
25808
|
+
}
|
|
25809
|
+
if (refusals.length) throw new Error(`suite release summaries refused; no member was released:
|
|
25810
|
+
${refusals.join("\n")}`);
|
|
25811
|
+
}
|
|
25812
|
+
async function requireSuiteReleaseSummary(deps, path2) {
|
|
25813
|
+
if (!path2) throw new Error(`a new mm-work suite Release requires --announce-summary-file <path> with a title + 2\u20135 notes \u2014 see ${SUMMARY_GUIDE}`);
|
|
25814
|
+
let content;
|
|
25815
|
+
try {
|
|
25816
|
+
content = await (deps.readFile ?? ((p) => (0, import_promises3.readFile)(p, "utf8")))(path2);
|
|
25817
|
+
} catch (e) {
|
|
25818
|
+
throw new Error(`could not read --announce-summary-file ${path2}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
|
|
25819
|
+
}
|
|
25820
|
+
validatePlainWordsSummary(content);
|
|
25784
25821
|
}
|
|
25785
25822
|
function upsertPlainWordsSection(body, section) {
|
|
25786
25823
|
let rest = body;
|
|
@@ -25810,7 +25847,8 @@ function plainWordsSummaryLines(content) {
|
|
|
25810
25847
|
async function writePlainWordsIfSuiteMember(deps, args) {
|
|
25811
25848
|
if (args.meta?.suite !== "mm-work") return;
|
|
25812
25849
|
try {
|
|
25813
|
-
|
|
25850
|
+
if (!args.summaryLines?.length) return;
|
|
25851
|
+
const lines2 = args.summaryLines;
|
|
25814
25852
|
const section = [
|
|
25815
25853
|
buildPlainWordsSection(lines2, args.big === true),
|
|
25816
25854
|
...args.summaryLinesTr?.length ? [buildPlainWordsSection(args.summaryLinesTr, false, PLAIN_WORDS_HEADING_TR)] : []
|
|
@@ -25962,14 +26000,14 @@ function readLocalGateWorkflows() {
|
|
|
25962
26000
|
const dir = (0, import_node_path24.join)(".github", "workflows");
|
|
25963
26001
|
let names;
|
|
25964
26002
|
try {
|
|
25965
|
-
names = (0,
|
|
26003
|
+
names = (0, import_node_fs25.readdirSync)(dir);
|
|
25966
26004
|
} catch {
|
|
25967
26005
|
return null;
|
|
25968
26006
|
}
|
|
25969
26007
|
const files = [];
|
|
25970
26008
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
25971
26009
|
try {
|
|
25972
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
26010
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs25.readFileSync)((0, import_node_path24.join)(dir, name), "utf8") });
|
|
25973
26011
|
} catch {
|
|
25974
26012
|
}
|
|
25975
26013
|
}
|
|
@@ -25979,14 +26017,14 @@ function readLocalWorkflows() {
|
|
|
25979
26017
|
const dir = (0, import_node_path24.join)(".github", "workflows");
|
|
25980
26018
|
let names;
|
|
25981
26019
|
try {
|
|
25982
|
-
names = (0,
|
|
26020
|
+
names = (0, import_node_fs25.readdirSync)(dir);
|
|
25983
26021
|
} catch {
|
|
25984
26022
|
return null;
|
|
25985
26023
|
}
|
|
25986
26024
|
const files = [];
|
|
25987
26025
|
for (const name of names.filter((n) => /\.ya?ml$/i.test(n))) {
|
|
25988
26026
|
try {
|
|
25989
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
26027
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs25.readFileSync)((0, import_node_path24.join)(dir, name), "utf8") });
|
|
25990
26028
|
} catch {
|
|
25991
26029
|
}
|
|
25992
26030
|
}
|
|
@@ -26868,7 +26906,7 @@ async function activeRcandLedgerCandidate(deps, cwd) {
|
|
|
26868
26906
|
}
|
|
26869
26907
|
let prior;
|
|
26870
26908
|
try {
|
|
26871
|
-
prior = parseReleaseLedger((0,
|
|
26909
|
+
prior = parseReleaseLedger((0, import_node_fs25.readFileSync)(path2, "utf8"));
|
|
26872
26910
|
} catch {
|
|
26873
26911
|
return void 0;
|
|
26874
26912
|
}
|
|
@@ -27157,6 +27195,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
27157
27195
|
}
|
|
27158
27196
|
const ledgerMode = persisted ? "strict" : "migration";
|
|
27159
27197
|
const partialRelease = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27198
|
+
if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
|
|
27160
27199
|
if (!partialRelease) {
|
|
27161
27200
|
try {
|
|
27162
27201
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
|
|
@@ -28031,6 +28070,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
28031
28070
|
const bumpIntent = await resolveTrainBumpIntent(deps, process.env, void 0, preOneZeroInForce(meta));
|
|
28032
28071
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
28033
28072
|
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
28073
|
+
if (command === "release" && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
|
|
28034
28074
|
const pipelineInput = { deps, ctx, command, meta, branchHints, watch, options, bumpIntent };
|
|
28035
28075
|
if (command === "rcand") {
|
|
28036
28076
|
if (directTrack) {
|
|
@@ -28344,7 +28384,7 @@ function planBranchRequiredContexts(input) {
|
|
|
28344
28384
|
})).filter((entry) => entry.contexts.length > 0);
|
|
28345
28385
|
return { common, pending, waiting };
|
|
28346
28386
|
}
|
|
28347
|
-
async function branchContextPlan(deps, repo, authority, meta) {
|
|
28387
|
+
async function branchContextPlan(deps, repo, authority, meta, emittedBranchesOnly = false) {
|
|
28348
28388
|
const branches = resolveRequiredCheckBranches(meta, repo);
|
|
28349
28389
|
const emittedByBranch = {};
|
|
28350
28390
|
const unreadable = [];
|
|
@@ -28354,7 +28394,10 @@ async function branchContextPlan(deps, repo, authority, meta) {
|
|
|
28354
28394
|
emittedByBranch[branch] = await resolveEmittedPrContexts(deps, repo, branch);
|
|
28355
28395
|
if (presence === void 0 || emittedByBranch[branch] === void 0) unreadable.push(branch);
|
|
28356
28396
|
}
|
|
28357
|
-
|
|
28397
|
+
const bindingBranches = emittedBranchesOnly ? branches.filter((branch) => authority.contexts.some((context) => emittedByBranch[branch]?.includes(context))) : branches;
|
|
28398
|
+
const plan = planBranchRequiredContexts({ contexts: authority.contexts, branches: bindingBranches, emittedByBranch });
|
|
28399
|
+
const waiting = planBranchRequiredContexts({ contexts: authority.contexts, branches, emittedByBranch }).waiting;
|
|
28400
|
+
return { ...plan, waiting, unreadable, bindingBranches };
|
|
28358
28401
|
}
|
|
28359
28402
|
function projectCommonAuthority(authority, common) {
|
|
28360
28403
|
const apiPayload = patchRulesetRequiredContexts(authority.apiPayload, common);
|
|
@@ -28608,8 +28651,8 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28608
28651
|
checks.push({
|
|
28609
28652
|
ok: pendingAligned,
|
|
28610
28653
|
label: "branch-specific required checks match workflow emitters",
|
|
28611
|
-
detail: pendingAligned ? void 0 :
|
|
28612
|
-
remediation: pendingAligned ? void 0 : `mmi-cli devops
|
|
28654
|
+
detail: pendingAligned ? void 0 : `pending ruleset contexts or branch conditions have not converged to the emitted registry requirements \u2014 run mmi-cli devops bootstrap apply ${repo} --only product-ruleset --execute`,
|
|
28655
|
+
remediation: pendingAligned ? void 0 : `mmi-cli devops bootstrap apply ${repo} --only product-ruleset --execute`
|
|
28613
28656
|
});
|
|
28614
28657
|
}
|
|
28615
28658
|
}
|
|
@@ -28641,7 +28684,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28641
28684
|
checks.push({
|
|
28642
28685
|
ok: aligned,
|
|
28643
28686
|
label: RULESET_REFERENCE_MATCH_LABEL,
|
|
28644
|
-
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}`)
|
|
28687
|
+
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}`) + ` \u2014 live repair: mmi-cli devops bootstrap apply ${repo} --only product-ruleset --execute`,
|
|
28645
28688
|
remediation: aligned ? void 0 : `mmi-cli devops ci reconcile --repo ${repo} --apply`
|
|
28646
28689
|
});
|
|
28647
28690
|
}
|
|
@@ -29184,9 +29227,14 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
|
|
|
29184
29227
|
}
|
|
29185
29228
|
return null;
|
|
29186
29229
|
}
|
|
29187
|
-
async function applyCiReconcileRepo(repo, deps) {
|
|
29230
|
+
async function applyCiReconcileRepo(repo, deps, options = {}) {
|
|
29231
|
+
if (options.dryRun && !options.productRulesetOnly) throw new Error("dry-run requires productRulesetOnly");
|
|
29188
29232
|
const meta = await deps.getProjectMeta(slugFromRepo(repo));
|
|
29233
|
+
if (options.productRulesetOnly && !meta?.requiredChecks?.length) {
|
|
29234
|
+
return { repo, applied: [], skipped: [], errors: ["product-ruleset requires registry META requiredChecks"] };
|
|
29235
|
+
}
|
|
29189
29236
|
const report = await auditRepoCi(repo, deps);
|
|
29237
|
+
const finish = (result2, pendingReason) => options.productRulesetOnly ? Promise.resolve(result2) : finalizeCiReconcile(repo, deps, result2, report, pendingReason);
|
|
29190
29238
|
const baseBranch = "development";
|
|
29191
29239
|
let raw = report.class === "deployable" && !report.explicitNoCi ? await fetchRulesetSeedBody(deps, repo) : null;
|
|
29192
29240
|
let authority = null;
|
|
@@ -29196,12 +29244,12 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29196
29244
|
authority = parseAuthoritativeRuleset(raw, meta, repo);
|
|
29197
29245
|
} catch (e) {
|
|
29198
29246
|
empty.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
29199
|
-
return
|
|
29247
|
+
return finish(empty);
|
|
29200
29248
|
}
|
|
29201
29249
|
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
29202
29250
|
if (hold) {
|
|
29203
29251
|
empty.skipped.push(hold);
|
|
29204
|
-
return
|
|
29252
|
+
return finish(empty, hold);
|
|
29205
29253
|
}
|
|
29206
29254
|
} else if (report.class === "deployable" && !report.explicitNoCi) {
|
|
29207
29255
|
const registryAuthority = registryAuthorityWithoutReference(meta, repo);
|
|
@@ -29209,59 +29257,64 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29209
29257
|
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch, meta);
|
|
29210
29258
|
if (hold) {
|
|
29211
29259
|
empty.skipped.push(hold);
|
|
29212
|
-
return
|
|
29260
|
+
return finish(empty, hold);
|
|
29213
29261
|
}
|
|
29214
29262
|
}
|
|
29215
29263
|
}
|
|
29216
|
-
const result = await applyCiReconcileMergeSettingsFromReport(repo, deps, report);
|
|
29264
|
+
const result = options.productRulesetOnly ? empty : await applyCiReconcileMergeSettingsFromReport(repo, deps, report);
|
|
29217
29265
|
if (report.class !== "deployable" || report.explicitNoCi) {
|
|
29218
|
-
return
|
|
29266
|
+
return finish(result);
|
|
29219
29267
|
}
|
|
29220
|
-
const refDelivery = await seedGateYml(repo, deps, meta, result);
|
|
29268
|
+
const refDelivery = options.productRulesetOnly ? void 0 : await seedGateYml(repo, deps, meta, result);
|
|
29221
29269
|
if (refDelivery === "pending") {
|
|
29222
29270
|
const reason = `${PRODUCT_RULESET_REF} is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
|
|
29223
29271
|
result.skipped.push(reason);
|
|
29224
|
-
return
|
|
29272
|
+
return finish(result, reason);
|
|
29225
29273
|
}
|
|
29226
29274
|
raw ??= await fetchRulesetSeedBody(deps, repo);
|
|
29227
29275
|
if (!raw) {
|
|
29228
29276
|
result.errors.push(`missing ${PRODUCT_RULESET_REF} on development \u2014 run bootstrap apply first`);
|
|
29229
|
-
return
|
|
29277
|
+
return finish(result);
|
|
29230
29278
|
}
|
|
29231
29279
|
if (authority == null) {
|
|
29232
29280
|
try {
|
|
29233
29281
|
authority = parseAuthoritativeRuleset(raw, meta, repo);
|
|
29234
29282
|
} catch (e) {
|
|
29235
29283
|
result.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
29236
|
-
return
|
|
29284
|
+
return finish(result);
|
|
29237
29285
|
}
|
|
29238
29286
|
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
29239
29287
|
if (hold) {
|
|
29240
29288
|
result.skipped.push(hold);
|
|
29241
|
-
return
|
|
29289
|
+
return finish(result, hold);
|
|
29242
29290
|
}
|
|
29243
29291
|
}
|
|
29244
29292
|
if (authority.contexts.length === 0) {
|
|
29245
29293
|
result.errors.push(`${authority.source} declares no required contexts`);
|
|
29246
|
-
return
|
|
29294
|
+
return finish(result);
|
|
29247
29295
|
}
|
|
29248
29296
|
let pendingPlan = null;
|
|
29249
29297
|
const declaredContexts = authority.contexts;
|
|
29250
29298
|
if (authority.source === "registry META requiredChecks") {
|
|
29251
|
-
pendingPlan = await branchContextPlan(deps, repo, authority, meta);
|
|
29299
|
+
pendingPlan = await branchContextPlan(deps, repo, authority, meta, options.productRulesetOnly);
|
|
29252
29300
|
if (pendingPlan.unreadable.length) {
|
|
29253
29301
|
const reason = `required branch workflow emitters unreadable on [${pendingPlan.unreadable.join(", ")}] \u2014 ruleset convergence deferred`;
|
|
29254
29302
|
result.skipped.push(reason);
|
|
29255
|
-
return
|
|
29303
|
+
return finish(result, reason);
|
|
29256
29304
|
}
|
|
29257
29305
|
if (pendingPlan.common.length === 0) {
|
|
29258
29306
|
result.errors.push("no common required context is emitted by every required branch \u2014 ruleset convergence deferred");
|
|
29259
|
-
return
|
|
29307
|
+
return finish(result);
|
|
29260
29308
|
}
|
|
29261
29309
|
authority = projectCommonAuthority(authority, pendingPlan.common);
|
|
29310
|
+
if (options.productRulesetOnly) {
|
|
29311
|
+
const apiPayload = patchRulesetBranchIncludes(authority.apiPayload, pendingPlan.bindingBranches);
|
|
29312
|
+
authority = { ...authority, apiPayload, branchIncludes: rulesetBranchIncludes(apiPayload) };
|
|
29313
|
+
for (const entry of pendingPlan.waiting) result.skipped.push(`${entry.branch} waits for workflow emitter(s) [${entry.contexts.join(", ")}]`);
|
|
29314
|
+
}
|
|
29262
29315
|
}
|
|
29263
29316
|
const sourceNeedsConvergence = !sameContexts(authority.committedContexts, authority.contexts) || !sameContexts(authority.committedBranchIncludes, authority.branchIncludes) || !authority.committedStrict;
|
|
29264
|
-
if (sourceNeedsConvergence) {
|
|
29317
|
+
if (sourceNeedsConvergence && !options.productRulesetOnly) {
|
|
29265
29318
|
try {
|
|
29266
29319
|
const delivery = await deliverSeedFile(
|
|
29267
29320
|
deps,
|
|
@@ -29278,15 +29331,15 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29278
29331
|
`${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}] delivered on ${delivery.plan.branch} (${delivery.plan.reason})${delivery.prUrl ? `: ${delivery.prUrl}` : ""}`
|
|
29279
29332
|
);
|
|
29280
29333
|
const reason = `${PRODUCT_RULESET_REF} authority is pending on its protected-base source PR; live ruleset convergence waits for that source to merge`;
|
|
29281
|
-
return
|
|
29334
|
+
return finish(result, reason);
|
|
29282
29335
|
} else {
|
|
29283
29336
|
result.applied.push(`reconciled ${PRODUCT_RULESET_REF} authority \u2192 contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}]`);
|
|
29284
29337
|
}
|
|
29285
29338
|
} catch (e) {
|
|
29286
29339
|
result.errors.push(`ruleset reference delivery failed: ${e.message}`);
|
|
29287
|
-
return
|
|
29340
|
+
return finish(result);
|
|
29288
29341
|
}
|
|
29289
|
-
} else {
|
|
29342
|
+
} else if (!options.productRulesetOnly) {
|
|
29290
29343
|
result.skipped.push(`${PRODUCT_RULESET_REF} already matches declared context and branch authority`);
|
|
29291
29344
|
}
|
|
29292
29345
|
let live;
|
|
@@ -29294,7 +29347,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29294
29347
|
live = await readLiveProductRuleset(deps, repo);
|
|
29295
29348
|
} catch (e) {
|
|
29296
29349
|
result.errors.push(`cannot read the live ${PRODUCT_RULESET_NAME} ruleset: ${e.message}`);
|
|
29297
|
-
return
|
|
29350
|
+
return finish(result);
|
|
29298
29351
|
}
|
|
29299
29352
|
const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
|
|
29300
29353
|
const liveBranchIncludes = live == null ? [] : rulesetBranchIncludes(live);
|
|
@@ -29312,7 +29365,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29312
29365
|
if (pendingPlan?.pending.some((entry) => entry.contexts.some((context) => unsafe.includes(context)))) {
|
|
29313
29366
|
const reason = `pending required-check rulesets left non-enforcing \u2014 [${unsafe.join(", ")}] emitted only by path-filtered workflows`;
|
|
29314
29367
|
result.skipped.push(reason);
|
|
29315
|
-
return
|
|
29368
|
+
return finish(result, reason);
|
|
29316
29369
|
}
|
|
29317
29370
|
const gateProvenGreen = await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles);
|
|
29318
29371
|
const plan = resolveProductRulesetReconcilePlan({
|
|
@@ -29329,25 +29382,25 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29329
29382
|
if (!plan.shouldActivate) {
|
|
29330
29383
|
if (plan.holdReason) {
|
|
29331
29384
|
result.skipped.push(plan.holdReason);
|
|
29332
|
-
return
|
|
29385
|
+
return finish(result, plan.holdReason);
|
|
29333
29386
|
}
|
|
29334
29387
|
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts, branches and up-to-date policy already match declared authority`);
|
|
29335
29388
|
}
|
|
29336
29389
|
if (plan.shouldActivate) {
|
|
29337
29390
|
try {
|
|
29338
|
-
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
|
|
29391
|
+
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement, options.dryRun);
|
|
29339
29392
|
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
29340
|
-
else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
29393
|
+
else result.applied.push(`product ruleset ${activation.action} (${activation.enforcement}; contexts [${authority.contexts.join(", ")}], branches [${authority.branchIncludes.join(", ")}])${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
29341
29394
|
} catch (e) {
|
|
29342
29395
|
result.errors.push(e.message);
|
|
29343
|
-
return
|
|
29396
|
+
return finish(result);
|
|
29344
29397
|
}
|
|
29345
29398
|
}
|
|
29346
29399
|
if (pendingPlan) {
|
|
29347
29400
|
if (pendingPlan.pending.length > 0 && !gateProvenGreen) {
|
|
29348
29401
|
const reason = "pending required-check rulesets left non-enforcing \u2014 the gate is not green on development";
|
|
29349
29402
|
result.skipped.push(reason);
|
|
29350
|
-
return
|
|
29403
|
+
return finish(result, reason);
|
|
29351
29404
|
}
|
|
29352
29405
|
try {
|
|
29353
29406
|
const desired = /* @__PURE__ */ new Set();
|
|
@@ -29363,20 +29416,20 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
29363
29416
|
patchRulesetRequiredContexts(statusOnly, entry.contexts),
|
|
29364
29417
|
[entry.branch]
|
|
29365
29418
|
);
|
|
29366
|
-
const activation = await activateProductRuleset(repo, payload, deps.client);
|
|
29367
|
-
(activation.action === "skipped" ? result.skipped : result.applied).push(`${name}: ${activation.action}`);
|
|
29419
|
+
const activation = await activateProductRuleset(repo, payload, deps.client, "active", options.dryRun);
|
|
29420
|
+
(activation.action === "skipped" ? result.skipped : result.applied).push(`${name}: ${activation.action} \u2014 contexts [${entry.contexts.join(", ")}]`);
|
|
29368
29421
|
}
|
|
29369
29422
|
const list = await deps.client.rest("GET", `repos/${repo}/rulesets`);
|
|
29370
29423
|
for (const stale of list ?? []) {
|
|
29371
29424
|
if (!stale.name?.startsWith(PRODUCT_PENDING_RULESET_PREFIX) || desired.has(stale.name) || stale.id == null) continue;
|
|
29372
|
-
await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
|
|
29425
|
+
if (!options.dryRun) await deps.client.rest("DELETE", `repos/${repo}/rulesets/${stale.id}`);
|
|
29373
29426
|
result.applied.push(`deleted converged ${stale.name}`);
|
|
29374
29427
|
}
|
|
29375
29428
|
} catch (e) {
|
|
29376
29429
|
result.errors.push(`pending ruleset convergence failed: ${e.message}`);
|
|
29377
29430
|
}
|
|
29378
29431
|
}
|
|
29379
|
-
return
|
|
29432
|
+
return finish(result);
|
|
29380
29433
|
}
|
|
29381
29434
|
|
|
29382
29435
|
// src/command-composition.ts
|
|
@@ -30728,7 +30781,7 @@ function registerBoardCommands(program3) {
|
|
|
30728
30781
|
}
|
|
30729
30782
|
|
|
30730
30783
|
// src/bootstrap-commands.ts
|
|
30731
|
-
var
|
|
30784
|
+
var import_node_fs29 = require("node:fs");
|
|
30732
30785
|
var import_node_os17 = require("node:os");
|
|
30733
30786
|
var import_node_path28 = require("node:path");
|
|
30734
30787
|
init_cli_shared();
|
|
@@ -30914,7 +30967,7 @@ init_github_client();
|
|
|
30914
30967
|
init_cli_shared();
|
|
30915
30968
|
|
|
30916
30969
|
// src/port-registry.ts
|
|
30917
|
-
var
|
|
30970
|
+
var import_node_fs26 = require("node:fs");
|
|
30918
30971
|
var import_node_path25 = require("node:path");
|
|
30919
30972
|
|
|
30920
30973
|
// ../infra/port-geometry.mjs
|
|
@@ -30929,8 +30982,8 @@ function nextPortBlock(registry2) {
|
|
|
30929
30982
|
return [base, base + PORT_SPAN];
|
|
30930
30983
|
}
|
|
30931
30984
|
function loadPortRegistry(path2) {
|
|
30932
|
-
if (!(0,
|
|
30933
|
-
const raw = JSON.parse((0,
|
|
30985
|
+
if (!(0, import_node_fs26.existsSync)(path2)) return {};
|
|
30986
|
+
const raw = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
|
|
30934
30987
|
const out = {};
|
|
30935
30988
|
for (const [key, value] of Object.entries(raw)) {
|
|
30936
30989
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -30960,7 +31013,7 @@ function existingPortRange(repo, registry2) {
|
|
|
30960
31013
|
function portRangeInfraAt(root, source) {
|
|
30961
31014
|
const registryPath = (0, import_node_path25.join)(root, "infra", "port-ranges.json");
|
|
30962
31015
|
const ddbScriptPath = (0, import_node_path25.join)(root, "infra", "port-ddb.mjs");
|
|
30963
|
-
if (!(0,
|
|
31016
|
+
if (!(0, import_node_fs26.existsSync)(registryPath) || !(0, import_node_fs26.existsSync)(ddbScriptPath)) return null;
|
|
30964
31017
|
return { root, source, registryPath, ddbScriptPath };
|
|
30965
31018
|
}
|
|
30966
31019
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
@@ -31042,7 +31095,7 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
31042
31095
|
}
|
|
31043
31096
|
|
|
31044
31097
|
// src/box-commands.ts
|
|
31045
|
-
var
|
|
31098
|
+
var import_node_fs28 = require("node:fs");
|
|
31046
31099
|
var import_node_child_process10 = require("node:child_process");
|
|
31047
31100
|
var import_node_os16 = require("node:os");
|
|
31048
31101
|
var import_node_path27 = require("node:path");
|
|
@@ -31174,7 +31227,7 @@ function applyRemoteScript(items, date) {
|
|
|
31174
31227
|
init_clean_exit();
|
|
31175
31228
|
|
|
31176
31229
|
// src/secrets-commands.ts
|
|
31177
|
-
var
|
|
31230
|
+
var import_node_fs27 = require("node:fs");
|
|
31178
31231
|
var import_node_path26 = require("node:path");
|
|
31179
31232
|
var import_node_os15 = require("node:os");
|
|
31180
31233
|
init_cli_shared();
|
|
@@ -32336,8 +32389,8 @@ async function decryptRailsCredentials(input) {
|
|
|
32336
32389
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
32337
32390
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
32338
32391
|
};
|
|
32339
|
-
if ((0,
|
|
32340
|
-
env.RAILS_MASTER_KEY = (0,
|
|
32392
|
+
if ((0, import_node_fs27.existsSync)(masterKeyPath)) {
|
|
32393
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs27.readFileSync)(masterKeyPath, "utf8").trim();
|
|
32341
32394
|
}
|
|
32342
32395
|
const script = [
|
|
32343
32396
|
'require "json"',
|
|
@@ -32347,9 +32400,9 @@ async function decryptRailsCredentials(input) {
|
|
|
32347
32400
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
32348
32401
|
"puts JSON.generate(config.config)"
|
|
32349
32402
|
].join("\n");
|
|
32350
|
-
const scriptDir = (0,
|
|
32403
|
+
const scriptDir = (0, import_node_fs27.mkdtempSync)((0, import_node_path26.join)((0, import_node_os15.tmpdir)(), "mmi-rails-decrypt-"));
|
|
32351
32404
|
const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
|
|
32352
|
-
(0,
|
|
32405
|
+
(0, import_node_fs27.writeFileSync)(scriptPath, script, "utf8");
|
|
32353
32406
|
try {
|
|
32354
32407
|
const args = ["exec", "ruby", scriptPath];
|
|
32355
32408
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -32361,7 +32414,7 @@ async function decryptRailsCredentials(input) {
|
|
|
32361
32414
|
});
|
|
32362
32415
|
return JSON.parse(stdout);
|
|
32363
32416
|
} finally {
|
|
32364
|
-
(0,
|
|
32417
|
+
(0, import_node_fs27.rmSync)(scriptDir, { recursive: true, force: true });
|
|
32365
32418
|
}
|
|
32366
32419
|
}
|
|
32367
32420
|
async function readSecretStdin() {
|
|
@@ -32451,7 +32504,7 @@ function registerSecretsCommands(program3) {
|
|
|
32451
32504
|
let body;
|
|
32452
32505
|
if (o.file) {
|
|
32453
32506
|
try {
|
|
32454
|
-
body = (0,
|
|
32507
|
+
body = (0, import_node_fs27.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
|
|
32455
32508
|
} catch (e) {
|
|
32456
32509
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
32457
32510
|
}
|
|
@@ -32587,7 +32640,7 @@ function registerSecretsCommands(program3) {
|
|
|
32587
32640
|
{
|
|
32588
32641
|
...d,
|
|
32589
32642
|
decryptRailsCredentials,
|
|
32590
|
-
removeFile: (path2) => (0,
|
|
32643
|
+
removeFile: (path2) => (0, import_node_fs27.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
|
|
32591
32644
|
},
|
|
32592
32645
|
{
|
|
32593
32646
|
repo: o.repo,
|
|
@@ -32635,8 +32688,6 @@ function registerSecretsCommands(program3) {
|
|
|
32635
32688
|
var BOX_KEYS = {
|
|
32636
32689
|
/** Opens every MM box INCLUDING the CI runner (`mmi-runner`). The general-purpose MM key. */
|
|
32637
32690
|
mmMaster: "HETZNER_MASTER_SSH_PRIVATE_KEY",
|
|
32638
|
-
/** Opens the OGUZ project's boxes. */
|
|
32639
|
-
oguzMaster: "HETZNER_OGUZ_MASTER_SSH_PRIVATE_KEY",
|
|
32640
32691
|
/** The deploy key. Opens MM prod/tenant boxes but NOT `mmi-runner` — a real trap: reaching for this key
|
|
32641
32692
|
* to get into the CI runner fails, and the failure reads like a broken box rather than a wrong key. */
|
|
32642
32693
|
mmDeploy: "HETZNER_SSH_PRIVATE_KEY"
|
|
@@ -32645,11 +32696,10 @@ var HETZNER_VAULT_SLUG = "_org";
|
|
|
32645
32696
|
var HETZNER_NS = "hetzner";
|
|
32646
32697
|
var nsKey = (key) => `${HETZNER_NS}/${key}`;
|
|
32647
32698
|
var PROJECT_API_TOKEN = {
|
|
32648
|
-
MM: nsKey("HETZNER_MM_API_TOKEN")
|
|
32649
|
-
OGUZ: nsKey("HETZNER_OGUZ_API_TOKEN")
|
|
32699
|
+
MM: nsKey("HETZNER_MM_API_TOKEN")
|
|
32650
32700
|
};
|
|
32651
|
-
function sshKeyForProject(
|
|
32652
|
-
return
|
|
32701
|
+
function sshKeyForProject(_project) {
|
|
32702
|
+
return BOX_KEYS.mmMaster;
|
|
32653
32703
|
}
|
|
32654
32704
|
function vaultPath(key) {
|
|
32655
32705
|
return `/mmi-future/${HETZNER_VAULT_SLUG}/${HETZNER_NS}/${key}`;
|
|
@@ -32752,7 +32802,7 @@ var HETZNER_API = "https://api.hetzner.cloud/v1/servers";
|
|
|
32752
32802
|
var HETZNER_TIMEOUT_MS = 15e3;
|
|
32753
32803
|
var PER_PAGE = 50;
|
|
32754
32804
|
var MAX_PAGES = 20;
|
|
32755
|
-
var PROJECTS = ["MM"
|
|
32805
|
+
var PROJECTS = ["MM"];
|
|
32756
32806
|
async function fetchProjectBoxes(project2, token) {
|
|
32757
32807
|
const boxes = [];
|
|
32758
32808
|
let page = 1;
|
|
@@ -32846,7 +32896,7 @@ function registerBoxCommands(program3) {
|
|
|
32846
32896
|
}
|
|
32847
32897
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
32848
32898
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
32849
|
-
if (wroteScript) (0,
|
|
32899
|
+
if (wroteScript) (0, import_node_fs28.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
32850
32900
|
if (o.json) {
|
|
32851
32901
|
console.log(JSON.stringify({
|
|
32852
32902
|
box: found,
|
|
@@ -32920,17 +32970,17 @@ function hubGitShow(ref) {
|
|
|
32920
32970
|
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
32921
32971
|
}
|
|
32922
32972
|
function runOnBox(found, script) {
|
|
32923
|
-
const dir = (0,
|
|
32973
|
+
const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-box-control-"));
|
|
32924
32974
|
try {
|
|
32925
32975
|
const recipe = (0, import_node_path27.join)(dir, "recipe.sh");
|
|
32926
|
-
(0,
|
|
32976
|
+
(0, import_node_fs28.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
32927
32977
|
const r = (0, import_node_child_process10.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
32928
32978
|
if (r.status !== 0) {
|
|
32929
32979
|
throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
|
|
32930
32980
|
}
|
|
32931
32981
|
return r.stdout;
|
|
32932
32982
|
} finally {
|
|
32933
|
-
(0,
|
|
32983
|
+
(0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
|
|
32934
32984
|
}
|
|
32935
32985
|
}
|
|
32936
32986
|
function formatControl(mode, box, rows) {
|
|
@@ -34433,8 +34483,8 @@ function suiteModulePackageAccessCheck(suite, deployModel) {
|
|
|
34433
34483
|
if (suite !== "mm-work" || deployModel !== "tenant-container") return void 0;
|
|
34434
34484
|
return {
|
|
34435
34485
|
ok: true,
|
|
34436
|
-
label: "mmwork-ds package access (manual org-admin step; unverified)",
|
|
34437
|
-
detail:
|
|
34486
|
+
label: "mmwork-ds + mmwork-screens package access (manual org-admin step; unverified)",
|
|
34487
|
+
detail: `An org admin must add this repository with Read under BOTH @mutmutco/mmwork-ds and @mutmutco/mmwork-screens \u2192 Package settings \u2192 Manage Actions access (#7984). No GitHub REST API can verify or grant this; without it the image build or the screens job fails with 403 read_package. Once screens is green, require it: mmi-cli oracle org project set <owner/repo> --var 'requiredChecks=["gate","screens / screens"]'.`
|
|
34438
34488
|
};
|
|
34439
34489
|
}
|
|
34440
34490
|
function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
@@ -34565,12 +34615,12 @@ async function verifyTargetedSeedPr(repo, number, plan, target, expected, client
|
|
|
34565
34615
|
// src/bootstrap-commands.ts
|
|
34566
34616
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
34567
34617
|
async function gateRenderDiff(current, rendered) {
|
|
34568
|
-
const dir = (0,
|
|
34618
|
+
const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path28.join)((0, import_node_os17.tmpdir)(), "mmi-gate-diff-"));
|
|
34569
34619
|
const before = (0, import_node_path28.join)(dir, "committed-gate.yml");
|
|
34570
34620
|
const after = (0, import_node_path28.join)(dir, "registry-render.yml");
|
|
34571
34621
|
try {
|
|
34572
|
-
(0,
|
|
34573
|
-
(0,
|
|
34622
|
+
(0, import_node_fs29.writeFileSync)(before, current);
|
|
34623
|
+
(0, import_node_fs29.writeFileSync)(after, rendered);
|
|
34574
34624
|
try {
|
|
34575
34625
|
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
34576
34626
|
} catch (e) {
|
|
@@ -34579,7 +34629,7 @@ async function gateRenderDiff(current, rendered) {
|
|
|
34579
34629
|
throw e;
|
|
34580
34630
|
}
|
|
34581
34631
|
} finally {
|
|
34582
|
-
(0,
|
|
34632
|
+
(0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
|
|
34583
34633
|
}
|
|
34584
34634
|
}
|
|
34585
34635
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -34639,13 +34689,13 @@ function registerBootstrapCommands(program3) {
|
|
|
34639
34689
|
})),
|
|
34640
34690
|
pushAllowlistOwners: verifyOwners,
|
|
34641
34691
|
pushAllowlistOwnersRead: verifyOwnersRead,
|
|
34642
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
34692
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null,
|
|
34643
34693
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
34644
34694
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
34645
34695
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
34646
34696
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
34647
34697
|
// sanction, which is the pre-#3664 behaviour.
|
|
34648
|
-
sanctionedAdmins: (0,
|
|
34698
|
+
sanctionedAdmins: (0, import_node_fs29.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs29.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
34649
34699
|
requiredGcpApis: (() => {
|
|
34650
34700
|
const v = meta?.requiredGcpApis;
|
|
34651
34701
|
if (Array.isArray(v)) return v;
|
|
@@ -34723,14 +34773,14 @@ function registerBootstrapCommands(program3) {
|
|
|
34723
34773
|
bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
|
|
34724
34774
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
34725
34775
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
34726
|
-
if (!(0,
|
|
34776
|
+
if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
|
|
34727
34777
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
34728
34778
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
34729
|
-
const manifest = loadBootstrapSeeds((0,
|
|
34779
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
|
|
34730
34780
|
const hubContents = /* @__PURE__ */ new Map();
|
|
34731
34781
|
for (const s of manifest.seeds) {
|
|
34732
34782
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
34733
|
-
hubContents.set(s.target, (0,
|
|
34783
|
+
hubContents.set(s.target, (0, import_node_fs29.existsSync)(s.target) ? (0, import_node_fs29.readFileSync)(s.target, "utf8") : null);
|
|
34734
34784
|
}
|
|
34735
34785
|
let targets;
|
|
34736
34786
|
let classOf = (_repo) => "deployable";
|
|
@@ -34830,7 +34880,7 @@ function registerBootstrapCommands(program3) {
|
|
|
34830
34880
|
}
|
|
34831
34881
|
if (findings.some((f) => f.state !== "waived")) process.exitCode = 1;
|
|
34832
34882
|
});
|
|
34833
|
-
bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").addOption(new Option("--class <class>", "deployable | content").default("deployable").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--box <name>", "resolve this live box and plan free registry DEPLOY# ports (container deploy models)").option("--domains <stages>", "optional comma-separated dev=host,rc=host,main=host deploy domains (requires --box)").option("--only <target>", "deliver ONLY this manifest target (#3818) \u2014 one file, no labels/ruleset/registry writes; or the special
|
|
34883
|
+
bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").addOption(new Option("--class <class>", "deployable | content").default("deployable").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--box <name>", "resolve this live box and plan free registry DEPLOY# ports (container deploy models)").option("--domains <stages>", "optional comma-separated dev=host,rc=host,main=host deploy domains (requires --box)").option("--only <target>", "deliver ONLY this manifest target (#3818) \u2014 one file, no labels/ruleset/registry writes; or the special targets environments (#5035) / product-ruleset (#7986) to reconcile only that live surface (dry-run classifies, --execute writes)").option("--from-config", "with --only gate.yml: adopt an equal legacy gate with a render stamp, then update from registry config via review PR; refuse hand edits").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo, cmdOpts) => {
|
|
34834
34884
|
const o = {
|
|
34835
34885
|
class: rawValue("--class", "deployable"),
|
|
34836
34886
|
projectType: rawValue("--project-type", ""),
|
|
@@ -34917,25 +34967,45 @@ function registerBootstrapCommands(program3) {
|
|
|
34917
34967
|
}
|
|
34918
34968
|
return;
|
|
34919
34969
|
}
|
|
34970
|
+
if (o.only.trim() === "product-ruleset") {
|
|
34971
|
+
if (cmdOpts.var?.length) return fail("bootstrap apply: --only product-ruleset accepts no --var overrides; set registry META instead");
|
|
34972
|
+
const read = await fetchProjectBySlugChecked(parsedRepo.slug, registryClientDeps(await loadConfig()));
|
|
34973
|
+
if (!read.ok) return failGraceful(`bootstrap apply: registry META unreadable (${read.error}); refusing product ruleset reconciliation`);
|
|
34974
|
+
if (!read.project?.requiredChecks?.length) return failGraceful("bootstrap apply: --only product-ruleset requires registry META requiredChecks");
|
|
34975
|
+
const result = await applyCiReconcileRepo(repo, {
|
|
34976
|
+
client: defaultGitHubClient(),
|
|
34977
|
+
listProjects: async () => [],
|
|
34978
|
+
getProjectMeta: async () => read.project
|
|
34979
|
+
}, { productRulesetOnly: true, dryRun: !o.execute });
|
|
34980
|
+
if (o.json) console.log(JSON.stringify(result, null, 2));
|
|
34981
|
+
else {
|
|
34982
|
+
console.log(`bootstrap apply --only product-ruleset \u2014 ${o.execute ? "LIVE" : "dry-run; no writes"} (${repo}):`);
|
|
34983
|
+
result.applied.forEach((line) => console.log(` ${o.execute ? "APPLY" : "PLAN"} ${line}`));
|
|
34984
|
+
result.skipped.forEach((line) => console.log(` SKIP ${line}`));
|
|
34985
|
+
result.errors.forEach((line) => console.error(` REFUSED ${line}`));
|
|
34986
|
+
}
|
|
34987
|
+
if (result.errors.length) return failGraceful("bootstrap apply: product ruleset reconciliation failed");
|
|
34988
|
+
return;
|
|
34989
|
+
}
|
|
34920
34990
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
34921
|
-
if (!(0,
|
|
34991
|
+
if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
34922
34992
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
34923
34993
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
34924
|
-
const manifest = loadBootstrapSeeds((0,
|
|
34994
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
|
|
34925
34995
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
34926
34996
|
const slug = parsedRepo.slug;
|
|
34927
34997
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
34928
34998
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
34929
|
-
const readFile10 = (p) => (0,
|
|
34999
|
+
const readFile10 = (p) => (0, import_node_fs29.existsSync)(p) ? (0, import_node_fs29.readFileSync)(p, "utf8") : null;
|
|
34930
35000
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
34931
35001
|
const putSeed = async (target, content, ref, sha) => {
|
|
34932
35002
|
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
34933
|
-
(0,
|
|
35003
|
+
(0, import_node_fs29.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
34934
35004
|
try {
|
|
34935
35005
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
34936
35006
|
} finally {
|
|
34937
35007
|
try {
|
|
34938
|
-
(0,
|
|
35008
|
+
(0, import_node_fs29.unlinkSync)(tmp);
|
|
34939
35009
|
} catch {
|
|
34940
35010
|
}
|
|
34941
35011
|
}
|
|
@@ -35003,9 +35073,10 @@ function registerBootstrapCommands(program3) {
|
|
|
35003
35073
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
35004
35074
|
if (vars.SUITE === "mm-work" || registryMeta?.suite === "mm-work") {
|
|
35005
35075
|
if (applyDeployModel === "tenant-container") {
|
|
35006
|
-
const
|
|
35076
|
+
const requiredChecks = registryMeta?.requiredChecks ?? ["gate"];
|
|
35077
|
+
const moduleGate = withDerivedRepoVars({}, parsedRepo, o.class, effectiveTrack, registryRequiredCheckBranches, requiredChecks);
|
|
35007
35078
|
vars.GATE_RULESET_CONTEXTS_JSON = moduleGate.GATE_RULESET_CONTEXTS_JSON;
|
|
35008
|
-
if (registryMeta) registryMeta = { ...registryMeta, requiredChecks
|
|
35079
|
+
if (registryMeta) registryMeta = { ...registryMeta, requiredChecks };
|
|
35009
35080
|
}
|
|
35010
35081
|
}
|
|
35011
35082
|
const controlClient = defaultGitHubClient();
|
|
@@ -35570,10 +35641,10 @@ LIVE apply to ${repo}:
|
|
|
35570
35641
|
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
|
|
35571
35642
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
35572
35643
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
35573
|
-
if (!(0,
|
|
35644
|
+
if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
35574
35645
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
35575
35646
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
35576
|
-
const manifest = loadBootstrapSeeds((0,
|
|
35647
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
|
|
35577
35648
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
35578
35649
|
if (!o.target) {
|
|
35579
35650
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -35582,9 +35653,9 @@ LIVE apply to ${repo}:
|
|
|
35582
35653
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
35583
35654
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
35584
35655
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
35585
|
-
if (!seed.managedBlock && !(0,
|
|
35586
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
35587
|
-
const readSeedFile2 = (path2) => (0,
|
|
35656
|
+
if (!seed.managedBlock && !(0, import_node_fs29.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
35657
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs29.readFileSync)(seed.target, "utf8");
|
|
35658
|
+
const readSeedFile2 = (path2) => (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null;
|
|
35588
35659
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
35589
35660
|
const cfg = await loadConfig();
|
|
35590
35661
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -35593,9 +35664,9 @@ LIVE apply to ${repo}:
|
|
|
35593
35664
|
}
|
|
35594
35665
|
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
35595
35666
|
let independentCount = rosterRepos.length;
|
|
35596
|
-
if ((0,
|
|
35667
|
+
if ((0, import_node_fs29.existsSync)("projects.json")) {
|
|
35597
35668
|
try {
|
|
35598
|
-
const local = JSON.parse((0,
|
|
35669
|
+
const local = JSON.parse((0, import_node_fs29.readFileSync)("projects.json", "utf8"));
|
|
35599
35670
|
const localRepos = /* @__PURE__ */ new Set();
|
|
35600
35671
|
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
35601
35672
|
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
@@ -35768,12 +35839,12 @@ LIVE apply to ${repo}:
|
|
|
35768
35839
|
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
35769
35840
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
35770
35841
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
35771
|
-
(0,
|
|
35842
|
+
(0, import_node_fs29.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
35772
35843
|
try {
|
|
35773
35844
|
await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
35774
35845
|
} finally {
|
|
35775
35846
|
try {
|
|
35776
|
-
(0,
|
|
35847
|
+
(0, import_node_fs29.unlinkSync)(tmp);
|
|
35777
35848
|
} catch {
|
|
35778
35849
|
}
|
|
35779
35850
|
}
|
|
@@ -35842,10 +35913,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
35842
35913
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
35843
35914
|
}
|
|
35844
35915
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
35845
|
-
if (!(0,
|
|
35916
|
+
if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
|
|
35846
35917
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
35847
35918
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
35848
|
-
const manifest = loadBootstrapSeeds((0,
|
|
35919
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
|
|
35849
35920
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
35850
35921
|
if (!o.target) {
|
|
35851
35922
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -35862,10 +35933,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
35862
35933
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
35863
35934
|
let candidates;
|
|
35864
35935
|
if (o.record) {
|
|
35865
|
-
if (!(0,
|
|
35936
|
+
if (!(0, import_node_fs29.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
35866
35937
|
let parsed;
|
|
35867
35938
|
try {
|
|
35868
|
-
parsed = JSON.parse((0,
|
|
35939
|
+
parsed = JSON.parse((0, import_node_fs29.readFileSync)(o.record, "utf8"));
|
|
35869
35940
|
} catch (e) {
|
|
35870
35941
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
35871
35942
|
}
|
|
@@ -35943,12 +36014,12 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
35943
36014
|
existingSha = void 0;
|
|
35944
36015
|
}
|
|
35945
36016
|
const tmp = (0, import_node_path28.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
35946
|
-
(0,
|
|
36017
|
+
(0, import_node_fs29.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
35947
36018
|
try {
|
|
35948
36019
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
35949
36020
|
} finally {
|
|
35950
36021
|
try {
|
|
35951
|
-
(0,
|
|
36022
|
+
(0, import_node_fs29.unlinkSync)(tmp);
|
|
35952
36023
|
} catch {
|
|
35953
36024
|
}
|
|
35954
36025
|
}
|
|
@@ -36404,7 +36475,7 @@ async function waitSuiteDev(deps, opts) {
|
|
|
36404
36475
|
}
|
|
36405
36476
|
|
|
36406
36477
|
// src/suite-train.ts
|
|
36407
|
-
var
|
|
36478
|
+
var import_node_fs30 = require("node:fs");
|
|
36408
36479
|
var import_node_path29 = require("node:path");
|
|
36409
36480
|
var import_node_crypto14 = require("node:crypto");
|
|
36410
36481
|
|
|
@@ -36434,6 +36505,17 @@ var SCHEMA = "mmi-suite-cut/v1";
|
|
|
36434
36505
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
36435
36506
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
36436
36507
|
var RELEASE_TAG = /^v\d+\.\d+\.\d+$/;
|
|
36508
|
+
async function reapplyRulesets(deps, record, after) {
|
|
36509
|
+
for (const member of record.members) {
|
|
36510
|
+
try {
|
|
36511
|
+
member.ruleset = { after, ok: true, lines: await deps.productRuleset(member.repo) };
|
|
36512
|
+
} catch (e) {
|
|
36513
|
+
member.ruleset = { after, ok: false, lines: [e.message] };
|
|
36514
|
+
console.warn(`${member.repo}: product ruleset not re-applied (${e.message}); run mmi-cli devops bootstrap apply ${member.repo} --only product-ruleset --execute from the MMI-Hub checkout`);
|
|
36515
|
+
}
|
|
36516
|
+
writeSuiteCut(deps.recordPath, record);
|
|
36517
|
+
}
|
|
36518
|
+
}
|
|
36437
36519
|
async function kitCheck(deps, shellSha, bundles, where) {
|
|
36438
36520
|
const shell2 = shellSha ? await deps.shellKit(shellSha) : void 0;
|
|
36439
36521
|
const kits = await Promise.all(bundles.map(async (b) => ({ id: b.id, kit: b.url ? await deps.bundleKit(b.url) : void 0 })));
|
|
@@ -36480,7 +36562,7 @@ function suiteReleaseTag(rc) {
|
|
|
36480
36562
|
function readSuiteCut(path2) {
|
|
36481
36563
|
let raw;
|
|
36482
36564
|
try {
|
|
36483
|
-
raw = (0,
|
|
36565
|
+
raw = (0, import_node_fs30.readFileSync)(path2, "utf8");
|
|
36484
36566
|
} catch (e) {
|
|
36485
36567
|
if (e.code === "ENOENT") return void 0;
|
|
36486
36568
|
throw e;
|
|
@@ -36491,16 +36573,16 @@ function readSuiteCut(path2) {
|
|
|
36491
36573
|
} catch {
|
|
36492
36574
|
throw new Error(`suite cut record unreadable at ${path2}; repair it, never recut`);
|
|
36493
36575
|
}
|
|
36494
|
-
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinsUnchanged !== void 0 && (record.pinsUnchanged !== true || record.pinPr !== void 0) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr && !record.pinsUnchanged) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0)))
|
|
36576
|
+
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinsUnchanged !== void 0 && (record.pinsUnchanged !== true || record.pinPr !== void 0) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr && !record.pinsUnchanged) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0) || m.ruleset !== void 0 && (!["rc", "main"].includes(m.ruleset.after) || typeof m.ruleset.ok !== "boolean" || !Array.isArray(m.ruleset.lines))))
|
|
36495
36577
|
throw new Error(`suite cut record has unknown or invalid state at ${path2}; repair it, never recut`);
|
|
36496
36578
|
return record;
|
|
36497
36579
|
}
|
|
36498
36580
|
function writeSuiteCut(path2, record) {
|
|
36499
|
-
(0,
|
|
36581
|
+
(0, import_node_fs30.mkdirSync)((0, import_node_path29.dirname)(path2), { recursive: true });
|
|
36500
36582
|
const temp = `${path2}.${(0, import_node_crypto14.randomBytes)(8).toString("hex")}.tmp`;
|
|
36501
36583
|
try {
|
|
36502
|
-
(0,
|
|
36503
|
-
(0,
|
|
36584
|
+
(0, import_node_fs30.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
36585
|
+
(0, import_node_fs30.renameSync)(temp, path2);
|
|
36504
36586
|
} catch (e) {
|
|
36505
36587
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
36506
36588
|
}
|
|
@@ -36549,25 +36631,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
36549
36631
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
36550
36632
|
}
|
|
36551
36633
|
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
36552
|
-
if (!(0,
|
|
36634
|
+
if (!(0, import_node_fs30.existsSync)(deps.ledgerPath)) return void 0;
|
|
36553
36635
|
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
36554
|
-
const ledger = parseReleaseLedger((0,
|
|
36636
|
+
const ledger = parseReleaseLedger((0, import_node_fs30.readFileSync)(deps.ledgerPath, "utf8"));
|
|
36555
36637
|
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
36556
36638
|
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
36557
36639
|
if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
|
|
36558
36640
|
if ((await deps.rcHead())?.toLowerCase() === ledger.tagSha.toLowerCase()) throw new Error(`origin rc already points at ${ledger.tag}; it was promoted, so its ledger is not retired`);
|
|
36559
36641
|
const archive = (0, import_node_path29.join)((0, import_node_path29.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
|
|
36560
|
-
if ((0,
|
|
36642
|
+
if ((0, import_node_fs30.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
|
|
36561
36643
|
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
36562
|
-
await fence(() => (0,
|
|
36644
|
+
await fence(() => (0, import_node_fs30.renameSync)(deps.ledgerPath, archive));
|
|
36563
36645
|
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
36564
36646
|
});
|
|
36565
36647
|
}
|
|
36566
36648
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
36567
36649
|
const record = abandonableCut(recordPath, ownerGo);
|
|
36568
36650
|
const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
36569
|
-
if ((0,
|
|
36570
|
-
(0,
|
|
36651
|
+
if ((0, import_node_fs30.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
36652
|
+
(0, import_node_fs30.renameSync)(recordPath, archive);
|
|
36571
36653
|
return { archive, orphanTags: record.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
36572
36654
|
}
|
|
36573
36655
|
async function suiteRestoreMainPins(deps) {
|
|
@@ -36633,12 +36715,12 @@ async function suiteRcand(deps, opts) {
|
|
|
36633
36715
|
}
|
|
36634
36716
|
if ((record?.announcement === "sent" || record?.announcement === "skipped") && record.leadReleased) {
|
|
36635
36717
|
const archive = `${deps.recordPath}.${record.leadRcTag}.json`;
|
|
36636
|
-
if ((0,
|
|
36637
|
-
(0,
|
|
36718
|
+
if ((0, import_node_fs30.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
36719
|
+
(0, import_node_fs30.renameSync)(deps.recordPath, archive);
|
|
36638
36720
|
record = void 0;
|
|
36639
36721
|
}
|
|
36640
36722
|
if (record && !record.pinPr && !record.leadRcTag && record.members.every((m) => !m.tag)) {
|
|
36641
|
-
(0,
|
|
36723
|
+
(0, import_node_fs30.rmSync)(deps.recordPath, { force: true });
|
|
36642
36724
|
record = void 0;
|
|
36643
36725
|
}
|
|
36644
36726
|
if (!record) {
|
|
@@ -36692,7 +36774,7 @@ async function suiteRcand(deps, opts) {
|
|
|
36692
36774
|
quiet.push(member);
|
|
36693
36775
|
}
|
|
36694
36776
|
if (record.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record.leadDevelopment, record.leadPreviousRc)) {
|
|
36695
|
-
(0,
|
|
36777
|
+
(0, import_node_fs30.rmSync)(deps.recordPath, { force: true });
|
|
36696
36778
|
throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
|
|
36697
36779
|
}
|
|
36698
36780
|
for (const member of quiet) {
|
|
@@ -36710,7 +36792,7 @@ async function suiteRcand(deps, opts) {
|
|
|
36710
36792
|
}
|
|
36711
36793
|
if (refusals.length) {
|
|
36712
36794
|
const untouched = record.members.every((m) => !m.tag) && !record.pinPr;
|
|
36713
|
-
if (untouched) (0,
|
|
36795
|
+
if (untouched) (0, import_node_fs30.rmSync)(deps.recordPath, { force: true });
|
|
36714
36796
|
throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
|
|
36715
36797
|
${refusals.join("\n")}
|
|
36716
36798
|
Each module must ship a live release that bind-mounts <deployPath>/data read-only and serves <data>/rc/module.js at /rc/module.js (docs/Guides/suite-trains.md).`);
|
|
@@ -36773,6 +36855,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
|
|
|
36773
36855
|
record.leadRcSha = leadCut.sha;
|
|
36774
36856
|
writeSuiteCut(deps.recordPath, record);
|
|
36775
36857
|
}
|
|
36858
|
+
await reapplyRulesets(deps, record, "rc");
|
|
36776
36859
|
return { plan, record };
|
|
36777
36860
|
}
|
|
36778
36861
|
async function suiteRelease(deps, ownerGo, big = false) {
|
|
@@ -36808,6 +36891,10 @@ async function suiteRelease(deps, ownerGo, big = false) {
|
|
|
36808
36891
|
throw new Error(`${member.repo}: live backend changed after the cut (${member.backend} \u2192 ${live.version}); release refused`);
|
|
36809
36892
|
}
|
|
36810
36893
|
if ((await deps.tagSha(SUITE_REPO, record.leadRcTag))?.toLowerCase() !== record.leadRcSha?.toLowerCase()) throw new Error("MM-Work rc tag changed after the cut; release refused");
|
|
36894
|
+
await deps.validateReleaseSummaries([
|
|
36895
|
+
...record.members.filter((m) => !m.carried && !m.released && !m.hotfixedTo).map((m) => ({ repo: m.repo, tag: m.releasedAs ?? suiteReleaseTag(m.tag) })),
|
|
36896
|
+
...!record.leadReleased ? [{ repo: SUITE_REPO, tag: suiteReleaseTag(record.leadRcTag) }] : []
|
|
36897
|
+
]);
|
|
36811
36898
|
const publishesMain = !record.leadReleased;
|
|
36812
36899
|
const refusals = [];
|
|
36813
36900
|
for (const member of record.members) {
|
|
@@ -36839,6 +36926,7 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
36839
36926
|
writeSuiteCut(deps.recordPath, record);
|
|
36840
36927
|
}
|
|
36841
36928
|
}
|
|
36929
|
+
await reapplyRulesets(deps, record, "main");
|
|
36842
36930
|
if (!record.leadReleased) {
|
|
36843
36931
|
await deps.release(SUITE_REPO, suiteReleaseTag(record.leadRcTag), record.leadRcSha, { big });
|
|
36844
36932
|
record.leadReleased = true;
|
|
@@ -36865,7 +36953,7 @@ var hotfixRecordPath = (deps) => (0, import_node_path29.join)((0, import_node_pa
|
|
|
36865
36953
|
function readSuiteHotfix(path2) {
|
|
36866
36954
|
let raw;
|
|
36867
36955
|
try {
|
|
36868
|
-
raw = (0,
|
|
36956
|
+
raw = (0, import_node_fs30.readFileSync)(path2, "utf8");
|
|
36869
36957
|
} catch (e) {
|
|
36870
36958
|
if (e.code === "ENOENT") return void 0;
|
|
36871
36959
|
throw e;
|
|
@@ -36904,11 +36992,11 @@ async function suiteHotfix(deps, opts) {
|
|
|
36904
36992
|
if (cut && !cut.leadReleased) throw new Error(`an unfinished suite cut (${cut.leadRcTag ?? "no lead tag"}) is in flight; finish it with suite release or retire it with suite abandon first`);
|
|
36905
36993
|
const path2 = hotfixRecordPath(deps);
|
|
36906
36994
|
let record = readSuiteHotfix(path2);
|
|
36907
|
-
|
|
36908
|
-
|
|
36909
|
-
record = void 0;
|
|
36910
|
-
}
|
|
36995
|
+
const completed = record?.mapped ? record : void 0;
|
|
36996
|
+
if (completed) record = void 0;
|
|
36911
36997
|
if (record && record.repo !== found.repo) throw new Error(`an unfinished suite hotfix for ${record.repo} is recorded; finish it (rerun with that member) first`);
|
|
36998
|
+
if (!record?.tag) await deps.validateHotfixSummary();
|
|
36999
|
+
if (completed) (0, import_node_fs30.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
|
|
36912
37000
|
const rec = record ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
|
|
36913
37001
|
const save = () => writeSuiteHotfix(path2, rec);
|
|
36914
37002
|
save();
|
|
@@ -37175,7 +37263,7 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
37175
37263
|
}
|
|
37176
37264
|
|
|
37177
37265
|
// src/suite-train-io.ts
|
|
37178
|
-
var
|
|
37266
|
+
var import_node_fs44 = require("node:fs");
|
|
37179
37267
|
var import_node_path43 = require("node:path");
|
|
37180
37268
|
init_cli_shared();
|
|
37181
37269
|
|
|
@@ -38237,6 +38325,7 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
|
|
|
38237
38325
|
await assertConsumerBomLineage(deps, mergedSha, tag);
|
|
38238
38326
|
}
|
|
38239
38327
|
const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
|
|
38328
|
+
if (!releaseExists && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
|
|
38240
38329
|
if (!releaseExists && isHubControlRepo(ctx.repo)) {
|
|
38241
38330
|
if (!options.announceSummaryFile) {
|
|
38242
38331
|
throw new Error("a new MMI-Hub hotfix Release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same hotfix release command \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#announce-summary-missing");
|
|
@@ -38852,7 +38941,7 @@ function checkHotfixCarries(options) {
|
|
|
38852
38941
|
}
|
|
38853
38942
|
|
|
38854
38943
|
// src/merge-cleanup.ts
|
|
38855
|
-
var
|
|
38944
|
+
var import_node_fs32 = require("node:fs");
|
|
38856
38945
|
var import_node_path31 = require("node:path");
|
|
38857
38946
|
var import_node_os18 = require("node:os");
|
|
38858
38947
|
init_cli_shared();
|
|
@@ -39168,7 +39257,7 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
39168
39257
|
|
|
39169
39258
|
// src/test-policy-core.ts
|
|
39170
39259
|
var import_node_child_process13 = require("node:child_process");
|
|
39171
|
-
var
|
|
39260
|
+
var import_node_fs31 = require("node:fs");
|
|
39172
39261
|
var import_node_path30 = require("node:path");
|
|
39173
39262
|
|
|
39174
39263
|
// src/test-command-policy-shared.mjs
|
|
@@ -39722,7 +39811,7 @@ function loadPolicySource(root, explicitRef, diffBase) {
|
|
|
39722
39811
|
}
|
|
39723
39812
|
function readFileOrNull2(path2) {
|
|
39724
39813
|
try {
|
|
39725
|
-
return (0,
|
|
39814
|
+
return (0, import_node_fs31.readFileSync)(path2, "utf8");
|
|
39726
39815
|
} catch {
|
|
39727
39816
|
return null;
|
|
39728
39817
|
}
|
|
@@ -39750,10 +39839,10 @@ function classify(changed, policy, present = () => false) {
|
|
|
39750
39839
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
39751
39840
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
39752
39841
|
}
|
|
39753
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
39842
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
|
|
39754
39843
|
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
39755
39844
|
}
|
|
39756
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
39845
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
|
|
39757
39846
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
39758
39847
|
return [...new Set(declared)].filter((p) => !exists((0, import_node_path30.join)(root, p)));
|
|
39759
39848
|
}
|
|
@@ -39894,7 +39983,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
39894
39983
|
}
|
|
39895
39984
|
const loaded = deps.policy ? { policy: deps.policy, source: { ref: null, sha: null, path: POLICY_FILE } } : loadPolicySource(root, deps.policyRef, deps.base);
|
|
39896
39985
|
const { policy, source: policySource } = loaded;
|
|
39897
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
39986
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs31.existsSync)(path2));
|
|
39898
39987
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
39899
39988
|
const base = deps.changed ? "(injected)" : deps.policyRef ? resolveHotfixBase(root) : resolveBase(root, deps.base);
|
|
39900
39989
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
@@ -40192,13 +40281,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
40192
40281
|
return rewritten === base ? null : rewritten;
|
|
40193
40282
|
}
|
|
40194
40283
|
function writeSquashBodyFile(body) {
|
|
40195
|
-
const dir = (0,
|
|
40284
|
+
const dir = (0, import_node_fs32.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
|
|
40196
40285
|
const path2 = (0, import_node_path31.join)(dir, "body.txt");
|
|
40197
|
-
(0,
|
|
40286
|
+
(0, import_node_fs32.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
40198
40287
|
`, "utf8");
|
|
40199
40288
|
return { path: path2, cleanup: () => {
|
|
40200
40289
|
try {
|
|
40201
|
-
(0,
|
|
40290
|
+
(0, import_node_fs32.rmSync)(dir, { recursive: true, force: true });
|
|
40202
40291
|
} catch {
|
|
40203
40292
|
}
|
|
40204
40293
|
} };
|
|
@@ -40405,7 +40494,7 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
40405
40494
|
}
|
|
40406
40495
|
|
|
40407
40496
|
// src/project-info-sync.ts
|
|
40408
|
-
var
|
|
40497
|
+
var import_node_fs33 = require("node:fs");
|
|
40409
40498
|
var import_node_path32 = require("node:path");
|
|
40410
40499
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
40411
40500
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
@@ -40454,13 +40543,13 @@ function sharedName(entries, fallback) {
|
|
|
40454
40543
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
40455
40544
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
40456
40545
|
const readmePath = (0, import_node_path32.join)(repoRoot2, "README.md");
|
|
40457
|
-
if (!(0,
|
|
40546
|
+
if (!(0, import_node_fs33.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
40458
40547
|
const entries = entriesFor(project2, projects);
|
|
40459
40548
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
40460
40549
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
40461
40550
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
40462
40551
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
40463
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
40552
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs33.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
40464
40553
|
const lines2 = [
|
|
40465
40554
|
`# ${projectName}`,
|
|
40466
40555
|
"",
|
|
@@ -40479,8 +40568,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
40479
40568
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
40480
40569
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
40481
40570
|
const orgDocs = [
|
|
40482
|
-
(0,
|
|
40483
|
-
(0,
|
|
40571
|
+
(0, import_node_fs33.existsSync)((0, import_node_path32.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
40572
|
+
(0, import_node_fs33.existsSync)((0, import_node_path32.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
40484
40573
|
].filter(Boolean);
|
|
40485
40574
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
40486
40575
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -40502,7 +40591,7 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
40502
40591
|
}
|
|
40503
40592
|
|
|
40504
40593
|
// src/train-commands.ts
|
|
40505
|
-
var
|
|
40594
|
+
var import_node_fs34 = require("node:fs");
|
|
40506
40595
|
var import_node_path33 = require("node:path");
|
|
40507
40596
|
init_cli_shared();
|
|
40508
40597
|
init_clean_exit();
|
|
@@ -40518,7 +40607,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
40518
40607
|
}
|
|
40519
40608
|
function readRepoVersion() {
|
|
40520
40609
|
try {
|
|
40521
|
-
return JSON.parse((0,
|
|
40610
|
+
return JSON.parse((0, import_node_fs34.readFileSync)((0, import_node_path33.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
40522
40611
|
} catch {
|
|
40523
40612
|
return void 0;
|
|
40524
40613
|
}
|
|
@@ -40883,7 +40972,7 @@ async function openSuitePinPr(deps, input) {
|
|
|
40883
40972
|
|
|
40884
40973
|
// src/command-register-collaboration.ts
|
|
40885
40974
|
var import_node_child_process17 = require("node:child_process");
|
|
40886
|
-
var
|
|
40975
|
+
var import_node_fs43 = require("node:fs");
|
|
40887
40976
|
var import_promises8 = require("node:fs/promises");
|
|
40888
40977
|
init_clean_exit();
|
|
40889
40978
|
init_cli_shared();
|
|
@@ -41017,7 +41106,7 @@ init_github_client();
|
|
|
41017
41106
|
init_github_quota();
|
|
41018
41107
|
|
|
41019
41108
|
// src/issue-commands.ts
|
|
41020
|
-
var
|
|
41109
|
+
var import_node_fs35 = require("node:fs");
|
|
41021
41110
|
var import_node_crypto15 = require("node:crypto");
|
|
41022
41111
|
init_cli_shared();
|
|
41023
41112
|
init_clean_exit();
|
|
@@ -41210,7 +41299,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
41210
41299
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
41211
41300
|
const patch = {};
|
|
41212
41301
|
let bodyChanged = false;
|
|
41213
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
41302
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs35.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
41214
41303
|
if (options.titleFile !== void 0) {
|
|
41215
41304
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
41216
41305
|
} else if (options.title !== void 0) {
|
|
@@ -41873,7 +41962,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
41873
41962
|
if (opts.batch) {
|
|
41874
41963
|
let specs;
|
|
41875
41964
|
try {
|
|
41876
|
-
const raw = (0,
|
|
41965
|
+
const raw = (0, import_node_fs35.readFileSync)(opts.batch, "utf8");
|
|
41877
41966
|
specs = JSON.parse(raw);
|
|
41878
41967
|
if (!Array.isArray(specs)) {
|
|
41879
41968
|
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
@@ -41950,7 +42039,7 @@ ${lines2}`, {
|
|
|
41950
42039
|
}
|
|
41951
42040
|
|
|
41952
42041
|
// src/post-merge-recon.ts
|
|
41953
|
-
var
|
|
42042
|
+
var import_node_fs36 = require("node:fs");
|
|
41954
42043
|
var import_node_path35 = require("node:path");
|
|
41955
42044
|
|
|
41956
42045
|
// src/cross-repo-filing-issue.ts
|
|
@@ -42120,16 +42209,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
42120
42209
|
}
|
|
42121
42210
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
42122
42211
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
42123
|
-
(0,
|
|
42124
|
-
(0,
|
|
42212
|
+
(0, import_node_fs36.mkdirSync)((0, import_node_path35.dirname)(path2), { recursive: true });
|
|
42213
|
+
(0, import_node_fs36.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
42125
42214
|
`, "utf8");
|
|
42126
42215
|
return path2;
|
|
42127
42216
|
}
|
|
42128
42217
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
42129
42218
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
42130
|
-
if (!(0,
|
|
42219
|
+
if (!(0, import_node_fs36.existsSync)(path2)) return;
|
|
42131
42220
|
try {
|
|
42132
|
-
(0,
|
|
42221
|
+
(0, import_node_fs36.unlinkSync)(path2);
|
|
42133
42222
|
} catch {
|
|
42134
42223
|
}
|
|
42135
42224
|
}
|
|
@@ -42605,7 +42694,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
42605
42694
|
|
|
42606
42695
|
// src/review-verdict.ts
|
|
42607
42696
|
var import_node_child_process14 = require("node:child_process");
|
|
42608
|
-
var
|
|
42697
|
+
var import_node_fs37 = require("node:fs");
|
|
42609
42698
|
var import_node_os20 = require("node:os");
|
|
42610
42699
|
var import_node_path36 = require("node:path");
|
|
42611
42700
|
init_cli_shared();
|
|
@@ -42772,15 +42861,15 @@ async function readPrIssueComments(number, repo) {
|
|
|
42772
42861
|
return comments;
|
|
42773
42862
|
}
|
|
42774
42863
|
async function postPrCommentFromFile(number, repo, body) {
|
|
42775
|
-
const dir = (0,
|
|
42864
|
+
const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path36.join)((0, import_node_os20.tmpdir)(), "mmi-review-verdict-"));
|
|
42776
42865
|
const path2 = (0, import_node_path36.join)(dir, "body.md");
|
|
42777
42866
|
try {
|
|
42778
|
-
(0,
|
|
42867
|
+
(0, import_node_fs37.writeFileSync)(path2, body, "utf8");
|
|
42779
42868
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
42780
42869
|
return stdout.trim();
|
|
42781
42870
|
} finally {
|
|
42782
42871
|
try {
|
|
42783
|
-
(0,
|
|
42872
|
+
(0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
|
|
42784
42873
|
} catch {
|
|
42785
42874
|
}
|
|
42786
42875
|
}
|
|
@@ -43014,17 +43103,17 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
43014
43103
|
}
|
|
43015
43104
|
|
|
43016
43105
|
// src/worktree-residue-sweep.ts
|
|
43017
|
-
var
|
|
43106
|
+
var import_node_fs42 = require("node:fs");
|
|
43018
43107
|
var import_node_path41 = require("node:path");
|
|
43019
43108
|
|
|
43020
43109
|
// src/worktree-merge-cleanup.ts
|
|
43021
|
-
var
|
|
43110
|
+
var import_node_fs41 = require("node:fs");
|
|
43022
43111
|
var import_node_path40 = require("node:path");
|
|
43023
43112
|
init_cli_shared();
|
|
43024
43113
|
|
|
43025
43114
|
// src/stage-runner.ts
|
|
43026
43115
|
var import_node_child_process16 = require("node:child_process");
|
|
43027
|
-
var
|
|
43116
|
+
var import_node_fs39 = require("node:fs");
|
|
43028
43117
|
var import_node_path38 = require("node:path");
|
|
43029
43118
|
var import_node_net = require("node:net");
|
|
43030
43119
|
var import_node_util5 = require("node:util");
|
|
@@ -43035,7 +43124,7 @@ function normalizeEol2(s) {
|
|
|
43035
43124
|
}
|
|
43036
43125
|
|
|
43037
43126
|
// src/env-detection.ts
|
|
43038
|
-
var
|
|
43127
|
+
var import_node_fs38 = require("node:fs");
|
|
43039
43128
|
var import_node_path37 = require("node:path");
|
|
43040
43129
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
43041
43130
|
function isEnvBaseName(name) {
|
|
@@ -43046,7 +43135,7 @@ function findEnvFiles(root) {
|
|
|
43046
43135
|
const walk2 = (dir, rel) => {
|
|
43047
43136
|
let entries;
|
|
43048
43137
|
try {
|
|
43049
|
-
entries = (0,
|
|
43138
|
+
entries = (0, import_node_fs38.readdirSync)(dir, { withFileTypes: true });
|
|
43050
43139
|
} catch {
|
|
43051
43140
|
return;
|
|
43052
43141
|
}
|
|
@@ -43060,7 +43149,7 @@ function findEnvFiles(root) {
|
|
|
43060
43149
|
if (isEnvBaseName(entry.name)) {
|
|
43061
43150
|
let isDir = false;
|
|
43062
43151
|
try {
|
|
43063
|
-
isDir = (0,
|
|
43152
|
+
isDir = (0, import_node_fs38.lstatSync)((0, import_node_path37.join)(dir, entry.name)).isDirectory();
|
|
43064
43153
|
} catch {
|
|
43065
43154
|
}
|
|
43066
43155
|
if (!isDir) found.push(relPath);
|
|
@@ -43173,12 +43262,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
|
|
|
43173
43262
|
function stageBash() {
|
|
43174
43263
|
if (process.platform !== "win32") return "bash";
|
|
43175
43264
|
const configured = process.env.SHELL;
|
|
43176
|
-
if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0,
|
|
43265
|
+
if (configured && (0, import_node_path38.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs39.existsSync)(configured)) return configured;
|
|
43177
43266
|
const candidates = [
|
|
43178
43267
|
(0, import_node_path38.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
43179
43268
|
...process.env.LOCALAPPDATA ? [(0, import_node_path38.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
43180
43269
|
];
|
|
43181
|
-
const bash = candidates.find((path2) => (0,
|
|
43270
|
+
const bash = candidates.find((path2) => (0, import_node_fs39.existsSync)(path2));
|
|
43182
43271
|
if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
|
|
43183
43272
|
return bash;
|
|
43184
43273
|
}
|
|
@@ -43534,7 +43623,7 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
43534
43623
|
function composeResolvesPort(cwd) {
|
|
43535
43624
|
if (process.env.PORT) return true;
|
|
43536
43625
|
const envFile = (0, import_node_path38.join)(cwd, ".env");
|
|
43537
|
-
return (0,
|
|
43626
|
+
return (0, import_node_fs39.existsSync)(envFile) && envFileKeys((0, import_node_fs39.readFileSync)(envFile, "utf8")).has("PORT");
|
|
43538
43627
|
}
|
|
43539
43628
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
43540
43629
|
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
@@ -43549,13 +43638,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
43549
43638
|
function stageComposeFiles(cwd) {
|
|
43550
43639
|
const files = ["docker-compose.yml"];
|
|
43551
43640
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
43552
|
-
if ((0,
|
|
43641
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(cwd, conventional))) files.push(conventional);
|
|
43553
43642
|
}
|
|
43554
43643
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
43555
43644
|
}
|
|
43556
43645
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
43557
43646
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
43558
|
-
if (!keys.length || !(0,
|
|
43647
|
+
if (!keys.length || !(0, import_node_fs39.existsSync)((0, import_node_path38.join)(cwd, "docker-compose.yml"))) return {};
|
|
43559
43648
|
let configJson;
|
|
43560
43649
|
try {
|
|
43561
43650
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -43572,20 +43661,20 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
43572
43661
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson));
|
|
43573
43662
|
if (!body) return {};
|
|
43574
43663
|
const overridePath = (0, import_node_path38.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
43575
|
-
(0,
|
|
43576
|
-
(0,
|
|
43664
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path38.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
43665
|
+
(0, import_node_fs39.writeFileSync)(overridePath, body, "utf8");
|
|
43577
43666
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
43578
43667
|
}
|
|
43579
43668
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
43580
43669
|
if (!config.ensureEnv) return;
|
|
43581
43670
|
const target = (0, import_node_path38.join)(cwd, config.ensureEnv.target);
|
|
43582
43671
|
const example = (0, import_node_path38.join)(cwd, config.ensureEnv.example);
|
|
43583
|
-
if (!(0,
|
|
43584
|
-
(0,
|
|
43585
|
-
} else if ((0,
|
|
43586
|
-
const stale = detectStaleEnvFile((0,
|
|
43587
|
-
exampleMtimeMs: (0,
|
|
43588
|
-
targetMtimeMs: (0,
|
|
43672
|
+
if (!(0, import_node_fs39.existsSync)(target) && (0, import_node_fs39.existsSync)(example)) {
|
|
43673
|
+
(0, import_node_fs39.copyFileSync)(example, target);
|
|
43674
|
+
} else if ((0, import_node_fs39.existsSync)(target) && (0, import_node_fs39.existsSync)(example)) {
|
|
43675
|
+
const stale = detectStaleEnvFile((0, import_node_fs39.readFileSync)(example, "utf8"), (0, import_node_fs39.readFileSync)(target, "utf8"), {
|
|
43676
|
+
exampleMtimeMs: (0, import_node_fs39.statSync)(example).mtimeMs,
|
|
43677
|
+
targetMtimeMs: (0, import_node_fs39.statSync)(target).mtimeMs
|
|
43589
43678
|
});
|
|
43590
43679
|
if (stale) {
|
|
43591
43680
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -43593,8 +43682,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
43593
43682
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
43594
43683
|
}
|
|
43595
43684
|
}
|
|
43596
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
43597
|
-
(0,
|
|
43685
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs39.existsSync)(target)) {
|
|
43686
|
+
(0, import_node_fs39.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs39.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
43598
43687
|
}
|
|
43599
43688
|
}
|
|
43600
43689
|
async function gitText(cwd, args) {
|
|
@@ -43624,20 +43713,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
43624
43713
|
return void 0;
|
|
43625
43714
|
}
|
|
43626
43715
|
function readState(path2) {
|
|
43627
|
-
if (!(0,
|
|
43716
|
+
if (!(0, import_node_fs39.existsSync)(path2)) return null;
|
|
43628
43717
|
try {
|
|
43629
|
-
return JSON.parse((0,
|
|
43718
|
+
return JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
|
|
43630
43719
|
} catch {
|
|
43631
43720
|
return null;
|
|
43632
43721
|
}
|
|
43633
43722
|
}
|
|
43634
43723
|
function mkdirFor(path2) {
|
|
43635
43724
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
43636
|
-
(0,
|
|
43725
|
+
(0, import_node_fs39.mkdirSync)(dir, { recursive: true });
|
|
43637
43726
|
}
|
|
43638
43727
|
function writeState(path2, state) {
|
|
43639
43728
|
mkdirFor(path2);
|
|
43640
|
-
(0,
|
|
43729
|
+
(0, import_node_fs39.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
43641
43730
|
}
|
|
43642
43731
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
43643
43732
|
const reservation = {
|
|
@@ -43666,7 +43755,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
43666
43755
|
);
|
|
43667
43756
|
}
|
|
43668
43757
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
43669
|
-
(0,
|
|
43758
|
+
(0, import_node_fs39.rmSync)(path2, { force: true });
|
|
43670
43759
|
}
|
|
43671
43760
|
}
|
|
43672
43761
|
async function killTree(pid) {
|
|
@@ -43913,8 +44002,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
43913
44002
|
});
|
|
43914
44003
|
}
|
|
43915
44004
|
} catch (e) {
|
|
43916
|
-
(0,
|
|
43917
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
44005
|
+
(0, import_node_fs39.rmSync)(statePath, { force: true });
|
|
44006
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs39.rmSync)(globalStatePath, { force: true });
|
|
43918
44007
|
throw e;
|
|
43919
44008
|
}
|
|
43920
44009
|
const started = await startStage(config, {
|
|
@@ -43929,7 +44018,7 @@ async function runStage(config = {}, opts = {}) {
|
|
|
43929
44018
|
}
|
|
43930
44019
|
|
|
43931
44020
|
// src/worktree-evidence-archive.ts
|
|
43932
|
-
var
|
|
44021
|
+
var import_node_fs40 = require("node:fs");
|
|
43933
44022
|
var import_node_path39 = require("node:path");
|
|
43934
44023
|
var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
|
|
43935
44024
|
function sanitizeArchiveSegment(value, max = 80) {
|
|
@@ -43945,17 +44034,17 @@ function resolveArtifactRunScope(env = process.env) {
|
|
|
43945
44034
|
}
|
|
43946
44035
|
function defaultIsDirectory(path2) {
|
|
43947
44036
|
try {
|
|
43948
|
-
return (0,
|
|
44037
|
+
return (0, import_node_fs40.lstatSync)(path2).isDirectory();
|
|
43949
44038
|
} catch {
|
|
43950
44039
|
return false;
|
|
43951
44040
|
}
|
|
43952
44041
|
}
|
|
43953
44042
|
function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
43954
|
-
const exists = deps.exists ??
|
|
44043
|
+
const exists = deps.exists ?? import_node_fs40.existsSync;
|
|
43955
44044
|
const isDirectory = deps.isDirectory ?? defaultIsDirectory;
|
|
43956
|
-
const copyDir = deps.copyDir ?? ((from, to) => (0,
|
|
44045
|
+
const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs40.cpSync)(from, to, { recursive: true, force: true }));
|
|
43957
44046
|
const mkdirp = deps.mkdirp ?? ((path2) => {
|
|
43958
|
-
(0,
|
|
44047
|
+
(0, import_node_fs40.mkdirSync)(path2, { recursive: true });
|
|
43959
44048
|
});
|
|
43960
44049
|
const env = deps.env ?? process.env;
|
|
43961
44050
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -43979,13 +44068,13 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
|
|
|
43979
44068
|
}
|
|
43980
44069
|
}
|
|
43981
44070
|
function defaultStat(path2) {
|
|
43982
|
-
const st = (0,
|
|
44071
|
+
const st = (0, import_node_fs40.statSync)(path2);
|
|
43983
44072
|
return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
|
|
43984
44073
|
}
|
|
43985
44074
|
function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
43986
|
-
const exists = deps.exists ??
|
|
44075
|
+
const exists = deps.exists ?? import_node_fs40.existsSync;
|
|
43987
44076
|
const stat4 = deps.stat ?? defaultStat;
|
|
43988
|
-
const readdir2 = deps.readdir ??
|
|
44077
|
+
const readdir2 = deps.readdir ?? import_node_fs40.readdirSync;
|
|
43989
44078
|
const tmpRoot = (0, import_node_path39.join)(worktreePath, "tmp");
|
|
43990
44079
|
if (!exists(tmpRoot)) return [];
|
|
43991
44080
|
const entries = [];
|
|
@@ -44018,10 +44107,10 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
|
|
|
44018
44107
|
return entries;
|
|
44019
44108
|
}
|
|
44020
44109
|
function archiveWorktreeTmpArtifacts(args, deps = {}) {
|
|
44021
|
-
const exists = deps.exists ??
|
|
44022
|
-
const copyDir = deps.copyDir ?? ((from, to) => (0,
|
|
44110
|
+
const exists = deps.exists ?? import_node_fs40.existsSync;
|
|
44111
|
+
const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs40.cpSync)(from, to, { recursive: true, force: true }));
|
|
44023
44112
|
const mkdirp = deps.mkdirp ?? ((path2) => {
|
|
44024
|
-
(0,
|
|
44113
|
+
(0, import_node_fs40.mkdirSync)(path2, { recursive: true });
|
|
44025
44114
|
});
|
|
44026
44115
|
const env = deps.env ?? process.env;
|
|
44027
44116
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -44108,7 +44197,7 @@ function normPath2(p) {
|
|
|
44108
44197
|
function unlinkNodeModulesJunction(wtPath) {
|
|
44109
44198
|
const nm = (0, import_node_path40.join)(wtPath, "node_modules");
|
|
44110
44199
|
try {
|
|
44111
|
-
if ((0,
|
|
44200
|
+
if ((0, import_node_fs41.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs41.rmdirSync)(nm);
|
|
44112
44201
|
return { ok: true };
|
|
44113
44202
|
} catch (e) {
|
|
44114
44203
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -44119,8 +44208,8 @@ function defaultSleep2(ms) {
|
|
|
44119
44208
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
44120
44209
|
}
|
|
44121
44210
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
44122
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
44123
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
44211
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs41.rmdirSync, exists = import_node_fs41.existsSync } = options;
|
|
44212
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs41.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
44124
44213
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
44125
44214
|
if (!junction.ok) return junction;
|
|
44126
44215
|
try {
|
|
@@ -44246,21 +44335,21 @@ async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
|
44246
44335
|
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path40.join)(wtPath, line.slice(0, -1)));
|
|
44247
44336
|
}
|
|
44248
44337
|
function safeRemoveTree(path2) {
|
|
44249
|
-
const stat4 = (0,
|
|
44338
|
+
const stat4 = (0, import_node_fs41.lstatSync)(path2);
|
|
44250
44339
|
if (stat4.isSymbolicLink()) {
|
|
44251
44340
|
try {
|
|
44252
|
-
(0,
|
|
44341
|
+
(0, import_node_fs41.rmdirSync)(path2);
|
|
44253
44342
|
} catch {
|
|
44254
|
-
(0,
|
|
44343
|
+
(0, import_node_fs41.unlinkSync)(path2);
|
|
44255
44344
|
}
|
|
44256
44345
|
return;
|
|
44257
44346
|
}
|
|
44258
44347
|
if (stat4.isDirectory()) {
|
|
44259
|
-
for (const entry of (0,
|
|
44260
|
-
(0,
|
|
44348
|
+
for (const entry of (0, import_node_fs41.readdirSync)(path2)) safeRemoveTree((0, import_node_path40.join)(path2, entry));
|
|
44349
|
+
(0, import_node_fs41.rmdirSync)(path2);
|
|
44261
44350
|
return;
|
|
44262
44351
|
}
|
|
44263
|
-
(0,
|
|
44352
|
+
(0, import_node_fs41.unlinkSync)(path2);
|
|
44264
44353
|
}
|
|
44265
44354
|
async function removeWorktreeNodeModules(wtPath) {
|
|
44266
44355
|
try {
|
|
@@ -44276,7 +44365,7 @@ function errorMessage(e) {
|
|
|
44276
44365
|
}
|
|
44277
44366
|
function resolvedOrRaw(path2) {
|
|
44278
44367
|
try {
|
|
44279
|
-
return normPath2((0,
|
|
44368
|
+
return normPath2((0, import_node_fs41.realpathSync)(path2));
|
|
44280
44369
|
} catch {
|
|
44281
44370
|
return normPath2(path2);
|
|
44282
44371
|
}
|
|
@@ -44284,10 +44373,10 @@ function resolvedOrRaw(path2) {
|
|
|
44284
44373
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
44285
44374
|
let realRoot;
|
|
44286
44375
|
try {
|
|
44287
|
-
if ((0,
|
|
44376
|
+
if ((0, import_node_fs41.lstatSync)(root).isSymbolicLink()) {
|
|
44288
44377
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
44289
44378
|
}
|
|
44290
|
-
realRoot = normPath2((0,
|
|
44379
|
+
realRoot = normPath2((0, import_node_fs41.realpathSync)(root));
|
|
44291
44380
|
} catch (e) {
|
|
44292
44381
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
44293
44382
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -44303,7 +44392,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
44303
44392
|
const dir = stack.pop();
|
|
44304
44393
|
let entries;
|
|
44305
44394
|
try {
|
|
44306
|
-
entries = (0,
|
|
44395
|
+
entries = (0, import_node_fs41.readdirSync)(dir, { withFileTypes: true });
|
|
44307
44396
|
} catch (e) {
|
|
44308
44397
|
if (e.code === "ENOENT") continue;
|
|
44309
44398
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
@@ -44313,7 +44402,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
44313
44402
|
if (entry.isSymbolicLink()) {
|
|
44314
44403
|
let target = "";
|
|
44315
44404
|
try {
|
|
44316
|
-
target = normPath2((0,
|
|
44405
|
+
target = normPath2((0, import_node_fs41.realpathSync)(child2));
|
|
44317
44406
|
} catch {
|
|
44318
44407
|
target = "";
|
|
44319
44408
|
}
|
|
@@ -44496,7 +44585,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
44496
44585
|
return report;
|
|
44497
44586
|
}
|
|
44498
44587
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
44499
|
-
const pathExists = options.pathExists ??
|
|
44588
|
+
const pathExists = options.pathExists ?? import_node_fs41.existsSync;
|
|
44500
44589
|
let afterWorktrees = [];
|
|
44501
44590
|
try {
|
|
44502
44591
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -44783,7 +44872,7 @@ function residueLedgerPath(gitCommonDir) {
|
|
|
44783
44872
|
}
|
|
44784
44873
|
function readPaths(file, deps) {
|
|
44785
44874
|
try {
|
|
44786
|
-
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0,
|
|
44875
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs42.readFileSync)(f, "utf8")))(file));
|
|
44787
44876
|
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
44788
44877
|
} catch {
|
|
44789
44878
|
return [];
|
|
@@ -44792,8 +44881,8 @@ function readPaths(file, deps) {
|
|
|
44792
44881
|
function writePaths(file, paths, deps) {
|
|
44793
44882
|
try {
|
|
44794
44883
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
44795
|
-
(0,
|
|
44796
|
-
(0,
|
|
44884
|
+
(0, import_node_fs42.mkdirSync)((0, import_node_path41.dirname)(f), { recursive: true });
|
|
44885
|
+
(0, import_node_fs42.writeFileSync)(f, t, "utf8");
|
|
44797
44886
|
});
|
|
44798
44887
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
44799
44888
|
`);
|
|
@@ -44807,17 +44896,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
|
44807
44896
|
writePaths(file, paths, deps);
|
|
44808
44897
|
}
|
|
44809
44898
|
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
44810
|
-
const exists = deps.exists ??
|
|
44811
|
-
const readDir = deps.readDir ?? ((p) => (0,
|
|
44899
|
+
const exists = deps.exists ?? import_node_fs42.existsSync;
|
|
44900
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs42.readdirSync)(p));
|
|
44812
44901
|
const isDir = deps.isDir ?? ((p) => {
|
|
44813
44902
|
try {
|
|
44814
|
-
return (0,
|
|
44903
|
+
return (0, import_node_fs42.statSync)(p).isDirectory();
|
|
44815
44904
|
} catch {
|
|
44816
44905
|
return false;
|
|
44817
44906
|
}
|
|
44818
44907
|
});
|
|
44819
44908
|
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
44820
|
-
const removeEmpty = deps.removeEmpty ?? ((p) => (0,
|
|
44909
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs42.rmdirSync)(p));
|
|
44821
44910
|
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
44822
44911
|
const live = new Set(registered.map(norm));
|
|
44823
44912
|
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
@@ -44897,8 +44986,8 @@ function argvWantsJson2() {
|
|
|
44897
44986
|
function hubRoot() {
|
|
44898
44987
|
const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
|
|
44899
44988
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
44900
|
-
if ((0,
|
|
44901
|
-
if ((0,
|
|
44989
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
|
|
44990
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
|
|
44902
44991
|
return null;
|
|
44903
44992
|
}
|
|
44904
44993
|
function ciAuditDeps() {
|
|
@@ -44911,7 +45000,7 @@ function ciAuditDeps() {
|
|
|
44911
45000
|
readSeedFile: (path2) => {
|
|
44912
45001
|
if (!root) return null;
|
|
44913
45002
|
const fullPath = (0, import_node_path42.join)(root, path2);
|
|
44914
|
-
return (0,
|
|
45003
|
+
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
44915
45004
|
}
|
|
44916
45005
|
};
|
|
44917
45006
|
}
|
|
@@ -45675,10 +45764,10 @@ ${list}`);
|
|
|
45675
45764
|
});
|
|
45676
45765
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
45677
45766
|
const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
|
|
45678
|
-
if (!(0,
|
|
45679
|
-
return (0,
|
|
45767
|
+
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
45768
|
+
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
45680
45769
|
try {
|
|
45681
|
-
return workflowReportsPrChecks((0,
|
|
45770
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
|
|
45682
45771
|
} catch {
|
|
45683
45772
|
return true;
|
|
45684
45773
|
}
|
|
@@ -45883,7 +45972,7 @@ ${list}`);
|
|
|
45883
45972
|
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) => {
|
|
45884
45973
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
45885
45974
|
const verdict = o.verdict;
|
|
45886
|
-
const findings = o.findingsFile ? (0,
|
|
45975
|
+
const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
45887
45976
|
let patch;
|
|
45888
45977
|
let head;
|
|
45889
45978
|
try {
|
|
@@ -46158,7 +46247,7 @@ ${list}`);
|
|
|
46158
46247
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
46159
46248
|
closingGuardInput,
|
|
46160
46249
|
method === "--squash",
|
|
46161
|
-
o.squashBodyFile ? (0,
|
|
46250
|
+
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
46162
46251
|
);
|
|
46163
46252
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
46164
46253
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -46288,7 +46377,7 @@ ${list}`);
|
|
|
46288
46377
|
}
|
|
46289
46378
|
if (!repoForPostCleanup) throw e;
|
|
46290
46379
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
46291
|
-
const commitMessage = bodyFile ? (0,
|
|
46380
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
46292
46381
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
46293
46382
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
46294
46383
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -46420,7 +46509,7 @@ ${list}`);
|
|
|
46420
46509
|
preserveWorktree: o.preserveWorktree,
|
|
46421
46510
|
gcAcknowledged: o.gc,
|
|
46422
46511
|
expectedHeadOid: headRefOid,
|
|
46423
|
-
pathExists: (p) => (0,
|
|
46512
|
+
pathExists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
46424
46513
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
46425
46514
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
46426
46515
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -47329,6 +47418,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
47329
47418
|
const slug = applyingRepo ? slugOf(applyingRepo) : "";
|
|
47330
47419
|
const isHub = applyingRepo?.toLowerCase() === ANNOUNCE_REPO.toLowerCase();
|
|
47331
47420
|
let announcesToChannel = isHub;
|
|
47421
|
+
let isWorkSuite = false;
|
|
47332
47422
|
let releaseLanguage2 = "en";
|
|
47333
47423
|
if (!isHub && slug) {
|
|
47334
47424
|
let read;
|
|
@@ -47342,9 +47432,17 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
47342
47432
|
`release: Hub registry read failed for ${slug} (${read.error}) \u2014 whether ${slug} announces to a project release channel is UNREAD, so the --announce-summary-file requirement is unproven. Likely transient (cold start, network, or auth blip); retry shortly.`
|
|
47343
47433
|
);
|
|
47344
47434
|
}
|
|
47435
|
+
isWorkSuite = read.project?.suite === "mm-work";
|
|
47345
47436
|
announcesToChannel = Boolean(read.project?.releaseChannel?.trim());
|
|
47346
47437
|
releaseLanguage2 = read.project?.releaseLanguage?.trim() || "en";
|
|
47347
47438
|
}
|
|
47439
|
+
if (isWorkSuite) {
|
|
47440
|
+
try {
|
|
47441
|
+
await requireSuiteReleaseSummary({}, o.announceSummaryFile);
|
|
47442
|
+
} catch (e) {
|
|
47443
|
+
return fail(`release: ${e.message}`);
|
|
47444
|
+
}
|
|
47445
|
+
}
|
|
47348
47446
|
if (announcesToChannel) {
|
|
47349
47447
|
if (!o.announceSummaryFile) {
|
|
47350
47448
|
return fail(
|
|
@@ -47648,11 +47746,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
47648
47746
|
const checkout = async (repo) => {
|
|
47649
47747
|
if (repo === SUITE_REPO) return root;
|
|
47650
47748
|
const target = (0, import_node_path43.join)(base, repo.split("/")[1]);
|
|
47651
|
-
if (!(0,
|
|
47652
|
-
(0,
|
|
47749
|
+
if (!(0, import_node_fs44.existsSync)(target)) {
|
|
47750
|
+
(0, import_node_fs44.mkdirSync)(base, { recursive: true });
|
|
47653
47751
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
47654
47752
|
}
|
|
47655
|
-
if (!(0,
|
|
47753
|
+
if (!(0, import_node_fs44.existsSync)((0, import_node_path43.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
47656
47754
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
47657
47755
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
47658
47756
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -47856,6 +47954,20 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
47856
47954
|
};
|
|
47857
47955
|
return {
|
|
47858
47956
|
recordPath,
|
|
47957
|
+
validateHotfixSummary: () => requireSuiteReleaseSummary({}, summaryFile),
|
|
47958
|
+
validateReleaseSummaries: (releases) => validateReleaseSummaries(
|
|
47959
|
+
releases,
|
|
47960
|
+
{ ...memberSummaryFiles, [SUITE_REPO]: summaryFile },
|
|
47961
|
+
async (repo, tag) => {
|
|
47962
|
+
try {
|
|
47963
|
+
await gh(["release", "view", tag, "--repo", repo, "--json", "tagName"]);
|
|
47964
|
+
return true;
|
|
47965
|
+
} catch (e) {
|
|
47966
|
+
if (/release not found|HTTP 404|Not Found|\(404\)/i.test(e.message)) return false;
|
|
47967
|
+
throw e;
|
|
47968
|
+
}
|
|
47969
|
+
}
|
|
47970
|
+
),
|
|
47859
47971
|
registry: registry2,
|
|
47860
47972
|
backend,
|
|
47861
47973
|
hold,
|
|
@@ -47936,10 +48048,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
47936
48048
|
},
|
|
47937
48049
|
// #7738: a member hotfixes as suite-solo; the lead hotfixes under its suite-lead capability (inTrain sets it).
|
|
47938
48050
|
hotfixStart: (repo, o) => inTrain(repo, async (deps) => {
|
|
48051
|
+
await requireSuiteReleaseSummary(deps, summaryFile);
|
|
47939
48052
|
const r = await runHotfixStart(deps, { from: o.from, ack: o.ack, suiteSolo: repo !== SUITE_REPO });
|
|
47940
48053
|
return { tag: r.tag };
|
|
47941
48054
|
}, repo !== SUITE_REPO),
|
|
47942
|
-
hotfixRelease: (repo, tag) => inTrain(repo, async (deps) => ({ tag: (await runHotfixRelease(deps, tag, { suiteSolo: repo !== SUITE_REPO })).tag }), repo !== SUITE_REPO),
|
|
48055
|
+
hotfixRelease: (repo, tag) => inTrain(repo, async (deps) => ({ tag: (await runHotfixRelease(deps, tag, { suiteSolo: repo !== SUITE_REPO, announceSummaryFile: summaryFile, announceSummaryFileTr: summaryFileTr })).tag }), repo !== SUITE_REPO),
|
|
47943
48056
|
// #7738: the #7714 solo pin leg, polled until the auto-merge lands (30 s x 40 = 20 min), then the caller decides.
|
|
47944
48057
|
soloPin: async (repo, tag) => {
|
|
47945
48058
|
for (let i = 0; ; i++) {
|
|
@@ -47960,13 +48073,27 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
47960
48073
|
const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
|
|
47961
48074
|
if (parseSuiteMapping(body)) return;
|
|
47962
48075
|
const dir = suiteStateDir(root);
|
|
47963
|
-
(0,
|
|
48076
|
+
(0, import_node_fs44.mkdirSync)(dir, { recursive: true });
|
|
47964
48077
|
const file = (0, import_node_path43.join)(dir, `notes-${leadTag}.md`);
|
|
47965
|
-
(0,
|
|
48078
|
+
(0, import_node_fs44.writeFileSync)(file, `${body}
|
|
47966
48079
|
|
|
47967
48080
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
47968
48081
|
await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
|
|
47969
48082
|
},
|
|
48083
|
+
productRuleset: async (repo) => {
|
|
48084
|
+
const read = await fetchProjectBySlugChecked(slugOf(repo), registryClientDeps(await loadConfig()));
|
|
48085
|
+
if (!read.ok) throw new Error(`registry META unreadable (${read.error})`);
|
|
48086
|
+
if (!read.project?.requiredChecks?.length) return ["skip: no registry META requiredChecks; the seeded ruleset stays as it is"];
|
|
48087
|
+
const result = await applyCiReconcileRepo(repo, {
|
|
48088
|
+
client: defaultGitHubClient(),
|
|
48089
|
+
listProjects: async () => [],
|
|
48090
|
+
getProjectMeta: async () => read.project
|
|
48091
|
+
}, { productRulesetOnly: true });
|
|
48092
|
+
const lines2 = [...result.applied, ...result.skipped.map((s) => `skip: ${s}`)];
|
|
48093
|
+
lines2.forEach((line) => console.log(`${repo}: product ruleset ${line}`));
|
|
48094
|
+
if (result.errors.length) throw new Error(result.errors.join("; "));
|
|
48095
|
+
return lines2;
|
|
48096
|
+
},
|
|
47970
48097
|
release,
|
|
47971
48098
|
shippedTag,
|
|
47972
48099
|
sameContent: sameContent2,
|
|
@@ -47989,7 +48116,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
|
|
|
47989
48116
|
|
|
47990
48117
|
// src/suite-commands.ts
|
|
47991
48118
|
init_cli_shared();
|
|
47992
|
-
var
|
|
48119
|
+
var import_node_fs45 = require("node:fs");
|
|
47993
48120
|
function parseMemberSummaryFiles(entries) {
|
|
47994
48121
|
const memberSummaryFiles = {};
|
|
47995
48122
|
for (const entry of entries) {
|
|
@@ -47999,12 +48126,6 @@ function parseMemberSummaryFiles(entries) {
|
|
|
47999
48126
|
}
|
|
48000
48127
|
return memberSummaryFiles;
|
|
48001
48128
|
}
|
|
48002
|
-
function validateSuiteReleaseSummary(path2) {
|
|
48003
|
-
if (!path2) throw new Error("suite release requires --announce-summary-file before promoting any member");
|
|
48004
|
-
const lines2 = (0, import_node_fs44.readFileSync)(path2, "utf8").split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
48005
|
-
if (lines2[0]?.startsWith("No changes to this module")) throw new Error("suite release requires a real MM-Work title, not the no-change placeholder");
|
|
48006
|
-
if (lines2.length < 3 || lines2.length > 6) throw new Error("suite release announcement needs 3\u20136 curated lines before promoting any member");
|
|
48007
|
-
}
|
|
48008
48129
|
function suiteRcDataProdDeps() {
|
|
48009
48130
|
return {
|
|
48010
48131
|
fetch: async (url, init) => {
|
|
@@ -48077,7 +48198,7 @@ function registerSuiteCommands(program3) {
|
|
|
48077
48198
|
try {
|
|
48078
48199
|
const io = suiteTrainIo(root);
|
|
48079
48200
|
retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
|
|
48080
|
-
if (retired && !(0,
|
|
48201
|
+
if (retired && !(0, import_node_fs45.existsSync)(io.recordPath)) {
|
|
48081
48202
|
console.log(o.json ? JSON.stringify({ retiredLeadRc: retired }, null, 2) : `suite abandon: retired the lead's failed, never-promoted rc candidate ${retired}; marked abandoned on origin (#7854)`);
|
|
48082
48203
|
return;
|
|
48083
48204
|
}
|
|
@@ -48099,10 +48220,9 @@ function registerSuiteCommands(program3) {
|
|
|
48099
48220
|
return fail(`suite abandon: ${e.message}`);
|
|
48100
48221
|
}
|
|
48101
48222
|
});
|
|
48102
|
-
suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable:
|
|
48223
|
+
suite.command("release").description("verify the cut and unchanged live backends, ship members then MM-Work, announce once").option("--owner-go", "explicit owner approval to release the tested candidate").option("--announce-summary-file <path>", "curated suite announcement, posted once after the train; also this cut's plain-words summary for MM-Work itself").option("--member-summary-file <owner/repo=path>", "repeatable: required title + 2\u20135 notes for each member being released; carried members need none", (val, prev = []) => [...prev, val], []).option("--announce-summary-file-tr <path>", "Turkish plain-words summary for MM-Work itself").option("--member-summary-file-tr <owner/repo=path>", "repeatable: a member's own Turkish plain-words summary", (val, prev = []) => [...prev, val], []).option("--big", "marks MM-Work's own release big for the plain-words section (#7510); a member is never marked big through this flag \u2014 it is big only via the read route's automatic minor/major detection").option("--json", "structured receipt").action(async (o) => {
|
|
48103
48224
|
try {
|
|
48104
48225
|
if (!o.ownerGo) throw new Error("suite release requires --owner-go; nothing was written");
|
|
48105
|
-
validateSuiteReleaseSummary(o.announceSummaryFile);
|
|
48106
48226
|
const memberSummaryFiles = parseMemberSummaryFiles(o.memberSummaryFile ?? []);
|
|
48107
48227
|
const memberSummaryFilesTr = parseMemberSummaryFiles(o.memberSummaryFileTr ?? []);
|
|
48108
48228
|
const root = await leadRoot();
|
|
@@ -48123,14 +48243,14 @@ function registerSuiteCommands(program3) {
|
|
|
48123
48243
|
return fail(`suite release: ${e.message}`);
|
|
48124
48244
|
}
|
|
48125
48245
|
});
|
|
48126
|
-
suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
|
|
48246
|
+
suite.command("hotfix <member>").description("member hotfix, main pin PR, MM-Work patch release, and the recorded suite version -> member versions mapping (#7738)").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR(s) or SHA(s) the member hotfix carries").option("--ack <shas>", "dev shas verified as not needed on main (see hotfix start --ack)").option("--announce-summary-file <path>", "required title + 2\u20135 notes for the member and lead hotfix Releases").option("--announce-summary-file-tr <path>", "optional Turkish plain-words notes").option("--owner-go", "explicit owner approval: ships the member and MM-Work to production (not needed for --dry-run)").option("--dry-run", "read-only: print the ordered plan; writes nothing").option("--json", "structured receipt").action(async (member, o) => {
|
|
48127
48247
|
try {
|
|
48128
48248
|
if (!o.ownerGo && !o.dryRun) throw new Error("suite hotfix requires --owner-go (it ships to production); nothing was written. --dry-run prints the plan");
|
|
48129
48249
|
const root = await leadRoot();
|
|
48130
48250
|
const unlock = o.dryRun ? async () => void 0 : await acquireTrainWriterLock(root, "suite hotfix");
|
|
48131
48251
|
let result;
|
|
48132
48252
|
try {
|
|
48133
|
-
result = await suiteHotfix(suiteTrainIo(root), { member, from: o.from, ack: o.ack?.split(",").map((s) => s.trim()).filter(Boolean), ownerGo: o.ownerGo, dryRun: o.dryRun });
|
|
48253
|
+
result = await suiteHotfix(suiteTrainIo(root, o.announceSummaryFile, {}, o.announceSummaryFileTr), { member, from: o.from, ack: o.ack?.split(",").map((s) => s.trim()).filter(Boolean), ownerGo: o.ownerGo, dryRun: o.dryRun });
|
|
48134
48254
|
} finally {
|
|
48135
48255
|
await unlock();
|
|
48136
48256
|
}
|
|
@@ -48267,7 +48387,7 @@ function suiteStatusDeps(github, registry2) {
|
|
|
48267
48387
|
|
|
48268
48388
|
// src/packages-adopt-export.ts
|
|
48269
48389
|
var import_node_child_process18 = require("node:child_process");
|
|
48270
|
-
var
|
|
48390
|
+
var import_node_fs46 = require("node:fs");
|
|
48271
48391
|
var import_node_os21 = require("node:os");
|
|
48272
48392
|
var import_node_path44 = require("node:path");
|
|
48273
48393
|
var import_node_util6 = require("node:util");
|
|
@@ -48891,36 +49011,36 @@ async function adoptExport(opts, deps) {
|
|
|
48891
49011
|
if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
|
|
48892
49012
|
const packagePath = (0, import_node_path44.join)(opts.dir, "package.json");
|
|
48893
49013
|
const lockPath = (0, import_node_path44.join)(opts.dir, "package-lock.json");
|
|
48894
|
-
const packageText = (0,
|
|
48895
|
-
const lockText = (0,
|
|
48896
|
-
const work = (0,
|
|
49014
|
+
const packageText = (0, import_node_fs46.readFileSync)(packagePath, "utf8");
|
|
49015
|
+
const lockText = (0, import_node_fs46.readFileSync)(lockPath, "utf8");
|
|
49016
|
+
const work = (0, import_node_fs46.mkdtempSync)((0, import_node_path44.join)((0, import_node_os21.tmpdir)(), "mmi-adopt-export-"));
|
|
48897
49017
|
try {
|
|
48898
49018
|
const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
|
|
48899
|
-
(0,
|
|
48900
|
-
(0,
|
|
48901
|
-
(0,
|
|
49019
|
+
(0, import_node_fs46.writeFileSync)((0, import_node_path44.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
|
|
49020
|
+
(0, import_node_fs46.writeFileSync)((0, import_node_path44.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
|
|
49021
|
+
(0, import_node_fs46.writeFileSync)((0, import_node_path44.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
|
|
48902
49022
|
const script = (0, import_node_path44.join)(work, "package_export.py");
|
|
48903
|
-
(0,
|
|
49023
|
+
(0, import_node_fs46.writeFileSync)(script, package_export_default);
|
|
48904
49024
|
await deps.python(script, ["verify", work, opts.ds, opts.screens]);
|
|
48905
49025
|
const verified = (0, import_node_path44.join)(work, "verified");
|
|
48906
|
-
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0,
|
|
49026
|
+
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path44.join)(verified, `${short3}.metadata.json`), "utf8"))]));
|
|
48907
49027
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
48908
49028
|
for (const { name } of plan.result.adopted) {
|
|
48909
49029
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
48910
49030
|
await deps.npm(["cache", "add", (0, import_node_path44.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
48911
49031
|
}
|
|
48912
|
-
(0,
|
|
48913
|
-
(0,
|
|
49032
|
+
(0, import_node_fs46.writeFileSync)(packagePath, plan.packageText);
|
|
49033
|
+
(0, import_node_fs46.writeFileSync)(lockPath, plan.lockText);
|
|
48914
49034
|
try {
|
|
48915
49035
|
await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
|
|
48916
49036
|
} catch (error) {
|
|
48917
|
-
(0,
|
|
48918
|
-
(0,
|
|
49037
|
+
(0, import_node_fs46.writeFileSync)(packagePath, packageText);
|
|
49038
|
+
(0, import_node_fs46.writeFileSync)(lockPath, lockText);
|
|
48919
49039
|
throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
|
|
48920
49040
|
}
|
|
48921
49041
|
return plan.result;
|
|
48922
49042
|
} finally {
|
|
48923
|
-
(0,
|
|
49043
|
+
(0, import_node_fs46.rmSync)(work, { recursive: true, force: true });
|
|
48924
49044
|
}
|
|
48925
49045
|
}
|
|
48926
49046
|
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process18.execFile);
|
|
@@ -48985,12 +49105,12 @@ function registerPackagesCommands(program3) {
|
|
|
48985
49105
|
init_cli_shared();
|
|
48986
49106
|
|
|
48987
49107
|
// src/wave-status.ts
|
|
48988
|
-
var
|
|
49108
|
+
var import_node_fs47 = require("node:fs");
|
|
48989
49109
|
function readStageSummary(worktreePath) {
|
|
48990
49110
|
const statePath = stageStatePath(worktreePath);
|
|
48991
|
-
if (!(0,
|
|
49111
|
+
if (!(0, import_node_fs47.existsSync)(statePath)) return void 0;
|
|
48992
49112
|
try {
|
|
48993
|
-
const state = JSON.parse((0,
|
|
49113
|
+
const state = JSON.parse((0, import_node_fs47.readFileSync)(statePath, "utf8"));
|
|
48994
49114
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
48995
49115
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
48996
49116
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -49047,7 +49167,7 @@ async function collectWaveStatus(deps) {
|
|
|
49047
49167
|
// src/discovery-commands.ts
|
|
49048
49168
|
init_github_client();
|
|
49049
49169
|
init_cli_shared();
|
|
49050
|
-
var
|
|
49170
|
+
var import_node_fs48 = require("node:fs");
|
|
49051
49171
|
var import_node_os22 = require("node:os");
|
|
49052
49172
|
var import_node_path45 = require("node:path");
|
|
49053
49173
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
@@ -49240,8 +49360,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
49240
49360
|
}
|
|
49241
49361
|
const home = (0, import_node_os22.homedir)();
|
|
49242
49362
|
const plugin = onboardPluginGate({
|
|
49243
|
-
readKnown: () => readFileSyncSafe((0, import_node_path45.join)(home, ...KNOWN_MARKETPLACES_RELATIVE),
|
|
49244
|
-
readSettings: () => readFileSyncSafe((0, import_node_path45.join)(home, ".claude", "settings.json"),
|
|
49363
|
+
readKnown: () => readFileSyncSafe((0, import_node_path45.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs48.readFileSync),
|
|
49364
|
+
readSettings: () => readFileSyncSafe((0, import_node_path45.join)(home, ".claude", "settings.json"), import_node_fs48.readFileSync)
|
|
49245
49365
|
});
|
|
49246
49366
|
return {
|
|
49247
49367
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -52345,7 +52465,7 @@ function registerSessionReport(program3) {
|
|
|
52345
52465
|
}
|
|
52346
52466
|
|
|
52347
52467
|
// src/stage-commands.ts
|
|
52348
|
-
var
|
|
52468
|
+
var import_node_fs49 = require("node:fs");
|
|
52349
52469
|
var import_node_path46 = require("node:path");
|
|
52350
52470
|
init_cli_shared();
|
|
52351
52471
|
init_clean_exit();
|
|
@@ -52594,8 +52714,8 @@ function registerStageCommands(program3) {
|
|
|
52594
52714
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
52595
52715
|
const resolution = decideStage({
|
|
52596
52716
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
52597
|
-
hasCompose: (0,
|
|
52598
|
-
hasEnvExample: (0,
|
|
52717
|
+
hasCompose: (0, import_node_fs49.existsSync)((0, import_node_path46.join)(process.cwd(), "docker-compose.yml")),
|
|
52718
|
+
hasEnvExample: (0, import_node_fs49.existsSync)((0, import_node_path46.join)(process.cwd(), ".env.example")),
|
|
52599
52719
|
repo
|
|
52600
52720
|
});
|
|
52601
52721
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -52888,14 +53008,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
52888
53008
|
|
|
52889
53009
|
// src/tenant-artifact.ts
|
|
52890
53010
|
var import_node_crypto16 = require("node:crypto");
|
|
52891
|
-
var
|
|
53011
|
+
var import_node_fs50 = require("node:fs");
|
|
52892
53012
|
var import_promises11 = require("node:fs/promises");
|
|
52893
53013
|
var import_node_path47 = require("node:path");
|
|
52894
53014
|
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}$/;
|
|
52895
53015
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
52896
53016
|
async function sha256File(path2) {
|
|
52897
53017
|
const hash = (0, import_node_crypto16.createHash)("sha256");
|
|
52898
|
-
for await (const chunk of (0,
|
|
53018
|
+
for await (const chunk of (0, import_node_fs50.createReadStream)(path2)) hash.update(chunk);
|
|
52899
53019
|
return hash.digest("hex");
|
|
52900
53020
|
}
|
|
52901
53021
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
@@ -52918,7 +53038,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
52918
53038
|
return [key, value];
|
|
52919
53039
|
}));
|
|
52920
53040
|
headers["content-length"] = String(info.size);
|
|
52921
|
-
const stream = (0,
|
|
53041
|
+
const stream = (0, import_node_fs50.createReadStream)(path2);
|
|
52922
53042
|
let uploaded;
|
|
52923
53043
|
try {
|
|
52924
53044
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -53057,7 +53177,7 @@ function renderVerifySecrets(body) {
|
|
|
53057
53177
|
}
|
|
53058
53178
|
|
|
53059
53179
|
// src/command-register-developer.ts
|
|
53060
|
-
var
|
|
53180
|
+
var import_node_fs54 = require("node:fs");
|
|
53061
53181
|
init_clean_exit();
|
|
53062
53182
|
init_cli_shared();
|
|
53063
53183
|
init_error_codes();
|
|
@@ -53125,13 +53245,13 @@ async function executeWaveLand(plan, deps) {
|
|
|
53125
53245
|
// src/dist-drift.ts
|
|
53126
53246
|
var import_node_child_process19 = require("node:child_process");
|
|
53127
53247
|
var import_node_crypto18 = require("node:crypto");
|
|
53128
|
-
var
|
|
53248
|
+
var import_node_fs52 = require("node:fs");
|
|
53129
53249
|
var import_node_os23 = require("node:os");
|
|
53130
53250
|
var import_node_path49 = require("node:path");
|
|
53131
53251
|
|
|
53132
53252
|
// ../scripts/distribution-digest.mjs
|
|
53133
53253
|
var import_node_crypto17 = require("node:crypto");
|
|
53134
|
-
var
|
|
53254
|
+
var import_node_fs51 = require("node:fs");
|
|
53135
53255
|
var import_node_path48 = require("node:path");
|
|
53136
53256
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
53137
53257
|
function repoPath(root, declaredPath, label) {
|
|
@@ -53145,7 +53265,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
53145
53265
|
function digestFiles(files) {
|
|
53146
53266
|
const hash = (0, import_node_crypto17.createHash)("sha256");
|
|
53147
53267
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
53148
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
53268
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs51.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs51.readFileSync)(file.absolute);
|
|
53149
53269
|
hash.update(file.relative, "utf8");
|
|
53150
53270
|
hash.update("\0");
|
|
53151
53271
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -53160,8 +53280,8 @@ function digestFiles(files) {
|
|
|
53160
53280
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
53161
53281
|
return digestFiles(packedFiles.map((path2) => {
|
|
53162
53282
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
53163
|
-
if (!(0,
|
|
53164
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
53283
|
+
if (!(0, import_node_fs51.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
53284
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs51.lstatSync)(absolute) };
|
|
53165
53285
|
}));
|
|
53166
53286
|
}
|
|
53167
53287
|
|
|
@@ -53245,12 +53365,12 @@ function renderDistDriftReceipt(receipt) {
|
|
|
53245
53365
|
return lines2;
|
|
53246
53366
|
}
|
|
53247
53367
|
function readOrNull(path2) {
|
|
53248
|
-
return (0,
|
|
53368
|
+
return (0, import_node_fs52.existsSync)(path2) ? (0, import_node_fs52.readFileSync)(path2) : null;
|
|
53249
53369
|
}
|
|
53250
53370
|
function walkFiles(root) {
|
|
53251
53371
|
const files = [];
|
|
53252
53372
|
const walk2 = (directory) => {
|
|
53253
|
-
for (const entry of (0,
|
|
53373
|
+
for (const entry of (0, import_node_fs52.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
53254
53374
|
const child2 = (0, import_node_path49.join)(directory, entry.name);
|
|
53255
53375
|
if (entry.isDirectory()) walk2(child2);
|
|
53256
53376
|
else files.push(child2);
|
|
@@ -53261,7 +53381,7 @@ function walkFiles(root) {
|
|
|
53261
53381
|
}
|
|
53262
53382
|
function bomPathFor(root) {
|
|
53263
53383
|
try {
|
|
53264
|
-
const registry2 = JSON.parse((0,
|
|
53384
|
+
const registry2 = JSON.parse((0, import_node_fs52.readFileSync)((0, import_node_path49.join)(root, "surfaces.json"), "utf8"));
|
|
53265
53385
|
return (0, import_node_path49.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
53266
53386
|
} catch {
|
|
53267
53387
|
return (0, import_node_path49.join)(root, "distribution-bom.json");
|
|
@@ -53277,7 +53397,7 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
53277
53397
|
});
|
|
53278
53398
|
}
|
|
53279
53399
|
function runDistStatus(root) {
|
|
53280
|
-
const stage = (0,
|
|
53400
|
+
const stage = (0, import_node_fs52.mkdtempSync)((0, import_node_path49.join)((0, import_node_os23.tmpdir)(), "mmi-dist-drift-"));
|
|
53281
53401
|
let overlayCount = 0;
|
|
53282
53402
|
try {
|
|
53283
53403
|
const cliOut = (0, import_node_path49.join)(stage, "cli-dist");
|
|
@@ -53293,19 +53413,19 @@ function runDistStatus(root) {
|
|
|
53293
53413
|
const tree = (path2) => readOrNull((0, import_node_path49.join)(root, path2));
|
|
53294
53414
|
const distRoot = (0, import_node_path49.join)(root, "cli", "dist");
|
|
53295
53415
|
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path49.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
53296
|
-
const bom = JSON.parse((0,
|
|
53416
|
+
const bom = JSON.parse((0, import_node_fs52.readFileSync)(bomPathFor(root), "utf8"));
|
|
53297
53417
|
const digest = (entries) => {
|
|
53298
53418
|
const overlay = (0, import_node_path49.join)(stage, `overlay-${overlayCount++}`);
|
|
53299
53419
|
for (const entry of entries) {
|
|
53300
53420
|
const target = (0, import_node_path49.join)(overlay, entry.path);
|
|
53301
|
-
(0,
|
|
53302
|
-
(0,
|
|
53421
|
+
(0, import_node_fs52.mkdirSync)((0, import_node_path49.dirname)(target), { recursive: true });
|
|
53422
|
+
(0, import_node_fs52.writeFileSync)(target, entry.bytes);
|
|
53303
53423
|
}
|
|
53304
53424
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
53305
53425
|
};
|
|
53306
53426
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
53307
53427
|
} finally {
|
|
53308
|
-
(0,
|
|
53428
|
+
(0, import_node_fs52.rmSync)(stage, { recursive: true, force: true });
|
|
53309
53429
|
}
|
|
53310
53430
|
}
|
|
53311
53431
|
|
|
@@ -53518,7 +53638,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
53518
53638
|
|
|
53519
53639
|
// src/spawn-policy-core.ts
|
|
53520
53640
|
var import_node_child_process20 = require("node:child_process");
|
|
53521
|
-
var
|
|
53641
|
+
var import_node_fs53 = require("node:fs");
|
|
53522
53642
|
var import_node_path51 = require("node:path");
|
|
53523
53643
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
53524
53644
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
@@ -53605,7 +53725,7 @@ function runSpawnPolicy(root) {
|
|
|
53605
53725
|
for (const file of files) {
|
|
53606
53726
|
let raw;
|
|
53607
53727
|
try {
|
|
53608
|
-
raw = (0,
|
|
53728
|
+
raw = (0, import_node_fs53.readFileSync)((0, import_node_path51.join)(root, file), "utf8");
|
|
53609
53729
|
} catch {
|
|
53610
53730
|
continue;
|
|
53611
53731
|
}
|
|
@@ -53626,18 +53746,18 @@ function registerDeveloperCommands(program3) {
|
|
|
53626
53746
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
53627
53747
|
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) => {
|
|
53628
53748
|
const path2 = (0, import_node_path52.join)(process.cwd(), ".gitignore");
|
|
53629
|
-
const current = (0,
|
|
53749
|
+
const current = (0, import_node_fs54.existsSync)(path2) ? (0, import_node_fs54.readFileSync)(path2, "utf8") : null;
|
|
53630
53750
|
const plan = planManagedGitignore(current);
|
|
53631
53751
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
53632
53752
|
if (opts.json) {
|
|
53633
|
-
if (opts.write && plan.changed) (0,
|
|
53753
|
+
if (opts.write && plan.changed) (0, import_node_fs54.writeFileSync)(path2, plan.content, "utf8");
|
|
53634
53754
|
console.log(JSON.stringify(plan, null, 2));
|
|
53635
53755
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
53636
53756
|
return;
|
|
53637
53757
|
}
|
|
53638
53758
|
if (opts.write) {
|
|
53639
53759
|
if (plan.changed) {
|
|
53640
|
-
(0,
|
|
53760
|
+
(0, import_node_fs54.writeFileSync)(path2, plan.content, "utf8");
|
|
53641
53761
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
53642
53762
|
} else {
|
|
53643
53763
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -54037,7 +54157,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
54037
54157
|
);
|
|
54038
54158
|
const result = applyPluginCachePlan(
|
|
54039
54159
|
plan,
|
|
54040
|
-
(p) => (0,
|
|
54160
|
+
(p) => (0, import_node_fs55.rmSync)(p, { recursive: true }),
|
|
54041
54161
|
stagingApplyFsGuard(configRoot)
|
|
54042
54162
|
);
|
|
54043
54163
|
return {
|
|
@@ -54072,7 +54192,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
54072
54192
|
// has no generated routing index would
|
|
54073
54193
|
// get a permanent — demanding an artifact it never asked for.
|
|
54074
54194
|
docsIndexState: (root) => {
|
|
54075
|
-
if (!(0,
|
|
54195
|
+
if (!(0, import_node_fs55.existsSync)((0, import_node_path53.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
54076
54196
|
const real = createDocsIndexDeps(root);
|
|
54077
54197
|
let docs;
|
|
54078
54198
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -54081,7 +54201,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
54081
54201
|
},
|
|
54082
54202
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
54083
54203
|
healDocsIndex: (root) => {
|
|
54084
|
-
if (!(0,
|
|
54204
|
+
if (!(0, import_node_fs55.existsSync)((0, import_node_path53.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
54085
54205
|
const real = createDocsIndexDeps(root);
|
|
54086
54206
|
let docs;
|
|
54087
54207
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -54795,7 +54915,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
54795
54915
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
54796
54916
|
if (o.secretsFile) {
|
|
54797
54917
|
try {
|
|
54798
|
-
vars.push(`secrets=${(0,
|
|
54918
|
+
vars.push(`secrets=${(0, import_node_fs55.readFileSync)(o.secretsFile, "utf8")}`);
|
|
54799
54919
|
} catch (e) {
|
|
54800
54920
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
54801
54921
|
}
|
|
@@ -55184,15 +55304,15 @@ function ciAuditDeps2() {
|
|
|
55184
55304
|
readSeedFile: (path2) => {
|
|
55185
55305
|
if (!root) return null;
|
|
55186
55306
|
const fullPath = (0, import_node_path53.join)(root, path2);
|
|
55187
|
-
return (0,
|
|
55307
|
+
return (0, import_node_fs55.existsSync)(fullPath) ? (0, import_node_fs55.readFileSync)(fullPath, "utf8") : null;
|
|
55188
55308
|
}
|
|
55189
55309
|
};
|
|
55190
55310
|
}
|
|
55191
55311
|
function hubRoot2() {
|
|
55192
55312
|
const fromPkg = (0, import_node_path53.join)(__dirname, "..", "..");
|
|
55193
55313
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
55194
|
-
if ((0,
|
|
55195
|
-
if ((0,
|
|
55314
|
+
if ((0, import_node_fs55.existsSync)((0, import_node_path53.join)(fromPkg, marker))) return fromPkg;
|
|
55315
|
+
if ((0, import_node_fs55.existsSync)((0, import_node_path53.join)(process.cwd(), marker))) return process.cwd();
|
|
55196
55316
|
return null;
|
|
55197
55317
|
}
|
|
55198
55318
|
registerQueryCommands(program2);
|
|
@@ -55318,10 +55438,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
55318
55438
|
targets = resolution.targets;
|
|
55319
55439
|
}
|
|
55320
55440
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
55321
|
-
const fileMatrix = (0,
|
|
55441
|
+
const fileMatrix = (0, import_node_fs55.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs55.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
55322
55442
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
55323
55443
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
55324
|
-
const sanctioned = (0,
|
|
55444
|
+
const sanctioned = (0, import_node_fs55.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs55.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
55325
55445
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
55326
55446
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
55327
55447
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -55352,7 +55472,7 @@ function directoryBytes(path2) {
|
|
|
55352
55472
|
let total = 0;
|
|
55353
55473
|
let entries;
|
|
55354
55474
|
try {
|
|
55355
|
-
entries = (0,
|
|
55475
|
+
entries = (0, import_node_fs55.readdirSync)(path2, { withFileTypes: true });
|
|
55356
55476
|
} catch {
|
|
55357
55477
|
return 0;
|
|
55358
55478
|
}
|
|
@@ -55361,7 +55481,7 @@ function directoryBytes(path2) {
|
|
|
55361
55481
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
55362
55482
|
else {
|
|
55363
55483
|
try {
|
|
55364
|
-
total += (0,
|
|
55484
|
+
total += (0, import_node_fs55.statSync)(child2).size;
|
|
55365
55485
|
} catch {
|
|
55366
55486
|
}
|
|
55367
55487
|
}
|
|
@@ -55369,25 +55489,25 @@ function directoryBytes(path2) {
|
|
|
55369
55489
|
return total;
|
|
55370
55490
|
}
|
|
55371
55491
|
function listDirEntries(dir) {
|
|
55372
|
-
return (0,
|
|
55492
|
+
return (0, import_node_fs55.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
55373
55493
|
}
|
|
55374
55494
|
function readInstalledPluginRefs(configRoot) {
|
|
55375
55495
|
const p = installedPluginsPathForConfig(configRoot);
|
|
55376
|
-
if (!(0,
|
|
55496
|
+
if (!(0, import_node_fs55.existsSync)(p)) return [];
|
|
55377
55497
|
try {
|
|
55378
|
-
return installedPluginPaths((0,
|
|
55498
|
+
return installedPluginPaths((0, import_node_fs55.readFileSync)(p, "utf8"));
|
|
55379
55499
|
} catch {
|
|
55380
55500
|
return null;
|
|
55381
55501
|
}
|
|
55382
55502
|
}
|
|
55383
55503
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
55384
55504
|
return {
|
|
55385
|
-
exists: (p) => (0,
|
|
55386
|
-
listVersionDirs: (root) => (0,
|
|
55505
|
+
exists: (p) => (0, import_node_fs55.existsSync)(p),
|
|
55506
|
+
listVersionDirs: (root) => (0, import_node_fs55.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
55387
55507
|
dirBytes,
|
|
55388
|
-
listStagingDirs: (root) => (0,
|
|
55508
|
+
listStagingDirs: (root) => (0, import_node_fs55.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
55389
55509
|
try {
|
|
55390
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path53.join)(root, d.name), listDirEntries, (p) => (0,
|
|
55510
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path53.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs55.statSync)(p).mtimeMs) };
|
|
55391
55511
|
} catch {
|
|
55392
55512
|
return { name: d.name, mtimeMs: Date.now() };
|
|
55393
55513
|
}
|
|
@@ -55402,9 +55522,9 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
55402
55522
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
55403
55523
|
mtimeMs: (name) => {
|
|
55404
55524
|
const p = (0, import_node_path53.join)(stagingRoot, name);
|
|
55405
|
-
if (!(0,
|
|
55525
|
+
if (!(0, import_node_fs55.existsSync)(p)) return null;
|
|
55406
55526
|
try {
|
|
55407
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
55527
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs55.statSync)(q).mtimeMs);
|
|
55408
55528
|
} catch {
|
|
55409
55529
|
return null;
|
|
55410
55530
|
}
|
|
@@ -55430,7 +55550,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
55430
55550
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
55431
55551
|
);
|
|
55432
55552
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
55433
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
55553
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs55.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
55434
55554
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
55435
55555
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
55436
55556
|
else console.log(renderPluginCachePlan(plan, result));
|