@mutmutco/cli 3.79.0 → 3.81.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 +634 -414
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,8 +3416,8 @@ var program = new Command();
|
|
|
3416
3416
|
|
|
3417
3417
|
// src/index.ts
|
|
3418
3418
|
var import_promises10 = require("node:fs/promises");
|
|
3419
|
-
var
|
|
3420
|
-
var
|
|
3419
|
+
var import_node_fs35 = require("node:fs");
|
|
3420
|
+
var import_node_child_process16 = require("node:child_process");
|
|
3421
3421
|
|
|
3422
3422
|
// src/cli-shared.ts
|
|
3423
3423
|
var import_node_child_process3 = require("node:child_process");
|
|
@@ -4483,6 +4483,7 @@ function trackedPathStatus(c, realPath, repoAnchor) {
|
|
|
4483
4483
|
const gitPath = c.kind === "dir" ? `${rel.replace(/\/+$/, "")}/` : rel;
|
|
4484
4484
|
try {
|
|
4485
4485
|
(0, import_node_child_process4.execFileSync)("git", ["-C", repoAnchor, "rev-parse", "--is-inside-work-tree"], {
|
|
4486
|
+
windowsHide: true,
|
|
4486
4487
|
stdio: ["ignore", "ignore", "ignore"],
|
|
4487
4488
|
timeout: 1e4
|
|
4488
4489
|
});
|
|
@@ -4491,6 +4492,7 @@ function trackedPathStatus(c, realPath, repoAnchor) {
|
|
|
4491
4492
|
}
|
|
4492
4493
|
try {
|
|
4493
4494
|
const out = (0, import_node_child_process4.execFileSync)("git", ["-C", repoAnchor, "ls-files", "-z", "--", gitPath], {
|
|
4495
|
+
windowsHide: true,
|
|
4494
4496
|
encoding: "utf8",
|
|
4495
4497
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4496
4498
|
timeout: 1e4
|
|
@@ -6259,6 +6261,8 @@ function readSettingsAutoUpdate(raw, name) {
|
|
|
6259
6261
|
var AUTO_UPDATE_TOGGLE_STEPS = "`/plugin` \u2192 Marketplaces tab \u2192 select the marketplace \u2192 Enable auto-update";
|
|
6260
6262
|
var CATALOG_CONTENT_REF = "main";
|
|
6261
6263
|
var CATALOG_REF_PIN_STEPS = `add \`"ref": "${CATALOG_CONTENT_REF}"\` to this marketplace's \`source\` in ~/.claude/plugins/known_marketplaces.json, then restart Claude`;
|
|
6264
|
+
var PIN_HEAL_COMMAND = "run `mmi-cli doctor --apply`";
|
|
6265
|
+
var ORG_MARKETPLACE_PINS = { autoUpdate: true, ref: CATALOG_CONTENT_REF };
|
|
6262
6266
|
function resolveCatalogRef(probe) {
|
|
6263
6267
|
const { name, registered, ref, autoUpdate } = probe;
|
|
6264
6268
|
if (!registered) return void 0;
|
|
@@ -6281,7 +6285,7 @@ function resolveCatalogRef(probe) {
|
|
|
6281
6285
|
reason: `no ref pinned \u2014 the catalog is served from the repo's default branch while plugin content is pinned to ${CATALOG_CONTENT_REF}`
|
|
6282
6286
|
};
|
|
6283
6287
|
}
|
|
6284
|
-
function checkMarketplaceAutoUpdate(probe) {
|
|
6288
|
+
function checkMarketplaceAutoUpdate(probe, healable = false) {
|
|
6285
6289
|
if (!probe) return null;
|
|
6286
6290
|
const evidence = [`${probe.name}: auto-update ${probe.effective ? "on" : "off"} \u2014 ${probe.reason}`];
|
|
6287
6291
|
if (probe.effective) {
|
|
@@ -6292,11 +6296,11 @@ function checkMarketplaceAutoUpdate(probe) {
|
|
|
6292
6296
|
ok: true,
|
|
6293
6297
|
warn: true,
|
|
6294
6298
|
label: `marketplace auto-update (${probe.name})`,
|
|
6295
|
-
detail: `off \u2014 this machine will not pick up a new plugin release on its own; turn it on with ${AUTO_UPDATE_TOGGLE_STEPS}`,
|
|
6299
|
+
detail: `off \u2014 this machine will not pick up a new plugin release on its own; ${healable ? `${PIN_HEAL_COMMAND} to turn it on` : `turn it on with ${AUTO_UPDATE_TOGGLE_STEPS}`}`,
|
|
6296
6300
|
verbose: evidence
|
|
6297
6301
|
};
|
|
6298
6302
|
}
|
|
6299
|
-
function checkMarketplaceCatalogRef(probe) {
|
|
6303
|
+
function checkMarketplaceCatalogRef(probe, healable = false) {
|
|
6300
6304
|
if (!probe) return null;
|
|
6301
6305
|
const label = `marketplace catalog ref (${probe.name})`;
|
|
6302
6306
|
const evidence = [`${probe.name}: ${probe.reason}`, `auto-update: ${probe.autoUpdate ? "on" : "off"}`];
|
|
@@ -6313,7 +6317,7 @@ function checkMarketplaceCatalogRef(probe) {
|
|
|
6313
6317
|
// ✗ branch carried it in `fix` only because `renderCheckLine` discarded `detail` on failure — one
|
|
6314
6318
|
// row, two conventions, decided by which field would actually print.
|
|
6315
6319
|
detail: `${drift} while auto-update is ON`,
|
|
6316
|
-
fix: `this machine installs plugin releases advertised by a branch nobody released; ${CATALOG_REF_PIN_STEPS}`,
|
|
6320
|
+
fix: `this machine installs plugin releases advertised by a branch nobody released; ${healable ? `${PIN_HEAL_COMMAND} to pin it` : CATALOG_REF_PIN_STEPS}`,
|
|
6317
6321
|
verbose: evidence
|
|
6318
6322
|
};
|
|
6319
6323
|
}
|
|
@@ -6322,11 +6326,11 @@ function checkMarketplaceCatalogRef(probe) {
|
|
|
6322
6326
|
ok: true,
|
|
6323
6327
|
warn: true,
|
|
6324
6328
|
label,
|
|
6325
|
-
detail: `${drift} \u2014 harmless while auto-update is off, and the thing to fix before turning it on; ${CATALOG_REF_PIN_STEPS}`,
|
|
6329
|
+
detail: `${drift} \u2014 harmless while auto-update is off, and the thing to fix before turning it on; ${healable ? `${PIN_HEAL_COMMAND} to pin it` : CATALOG_REF_PIN_STEPS}`,
|
|
6326
6330
|
verbose: evidence
|
|
6327
6331
|
};
|
|
6328
6332
|
}
|
|
6329
|
-
function marketplaceRows(name, known, settings) {
|
|
6333
|
+
function marketplaceRows(name, known, settings, healable = false) {
|
|
6330
6334
|
const { registered, declared, ref } = readKnownMarketplace(known, name);
|
|
6331
6335
|
const autoUpdate = resolveAutoUpdate({
|
|
6332
6336
|
name,
|
|
@@ -6334,8 +6338,9 @@ function marketplaceRows(name, known, settings) {
|
|
|
6334
6338
|
declared,
|
|
6335
6339
|
settingsDeclared: readSettingsAutoUpdate(settings, name)
|
|
6336
6340
|
});
|
|
6337
|
-
const rows = [checkMarketplaceAutoUpdate(autoUpdate), checkMarketplaceCatalogRef(
|
|
6338
|
-
resolveCatalogRef({ name, registered, ref, autoUpdate: autoUpdate.effective })
|
|
6341
|
+
const rows = [checkMarketplaceAutoUpdate(autoUpdate, healable), checkMarketplaceCatalogRef(
|
|
6342
|
+
resolveCatalogRef({ name, registered, ref, autoUpdate: autoUpdate.effective }),
|
|
6343
|
+
healable
|
|
6339
6344
|
)];
|
|
6340
6345
|
return rows.filter((r) => r !== null);
|
|
6341
6346
|
}
|
|
@@ -6432,7 +6437,7 @@ function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
|
6432
6437
|
return [factsFor(""), ...children.map(factsFor).filter((f) => f.hasPackageJson)];
|
|
6433
6438
|
}
|
|
6434
6439
|
function npmInstallTargets(dirs) {
|
|
6435
|
-
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
|
|
6440
|
+
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install --no-package-lock" }));
|
|
6436
6441
|
}
|
|
6437
6442
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
6438
6443
|
return fs2.isFile((0, import_node_path10.join)(root, ".git"));
|
|
@@ -6619,7 +6624,7 @@ function commandLadderHint() {
|
|
|
6619
6624
|
}
|
|
6620
6625
|
|
|
6621
6626
|
// src/index.ts
|
|
6622
|
-
var
|
|
6627
|
+
var import_node_path34 = require("node:path");
|
|
6623
6628
|
|
|
6624
6629
|
// src/merge-ci-policy.ts
|
|
6625
6630
|
function resolveMergeCiPolicy(input) {
|
|
@@ -9512,6 +9517,57 @@ async function runReleaseAbort(deps, options = {}) {
|
|
|
9512
9517
|
note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting`
|
|
9513
9518
|
};
|
|
9514
9519
|
}
|
|
9520
|
+
async function runReleasePublishRetry(deps, runId, options = {}) {
|
|
9521
|
+
if (!options.approved) {
|
|
9522
|
+
throw new Error("release --retry-publish requires --apply after explicit approval; nothing was written");
|
|
9523
|
+
}
|
|
9524
|
+
if (!Number.isSafeInteger(runId) || runId <= 0) throw new Error(`invalid publish run id ${runId}`);
|
|
9525
|
+
const ctx = await buildTrainApplyContext(deps);
|
|
9526
|
+
if (!isHubControlRepo(ctx.repo)) {
|
|
9527
|
+
throw new Error("release --retry-publish is limited to mutmutco/MMI-Hub");
|
|
9528
|
+
}
|
|
9529
|
+
await requireCleanTree(deps);
|
|
9530
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
9531
|
+
const readRun = async () => {
|
|
9532
|
+
const raw = await deps.run("gh", ["run", "view", String(runId), "--repo", ctx.repo, "--json", "databaseId,workflowName,event,headBranch,headSha,status,conclusion,url"]);
|
|
9533
|
+
try {
|
|
9534
|
+
return JSON.parse(raw);
|
|
9535
|
+
} catch {
|
|
9536
|
+
throw new Error(`publish run ${runId} metadata was not valid JSON`);
|
|
9537
|
+
}
|
|
9538
|
+
};
|
|
9539
|
+
const run = await readRun();
|
|
9540
|
+
if (run.databaseId !== runId || run.workflowName !== "publish" || run.event !== "release" || run.status !== "completed" || run.conclusion !== "failure") {
|
|
9541
|
+
throw new Error(`run ${runId} is not a completed failed Hub publish release run; nothing was written`);
|
|
9542
|
+
}
|
|
9543
|
+
const tag = run.headBranch ?? "";
|
|
9544
|
+
const tagSha = run.headSha ?? "";
|
|
9545
|
+
if (!/^v\d+\.\d+\.\d+$/.test(tag) || !/^[0-9a-f]{40}$/.test(tagSha)) {
|
|
9546
|
+
throw new Error(`run ${runId} has invalid release identity (${tag || "(no tag)"} / ${tagSha || "(no sha)"}); nothing was written`);
|
|
9547
|
+
}
|
|
9548
|
+
if (await probeRemoteTag(deps, tag) !== tagSha) {
|
|
9549
|
+
throw new Error(`run ${runId} does not match origin ${tag}; nothing was written`);
|
|
9550
|
+
}
|
|
9551
|
+
await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
|
|
9552
|
+
await deps.run("gh", ["run", "rerun", String(runId), "--repo", ctx.repo, "--failed"]);
|
|
9553
|
+
if (options.watch) {
|
|
9554
|
+
await deps.run("gh", ["run", "watch", String(runId), "--repo", ctx.repo, "--exit-status"]);
|
|
9555
|
+
const completed = await readRun();
|
|
9556
|
+
if (completed.status !== "completed" || completed.conclusion !== "success") {
|
|
9557
|
+
throw new Error(`publish run ${runId} did not finish successfully after retry`);
|
|
9558
|
+
}
|
|
9559
|
+
}
|
|
9560
|
+
return {
|
|
9561
|
+
command: "release-retry-publish",
|
|
9562
|
+
repo: ctx.repo,
|
|
9563
|
+
tag,
|
|
9564
|
+
tagSha,
|
|
9565
|
+
runId,
|
|
9566
|
+
runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
|
|
9567
|
+
status: options.watch ? "success" : "pending",
|
|
9568
|
+
note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} failed jobs queued for retry`
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9515
9571
|
async function runTrainApplyPipeline(mode, input) {
|
|
9516
9572
|
const { deps, ctx, command, meta, branchHints, watch, options } = input;
|
|
9517
9573
|
const directTrack = input.directTrack ?? false;
|
|
@@ -13389,6 +13445,7 @@ function spawnExitCode(status, signal) {
|
|
|
13389
13445
|
function defaultSpawn(command, args, env) {
|
|
13390
13446
|
const target = resolveSpawnTarget(command, args);
|
|
13391
13447
|
const r = (0, import_node_child_process6.spawnSync)(target.cmd, target.args, {
|
|
13448
|
+
windowsHide: true,
|
|
13392
13449
|
stdio: "inherit",
|
|
13393
13450
|
env,
|
|
13394
13451
|
windowsVerbatimArguments: target.windowsVerbatimArguments
|
|
@@ -14552,9 +14609,10 @@ async function fetchNpmReleasedVersion() {
|
|
|
14552
14609
|
}
|
|
14553
14610
|
}
|
|
14554
14611
|
var NPM_INSTALL_TIMEOUT_MS = 12e4;
|
|
14555
|
-
async function npmSelfUpdateCli(target) {
|
|
14612
|
+
async function npmSelfUpdateCli(target, onStep) {
|
|
14556
14613
|
const command = cliUpdateCommand(target);
|
|
14557
14614
|
try {
|
|
14615
|
+
onStep?.(command);
|
|
14558
14616
|
await runHostBin("npm", ["install", "-g", `@mutmutco/cli@${target ?? "latest"}`], { timeout: NPM_INSTALL_TIMEOUT_MS });
|
|
14559
14617
|
return { ok: true, detail: `${command} exited 0` };
|
|
14560
14618
|
} catch (e) {
|
|
@@ -14620,9 +14678,6 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
14620
14678
|
) : (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
|
|
14621
14679
|
};
|
|
14622
14680
|
}
|
|
14623
|
-
function activePluginGuardState(isOrgRepo) {
|
|
14624
|
-
return buildPluginGuardDecision(snapshotPluginGuardInput(detectSurface(process.env), isOrgRepo)).state;
|
|
14625
|
-
}
|
|
14626
14681
|
async function runClaudePlugin(args) {
|
|
14627
14682
|
try {
|
|
14628
14683
|
await runHostBin("claude", args, { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
|
|
@@ -14792,27 +14847,35 @@ async function applyPluginHeal(surface, log, opts) {
|
|
|
14792
14847
|
if (restored) log(` ${restored}`);
|
|
14793
14848
|
return true;
|
|
14794
14849
|
}
|
|
14795
|
-
async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
|
|
14850
|
+
async function healClaudePluginForDoctor(surface = detectSurface(process.env), onStep) {
|
|
14796
14851
|
if (surfaceToken(surface) !== "claude") {
|
|
14797
14852
|
return { ok: false, detail: `not a Claude surface (${surface}) \u2014 no Hub-shipped plugin to reinstall` };
|
|
14798
14853
|
}
|
|
14799
14854
|
const steps = [];
|
|
14800
|
-
const ok = await applyPluginHeal(surface, (msg) =>
|
|
14855
|
+
const ok = await applyPluginHeal(surface, (msg) => {
|
|
14856
|
+
const line = msg.trim();
|
|
14857
|
+
steps.push(line);
|
|
14858
|
+
if (line) onStep?.(line);
|
|
14859
|
+
});
|
|
14801
14860
|
const pinNote = steps.find((s) => s.startsWith("re-pinned ") || s.includes("re-pin by hand"));
|
|
14802
14861
|
return {
|
|
14803
14862
|
ok,
|
|
14804
14863
|
detail: ok ? `marketplace remove \u2192 add \u2192 install succeeded${pinNote ? `; ${pinNote}` : ""}` : `\`claude plugin\` reinstall failed or was skipped${steps.length ? ` (${steps[steps.length - 1]})` : ""}`
|
|
14805
14864
|
};
|
|
14806
14865
|
}
|
|
14807
|
-
async function healActivePluginForDoctor(surface = detectSurface(process.env)) {
|
|
14866
|
+
async function healActivePluginForDoctor(surface = detectSurface(process.env), onStep) {
|
|
14808
14867
|
const token = surfaceToken(surface);
|
|
14809
14868
|
if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
|
|
14810
14869
|
return { ok: false, detail: `not a supported plugin surface (${surface})` };
|
|
14811
14870
|
}
|
|
14812
|
-
if (token === "claude") return healClaudePluginForDoctor(surface);
|
|
14871
|
+
if (token === "claude") return healClaudePluginForDoctor(surface, onStep);
|
|
14813
14872
|
if (token === "cursor") return installCursorPluginCheckout();
|
|
14814
14873
|
const steps = [];
|
|
14815
|
-
const applied = await applyPluginHeal(surface, (msg) =>
|
|
14874
|
+
const applied = await applyPluginHeal(surface, (msg) => {
|
|
14875
|
+
const line = msg.trim();
|
|
14876
|
+
steps.push(line);
|
|
14877
|
+
if (line) onStep?.(line);
|
|
14878
|
+
});
|
|
14816
14879
|
const snapshot = applied ? snapshotPluginGuardInput(surface, true) : void 0;
|
|
14817
14880
|
const guardState = snapshot ? buildPluginGuardDecision(snapshot).state : "unresolved";
|
|
14818
14881
|
const ok = applied && guardState === "healthy";
|
|
@@ -14835,7 +14898,7 @@ function readKnownMarketplacesFile(path2) {
|
|
|
14835
14898
|
return void 0;
|
|
14836
14899
|
}
|
|
14837
14900
|
}
|
|
14838
|
-
function
|
|
14901
|
+
function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb) {
|
|
14839
14902
|
if (pins.size === 0) return void 0;
|
|
14840
14903
|
const after = readKnownMarketplacesFile(path2);
|
|
14841
14904
|
const next = restoreMarketplacePins(after, pins);
|
|
@@ -14843,14 +14906,35 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
|
14843
14906
|
try {
|
|
14844
14907
|
(0, import_node_fs18.writeFileSync)(path2, next, "utf8");
|
|
14845
14908
|
} catch {
|
|
14846
|
-
return `could NOT
|
|
14909
|
+
return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
|
|
14847
14910
|
}
|
|
14848
14911
|
const verify = readKnownMarketplacesFile(path2);
|
|
14849
14912
|
const failed = [...pins].filter(([name, want]) => {
|
|
14850
14913
|
const got = readKnownMarketplace(verify, name);
|
|
14851
14914
|
return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
|
|
14852
14915
|
});
|
|
14853
|
-
return failed.length ?
|
|
14916
|
+
return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
|
|
14917
|
+
}
|
|
14918
|
+
function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
14919
|
+
return writeMarketplacePinsOnDisk(path2, pins, restoredPinsLine, "restore");
|
|
14920
|
+
}
|
|
14921
|
+
function applyOrgMarketplacePins(path2, names) {
|
|
14922
|
+
return writeMarketplacePinsOnDisk(
|
|
14923
|
+
path2,
|
|
14924
|
+
new Map(names.map((name) => [name, ORG_MARKETPLACE_PINS])),
|
|
14925
|
+
(pins) => `pinned ${[...pins.keys()].join(", ")} to ${ORG_MARKETPLACE_PINS.ref} with auto-update on`,
|
|
14926
|
+
"pin"
|
|
14927
|
+
);
|
|
14928
|
+
}
|
|
14929
|
+
function restoredPinsLine(pins) {
|
|
14930
|
+
const named = [...pins].map(([name, want]) => {
|
|
14931
|
+
const fields = [
|
|
14932
|
+
...typeof want.autoUpdate === "boolean" ? ["auto-update"] : [],
|
|
14933
|
+
...want.ref ? ["catalog ref"] : []
|
|
14934
|
+
];
|
|
14935
|
+
return `${name} (${fields.join(" + ")})`;
|
|
14936
|
+
});
|
|
14937
|
+
return `re-pinned ${named.join(", ")} \u2014 restored across the reinstall`;
|
|
14854
14938
|
}
|
|
14855
14939
|
async function runGuard(readOrigin) {
|
|
14856
14940
|
const surface = detectSurface(process.env);
|
|
@@ -16505,12 +16589,13 @@ var PRIMARY_GROUPS = [
|
|
|
16505
16589
|
["Plan and work", ["board", "issue", "worktree", "stage"]],
|
|
16506
16590
|
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
|
|
16507
16591
|
// `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
|
|
16508
|
-
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605).
|
|
16509
|
-
|
|
16592
|
+
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
|
|
16593
|
+
// joins them on the same footing (#3979).
|
|
16594
|
+
["Setup and support", ["bootstrap", "secrets", "docs", "tests", "spawn"]],
|
|
16510
16595
|
["Coordinate and improve", ["wave", "report", "skill-lesson"]]
|
|
16511
16596
|
];
|
|
16512
16597
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
|
|
16513
|
-
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "wave", "report", "skill-lesson"]);
|
|
16598
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "wave", "report", "skill-lesson"]);
|
|
16514
16599
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
16515
16600
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
16516
16601
|
var topLevelPosition = 0;
|
|
@@ -16550,6 +16635,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
16550
16635
|
secrets: { module_owner: "cli/src/secrets-commands.ts", consumer: "authenticated-operator" },
|
|
16551
16636
|
docs: { module_owner: "cli/src/docs-index-command.ts", consumer: "repo-gates" },
|
|
16552
16637
|
tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
|
|
16638
|
+
spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
|
|
16553
16639
|
wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
|
|
16554
16640
|
report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
|
|
16555
16641
|
"skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
|
|
@@ -18841,10 +18927,115 @@ function runDocRefs(root, deps = {}) {
|
|
|
18841
18927
|
};
|
|
18842
18928
|
}
|
|
18843
18929
|
|
|
18844
|
-
// src/
|
|
18930
|
+
// src/spawn-policy-core.ts
|
|
18845
18931
|
var import_node_child_process11 = require("node:child_process");
|
|
18846
18932
|
var import_node_fs21 = require("node:fs");
|
|
18847
18933
|
var import_node_path19 = require("node:path");
|
|
18934
|
+
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
18935
|
+
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
18936
|
+
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
18937
|
+
var EXCLUDED = [
|
|
18938
|
+
/(^|\/)node_modules\//,
|
|
18939
|
+
/(^|\/)(dist|build|coverage)\//,
|
|
18940
|
+
/(^|\/)(test|tests|__tests__|fixtures|__fixtures__)\//,
|
|
18941
|
+
/\.(test|spec)\.[cm]?[jt]s$/,
|
|
18942
|
+
/\.d\.[cm]?ts$/
|
|
18943
|
+
];
|
|
18944
|
+
function stripComments(src) {
|
|
18945
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/g, (m, p) => p + " ".repeat(m.length - p.length));
|
|
18946
|
+
}
|
|
18947
|
+
function callArgs(src, open2) {
|
|
18948
|
+
let depth = 0;
|
|
18949
|
+
for (let i = open2; i < src.length; i++) {
|
|
18950
|
+
const c = src[i];
|
|
18951
|
+
if (c === "(") depth++;
|
|
18952
|
+
else if (c === ")") {
|
|
18953
|
+
depth--;
|
|
18954
|
+
if (depth === 0) return src.slice(open2, i + 1);
|
|
18955
|
+
}
|
|
18956
|
+
}
|
|
18957
|
+
return "";
|
|
18958
|
+
}
|
|
18959
|
+
function findViolationsInSource(raw) {
|
|
18960
|
+
if (!/from\s+["']node:child_process["']/.test(raw)) return [];
|
|
18961
|
+
const importBlock = /import\s*(?:type\s*)?\{([^}]*)\}\s*from\s+["']node:child_process["']/.exec(raw);
|
|
18962
|
+
const imported = new Set(
|
|
18963
|
+
(importBlock?.[1] ?? "").split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean)
|
|
18964
|
+
);
|
|
18965
|
+
if (imported.size === 0) return [];
|
|
18966
|
+
const rawLines = raw.split("\n");
|
|
18967
|
+
const src = stripComments(raw);
|
|
18968
|
+
const call = new RegExp(CALL_SOURCE, "g");
|
|
18969
|
+
const found = [];
|
|
18970
|
+
let m;
|
|
18971
|
+
while (m = call.exec(src)) {
|
|
18972
|
+
const callee = m[2];
|
|
18973
|
+
if (!imported.has(callee)) continue;
|
|
18974
|
+
const open2 = m.index + m[0].length - 1;
|
|
18975
|
+
const args = callArgs(src, open2);
|
|
18976
|
+
if (!args) continue;
|
|
18977
|
+
if (args.includes("windowsHide")) continue;
|
|
18978
|
+
const line = src.slice(0, m.index + m[1].length).split("\n").length;
|
|
18979
|
+
if (/windows-hide-exempt:/.test(rawLines[line - 2] ?? "")) continue;
|
|
18980
|
+
if (/\.{3}\s*\w+/.test(args)) continue;
|
|
18981
|
+
if (/\b(opts|options|spawnOptions)\b/.test(args)) continue;
|
|
18982
|
+
const identifiers = new Set(args.match(/\b[A-Za-z_$][\w$]*\b/g) ?? []);
|
|
18983
|
+
let satisfied = false;
|
|
18984
|
+
for (const id of identifiers) {
|
|
18985
|
+
if (SPAWNERS.includes(id)) continue;
|
|
18986
|
+
const decl = new RegExp(String.raw`\b(?:const|let|var)\s+${id}\b[^;]*`, "s").exec(src);
|
|
18987
|
+
if (decl && decl[0].includes("windowsHide")) {
|
|
18988
|
+
satisfied = true;
|
|
18989
|
+
break;
|
|
18990
|
+
}
|
|
18991
|
+
if (new RegExp(String.raw`\b${id}\s*:\s*\{[^}]*windowsHide`, "s").test(src)) {
|
|
18992
|
+
satisfied = true;
|
|
18993
|
+
break;
|
|
18994
|
+
}
|
|
18995
|
+
}
|
|
18996
|
+
if (satisfied) continue;
|
|
18997
|
+
found.push({ line, callee });
|
|
18998
|
+
}
|
|
18999
|
+
return found;
|
|
19000
|
+
}
|
|
19001
|
+
function policedFiles(root) {
|
|
19002
|
+
const r = (0, import_node_child_process11.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
19003
|
+
cwd: root,
|
|
19004
|
+
encoding: "utf8",
|
|
19005
|
+
windowsHide: true,
|
|
19006
|
+
maxBuffer: 32 * 1024 * 1024
|
|
19007
|
+
});
|
|
19008
|
+
if (r.status !== 0) {
|
|
19009
|
+
throw new Error(`spawn policy: git ls-files failed: ${(r.stderr || "").trim() || `exit ${r.status}`}`);
|
|
19010
|
+
}
|
|
19011
|
+
return (r.stdout ?? "").split("\0").filter(Boolean).filter((f) => SOURCE_EXT.test(f)).filter((f) => !EXCLUDED.some((re) => re.test(f)));
|
|
19012
|
+
}
|
|
19013
|
+
function runSpawnPolicy(root) {
|
|
19014
|
+
const files = policedFiles(root);
|
|
19015
|
+
const findings = [];
|
|
19016
|
+
for (const file of files) {
|
|
19017
|
+
let raw;
|
|
19018
|
+
try {
|
|
19019
|
+
raw = (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(root, file), "utf8");
|
|
19020
|
+
} catch {
|
|
19021
|
+
continue;
|
|
19022
|
+
}
|
|
19023
|
+
for (const v of findViolationsInSource(raw)) {
|
|
19024
|
+
findings.push({
|
|
19025
|
+
file,
|
|
19026
|
+
line: v.line,
|
|
19027
|
+
callee: v.callee,
|
|
19028
|
+
detail: `${file}:${v.line} ${v.callee}(...) has no windowsHide`
|
|
19029
|
+
});
|
|
19030
|
+
}
|
|
19031
|
+
}
|
|
19032
|
+
return { ok: findings.length === 0, scannedCount: files.length, findings };
|
|
19033
|
+
}
|
|
19034
|
+
|
|
19035
|
+
// src/test-policy-core.ts
|
|
19036
|
+
var import_node_child_process12 = require("node:child_process");
|
|
19037
|
+
var import_node_fs22 = require("node:fs");
|
|
19038
|
+
var import_node_path20 = require("node:path");
|
|
18848
19039
|
var POLICY_FILE = "test-policy.json";
|
|
18849
19040
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
18850
19041
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -18897,7 +19088,7 @@ function isTestPath(path2) {
|
|
|
18897
19088
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
18898
19089
|
}
|
|
18899
19090
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
18900
|
-
const raw = readFile9((0,
|
|
19091
|
+
const raw = readFile9((0, import_node_path20.join)(root, POLICY_FILE));
|
|
18901
19092
|
if (raw == null) return { mandatory: [], declared: false };
|
|
18902
19093
|
try {
|
|
18903
19094
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -18907,7 +19098,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
|
18907
19098
|
}
|
|
18908
19099
|
function readFileOrNull2(path2) {
|
|
18909
19100
|
try {
|
|
18910
|
-
return (0,
|
|
19101
|
+
return (0, import_node_fs22.readFileSync)(path2, "utf8");
|
|
18911
19102
|
} catch {
|
|
18912
19103
|
return null;
|
|
18913
19104
|
}
|
|
@@ -18934,12 +19125,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
18934
19125
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
18935
19126
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
18936
19127
|
}
|
|
18937
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
18938
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
19128
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
|
|
19129
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path20.join)(root, p)));
|
|
18939
19130
|
}
|
|
18940
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
19131
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
|
|
18941
19132
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
18942
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
19133
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path20.join)(root, p)));
|
|
18943
19134
|
}
|
|
18944
19135
|
function evaluate(changed, policy, present = () => false) {
|
|
18945
19136
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -18972,14 +19163,15 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
18972
19163
|
return findings;
|
|
18973
19164
|
}
|
|
18974
19165
|
function git(args, cwd) {
|
|
18975
|
-
return (0,
|
|
19166
|
+
return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
18976
19167
|
}
|
|
18977
19168
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
18978
19169
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
18979
19170
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
18980
19171
|
function parseTrailers(message, cwd) {
|
|
18981
19172
|
try {
|
|
18982
|
-
return (0,
|
|
19173
|
+
return (0, import_node_child_process12.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
19174
|
+
windowsHide: true,
|
|
18983
19175
|
cwd,
|
|
18984
19176
|
input: message,
|
|
18985
19177
|
encoding: "utf8",
|
|
@@ -19120,13 +19312,13 @@ function changedFilesSince(base, cwd) {
|
|
|
19120
19312
|
}
|
|
19121
19313
|
function runTestPolicy(root, deps = {}) {
|
|
19122
19314
|
const policy = deps.policy ?? loadPolicy(root);
|
|
19123
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
19315
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs22.existsSync)(path2));
|
|
19124
19316
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
19125
19317
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
19126
19318
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
19127
19319
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
19128
19320
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
19129
|
-
const present = (path2) => exists((0,
|
|
19321
|
+
const present = (path2) => exists((0, import_node_path20.join)(root, path2));
|
|
19130
19322
|
const removedByThisDiff = removedPaths(changed);
|
|
19131
19323
|
const staleFindings = [];
|
|
19132
19324
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -19282,8 +19474,8 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
19282
19474
|
}
|
|
19283
19475
|
|
|
19284
19476
|
// src/project-info-sync.ts
|
|
19285
|
-
var
|
|
19286
|
-
var
|
|
19477
|
+
var import_node_fs23 = require("node:fs");
|
|
19478
|
+
var import_node_path21 = require("node:path");
|
|
19287
19479
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
19288
19480
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
19289
19481
|
projectV2 { id }
|
|
@@ -19328,14 +19520,14 @@ function sharedName(entries, fallback) {
|
|
|
19328
19520
|
}
|
|
19329
19521
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
19330
19522
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
19331
|
-
const readmePath = (0,
|
|
19332
|
-
if (!(0,
|
|
19523
|
+
const readmePath = (0, import_node_path21.join)(repoRoot2, "README.md");
|
|
19524
|
+
if (!(0, import_node_fs23.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
19333
19525
|
const entries = entriesFor(project2, projects);
|
|
19334
19526
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
19335
19527
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
19336
19528
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
19337
19529
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
19338
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
19530
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs23.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
19339
19531
|
const lines = [
|
|
19340
19532
|
`# ${projectName}`,
|
|
19341
19533
|
"",
|
|
@@ -19354,8 +19546,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
19354
19546
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
19355
19547
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
19356
19548
|
const orgDocs = [
|
|
19357
|
-
(0,
|
|
19358
|
-
(0,
|
|
19549
|
+
(0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
19550
|
+
(0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
19359
19551
|
].filter(Boolean);
|
|
19360
19552
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
19361
19553
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -20214,8 +20406,8 @@ function writeError(res) {
|
|
|
20214
20406
|
}
|
|
20215
20407
|
|
|
20216
20408
|
// src/secrets-commands.ts
|
|
20217
|
-
var
|
|
20218
|
-
var
|
|
20409
|
+
var import_node_fs24 = require("node:fs");
|
|
20410
|
+
var import_node_path22 = require("node:path");
|
|
20219
20411
|
var import_node_os8 = require("node:os");
|
|
20220
20412
|
|
|
20221
20413
|
// src/project-runtime.ts
|
|
@@ -20339,18 +20531,18 @@ function collectMap(value, previous = []) {
|
|
|
20339
20531
|
return [...previous, value];
|
|
20340
20532
|
}
|
|
20341
20533
|
async function decryptRailsCredentials(input) {
|
|
20342
|
-
const appDir = (0,
|
|
20534
|
+
const appDir = (0, import_node_path22.resolve)(input.appDir ?? process.cwd());
|
|
20343
20535
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
20344
20536
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
20345
|
-
const credentialsPath = (0,
|
|
20346
|
-
const masterKeyPath = (0,
|
|
20537
|
+
const credentialsPath = (0, import_node_path22.resolve)(appDir, credentialsFile);
|
|
20538
|
+
const masterKeyPath = (0, import_node_path22.resolve)(appDir, masterKeyFile);
|
|
20347
20539
|
const env = {
|
|
20348
20540
|
...process.env,
|
|
20349
20541
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
20350
20542
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
20351
20543
|
};
|
|
20352
|
-
if ((0,
|
|
20353
|
-
env.RAILS_MASTER_KEY = (0,
|
|
20544
|
+
if ((0, import_node_fs24.existsSync)(masterKeyPath)) {
|
|
20545
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs24.readFileSync)(masterKeyPath, "utf8").trim();
|
|
20354
20546
|
}
|
|
20355
20547
|
const script = [
|
|
20356
20548
|
'require "json"',
|
|
@@ -20360,9 +20552,9 @@ async function decryptRailsCredentials(input) {
|
|
|
20360
20552
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
20361
20553
|
"puts JSON.generate(config.config)"
|
|
20362
20554
|
].join("\n");
|
|
20363
|
-
const scriptDir = (0,
|
|
20364
|
-
const scriptPath = (0,
|
|
20365
|
-
(0,
|
|
20555
|
+
const scriptDir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os8.tmpdir)(), "mmi-rails-decrypt-"));
|
|
20556
|
+
const scriptPath = (0, import_node_path22.join)(scriptDir, "decrypt.rb");
|
|
20557
|
+
(0, import_node_fs24.writeFileSync)(scriptPath, script, "utf8");
|
|
20366
20558
|
try {
|
|
20367
20559
|
const args = ["exec", "ruby", scriptPath];
|
|
20368
20560
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -20374,7 +20566,7 @@ async function decryptRailsCredentials(input) {
|
|
|
20374
20566
|
});
|
|
20375
20567
|
return JSON.parse(stdout);
|
|
20376
20568
|
} finally {
|
|
20377
|
-
(0,
|
|
20569
|
+
(0, import_node_fs24.rmSync)(scriptDir, { recursive: true, force: true });
|
|
20378
20570
|
}
|
|
20379
20571
|
}
|
|
20380
20572
|
async function readSecretStdin() {
|
|
@@ -20464,7 +20656,7 @@ function registerSecretsCommands(program3) {
|
|
|
20464
20656
|
let body;
|
|
20465
20657
|
if (o.file) {
|
|
20466
20658
|
try {
|
|
20467
|
-
body = (0,
|
|
20659
|
+
body = (0, import_node_fs24.readFileSync)((0, import_node_path22.resolve)(o.file), "utf8");
|
|
20468
20660
|
} catch (e) {
|
|
20469
20661
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
20470
20662
|
}
|
|
@@ -20569,7 +20761,7 @@ function registerSecretsCommands(program3) {
|
|
|
20569
20761
|
{
|
|
20570
20762
|
...d,
|
|
20571
20763
|
decryptRailsCredentials,
|
|
20572
|
-
removeFile: (path2) => (0,
|
|
20764
|
+
removeFile: (path2) => (0, import_node_fs24.unlinkSync)((0, import_node_path22.resolve)(o.appDir ?? process.cwd(), path2))
|
|
20573
20765
|
},
|
|
20574
20766
|
{
|
|
20575
20767
|
repo: o.repo,
|
|
@@ -20763,7 +20955,7 @@ function checkGithubPools(probe) {
|
|
|
20763
20955
|
}
|
|
20764
20956
|
|
|
20765
20957
|
// src/box-commands.ts
|
|
20766
|
-
var
|
|
20958
|
+
var import_node_fs25 = require("node:fs");
|
|
20767
20959
|
|
|
20768
20960
|
// src/box.ts
|
|
20769
20961
|
var BOX_KEYS = {
|
|
@@ -20966,7 +21158,7 @@ function registerBoxCommands(program3) {
|
|
|
20966
21158
|
}
|
|
20967
21159
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
20968
21160
|
else if (o.ssh && o.script) {
|
|
20969
|
-
(0,
|
|
21161
|
+
(0, import_node_fs25.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
20970
21162
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
20971
21163
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
20972
21164
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -20981,7 +21173,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
20981
21173
|
|
|
20982
21174
|
// src/schedules-commands.ts
|
|
20983
21175
|
var import_promises3 = require("node:fs/promises");
|
|
20984
|
-
var
|
|
21176
|
+
var import_node_child_process13 = require("node:child_process");
|
|
20985
21177
|
var import_node_util7 = require("node:util");
|
|
20986
21178
|
|
|
20987
21179
|
// src/schedules.ts
|
|
@@ -21421,7 +21613,7 @@ function assembleReconciliation(githubEntries2, readRepos, registry2, activeWork
|
|
|
21421
21613
|
}
|
|
21422
21614
|
|
|
21423
21615
|
// src/schedules-commands.ts
|
|
21424
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
21616
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process13.execFile);
|
|
21425
21617
|
var AWS_REGION = "eu-central-1";
|
|
21426
21618
|
var AWS_TIMEOUT_MS = 3e4;
|
|
21427
21619
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -21658,7 +21850,7 @@ function registerSchedulesCommands(program3) {
|
|
|
21658
21850
|
|
|
21659
21851
|
// src/file-lock.ts
|
|
21660
21852
|
var import_promises4 = require("node:fs/promises");
|
|
21661
|
-
var
|
|
21853
|
+
var import_node_path23 = require("node:path");
|
|
21662
21854
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21663
21855
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
21664
21856
|
var FileLockBusyError = class extends Error {
|
|
@@ -21743,7 +21935,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
21743
21935
|
}
|
|
21744
21936
|
async function withFileLock(lockPath, opts, fn) {
|
|
21745
21937
|
const resolved = resolveFileLockOpts(opts);
|
|
21746
|
-
await (0, import_promises4.mkdir)((0,
|
|
21938
|
+
await (0, import_promises4.mkdir)((0, import_node_path23.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
21747
21939
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
21748
21940
|
try {
|
|
21749
21941
|
return await fn();
|
|
@@ -21754,7 +21946,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
21754
21946
|
|
|
21755
21947
|
// src/schedules-lift-command.ts
|
|
21756
21948
|
var import_promises5 = require("node:fs/promises");
|
|
21757
|
-
var
|
|
21949
|
+
var import_node_path24 = require("node:path");
|
|
21758
21950
|
|
|
21759
21951
|
// src/schedules-lift.ts
|
|
21760
21952
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -21860,7 +22052,7 @@ async function readWorkflowFiles(dir) {
|
|
|
21860
22052
|
const files = [];
|
|
21861
22053
|
for (const name of names.sort()) {
|
|
21862
22054
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
21863
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0,
|
|
22055
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path24.join)(dir, name), "utf8") });
|
|
21864
22056
|
}
|
|
21865
22057
|
return files;
|
|
21866
22058
|
}
|
|
@@ -22402,9 +22594,9 @@ function registerQueryCommands(program3) {
|
|
|
22402
22594
|
}
|
|
22403
22595
|
|
|
22404
22596
|
// src/bootstrap-commands.ts
|
|
22405
|
-
var
|
|
22597
|
+
var import_node_fs26 = require("node:fs");
|
|
22406
22598
|
var import_node_os9 = require("node:os");
|
|
22407
|
-
var
|
|
22599
|
+
var import_node_path25 = require("node:path");
|
|
22408
22600
|
|
|
22409
22601
|
// src/bootstrap-drift.ts
|
|
22410
22602
|
function byteComparableSeeds(manifest, cls) {
|
|
@@ -23018,13 +23210,13 @@ function registerBootstrapCommands(program3) {
|
|
|
23018
23210
|
client: defaultGitHubClient(),
|
|
23019
23211
|
projectMeta: meta,
|
|
23020
23212
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
23021
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
23213
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null,
|
|
23022
23214
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
23023
23215
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
23024
23216
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
23025
23217
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
23026
23218
|
// sanction, which is the pre-#3664 behaviour.
|
|
23027
|
-
sanctionedAdmins: (0,
|
|
23219
|
+
sanctionedAdmins: (0, import_node_fs26.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs26.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
23028
23220
|
requiredGcpApis: (() => {
|
|
23029
23221
|
const v = meta?.requiredGcpApis;
|
|
23030
23222
|
if (Array.isArray(v)) return v;
|
|
@@ -23062,12 +23254,12 @@ function registerBootstrapCommands(program3) {
|
|
|
23062
23254
|
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 () => {
|
|
23063
23255
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
23064
23256
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23065
|
-
if (!(0,
|
|
23066
|
-
const manifest = loadBootstrapSeeds((0,
|
|
23257
|
+
if (!(0, import_node_fs26.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`);
|
|
23258
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
|
|
23067
23259
|
const hubContents = /* @__PURE__ */ new Map();
|
|
23068
23260
|
for (const s of manifest.seeds) {
|
|
23069
23261
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
23070
|
-
hubContents.set(s.target, (0,
|
|
23262
|
+
hubContents.set(s.target, (0, import_node_fs26.existsSync)(s.target) ? (0, import_node_fs26.readFileSync)(s.target, "utf8") : null);
|
|
23071
23263
|
}
|
|
23072
23264
|
let targets;
|
|
23073
23265
|
let classOf = (_repo) => "deployable";
|
|
@@ -23144,8 +23336,8 @@ function registerBootstrapCommands(program3) {
|
|
|
23144
23336
|
return fail(`bootstrap apply: ${e.message}`);
|
|
23145
23337
|
}
|
|
23146
23338
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23147
|
-
if (!(0,
|
|
23148
|
-
const manifest = loadBootstrapSeeds((0,
|
|
23339
|
+
if (!(0, import_node_fs26.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`);
|
|
23340
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
|
|
23149
23341
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
23150
23342
|
const slug = parsedRepo.slug;
|
|
23151
23343
|
const onlyTarget = o.only.trim();
|
|
@@ -23156,16 +23348,16 @@ function registerBootstrapCommands(program3) {
|
|
|
23156
23348
|
${known}`);
|
|
23157
23349
|
}
|
|
23158
23350
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
23159
|
-
const readFile9 = (p) => (0,
|
|
23351
|
+
const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
|
|
23160
23352
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
23161
23353
|
const putSeed = async (target, content, ref, sha) => {
|
|
23162
|
-
const tmp = (0,
|
|
23163
|
-
(0,
|
|
23354
|
+
const tmp = (0, import_node_path25.join)((0, import_node_os9.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
23355
|
+
(0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
23164
23356
|
try {
|
|
23165
23357
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
23166
23358
|
} finally {
|
|
23167
23359
|
try {
|
|
23168
|
-
(0,
|
|
23360
|
+
(0, import_node_fs26.unlinkSync)(tmp);
|
|
23169
23361
|
} catch {
|
|
23170
23362
|
}
|
|
23171
23363
|
}
|
|
@@ -23417,12 +23609,12 @@ LIVE apply to ${repo}:
|
|
|
23417
23609
|
}
|
|
23418
23610
|
|
|
23419
23611
|
// src/stage-commands.ts
|
|
23420
|
-
var
|
|
23421
|
-
var
|
|
23612
|
+
var import_node_fs28 = require("node:fs");
|
|
23613
|
+
var import_node_path27 = require("node:path");
|
|
23422
23614
|
|
|
23423
23615
|
// src/port-registry.ts
|
|
23424
|
-
var
|
|
23425
|
-
var
|
|
23616
|
+
var import_node_fs27 = require("node:fs");
|
|
23617
|
+
var import_node_path26 = require("node:path");
|
|
23426
23618
|
|
|
23427
23619
|
// ../infra/port-geometry.mjs
|
|
23428
23620
|
var PORT_BLOCK = 100;
|
|
@@ -23436,8 +23628,8 @@ function nextPortBlock(registry2) {
|
|
|
23436
23628
|
return [base, base + PORT_SPAN];
|
|
23437
23629
|
}
|
|
23438
23630
|
function loadPortRegistry(path2) {
|
|
23439
|
-
if (!(0,
|
|
23440
|
-
const raw = JSON.parse((0,
|
|
23631
|
+
if (!(0, import_node_fs27.existsSync)(path2)) return {};
|
|
23632
|
+
const raw = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
|
|
23441
23633
|
const out = {};
|
|
23442
23634
|
for (const [key, value] of Object.entries(raw)) {
|
|
23443
23635
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -23451,9 +23643,9 @@ function ensurePortRange(repo, path2) {
|
|
|
23451
23643
|
const existing = registry2[repo];
|
|
23452
23644
|
if (existing) return existing;
|
|
23453
23645
|
const range = nextPortBlock(registry2);
|
|
23454
|
-
const raw = (0,
|
|
23646
|
+
const raw = (0, import_node_fs27.existsSync)(path2) ? JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8")) : {};
|
|
23455
23647
|
raw[repo] = range;
|
|
23456
|
-
(0,
|
|
23648
|
+
(0, import_node_fs27.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
23457
23649
|
return range;
|
|
23458
23650
|
}
|
|
23459
23651
|
function portCursorSeed(registry2) {
|
|
@@ -23475,22 +23667,22 @@ function existingPortRange(repo, registry2) {
|
|
|
23475
23667
|
return registry2[repo] ?? null;
|
|
23476
23668
|
}
|
|
23477
23669
|
function portRangeInfraAt(root, source) {
|
|
23478
|
-
const registryPath = (0,
|
|
23479
|
-
const ddbScriptPath = (0,
|
|
23480
|
-
if (!(0,
|
|
23670
|
+
const registryPath = (0, import_node_path26.join)(root, "infra", "port-ranges.json");
|
|
23671
|
+
const ddbScriptPath = (0, import_node_path26.join)(root, "infra", "port-ddb.mjs");
|
|
23672
|
+
if (!(0, import_node_fs27.existsSync)(registryPath) || !(0, import_node_fs27.existsSync)(ddbScriptPath)) return null;
|
|
23481
23673
|
return { root, source, registryPath, ddbScriptPath };
|
|
23482
23674
|
}
|
|
23483
23675
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
23484
23676
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
23485
23677
|
if (direct) return direct;
|
|
23486
|
-
for (let dir = cwd; ; dir = (0,
|
|
23487
|
-
const sibling = portRangeInfraAt((0,
|
|
23678
|
+
for (let dir = cwd; ; dir = (0, import_node_path26.dirname)(dir)) {
|
|
23679
|
+
const sibling = portRangeInfraAt((0, import_node_path26.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
23488
23680
|
if (sibling) return sibling;
|
|
23489
|
-
const parent = (0,
|
|
23681
|
+
const parent = (0, import_node_path26.dirname)(dir);
|
|
23490
23682
|
if (parent === dir) break;
|
|
23491
23683
|
}
|
|
23492
23684
|
if (packageDir) {
|
|
23493
|
-
const pkgRoot = (0,
|
|
23685
|
+
const pkgRoot = (0, import_node_path26.join)(packageDir, "..", "..");
|
|
23494
23686
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
23495
23687
|
if (pkgFrom) return pkgFrom;
|
|
23496
23688
|
}
|
|
@@ -23666,8 +23858,8 @@ function registerStageCommands(program3) {
|
|
|
23666
23858
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
23667
23859
|
return decideStage({
|
|
23668
23860
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
23669
|
-
hasCompose: (0,
|
|
23670
|
-
hasEnvExample: (0,
|
|
23861
|
+
hasCompose: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), "docker-compose.yml")),
|
|
23862
|
+
hasEnvExample: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), ".env.example"))
|
|
23671
23863
|
});
|
|
23672
23864
|
}
|
|
23673
23865
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -24105,11 +24297,11 @@ function registerBoardCommands(program3) {
|
|
|
24105
24297
|
}
|
|
24106
24298
|
|
|
24107
24299
|
// src/merge-cleanup.ts
|
|
24108
|
-
var
|
|
24300
|
+
var import_node_fs29 = require("node:fs");
|
|
24109
24301
|
var import_promises7 = require("node:fs/promises");
|
|
24110
|
-
var
|
|
24302
|
+
var import_node_path29 = require("node:path");
|
|
24111
24303
|
var import_node_os10 = require("node:os");
|
|
24112
|
-
var
|
|
24304
|
+
var import_node_child_process14 = require("node:child_process");
|
|
24113
24305
|
|
|
24114
24306
|
// src/board-advance.ts
|
|
24115
24307
|
function repoOf2(ref) {
|
|
@@ -24195,7 +24387,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
24195
24387
|
|
|
24196
24388
|
// src/deferred-registry-store.ts
|
|
24197
24389
|
var import_promises6 = require("node:fs/promises");
|
|
24198
|
-
var
|
|
24390
|
+
var import_node_path28 = require("node:path");
|
|
24199
24391
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
24200
24392
|
async function atomicWrite(target, contents) {
|
|
24201
24393
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -24246,12 +24438,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
24246
24438
|
},
|
|
24247
24439
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
24248
24440
|
write: async (entries) => {
|
|
24249
|
-
await (0, import_promises6.mkdir)((0,
|
|
24441
|
+
await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
|
|
24250
24442
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
24251
24443
|
},
|
|
24252
24444
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
24253
24445
|
update: async (mutate) => {
|
|
24254
|
-
await (0, import_promises6.mkdir)((0,
|
|
24446
|
+
await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
|
|
24255
24447
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
24256
24448
|
for (; ; ) {
|
|
24257
24449
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -24412,7 +24604,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24412
24604
|
);
|
|
24413
24605
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
24414
24606
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
24415
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
24607
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path29.dirname)((0, import_node_path29.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
24416
24608
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
24417
24609
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
24418
24610
|
const removalNow = Date.now();
|
|
@@ -24443,7 +24635,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24443
24635
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
24444
24636
|
beforeWorktrees,
|
|
24445
24637
|
startingPath: branch.worktreePath,
|
|
24446
|
-
pathExists: (p) => (0,
|
|
24638
|
+
pathExists: (p) => (0, import_node_fs29.existsSync)(p),
|
|
24447
24639
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
24448
24640
|
teardownWorktreeStage,
|
|
24449
24641
|
deferredStore,
|
|
@@ -24471,7 +24663,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24471
24663
|
for (const wt of worktreeDirsToRemove) {
|
|
24472
24664
|
try {
|
|
24473
24665
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
24474
|
-
realpath: (path2) => (0,
|
|
24666
|
+
realpath: (path2) => (0, import_node_fs29.realpathSync)(path2)
|
|
24475
24667
|
});
|
|
24476
24668
|
if (!cleanupTarget.ok) {
|
|
24477
24669
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -24536,13 +24728,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
24536
24728
|
const commits = JSON.parse(raw).commits ?? [];
|
|
24537
24729
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
24538
24730
|
if (!body) return void 0;
|
|
24539
|
-
const dir = (0,
|
|
24540
|
-
const path2 = (0,
|
|
24541
|
-
(0,
|
|
24731
|
+
const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path29.join)((0, import_node_os10.tmpdir)(), "mmi-squash-body-"));
|
|
24732
|
+
const path2 = (0, import_node_path29.join)(dir, "body.txt");
|
|
24733
|
+
(0, import_node_fs29.writeFileSync)(path2, `${body}
|
|
24542
24734
|
`, "utf8");
|
|
24543
24735
|
return { path: path2, cleanup: () => {
|
|
24544
24736
|
try {
|
|
24545
|
-
(0,
|
|
24737
|
+
(0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
|
|
24546
24738
|
} catch {
|
|
24547
24739
|
}
|
|
24548
24740
|
} };
|
|
@@ -24650,7 +24842,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
24650
24842
|
}
|
|
24651
24843
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
24652
24844
|
function spawnDeferredGcSweep() {
|
|
24653
|
-
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn:
|
|
24845
|
+
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
24654
24846
|
}
|
|
24655
24847
|
async function createDeferredWorktreeStore() {
|
|
24656
24848
|
try {
|
|
@@ -24664,13 +24856,13 @@ var realWorktreeDirRemover = {
|
|
|
24664
24856
|
probe: (p) => {
|
|
24665
24857
|
let st;
|
|
24666
24858
|
try {
|
|
24667
|
-
st = (0,
|
|
24859
|
+
st = (0, import_node_fs29.lstatSync)(p);
|
|
24668
24860
|
} catch {
|
|
24669
24861
|
return null;
|
|
24670
24862
|
}
|
|
24671
24863
|
if (st.isSymbolicLink()) return "link";
|
|
24672
24864
|
try {
|
|
24673
|
-
(0,
|
|
24865
|
+
(0, import_node_fs29.readlinkSync)(p);
|
|
24674
24866
|
return "link";
|
|
24675
24867
|
} catch {
|
|
24676
24868
|
}
|
|
@@ -24678,7 +24870,7 @@ var realWorktreeDirRemover = {
|
|
|
24678
24870
|
},
|
|
24679
24871
|
readdir: (p) => {
|
|
24680
24872
|
try {
|
|
24681
|
-
return (0,
|
|
24873
|
+
return (0, import_node_fs29.readdirSync)(p);
|
|
24682
24874
|
} catch {
|
|
24683
24875
|
return [];
|
|
24684
24876
|
}
|
|
@@ -24687,9 +24879,9 @@ var realWorktreeDirRemover = {
|
|
|
24687
24879
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
24688
24880
|
detachLink: (p) => {
|
|
24689
24881
|
try {
|
|
24690
|
-
(0,
|
|
24882
|
+
(0, import_node_fs29.rmdirSync)(p);
|
|
24691
24883
|
} catch {
|
|
24692
|
-
(0,
|
|
24884
|
+
(0, import_node_fs29.unlinkSync)(p);
|
|
24693
24885
|
}
|
|
24694
24886
|
},
|
|
24695
24887
|
removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -24722,9 +24914,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
24722
24914
|
}
|
|
24723
24915
|
}
|
|
24724
24916
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
24725
|
-
if (!(0,
|
|
24917
|
+
if (!(0, import_node_fs29.existsSync)(statePath)) return false;
|
|
24726
24918
|
try {
|
|
24727
|
-
const state = JSON.parse((0,
|
|
24919
|
+
const state = JSON.parse((0, import_node_fs29.readFileSync)(statePath, "utf8"));
|
|
24728
24920
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
24729
24921
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
24730
24922
|
} catch {
|
|
@@ -24931,9 +25123,9 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
24931
25123
|
}
|
|
24932
25124
|
|
|
24933
25125
|
// src/worktree-lifecycle-commands.ts
|
|
24934
|
-
var
|
|
25126
|
+
var import_node_fs30 = require("node:fs");
|
|
24935
25127
|
var import_promises8 = require("node:fs/promises");
|
|
24936
|
-
var
|
|
25128
|
+
var import_node_path30 = require("node:path");
|
|
24937
25129
|
var GH_TIMEOUT_MS = 2e4;
|
|
24938
25130
|
var DEFAULT_BASE = "origin/development";
|
|
24939
25131
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -25069,7 +25261,7 @@ function classifyStaleLeaks(input) {
|
|
|
25069
25261
|
var defaultOrphanDirScanDeps = {
|
|
25070
25262
|
listDirs: (root) => {
|
|
25071
25263
|
try {
|
|
25072
|
-
return (0,
|
|
25264
|
+
return (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path30.join)(root, e.name));
|
|
25073
25265
|
} catch {
|
|
25074
25266
|
return [];
|
|
25075
25267
|
}
|
|
@@ -25216,13 +25408,13 @@ function registerWorktreeCommands(program3) {
|
|
|
25216
25408
|
const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
|
|
25217
25409
|
const branch = headBorn ? (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim() : (await execFileP2("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25218
25410
|
if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
|
|
25219
|
-
const gitFile = (0,
|
|
25220
|
-
const isLinked = (0,
|
|
25411
|
+
const gitFile = (0, import_node_path30.join)(wtPath, ".git");
|
|
25412
|
+
const isLinked = (0, import_node_fs30.existsSync)(gitFile) && (0, import_node_fs30.statSync)(gitFile).isFile();
|
|
25221
25413
|
if (apply && !isLinked) {
|
|
25222
25414
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
25223
25415
|
}
|
|
25224
25416
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25225
|
-
const primaryCheckout = commonDir ? (0,
|
|
25417
|
+
const primaryCheckout = commonDir ? (0, import_node_path30.dirname)(commonDir) : wtPath;
|
|
25226
25418
|
const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
|
|
25227
25419
|
const orphan = classifyOrphanedWorktree({
|
|
25228
25420
|
branch,
|
|
@@ -25409,10 +25601,10 @@ async function gatherWorktreeContext() {
|
|
|
25409
25601
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
25410
25602
|
}
|
|
25411
25603
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
25412
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
25604
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
25413
25605
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
25414
25606
|
let orphanDirs = [];
|
|
25415
|
-
if ((0,
|
|
25607
|
+
if ((0, import_node_fs30.existsSync)(wtRoot)) {
|
|
25416
25608
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
25417
25609
|
...defaultOrphanDirScanDeps,
|
|
25418
25610
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -25435,7 +25627,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
25435
25627
|
}
|
|
25436
25628
|
|
|
25437
25629
|
// src/issue-commands.ts
|
|
25438
|
-
var
|
|
25630
|
+
var import_node_fs31 = require("node:fs");
|
|
25439
25631
|
var import_node_crypto5 = require("node:crypto");
|
|
25440
25632
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
25441
25633
|
var ReparentConflictError = class extends Error {
|
|
@@ -25453,7 +25645,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
25453
25645
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
25454
25646
|
const patch = {};
|
|
25455
25647
|
let bodyChanged = false;
|
|
25456
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
25648
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs31.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
25457
25649
|
if (options.titleFile !== void 0) {
|
|
25458
25650
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
25459
25651
|
} else if (options.title !== void 0) {
|
|
@@ -26016,7 +26208,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
26016
26208
|
if (opts.batch) {
|
|
26017
26209
|
let specs;
|
|
26018
26210
|
try {
|
|
26019
|
-
const raw = (0,
|
|
26211
|
+
const raw = (0, import_node_fs31.readFileSync)(opts.batch, "utf8");
|
|
26020
26212
|
specs = JSON.parse(raw);
|
|
26021
26213
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
26022
26214
|
} catch (e) {
|
|
@@ -26090,8 +26282,8 @@ ${lines}`, {
|
|
|
26090
26282
|
}
|
|
26091
26283
|
|
|
26092
26284
|
// src/train-commands.ts
|
|
26093
|
-
var
|
|
26094
|
-
var
|
|
26285
|
+
var import_node_fs32 = require("node:fs");
|
|
26286
|
+
var import_node_path31 = require("node:path");
|
|
26095
26287
|
|
|
26096
26288
|
// src/train-status.ts
|
|
26097
26289
|
function buildTrainStatusReport(input) {
|
|
@@ -26131,7 +26323,7 @@ function formatTrainStatus(r) {
|
|
|
26131
26323
|
// src/train-commands.ts
|
|
26132
26324
|
function readRepoVersion() {
|
|
26133
26325
|
try {
|
|
26134
|
-
return JSON.parse((0,
|
|
26326
|
+
return JSON.parse((0, import_node_fs32.readFileSync)((0, import_node_path31.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
26135
26327
|
} catch {
|
|
26136
26328
|
return void 0;
|
|
26137
26329
|
}
|
|
@@ -26277,9 +26469,9 @@ function registerDeployCommands(program3) {
|
|
|
26277
26469
|
}
|
|
26278
26470
|
|
|
26279
26471
|
// src/discovery-commands.ts
|
|
26280
|
-
var
|
|
26472
|
+
var import_node_fs33 = require("node:fs");
|
|
26281
26473
|
var import_node_os11 = require("node:os");
|
|
26282
|
-
var
|
|
26474
|
+
var import_node_path32 = require("node:path");
|
|
26283
26475
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
26284
26476
|
async function collectStatus() {
|
|
26285
26477
|
let branch = "";
|
|
@@ -26456,8 +26648,8 @@ async function collectOnboardStatus() {
|
|
|
26456
26648
|
}
|
|
26457
26649
|
const home = (0, import_node_os11.homedir)();
|
|
26458
26650
|
const plugin = onboardPluginGate({
|
|
26459
|
-
readKnown: () => readFileSyncSafe((0,
|
|
26460
|
-
readSettings: () => readFileSyncSafe((0,
|
|
26651
|
+
readKnown: () => readFileSyncSafe((0, import_node_path32.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
|
|
26652
|
+
readSettings: () => readFileSyncSafe((0, import_node_path32.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
|
|
26461
26653
|
});
|
|
26462
26654
|
return { track, board, registry: registry2, secrets, plugin, nextCommand };
|
|
26463
26655
|
}
|
|
@@ -27301,19 +27493,6 @@ function renderTally(checks) {
|
|
|
27301
27493
|
const healthy = total - checks.filter((c) => !c.ok).length;
|
|
27302
27494
|
return healthy === total ? `\u2713 all ${total} checks healthy` : `\u2713 ${healthy} of ${total} checks healthy`;
|
|
27303
27495
|
}
|
|
27304
|
-
function renderDoctorText(checks, opts) {
|
|
27305
|
-
const lines = [];
|
|
27306
|
-
const shown = opts.verbose ? checks : checks.filter((c) => !c.ok || c.warn);
|
|
27307
|
-
for (const check of shown) {
|
|
27308
|
-
lines.push(renderCheckLine(check));
|
|
27309
|
-
if (opts.verbose) {
|
|
27310
|
-
for (const evidence of check.verbose ?? []) lines.push(`${VERBOSE_INDENT}${evidence}`);
|
|
27311
|
-
}
|
|
27312
|
-
}
|
|
27313
|
-
lines.push(renderTally(checks));
|
|
27314
|
-
if (opts.restartPending) lines.push(RESTART_LINE);
|
|
27315
|
-
return lines.join("\n");
|
|
27316
|
-
}
|
|
27317
27496
|
function doctorReportExitCode(checks) {
|
|
27318
27497
|
return checks.some((c) => !c.ok && !c.reportOnly) ? 1 : 0;
|
|
27319
27498
|
}
|
|
@@ -28003,7 +28182,8 @@ var DOCTOR_SURFACES = Object.freeze(
|
|
|
28003
28182
|
installLocator: surface.install.locator,
|
|
28004
28183
|
upgradeMechanism: surface.upgrade.mechanism,
|
|
28005
28184
|
reload: surface.upgrade.reload,
|
|
28006
|
-
repairOwner: surface.ownership.repair === "mmi-cli" ? "mmi-cli" : "operator"
|
|
28185
|
+
repairOwner: surface.ownership.repair === "mmi-cli" ? "mmi-cli" : "operator",
|
|
28186
|
+
trustOwner: surface.ownership.trust === "operator" ? "operator" : "host"
|
|
28007
28187
|
}))
|
|
28008
28188
|
);
|
|
28009
28189
|
function doctorSurface(token) {
|
|
@@ -28036,11 +28216,8 @@ function diagnoseSurface(evidence) {
|
|
|
28036
28216
|
return { ...base, state: "clean" };
|
|
28037
28217
|
}
|
|
28038
28218
|
function reloadInstruction(descriptor) {
|
|
28039
|
-
|
|
28040
|
-
|
|
28041
|
-
if (descriptor.token === "kimi") return "restart Kimi Code CLI";
|
|
28042
|
-
if (descriptor.token === "kilo") return "restart Kilo Code";
|
|
28043
|
-
return descriptor.reload === "workspace" ? `reload ${descriptor.displayName}` : `restart ${descriptor.displayName}`;
|
|
28219
|
+
const verb = descriptor.reload === "workspace" ? "reload" : "restart";
|
|
28220
|
+
return `${verb} ${descriptor.displayName}`;
|
|
28044
28221
|
}
|
|
28045
28222
|
function planSurfaceRepair(diagnosis) {
|
|
28046
28223
|
if (diagnosis.state === "clean" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
@@ -28085,10 +28262,16 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
28085
28262
|
detail: detailByState[state],
|
|
28086
28263
|
...plan ? { fix: plan.instruction } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
28087
28264
|
verbose: [
|
|
28265
|
+
// The three numbers the legacy builder used to print. They belong here now that this is the only
|
|
28266
|
+
// plugin row, and a report that names a state without naming the versions behind it is not evidence.
|
|
28267
|
+
`installed: ${diagnosis.installedVersion ?? "(none)"}`,
|
|
28268
|
+
`released: ${diagnosis.releasedVersion ?? "(not checked \u2014 offline or --fast)"}`,
|
|
28269
|
+
`state: ${state}`,
|
|
28088
28270
|
`registry surface: ${descriptor.token}`,
|
|
28089
28271
|
`install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
|
|
28090
28272
|
`repair owner: ${descriptor.repairOwner}`,
|
|
28091
|
-
`artifacts: ${descriptor.artifactIds.join(", ")}
|
|
28273
|
+
`artifacts: ${descriptor.artifactIds.join(", ")}`,
|
|
28274
|
+
...diagnosis.repairDetail ? [`heal: ${diagnosis.repairDetail}`] : []
|
|
28092
28275
|
]
|
|
28093
28276
|
};
|
|
28094
28277
|
}
|
|
@@ -28186,77 +28369,23 @@ function checkWorktreeRoots(probe) {
|
|
|
28186
28369
|
verbose: evidence
|
|
28187
28370
|
};
|
|
28188
28371
|
}
|
|
28189
|
-
function
|
|
28190
|
-
if (probe.guardState === "no-install" || probe.guardState === "unresolved") return "unresolved";
|
|
28191
|
-
const behind = Boolean(probe.installed && probe.released) && compareVersions(probe.installed, probe.released) < 0;
|
|
28192
|
-
return behind ? "behind" : null;
|
|
28193
|
-
}
|
|
28194
|
-
function checkClaudePlugin(probe) {
|
|
28195
|
-
const { installed, released, guardState } = probe;
|
|
28196
|
-
const codex = probe.surface === "codex";
|
|
28197
|
-
const kilo = probe.surface === "kilo";
|
|
28198
|
-
const cursor = probe.surface === "cursor";
|
|
28199
|
-
const id = codex ? "codex-plugin" : kilo ? "kilo-plugin" : cursor ? "cursor-plugin" : "claude-plugin";
|
|
28200
|
-
const label = codex ? "Codex plugin" : kilo ? "Kilo plugin" : cursor ? "Cursor plugin" : "Claude plugin";
|
|
28201
|
-
const restart = codex ? "restart Codex" : kilo ? "reload Kilo" : cursor ? "reload Cursor" : "restart Claude";
|
|
28202
|
-
const evidence = [
|
|
28203
|
-
`installed: ${installed ?? "(none)"}`,
|
|
28204
|
-
// #3485 item 7: when the value was reused from the banner's once-a-day cache, say so and say how old.
|
|
28205
|
-
`released: ${released ?? "(not checked \u2014 offline or --fast)"}${released && probe.releasedNote ? ` ${probe.releasedNote}` : ""}`,
|
|
28206
|
-
`resolvable: ${guardState}`
|
|
28207
|
-
];
|
|
28208
|
-
const trigger = pluginHealTrigger(probe);
|
|
28209
|
-
if (trigger === "unresolved") {
|
|
28210
|
-
return {
|
|
28211
|
-
id,
|
|
28212
|
-
ok: false,
|
|
28213
|
-
label,
|
|
28214
|
-
detail: guardState === "no-install" ? "not installed" : "unresolved (delivery/cache missing)",
|
|
28215
|
-
fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall the MMI plugin, then ${restart}`,
|
|
28216
|
-
verbose: evidence
|
|
28217
|
-
};
|
|
28218
|
-
}
|
|
28219
|
-
if (trigger === "behind") {
|
|
28220
|
-
return {
|
|
28221
|
-
id,
|
|
28222
|
-
ok: false,
|
|
28223
|
-
label,
|
|
28224
|
-
detail: `${installed} \u2192 ${released}`,
|
|
28225
|
-
// Name the CLI verb that actually fixes it, like every other red row — `/plugin` is the manual
|
|
28226
|
-
// fallback, not the first resort (#3282).
|
|
28227
|
-
fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall it, then ${restart}`,
|
|
28228
|
-
verbose: evidence
|
|
28229
|
-
};
|
|
28230
|
-
}
|
|
28231
|
-
if (!released) {
|
|
28232
|
-
return {
|
|
28233
|
-
id,
|
|
28234
|
-
ok: false,
|
|
28235
|
-
reportOnly: true,
|
|
28236
|
-
label,
|
|
28237
|
-
detail: `${installed ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
28238
|
-
fix: "check it directly: `npm view @mutmutco/cli version`, then `mmi-cli plugin heal` if behind",
|
|
28239
|
-
verbose: evidence
|
|
28240
|
-
};
|
|
28241
|
-
}
|
|
28242
|
-
return { id, ok: true, label, ...installed ? { detail: installed } : {}, verbose: evidence };
|
|
28243
|
-
}
|
|
28244
|
-
function checkCodexHookTrust(probe) {
|
|
28372
|
+
function checkCodexHookTrust(probe, displayName = "Codex") {
|
|
28245
28373
|
if (!probe?.applicable) return null;
|
|
28246
28374
|
const evidence = [
|
|
28247
28375
|
`stored approval rows: ${probe.trustedCount}/${probe.requiredCount}`,
|
|
28248
28376
|
"current command hashes: verify interactively in /hooks"
|
|
28249
28377
|
];
|
|
28378
|
+
const label = `${displayName} hook trust`;
|
|
28250
28379
|
if (probe.trusted) {
|
|
28251
|
-
return { id: "codex-hook-trust", ok: true, label
|
|
28380
|
+
return { id: "codex-hook-trust", ok: true, label, detail: "trusted", verbose: evidence };
|
|
28252
28381
|
}
|
|
28253
28382
|
return {
|
|
28254
28383
|
id: "codex-hook-trust",
|
|
28255
28384
|
ok: true,
|
|
28256
28385
|
warn: true,
|
|
28257
|
-
label
|
|
28386
|
+
label,
|
|
28258
28387
|
detail: probe.requiredCount > 0 ? `${probe.trustedCount}/${probe.requiredCount} approval rows present \u2014 current hashes unverified` : "hook bundle could not be verified",
|
|
28259
|
-
fix: probe.requiredCount > 0 ?
|
|
28388
|
+
fix: probe.requiredCount > 0 ? `${displayName} does not allow silent hook approval; run \`/hooks\`, review the MMI commands, and trust them` : `run \`mmi-cli plugin heal\`, restart ${displayName}, then review and trust MMI under \`/hooks\``,
|
|
28260
28389
|
verbose: evidence
|
|
28261
28390
|
};
|
|
28262
28391
|
}
|
|
@@ -28481,6 +28610,12 @@ function gcReapable(plan) {
|
|
|
28481
28610
|
async function runDoctorClean(opts, io, deps) {
|
|
28482
28611
|
const applyEnv = Boolean(opts.apply) || Boolean(opts.preflight);
|
|
28483
28612
|
const applyRepo = Boolean(opts.apply) && opts.repoWrites !== false;
|
|
28613
|
+
const lane = {
|
|
28614
|
+
banner: Boolean(opts.banner),
|
|
28615
|
+
fast: Boolean(opts.fast),
|
|
28616
|
+
preflight: Boolean(opts.preflight),
|
|
28617
|
+
full: !opts.fast && !opts.banner && !opts.preflight
|
|
28618
|
+
};
|
|
28484
28619
|
const probeReleased = !opts.fast || Boolean(opts.self);
|
|
28485
28620
|
const probeAws = !opts.fast && !opts.banner;
|
|
28486
28621
|
const [login, isOrgRepo, released, callerArn, reach] = await Promise.all([
|
|
@@ -28495,108 +28630,69 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28495
28630
|
opts.banner || opts.preflight || opts.fast && !opts.self ? Promise.resolve(void 0) : deps.githubRepoReach?.() ?? Promise.resolve(void 0)
|
|
28496
28631
|
]);
|
|
28497
28632
|
const ghInstalled2 = login ? true : await deps.ghInstalled();
|
|
28498
|
-
const registryEvidence = deps.surfaceEvidence
|
|
28499
|
-
const
|
|
28500
|
-
const pluginSurface = deps.pluginSurface?.() ?? "claude-cli";
|
|
28501
|
-
const codexSurface = registryEvidence?.descriptor.token === "codex" || pluginSurface === "codex";
|
|
28502
|
-
const cursorSurface = pluginSurface === "cursor";
|
|
28503
|
-
const restartAction = registryEvidence ? surfaceRestartAction(registryEvidence.descriptor) : codexSurface ? "restart Codex" : cursorSurface ? "reload Cursor" : pluginSurface === "kilo" ? "restart Kilo Code" : "restart Claude";
|
|
28633
|
+
const registryEvidence = deps.surfaceEvidence(isOrgRepo);
|
|
28634
|
+
const restartAction = registryEvidence ? surfaceRestartAction(registryEvidence.descriptor) : "restart the agent host";
|
|
28504
28635
|
const checks = [];
|
|
28505
28636
|
let restartPending = false;
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
const current = deps.readGitignore();
|
|
28515
|
-
const gi = planGitignore(current);
|
|
28516
|
-
const giEvidence = [
|
|
28517
|
-
`.gitignore: ${current === null ? "absent" : `${current.split("\n").length} lines`}`,
|
|
28518
|
-
`managed block: ${gi.ok ? "present and current" : "missing or out of date"}`
|
|
28519
|
-
];
|
|
28520
|
-
if (gi.ok) {
|
|
28521
|
-
checks.push({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
|
|
28522
|
-
} else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
|
|
28523
|
-
checks.push({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
28524
|
-
restartPending = true;
|
|
28525
|
-
} else {
|
|
28526
|
-
checks.push({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
|
|
28527
|
-
}
|
|
28528
|
-
}
|
|
28529
|
-
const releasedNote = deps.releasedVersionNote?.();
|
|
28530
|
-
const pluginProbe = {
|
|
28531
|
-
installed,
|
|
28532
|
-
released,
|
|
28533
|
-
guardState: registryEvidence?.guardState ?? deps.pluginGuardState(isOrgRepo),
|
|
28534
|
-
releasedNote,
|
|
28535
|
-
surface: pluginSurface
|
|
28637
|
+
const streamed = /* @__PURE__ */ new Set();
|
|
28638
|
+
const worthPrinting = (c) => Boolean(opts.verbose) || !c.ok || Boolean(c.warn);
|
|
28639
|
+
const emitNow = (check) => {
|
|
28640
|
+
checks.push(check);
|
|
28641
|
+
if (opts.json || !worthPrinting(check)) return;
|
|
28642
|
+
streamed.add(check);
|
|
28643
|
+
io.log(renderCheckLine(check));
|
|
28644
|
+
if (opts.verbose) for (const evidence of check.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
28536
28645
|
};
|
|
28537
|
-
const
|
|
28538
|
-
|
|
28539
|
-
|
|
28646
|
+
const healIntent = (line) => {
|
|
28647
|
+
if (!opts.json) io.log(`\u21BB ${line}`);
|
|
28648
|
+
};
|
|
28649
|
+
const healStep = (message) => {
|
|
28650
|
+
if (!opts.json) io.log(`${VERBOSE_INDENT}${message.trim()}`);
|
|
28651
|
+
};
|
|
28652
|
+
const releasedNote = deps.releasedVersionNote?.();
|
|
28540
28653
|
let pluginHealed = false;
|
|
28541
|
-
|
|
28542
|
-
|
|
28543
|
-
|
|
28544
|
-
|
|
28545
|
-
|
|
28654
|
+
async function runPluginRow() {
|
|
28655
|
+
if (!registryEvidence) return;
|
|
28656
|
+
const diagnosis = diagnoseSurface({ ...registryEvidence, releasedVersion: released });
|
|
28657
|
+
const repair = planSurfaceRepair(diagnosis);
|
|
28658
|
+
if (applyEnv && deps.healPlugin && repair?.supported) {
|
|
28659
|
+
const { descriptor } = registryEvidence;
|
|
28660
|
+
healIntent(`${descriptor.displayName} plugin \u2014 healing via ${descriptor.installMechanism} (${descriptor.installLocator})`);
|
|
28661
|
+
const heal = await deps.healPlugin(healStep);
|
|
28662
|
+
pluginHealed = heal.ok;
|
|
28663
|
+
const row = buildSurfaceDoctorCheck(diagnoseSurface({
|
|
28546
28664
|
...registryEvidence,
|
|
28547
28665
|
releasedVersion: released,
|
|
28548
28666
|
repair: { attempted: true, ok: heal.ok, detail: heal.detail }
|
|
28549
|
-
});
|
|
28550
|
-
|
|
28551
|
-
|
|
28552
|
-
|
|
28553
|
-
|
|
28554
|
-
|
|
28555
|
-
const measured = healTrigger === "behind" ? `${installed} \u2192 ${released}` : "unresolved install";
|
|
28556
|
-
const healEvidence = [
|
|
28557
|
-
`installed: ${installed ?? "(none)"}`,
|
|
28558
|
-
`released: ${released ?? "(not checked \u2014 offline or --fast)"}`,
|
|
28559
|
-
`resolvable: ${pluginProbe.guardState}`,
|
|
28560
|
-
`heal: ${heal.detail}`
|
|
28561
|
-
];
|
|
28562
|
-
checks.push(heal.ok ? {
|
|
28563
|
-
id: codexSurface ? "codex-plugin" : pluginSurface === "kilo" ? "kilo-plugin" : cursorSurface ? "cursor-plugin" : "claude-plugin",
|
|
28564
|
-
ok: true,
|
|
28565
|
-
label: codexSurface ? "Codex plugin" : pluginSurface === "kilo" ? "Kilo plugin" : cursorSurface ? "Cursor plugin" : "Claude plugin",
|
|
28566
|
-
detail: pluginSurface === "kilo" ? `${measured} \u2014 reinstalled via \`kilo plugin\` (the plugin provisions the skills on next load)` : cursorSurface ? `${measured} \u2014 replaced the managed local checkout (previous copy quarantined)` : `${measured} \u2014 reinstalled via the MMI marketplace (remove \u2192 add \u2192 ${codexSurface ? "add" : "install"})${codexSurface ? "; review trust in /hooks" : ""}`,
|
|
28567
|
-
verbose: healEvidence
|
|
28568
|
-
} : {
|
|
28569
|
-
id: codexSurface ? "codex-plugin" : pluginSurface === "kilo" ? "kilo-plugin" : cursorSurface ? "cursor-plugin" : "claude-plugin",
|
|
28570
|
-
ok: false,
|
|
28571
|
-
label: codexSurface ? "Codex plugin" : pluginSurface === "kilo" ? "Kilo plugin" : cursorSurface ? "Cursor plugin" : "Claude plugin",
|
|
28572
|
-
detail: measured,
|
|
28573
|
-
// #3489: a heal skipped because another doctor holds the env-heal lock is a real ✗ — this run did
|
|
28574
|
-
// not fix what it found — but it is not this invocation's to clear. The other process is doing it;
|
|
28575
|
-
// waiting is the correct response and a re-run finds it healed. A heal that RAN and failed is a
|
|
28576
|
-
// genuine gap and still gates.
|
|
28577
|
-
...heal.skipped ? { reportOnly: true } : {},
|
|
28578
|
-
fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes` : `auto-heal failed (${heal.detail}) \u2014 run \`mmi-cli plugin heal\`, then ${codexSurface ? "restart Codex" : pluginSurface === "kilo" ? "reload Kilo" : cursorSurface ? "reload Cursor" : "restart Claude"}`,
|
|
28579
|
-
verbose: healEvidence
|
|
28580
|
-
});
|
|
28667
|
+
}));
|
|
28668
|
+
if (heal.skipped) {
|
|
28669
|
+
row.reportOnly = true;
|
|
28670
|
+
row.fix = `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes`;
|
|
28671
|
+
}
|
|
28672
|
+
emitNow(row);
|
|
28581
28673
|
if (!heal.skipped) restartPending = true;
|
|
28674
|
+
} else if (diagnosis.state !== "skipped") {
|
|
28675
|
+
const row = buildSurfaceDoctorCheck(diagnosis);
|
|
28676
|
+
emitNow(row);
|
|
28677
|
+
if (!row.ok && repair?.supported) restartPending = true;
|
|
28678
|
+
}
|
|
28679
|
+
if (registryEvidence.descriptor.trustOwner === "operator" && (registryEvidence.guardState === "healthy" || pluginHealed)) {
|
|
28680
|
+
const trust = checkCodexHookTrust(deps.pluginTrustState?.(), registryEvidence.descriptor.displayName);
|
|
28681
|
+
if (trust) emitNow(trust);
|
|
28682
|
+
}
|
|
28683
|
+
}
|
|
28684
|
+
async function runCliRow() {
|
|
28685
|
+
const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
|
|
28686
|
+
const cliReport = buildVersionLagReport(cliInput);
|
|
28687
|
+
if (!(applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm")) {
|
|
28688
|
+
const cli = checkCliVersion(cliInput, releasedNote);
|
|
28689
|
+
if (cli) emitNow(cli);
|
|
28690
|
+
return;
|
|
28582
28691
|
}
|
|
28583
|
-
|
|
28584
|
-
const
|
|
28585
|
-
if (plugin) {
|
|
28586
|
-
if (!("state" in plugin) || plugin.state !== "skipped") checks.push(plugin);
|
|
28587
|
-
if (!plugin.ok && (registryDiagnosis ? Boolean(registryRepairPlan?.supported) : true)) restartPending = true;
|
|
28588
|
-
}
|
|
28589
|
-
}
|
|
28590
|
-
if (codexSurface && (pluginProbe.guardState === "healthy" || pluginHealed)) {
|
|
28591
|
-
const trust = checkCodexHookTrust(deps.pluginTrustState?.());
|
|
28592
|
-
if (trust) checks.push(trust);
|
|
28593
|
-
}
|
|
28594
|
-
const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
|
|
28595
|
-
const cliReport = buildVersionLagReport(cliInput);
|
|
28596
|
-
if (applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
|
|
28597
|
-
const heal = await deps.updateCli(cliReport.releasedVersion);
|
|
28692
|
+
healIntent(`mmi-cli \u2014 self-updating ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion} via npm install -g`);
|
|
28693
|
+
const heal = await deps.updateCli(cliReport.releasedVersion, healStep);
|
|
28598
28694
|
const healEvidence = [`running: ${cliReport.currentVersion}`, `published: ${cliReport.releasedVersion}`, `heal: ${heal.detail}`];
|
|
28599
|
-
|
|
28695
|
+
emitNow(heal.ok ? {
|
|
28600
28696
|
id: "cli-version",
|
|
28601
28697
|
ok: true,
|
|
28602
28698
|
label: "mmi-cli",
|
|
@@ -28612,17 +28708,54 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28612
28708
|
fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is updating it now; re-run once it finishes` : `self-update failed (${heal.detail}) \u2014 run \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
|
|
28613
28709
|
verbose: healEvidence
|
|
28614
28710
|
});
|
|
28615
|
-
} else {
|
|
28616
|
-
const cli = checkCliVersion(cliInput, releasedNote);
|
|
28617
|
-
if (cli) checks.push(cli);
|
|
28618
28711
|
}
|
|
28619
|
-
|
|
28620
|
-
|
|
28712
|
+
async function runGithubAuthRow() {
|
|
28713
|
+
emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
|
|
28714
|
+
}
|
|
28715
|
+
async function runGithubPoolsRows() {
|
|
28716
|
+
for (const pool of checkGithubPools(await deps.githubPools())) emitNow(pool);
|
|
28717
|
+
}
|
|
28718
|
+
async function runAwsRow() {
|
|
28719
|
+
const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
|
|
28720
|
+
if (aws) emitNow(aws);
|
|
28721
|
+
}
|
|
28722
|
+
async function runRepoWorktreesRow() {
|
|
28723
|
+
emitNow(checkRepoWorktrees({ isOrgRepo, hasRepoLocalWorktrees: deps.hasRepoLocalWorktrees() }));
|
|
28724
|
+
}
|
|
28725
|
+
async function runGitignoreRow() {
|
|
28726
|
+
const current = deps.readGitignore();
|
|
28727
|
+
const gi = planGitignore(current);
|
|
28728
|
+
const giEvidence = [
|
|
28729
|
+
`.gitignore: ${current === null ? "absent" : `${current.split("\n").length} lines`}`,
|
|
28730
|
+
`managed block: ${gi.ok ? "present and current" : "missing or out of date"}`
|
|
28731
|
+
];
|
|
28732
|
+
if (gi.ok) {
|
|
28733
|
+
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
|
|
28734
|
+
} else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
|
|
28735
|
+
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
28736
|
+
restartPending = true;
|
|
28737
|
+
} else {
|
|
28738
|
+
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
|
|
28739
|
+
}
|
|
28740
|
+
}
|
|
28741
|
+
async function runPluginCacheRow() {
|
|
28742
|
+
emitNow(checkPluginCache(deps.pluginCache()));
|
|
28743
|
+
}
|
|
28744
|
+
async function runSessionPayloadRow() {
|
|
28621
28745
|
const payload = checkSessionPayload(deps.sessionPayload());
|
|
28622
|
-
if (payload)
|
|
28746
|
+
if (payload) emitNow(payload);
|
|
28747
|
+
}
|
|
28748
|
+
async function runMarketplaceRows() {
|
|
28749
|
+
if (applyEnv) {
|
|
28750
|
+
const healed = deps.healMarketplacePins();
|
|
28751
|
+
if (healed) {
|
|
28752
|
+
healIntent(`marketplace pins \u2014 ${healed}`);
|
|
28753
|
+
restartPending = true;
|
|
28754
|
+
}
|
|
28755
|
+
}
|
|
28756
|
+
for (const row of deps.marketplaceRows()) emitNow(row);
|
|
28623
28757
|
}
|
|
28624
|
-
|
|
28625
|
-
if (!opts.fast && !opts.banner && !opts.preflight) {
|
|
28758
|
+
async function runSchedulesRow() {
|
|
28626
28759
|
const probe = await deps.schedulesNotebook().catch((e) => ({
|
|
28627
28760
|
armed: 0,
|
|
28628
28761
|
live: 0,
|
|
@@ -28631,32 +28764,32 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28631
28764
|
drift: []
|
|
28632
28765
|
}));
|
|
28633
28766
|
const sched = checkSchedules(probe);
|
|
28634
|
-
if (sched)
|
|
28767
|
+
if (sched) emitNow(sched);
|
|
28635
28768
|
}
|
|
28636
|
-
|
|
28769
|
+
async function runRedactorRow() {
|
|
28637
28770
|
const redactor = checkRedactorLiveness(deps.redactorLiveness());
|
|
28638
|
-
if (redactor)
|
|
28771
|
+
if (redactor) emitNow(redactor);
|
|
28639
28772
|
}
|
|
28640
|
-
|
|
28773
|
+
async function runDocsAuditRow() {
|
|
28641
28774
|
const probe = await deps.docsAudit().catch((e) => ({
|
|
28642
28775
|
armed: true,
|
|
28643
28776
|
ok: false,
|
|
28644
28777
|
detail: `docs-audit probe failed \u2014 ${e.message}`
|
|
28645
28778
|
}));
|
|
28646
28779
|
const docsAudit2 = checkDocsAudit(probe);
|
|
28647
|
-
if (docsAudit2)
|
|
28780
|
+
if (docsAudit2) emitNow(docsAudit2);
|
|
28648
28781
|
}
|
|
28649
|
-
|
|
28782
|
+
async function runWorktreeRootsRow() {
|
|
28650
28783
|
const probe = await deps.worktreeRoots().catch(() => void 0);
|
|
28651
28784
|
const roots = checkWorktreeRoots(probe);
|
|
28652
|
-
if (roots)
|
|
28785
|
+
if (roots) emitNow(roots);
|
|
28653
28786
|
}
|
|
28654
|
-
|
|
28787
|
+
async function runTrainSyncRow() {
|
|
28655
28788
|
try {
|
|
28656
|
-
|
|
28789
|
+
emitNow(checkTrainSync(await deps.syncTrain()));
|
|
28657
28790
|
} catch (e) {
|
|
28658
28791
|
const message = e instanceof Error ? e.message : String(e);
|
|
28659
|
-
|
|
28792
|
+
emitNow({
|
|
28660
28793
|
ok: false,
|
|
28661
28794
|
id: "train-branches",
|
|
28662
28795
|
label: "train branches",
|
|
@@ -28664,6 +28797,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28664
28797
|
verbose: [`sync threw: ${message}`, "no train branch was fast-forwarded this run"]
|
|
28665
28798
|
});
|
|
28666
28799
|
}
|
|
28800
|
+
}
|
|
28801
|
+
async function runHousekeeperRows() {
|
|
28667
28802
|
const repoRoot2 = await deps.repoRoot();
|
|
28668
28803
|
try {
|
|
28669
28804
|
const plan = await deps.gcPlan("origin", 200);
|
|
@@ -28674,7 +28809,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28674
28809
|
if (r.removedBranches.length || r.removedRemoteBranches.length) detailParts.push(`${r.removedBranches.length + r.removedRemoteBranches.length} merged branches`);
|
|
28675
28810
|
if (r.removedWorktreeDirs.length) detailParts.push(`${r.removedWorktreeDirs.length} dead worktrees`);
|
|
28676
28811
|
if (r.removedTrackingRefs.length) detailParts.push(`${r.removedTrackingRefs.length} stale refs`);
|
|
28677
|
-
|
|
28812
|
+
emitNow({
|
|
28678
28813
|
id: "branches-worktrees",
|
|
28679
28814
|
ok: true,
|
|
28680
28815
|
label: "branches / worktrees",
|
|
@@ -28697,7 +28832,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28697
28832
|
...plan.trackingRefs.map((r) => `stale tracking ref: ${r.ref}`),
|
|
28698
28833
|
...plan.worktreeDirs.map((w) => `dead worktree: ${w.path} (${w.reason})`)
|
|
28699
28834
|
];
|
|
28700
|
-
|
|
28835
|
+
emitNow(n === 0 ? { id: "branches-worktrees", ok: true, label: "branches / worktrees", verbose: ["nothing reapable"] } : {
|
|
28701
28836
|
id: "branches-worktrees",
|
|
28702
28837
|
ok: false,
|
|
28703
28838
|
label: "branches / worktrees",
|
|
@@ -28715,12 +28850,35 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28715
28850
|
if (applyRepo && scratch.plan.safeAuto.length > 0) {
|
|
28716
28851
|
const applied = deps.executeScratchGc(repoRoot2, { apply: true });
|
|
28717
28852
|
const pruned = applied.applied?.pruned.length ?? 0;
|
|
28718
|
-
|
|
28853
|
+
emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
|
|
28719
28854
|
if (pruned) restartPending = true;
|
|
28720
28855
|
} else {
|
|
28721
|
-
|
|
28722
|
-
}
|
|
28723
|
-
}
|
|
28856
|
+
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 });
|
|
28857
|
+
}
|
|
28858
|
+
}
|
|
28859
|
+
const table = [
|
|
28860
|
+
// Heals first: the slowest work starts at second zero, and a stale mmi-cli is the binary every row
|
|
28861
|
+
// below is being measured with (#3954).
|
|
28862
|
+
{ id: "plugin", when: true, run: runPluginRow },
|
|
28863
|
+
{ id: "cli-version", when: true, run: runCliRow },
|
|
28864
|
+
{ id: "github-auth", when: true, run: runGithubAuthRow },
|
|
28865
|
+
{ id: "github-pools", when: lane.full, run: runGithubPoolsRows },
|
|
28866
|
+
{ id: "aws-identity", when: true, run: runAwsRow },
|
|
28867
|
+
{ id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
|
|
28868
|
+
{ id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
|
|
28869
|
+
{ id: "plugin-cache", when: true, run: runPluginCacheRow },
|
|
28870
|
+
{ id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
|
|
28871
|
+
{ id: "marketplace", when: true, run: runMarketplaceRows },
|
|
28872
|
+
{ id: "schedules", when: lane.full, run: runSchedulesRow },
|
|
28873
|
+
{ id: "redactor-liveness", when: lane.full, run: runRedactorRow },
|
|
28874
|
+
{ id: "docs-audit", when: lane.full, run: runDocsAuditRow },
|
|
28875
|
+
{ id: "worktree-roots", when: lane.full, run: runWorktreeRootsRow },
|
|
28876
|
+
// The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
|
|
28877
|
+
// and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
|
|
28878
|
+
{ id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
|
|
28879
|
+
{ id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
|
|
28880
|
+
];
|
|
28881
|
+
for (const entry of table) if (entry.when) await entry.run();
|
|
28724
28882
|
const exitCode = doctorReportExitCode(checks);
|
|
28725
28883
|
if (opts.json) {
|
|
28726
28884
|
const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
|
|
@@ -28733,7 +28891,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28733
28891
|
return exitCode;
|
|
28734
28892
|
}
|
|
28735
28893
|
if (opts.banner) {
|
|
28736
|
-
const actionable = checks.filter((c) => !c.ok || c.warn);
|
|
28894
|
+
const actionable = checks.filter((c) => (!c.ok || c.warn) && !streamed.has(c));
|
|
28737
28895
|
for (const c of actionable) {
|
|
28738
28896
|
io.log(renderReport([c], { restartPending: false }));
|
|
28739
28897
|
if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
@@ -28741,10 +28899,16 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28741
28899
|
if (restartPending) io.log(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
|
|
28742
28900
|
return 0;
|
|
28743
28901
|
}
|
|
28744
|
-
const
|
|
28745
|
-
const
|
|
28746
|
-
|
|
28747
|
-
|
|
28902
|
+
const rest = checks.filter((c) => !streamed.has(c));
|
|
28903
|
+
const shown = opts.verbose ? rest : rest.filter((c) => !c.ok || c.warn);
|
|
28904
|
+
const lines = [];
|
|
28905
|
+
for (const check of shown) {
|
|
28906
|
+
lines.push(renderCheckLine(check));
|
|
28907
|
+
if (opts.verbose) for (const evidence of check.verbose ?? []) lines.push(`${VERBOSE_INDENT}${evidence}`);
|
|
28908
|
+
}
|
|
28909
|
+
lines.push(renderTally(checks));
|
|
28910
|
+
if (restartPending) lines.push(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
|
|
28911
|
+
io.log(lines.join("\n"));
|
|
28748
28912
|
return exitCode;
|
|
28749
28913
|
}
|
|
28750
28914
|
|
|
@@ -28767,17 +28931,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
28767
28931
|
}
|
|
28768
28932
|
function ghHostsConfigPath(env, platform2) {
|
|
28769
28933
|
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
28770
|
-
const
|
|
28934
|
+
const join29 = (...parts) => parts.join(sep2);
|
|
28771
28935
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
28772
|
-
if (explicit) return
|
|
28936
|
+
if (explicit) return join29(explicit, "hosts.yml");
|
|
28773
28937
|
if (platform2 === "win32") {
|
|
28774
28938
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
28775
|
-
return appData ?
|
|
28939
|
+
return appData ? join29(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
28776
28940
|
}
|
|
28777
28941
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
28778
|
-
if (xdg) return
|
|
28942
|
+
if (xdg) return join29(xdg, "gh", "hosts.yml");
|
|
28779
28943
|
const home = env.HOME?.trim();
|
|
28780
|
-
return home ?
|
|
28944
|
+
return home ? join29(home, ".config", "gh", "hosts.yml") : void 0;
|
|
28781
28945
|
}
|
|
28782
28946
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
28783
28947
|
let hostIndent = null;
|
|
@@ -28827,17 +28991,17 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
28827
28991
|
}
|
|
28828
28992
|
|
|
28829
28993
|
// src/doctor-io.ts
|
|
28830
|
-
var
|
|
28994
|
+
var import_node_fs34 = require("node:fs");
|
|
28831
28995
|
var import_node_os12 = require("node:os");
|
|
28832
|
-
var
|
|
28833
|
-
var
|
|
28996
|
+
var import_node_path33 = require("node:path");
|
|
28997
|
+
var import_node_child_process15 = require("node:child_process");
|
|
28834
28998
|
var import_node_util8 = require("node:util");
|
|
28835
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
28999
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process15.execFile);
|
|
28836
29000
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
28837
29001
|
function installedClaudePluginVersion() {
|
|
28838
29002
|
try {
|
|
28839
29003
|
const file = JSON.parse(
|
|
28840
|
-
(0,
|
|
29004
|
+
(0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
28841
29005
|
);
|
|
28842
29006
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
28843
29007
|
if (versions.length === 0) return void 0;
|
|
@@ -28848,7 +29012,7 @@ function installedClaudePluginVersion() {
|
|
|
28848
29012
|
}
|
|
28849
29013
|
function manifestVersion(path2) {
|
|
28850
29014
|
try {
|
|
28851
|
-
const manifest = JSON.parse((0,
|
|
29015
|
+
const manifest = JSON.parse((0, import_node_fs34.readFileSync)(path2, "utf8"));
|
|
28852
29016
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
28853
29017
|
} catch {
|
|
28854
29018
|
return void 0;
|
|
@@ -28858,27 +29022,27 @@ function installedSurfacePluginVersion(surface) {
|
|
|
28858
29022
|
const token = surfaceToken(surface);
|
|
28859
29023
|
if (token === "kilo") {
|
|
28860
29024
|
try {
|
|
28861
|
-
const stamp = (0,
|
|
29025
|
+
const stamp = (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
28862
29026
|
return stamp || void 0;
|
|
28863
29027
|
} catch {
|
|
28864
29028
|
return void 0;
|
|
28865
29029
|
}
|
|
28866
29030
|
}
|
|
28867
29031
|
if (token === "cursor") {
|
|
28868
|
-
return manifestVersion((0,
|
|
29032
|
+
return manifestVersion((0, import_node_path33.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
28869
29033
|
}
|
|
28870
29034
|
if (token === "kimi") {
|
|
28871
|
-
return manifestVersion((0,
|
|
29035
|
+
return manifestVersion((0, import_node_path33.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
28872
29036
|
}
|
|
28873
29037
|
if (token === "claude") return installedClaudePluginVersion();
|
|
28874
29038
|
if (token !== "codex") return void 0;
|
|
28875
29039
|
try {
|
|
28876
|
-
const raw = process.platform === "win32" ? (0,
|
|
29040
|
+
const raw = process.platform === "win32" ? (0, import_node_child_process15.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
|
|
28877
29041
|
encoding: "utf8",
|
|
28878
29042
|
stdio: ["ignore", "pipe", "ignore"],
|
|
28879
29043
|
timeout: 15e3,
|
|
28880
29044
|
windowsHide: true
|
|
28881
|
-
}) : (0,
|
|
29045
|
+
}) : (0, import_node_child_process15.execFileSync)("codex", ["plugin", "list", "--json"], {
|
|
28882
29046
|
encoding: "utf8",
|
|
28883
29047
|
stdio: ["ignore", "pipe", "ignore"],
|
|
28884
29048
|
timeout: 15e3,
|
|
@@ -28896,7 +29060,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
|
28896
29060
|
}
|
|
28897
29061
|
function worktreeRootSync() {
|
|
28898
29062
|
try {
|
|
28899
|
-
const out = (0,
|
|
29063
|
+
const out = (0, import_node_child_process15.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
28900
29064
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
28901
29065
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
28902
29066
|
return root || null;
|
|
@@ -28906,13 +29070,13 @@ function worktreeRootSync() {
|
|
|
28906
29070
|
}
|
|
28907
29071
|
var gitignorePath = () => {
|
|
28908
29072
|
const root = worktreeRootSync();
|
|
28909
|
-
return root === null ? null : (0,
|
|
29073
|
+
return root === null ? null : (0, import_node_path33.join)(root, ".gitignore");
|
|
28910
29074
|
};
|
|
28911
29075
|
function readGitignore() {
|
|
28912
29076
|
const path2 = gitignorePath();
|
|
28913
29077
|
if (path2 === null) return null;
|
|
28914
29078
|
try {
|
|
28915
|
-
return (0,
|
|
29079
|
+
return (0, import_node_fs34.readFileSync)(path2, "utf8");
|
|
28916
29080
|
} catch {
|
|
28917
29081
|
return null;
|
|
28918
29082
|
}
|
|
@@ -28921,7 +29085,7 @@ function writeGitignore(content) {
|
|
|
28921
29085
|
const path2 = gitignorePath();
|
|
28922
29086
|
if (path2 === null) return false;
|
|
28923
29087
|
try {
|
|
28924
|
-
(0,
|
|
29088
|
+
(0, import_node_fs34.writeFileSync)(path2, content, "utf8");
|
|
28925
29089
|
return true;
|
|
28926
29090
|
} catch {
|
|
28927
29091
|
return false;
|
|
@@ -28945,7 +29109,7 @@ async function repoRoot() {
|
|
|
28945
29109
|
}
|
|
28946
29110
|
function hasRepoLocalWorktrees() {
|
|
28947
29111
|
const root = worktreeRootSync();
|
|
28948
|
-
return root !== null && (0,
|
|
29112
|
+
return root !== null && (0, import_node_fs34.existsSync)((0, import_node_path33.join)(root, ".worktrees"));
|
|
28949
29113
|
}
|
|
28950
29114
|
|
|
28951
29115
|
// src/index.ts
|
|
@@ -28981,8 +29145,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
28981
29145
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
28982
29146
|
try {
|
|
28983
29147
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
28984
|
-
if (!hostsPath || !(0,
|
|
28985
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
29148
|
+
if (!hostsPath || !(0, import_node_fs35.existsSync)(hostsPath)) return void 0;
|
|
29149
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs35.readFileSync)(hostsPath, "utf8")));
|
|
28986
29150
|
} catch {
|
|
28987
29151
|
return void 0;
|
|
28988
29152
|
}
|
|
@@ -28990,7 +29154,7 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
28990
29154
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
28991
29155
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
28992
29156
|
function envHealLockPath(home) {
|
|
28993
|
-
return (0,
|
|
29157
|
+
return (0, import_node_path34.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
28994
29158
|
}
|
|
28995
29159
|
async function withEnvHealLock(what, run) {
|
|
28996
29160
|
try {
|
|
@@ -29065,10 +29229,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29065
29229
|
githubRepoReach: githubRepoReachProbe,
|
|
29066
29230
|
awsCallerArn,
|
|
29067
29231
|
isOrgRepo: () => isOrgRepoRoot(),
|
|
29068
|
-
installedPluginVersion: installedActivePluginVersion,
|
|
29069
29232
|
surfaceEvidence: surfaceEvidenceOnce,
|
|
29070
|
-
pluginGuardState: activePluginGuardState,
|
|
29071
|
-
pluginSurface: () => detectSurface(process.env),
|
|
29072
29233
|
pluginTrustState: () => codexHookTrustState(),
|
|
29073
29234
|
releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
|
|
29074
29235
|
releasedVersionNote: throttled ? throttled.note : void 0,
|
|
@@ -29077,13 +29238,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29077
29238
|
// marketplace clone + plugin cache) and neither is idempotent under concurrency. They share ONE lock
|
|
29078
29239
|
// rather than one each, because they are not independent: the plugin heal reinstalls a bundle that
|
|
29079
29240
|
// carries a CLI shim, so a concurrent npm global install races the same PATH surface.
|
|
29080
|
-
updateCli: (target) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target)),
|
|
29241
|
+
updateCli: (target, onStep) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target, onStep)),
|
|
29081
29242
|
// #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
|
|
29082
29243
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
29083
|
-
healPlugin: () => {
|
|
29244
|
+
healPlugin: (onStep) => {
|
|
29084
29245
|
const surface = detectSurface(process.env);
|
|
29085
|
-
const host =
|
|
29086
|
-
return withEnvHealLock(`${host} plugin reinstall`, () => healActivePluginForDoctor(surface));
|
|
29246
|
+
const host = surfaceEvidenceOnce(true)?.descriptor.displayName ?? surface;
|
|
29247
|
+
return withEnvHealLock(`${host} plugin reinstall`, () => healActivePluginForDoctor(surface, onStep));
|
|
29087
29248
|
},
|
|
29088
29249
|
currentCliVersion: resolveClientVersion,
|
|
29089
29250
|
readGitignore,
|
|
@@ -29179,12 +29340,25 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29179
29340
|
const home = (0, import_node_os13.homedir)();
|
|
29180
29341
|
return marketplaceRows(
|
|
29181
29342
|
MMI_MARKETPLACE_NAME,
|
|
29182
|
-
readFileSyncSafe((0,
|
|
29183
|
-
readFileSyncSafe((0,
|
|
29343
|
+
readFileSyncSafe((0, import_node_path34.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs35.readFileSync),
|
|
29344
|
+
readFileSyncSafe((0, import_node_path34.join)(home, ".claude", "settings.json"), import_node_fs35.readFileSync),
|
|
29345
|
+
// #3974: this CLI heals these rows, so report mode names `doctor --apply` rather than the hand
|
|
29346
|
+
// edit. Unconditional — it is a fact about mmi-cli, not about whether --apply was passed.
|
|
29347
|
+
true
|
|
29184
29348
|
);
|
|
29185
29349
|
} catch {
|
|
29186
29350
|
return [];
|
|
29187
29351
|
}
|
|
29352
|
+
},
|
|
29353
|
+
// #3974: the heal behind those rows, under --apply only. Same Codex carve-out as the rows above —
|
|
29354
|
+
// Codex keeps its registration in config.toml, so there is no ~/.claude registration to pin.
|
|
29355
|
+
healMarketplacePins: () => {
|
|
29356
|
+
try {
|
|
29357
|
+
if (detectSurface(process.env) === "codex") return void 0;
|
|
29358
|
+
return applyOrgMarketplacePins((0, import_node_path34.join)((0, import_node_os13.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE), [MMI_MARKETPLACE_NAME]);
|
|
29359
|
+
} catch {
|
|
29360
|
+
return void 0;
|
|
29361
|
+
}
|
|
29188
29362
|
}
|
|
29189
29363
|
};
|
|
29190
29364
|
}
|
|
@@ -29386,19 +29560,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
29386
29560
|
});
|
|
29387
29561
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
29388
29562
|
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) => {
|
|
29389
|
-
const path2 = (0,
|
|
29390
|
-
const current = (0,
|
|
29563
|
+
const path2 = (0, import_node_path34.join)(process.cwd(), ".gitignore");
|
|
29564
|
+
const current = (0, import_node_fs35.existsSync)(path2) ? (0, import_node_fs35.readFileSync)(path2, "utf8") : null;
|
|
29391
29565
|
const plan = planManagedGitignore(current);
|
|
29392
29566
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
29393
29567
|
if (opts.json) {
|
|
29394
|
-
if (opts.write && plan.changed) (0,
|
|
29568
|
+
if (opts.write && plan.changed) (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
|
|
29395
29569
|
console.log(JSON.stringify(plan, null, 2));
|
|
29396
29570
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
29397
29571
|
return;
|
|
29398
29572
|
}
|
|
29399
29573
|
if (opts.write) {
|
|
29400
29574
|
if (plan.changed) {
|
|
29401
|
-
(0,
|
|
29575
|
+
(0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
|
|
29402
29576
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
29403
29577
|
} else {
|
|
29404
29578
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -29553,8 +29727,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
29553
29727
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
29554
29728
|
let root;
|
|
29555
29729
|
if (o.root !== void 0) {
|
|
29556
|
-
root = (0,
|
|
29557
|
-
if (!(0,
|
|
29730
|
+
root = (0, import_node_path34.resolve)(o.root);
|
|
29731
|
+
if (!(0, import_node_fs35.existsSync)(root) || !(0, import_node_fs35.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
29558
29732
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
29559
29733
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
29560
29734
|
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -29596,7 +29770,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
29596
29770
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
29597
29771
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
29598
29772
|
return new Promise((resolve5, reject) => {
|
|
29599
|
-
const child2 = (0,
|
|
29773
|
+
const child2 = (0, import_node_child_process16.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
|
|
29600
29774
|
const timer = setTimeout(() => {
|
|
29601
29775
|
try {
|
|
29602
29776
|
child2.kill();
|
|
@@ -29619,7 +29793,7 @@ async function primaryCheckoutRoot(from) {
|
|
|
29619
29793
|
return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
29620
29794
|
}
|
|
29621
29795
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
29622
|
-
if (!(0,
|
|
29796
|
+
if (!(0, import_node_fs35.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
29623
29797
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
29624
29798
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
29625
29799
|
if (!registered.length) {
|
|
@@ -29640,26 +29814,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
29640
29814
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
29641
29815
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
29642
29816
|
const take = () => {
|
|
29643
|
-
const fd = (0,
|
|
29817
|
+
const fd = (0, import_node_fs35.openSync)(lockPath, "wx");
|
|
29644
29818
|
try {
|
|
29645
|
-
(0,
|
|
29819
|
+
(0, import_node_fs35.writeSync)(fd, String(Date.now()));
|
|
29646
29820
|
} finally {
|
|
29647
|
-
(0,
|
|
29821
|
+
(0, import_node_fs35.closeSync)(fd);
|
|
29648
29822
|
}
|
|
29649
29823
|
return () => {
|
|
29650
29824
|
try {
|
|
29651
|
-
(0,
|
|
29825
|
+
(0, import_node_fs35.rmSync)(lockPath, { force: true });
|
|
29652
29826
|
} catch {
|
|
29653
29827
|
}
|
|
29654
29828
|
};
|
|
29655
29829
|
};
|
|
29656
29830
|
try {
|
|
29657
|
-
(0,
|
|
29831
|
+
(0, import_node_fs35.mkdirSync)((0, import_node_path34.dirname)(lockPath), { recursive: true });
|
|
29658
29832
|
return take();
|
|
29659
29833
|
} catch {
|
|
29660
29834
|
try {
|
|
29661
|
-
if (Date.now() - (0,
|
|
29662
|
-
(0,
|
|
29835
|
+
if (Date.now() - (0, import_node_fs35.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
29836
|
+
(0, import_node_fs35.rmSync)(lockPath, { force: true });
|
|
29663
29837
|
return take();
|
|
29664
29838
|
}
|
|
29665
29839
|
} catch {
|
|
@@ -29882,7 +30056,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
29882
30056
|
try {
|
|
29883
30057
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
29884
30058
|
if (o.repo) args.push("--repo", o.repo);
|
|
29885
|
-
spawnDetachedSelf(args, { spawn:
|
|
30059
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
29886
30060
|
} catch {
|
|
29887
30061
|
}
|
|
29888
30062
|
}
|
|
@@ -29932,6 +30106,29 @@ docs.command("refs").description("deterministic doc reference gate: every backti
|
|
|
29932
30106
|
await failGraceful(e.message);
|
|
29933
30107
|
}
|
|
29934
30108
|
});
|
|
30109
|
+
var spawnCmd = program2.command("spawn").description("this repo's process-spawn contract \u2014 every child process must be unable to pop a console window");
|
|
30110
|
+
spawnCmd.command("policy").description("enforce the windowsHide contract across this repo's tracked source: a child_process call must set windowsHide, or take its options from a named constant, a type annotation, or a forwarded caller bag that does. Waive a call the scan cannot classify with a `// windows-hide-exempt: <reason>` comment on the line above it (#3979)").option("--json", "machine-readable result: { ok, scannedCount, findings[] }").action(async (o) => {
|
|
30111
|
+
try {
|
|
30112
|
+
const root = await repoRoot();
|
|
30113
|
+
const result = runSpawnPolicy(root);
|
|
30114
|
+
if (o.json) {
|
|
30115
|
+
consoleIo.log(JSON.stringify(result, null, 2));
|
|
30116
|
+
if (!result.ok) process.exitCode = 1;
|
|
30117
|
+
return;
|
|
30118
|
+
}
|
|
30119
|
+
if (result.ok) {
|
|
30120
|
+
console.log(`spawn policy: OK (${result.scannedCount} source file(s) scanned).`);
|
|
30121
|
+
return;
|
|
30122
|
+
}
|
|
30123
|
+
for (const f of result.findings) console.error(`spawn policy: ${f.detail}`);
|
|
30124
|
+
console.error(
|
|
30125
|
+
"\nA process spawned without windowsHide allocates a console when it has none to inherit.\nWhere Windows Terminal is the default terminal, that console becomes a desktop window the\nhost never reaps \u2014 it outlives the process as an empty frame. Set windowsHide: true, or\nroute the call through a helper that does."
|
|
30126
|
+
);
|
|
30127
|
+
process.exitCode = 1;
|
|
30128
|
+
} catch (e) {
|
|
30129
|
+
await failGraceful(e.message);
|
|
30130
|
+
}
|
|
30131
|
+
});
|
|
29935
30132
|
var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
29936
30133
|
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
|
|
29937
30134
|
try {
|
|
@@ -30238,7 +30435,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
30238
30435
|
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`);
|
|
30239
30436
|
if (o.secretsFile) {
|
|
30240
30437
|
try {
|
|
30241
|
-
vars.push(`secrets=${(0,
|
|
30438
|
+
vars.push(`secrets=${(0, import_node_fs35.readFileSync)(o.secretsFile, "utf8")}`);
|
|
30242
30439
|
} catch (e) {
|
|
30243
30440
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
30244
30441
|
}
|
|
@@ -30986,11 +31183,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
30986
31183
|
}
|
|
30987
31184
|
});
|
|
30988
31185
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
30989
|
-
const wfDir = (0,
|
|
30990
|
-
if (!(0,
|
|
30991
|
-
return (0,
|
|
31186
|
+
const wfDir = (0, import_node_path34.join)(cwd, ".github", "workflows");
|
|
31187
|
+
if (!(0, import_node_fs35.existsSync)(wfDir)) return [];
|
|
31188
|
+
return (0, import_node_fs35.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
30992
31189
|
try {
|
|
30993
|
-
return workflowReportsPrChecks((0,
|
|
31190
|
+
return workflowReportsPrChecks((0, import_node_fs35.readFileSync)((0, import_node_path34.join)(wfDir, name), "utf8"));
|
|
30994
31191
|
} catch {
|
|
30995
31192
|
return true;
|
|
30996
31193
|
}
|
|
@@ -31022,16 +31219,16 @@ function ciAuditDeps() {
|
|
|
31022
31219
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
31023
31220
|
readSeedFile: (path2) => {
|
|
31024
31221
|
if (!root) return null;
|
|
31025
|
-
const fullPath = (0,
|
|
31026
|
-
return (0,
|
|
31222
|
+
const fullPath = (0, import_node_path34.join)(root, path2);
|
|
31223
|
+
return (0, import_node_fs35.existsSync)(fullPath) ? (0, import_node_fs35.readFileSync)(fullPath, "utf8") : null;
|
|
31027
31224
|
}
|
|
31028
31225
|
};
|
|
31029
31226
|
}
|
|
31030
31227
|
function hubRoot() {
|
|
31031
|
-
const fromPkg = (0,
|
|
31228
|
+
const fromPkg = (0, import_node_path34.join)(__dirname, "..", "..");
|
|
31032
31229
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
31033
|
-
if ((0,
|
|
31034
|
-
if ((0,
|
|
31230
|
+
if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(fromPkg, marker))) return fromPkg;
|
|
31231
|
+
if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(process.cwd(), marker))) return process.cwd();
|
|
31035
31232
|
return null;
|
|
31036
31233
|
}
|
|
31037
31234
|
pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs no-ci (for grind/build agents)").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").action(async (o) => {
|
|
@@ -31334,7 +31531,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
31334
31531
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
31335
31532
|
beforeWorktrees,
|
|
31336
31533
|
startingPath,
|
|
31337
|
-
pathExists: (p) => (0,
|
|
31534
|
+
pathExists: (p) => (0, import_node_fs35.existsSync)(p),
|
|
31338
31535
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
31339
31536
|
teardownWorktreeStage,
|
|
31340
31537
|
deferredStore,
|
|
@@ -31485,6 +31682,9 @@ function renderReleaseResume(r) {
|
|
|
31485
31682
|
function renderReleaseAbort(r) {
|
|
31486
31683
|
return `mmi-cli release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
31487
31684
|
}
|
|
31685
|
+
function renderReleasePublishRetry(r) {
|
|
31686
|
+
return `mmi-cli release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
31687
|
+
}
|
|
31488
31688
|
function renderRcandResume(r) {
|
|
31489
31689
|
return `mmi-cli rcand --resume: promoted ${r.repo} \u2192 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
|
|
31490
31690
|
}
|
|
@@ -31532,7 +31732,8 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
31532
31732
|
{ flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line Hub Slack summary; required for a new MMI-Hub --apply (#883/#3901)" },
|
|
31533
31733
|
{ flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
|
|
31534
31734
|
{ flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
|
|
31535
|
-
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed Hub tag and restore local main for a clean recut (#3944)" }
|
|
31735
|
+
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed Hub tag and restore local main for a clean recut (#3944)" },
|
|
31736
|
+
{ flags: "--retry-publish <run-id>", description: "with --apply, retry failed jobs of one proven Hub publish release run (#3949)" }
|
|
31536
31737
|
];
|
|
31537
31738
|
for (const f of RELEASE_ONLY_FLAGS) {
|
|
31538
31739
|
if (commandName === "release") {
|
|
@@ -31559,6 +31760,25 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
31559
31760
|
if (o.abort && commandName !== "release") {
|
|
31560
31761
|
return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished Hub release tag. Run: mmi-cli release --abort --apply`);
|
|
31561
31762
|
}
|
|
31763
|
+
if (o.retryPublish && commandName !== "release") {
|
|
31764
|
+
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli release --retry-publish <run-id> --apply --watch`);
|
|
31765
|
+
}
|
|
31766
|
+
if (o.retryPublish) {
|
|
31767
|
+
if (o.resume || o.abort) return fail("release: --retry-publish cannot be combined with --resume or --abort");
|
|
31768
|
+
if (!o.apply) return fail("release: --retry-publish requires --apply after explicit approval; nothing was written");
|
|
31769
|
+
if (o.announceSummaryFile || o.ack || o.dev) return fail("release: --retry-publish accepts only --apply, --watch, --repo and --json");
|
|
31770
|
+
if (o.repo) {
|
|
31771
|
+
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli release --retry-publish ${o.retryPublish} --apply${o.watch ? " --watch" : ""}`);
|
|
31772
|
+
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
31773
|
+
}
|
|
31774
|
+
const runId = Number.parseInt(o.retryPublish, 10);
|
|
31775
|
+
try {
|
|
31776
|
+
const result = await runReleasePublishRetry(trainApplyDeps(), runId, { approved: true, watch: o.watch });
|
|
31777
|
+
return printLine(o.json ? JSON.stringify(result, null, 2) : renderReleasePublishRetry(result));
|
|
31778
|
+
} catch (e) {
|
|
31779
|
+
return failGraceful(`release --retry-publish: ${e.message}`);
|
|
31780
|
+
}
|
|
31781
|
+
}
|
|
31562
31782
|
if (o.abort) {
|
|
31563
31783
|
if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
|
|
31564
31784
|
if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
|
|
@@ -31786,12 +32006,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
31786
32006
|
targets = resolution.targets;
|
|
31787
32007
|
}
|
|
31788
32008
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
31789
|
-
const fileMatrix = (0,
|
|
32009
|
+
const fileMatrix = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
31790
32010
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
31791
32011
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
31792
|
-
const fileContracts = (0,
|
|
32012
|
+
const fileContracts = (0, import_node_fs35.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs35.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
31793
32013
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
31794
|
-
const sanctioned = (0,
|
|
32014
|
+
const sanctioned = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
31795
32015
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
31796
32016
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
31797
32017
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -31824,16 +32044,16 @@ function directoryBytes(path2) {
|
|
|
31824
32044
|
let total = 0;
|
|
31825
32045
|
let entries;
|
|
31826
32046
|
try {
|
|
31827
|
-
entries = (0,
|
|
32047
|
+
entries = (0, import_node_fs35.readdirSync)(path2, { withFileTypes: true });
|
|
31828
32048
|
} catch {
|
|
31829
32049
|
return 0;
|
|
31830
32050
|
}
|
|
31831
32051
|
for (const entry of entries) {
|
|
31832
|
-
const child2 = (0,
|
|
32052
|
+
const child2 = (0, import_node_path34.join)(path2, entry.name);
|
|
31833
32053
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
31834
32054
|
else {
|
|
31835
32055
|
try {
|
|
31836
|
-
total += (0,
|
|
32056
|
+
total += (0, import_node_fs35.statSync)(child2).size;
|
|
31837
32057
|
} catch {
|
|
31838
32058
|
}
|
|
31839
32059
|
}
|
|
@@ -31841,25 +32061,25 @@ function directoryBytes(path2) {
|
|
|
31841
32061
|
return total;
|
|
31842
32062
|
}
|
|
31843
32063
|
function listDirEntries(dir) {
|
|
31844
|
-
return (0,
|
|
32064
|
+
return (0, import_node_fs35.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
31845
32065
|
}
|
|
31846
32066
|
function readInstalledPluginRefs(configRoot) {
|
|
31847
32067
|
const p = installedPluginsPathForConfig(configRoot);
|
|
31848
|
-
if (!(0,
|
|
32068
|
+
if (!(0, import_node_fs35.existsSync)(p)) return [];
|
|
31849
32069
|
try {
|
|
31850
|
-
return installedPluginPaths((0,
|
|
32070
|
+
return installedPluginPaths((0, import_node_fs35.readFileSync)(p, "utf8"));
|
|
31851
32071
|
} catch {
|
|
31852
32072
|
return null;
|
|
31853
32073
|
}
|
|
31854
32074
|
}
|
|
31855
32075
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
31856
32076
|
return {
|
|
31857
|
-
exists: (p) => (0,
|
|
31858
|
-
listVersionDirs: (root) => (0,
|
|
32077
|
+
exists: (p) => (0, import_node_fs35.existsSync)(p),
|
|
32078
|
+
listVersionDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
31859
32079
|
dirBytes,
|
|
31860
|
-
listStagingDirs: (root) => (0,
|
|
32080
|
+
listStagingDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
31861
32081
|
try {
|
|
31862
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
32082
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path34.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs35.statSync)(p).mtimeMs) };
|
|
31863
32083
|
} catch {
|
|
31864
32084
|
return { name: d.name, mtimeMs: Date.now() };
|
|
31865
32085
|
}
|
|
@@ -31873,10 +32093,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
31873
32093
|
return {
|
|
31874
32094
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
31875
32095
|
mtimeMs: (name) => {
|
|
31876
|
-
const p = (0,
|
|
31877
|
-
if (!(0,
|
|
32096
|
+
const p = (0, import_node_path34.join)(stagingRoot, name);
|
|
32097
|
+
if (!(0, import_node_fs35.existsSync)(p)) return null;
|
|
31878
32098
|
try {
|
|
31879
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
32099
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs35.statSync)(q).mtimeMs);
|
|
31880
32100
|
} catch {
|
|
31881
32101
|
return null;
|
|
31882
32102
|
}
|
|
@@ -31902,7 +32122,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
31902
32122
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
31903
32123
|
);
|
|
31904
32124
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
31905
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
32125
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs35.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
31906
32126
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
31907
32127
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
31908
32128
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -31965,7 +32185,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
31965
32185
|
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
31966
32186
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
31967
32187
|
if (worktreeBanner) {
|
|
31968
|
-
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn:
|
|
32188
|
+
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
31969
32189
|
bannerIo.log(worktreeBanner);
|
|
31970
32190
|
}
|
|
31971
32191
|
if (isLinkedWorktree(process.cwd())) {
|