@mutmutco/cli 3.136.0 → 3.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +354 -80
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5849,6 +5849,21 @@ async function sweepDeferredWorktrees(store, deps, removalContext) {
|
|
|
5849
5849
|
continue;
|
|
5850
5850
|
}
|
|
5851
5851
|
const owner = removalContext ? lookupWorktreeOwner(removalContext.primaryRoot, entry.path) : void 0;
|
|
5852
|
+
if (!current && deps.pathExists?.(entry.path) === false) {
|
|
5853
|
+
removed.push(entry.path);
|
|
5854
|
+
if (removalContext) {
|
|
5855
|
+
recordWorktreeRemoval(removalContext.primaryRoot, {
|
|
5856
|
+
action: "removed",
|
|
5857
|
+
command: removalContext.command,
|
|
5858
|
+
target: entry.path,
|
|
5859
|
+
branch: entry.branch,
|
|
5860
|
+
actor: removalContext.actor,
|
|
5861
|
+
owner,
|
|
5862
|
+
reason: "deferred worktree already absent from Git registration and filesystem"
|
|
5863
|
+
});
|
|
5864
|
+
}
|
|
5865
|
+
continue;
|
|
5866
|
+
}
|
|
5852
5867
|
if (removalContext) {
|
|
5853
5868
|
const activeRoot = removalContext.activeWorkspaceRoot ?? resolveActiveWorkspaceRoot();
|
|
5854
5869
|
const activeGuard = decideActiveWorkspaceGuard(entry.path, activeRoot, process.platform, {
|
|
@@ -8398,6 +8413,12 @@ function loadBootstrapSeeds(manifestJson) {
|
|
|
8398
8413
|
if (s.ownership !== "org" && s.ownership !== "repo") {
|
|
8399
8414
|
throw new Error(`invalid seed ownership '${s.ownership}' for ${s.target} (must be 'org' or 'repo')`);
|
|
8400
8415
|
}
|
|
8416
|
+
if (s.managedBlock) {
|
|
8417
|
+
const block = s.managedBlock;
|
|
8418
|
+
if (!block.source || !block.begin || !block.end || block.begin === block.end || /[\r\n]/.test(block.begin + block.end)) {
|
|
8419
|
+
throw new Error(`invalid managedBlock for ${s.target} (needs source and distinct single-line begin/end markers)`);
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8401
8422
|
}
|
|
8402
8423
|
return {
|
|
8403
8424
|
seeds,
|
|
@@ -8413,6 +8434,68 @@ function missingPlaceholders(rendered) {
|
|
|
8413
8434
|
for (const m of rendered.matchAll(PLACEHOLDER_RE)) out.add(m[1]);
|
|
8414
8435
|
return [...out];
|
|
8415
8436
|
}
|
|
8437
|
+
function isPropagatableSeed(seed) {
|
|
8438
|
+
return seed.ownership === "org" && seed.source === "self" || seed.managedBlock != null;
|
|
8439
|
+
}
|
|
8440
|
+
function sourceLines(content) {
|
|
8441
|
+
const lines = [];
|
|
8442
|
+
const newline = /\r\n|\n|\r/g;
|
|
8443
|
+
let start = 0;
|
|
8444
|
+
for (const match of content.matchAll(newline)) {
|
|
8445
|
+
const index = match.index;
|
|
8446
|
+
lines.push({ start, textEnd: index, end: index + match[0].length, text: content.slice(start, index) });
|
|
8447
|
+
start = index + match[0].length;
|
|
8448
|
+
}
|
|
8449
|
+
lines.push({ start, textEnd: content.length, end: content.length, text: content.slice(start) });
|
|
8450
|
+
return lines;
|
|
8451
|
+
}
|
|
8452
|
+
function normalizeEol(content) {
|
|
8453
|
+
return content.replace(/\r\n|\r|\n/g, "\n");
|
|
8454
|
+
}
|
|
8455
|
+
function upsertManagedSeedBlock(current, desired, begin, end) {
|
|
8456
|
+
const desiredLines = sourceLines(desired);
|
|
8457
|
+
const desiredBegins = desiredLines.filter((line) => line.text === begin);
|
|
8458
|
+
const desiredEnds = desiredLines.filter((line) => line.text === end);
|
|
8459
|
+
if (desiredBegins.length !== 1 || desiredEnds.length !== 1 || desiredEnds[0].start <= desiredBegins[0].start) {
|
|
8460
|
+
return { ok: false, reason: "managed block source must contain exactly one well-ordered marker pair" };
|
|
8461
|
+
}
|
|
8462
|
+
const desiredBegin = desiredBegins[0];
|
|
8463
|
+
const desiredEnd = desiredEnds[0];
|
|
8464
|
+
if (desired.slice(0, desiredBegin.start).trim() || desired.slice(desiredEnd.end).trim()) {
|
|
8465
|
+
return { ok: false, reason: "managed block source must contain only the bounded block" };
|
|
8466
|
+
}
|
|
8467
|
+
const canonical = desired.slice(desiredBegin.start, desiredEnd.textEnd);
|
|
8468
|
+
const currentLines = sourceLines(current);
|
|
8469
|
+
const begins = currentLines.filter((line) => line.text === begin);
|
|
8470
|
+
const ends = currentLines.filter((line) => line.text === end);
|
|
8471
|
+
if (begins.length === 0 && ends.length === 0) {
|
|
8472
|
+
const eol2 = current.match(/\r\n|\n|\r/)?.[0] ?? "\n";
|
|
8473
|
+
const block2 = normalizeEol(canonical).replace(/\n/g, eol2);
|
|
8474
|
+
if (current.length === 0) return { ok: true, content: `${block2}${eol2}`, changed: true, action: "insert" };
|
|
8475
|
+
const separator = /(?:\r\n|\n|\r)$/.test(current) ? eol2 : `${eol2}${eol2}`;
|
|
8476
|
+
return { ok: true, content: `${current}${separator}${block2}${eol2}`, changed: true, action: "insert" };
|
|
8477
|
+
}
|
|
8478
|
+
if (begins.length !== 1 || ends.length !== 1) {
|
|
8479
|
+
return { ok: false, reason: `malformed managed block markers (begin=${begins.length}, end=${ends.length})` };
|
|
8480
|
+
}
|
|
8481
|
+
const currentBegin = begins[0];
|
|
8482
|
+
const currentEnd = ends[0];
|
|
8483
|
+
if (currentEnd.start <= currentBegin.start) {
|
|
8484
|
+
return { ok: false, reason: "malformed managed block markers (end precedes begin)" };
|
|
8485
|
+
}
|
|
8486
|
+
const existing = current.slice(currentBegin.start, currentEnd.textEnd);
|
|
8487
|
+
if (normalizeEol(existing) === normalizeEol(canonical)) {
|
|
8488
|
+
return { ok: true, content: current, changed: false, action: "current" };
|
|
8489
|
+
}
|
|
8490
|
+
const eol = current.match(/\r\n|\n|\r/)?.[0] ?? "\n";
|
|
8491
|
+
const block = normalizeEol(canonical).replace(/\n/g, eol);
|
|
8492
|
+
return {
|
|
8493
|
+
ok: true,
|
|
8494
|
+
content: `${current.slice(0, currentBegin.start)}${block}${current.slice(currentEnd.textEnd)}`,
|
|
8495
|
+
changed: true,
|
|
8496
|
+
action: "replace"
|
|
8497
|
+
};
|
|
8498
|
+
}
|
|
8416
8499
|
async function resolveHubSeedSource(execGit, defaultBranch = "development") {
|
|
8417
8500
|
let status;
|
|
8418
8501
|
try {
|
|
@@ -9477,6 +9560,9 @@ function seedMatchesProjectType(seed, projectType) {
|
|
|
9477
9560
|
return projectType != null && seed.projectTypes.includes(projectType);
|
|
9478
9561
|
}
|
|
9479
9562
|
function planSeedAction(seed, exists) {
|
|
9563
|
+
if (seed.managedBlock) {
|
|
9564
|
+
return exists ? { target: seed.target, action: "update", ownership: "repo", reason: "repo-owned file; Hub-managed block reconciled in place" } : { target: seed.target, action: "create", ownership: "repo", reason: "repo-owned, missing; full template created with Hub-managed block" };
|
|
9565
|
+
}
|
|
9480
9566
|
if (seed.source === "managed-block") {
|
|
9481
9567
|
return exists ? { target: seed.target, action: "update", ownership: "org", reason: "org-managed block merged in-place (repo-owned lines preserved)" } : { target: seed.target, action: "create", ownership: "org", reason: "org-managed block; .gitignore absent, created" };
|
|
9482
9568
|
}
|
|
@@ -9535,6 +9621,18 @@ function resolveSeedContent(seed, vars, readFile9) {
|
|
|
9535
9621
|
}
|
|
9536
9622
|
return null;
|
|
9537
9623
|
}
|
|
9624
|
+
function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
|
|
9625
|
+
if (!seed.managedBlock) {
|
|
9626
|
+
return { ok: true, content: resolveSeedContent(seed, vars, readFile9), managed: seed.source === "managed-block" };
|
|
9627
|
+
}
|
|
9628
|
+
const base = remoteContent ?? resolveSeedContent(seed, vars, readFile9);
|
|
9629
|
+
if (base == null) return { ok: true, content: null, managed: true };
|
|
9630
|
+
const blockSeed = { ...seed, source: seed.managedBlock.source, managedBlock: void 0 };
|
|
9631
|
+
const desired = resolveSeedContent(blockSeed, vars, readFile9);
|
|
9632
|
+
if (desired == null) return { ok: true, content: null, managed: true };
|
|
9633
|
+
const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
|
|
9634
|
+
return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
|
|
9635
|
+
}
|
|
9538
9636
|
function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
9539
9637
|
const parsedRepo = parseOwnerRepo(repo);
|
|
9540
9638
|
const slug = parsedRepo.slug;
|
|
@@ -14122,6 +14220,11 @@ function semverRelation(a, b) {
|
|
|
14122
14220
|
function classifyRow(row) {
|
|
14123
14221
|
const findings = [];
|
|
14124
14222
|
const reasons = /* @__PURE__ */ new Set();
|
|
14223
|
+
if (row.onboarding.status === "drift") {
|
|
14224
|
+
findings.push(`canonical onboarding link drift: ${row.onboarding.detail}`);
|
|
14225
|
+
} else if (row.onboarding.status === "unknown") {
|
|
14226
|
+
reasons.add(`canonical onboarding link evidence unknown: ${row.onboarding.detail}`);
|
|
14227
|
+
}
|
|
14125
14228
|
const releaseRequired = row.track !== "trunk" && !row.classifications.includes("no-deploy-surface");
|
|
14126
14229
|
const tagIsVersion = Boolean(row.release.version) && row.unknowns.some((u) => u.startsWith(NO_VERSION_LOCK_PREFIX));
|
|
14127
14230
|
for (const unknown of row.unknowns) {
|
|
@@ -14384,10 +14487,10 @@ var rollout_plan_default = {
|
|
|
14384
14487
|
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)."
|
|
14385
14488
|
},
|
|
14386
14489
|
baseline: {
|
|
14387
|
-
version: "3.
|
|
14388
|
-
tag: "v3.
|
|
14389
|
-
commit: "
|
|
14390
|
-
npm: "@mutmutco/cli@3.
|
|
14490
|
+
version: "3.137.0",
|
|
14491
|
+
tag: "v3.137.0",
|
|
14492
|
+
commit: "48d8161cd121",
|
|
14493
|
+
npm: "@mutmutco/cli@3.137.0"
|
|
14391
14494
|
},
|
|
14392
14495
|
exitCriterion: "fleet-n-of-n",
|
|
14393
14496
|
hubOnlyShortcut: "forbidden",
|
|
@@ -14404,14 +14507,14 @@ var rollout_plan_default = {
|
|
|
14404
14507
|
repo: "mutmutco/mmi-hub",
|
|
14405
14508
|
role: "canary",
|
|
14406
14509
|
schedule: "train",
|
|
14407
|
-
v3Target: "v3.
|
|
14510
|
+
v3Target: "v3.137.0"
|
|
14408
14511
|
}
|
|
14409
14512
|
],
|
|
14410
14513
|
rollbackTrigger: "Any red inside the post-cut soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a v3 client refused while the compat window must still admit it (SUPPORTED_MINOR_WINDOW=2, MIN_CLIENT_VERSION 0.0.0 \u2014 D6a), or npm consumer install/doctor failure on the v4 dist.",
|
|
14411
14514
|
rollback: {
|
|
14412
14515
|
independent: true,
|
|
14413
|
-
mechanism: "npm dist-tag latest -> 3.
|
|
14414
|
-
v3Target: "v3.
|
|
14516
|
+
mechanism: "npm dist-tag latest -> 3.137.0 and redeploy the Hub Lambda from tag v3.137.0 (48d8161cd121); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
14517
|
+
v3Target: "v3.137.0 (@mutmutco/cli@3.137.0, tag commit 48d8161cd121 \u2014 the preserved latest-v3 distribution, D6b)"
|
|
14415
14518
|
}
|
|
14416
14519
|
},
|
|
14417
14520
|
{
|
|
@@ -15619,6 +15722,7 @@ function registerQueryCommands(program3) {
|
|
|
15619
15722
|
|
|
15620
15723
|
// src/fleet-track-inventory.ts
|
|
15621
15724
|
var NO_DEPLOY_SURFACE_MODELS = /* @__PURE__ */ new Set(["none", "content"]);
|
|
15725
|
+
var CANONICAL_ONBOARDING_URL = "https://github.com/mutmutco/MMI-Hub/blob/main/docs/Architecture/agentic-dev-environment.md";
|
|
15622
15726
|
function ghErrorText(e) {
|
|
15623
15727
|
const err = e;
|
|
15624
15728
|
return String(err?.stderr || err?.message || e).trim().replace(/\s+/g, " ");
|
|
@@ -15649,6 +15753,23 @@ async function readFleetPluginProbe(deps, repo, branch) {
|
|
|
15649
15753
|
return isGhNotFound2(e) ? { repo, state: "absent" } : { repo, state: "unknown", unknown: `plugin surface read failed: ${ghErrorText(e)}` };
|
|
15650
15754
|
}
|
|
15651
15755
|
}
|
|
15756
|
+
async function readFleetOnboardingProbe(deps, repo, branch) {
|
|
15757
|
+
const [owner, name] = repo.split("/");
|
|
15758
|
+
const ref = branch ? `?ref=${encodeURIComponent(branch)}` : "";
|
|
15759
|
+
try {
|
|
15760
|
+
const raw = await deps.ghJson([
|
|
15761
|
+
"api",
|
|
15762
|
+
`repos/${encodeURIComponent(owner ?? "")}/${encodeURIComponent(name ?? "")}/contents/README.md${ref}`
|
|
15763
|
+
]);
|
|
15764
|
+
if (raw.encoding !== "base64" || typeof raw.content !== "string") {
|
|
15765
|
+
return { repo, status: "unknown", detail: "README contents read returned no base64 content" };
|
|
15766
|
+
}
|
|
15767
|
+
const readme = Buffer.from(raw.content.replace(/\s/g, ""), "base64").toString("utf8");
|
|
15768
|
+
return readme.includes(CANONICAL_ONBOARDING_URL) ? { repo, status: "canonical", detail: CANONICAL_ONBOARDING_URL } : { repo, status: "drift", detail: `README.md does not link ${CANONICAL_ONBOARDING_URL}` };
|
|
15769
|
+
} catch (e) {
|
|
15770
|
+
return isGhNotFound2(e) ? { repo, status: "drift", detail: `README.md is absent; expected ${CANONICAL_ONBOARDING_URL}` } : { repo, status: "unknown", detail: `onboarding README read failed: ${ghErrorText(e)}` };
|
|
15771
|
+
}
|
|
15772
|
+
}
|
|
15652
15773
|
async function mapBounded2(values, limit, read) {
|
|
15653
15774
|
const results = new Array(values.length);
|
|
15654
15775
|
let next = 0;
|
|
@@ -15679,9 +15800,10 @@ function classificationsOf(surfaces) {
|
|
|
15679
15800
|
if (surfaces.deploy === "absent") classes.push("no-deploy-surface");
|
|
15680
15801
|
return classes;
|
|
15681
15802
|
}
|
|
15682
|
-
function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt) {
|
|
15803
|
+
function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generatedAt, onboardingProbes = []) {
|
|
15683
15804
|
const releaseByRepo = new Map(releaseProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
15684
15805
|
const pluginByRepo = new Map(pluginProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
15806
|
+
const onboardingByRepo = new Map(onboardingProbes.map((p) => [p.repo.toLowerCase(), p]));
|
|
15685
15807
|
const projectByRepo = /* @__PURE__ */ new Map();
|
|
15686
15808
|
const anomalies = [];
|
|
15687
15809
|
for (const project2 of projects) {
|
|
@@ -15703,11 +15825,13 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15703
15825
|
const project2 = projectByRepo.get(repo.toLowerCase());
|
|
15704
15826
|
const release = releaseByRepo.get(repo.toLowerCase());
|
|
15705
15827
|
const plugin = pluginByRepo.get(repo.toLowerCase());
|
|
15828
|
+
const onboarding = onboardingByRepo.get(repo.toLowerCase()) ?? { repo, status: "unknown", detail: "onboarding README was not probed" };
|
|
15706
15829
|
const surfaces = classifySurfaces(project2, plugin);
|
|
15707
15830
|
const unknowns = [...release?.unknowns ?? []];
|
|
15708
15831
|
if (!release) unknowns.push("GitHub release evidence was not collected");
|
|
15709
15832
|
if (plugin?.unknown) unknowns.push(plugin.unknown);
|
|
15710
15833
|
if (!plugin) unknowns.push("plugin surface was not probed");
|
|
15834
|
+
if (onboarding.status === "unknown") unknowns.push(onboarding.detail);
|
|
15711
15835
|
return {
|
|
15712
15836
|
repo,
|
|
15713
15837
|
slug: String(project2?.slug ?? repo.split("/")[1] ?? repo).toLowerCase(),
|
|
@@ -15719,6 +15843,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15719
15843
|
surfaces,
|
|
15720
15844
|
classifications: classificationsOf(surfaces),
|
|
15721
15845
|
pluginVersion: plugin?.version ?? null,
|
|
15846
|
+
onboarding,
|
|
15722
15847
|
release: {
|
|
15723
15848
|
tag: release?.releaseTag ?? null,
|
|
15724
15849
|
version: release?.releasedVersion ?? null,
|
|
@@ -15731,7 +15856,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15731
15856
|
requiredChecks: Array.isArray(project2?.requiredChecks) ? project2.requiredChecks : null,
|
|
15732
15857
|
exemptReason: typeof project2?.ciExemptReason === "string" ? project2.ciExemptReason : null
|
|
15733
15858
|
},
|
|
15734
|
-
source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live" },
|
|
15859
|
+
source: { registry: "hub-registry-live", release: "github-releases-live", plugin: "github-contents-live", onboarding: "github-readme-live" },
|
|
15735
15860
|
freshness: { readAt: generatedAt, releasedAt: release?.releasedAt ?? null },
|
|
15736
15861
|
unknowns
|
|
15737
15862
|
};
|
|
@@ -15744,6 +15869,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15744
15869
|
roster: "hub-registry-live",
|
|
15745
15870
|
releases: "github-releases-live",
|
|
15746
15871
|
plugin: "github-contents-live",
|
|
15872
|
+
onboarding: "github-readme-live",
|
|
15747
15873
|
committedInventory: "never"
|
|
15748
15874
|
},
|
|
15749
15875
|
counts: {
|
|
@@ -15754,6 +15880,7 @@ function buildFleetTrackInventory(projects, releaseProbes, pluginProbes, generat
|
|
|
15754
15880
|
noVault: repos.filter((r) => r.classifications.includes("no-vault")).length,
|
|
15755
15881
|
noPlugin: repos.filter((r) => r.classifications.includes("no-plugin")).length,
|
|
15756
15882
|
noDeploySurface: repos.filter((r) => r.classifications.includes("no-deploy-surface")).length,
|
|
15883
|
+
onboardingDrift: repos.filter((r) => r.onboarding.status === "drift").length,
|
|
15757
15884
|
unknownRows: repos.filter((r) => r.unknowns.length > 0).length,
|
|
15758
15885
|
anomalies: anomalies.length
|
|
15759
15886
|
},
|
|
@@ -15785,11 +15912,12 @@ async function readFleetTrackInventory(deps = defaultFleetTrackInventoryDeps())
|
|
|
15785
15912
|
}
|
|
15786
15913
|
}
|
|
15787
15914
|
}
|
|
15788
|
-
const [releaseProbes, pluginProbes] = await Promise.all([
|
|
15915
|
+
const [releaseProbes, pluginProbes, onboardingProbes] = await Promise.all([
|
|
15789
15916
|
mapBounded2(repos, 6, (repo) => readOrgVersionProbe(deps.query, repo)),
|
|
15790
|
-
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
|
|
15917
|
+
mapBounded2(repos, 6, (repo) => readFleetPluginProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase()))),
|
|
15918
|
+
mapBounded2(repos, 6, (repo) => readFleetOnboardingProbe(deps.query, repo, branchByRepo.get(repo.toLowerCase())))
|
|
15791
15919
|
]);
|
|
15792
|
-
return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString());
|
|
15920
|
+
return buildFleetTrackInventory(projects, releaseProbes, pluginProbes, deps.now().toISOString(), onboardingProbes);
|
|
15793
15921
|
}
|
|
15794
15922
|
function formatFleetTrackInventory(report) {
|
|
15795
15923
|
const { counts } = report;
|
|
@@ -15797,13 +15925,13 @@ function formatFleetTrackInventory(report) {
|
|
|
15797
15925
|
`fleet version-track inventory \u2014 ${counts.registeredRepos} registered repositories (${counts.projects} registry projects)`,
|
|
15798
15926
|
`source: live Hub registry + live GitHub releases/contents \xB7 read ${report.generatedAt} \xB7 committed inventory: never`,
|
|
15799
15927
|
`tracks: ${counts.tracks.full} full \xB7 ${counts.tracks.direct} direct \xB7 ${counts.tracks.trunk} trunk`,
|
|
15800
|
-
`classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.unknownRows} rows with unknowns`,
|
|
15928
|
+
`classified: ${counts.noBoard} no-board \xB7 ${counts.noVault} no-vault \xB7 ${counts.noPlugin} no-plugin \xB7 ${counts.noDeploySurface} no-deploy-surface \xB7 ${counts.onboardingDrift} onboarding-link drift \xB7 ${counts.unknownRows} rows with unknowns`,
|
|
15801
15929
|
"",
|
|
15802
|
-
"repository | track | deploy model | released | plugin | classifications"
|
|
15930
|
+
"repository | track | deploy model | released | plugin | onboarding | classifications"
|
|
15803
15931
|
];
|
|
15804
15932
|
for (const row of report.repos) {
|
|
15805
15933
|
lines.push(
|
|
15806
|
-
`${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.classifications.join(", ") || "none"}`
|
|
15934
|
+
`${row.repo} | ${row.track}${row.declaredTrack ? "" : " (derived)"} | ${row.deployModel ?? "UNDECLARED"} | ${row.release.version ?? "UNKNOWN"} | ${row.pluginVersion ?? (row.surfaces.plugin === "present" ? "present" : row.surfaces.plugin)} | ${row.onboarding.status} | ${row.classifications.join(", ") || "none"}`
|
|
15807
15935
|
);
|
|
15808
15936
|
for (const unknown of row.unknowns) lines.push(` ? ${unknown}`);
|
|
15809
15937
|
}
|
|
@@ -15818,7 +15946,7 @@ function formatFleetTrackInventory(report) {
|
|
|
15818
15946
|
function registerFleetTrackInventory(program3) {
|
|
15819
15947
|
const train = program3.commands.find((c) => c.name() === "train");
|
|
15820
15948
|
if (!train) throw new Error("train inventory registration requires the train command group");
|
|
15821
|
-
train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track,
|
|
15949
|
+
train.command("inventory").description("live fleet version-track inventory (#4451) \u2014 every registered repo's track, release evidence, surface classification, and canonical Hub onboarding-link evidence").option("--json", "machine-readable output (the typed D2b input shape)").action(async (o) => {
|
|
15822
15950
|
try {
|
|
15823
15951
|
const report = await readFleetTrackInventory();
|
|
15824
15952
|
console.log(o.json ? JSON.stringify(report, null, 2) : formatFleetTrackInventory(report));
|
|
@@ -20069,7 +20197,7 @@ var import_node_net = require("node:net");
|
|
|
20069
20197
|
var import_node_util5 = require("node:util");
|
|
20070
20198
|
|
|
20071
20199
|
// src/rules-sync.ts
|
|
20072
|
-
function
|
|
20200
|
+
function normalizeEol2(s) {
|
|
20073
20201
|
return s.replace(/\r\n/g, "\n");
|
|
20074
20202
|
}
|
|
20075
20203
|
|
|
@@ -20122,8 +20250,8 @@ function envFileKeys(content) {
|
|
|
20122
20250
|
return keys;
|
|
20123
20251
|
}
|
|
20124
20252
|
function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
|
|
20125
|
-
const example =
|
|
20126
|
-
const target =
|
|
20253
|
+
const example = normalizeEol2(exampleContent);
|
|
20254
|
+
const target = normalizeEol2(targetContent);
|
|
20127
20255
|
const exampleKeys = envFileKeys(example);
|
|
20128
20256
|
const targetKeys = envFileKeys(target);
|
|
20129
20257
|
for (const key of exampleKeys) {
|
|
@@ -25448,7 +25576,7 @@ function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync
|
|
|
25448
25576
|
return [];
|
|
25449
25577
|
}
|
|
25450
25578
|
}
|
|
25451
|
-
function rebuildRepoIndex(cwd,
|
|
25579
|
+
function rebuildRepoIndex(cwd, repoSlug3) {
|
|
25452
25580
|
const candidates = listCandidatePaths(cwd).filter(isIndexablePath);
|
|
25453
25581
|
const ignored = defaultIsIgnored(cwd, candidates);
|
|
25454
25582
|
const readmeHints = loadReadmeHints(cwd, candidates.filter((p) => !ignored.has(p)));
|
|
@@ -25480,7 +25608,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
25480
25608
|
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
25481
25609
|
const projection = {
|
|
25482
25610
|
schema: REPO_INDEX_SCHEMA,
|
|
25483
|
-
repo:
|
|
25611
|
+
repo: repoSlug3,
|
|
25484
25612
|
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25485
25613
|
entries
|
|
25486
25614
|
};
|
|
@@ -28756,6 +28884,71 @@ function renderPropagationReport(plan) {
|
|
|
28756
28884
|
return lines.join("\n");
|
|
28757
28885
|
}
|
|
28758
28886
|
|
|
28887
|
+
// src/bootstrap-propagation-identity.ts
|
|
28888
|
+
var import_node_crypto8 = require("node:crypto");
|
|
28889
|
+
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
28890
|
+
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
28891
|
+
function safeBranchPart(value, maxLength, fallback) {
|
|
28892
|
+
const safe = value.normalize("NFKD").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/g, "");
|
|
28893
|
+
return safe || fallback;
|
|
28894
|
+
}
|
|
28895
|
+
function repoSlug2(repo) {
|
|
28896
|
+
return repo.trim().split("/").pop()?.toLowerCase() || "repo";
|
|
28897
|
+
}
|
|
28898
|
+
function propagationBranch(repo, target) {
|
|
28899
|
+
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
28900
|
+
const targetPart = safeBranchPart(target, 48, "target");
|
|
28901
|
+
const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
28902
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
28903
|
+
}
|
|
28904
|
+
function legacyPropagationBranch(repo) {
|
|
28905
|
+
return `${PROPAGATION_BRANCH_PREFIX}${repoSlug2(repo)}`;
|
|
28906
|
+
}
|
|
28907
|
+
function renderPropagationTargetMarker(target) {
|
|
28908
|
+
return `<!-- ${TARGET_MARKER_NAME}: ${JSON.stringify(target)} -->`;
|
|
28909
|
+
}
|
|
28910
|
+
function targetMarkers(body) {
|
|
28911
|
+
const present = body.includes(`${TARGET_MARKER_NAME}:`);
|
|
28912
|
+
const targets = [];
|
|
28913
|
+
const marker = new RegExp(`<!--\\s*${TARGET_MARKER_NAME}:\\s*(.*?)\\s*-->`, "g");
|
|
28914
|
+
for (const match of body.matchAll(marker)) {
|
|
28915
|
+
try {
|
|
28916
|
+
const parsed = JSON.parse(match[1]);
|
|
28917
|
+
if (typeof parsed === "string") targets.push(parsed);
|
|
28918
|
+
} catch {
|
|
28919
|
+
}
|
|
28920
|
+
}
|
|
28921
|
+
return { present, targets };
|
|
28922
|
+
}
|
|
28923
|
+
function canonicalProseTargets(body) {
|
|
28924
|
+
const targets = [];
|
|
28925
|
+
const command = /bootstrap propagate --target (.+?) --execute`/g;
|
|
28926
|
+
const prose = /(?:org-owned copy of|declared marker-bounded block inside repo-owned) `([^`]+)` — the file this PR carries and nothing else/g;
|
|
28927
|
+
for (const match of body.matchAll(command)) targets.push(match[1]);
|
|
28928
|
+
for (const match of body.matchAll(prose)) targets.push(match[1]);
|
|
28929
|
+
return targets;
|
|
28930
|
+
}
|
|
28931
|
+
function propagationPrMatchesTarget(pr2, target, identity) {
|
|
28932
|
+
const body = pr2.body ?? "";
|
|
28933
|
+
const markers = targetMarkers(body);
|
|
28934
|
+
if (markers.present) {
|
|
28935
|
+
if (markers.targets.length !== 1 || markers.targets[0] !== target) return false;
|
|
28936
|
+
return !pr2.files?.length || pr2.files.length === 1 && pr2.files[0] === target;
|
|
28937
|
+
}
|
|
28938
|
+
if (identity === "target-specific") return false;
|
|
28939
|
+
const proseTargets = canonicalProseTargets(body);
|
|
28940
|
+
if (proseTargets.some((candidate) => candidate !== target)) return false;
|
|
28941
|
+
if (pr2.files?.length) {
|
|
28942
|
+
return pr2.files.length === 1 && pr2.files[0] === target;
|
|
28943
|
+
}
|
|
28944
|
+
return proseTargets.length > 0;
|
|
28945
|
+
}
|
|
28946
|
+
function propagationPrCandidatesForTarget(target, targetSpecificPrs, legacyPrs) {
|
|
28947
|
+
const targetSpecific = targetSpecificPrs.filter((pr2) => propagationPrMatchesTarget(pr2, target, "target-specific"));
|
|
28948
|
+
if (targetSpecific.length) return targetSpecific;
|
|
28949
|
+
return legacyPrs.filter((pr2) => propagationPrMatchesTarget(pr2, target, "legacy"));
|
|
28950
|
+
}
|
|
28951
|
+
|
|
28759
28952
|
// src/bootstrap-rollback.ts
|
|
28760
28953
|
function resolveRollbackRecord(records, repo, target) {
|
|
28761
28954
|
const candidates = records.filter((r) => r.repo === repo && r.target === target && r.mergeSha);
|
|
@@ -29609,6 +29802,7 @@ function registerBootstrapCommands(program3) {
|
|
|
29609
29802
|
return fail(`bootstrap apply: --only '${onlyTarget}' names no seed in ${manifestPath}. Declared targets:
|
|
29610
29803
|
${known}`);
|
|
29611
29804
|
}
|
|
29805
|
+
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
29612
29806
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
29613
29807
|
const readFile9 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
|
|
29614
29808
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
@@ -29716,9 +29910,22 @@ function registerBootstrapCommands(program3) {
|
|
|
29716
29910
|
exists = false;
|
|
29717
29911
|
}
|
|
29718
29912
|
const planned = planSeedAction(resolved, exists);
|
|
29719
|
-
const
|
|
29720
|
-
|
|
29721
|
-
|
|
29913
|
+
const isLegacyBlock = resolved.source === "managed-block";
|
|
29914
|
+
let content = null;
|
|
29915
|
+
let isManaged = isLegacyBlock || resolved.managedBlock != null;
|
|
29916
|
+
if (planned.action === "create" || planned.action === "update") {
|
|
29917
|
+
if (isLegacyBlock) {
|
|
29918
|
+
content = upsertManagedGitignoreBlock(remoteContent).content;
|
|
29919
|
+
} else {
|
|
29920
|
+
const writeContent = resolveSeedWriteContent(resolved, vars, readFile9, remoteContent);
|
|
29921
|
+
if (!writeContent.ok) {
|
|
29922
|
+
return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
29923
|
+
}
|
|
29924
|
+
content = writeContent.content;
|
|
29925
|
+
isManaged = writeContent.managed;
|
|
29926
|
+
}
|
|
29927
|
+
}
|
|
29928
|
+
const action = reconcileSeedAction(planned, content, isManaged, remoteContent);
|
|
29722
29929
|
actions.push(action);
|
|
29723
29930
|
const docBody = content ?? remoteContent;
|
|
29724
29931
|
if (resolved.target.startsWith("docs/") && resolved.target.endsWith(".md") && docBody !== null) {
|
|
@@ -29771,11 +29978,11 @@ function registerBootstrapCommands(program3) {
|
|
|
29771
29978
|
"--head",
|
|
29772
29979
|
seedPlan.branch,
|
|
29773
29980
|
"--title",
|
|
29774
|
-
onlyTarget ? `chore: propagate org-owned ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
|
|
29981
|
+
onlyTarget ? `chore: propagate ${onlyManagedBlock ? "Hub-managed block in" : "org-owned"} ${onlyTarget} from MMI-Hub` : `bootstrap: seed ${parsedRepo.name} (${baseBranch} is protected)`,
|
|
29775
29982
|
"--body",
|
|
29776
29983
|
onlyTarget ? `Auto-opened by \`mmi-cli devops bootstrap apply ${repo} --only ${onlyTarget} --execute\` (#3818).
|
|
29777
29984
|
|
|
29778
|
-
\`${onlyTarget}
|
|
29985
|
+
${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owned \`${onlyTarget}\`` : `The org-owned \`${onlyTarget}\` seed`} is declared by MMI-Hub's \`skills/bootstrap/seeds/manifest.json\` at MMI-Hub@${seedSource.sha}; this PR reconciles that declared scope and preserves everything outside it. It carries that file and nothing else \u2014 no labels, ruleset, merge settings or registry META were touched.
|
|
29779
29986
|
|
|
29780
29987
|
\`${baseBranch}\` is protected (${seedPlan.reason}), so delivery goes via this branch + PR \u2014 a direct contents PUT 409s on a protected base.` : `Auto-opened by \`mmi-cli devops bootstrap apply --execute ${repo}\` (#2286): \`${baseBranch}\` is protected (${seedPlan.reason}), so the org-managed seed files are delivered via this branch + PR \u2014 a direct contents PUT 409s on a protected base ("N of N required status checks are expected"). Seeds are from MMI-Hub@${seedSource.sha}.`
|
|
29781
29988
|
]);
|
|
@@ -29881,23 +30088,24 @@ LIVE apply to ${repo}:
|
|
|
29881
30088
|
${applied.join("\n ")}`);
|
|
29882
30089
|
}
|
|
29883
30090
|
});
|
|
29884
|
-
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
|
|
30091
|
+
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 () => {
|
|
29885
30092
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
29886
30093
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
29887
30094
|
if (!(0, import_node_fs38.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`);
|
|
29888
30095
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
29889
30096
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
29890
30097
|
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
29891
|
-
const propagatable = manifest.seeds.filter(
|
|
30098
|
+
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
29892
30099
|
if (!o.target) {
|
|
29893
30100
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
29894
30101
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
29895
30102
|
}
|
|
29896
30103
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
29897
|
-
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no
|
|
30104
|
+
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
29898
30105
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
29899
|
-
if (!(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
29900
|
-
const hubContent = (0, import_node_fs38.readFileSync)(seed.target, "utf8");
|
|
30106
|
+
if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
30107
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
|
|
30108
|
+
const readSeedFile = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
|
|
29901
30109
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
29902
30110
|
const cfg = await loadConfig();
|
|
29903
30111
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -29953,11 +30161,12 @@ LIVE apply to ${repo}:
|
|
|
29953
30161
|
});
|
|
29954
30162
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
29955
30163
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
29956
|
-
const branchPrefix = "seed-propagate";
|
|
29957
30164
|
const reads = [];
|
|
30165
|
+
const desiredByRepo = /* @__PURE__ */ new Map();
|
|
29958
30166
|
for (const r of repos) {
|
|
29959
30167
|
if (r.waiver) continue;
|
|
29960
|
-
const
|
|
30168
|
+
const repoClass = classOf(r.repo);
|
|
30169
|
+
const baseBranch = repoClass === "content" ? "main" : "development";
|
|
29961
30170
|
let content = null;
|
|
29962
30171
|
try {
|
|
29963
30172
|
const resp = await gh(["api", `repos/${r.repo}/contents/${enc(seed.target)}?ref=${baseBranch}`]);
|
|
@@ -29966,13 +30175,35 @@ LIVE apply to ${repo}:
|
|
|
29966
30175
|
} catch {
|
|
29967
30176
|
content = null;
|
|
29968
30177
|
}
|
|
29969
|
-
|
|
30178
|
+
let desired;
|
|
30179
|
+
if (seed.managedBlock) {
|
|
30180
|
+
const vars = withDerivedRepoVars({}, parseOwnerRepo(r.repo), repoClass);
|
|
30181
|
+
const resolved = resolveSeedWriteContent(seed, vars, readSeedFile, content);
|
|
30182
|
+
if (!resolved.ok) {
|
|
30183
|
+
return fail(`bootstrap propagate: ${r.repo} ${seed.target}: ${resolved.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
30184
|
+
}
|
|
30185
|
+
if (resolved.content == null) {
|
|
30186
|
+
return fail(`bootstrap propagate: cannot resolve the full template or managed block source for '${seed.target}' \u2014 refusing a partial fleet plan`);
|
|
30187
|
+
}
|
|
30188
|
+
desired = resolved.content;
|
|
30189
|
+
} else {
|
|
30190
|
+
desired = hubContent;
|
|
30191
|
+
}
|
|
30192
|
+
desiredByRepo.set(r.repo, desired);
|
|
30193
|
+
const drift = compareSeedBytes(desired, content);
|
|
29970
30194
|
let pr2;
|
|
29971
30195
|
try {
|
|
29972
|
-
const
|
|
29973
|
-
|
|
29974
|
-
|
|
29975
|
-
|
|
30196
|
+
const listBranchPrs = async (branch) => {
|
|
30197
|
+
const listed = await gh(["pr", "list", "--repo", r.repo, "--head", branch, "--base", baseBranch, "--state", "all", "--json", "number,url,state,statusCheckRollup,mergeCommit,body,files", "--limit", "100"]);
|
|
30198
|
+
return JSON.parse(listed.stdout || "[]");
|
|
30199
|
+
};
|
|
30200
|
+
const targetSpecific = (await listBranchPrs(propagationBranch(r.repo, seed.target))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
|
|
30201
|
+
let identified = propagationPrCandidatesForTarget(seed.target, targetSpecific, []);
|
|
30202
|
+
if (!identified.length) {
|
|
30203
|
+
const legacy = (await listBranchPrs(legacyPropagationBranch(r.repo))).map((p2) => ({ ...p2, files: p2.files?.map((f) => f.path) }));
|
|
30204
|
+
identified = propagationPrCandidatesForTarget(seed.target, [], legacy);
|
|
30205
|
+
}
|
|
30206
|
+
const p = identified[0];
|
|
29976
30207
|
if (p) {
|
|
29977
30208
|
const rollup = p.statusCheckRollup ?? [];
|
|
29978
30209
|
const checks = rollup.length === 0 ? "none" : rollup.some((c) => c.conclusion === "FAILURE" || c.state === "FAILURE") ? "red" : rollup.every((c) => c.conclusion === "SUCCESS" || c.state === "SUCCESS") ? "success" : "pending";
|
|
@@ -30005,9 +30236,8 @@ LIVE apply to ${repo}:
|
|
|
30005
30236
|
const headSha = seedSource.sha;
|
|
30006
30237
|
for (const rec of plan.records) {
|
|
30007
30238
|
if (rec.action !== "open-pr") continue;
|
|
30008
|
-
const repoEntry = repos.find((r) => r.repo === rec.repo);
|
|
30009
30239
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
30010
|
-
const branch =
|
|
30240
|
+
const branch = propagationBranch(rec.repo, seed.target);
|
|
30011
30241
|
const baseRef = await gh(["api", `repos/${rec.repo}/git/ref/heads/${baseBranch}`, "--jq", ".object.sha"]);
|
|
30012
30242
|
const baseSha = baseRef.stdout.trim();
|
|
30013
30243
|
let branchExists = true;
|
|
@@ -30025,7 +30255,9 @@ LIVE apply to ${repo}:
|
|
|
30025
30255
|
existingSha = void 0;
|
|
30026
30256
|
}
|
|
30027
30257
|
const tmp = (0, import_node_path36.join)((0, import_node_os16.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
30028
|
-
|
|
30258
|
+
const desiredContent = desiredByRepo.get(rec.repo);
|
|
30259
|
+
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
30260
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
30029
30261
|
try {
|
|
30030
30262
|
await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
30031
30263
|
} finally {
|
|
@@ -30034,8 +30266,14 @@ LIVE apply to ${repo}:
|
|
|
30034
30266
|
} catch {
|
|
30035
30267
|
}
|
|
30036
30268
|
}
|
|
30037
|
-
const openPrs = await gh(["pr", "list", "--repo", rec.repo, "--head", branch, "--base", baseBranch, "--state", "open", "--json", "number,url"]);
|
|
30038
|
-
const
|
|
30269
|
+
const openPrs = await gh(["pr", "list", "--repo", rec.repo, "--head", branch, "--base", baseBranch, "--state", "open", "--json", "number,url,body,files"]);
|
|
30270
|
+
const listedOpenPrs = JSON.parse(openPrs.stdout || "[]");
|
|
30271
|
+
const identifiedOpenPrs = propagationPrCandidatesForTarget(
|
|
30272
|
+
seed.target,
|
|
30273
|
+
listedOpenPrs.map((p) => ({ ...p, files: p.files?.map((f) => f.path) })),
|
|
30274
|
+
[]
|
|
30275
|
+
);
|
|
30276
|
+
const prDecision = decideSeedPrAction(identifiedOpenPrs);
|
|
30039
30277
|
let prUrl;
|
|
30040
30278
|
if (prDecision.action === "reuse") {
|
|
30041
30279
|
prUrl = prDecision.url;
|
|
@@ -30050,11 +30288,12 @@ LIVE apply to ${repo}:
|
|
|
30050
30288
|
"--head",
|
|
30051
30289
|
branch,
|
|
30052
30290
|
"--title",
|
|
30053
|
-
`chore: propagate org-owned ${seed.target} from MMI-Hub (wave ${rec.wave})`,
|
|
30291
|
+
`chore: propagate ${seed.managedBlock ? "Hub-managed block in" : "org-owned"} ${seed.target} from MMI-Hub (wave ${rec.wave})`,
|
|
30054
30292
|
"--body",
|
|
30055
30293
|
`Auto-opened by \`mmi-cli devops bootstrap propagate --target ${seed.target} --execute\` (#4238), wave ${rec.wave}.
|
|
30056
30294
|
|
|
30057
|
-
Propagates MMI-Hub@${headSha}
|
|
30295
|
+
Propagates MMI-Hub@${headSha}'s ${seed.managedBlock ? "declared marker-bounded block inside repo-owned" : "org-owned copy of"} \`${seed.target}\` \u2014 the file this PR carries and nothing else; content outside a managed block remains untouched.
|
|
30296
|
+
${renderPropagationTargetMarker(seed.target)}
|
|
30058
30297
|
|
|
30059
30298
|
Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target} --execute\` (#4240) reverts this PR's merge commit; never re-run propagate with old bytes.`
|
|
30060
30299
|
]);
|
|
@@ -30092,18 +30331,18 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
30092
30331
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
30093
30332
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
30094
30333
|
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
30095
|
-
const propagatable = manifest.seeds.filter(
|
|
30334
|
+
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
30096
30335
|
if (!o.target) {
|
|
30097
30336
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
30098
30337
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
30099
30338
|
}
|
|
30100
30339
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
30101
|
-
if (!seed) return fail(`bootstrap rollback: --target '${o.target}' names no
|
|
30340
|
+
if (!seed) return fail(`bootstrap rollback: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable (hence rollback-able) targets:
|
|
30102
30341
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
30103
30342
|
const slug = parsedRepo.slug;
|
|
30104
30343
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
30105
|
-
const
|
|
30106
|
-
const
|
|
30344
|
+
const targetPropagateBranch = propagationBranch(repo, seed.target);
|
|
30345
|
+
const legacyPropagateBranch = legacyPropagationBranch(repo);
|
|
30107
30346
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
30108
30347
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
30109
30348
|
let candidates;
|
|
@@ -30126,9 +30365,17 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
30126
30365
|
} else {
|
|
30127
30366
|
candidates = [];
|
|
30128
30367
|
try {
|
|
30129
|
-
const
|
|
30130
|
-
|
|
30131
|
-
|
|
30368
|
+
const listMergedBranchPrs = async (branch) => {
|
|
30369
|
+
const listed = await gh(["pr", "list", "--repo", repo, "--head", branch, "--base", baseBranch, "--state", "merged", "--json", "number,url,mergedAt,mergeCommit,body,files", "--limit", "100"]);
|
|
30370
|
+
return JSON.parse(listed.stdout || "[]");
|
|
30371
|
+
};
|
|
30372
|
+
const targetSpecific = (await listMergedBranchPrs(targetPropagateBranch)).map((p) => ({ ...p, files: p.files?.map((f) => f.path) }));
|
|
30373
|
+
let identified = propagationPrCandidatesForTarget(seed.target, targetSpecific, []);
|
|
30374
|
+
if (!identified.length) {
|
|
30375
|
+
const legacy = (await listMergedBranchPrs(legacyPropagateBranch)).map((p) => ({ ...p, files: p.files?.map((f) => f.path) }));
|
|
30376
|
+
identified = propagationPrCandidatesForTarget(seed.target, [], legacy);
|
|
30377
|
+
}
|
|
30378
|
+
for (const p of identified) {
|
|
30132
30379
|
if (!p.mergeCommit?.oid) continue;
|
|
30133
30380
|
candidates.push({
|
|
30134
30381
|
repo,
|
|
@@ -30137,11 +30384,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
30137
30384
|
url: p.url,
|
|
30138
30385
|
mergeSha: p.mergeCommit.oid,
|
|
30139
30386
|
mergedAt: p.mergedAt ?? "",
|
|
30140
|
-
files:
|
|
30387
|
+
files: p.files ?? []
|
|
30141
30388
|
});
|
|
30142
30389
|
}
|
|
30143
30390
|
} catch (e) {
|
|
30144
|
-
return fail(`bootstrap rollback: could not read ${repo}'s merged ${
|
|
30391
|
+
return fail(`bootstrap rollback: could not read ${repo}'s merged ${targetPropagateBranch} or positively matching legacy PR history: ${e.message}`);
|
|
30145
30392
|
}
|
|
30146
30393
|
}
|
|
30147
30394
|
const plan = planRollback(repo, seed.target, slug, candidates);
|
|
@@ -32136,7 +32383,7 @@ var import_node_os18 = require("node:os");
|
|
|
32136
32383
|
var import_node_path42 = require("node:path");
|
|
32137
32384
|
|
|
32138
32385
|
// src/worktree-install-cache.ts
|
|
32139
|
-
var
|
|
32386
|
+
var import_node_crypto9 = require("node:crypto");
|
|
32140
32387
|
var import_node_fs42 = require("node:fs");
|
|
32141
32388
|
var import_node_path41 = require("node:path");
|
|
32142
32389
|
var CACHE_DIR = "worktree-install-cache";
|
|
@@ -32164,7 +32411,7 @@ var realWorktreeInstallCacheFs = {
|
|
|
32164
32411
|
}
|
|
32165
32412
|
};
|
|
32166
32413
|
function hashLockfileBytes(contents) {
|
|
32167
|
-
return (0,
|
|
32414
|
+
return (0, import_node_crypto9.createHash)("sha256").update(contents).digest("hex");
|
|
32168
32415
|
}
|
|
32169
32416
|
function resolveWorktreeInstallLockfile(packageDir, fs2 = realWorktreeInstallCacheFs) {
|
|
32170
32417
|
for (const name of LOCKFILE_NAMES) {
|
|
@@ -33190,7 +33437,7 @@ ${err.stderr ?? ""}`;
|
|
|
33190
33437
|
|
|
33191
33438
|
// src/issue-commands.ts
|
|
33192
33439
|
var import_node_fs44 = require("node:fs");
|
|
33193
|
-
var
|
|
33440
|
+
var import_node_crypto10 = require("node:crypto");
|
|
33194
33441
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
33195
33442
|
var ReparentConflictError = class extends Error {
|
|
33196
33443
|
constructor(message, payload) {
|
|
@@ -33481,7 +33728,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
33481
33728
|
const identity = `${spec.type}
|
|
33482
33729
|
${spec.title.trim()}
|
|
33483
33730
|
${spec.body ?? ""}`;
|
|
33484
|
-
const hash = (0,
|
|
33731
|
+
const hash = (0, import_node_crypto10.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
33485
33732
|
return `${batchKey}:${hash}`;
|
|
33486
33733
|
}
|
|
33487
33734
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -34160,7 +34407,7 @@ function onboardPluginGate(deps) {
|
|
|
34160
34407
|
declared,
|
|
34161
34408
|
settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
|
|
34162
34409
|
}).effective;
|
|
34163
|
-
return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-
|
|
34410
|
+
return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-hub update` outside Claude Code to disable it" } : { ok: true, detail: "background auto-update off; mmi-hub is the single release-gated writer" };
|
|
34164
34411
|
}
|
|
34165
34412
|
async function collectOnboardStatus(opts = {}) {
|
|
34166
34413
|
const cfg = await loadConfig();
|
|
@@ -36460,13 +36707,13 @@ var surfaces_default = {
|
|
|
36460
36707
|
publishVisibility: "public"
|
|
36461
36708
|
},
|
|
36462
36709
|
{
|
|
36463
|
-
id: "mmi-
|
|
36710
|
+
id: "mmi-hub",
|
|
36464
36711
|
classification: "capability",
|
|
36465
36712
|
kind: "cli",
|
|
36466
36713
|
ownerPath: "updater/package.json",
|
|
36467
36714
|
deliveryPath: "updater/package.json",
|
|
36468
36715
|
delivery: "npm",
|
|
36469
|
-
applicability: "editor-agnostic
|
|
36716
|
+
applicability: "editor-agnostic MMI installation and maintenance; npm package @mutmutco/hub exposing mmi-hub",
|
|
36470
36717
|
versionCoordinated: true,
|
|
36471
36718
|
versionPaths: [
|
|
36472
36719
|
{
|
|
@@ -36483,6 +36730,34 @@ var surfaces_default = {
|
|
|
36483
36730
|
},
|
|
36484
36731
|
publishVisibility: "public"
|
|
36485
36732
|
},
|
|
36733
|
+
{
|
|
36734
|
+
id: "mmi-updater-compat",
|
|
36735
|
+
classification: "packaging",
|
|
36736
|
+
kind: "cli",
|
|
36737
|
+
ownerPath: "packages/updater-compat/package.json",
|
|
36738
|
+
deliveryPath: "packages/updater-compat/package.json",
|
|
36739
|
+
delivery: "npm",
|
|
36740
|
+
applicability: "one-window deprecated @mutmutco/updater / mmi-updater wrapper delegating to the exact coordinated @mutmutco/hub version",
|
|
36741
|
+
versionCoordinated: true,
|
|
36742
|
+
versionPaths: [
|
|
36743
|
+
{
|
|
36744
|
+
path: "packages/updater-compat/package.json",
|
|
36745
|
+
pointer: "version"
|
|
36746
|
+
},
|
|
36747
|
+
{
|
|
36748
|
+
path: "packages/updater-compat/package.json",
|
|
36749
|
+
pointer: "dependencies.@mutmutco/hub"
|
|
36750
|
+
}
|
|
36751
|
+
],
|
|
36752
|
+
additionalPaths: [
|
|
36753
|
+
"packages/updater-compat/dist"
|
|
36754
|
+
],
|
|
36755
|
+
artifactIdentity: {
|
|
36756
|
+
kind: "npm-pack",
|
|
36757
|
+
packagePath: "packages/updater-compat"
|
|
36758
|
+
},
|
|
36759
|
+
publishVisibility: "public"
|
|
36760
|
+
},
|
|
36486
36761
|
{
|
|
36487
36762
|
id: "mmi-cli-lock",
|
|
36488
36763
|
classification: "packaging",
|
|
@@ -36944,7 +37219,7 @@ function checkCliVersion(input, releasedNote) {
|
|
|
36944
37219
|
warn: true,
|
|
36945
37220
|
label: "mmi-cli",
|
|
36946
37221
|
detail: `${report.currentVersion} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
36947
|
-
fix: "check
|
|
37222
|
+
fix: "check actual maintenance state with `mmi-hub status`; converge with `mmi-hub update`",
|
|
36948
37223
|
verbose: evidence
|
|
36949
37224
|
};
|
|
36950
37225
|
}
|
|
@@ -36954,7 +37229,7 @@ function checkCliVersion(input, releasedNote) {
|
|
|
36954
37229
|
ok: false,
|
|
36955
37230
|
label: "mmi-cli",
|
|
36956
37231
|
detail: `${report.currentVersion} \u2192 ${report.releasedVersion}`,
|
|
36957
|
-
fix:
|
|
37232
|
+
fix: "run `mmi-hub update` to converge the CLI and every present host surface",
|
|
36958
37233
|
verbose: evidence
|
|
36959
37234
|
};
|
|
36960
37235
|
}
|
|
@@ -36967,8 +37242,8 @@ function checkFleetDrift(probe) {
|
|
|
36967
37242
|
id,
|
|
36968
37243
|
ok: true,
|
|
36969
37244
|
label,
|
|
36970
|
-
detail: "no
|
|
36971
|
-
verbose: [`journal: ${probe.journalPath} (absent)`, "
|
|
37245
|
+
detail: "no MMI Hub maintenance journal on this machine",
|
|
37246
|
+
verbose: [`journal: ${probe.journalPath} (absent)`, "bootstrap with `npm install -g @mutmutco/hub` then `mmi-hub install`"]
|
|
36972
37247
|
};
|
|
36973
37248
|
}
|
|
36974
37249
|
if (probe.unreadable) {
|
|
@@ -36979,14 +37254,14 @@ function checkFleetDrift(probe) {
|
|
|
36979
37254
|
warn: true,
|
|
36980
37255
|
label,
|
|
36981
37256
|
detail: `journal could not be read \u2014 ${probe.unreadable}`,
|
|
36982
|
-
fix: `read ${probe.journalPath} directly, or
|
|
37257
|
+
fix: `read ${probe.journalPath} directly, or repair maintenance with \`mmi-hub install\``,
|
|
36983
37258
|
verbose: [`journal: ${probe.journalPath}`, `read failed: ${probe.unreadable}`]
|
|
36984
37259
|
};
|
|
36985
37260
|
}
|
|
36986
37261
|
const evidence = [
|
|
36987
37262
|
`journal: ${probe.journalPath}${probe.lastEventAt ? ` (last event ${probe.lastEventAt})` : ""}${probe.truncated ? ` \u2014 newest ${JOURNAL_TAIL_BYTES / 1024} KiB read` : ""}`,
|
|
36988
37263
|
`expected: ${probe.expected ?? "(no gated release recorded yet)"}`,
|
|
36989
|
-
...probe.surfaces.map((s) => `${s.surface}:
|
|
37264
|
+
...probe.surfaces.map((s) => `${s.surface}: last recorded ${s.installed ?? "unknown"} \u2014 ${s.verdict}${s.detail ? ` (${s.detail})` : ""}${s.compat ? ` [compat ${s.compat}]` : ""}`),
|
|
36990
37265
|
...probe.absent?.length ? [`absent since their last arm, skipped by the reconciler: ${probe.absent.join(", ")}`] : []
|
|
36991
37266
|
];
|
|
36992
37267
|
if (!probe.expected) {
|
|
@@ -37031,7 +37306,7 @@ function checkFleetDrift(probe) {
|
|
|
37031
37306
|
warn: true,
|
|
37032
37307
|
label,
|
|
37033
37308
|
detail: `${probe.expected} \u2014 ${probe.surfaces.length - unverified.length} converged, ${unverifiedNote}: no installed version recorded`,
|
|
37034
|
-
fix: `read why in ${probe.journalPath} (each arm records its own reason) \u2014 \`mmi-
|
|
37309
|
+
fix: `read why in ${probe.journalPath} (each arm records its own reason) \u2014 \`mmi-hub update\` retries now; doctor never installs`,
|
|
37035
37310
|
verbose: evidence
|
|
37036
37311
|
};
|
|
37037
37312
|
}
|
|
@@ -37042,7 +37317,7 @@ function checkFleetDrift(probe) {
|
|
|
37042
37317
|
label,
|
|
37043
37318
|
...unverified.length ? { verified: false } : {},
|
|
37044
37319
|
detail: `${drifted.length} of ${probe.surfaces.length} surface(s) behind ${probe.expected}: ${drifted.map((s) => `${s.surface} ${s.installed}`).join(", ")}${unverifiedNote ? `; ${unverifiedNote}` : ""}`,
|
|
37045
|
-
fix: `
|
|
37320
|
+
fix: `hourly Hub maintenance converges these on its next tick \u2014 \`mmi-hub update\` runs one now; doctor never installs${unverified.length ? `. The unverified surface(s) recorded no version \u2014 read their reason in ${probe.journalPath}` : ""}`,
|
|
37046
37321
|
verbose: evidence
|
|
37047
37322
|
};
|
|
37048
37323
|
}
|
|
@@ -37335,7 +37610,7 @@ function gcReapable(plan) {
|
|
|
37335
37610
|
async function runDoctorClean(opts, io, deps) {
|
|
37336
37611
|
const full = !opts.fast && !opts.banner && !opts.preflight;
|
|
37337
37612
|
const applyEnv = full;
|
|
37338
|
-
const applyRepo = full && opts.repoWrites
|
|
37613
|
+
const applyRepo = full && opts.repoWrites === true;
|
|
37339
37614
|
const lane = {
|
|
37340
37615
|
banner: Boolean(opts.banner),
|
|
37341
37616
|
fast: Boolean(opts.fast),
|
|
@@ -37450,7 +37725,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37450
37725
|
...cli,
|
|
37451
37726
|
ok: false,
|
|
37452
37727
|
detail: cli.ok ? `missing commands: ${missing.join(", ")}` : cli.detail,
|
|
37453
|
-
fix: `run
|
|
37728
|
+
fix: `run \`mmi-hub update\` \u2014 the CLI lacks ${missing.join(", ")} (or wait for hourly Hub maintenance)`,
|
|
37454
37729
|
verbose: [...cli.verbose ?? [], `missing commands: ${missing.join(", ")}`]
|
|
37455
37730
|
});
|
|
37456
37731
|
}
|
|
@@ -37483,7 +37758,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37483
37758
|
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
37484
37759
|
restartPending = true;
|
|
37485
37760
|
} else {
|
|
37486
|
-
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor
|
|
37761
|
+
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
|
|
37487
37762
|
}
|
|
37488
37763
|
}
|
|
37489
37764
|
async function runPluginCacheRow() {
|
|
@@ -37906,7 +38181,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37906
38181
|
// #3574: `${n} stale` was prepended to `fix` because the ✗ branch would not render `detail` —
|
|
37907
38182
|
// the same fact this row already puts in `detail` on its ✓ path four lines up. One convention now.
|
|
37908
38183
|
detail: `${n} stale`,
|
|
37909
|
-
fix: "run `mmi-cli doctor
|
|
38184
|
+
fix: "run `mmi-cli doctor --apply` to reap merged branches, stale refs, and dead worktrees",
|
|
37910
38185
|
verbose: gcEvidence
|
|
37911
38186
|
});
|
|
37912
38187
|
}
|
|
@@ -37924,7 +38199,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
37924
38199
|
restartPending = true;
|
|
37925
38200
|
}
|
|
37926
38201
|
} else {
|
|
37927
|
-
emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor
|
|
38202
|
+
emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --apply`", verbose: scratchEvidence });
|
|
37928
38203
|
}
|
|
37929
38204
|
}
|
|
37930
38205
|
const prefix = [
|
|
@@ -38386,9 +38661,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38386
38661
|
pluginTrustState: () => codexHookTrustState(),
|
|
38387
38662
|
releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
|
|
38388
38663
|
releasedVersionNote: throttled ? throttled.note : void 0,
|
|
38389
|
-
// #4954: the
|
|
38390
|
-
//
|
|
38391
|
-
// journal `mmi-updater stamp` reports from, parsed here into one row.
|
|
38664
|
+
// #4954/#5023: the Hub maintenance journal is read-only last-run evidence. `@mutmutco/hub`
|
|
38665
|
+
// owns every version install; `mmi-hub status` separately probes actual installed state.
|
|
38392
38666
|
fleetDrift: () => readFleetDrift(process.env),
|
|
38393
38667
|
// #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
|
|
38394
38668
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
@@ -42001,14 +42275,14 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
42001
42275
|
});
|
|
42002
42276
|
access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
42003
42277
|
var isWin2 = process.platform === "win32";
|
|
42004
|
-
program2.command("doctor").description("heal plugin wiring and
|
|
42278
|
+
program2.command("doctor").description("safely heal active plugin wiring and report hygiene; repository cleanup requires explicit --apply (#5023), while version convergence belongs to `mmi-hub update`").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the canonical MMI Agentic Onboarding URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "advanced explicit lane: also apply guarded repository cleanup (gitignore, docs index, board mechanics, merged branches, dead worktrees, aged scratch)").option("--no-repo-writes", "compatibility spelling for the safe default: env/plugin repairs only, never mutate the repository").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run may heal safe machine-global plugin wiring but never mutates the repository. Run\n`mmi-cli doctor --apply` only when you explicitly want the guarded full repository cleanup lane.\nCLI and host version convergence belongs to `mmi-hub update`; doctor reports lag and last-run evidence.\n--banner/--fast/--self/--preflight are read-only lanes.\n").action(async (opts) => {
|
|
42005
42279
|
if (opts.guide) {
|
|
42006
|
-
consoleIo.log(
|
|
42280
|
+
consoleIo.log(`MMI Agentic Onboarding: ${CANONICAL_ONBOARDING_URL}`);
|
|
42007
42281
|
return;
|
|
42008
42282
|
}
|
|
42009
42283
|
process.exitCode = await runDoctorClean(
|
|
42010
42284
|
{
|
|
42011
|
-
repoWrites: opts.repoWrites,
|
|
42285
|
+
repoWrites: opts.apply === true && opts.repoWrites !== false,
|
|
42012
42286
|
banner: opts.banner,
|
|
42013
42287
|
preflight: opts.preflight,
|
|
42014
42288
|
fast: opts.fast || opts.self,
|
package/package.json
CHANGED