@mutmutco/cli 4.2.2 → 4.2.4
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 +140 -51
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,7 +3416,7 @@ var program = new Command();
|
|
|
3416
3416
|
// src/index.ts
|
|
3417
3417
|
var import_promises8 = require("node:fs/promises");
|
|
3418
3418
|
var import_node_fs43 = require("node:fs");
|
|
3419
|
-
var
|
|
3419
|
+
var import_node_child_process20 = require("node:child_process");
|
|
3420
3420
|
|
|
3421
3421
|
// src/cli-shared.ts
|
|
3422
3422
|
var import_node_child_process3 = require("node:child_process");
|
|
@@ -11802,10 +11802,10 @@ var rollout_plan_default = {
|
|
|
11802
11802
|
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)."
|
|
11803
11803
|
},
|
|
11804
11804
|
baseline: {
|
|
11805
|
-
version: "4.2.
|
|
11806
|
-
tag: "v4.2.
|
|
11807
|
-
commit: "
|
|
11808
|
-
npm: "@mutmutco/cli@4.2.
|
|
11805
|
+
version: "4.2.4",
|
|
11806
|
+
tag: "v4.2.4",
|
|
11807
|
+
commit: "91aa25abb871",
|
|
11808
|
+
npm: "@mutmutco/cli@4.2.4"
|
|
11809
11809
|
},
|
|
11810
11810
|
exitCriterion: "fleet-n-of-n",
|
|
11811
11811
|
hubOnlyShortcut: "forbidden",
|
|
@@ -11822,14 +11822,14 @@ var rollout_plan_default = {
|
|
|
11822
11822
|
repo: "mutmutco/mmi-hub",
|
|
11823
11823
|
role: "canary",
|
|
11824
11824
|
schedule: "train",
|
|
11825
|
-
v3Target: "v4.2.
|
|
11825
|
+
v3Target: "v4.2.4"
|
|
11826
11826
|
}
|
|
11827
11827
|
],
|
|
11828
11828
|
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.",
|
|
11829
11829
|
rollback: {
|
|
11830
11830
|
independent: true,
|
|
11831
|
-
mechanism: "npm dist-tag latest -> 4.2.
|
|
11832
|
-
v3Target: "v4.2.
|
|
11831
|
+
mechanism: "npm dist-tag latest -> 4.2.4 and redeploy the Hub Lambda from tag v4.2.4 (91aa25abb871); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
11832
|
+
v3Target: "v4.2.4 (@mutmutco/cli@4.2.4, tag commit 91aa25abb871 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
11833
11833
|
}
|
|
11834
11834
|
},
|
|
11835
11835
|
{
|
|
@@ -24031,6 +24031,11 @@ function renderSweep(r) {
|
|
|
24031
24031
|
|
|
24032
24032
|
// src/hotfix-apply.ts
|
|
24033
24033
|
var HOTFIX_RELEASE_WORKFLOWS = ["deploy.yml", "publish.yml"];
|
|
24034
|
+
function hotfixStatusWorkflows(deployModel) {
|
|
24035
|
+
if (deployModel === "registry-publish") return ["publish.yml"];
|
|
24036
|
+
if (deployModel === void 0 || deployModel === "hub-serverless" || deployModel === "tenant-container" || deployModel === "solo-container") return HOTFIX_RELEASE_WORKFLOWS;
|
|
24037
|
+
return [];
|
|
24038
|
+
}
|
|
24034
24039
|
var HOTFIX_RUN_FIND_ATTEMPTS = 10;
|
|
24035
24040
|
var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
|
|
24036
24041
|
var HOTFIX_VERIFY_ATTEMPTS = 5;
|
|
@@ -24666,29 +24671,35 @@ async function runHotfixStatus(deps, versionInput) {
|
|
|
24666
24671
|
const work = (async () => {
|
|
24667
24672
|
const ctx = await buildTrainApplyContext(deps);
|
|
24668
24673
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
24674
|
+
let workflows = HOTFIX_RELEASE_WORKFLOWS;
|
|
24675
|
+
let warnings = [];
|
|
24676
|
+
try {
|
|
24677
|
+
workflows = hotfixStatusWorkflows(await resolveHotfixDeployModel(deps, ctx));
|
|
24678
|
+
} catch (e) {
|
|
24679
|
+
warnings = [`deploy model unverified (${e.message ?? e}) \u2014 probed the default deploy.yml+publish.yml run list, so an unproven run may hold the verdict at unverified`];
|
|
24680
|
+
}
|
|
24669
24681
|
let tag;
|
|
24670
24682
|
let version;
|
|
24671
|
-
let warnings = [];
|
|
24672
24683
|
if (versionInput) {
|
|
24673
24684
|
({ tag, version } = normalizeHotfixVersion(versionInput));
|
|
24674
24685
|
} else {
|
|
24675
24686
|
const latest = await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--merged", "origin/main", "--sort=-v:refname"]).then((out) => out.split("\n").map((s) => s.trim()).find((t) => /^v\d+\.\d+\.\d+$/.test(t)));
|
|
24676
24687
|
if (latest) {
|
|
24677
|
-
const latestFacts = await gatherHotfixFacts(deps, ctx, latest, latest.slice(1));
|
|
24688
|
+
const latestFacts = await gatherHotfixFacts(deps, ctx, latest, latest.slice(1), workflows);
|
|
24678
24689
|
const latestDerived = deriveHotfixState(latestFacts);
|
|
24679
24690
|
if (latestDerived.state !== "complete") {
|
|
24680
24691
|
return { ...ctx, command: "hotfix-status", ...latestFacts, ...latestDerived, warnings };
|
|
24681
24692
|
}
|
|
24682
24693
|
}
|
|
24683
|
-
const found = await findInFlightHotfixVersion(deps, ctx, latest);
|
|
24684
|
-
warnings = supersededHotfixWarnings(found.superseded, latest);
|
|
24694
|
+
const found = await findInFlightHotfixVersion(deps, ctx, latest, workflows);
|
|
24695
|
+
warnings = [...warnings, ...supersededHotfixWarnings(found.superseded, latest)];
|
|
24685
24696
|
if (found.inFlight) {
|
|
24686
24697
|
({ tag, version } = found.inFlight);
|
|
24687
24698
|
} else {
|
|
24688
24699
|
({ tag, version } = await deriveHotfixVersion(deps));
|
|
24689
24700
|
}
|
|
24690
24701
|
}
|
|
24691
|
-
const facts = await gatherHotfixFacts(deps, ctx, tag, version);
|
|
24702
|
+
const facts = await gatherHotfixFacts(deps, ctx, tag, version, workflows);
|
|
24692
24703
|
return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts), warnings };
|
|
24693
24704
|
})();
|
|
24694
24705
|
let timer;
|
|
@@ -24707,7 +24718,7 @@ async function runHotfixStatus(deps, versionInput) {
|
|
|
24707
24718
|
if (timer) clearTimeout(timer);
|
|
24708
24719
|
}
|
|
24709
24720
|
}
|
|
24710
|
-
async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
24721
|
+
async function gatherHotfixFacts(deps, ctx, tag, version, workflows = HOTFIX_RELEASE_WORKFLOWS) {
|
|
24711
24722
|
const branchExists = Boolean(clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
|
|
24712
24723
|
const pr2 = await findHotfixPr(deps, ctx, tag);
|
|
24713
24724
|
const remoteTag = clean3(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
|
|
@@ -24716,7 +24727,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
|
24716
24727
|
const releaseExists = await hotfixReleaseExists(deps, ctx, tag);
|
|
24717
24728
|
const runs = [];
|
|
24718
24729
|
if (releaseExists && tagSha) {
|
|
24719
|
-
for (const workflow of
|
|
24730
|
+
for (const workflow of workflows) {
|
|
24720
24731
|
try {
|
|
24721
24732
|
const out = await deps.run("gh", [
|
|
24722
24733
|
"run",
|
|
@@ -24772,7 +24783,7 @@ function supersededHotfixWarnings(superseded, latestMainTag) {
|
|
|
24772
24783
|
if (superseded.length === 0) return [];
|
|
24773
24784
|
return [`skipped superseded hotfix marker(s) ${superseded.join(", ")} \u2014 merged to main but never released and at/below the latest released tag ${latestMainTag ?? "(none)"}; later releases already absorbed them (#976). Not actionable \u2014 run mmi-cli devops hotfix start for a new fix.`];
|
|
24774
24785
|
}
|
|
24775
|
-
async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
|
|
24786
|
+
async function findInFlightHotfixVersion(deps, ctx, latestMainTag, workflows = HOTFIX_RELEASE_WORKFLOWS) {
|
|
24776
24787
|
const tags = /* @__PURE__ */ new Set();
|
|
24777
24788
|
const out = await deps.run("gh", [
|
|
24778
24789
|
"pr",
|
|
@@ -24802,13 +24813,13 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
|
|
|
24802
24813
|
const fresh = latestMainTag ? sorted.filter((t) => compareHotfixVersions(t, latestMainTag) > 0) : sorted;
|
|
24803
24814
|
for (const tag of fresh) {
|
|
24804
24815
|
const version = tag.slice(1);
|
|
24805
|
-
const facts = await gatherHotfixFacts(deps, ctx, tag, version);
|
|
24816
|
+
const facts = await gatherHotfixFacts(deps, ctx, tag, version, workflows);
|
|
24806
24817
|
if (deriveHotfixState(facts).state !== "complete") return { inFlight: { tag, version }, superseded: [] };
|
|
24807
24818
|
}
|
|
24808
24819
|
const stale = latestMainTag ? sorted.filter((t) => compareHotfixVersions(t, latestMainTag) <= 0) : [];
|
|
24809
24820
|
const superseded = [];
|
|
24810
24821
|
for (const tag of stale) {
|
|
24811
|
-
const facts = await gatherHotfixFacts(deps, ctx, tag, tag.slice(1));
|
|
24822
|
+
const facts = await gatherHotfixFacts(deps, ctx, tag, tag.slice(1), workflows);
|
|
24812
24823
|
if (deriveHotfixState(facts).state !== "complete") superseded.push(tag);
|
|
24813
24824
|
}
|
|
24814
24825
|
return { inFlight: null, superseded };
|
|
@@ -25152,6 +25163,16 @@ var WAIVABLE_KINDS = [
|
|
|
25152
25163
|
"stale-satisfied-by"
|
|
25153
25164
|
];
|
|
25154
25165
|
var TEST_WORK_KIND = "unrequested-test-file";
|
|
25166
|
+
var TEST_FILE_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
25167
|
+
var PY_TEST_FILE_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
25168
|
+
function isTestPath(path2) {
|
|
25169
|
+
return typeof path2 === "string" && (TEST_FILE_RE.test(path2) || PY_TEST_FILE_RE.test(path2));
|
|
25170
|
+
}
|
|
25171
|
+
function editsExistingTest(paths, addedPaths = []) {
|
|
25172
|
+
const added = new Set(Array.isArray(addedPaths) ? addedPaths : []);
|
|
25173
|
+
const list = Array.isArray(paths) ? paths : [];
|
|
25174
|
+
return list.some((path2) => isTestPath(path2) && !added.has(path2));
|
|
25175
|
+
}
|
|
25155
25176
|
function translateGlob(glob) {
|
|
25156
25177
|
let out = "";
|
|
25157
25178
|
for (let i = 0; i < glob.length; i += 1) {
|
|
@@ -25194,7 +25215,7 @@ function matchedMandatoryGlobs(paths, mandatory) {
|
|
|
25194
25215
|
return list.some((path2) => re.test(path2));
|
|
25195
25216
|
});
|
|
25196
25217
|
}
|
|
25197
|
-
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null } = {}) {
|
|
25218
|
+
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null, addedPaths = [] } = {}) {
|
|
25198
25219
|
const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
|
|
25199
25220
|
if (!regulated) {
|
|
25200
25221
|
return {
|
|
@@ -25202,18 +25223,23 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
|
|
|
25202
25223
|
matchedMandatoryGlobs: [],
|
|
25203
25224
|
matchedMandatoryCount: 0,
|
|
25204
25225
|
testCommandsAllowed: true,
|
|
25226
|
+
editsExistingTest: false,
|
|
25205
25227
|
reasonId: null,
|
|
25206
25228
|
commandClasses: { allowed: [TEST_COMMAND_CLASS], refused: [] }
|
|
25207
25229
|
};
|
|
25208
25230
|
}
|
|
25209
25231
|
const matched = matchedMandatoryGlobs(paths, mandatory);
|
|
25210
25232
|
const overrideAuthorizes = Array.isArray(override?.kinds) && override.kinds.includes(TEST_WORK_KIND);
|
|
25211
|
-
const
|
|
25233
|
+
const existingTestEdited = editsExistingTest(paths, addedPaths);
|
|
25234
|
+
const testCommandsAllowed = matched.length > 0 || overrideAuthorizes || existingTestEdited;
|
|
25212
25235
|
return {
|
|
25213
25236
|
configuredMandatoryCount,
|
|
25214
25237
|
matchedMandatoryGlobs: matched,
|
|
25215
25238
|
matchedMandatoryCount: matched.length,
|
|
25216
25239
|
testCommandsAllowed,
|
|
25240
|
+
/** #5842: true when the diff edits a test file that already existed — one of the facts that can
|
|
25241
|
+
* authorize the class, named so a refusal can say what was missing. */
|
|
25242
|
+
editsExistingTest: existingTestEdited,
|
|
25217
25243
|
reasonId: testCommandsAllowed ? null : "test-command-outside-mandatory-zone",
|
|
25218
25244
|
commandClasses: {
|
|
25219
25245
|
allowed: testCommandsAllowed ? [TEST_COMMAND_CLASS] : [],
|
|
@@ -25291,8 +25317,8 @@ function readOverride(base, cwd) {
|
|
|
25291
25317
|
|
|
25292
25318
|
// src/test-policy-core.ts
|
|
25293
25319
|
var POLICY_FILE = "test-policy.json";
|
|
25294
|
-
var TEST_RE =
|
|
25295
|
-
var PY_TEST_RE =
|
|
25320
|
+
var TEST_RE = TEST_FILE_RE;
|
|
25321
|
+
var PY_TEST_RE = PY_TEST_FILE_RE;
|
|
25296
25322
|
function translate(glob) {
|
|
25297
25323
|
let out = "";
|
|
25298
25324
|
for (let i = 0; i < glob.length; i++) {
|
|
@@ -25327,7 +25353,7 @@ function translate(glob) {
|
|
|
25327
25353
|
function globToRegExp2(glob) {
|
|
25328
25354
|
return new RegExp(`^${translate(glob)}$`);
|
|
25329
25355
|
}
|
|
25330
|
-
function
|
|
25356
|
+
function isTestPath2(path2) {
|
|
25331
25357
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
25332
25358
|
}
|
|
25333
25359
|
var SUPPORTED_SOURCE = /\.[cm]?[jt]s$/;
|
|
@@ -25561,7 +25587,7 @@ function annotateChangeMeaning(changed, policy, read) {
|
|
|
25561
25587
|
const matchers = (policy.mandatory ?? []).map((m) => globToRegExp2(m.glob));
|
|
25562
25588
|
return changed.map((file) => {
|
|
25563
25589
|
if (file.status !== "M" || !SUPPORTED_SOURCE.test(file.path)) return file;
|
|
25564
|
-
if (!matchers.some((re) => re.test(file.path)) && !
|
|
25590
|
+
if (!matchers.some((re) => re.test(file.path)) && !isTestPath2(file.path)) return file;
|
|
25565
25591
|
let pair;
|
|
25566
25592
|
try {
|
|
25567
25593
|
pair = read(file.path);
|
|
@@ -25598,7 +25624,7 @@ function removedPaths(changed) {
|
|
|
25598
25624
|
function classify(changed, policy, present = () => false) {
|
|
25599
25625
|
const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp2(m.glob) }));
|
|
25600
25626
|
const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
|
|
25601
|
-
const testChanges = changed.filter((f) =>
|
|
25627
|
+
const testChanges = changed.filter((f) => isTestPath2(f.path));
|
|
25602
25628
|
const addedTests = testChanges.filter((f) => f.status === "A");
|
|
25603
25629
|
const removed = removedPaths(changed);
|
|
25604
25630
|
const discharged = (m) => (m.satisfiedBy?.length ?? 0) > 0 && m.satisfiedBy.every((p) => present(p) && !removed.has(p));
|
|
@@ -25787,6 +25813,9 @@ function runTestPolicy(root, deps = {}) {
|
|
|
25787
25813
|
const findings = blocking.length > 0 ? blocking : sift(evaluate(changed, policy, present));
|
|
25788
25814
|
const commandPolicy = evaluateTestCommandPolicy({
|
|
25789
25815
|
paths: changed.map((f) => f.path),
|
|
25816
|
+
// #5842: `A` is added, `R` is a rename whose new name did not exist at the base either. Both are
|
|
25817
|
+
// test work this diff CREATED, which only a waiver may authorize (#5804).
|
|
25818
|
+
addedPaths: changed.filter((f) => f.status === "A" || f.status === "R").map((f) => f.path),
|
|
25790
25819
|
mandatory: policy.mandatory,
|
|
25791
25820
|
regulated: policy.declared !== false,
|
|
25792
25821
|
override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null
|
|
@@ -32419,7 +32448,53 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
32419
32448
|
}
|
|
32420
32449
|
|
|
32421
32450
|
// src/pr-create-docs-check.ts
|
|
32451
|
+
var import_node_child_process18 = require("node:child_process");
|
|
32422
32452
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
32453
|
+
function catFileBatch(root, ref, paths) {
|
|
32454
|
+
if (paths.length === 0) return Promise.resolve([]);
|
|
32455
|
+
return new Promise((resolve6) => {
|
|
32456
|
+
const chunks = [];
|
|
32457
|
+
let settled = false;
|
|
32458
|
+
const child2 = (0, import_node_child_process18.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
32459
|
+
const finish = () => {
|
|
32460
|
+
if (settled) return;
|
|
32461
|
+
settled = true;
|
|
32462
|
+
clearTimeout(timer);
|
|
32463
|
+
resolve6(parseCatFileBatch(Buffer.concat(chunks), paths.length));
|
|
32464
|
+
};
|
|
32465
|
+
const timer = setTimeout(() => {
|
|
32466
|
+
child2.kill("SIGKILL");
|
|
32467
|
+
finish();
|
|
32468
|
+
}, GIT_TIMEOUT_MS2);
|
|
32469
|
+
timer.unref?.();
|
|
32470
|
+
child2.stdout.on("data", (c) => chunks.push(c));
|
|
32471
|
+
child2.on("error", finish);
|
|
32472
|
+
child2.stdin.on("error", () => {
|
|
32473
|
+
});
|
|
32474
|
+
child2.on("close", finish);
|
|
32475
|
+
child2.stdin.end(paths.map((path2) => `${ref}:${path2}
|
|
32476
|
+
`).join(""));
|
|
32477
|
+
});
|
|
32478
|
+
}
|
|
32479
|
+
function parseCatFileBatch(out, expected) {
|
|
32480
|
+
const results = [];
|
|
32481
|
+
let at = 0;
|
|
32482
|
+
while (results.length < expected) {
|
|
32483
|
+
const eol = out.indexOf(10, at);
|
|
32484
|
+
if (eol === -1) break;
|
|
32485
|
+
const header = out.toString("utf8", at, eol);
|
|
32486
|
+
at = eol + 1;
|
|
32487
|
+
if (header.endsWith(" missing")) {
|
|
32488
|
+
results.push(void 0);
|
|
32489
|
+
continue;
|
|
32490
|
+
}
|
|
32491
|
+
const size = Number(header.slice(header.lastIndexOf(" ") + 1));
|
|
32492
|
+
if (!Number.isInteger(size) || size < 0 || at + size > out.length) break;
|
|
32493
|
+
results.push(out.toString("utf8", at, at + size));
|
|
32494
|
+
at = at + size + 1;
|
|
32495
|
+
}
|
|
32496
|
+
return results;
|
|
32497
|
+
}
|
|
32423
32498
|
function createPrCreateDocsIndexDeps() {
|
|
32424
32499
|
return {
|
|
32425
32500
|
worktreeRoot: async () => {
|
|
@@ -32448,20 +32523,13 @@ function createPrCreateDocsIndexDeps() {
|
|
|
32448
32523
|
},
|
|
32449
32524
|
listDocsAtRef: async (root, ref) => {
|
|
32450
32525
|
try {
|
|
32451
|
-
const { stdout } = await execFileP2("git", ["-C", root, "ls-tree", "-r", "--name-only", ref, "--", "docs"], { timeout: GIT_TIMEOUT_MS2 });
|
|
32452
|
-
return stdout.split(
|
|
32526
|
+
const { stdout } = await execFileP2("git", ["-C", root, "ls-tree", "-r", "-z", "--name-only", ref, "--", "docs"], { timeout: GIT_TIMEOUT_MS2 });
|
|
32527
|
+
return stdout.split("\0").filter(Boolean);
|
|
32453
32528
|
} catch {
|
|
32454
32529
|
return [];
|
|
32455
32530
|
}
|
|
32456
32531
|
},
|
|
32457
|
-
|
|
32458
|
-
try {
|
|
32459
|
-
const { stdout } = await execFileP2("git", ["-C", root, "show", `${ref}:${path2}`], { timeout: GIT_TIMEOUT_MS2 });
|
|
32460
|
-
return stdout;
|
|
32461
|
-
} catch {
|
|
32462
|
-
return void 0;
|
|
32463
|
-
}
|
|
32464
|
-
}
|
|
32532
|
+
readManyAtRef: (root, ref, paths) => catFileBatch(root, ref, paths)
|
|
32465
32533
|
};
|
|
32466
32534
|
}
|
|
32467
32535
|
async function checkDocsIndexAtHead(opts, deps) {
|
|
@@ -32476,10 +32544,10 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
32476
32544
|
const paths = await deps.listDocsAtRef(root, ref);
|
|
32477
32545
|
if (!paths.includes(DOCS_INDEX_PATH)) return void 0;
|
|
32478
32546
|
const relDocs = paths.filter((p) => p.startsWith("docs/") && p.endsWith(".md")).map((p) => p.slice("docs/".length)).filter(isRoutableDocsPath);
|
|
32479
|
-
const
|
|
32480
|
-
|
|
32481
|
-
|
|
32482
|
-
|
|
32547
|
+
const contents = await deps.readManyAtRef(root, ref, [DOCS_INDEX_PATH, ...relDocs.map((rel) => `docs/${rel}`)]);
|
|
32548
|
+
const [indexContent, ...docContents] = contents;
|
|
32549
|
+
if (contents.length !== relDocs.length + 1) return void 0;
|
|
32550
|
+
if (indexContent === void 0 || docContents.some((c) => c === void 0)) return void 0;
|
|
32483
32551
|
const contentByPath = new Map(relDocs.map((rel, i) => [rel, docContents[i] ?? ""]));
|
|
32484
32552
|
const atRefDeps = {
|
|
32485
32553
|
listDocs: () => relDocs,
|
|
@@ -32495,7 +32563,7 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
32495
32563
|
}
|
|
32496
32564
|
return {
|
|
32497
32565
|
ok: false,
|
|
32498
|
-
detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there`,
|
|
32566
|
+
detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there (checked ${ref} over ${relDocs.length} indexed doc${relDocs.length === 1 ? "" : "s"})`,
|
|
32499
32567
|
fix: "run `mmi-cli oracle docs index --write`, commit docs/index.md, and push again before retrying `pr create`"
|
|
32500
32568
|
};
|
|
32501
32569
|
}
|
|
@@ -36637,6 +36705,9 @@ function diagnoseSurface(evidence) {
|
|
|
36637
36705
|
if (evidence.manifest === "invalid") return { ...base, state: "corrupt" };
|
|
36638
36706
|
if (!evidence.releasedVersion) return { ...base, state: "freshness-unknown" };
|
|
36639
36707
|
if (evidence.installedVersion && evidence.releasedVersion && compareVersions(evidence.installedVersion, evidence.releasedVersion) < 0) {
|
|
36708
|
+
if (evidence.stagedVersion && evidence.stagedVersion === evidence.releasedVersion) {
|
|
36709
|
+
return { ...base, state: "staged" };
|
|
36710
|
+
}
|
|
36640
36711
|
return { ...base, state: "stale" };
|
|
36641
36712
|
}
|
|
36642
36713
|
return { ...base, state: "clean" };
|
|
@@ -36647,14 +36718,16 @@ function reloadInstruction(descriptor) {
|
|
|
36647
36718
|
return `${verb} ${descriptor.displayName}`;
|
|
36648
36719
|
}
|
|
36649
36720
|
function planSurfaceRepair(diagnosis) {
|
|
36650
|
-
if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
36721
|
+
if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "staged" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
36651
36722
|
const descriptor = diagnosis.descriptor;
|
|
36652
36723
|
if (descriptor.repairOwner === "mmi-hub") {
|
|
36653
36724
|
return {
|
|
36654
36725
|
supported: false,
|
|
36655
36726
|
owner: "mmi-hub",
|
|
36656
36727
|
changes: [],
|
|
36657
|
-
|
|
36728
|
+
// #5833: name the SURFACE this row is about — the literal "Hermes plugin" sent every other
|
|
36729
|
+
// mmi-hub-owned surface (Claude Code included) chasing a different host that was already current.
|
|
36730
|
+
instruction: `run \`mmi-hub update\` to converge the MMI-managed ${descriptor.displayName} plugin, then ${reloadInstruction(descriptor)}`
|
|
36658
36731
|
};
|
|
36659
36732
|
}
|
|
36660
36733
|
if (descriptor.repairOwner !== "mmi-cli") {
|
|
@@ -36681,6 +36754,8 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
36681
36754
|
skipped: "skipped \u2014 host not active",
|
|
36682
36755
|
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
36683
36756
|
"pending-reload": `pending ${descriptor.token === "hermes" ? "fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
|
|
36757
|
+
// #5833: the update already staged the released tree; the wording mirrors `mmi-hub status`.
|
|
36758
|
+
staged: `loaded ${diagnosis.installedVersion}; staged ${diagnosis.releasedVersion} for next launch \u2014 ${descriptor.token === "hermes" ? "start a fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} ${descriptor.displayName} to converge`,
|
|
36684
36759
|
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
36685
36760
|
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
36686
36761
|
missing: "missing \u2014 no install record",
|
|
@@ -36692,14 +36767,14 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
36692
36767
|
id: `${descriptor.token}-plugin`,
|
|
36693
36768
|
surface: descriptor.token,
|
|
36694
36769
|
state,
|
|
36695
|
-
ok: state === "clean" || state === "skipped" || state === "pending-reload",
|
|
36770
|
+
ok: state === "clean" || state === "skipped" || state === "pending-reload" || state === "staged",
|
|
36696
36771
|
...state === "freshness-unknown" ? { reportOnly: true } : {},
|
|
36697
|
-
// Nothing is broken, so this is not a ✗ — but
|
|
36772
|
+
// Nothing is broken, so this is not a ✗ — but these are the OK rows an operator must act on, and the
|
|
36698
36773
|
// short default lane and the SessionStart banner both filter to failures unless a row says otherwise.
|
|
36699
|
-
...state === "pending-reload" ? { warn: true } : {},
|
|
36774
|
+
...state === "pending-reload" || state === "staged" ? { warn: true } : {},
|
|
36700
36775
|
label: `${descriptor.displayName} plugin`,
|
|
36701
36776
|
detail: detailByState[state],
|
|
36702
|
-
...plan ? { fix: plan.instruction } : state === "pending-reload" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
36777
|
+
...plan ? { fix: plan.instruction } : state === "pending-reload" || state === "staged" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
36703
36778
|
verbose: [
|
|
36704
36779
|
// The three numbers the legacy builder used to print. They belong here now that this is the only
|
|
36705
36780
|
// plugin row, and a report that names a state without naming the versions behind it is not evidence.
|
|
@@ -37807,7 +37882,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
37807
37882
|
var import_node_fs42 = require("node:fs");
|
|
37808
37883
|
var import_node_os18 = require("node:os");
|
|
37809
37884
|
var import_node_path39 = require("node:path");
|
|
37810
|
-
var
|
|
37885
|
+
var import_node_child_process19 = require("node:child_process");
|
|
37811
37886
|
var import_node_util8 = require("node:util");
|
|
37812
37887
|
|
|
37813
37888
|
// src/discard-sink.ts
|
|
@@ -37816,13 +37891,13 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
|
|
|
37816
37891
|
}
|
|
37817
37892
|
|
|
37818
37893
|
// src/doctor-io.ts
|
|
37819
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
37894
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
|
|
37820
37895
|
function execFileCapture(file, args, opts = {}) {
|
|
37821
37896
|
const sink = nodeDiscardSinkPath();
|
|
37822
37897
|
const inFd = (0, import_node_fs42.openSync)(sink, "r");
|
|
37823
37898
|
const errFd = (0, import_node_fs42.openSync)(sink, "w");
|
|
37824
37899
|
try {
|
|
37825
|
-
return (0,
|
|
37900
|
+
return (0, import_node_child_process19.execFileSync)(file, args, {
|
|
37826
37901
|
...opts,
|
|
37827
37902
|
encoding: "utf8",
|
|
37828
37903
|
stdio: [inFd, "pipe", errFd]
|
|
@@ -37833,6 +37908,18 @@ function execFileCapture(file, args, opts = {}) {
|
|
|
37833
37908
|
}
|
|
37834
37909
|
}
|
|
37835
37910
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
37911
|
+
function stagedClaudePluginVersion() {
|
|
37912
|
+
try {
|
|
37913
|
+
const stagingRoot = (0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "staging");
|
|
37914
|
+
const record = JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(stagingRoot, "launch-target.json"), "utf8"));
|
|
37915
|
+
if (typeof record.target !== "string" || typeof record.incoming !== "string") return void 0;
|
|
37916
|
+
const manifest = JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path39.join)(record.incoming, ".claude-plugin", "plugin.json"), "utf8"));
|
|
37917
|
+
if (typeof manifest.version !== "string") return void 0;
|
|
37918
|
+
return manifest.version === record.target ? record.target : void 0;
|
|
37919
|
+
} catch {
|
|
37920
|
+
return void 0;
|
|
37921
|
+
}
|
|
37922
|
+
}
|
|
37836
37923
|
function installedClaudePluginVersion() {
|
|
37837
37924
|
try {
|
|
37838
37925
|
const file = JSON.parse(
|
|
@@ -38091,6 +38178,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38091
38178
|
const hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
|
|
38092
38179
|
const kimi = token === "kimi" ? kimiPluginHostEvidence(surfaceConfigRoot("kimi")) : void 0;
|
|
38093
38180
|
const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
|
|
38181
|
+
const stagedVersion = token === "claude" ? stagedClaudePluginVersion() : void 0;
|
|
38094
38182
|
surfaceEvidence = {
|
|
38095
38183
|
descriptor,
|
|
38096
38184
|
// Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
|
|
@@ -38102,7 +38190,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38102
38190
|
manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
|
|
38103
38191
|
guardState: buildPluginGuardDecision(snapshot).state,
|
|
38104
38192
|
...kimi?.receipt ? { receipt: kimi.receipt } : {},
|
|
38105
|
-
...installedVersion ? { installedVersion } : {}
|
|
38193
|
+
...installedVersion ? { installedVersion } : {},
|
|
38194
|
+
...stagedVersion ? { stagedVersion } : {}
|
|
38106
38195
|
};
|
|
38107
38196
|
return surfaceEvidence;
|
|
38108
38197
|
};
|
|
@@ -38751,7 +38840,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
38751
38840
|
try {
|
|
38752
38841
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
38753
38842
|
if (o.repo) args.push("--repo", o.repo);
|
|
38754
|
-
spawnDetachedSelf(args, { spawn:
|
|
38843
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
38755
38844
|
} catch {
|
|
38756
38845
|
}
|
|
38757
38846
|
}
|
package/package.json
CHANGED