@mutmutco/cli 3.97.0 → 3.98.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 +586 -257
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,7 +3416,7 @@ var program = new Command();
|
|
|
3416
3416
|
|
|
3417
3417
|
// src/index.ts
|
|
3418
3418
|
var import_promises11 = require("node:fs/promises");
|
|
3419
|
-
var
|
|
3419
|
+
var import_node_fs40 = require("node:fs");
|
|
3420
3420
|
var import_node_child_process18 = require("node:child_process");
|
|
3421
3421
|
|
|
3422
3422
|
// src/cli-shared.ts
|
|
@@ -6999,7 +6999,7 @@ function commandLadderHint() {
|
|
|
6999
6999
|
}
|
|
7000
7000
|
|
|
7001
7001
|
// src/index.ts
|
|
7002
|
-
var
|
|
7002
|
+
var import_node_path38 = require("node:path");
|
|
7003
7003
|
|
|
7004
7004
|
// src/merge-ci-policy.ts
|
|
7005
7005
|
function resolveMergeCiPolicy(input) {
|
|
@@ -7643,6 +7643,9 @@ var CMD_RE = /`mmi-cli ((?:[a-z][a-z-]*)(?: [a-z][a-z-]*){0,3})/g;
|
|
|
7643
7643
|
var RETIRED_RE = /\b(retired|historical|removed|deleted|gone|no longer|superseded|legacy)\b/i;
|
|
7644
7644
|
var ROOT_DOCS = ["README.md", "architecture.md"];
|
|
7645
7645
|
var SKIP_WALK = ["docs/Archive/", "docs/incidents/", "docs/research/"];
|
|
7646
|
+
function refFirstSegment(ref) {
|
|
7647
|
+
return ref.replace(/^(\.\/)+/, "").split("/")[0];
|
|
7648
|
+
}
|
|
7646
7649
|
function stripFences(markdown) {
|
|
7647
7650
|
let inFence = false;
|
|
7648
7651
|
return markdown.split(/\r?\n/).map((line) => {
|
|
@@ -7745,6 +7748,12 @@ function extractCommands(markdown) {
|
|
|
7745
7748
|
}
|
|
7746
7749
|
function checkRefs(root, deps, docs2) {
|
|
7747
7750
|
const { exists, isIgnored = () => /* @__PURE__ */ new Set() } = deps;
|
|
7751
|
+
const allFirstSegments = /* @__PURE__ */ new Set();
|
|
7752
|
+
for (const markdown of Object.values(docs2)) {
|
|
7753
|
+
for (const { ref } of extractRefs(markdown)) allFirstSegments.add(refFirstSegment(ref));
|
|
7754
|
+
}
|
|
7755
|
+
const tracked = allFirstSegments.size ? deps.trackedFirstSegments?.([...allFirstSegments]) ?? null : null;
|
|
7756
|
+
const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path12.join)(root, first));
|
|
7748
7757
|
const candidates = [];
|
|
7749
7758
|
const direct = [];
|
|
7750
7759
|
for (const [doc, markdown] of Object.entries(docs2)) {
|
|
@@ -7781,8 +7790,7 @@ function checkRefs(root, deps, docs2) {
|
|
|
7781
7790
|
}
|
|
7782
7791
|
}
|
|
7783
7792
|
for (const { ref, line } of extractRefs(markdown)) {
|
|
7784
|
-
|
|
7785
|
-
if (!exists((0, import_node_path12.join)(root, first))) continue;
|
|
7793
|
+
if (!firstVerifiable(refFirstSegment(ref))) continue;
|
|
7786
7794
|
if (!exists((0, import_node_path12.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
|
|
7787
7795
|
}
|
|
7788
7796
|
for (const { target, line, resolved, missing } of links) {
|
|
@@ -7872,18 +7880,43 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process5.exec
|
|
|
7872
7880
|
throw error;
|
|
7873
7881
|
}
|
|
7874
7882
|
}
|
|
7883
|
+
function defaultTrackedFirstSegments(root, firstSegments, exec = import_node_child_process5.execFileSync) {
|
|
7884
|
+
if (firstSegments.length === 0) return /* @__PURE__ */ new Set();
|
|
7885
|
+
try {
|
|
7886
|
+
const out = exec("git", ["ls-files", "-z", "--", ...firstSegments.map((s) => `:(literal)${s}`)], {
|
|
7887
|
+
cwd: root,
|
|
7888
|
+
encoding: "utf8",
|
|
7889
|
+
maxBuffer: CHECK_IGNORE_MAX_BUFFER
|
|
7890
|
+
});
|
|
7891
|
+
const tracked = /* @__PURE__ */ new Set();
|
|
7892
|
+
for (const path2 of out.split("\0")) {
|
|
7893
|
+
if (path2) tracked.add(path2.split("/")[0]);
|
|
7894
|
+
}
|
|
7895
|
+
return tracked;
|
|
7896
|
+
} catch (error) {
|
|
7897
|
+
if (error?.status === 128) return null;
|
|
7898
|
+
if (error?.code === "ENOENT") return null;
|
|
7899
|
+
if (error?.code === "ENOBUFS") {
|
|
7900
|
+
throw new Error(
|
|
7901
|
+
`git ls-files produced more than ${CHECK_IGNORE_MAX_BUFFER} bytes for ${firstSegments.length} segment(s) \u2014 the tracked set cannot be read, and treating it as empty would silently skip every ref (#4208)`
|
|
7902
|
+
);
|
|
7903
|
+
}
|
|
7904
|
+
throw error;
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7875
7907
|
function runDocRefs(root, deps = {}) {
|
|
7876
7908
|
const readFile9 = deps.readFile ?? readFileOrNull;
|
|
7877
7909
|
const exists = deps.exists ?? import_node_fs13.existsSync;
|
|
7878
7910
|
const listDocs = deps.listDocs ?? defaultListDocs;
|
|
7879
7911
|
const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
|
|
7912
|
+
const trackedFirstSegments = deps.trackedFirstSegments ?? ((segs) => defaultTrackedFirstSegments(root, segs));
|
|
7880
7913
|
const commandPaths = deps.commandPaths ?? null;
|
|
7881
7914
|
const walked = listDocs(root);
|
|
7882
7915
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
7883
7916
|
const docs2 = Object.fromEntries(
|
|
7884
7917
|
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
|
|
7885
7918
|
);
|
|
7886
|
-
const refResult = checkRefs(root, { exists, isIgnored }, docs2);
|
|
7919
|
+
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs2);
|
|
7887
7920
|
const findings = [
|
|
7888
7921
|
...checkPins(root, readFile9, docs2).findings,
|
|
7889
7922
|
...refResult.findings
|
|
@@ -9218,15 +9251,15 @@ function healJervCodePackageRegistration(opts = {}) {
|
|
|
9218
9251
|
return { available: true, ok: true, changed: false, version: null, detail: "skipped \u2014 no installed MMI clone carries the pi wrapper (install the Claude plugin first)" };
|
|
9219
9252
|
}
|
|
9220
9253
|
const packagePath = `${clone.path.replace(/\\/g, "/").replace(/\/+$/, "")}/${JERVCODE_WRAPPER_DIR}`;
|
|
9221
|
-
const
|
|
9222
|
-
const settings = readPiSettings(
|
|
9254
|
+
const settingsPath2 = (0, import_node_path14.join)(agentDir, "settings.json");
|
|
9255
|
+
const settings = readPiSettings(settingsPath2);
|
|
9223
9256
|
if (settings === null) {
|
|
9224
9257
|
return {
|
|
9225
9258
|
available: true,
|
|
9226
9259
|
ok: false,
|
|
9227
9260
|
changed: false,
|
|
9228
9261
|
version: clone.version,
|
|
9229
|
-
detail: `package NOT registered \u2014 ${
|
|
9262
|
+
detail: `package NOT registered \u2014 ${settingsPath2} is not a strict JSON object, so mmi left it alone; add ${packagePath} to its packages array by hand`
|
|
9230
9263
|
};
|
|
9231
9264
|
}
|
|
9232
9265
|
const current = settings ?? {};
|
|
@@ -9237,12 +9270,12 @@ function healJervCodePackageRegistration(opts = {}) {
|
|
|
9237
9270
|
}
|
|
9238
9271
|
current.packages = merged.next;
|
|
9239
9272
|
try {
|
|
9240
|
-
(0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(
|
|
9241
|
-
const tmp = `${
|
|
9273
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(settingsPath2), { recursive: true });
|
|
9274
|
+
const tmp = `${settingsPath2}.tmp-${process.pid}`;
|
|
9242
9275
|
(0, import_node_fs15.writeFileSync)(tmp, `${JSON.stringify(current, null, 2)}
|
|
9243
9276
|
`, "utf8");
|
|
9244
9277
|
try {
|
|
9245
|
-
(0, import_node_fs15.renameSync)(tmp,
|
|
9278
|
+
(0, import_node_fs15.renameSync)(tmp, settingsPath2);
|
|
9246
9279
|
} catch (renameError) {
|
|
9247
9280
|
(0, import_node_fs15.rmSync)(tmp, { force: true });
|
|
9248
9281
|
throw renameError;
|
|
@@ -12011,6 +12044,12 @@ async function auditRepoCi(repo, deps) {
|
|
|
12011
12044
|
label: "delete_branch_on_merge enabled",
|
|
12012
12045
|
detail: info.delete_branch_on_merge === true ? void 0 : "false or unavailable"
|
|
12013
12046
|
});
|
|
12047
|
+
checks.push({
|
|
12048
|
+
ok: info.has_wiki === false,
|
|
12049
|
+
label: "has_wiki disabled",
|
|
12050
|
+
detail: info.has_wiki === false ? void 0 : "wikis are retired org-wide",
|
|
12051
|
+
remediation: `gh api -X PATCH repos/${repo} -F has_wiki=false`
|
|
12052
|
+
});
|
|
12014
12053
|
const hasCanonicalGateWorkflow = repoClass === "hub" ? true : repoClass === "content" ? true : await contentExists(deps, repo, baseBranch, PRODUCT_GATE_PATH);
|
|
12015
12054
|
let prWorkflowPaths = repoClass === "deployable" && hasCanonicalGateWorkflow ? [PRODUCT_GATE_PATH] : [];
|
|
12016
12055
|
if (repoClass === "deployable" && !hasCanonicalGateWorkflow) {
|
|
@@ -12269,7 +12308,7 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
|
|
|
12269
12308
|
const applied = [];
|
|
12270
12309
|
const skipped = [];
|
|
12271
12310
|
const errors = [];
|
|
12272
|
-
const mergeChecks = report.checks.filter((c) => c.label.startsWith("allow_") || c.label.startsWith("delete_branch"));
|
|
12311
|
+
const mergeChecks = report.checks.filter((c) => c.label.startsWith("allow_") || c.label.startsWith("delete_branch") || c.label.startsWith("has_wiki"));
|
|
12273
12312
|
const needsPatch = mergeChecks.some((c) => !c.ok);
|
|
12274
12313
|
if (!needsPatch) {
|
|
12275
12314
|
skipped.push("merge settings already canonical");
|
|
@@ -12280,10 +12319,11 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
|
|
|
12280
12319
|
body: {
|
|
12281
12320
|
allow_auto_merge: true,
|
|
12282
12321
|
allow_squash_merge: true,
|
|
12283
|
-
delete_branch_on_merge: true
|
|
12322
|
+
delete_branch_on_merge: true,
|
|
12323
|
+
has_wiki: false
|
|
12284
12324
|
}
|
|
12285
12325
|
});
|
|
12286
|
-
applied.push("allow_auto_merge, allow_squash_merge, delete_branch_on_merge");
|
|
12326
|
+
applied.push("allow_auto_merge, allow_squash_merge, delete_branch_on_merge, has_wiki=false");
|
|
12287
12327
|
} catch (e) {
|
|
12288
12328
|
errors.push(e.message);
|
|
12289
12329
|
}
|
|
@@ -12466,7 +12506,7 @@ async function parkProductRuleset(repo, deps) {
|
|
|
12466
12506
|
return result;
|
|
12467
12507
|
}
|
|
12468
12508
|
function reconcileOwnedFailures(report) {
|
|
12469
|
-
return report.checks.filter((check) => !check.ok).filter((check) => check.label.startsWith("allow_") || check.label.startsWith("delete_branch") || check.label.startsWith("gate workflow committed on ") || check.label.startsWith("product ruleset reference committed on ") || check.label === "product required status checks active" || check.label === RULESET_REFERENCE_MATCH_LABEL || check.label === REQUIRED_CONTEXTS_EMITTED_LABEL || check.label === TAG_ADDRESSABLE_CONTEXTS_LABEL).map((check) => check.label);
|
|
12509
|
+
return report.checks.filter((check) => !check.ok).filter((check) => check.label.startsWith("allow_") || check.label.startsWith("delete_branch") || check.label.startsWith("has_wiki") || check.label.startsWith("gate workflow committed on ") || check.label.startsWith("product ruleset reference committed on ") || check.label === "product required status checks active" || check.label === RULESET_REFERENCE_MATCH_LABEL || check.label === REQUIRED_CONTEXTS_EMITTED_LABEL || check.label === TAG_ADDRESSABLE_CONTEXTS_LABEL).map((check) => check.label);
|
|
12470
12510
|
}
|
|
12471
12511
|
async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
|
|
12472
12512
|
if (result.errors.length > 0) {
|
|
@@ -17825,18 +17865,25 @@ function consolidateCommandNamespaces(program3) {
|
|
|
17825
17865
|
move(program3, stage, "port-range");
|
|
17826
17866
|
}
|
|
17827
17867
|
|
|
17868
|
+
// src/pi-plugin-registration.ts
|
|
17869
|
+
var import_node_fs22 = require("node:fs");
|
|
17870
|
+
var import_node_path20 = require("node:path");
|
|
17871
|
+
|
|
17828
17872
|
// src/plugin-cache-prune.ts
|
|
17829
17873
|
var PLUGIN_CACHE_KEEP = 2;
|
|
17830
17874
|
var VERSION_DIR = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
|
|
17831
17875
|
function isVersionDirName(name) {
|
|
17832
17876
|
return VERSION_DIR.test(name);
|
|
17833
17877
|
}
|
|
17834
|
-
function selectPrunablePluginVersions(names, currentVersion) {
|
|
17878
|
+
function selectPrunablePluginVersions(names, currentVersion, installedVersion) {
|
|
17835
17879
|
const versions = [...new Set(names)].filter(isVersionDirName);
|
|
17836
17880
|
if (versions.length <= PLUGIN_CACHE_KEEP) return [];
|
|
17881
|
+
if (!currentVersion && !installedVersion) return [];
|
|
17837
17882
|
const newestFirst = [...versions].sort((a, b) => compareVersions(b, a));
|
|
17838
17883
|
const keep = /* @__PURE__ */ new Set();
|
|
17884
|
+
keep.add(newestFirst[0]);
|
|
17839
17885
|
if (currentVersion && versions.includes(currentVersion)) keep.add(currentVersion);
|
|
17886
|
+
if (installedVersion && versions.includes(installedVersion)) keep.add(installedVersion);
|
|
17840
17887
|
for (const v of newestFirst) {
|
|
17841
17888
|
if (keep.size >= PLUGIN_CACHE_KEEP) break;
|
|
17842
17889
|
keep.add(v);
|
|
@@ -17964,7 +18011,7 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
|
|
|
17964
18011
|
return absent;
|
|
17965
18012
|
}
|
|
17966
18013
|
const versions = names.filter(isVersionDirName);
|
|
17967
|
-
const prune = selectPrunablePluginVersions(versions, running);
|
|
18014
|
+
const prune = selectPrunablePluginVersions(versions, running, opts.installedVersion);
|
|
17968
18015
|
const pruneSet = new Set(prune);
|
|
17969
18016
|
const keep = [...versions].sort((a, b) => compareVersions(b, a)).filter((v) => !pruneSet.has(v));
|
|
17970
18017
|
const bytes = opts.withBytes ? prune.reduce((sum, v) => sum + deps.dirBytes(`${cacheRoot}/${v}`), 0) : 0;
|
|
@@ -18063,6 +18110,52 @@ function renderPluginCachePlan(plan, applied) {
|
|
|
18063
18110
|
return lines.join("\n");
|
|
18064
18111
|
}
|
|
18065
18112
|
|
|
18113
|
+
// src/pi-plugin-registration.ts
|
|
18114
|
+
function isMmiPiPackage(entry) {
|
|
18115
|
+
return /[\\/]mutmutco[\\/]mmi[\\/][^\\/]+[\\/]\.pi-plugin\/?$/.test(entry);
|
|
18116
|
+
}
|
|
18117
|
+
function expectedPiPluginPath(home, env, installedVersion) {
|
|
18118
|
+
const root = env.CLAUDE_PLUGIN_ROOT?.trim();
|
|
18119
|
+
if (root && /[\\/]mutmutco[\\/]mmi[\\/]/.test(root)) return (0, import_node_path20.join)(root, ".pi-plugin");
|
|
18120
|
+
const version = installedVersion ?? runningPluginVersion(env);
|
|
18121
|
+
if (!version) return void 0;
|
|
18122
|
+
return (0, import_node_path20.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi", version, ".pi-plugin");
|
|
18123
|
+
}
|
|
18124
|
+
function settingsPath(home) {
|
|
18125
|
+
return (0, import_node_path20.join)(home, ".pi", "agent", "settings.json");
|
|
18126
|
+
}
|
|
18127
|
+
function readPiPluginState(home, env, installedVersion) {
|
|
18128
|
+
if (!(0, import_node_fs22.existsSync)((0, import_node_path20.join)(home, ".pi", "agent"))) return void 0;
|
|
18129
|
+
const expectedPath = expectedPiPluginPath(home, env, installedVersion);
|
|
18130
|
+
if (!expectedPath || !(0, import_node_fs22.existsSync)(expectedPath)) return void 0;
|
|
18131
|
+
const file = settingsPath(home);
|
|
18132
|
+
if (!(0, import_node_fs22.existsSync)(file)) return { expectedPath, settingsReadable: true };
|
|
18133
|
+
try {
|
|
18134
|
+
const parsed = JSON.parse((0, import_node_fs22.readFileSync)(file, "utf8"));
|
|
18135
|
+
const packages = Array.isArray(parsed.packages) ? parsed.packages : [];
|
|
18136
|
+
return { expectedPath, registeredPath: packages.find((p) => typeof p === "string" && isMmiPiPackage(p)), settingsReadable: true };
|
|
18137
|
+
} catch {
|
|
18138
|
+
return { expectedPath, settingsReadable: false };
|
|
18139
|
+
}
|
|
18140
|
+
}
|
|
18141
|
+
function healPiPluginRegistration(home, env, installedVersion) {
|
|
18142
|
+
const state = readPiPluginState(home, env, installedVersion);
|
|
18143
|
+
if (!state) return { ok: false, detail: "no ~/.pi/agent or no installed .pi-plugin payload" };
|
|
18144
|
+
if (!state.settingsReadable) return { ok: false, detail: "settings.json unreadable \u2014 nothing written (fail closed)" };
|
|
18145
|
+
const file = settingsPath(home);
|
|
18146
|
+
try {
|
|
18147
|
+
const parsed = (0, import_node_fs22.existsSync)(file) ? JSON.parse((0, import_node_fs22.readFileSync)(file, "utf8")) : {};
|
|
18148
|
+
const packages = Array.isArray(parsed.packages) ? parsed.packages : [];
|
|
18149
|
+
const next = packages.filter((p) => !(typeof p === "string" && isMmiPiPackage(p)));
|
|
18150
|
+
next.push(state.expectedPath);
|
|
18151
|
+
(0, import_node_fs22.writeFileSync)(file, `${JSON.stringify({ ...parsed, packages: next }, null, 2)}
|
|
18152
|
+
`);
|
|
18153
|
+
return { ok: true, detail: state.registeredPath ? `replaced ${state.registeredPath}` : `registered ${state.expectedPath}` };
|
|
18154
|
+
} catch (e) {
|
|
18155
|
+
return { ok: false, detail: e.message };
|
|
18156
|
+
}
|
|
18157
|
+
}
|
|
18158
|
+
|
|
18066
18159
|
// src/skill-lesson.ts
|
|
18067
18160
|
var SKILL_LESSON_LABEL = "skill-lesson";
|
|
18068
18161
|
var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage", "worktree"];
|
|
@@ -19490,8 +19583,8 @@ function renderAccessReport(report) {
|
|
|
19490
19583
|
// src/repo-index.ts
|
|
19491
19584
|
var import_node_crypto4 = require("node:crypto");
|
|
19492
19585
|
var import_node_child_process11 = require("node:child_process");
|
|
19493
|
-
var
|
|
19494
|
-
var
|
|
19586
|
+
var import_node_fs23 = require("node:fs");
|
|
19587
|
+
var import_node_path21 = require("node:path");
|
|
19495
19588
|
var REPO_INDEX_SCHEMA = 1;
|
|
19496
19589
|
var HARD_DENY = [
|
|
19497
19590
|
/(^|\/)\.env(\.|$)/i,
|
|
@@ -19616,11 +19709,11 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
19616
19709
|
}
|
|
19617
19710
|
for (const rel of readmes) {
|
|
19618
19711
|
if (isHardDeniedPath(rel)) continue;
|
|
19619
|
-
const abs = (0,
|
|
19620
|
-
if (!(0,
|
|
19712
|
+
const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
|
|
19713
|
+
if (!(0, import_node_fs23.existsSync)(abs)) continue;
|
|
19621
19714
|
let text;
|
|
19622
19715
|
try {
|
|
19623
|
-
text = (0,
|
|
19716
|
+
text = (0, import_node_fs23.readFileSync)(abs, "utf8");
|
|
19624
19717
|
} catch {
|
|
19625
19718
|
continue;
|
|
19626
19719
|
}
|
|
@@ -19633,7 +19726,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
19633
19726
|
return hints;
|
|
19634
19727
|
}
|
|
19635
19728
|
function toPosix(p) {
|
|
19636
|
-
return p.split(
|
|
19729
|
+
return p.split(import_node_path21.sep).join("/");
|
|
19637
19730
|
}
|
|
19638
19731
|
function listCandidatePaths(cwd, exec = import_node_child_process11.execFileSync) {
|
|
19639
19732
|
try {
|
|
@@ -19655,11 +19748,11 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
19655
19748
|
for (const rel of candidates) {
|
|
19656
19749
|
if (ignored.has(rel)) continue;
|
|
19657
19750
|
if (isHardDeniedPath(rel)) continue;
|
|
19658
|
-
const abs = (0,
|
|
19659
|
-
if (!(0,
|
|
19751
|
+
const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
|
|
19752
|
+
if (!(0, import_node_fs23.existsSync)(abs)) continue;
|
|
19660
19753
|
let text;
|
|
19661
19754
|
try {
|
|
19662
|
-
text = (0,
|
|
19755
|
+
text = (0, import_node_fs23.readFileSync)(abs, "utf8");
|
|
19663
19756
|
} catch {
|
|
19664
19757
|
continue;
|
|
19665
19758
|
}
|
|
@@ -19684,16 +19777,16 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
19684
19777
|
entries
|
|
19685
19778
|
};
|
|
19686
19779
|
const store = repoIndexStorePath(cwd);
|
|
19687
|
-
(0,
|
|
19688
|
-
(0,
|
|
19780
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
|
|
19781
|
+
(0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
|
|
19689
19782
|
`, "utf8");
|
|
19690
19783
|
return projection;
|
|
19691
19784
|
}
|
|
19692
19785
|
function loadRepoIndex(cwd) {
|
|
19693
19786
|
const store = repoIndexStorePath(cwd);
|
|
19694
|
-
if (!(0,
|
|
19787
|
+
if (!(0, import_node_fs23.existsSync)(store)) return null;
|
|
19695
19788
|
try {
|
|
19696
|
-
const raw = JSON.parse((0,
|
|
19789
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(store, "utf8"));
|
|
19697
19790
|
if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
|
|
19698
19791
|
return raw;
|
|
19699
19792
|
} catch {
|
|
@@ -19765,7 +19858,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process11.execFileSync) {
|
|
|
19765
19858
|
if (m?.[1]) return m[1].toLowerCase();
|
|
19766
19859
|
} catch {
|
|
19767
19860
|
}
|
|
19768
|
-
return ((0,
|
|
19861
|
+
return ((0, import_node_path21.basename)(cwd) || "local").toLowerCase();
|
|
19769
19862
|
}
|
|
19770
19863
|
|
|
19771
19864
|
// src/repo-index-cloud-client.ts
|
|
@@ -19905,9 +19998,9 @@ async function gcRepoIndexCloud(deps) {
|
|
|
19905
19998
|
}
|
|
19906
19999
|
|
|
19907
20000
|
// src/repo-index-sync.ts
|
|
19908
|
-
var
|
|
20001
|
+
var import_node_fs24 = require("node:fs");
|
|
19909
20002
|
var import_node_os9 = require("node:os");
|
|
19910
|
-
var
|
|
20003
|
+
var import_node_path22 = require("node:path");
|
|
19911
20004
|
var import_node_child_process12 = require("node:child_process");
|
|
19912
20005
|
var MAX_EMBED_BACKFILL_ROUNDS = 40;
|
|
19913
20006
|
function normalizeRepo(raw) {
|
|
@@ -19951,7 +20044,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
19951
20044
|
const failed = [];
|
|
19952
20045
|
const skipped = [];
|
|
19953
20046
|
for (const repo of repos) {
|
|
19954
|
-
const dir = (0,
|
|
20047
|
+
const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
|
|
19955
20048
|
try {
|
|
19956
20049
|
shallowClone(repo, dir, opts.githubToken);
|
|
19957
20050
|
const built = rebuildRepoIndex(dir, repo);
|
|
@@ -20001,7 +20094,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
20001
20094
|
failed.push({ repo, error: e.message });
|
|
20002
20095
|
} finally {
|
|
20003
20096
|
try {
|
|
20004
|
-
(0,
|
|
20097
|
+
(0, import_node_fs24.rmSync)(dir, { recursive: true, force: true });
|
|
20005
20098
|
} catch {
|
|
20006
20099
|
}
|
|
20007
20100
|
}
|
|
@@ -20010,7 +20103,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
20010
20103
|
}
|
|
20011
20104
|
|
|
20012
20105
|
// src/repo-index-health.ts
|
|
20013
|
-
var
|
|
20106
|
+
var import_node_fs25 = require("node:fs");
|
|
20014
20107
|
|
|
20015
20108
|
// testdata/repo-index-golden-queries.json
|
|
20016
20109
|
var repo_index_golden_queries_default = {
|
|
@@ -20052,7 +20145,7 @@ function assertGoldenSuite(raw, source) {
|
|
|
20052
20145
|
function loadGoldenSuite(path2) {
|
|
20053
20146
|
let text;
|
|
20054
20147
|
try {
|
|
20055
|
-
text = (0,
|
|
20148
|
+
text = (0, import_node_fs25.readFileSync)(path2, "utf8");
|
|
20056
20149
|
} catch (e) {
|
|
20057
20150
|
throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
|
|
20058
20151
|
}
|
|
@@ -20196,8 +20289,8 @@ async function runRepoIndexHealth(opts) {
|
|
|
20196
20289
|
|
|
20197
20290
|
// src/spawn-policy-core.ts
|
|
20198
20291
|
var import_node_child_process13 = require("node:child_process");
|
|
20199
|
-
var
|
|
20200
|
-
var
|
|
20292
|
+
var import_node_fs26 = require("node:fs");
|
|
20293
|
+
var import_node_path23 = require("node:path");
|
|
20201
20294
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
20202
20295
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
20203
20296
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -20283,7 +20376,7 @@ function runSpawnPolicy(root) {
|
|
|
20283
20376
|
for (const file of files) {
|
|
20284
20377
|
let raw;
|
|
20285
20378
|
try {
|
|
20286
|
-
raw = (0,
|
|
20379
|
+
raw = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
|
|
20287
20380
|
} catch {
|
|
20288
20381
|
continue;
|
|
20289
20382
|
}
|
|
@@ -20301,8 +20394,8 @@ function runSpawnPolicy(root) {
|
|
|
20301
20394
|
|
|
20302
20395
|
// src/test-policy-core.ts
|
|
20303
20396
|
var import_node_child_process14 = require("node:child_process");
|
|
20304
|
-
var
|
|
20305
|
-
var
|
|
20397
|
+
var import_node_fs27 = require("node:fs");
|
|
20398
|
+
var import_node_path24 = require("node:path");
|
|
20306
20399
|
var POLICY_FILE = "test-policy.json";
|
|
20307
20400
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
20308
20401
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -20355,7 +20448,7 @@ function isTestPath(path2) {
|
|
|
20355
20448
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
20356
20449
|
}
|
|
20357
20450
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
20358
|
-
const raw = readFile9((0,
|
|
20451
|
+
const raw = readFile9((0, import_node_path24.join)(root, POLICY_FILE));
|
|
20359
20452
|
if (raw == null) return { mandatory: [], declared: false };
|
|
20360
20453
|
try {
|
|
20361
20454
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -20365,7 +20458,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
|
20365
20458
|
}
|
|
20366
20459
|
function readFileOrNull2(path2) {
|
|
20367
20460
|
try {
|
|
20368
|
-
return (0,
|
|
20461
|
+
return (0, import_node_fs27.readFileSync)(path2, "utf8");
|
|
20369
20462
|
} catch {
|
|
20370
20463
|
return null;
|
|
20371
20464
|
}
|
|
@@ -20392,12 +20485,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
20392
20485
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
20393
20486
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
20394
20487
|
}
|
|
20395
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
20396
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
20488
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
|
|
20489
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path24.join)(root, p)));
|
|
20397
20490
|
}
|
|
20398
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
20491
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
|
|
20399
20492
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
20400
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
20493
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path24.join)(root, p)));
|
|
20401
20494
|
}
|
|
20402
20495
|
function evaluate(changed, policy, present = () => false) {
|
|
20403
20496
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -20579,13 +20672,13 @@ function changedFilesSince(base, cwd) {
|
|
|
20579
20672
|
}
|
|
20580
20673
|
function runTestPolicy(root, deps = {}) {
|
|
20581
20674
|
const policy = deps.policy ?? loadPolicy(root);
|
|
20582
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
20675
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs27.existsSync)(path2));
|
|
20583
20676
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
20584
20677
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
20585
20678
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
20586
20679
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
20587
20680
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
20588
|
-
const present = (path2) => exists((0,
|
|
20681
|
+
const present = (path2) => exists((0, import_node_path24.join)(root, path2));
|
|
20589
20682
|
const removedByThisDiff = removedPaths(changed);
|
|
20590
20683
|
const staleFindings = [];
|
|
20591
20684
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -20741,8 +20834,8 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
20741
20834
|
}
|
|
20742
20835
|
|
|
20743
20836
|
// src/project-info-sync.ts
|
|
20744
|
-
var
|
|
20745
|
-
var
|
|
20837
|
+
var import_node_fs28 = require("node:fs");
|
|
20838
|
+
var import_node_path25 = require("node:path");
|
|
20746
20839
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
20747
20840
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
20748
20841
|
projectV2 { id }
|
|
@@ -20787,14 +20880,14 @@ function sharedName(entries, fallback) {
|
|
|
20787
20880
|
}
|
|
20788
20881
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
20789
20882
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
20790
|
-
const readmePath = (0,
|
|
20791
|
-
if (!(0,
|
|
20883
|
+
const readmePath = (0, import_node_path25.join)(repoRoot2, "README.md");
|
|
20884
|
+
if (!(0, import_node_fs28.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
20792
20885
|
const entries = entriesFor(project2, projects);
|
|
20793
20886
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
20794
20887
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
20795
20888
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
20796
20889
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
20797
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
20890
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs28.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
20798
20891
|
const lines = [
|
|
20799
20892
|
`# ${projectName}`,
|
|
20800
20893
|
"",
|
|
@@ -20813,8 +20906,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
20813
20906
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
20814
20907
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
20815
20908
|
const orgDocs = [
|
|
20816
|
-
(0,
|
|
20817
|
-
(0,
|
|
20909
|
+
(0, import_node_fs28.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
20910
|
+
(0, import_node_fs28.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
20818
20911
|
].filter(Boolean);
|
|
20819
20912
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
20820
20913
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -21673,8 +21766,8 @@ function writeError(res) {
|
|
|
21673
21766
|
}
|
|
21674
21767
|
|
|
21675
21768
|
// src/secrets-commands.ts
|
|
21676
|
-
var
|
|
21677
|
-
var
|
|
21769
|
+
var import_node_fs29 = require("node:fs");
|
|
21770
|
+
var import_node_path26 = require("node:path");
|
|
21678
21771
|
var import_node_os10 = require("node:os");
|
|
21679
21772
|
|
|
21680
21773
|
// src/project-runtime.ts
|
|
@@ -21798,18 +21891,18 @@ function collectMap(value, previous = []) {
|
|
|
21798
21891
|
return [...previous, value];
|
|
21799
21892
|
}
|
|
21800
21893
|
async function decryptRailsCredentials(input) {
|
|
21801
|
-
const appDir = (0,
|
|
21894
|
+
const appDir = (0, import_node_path26.resolve)(input.appDir ?? process.cwd());
|
|
21802
21895
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
21803
21896
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
21804
|
-
const credentialsPath = (0,
|
|
21805
|
-
const masterKeyPath = (0,
|
|
21897
|
+
const credentialsPath = (0, import_node_path26.resolve)(appDir, credentialsFile);
|
|
21898
|
+
const masterKeyPath = (0, import_node_path26.resolve)(appDir, masterKeyFile);
|
|
21806
21899
|
const env = {
|
|
21807
21900
|
...process.env,
|
|
21808
21901
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
21809
21902
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
21810
21903
|
};
|
|
21811
|
-
if ((0,
|
|
21812
|
-
env.RAILS_MASTER_KEY = (0,
|
|
21904
|
+
if ((0, import_node_fs29.existsSync)(masterKeyPath)) {
|
|
21905
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs29.readFileSync)(masterKeyPath, "utf8").trim();
|
|
21813
21906
|
}
|
|
21814
21907
|
const script = [
|
|
21815
21908
|
'require "json"',
|
|
@@ -21819,9 +21912,9 @@ async function decryptRailsCredentials(input) {
|
|
|
21819
21912
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
21820
21913
|
"puts JSON.generate(config.config)"
|
|
21821
21914
|
].join("\n");
|
|
21822
|
-
const scriptDir = (0,
|
|
21823
|
-
const scriptPath = (0,
|
|
21824
|
-
(0,
|
|
21915
|
+
const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
|
|
21916
|
+
const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
|
|
21917
|
+
(0, import_node_fs29.writeFileSync)(scriptPath, script, "utf8");
|
|
21825
21918
|
try {
|
|
21826
21919
|
const args = ["exec", "ruby", scriptPath];
|
|
21827
21920
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -21833,7 +21926,7 @@ async function decryptRailsCredentials(input) {
|
|
|
21833
21926
|
});
|
|
21834
21927
|
return JSON.parse(stdout);
|
|
21835
21928
|
} finally {
|
|
21836
|
-
(0,
|
|
21929
|
+
(0, import_node_fs29.rmSync)(scriptDir, { recursive: true, force: true });
|
|
21837
21930
|
}
|
|
21838
21931
|
}
|
|
21839
21932
|
async function readSecretStdin() {
|
|
@@ -21923,7 +22016,7 @@ function registerSecretsCommands(program3) {
|
|
|
21923
22016
|
let body;
|
|
21924
22017
|
if (o.file) {
|
|
21925
22018
|
try {
|
|
21926
|
-
body = (0,
|
|
22019
|
+
body = (0, import_node_fs29.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
|
|
21927
22020
|
} catch (e) {
|
|
21928
22021
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
21929
22022
|
}
|
|
@@ -22028,7 +22121,7 @@ function registerSecretsCommands(program3) {
|
|
|
22028
22121
|
{
|
|
22029
22122
|
...d,
|
|
22030
22123
|
decryptRailsCredentials,
|
|
22031
|
-
removeFile: (path2) => (0,
|
|
22124
|
+
removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
|
|
22032
22125
|
},
|
|
22033
22126
|
{
|
|
22034
22127
|
repo: o.repo,
|
|
@@ -22192,7 +22285,7 @@ async function activateAppActor(commandPath3, env, mint) {
|
|
|
22192
22285
|
}
|
|
22193
22286
|
|
|
22194
22287
|
// src/box-commands.ts
|
|
22195
|
-
var
|
|
22288
|
+
var import_node_fs30 = require("node:fs");
|
|
22196
22289
|
|
|
22197
22290
|
// src/box.ts
|
|
22198
22291
|
var BOX_KEYS = {
|
|
@@ -22395,7 +22488,7 @@ function registerBoxCommands(program3) {
|
|
|
22395
22488
|
}
|
|
22396
22489
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
22397
22490
|
else if (o.ssh && o.script) {
|
|
22398
|
-
(0,
|
|
22491
|
+
(0, import_node_fs30.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
22399
22492
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
22400
22493
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
22401
22494
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -23084,7 +23177,7 @@ function registerSchedulesCommands(program3) {
|
|
|
23084
23177
|
|
|
23085
23178
|
// src/file-lock.ts
|
|
23086
23179
|
var import_promises5 = require("node:fs/promises");
|
|
23087
|
-
var
|
|
23180
|
+
var import_node_path27 = require("node:path");
|
|
23088
23181
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
23089
23182
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
23090
23183
|
var FileLockBusyError = class extends Error {
|
|
@@ -23169,7 +23262,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
23169
23262
|
}
|
|
23170
23263
|
async function withFileLock(lockPath, opts, fn) {
|
|
23171
23264
|
const resolved = resolveFileLockOpts(opts);
|
|
23172
|
-
await (0, import_promises5.mkdir)((0,
|
|
23265
|
+
await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
23173
23266
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
23174
23267
|
try {
|
|
23175
23268
|
return await fn();
|
|
@@ -23180,7 +23273,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
23180
23273
|
|
|
23181
23274
|
// src/schedules-lift-command.ts
|
|
23182
23275
|
var import_promises6 = require("node:fs/promises");
|
|
23183
|
-
var
|
|
23276
|
+
var import_node_path28 = require("node:path");
|
|
23184
23277
|
|
|
23185
23278
|
// src/schedules-lift.ts
|
|
23186
23279
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -23286,7 +23379,7 @@ async function readWorkflowFiles(dir) {
|
|
|
23286
23379
|
const files = [];
|
|
23287
23380
|
for (const name of names.sort()) {
|
|
23288
23381
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
23289
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0,
|
|
23382
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path28.join)(dir, name), "utf8") });
|
|
23290
23383
|
}
|
|
23291
23384
|
return files;
|
|
23292
23385
|
}
|
|
@@ -23828,9 +23921,9 @@ function registerQueryCommands(program3) {
|
|
|
23828
23921
|
}
|
|
23829
23922
|
|
|
23830
23923
|
// src/bootstrap-commands.ts
|
|
23831
|
-
var
|
|
23924
|
+
var import_node_fs31 = require("node:fs");
|
|
23832
23925
|
var import_node_os11 = require("node:os");
|
|
23833
|
-
var
|
|
23926
|
+
var import_node_path29 = require("node:path");
|
|
23834
23927
|
|
|
23835
23928
|
// src/bootstrap-drift.ts
|
|
23836
23929
|
function byteComparableSeeds(manifest, cls) {
|
|
@@ -24102,6 +24195,11 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
24102
24195
|
const repoInfo = await restJson3(deps, `repos/${repo}`, {});
|
|
24103
24196
|
checks.push({ ok: Boolean(repoInfo.default_branch), label: "repo exists" });
|
|
24104
24197
|
checks.push({ ok: repoInfo.default_branch === baseBranch, label: `default branch is ${baseBranch}`, detail: repoInfo.default_branch || "missing" });
|
|
24198
|
+
checks.push({
|
|
24199
|
+
ok: repoInfo.has_wiki === false,
|
|
24200
|
+
label: "has_wiki disabled",
|
|
24201
|
+
detail: repoInfo.has_wiki === false ? void 0 : `wikis are retired org-wide \u2014 gh api -X PATCH repos/${repo} -F has_wiki=false`
|
|
24202
|
+
});
|
|
24105
24203
|
const branchList = await restPagedJson2(deps, `repos/${repo}/branches`, []);
|
|
24106
24204
|
const branchNames = new Set(branchList.map((b) => b.name));
|
|
24107
24205
|
for (const branch of branchesWanted) {
|
|
@@ -24543,13 +24641,13 @@ function registerBootstrapCommands(program3) {
|
|
|
24543
24641
|
client: defaultGitHubClient(),
|
|
24544
24642
|
projectMeta: meta,
|
|
24545
24643
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
24546
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
24644
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs31.existsSync)(path2) ? (0, import_node_fs31.readFileSync)(path2, "utf8") : null,
|
|
24547
24645
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
24548
24646
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
24549
24647
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
24550
24648
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
24551
24649
|
// sanction, which is the pre-#3664 behaviour.
|
|
24552
|
-
sanctionedAdmins: (0,
|
|
24650
|
+
sanctionedAdmins: (0, import_node_fs31.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs31.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
24553
24651
|
requiredGcpApis: (() => {
|
|
24554
24652
|
const v = meta?.requiredGcpApis;
|
|
24555
24653
|
if (Array.isArray(v)) return v;
|
|
@@ -24602,12 +24700,12 @@ function registerBootstrapCommands(program3) {
|
|
|
24602
24700
|
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 () => {
|
|
24603
24701
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
24604
24702
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
24605
|
-
if (!(0,
|
|
24606
|
-
const manifest = loadBootstrapSeeds((0,
|
|
24703
|
+
if (!(0, import_node_fs31.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`);
|
|
24704
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs31.readFileSync)(manifestPath, "utf8"));
|
|
24607
24705
|
const hubContents = /* @__PURE__ */ new Map();
|
|
24608
24706
|
for (const s of manifest.seeds) {
|
|
24609
24707
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
24610
|
-
hubContents.set(s.target, (0,
|
|
24708
|
+
hubContents.set(s.target, (0, import_node_fs31.existsSync)(s.target) ? (0, import_node_fs31.readFileSync)(s.target, "utf8") : null);
|
|
24611
24709
|
}
|
|
24612
24710
|
let targets;
|
|
24613
24711
|
let classOf = (_repo) => "deployable";
|
|
@@ -24684,8 +24782,8 @@ function registerBootstrapCommands(program3) {
|
|
|
24684
24782
|
return fail(`bootstrap apply: ${e.message}`);
|
|
24685
24783
|
}
|
|
24686
24784
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
24687
|
-
if (!(0,
|
|
24688
|
-
const manifest = loadBootstrapSeeds((0,
|
|
24785
|
+
if (!(0, import_node_fs31.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`);
|
|
24786
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs31.readFileSync)(manifestPath, "utf8"));
|
|
24689
24787
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
24690
24788
|
const slug = parsedRepo.slug;
|
|
24691
24789
|
const onlyTarget = o.only.trim();
|
|
@@ -24696,16 +24794,16 @@ function registerBootstrapCommands(program3) {
|
|
|
24696
24794
|
${known}`);
|
|
24697
24795
|
}
|
|
24698
24796
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
24699
|
-
const readFile9 = (p) => (0,
|
|
24797
|
+
const readFile9 = (p) => (0, import_node_fs31.existsSync)(p) ? (0, import_node_fs31.readFileSync)(p, "utf8") : null;
|
|
24700
24798
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
24701
24799
|
const putSeed = async (target, content, ref, sha) => {
|
|
24702
|
-
const tmp = (0,
|
|
24703
|
-
(0,
|
|
24800
|
+
const tmp = (0, import_node_path29.join)((0, import_node_os11.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
24801
|
+
(0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
24704
24802
|
try {
|
|
24705
24803
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
24706
24804
|
} finally {
|
|
24707
24805
|
try {
|
|
24708
|
-
(0,
|
|
24806
|
+
(0, import_node_fs31.unlinkSync)(tmp);
|
|
24709
24807
|
} catch {
|
|
24710
24808
|
}
|
|
24711
24809
|
}
|
|
@@ -24879,6 +24977,14 @@ function registerBootstrapCommands(program3) {
|
|
|
24879
24977
|
});
|
|
24880
24978
|
applied.push(autoMergeEnabled ? `seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge enabled)` : `seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge refused \u2014 PR is already clean. Land it: mmi-cli pr land <n> --repo ${repo})`);
|
|
24881
24979
|
}
|
|
24980
|
+
if (o.execute && !onlyTarget) {
|
|
24981
|
+
try {
|
|
24982
|
+
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-F", "has_wiki=false"]);
|
|
24983
|
+
applied.push("has_wiki=false (wikis retired org-wide)");
|
|
24984
|
+
} catch (e) {
|
|
24985
|
+
applied.push(`has_wiki=false (failed: ${e.message})`);
|
|
24986
|
+
}
|
|
24987
|
+
}
|
|
24882
24988
|
if (o.execute && !onlyTarget && o.class === "deployable") {
|
|
24883
24989
|
try {
|
|
24884
24990
|
await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-f", "delete_branch_on_merge=true"]);
|
|
@@ -24962,12 +25068,12 @@ LIVE apply to ${repo}:
|
|
|
24962
25068
|
}
|
|
24963
25069
|
|
|
24964
25070
|
// src/stage-commands.ts
|
|
24965
|
-
var
|
|
24966
|
-
var
|
|
25071
|
+
var import_node_fs33 = require("node:fs");
|
|
25072
|
+
var import_node_path31 = require("node:path");
|
|
24967
25073
|
|
|
24968
25074
|
// src/port-registry.ts
|
|
24969
|
-
var
|
|
24970
|
-
var
|
|
25075
|
+
var import_node_fs32 = require("node:fs");
|
|
25076
|
+
var import_node_path30 = require("node:path");
|
|
24971
25077
|
|
|
24972
25078
|
// ../infra/port-geometry.mjs
|
|
24973
25079
|
var PORT_BLOCK = 100;
|
|
@@ -24981,8 +25087,8 @@ function nextPortBlock(registry2) {
|
|
|
24981
25087
|
return [base, base + PORT_SPAN];
|
|
24982
25088
|
}
|
|
24983
25089
|
function loadPortRegistry(path2) {
|
|
24984
|
-
if (!(0,
|
|
24985
|
-
const raw = JSON.parse((0,
|
|
25090
|
+
if (!(0, import_node_fs32.existsSync)(path2)) return {};
|
|
25091
|
+
const raw = JSON.parse((0, import_node_fs32.readFileSync)(path2, "utf8"));
|
|
24986
25092
|
const out = {};
|
|
24987
25093
|
for (const [key, value] of Object.entries(raw)) {
|
|
24988
25094
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -24996,9 +25102,9 @@ function ensurePortRange(repo, path2) {
|
|
|
24996
25102
|
const existing = registry2[repo];
|
|
24997
25103
|
if (existing) return existing;
|
|
24998
25104
|
const range = nextPortBlock(registry2);
|
|
24999
|
-
const raw = (0,
|
|
25105
|
+
const raw = (0, import_node_fs32.existsSync)(path2) ? JSON.parse((0, import_node_fs32.readFileSync)(path2, "utf8")) : {};
|
|
25000
25106
|
raw[repo] = range;
|
|
25001
|
-
(0,
|
|
25107
|
+
(0, import_node_fs32.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
25002
25108
|
return range;
|
|
25003
25109
|
}
|
|
25004
25110
|
function portCursorSeed(registry2) {
|
|
@@ -25020,22 +25126,22 @@ function existingPortRange(repo, registry2) {
|
|
|
25020
25126
|
return registry2[repo] ?? null;
|
|
25021
25127
|
}
|
|
25022
25128
|
function portRangeInfraAt(root, source) {
|
|
25023
|
-
const registryPath = (0,
|
|
25024
|
-
const ddbScriptPath = (0,
|
|
25025
|
-
if (!(0,
|
|
25129
|
+
const registryPath = (0, import_node_path30.join)(root, "infra", "port-ranges.json");
|
|
25130
|
+
const ddbScriptPath = (0, import_node_path30.join)(root, "infra", "port-ddb.mjs");
|
|
25131
|
+
if (!(0, import_node_fs32.existsSync)(registryPath) || !(0, import_node_fs32.existsSync)(ddbScriptPath)) return null;
|
|
25026
25132
|
return { root, source, registryPath, ddbScriptPath };
|
|
25027
25133
|
}
|
|
25028
25134
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
25029
25135
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
25030
25136
|
if (direct) return direct;
|
|
25031
|
-
for (let dir = cwd; ; dir = (0,
|
|
25032
|
-
const sibling = portRangeInfraAt((0,
|
|
25137
|
+
for (let dir = cwd; ; dir = (0, import_node_path30.dirname)(dir)) {
|
|
25138
|
+
const sibling = portRangeInfraAt((0, import_node_path30.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
25033
25139
|
if (sibling) return sibling;
|
|
25034
|
-
const parent = (0,
|
|
25140
|
+
const parent = (0, import_node_path30.dirname)(dir);
|
|
25035
25141
|
if (parent === dir) break;
|
|
25036
25142
|
}
|
|
25037
25143
|
if (packageDir) {
|
|
25038
|
-
const pkgRoot = (0,
|
|
25144
|
+
const pkgRoot = (0, import_node_path30.join)(packageDir, "..", "..");
|
|
25039
25145
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
25040
25146
|
if (pkgFrom) return pkgFrom;
|
|
25041
25147
|
}
|
|
@@ -25229,8 +25335,8 @@ function registerStageCommands(program3) {
|
|
|
25229
25335
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
25230
25336
|
return decideStage({
|
|
25231
25337
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
25232
|
-
hasCompose: (0,
|
|
25233
|
-
hasEnvExample: (0,
|
|
25338
|
+
hasCompose: (0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), "docker-compose.yml")),
|
|
25339
|
+
hasEnvExample: (0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), ".env.example"))
|
|
25234
25340
|
});
|
|
25235
25341
|
}
|
|
25236
25342
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -25668,9 +25774,9 @@ function registerBoardCommands(program3) {
|
|
|
25668
25774
|
}
|
|
25669
25775
|
|
|
25670
25776
|
// src/merge-cleanup.ts
|
|
25671
|
-
var
|
|
25777
|
+
var import_node_fs34 = require("node:fs");
|
|
25672
25778
|
var import_promises8 = require("node:fs/promises");
|
|
25673
|
-
var
|
|
25779
|
+
var import_node_path33 = require("node:path");
|
|
25674
25780
|
var import_node_os12 = require("node:os");
|
|
25675
25781
|
var import_node_child_process16 = require("node:child_process");
|
|
25676
25782
|
|
|
@@ -25758,7 +25864,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
25758
25864
|
|
|
25759
25865
|
// src/deferred-registry-store.ts
|
|
25760
25866
|
var import_promises7 = require("node:fs/promises");
|
|
25761
|
-
var
|
|
25867
|
+
var import_node_path32 = require("node:path");
|
|
25762
25868
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
25763
25869
|
async function atomicWrite(target, contents) {
|
|
25764
25870
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -25809,12 +25915,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
25809
25915
|
},
|
|
25810
25916
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
25811
25917
|
write: async (entries) => {
|
|
25812
|
-
await (0, import_promises7.mkdir)((0,
|
|
25918
|
+
await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
|
|
25813
25919
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
25814
25920
|
},
|
|
25815
25921
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
25816
25922
|
update: async (mutate) => {
|
|
25817
|
-
await (0, import_promises7.mkdir)((0,
|
|
25923
|
+
await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
|
|
25818
25924
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
25819
25925
|
for (; ; ) {
|
|
25820
25926
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -25962,7 +26068,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
25962
26068
|
);
|
|
25963
26069
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
25964
26070
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
25965
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
26071
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path33.dirname)((0, import_node_path33.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
25966
26072
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
25967
26073
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
25968
26074
|
const removalNow = Date.now();
|
|
@@ -25993,7 +26099,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
25993
26099
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
25994
26100
|
beforeWorktrees,
|
|
25995
26101
|
startingPath: branch.worktreePath,
|
|
25996
|
-
pathExists: (p) => (0,
|
|
26102
|
+
pathExists: (p) => (0, import_node_fs34.existsSync)(p),
|
|
25997
26103
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
25998
26104
|
teardownWorktreeStage,
|
|
25999
26105
|
deferredStore,
|
|
@@ -26021,7 +26127,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
26021
26127
|
let removalAttempted = false;
|
|
26022
26128
|
try {
|
|
26023
26129
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
26024
|
-
realpath: (path2) => (0,
|
|
26130
|
+
realpath: (path2) => (0, import_node_fs34.realpathSync)(path2)
|
|
26025
26131
|
});
|
|
26026
26132
|
if (!cleanupTarget.ok) {
|
|
26027
26133
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -26107,13 +26213,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
26107
26213
|
const commits = JSON.parse(raw).commits ?? [];
|
|
26108
26214
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
26109
26215
|
if (!body) return void 0;
|
|
26110
|
-
const dir = (0,
|
|
26111
|
-
const path2 = (0,
|
|
26112
|
-
(0,
|
|
26216
|
+
const dir = (0, import_node_fs34.mkdtempSync)((0, import_node_path33.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
|
|
26217
|
+
const path2 = (0, import_node_path33.join)(dir, "body.txt");
|
|
26218
|
+
(0, import_node_fs34.writeFileSync)(path2, `${body}
|
|
26113
26219
|
`, "utf8");
|
|
26114
26220
|
return { path: path2, cleanup: () => {
|
|
26115
26221
|
try {
|
|
26116
|
-
(0,
|
|
26222
|
+
(0, import_node_fs34.rmSync)(dir, { recursive: true, force: true });
|
|
26117
26223
|
} catch {
|
|
26118
26224
|
}
|
|
26119
26225
|
} };
|
|
@@ -26235,13 +26341,13 @@ var realWorktreeDirRemover = {
|
|
|
26235
26341
|
probe: (p) => {
|
|
26236
26342
|
let st;
|
|
26237
26343
|
try {
|
|
26238
|
-
st = (0,
|
|
26344
|
+
st = (0, import_node_fs34.lstatSync)(p);
|
|
26239
26345
|
} catch {
|
|
26240
26346
|
return null;
|
|
26241
26347
|
}
|
|
26242
26348
|
if (st.isSymbolicLink()) return "link";
|
|
26243
26349
|
try {
|
|
26244
|
-
(0,
|
|
26350
|
+
(0, import_node_fs34.readlinkSync)(p);
|
|
26245
26351
|
return "link";
|
|
26246
26352
|
} catch {
|
|
26247
26353
|
}
|
|
@@ -26249,7 +26355,7 @@ var realWorktreeDirRemover = {
|
|
|
26249
26355
|
},
|
|
26250
26356
|
readdir: (p) => {
|
|
26251
26357
|
try {
|
|
26252
|
-
return (0,
|
|
26358
|
+
return (0, import_node_fs34.readdirSync)(p);
|
|
26253
26359
|
} catch {
|
|
26254
26360
|
return [];
|
|
26255
26361
|
}
|
|
@@ -26258,9 +26364,9 @@ var realWorktreeDirRemover = {
|
|
|
26258
26364
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
26259
26365
|
detachLink: (p) => {
|
|
26260
26366
|
try {
|
|
26261
|
-
(0,
|
|
26367
|
+
(0, import_node_fs34.rmdirSync)(p);
|
|
26262
26368
|
} catch {
|
|
26263
|
-
(0,
|
|
26369
|
+
(0, import_node_fs34.unlinkSync)(p);
|
|
26264
26370
|
}
|
|
26265
26371
|
},
|
|
26266
26372
|
removeTree: (p) => (0, import_promises8.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -26293,9 +26399,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
26293
26399
|
}
|
|
26294
26400
|
}
|
|
26295
26401
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
26296
|
-
if (!(0,
|
|
26402
|
+
if (!(0, import_node_fs34.existsSync)(statePath)) return false;
|
|
26297
26403
|
try {
|
|
26298
|
-
const state = JSON.parse((0,
|
|
26404
|
+
const state = JSON.parse((0, import_node_fs34.readFileSync)(statePath, "utf8"));
|
|
26299
26405
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
26300
26406
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
26301
26407
|
} catch {
|
|
@@ -26626,9 +26732,9 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
26626
26732
|
}
|
|
26627
26733
|
|
|
26628
26734
|
// src/worktree-lifecycle-commands.ts
|
|
26629
|
-
var
|
|
26735
|
+
var import_node_fs35 = require("node:fs");
|
|
26630
26736
|
var import_promises9 = require("node:fs/promises");
|
|
26631
|
-
var
|
|
26737
|
+
var import_node_path34 = require("node:path");
|
|
26632
26738
|
var GH_TIMEOUT_MS = 2e4;
|
|
26633
26739
|
var DEFAULT_BASE = "origin/development";
|
|
26634
26740
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -26774,7 +26880,7 @@ function classifyStaleLeaks(input) {
|
|
|
26774
26880
|
var defaultOrphanDirScanDeps = {
|
|
26775
26881
|
listDirs: (root) => {
|
|
26776
26882
|
try {
|
|
26777
|
-
return (0,
|
|
26883
|
+
return (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path34.join)(root, e.name));
|
|
26778
26884
|
} catch {
|
|
26779
26885
|
return [];
|
|
26780
26886
|
}
|
|
@@ -26927,13 +27033,13 @@ function registerWorktreeCommands(program3) {
|
|
|
26927
27033
|
const detached = headBorn && !symbolicBranch;
|
|
26928
27034
|
const branch = symbolicBranch || (detached ? "HEAD" : "");
|
|
26929
27035
|
if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
|
|
26930
|
-
const gitFile = (0,
|
|
26931
|
-
const isLinked = (0,
|
|
27036
|
+
const gitFile = (0, import_node_path34.join)(wtPath, ".git");
|
|
27037
|
+
const isLinked = (0, import_node_fs35.existsSync)(gitFile) && (0, import_node_fs35.statSync)(gitFile).isFile();
|
|
26932
27038
|
if (apply && !isLinked) {
|
|
26933
27039
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
26934
27040
|
}
|
|
26935
27041
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
26936
|
-
const primaryCheckout = commonDir ? (0,
|
|
27042
|
+
const primaryCheckout = commonDir ? (0, import_node_path34.dirname)(commonDir) : wtPath;
|
|
26937
27043
|
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);
|
|
26938
27044
|
const orphan = classifyOrphanedWorktree({
|
|
26939
27045
|
branch,
|
|
@@ -27144,10 +27250,10 @@ async function gatherWorktreeContext() {
|
|
|
27144
27250
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
27145
27251
|
}
|
|
27146
27252
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
27147
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
27253
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path34.dirname)((0, import_node_path34.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
27148
27254
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
27149
27255
|
let orphanDirs = [];
|
|
27150
|
-
if ((0,
|
|
27256
|
+
if ((0, import_node_fs35.existsSync)(wtRoot)) {
|
|
27151
27257
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
27152
27258
|
...defaultOrphanDirScanDeps,
|
|
27153
27259
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -27173,7 +27279,7 @@ ${err.stderr ?? ""}`;
|
|
|
27173
27279
|
}
|
|
27174
27280
|
|
|
27175
27281
|
// src/issue-commands.ts
|
|
27176
|
-
var
|
|
27282
|
+
var import_node_fs36 = require("node:fs");
|
|
27177
27283
|
var import_node_crypto6 = require("node:crypto");
|
|
27178
27284
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
27179
27285
|
var ReparentConflictError = class extends Error {
|
|
@@ -27191,7 +27297,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
27191
27297
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
27192
27298
|
const patch = {};
|
|
27193
27299
|
let bodyChanged = false;
|
|
27194
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
27300
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs36.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
27195
27301
|
if (options.titleFile !== void 0) {
|
|
27196
27302
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
27197
27303
|
} else if (options.title !== void 0) {
|
|
@@ -27754,7 +27860,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
27754
27860
|
if (opts.batch) {
|
|
27755
27861
|
let specs;
|
|
27756
27862
|
try {
|
|
27757
|
-
const raw = (0,
|
|
27863
|
+
const raw = (0, import_node_fs36.readFileSync)(opts.batch, "utf8");
|
|
27758
27864
|
specs = JSON.parse(raw);
|
|
27759
27865
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
27760
27866
|
} catch (e) {
|
|
@@ -27828,8 +27934,8 @@ ${lines}`, {
|
|
|
27828
27934
|
}
|
|
27829
27935
|
|
|
27830
27936
|
// src/train-commands.ts
|
|
27831
|
-
var
|
|
27832
|
-
var
|
|
27937
|
+
var import_node_fs37 = require("node:fs");
|
|
27938
|
+
var import_node_path35 = require("node:path");
|
|
27833
27939
|
|
|
27834
27940
|
// src/train-status.ts
|
|
27835
27941
|
function buildTrainStatusReport(input) {
|
|
@@ -27869,7 +27975,7 @@ function formatTrainStatus(r) {
|
|
|
27869
27975
|
// src/train-commands.ts
|
|
27870
27976
|
function readRepoVersion() {
|
|
27871
27977
|
try {
|
|
27872
|
-
return JSON.parse((0,
|
|
27978
|
+
return JSON.parse((0, import_node_fs37.readFileSync)((0, import_node_path35.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
27873
27979
|
} catch {
|
|
27874
27980
|
return void 0;
|
|
27875
27981
|
}
|
|
@@ -28015,9 +28121,9 @@ function registerDeployCommands(program3) {
|
|
|
28015
28121
|
}
|
|
28016
28122
|
|
|
28017
28123
|
// src/discovery-commands.ts
|
|
28018
|
-
var
|
|
28124
|
+
var import_node_fs38 = require("node:fs");
|
|
28019
28125
|
var import_node_os13 = require("node:os");
|
|
28020
|
-
var
|
|
28126
|
+
var import_node_path36 = require("node:path");
|
|
28021
28127
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
28022
28128
|
async function collectStatus() {
|
|
28023
28129
|
const repo = await resolveRepo();
|
|
@@ -28207,8 +28313,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
28207
28313
|
}
|
|
28208
28314
|
const home = (0, import_node_os13.homedir)();
|
|
28209
28315
|
const plugin = onboardPluginGate({
|
|
28210
|
-
readKnown: () => readFileSyncSafe((0,
|
|
28211
|
-
readSettings: () => readFileSyncSafe((0,
|
|
28316
|
+
readKnown: () => readFileSyncSafe((0, import_node_path36.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs38.readFileSync),
|
|
28317
|
+
readSettings: () => readFileSyncSafe((0, import_node_path36.join)(home, ".claude", "settings.json"), import_node_fs38.readFileSync)
|
|
28212
28318
|
});
|
|
28213
28319
|
return { track, board, registry: registry2, secrets, plugin, estateCli, nextCommand };
|
|
28214
28320
|
}
|
|
@@ -29985,6 +30091,10 @@ function surfaceRestartAction(descriptor) {
|
|
|
29985
30091
|
}
|
|
29986
30092
|
|
|
29987
30093
|
// src/doctor-clean.ts
|
|
30094
|
+
function doctorSnapshotFingerprint(checks) {
|
|
30095
|
+
const rows = [...checks].sort((a, b) => `${a.id ?? a.label}`.localeCompare(`${b.id ?? b.label}`)).map((c) => [c.id ?? c.label, c.ok, c.verified ?? null, c.detail ?? null]);
|
|
30096
|
+
return JSON.stringify(rows);
|
|
30097
|
+
}
|
|
29988
30098
|
function checkGithubAuth(probe) {
|
|
29989
30099
|
const login = probe.login?.trim();
|
|
29990
30100
|
const authed = Boolean(login);
|
|
@@ -30351,7 +30461,7 @@ function gcReapable(plan) {
|
|
|
30351
30461
|
}
|
|
30352
30462
|
async function runDoctorClean(opts, io, deps) {
|
|
30353
30463
|
const full = !opts.fast && !opts.banner && !opts.preflight;
|
|
30354
|
-
const applyEnv = full
|
|
30464
|
+
const applyEnv = full;
|
|
30355
30465
|
const applyRepo = full && opts.repoWrites !== false;
|
|
30356
30466
|
const lane = {
|
|
30357
30467
|
banner: Boolean(opts.banner),
|
|
@@ -30378,11 +30488,14 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30378
30488
|
const checks = [];
|
|
30379
30489
|
let restartPending = false;
|
|
30380
30490
|
const streamed = /* @__PURE__ */ new Set();
|
|
30491
|
+
const streamedLines = /* @__PURE__ */ new Set();
|
|
30492
|
+
const alreadyShown = (c) => streamed.has(c) || streamedLines.has(renderCheckLine(c));
|
|
30381
30493
|
const worthPrinting = (c) => Boolean(opts.verbose) || !c.ok || Boolean(c.warn);
|
|
30382
30494
|
const emitNow = (check) => {
|
|
30383
30495
|
checks.push(check);
|
|
30384
|
-
if (opts.json || !worthPrinting(check)) return;
|
|
30496
|
+
if (opts.json || !streamingPass || !worthPrinting(check)) return;
|
|
30385
30497
|
streamed.add(check);
|
|
30498
|
+
streamedLines.add(renderCheckLine(check));
|
|
30386
30499
|
io.log(renderCheckLine(check));
|
|
30387
30500
|
if (opts.verbose) for (const evidence of check.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
30388
30501
|
};
|
|
@@ -30394,13 +30507,35 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30394
30507
|
};
|
|
30395
30508
|
const releasedNote = deps.releasedVersionNote?.();
|
|
30396
30509
|
let pluginHealed = false;
|
|
30510
|
+
const spentHeals = /* @__PURE__ */ new Set();
|
|
30511
|
+
let healChangedThisPass = false;
|
|
30512
|
+
const markHealChanged = () => {
|
|
30513
|
+
healChangedThisPass = true;
|
|
30514
|
+
};
|
|
30515
|
+
const traceHeal = (kind) => opts.healTrace?.(kind);
|
|
30516
|
+
const spendOnce = (kind) => {
|
|
30517
|
+
if (spentHeals.has(kind)) return false;
|
|
30518
|
+
spentHeals.add(kind);
|
|
30519
|
+
return true;
|
|
30520
|
+
};
|
|
30521
|
+
const spentRows = /* @__PURE__ */ new Map();
|
|
30397
30522
|
async function runPluginRow() {
|
|
30398
30523
|
if (!registryEvidence) return;
|
|
30524
|
+
const spentRow = spentRows.get("plugin-chain");
|
|
30525
|
+
if (spentRow) {
|
|
30526
|
+
emitNow(spentRow);
|
|
30527
|
+
if (registryEvidence.descriptor.trustOwner === "operator" && (registryEvidence.guardState === "healthy" || pluginHealed)) {
|
|
30528
|
+
const trust = checkCodexHookTrust(deps.pluginTrustState?.(), registryEvidence.descriptor.displayName);
|
|
30529
|
+
if (trust) emitNow(trust);
|
|
30530
|
+
}
|
|
30531
|
+
return;
|
|
30532
|
+
}
|
|
30399
30533
|
const diagnosis = diagnoseSurface({ ...registryEvidence, releasedVersion: released });
|
|
30400
30534
|
const repair = planSurfaceRepair(diagnosis);
|
|
30401
|
-
if (applyEnv && deps.healPlugin && repair?.supported) {
|
|
30535
|
+
if (applyEnv && deps.healPlugin && repair?.supported && spendOnce("plugin-chain")) {
|
|
30402
30536
|
const { descriptor } = registryEvidence;
|
|
30403
30537
|
healIntent(`${descriptor.displayName} plugin \u2014 healing via ${descriptor.installMechanism} (${descriptor.installLocator})`);
|
|
30538
|
+
traceHeal("plugin-chain");
|
|
30404
30539
|
const heal = await deps.healPlugin(healStep);
|
|
30405
30540
|
pluginHealed = heal.ok;
|
|
30406
30541
|
const row = buildSurfaceDoctorCheck(diagnoseSurface({
|
|
@@ -30413,7 +30548,12 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30413
30548
|
row.fix = `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes`;
|
|
30414
30549
|
}
|
|
30415
30550
|
emitNow(row);
|
|
30551
|
+
if (heal.skipped) spentHeals.delete("plugin-chain");
|
|
30416
30552
|
if (!heal.skipped) restartPending = true;
|
|
30553
|
+
if (heal.ok && !heal.skipped) {
|
|
30554
|
+
markHealChanged();
|
|
30555
|
+
spentRows.set("plugin-chain", row);
|
|
30556
|
+
}
|
|
30417
30557
|
} else if (diagnosis.state !== "skipped") {
|
|
30418
30558
|
const row = buildSurfaceDoctorCheck(diagnosis);
|
|
30419
30559
|
emitNow(row);
|
|
@@ -30425,13 +30565,18 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30425
30565
|
}
|
|
30426
30566
|
}
|
|
30427
30567
|
async function runCliRow() {
|
|
30568
|
+
const spentRow = spentRows.get("cli-self-update");
|
|
30569
|
+
if (spentRow) {
|
|
30570
|
+
emitNow(spentRow);
|
|
30571
|
+
return;
|
|
30572
|
+
}
|
|
30428
30573
|
const missing = deps.missingCliCommands?.() ?? [];
|
|
30429
30574
|
const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
|
|
30430
30575
|
const cliReport = buildVersionLagReport(cliInput);
|
|
30431
30576
|
const capabilityGap = missing.length > 0 && Boolean(cliReport.releasedVersion);
|
|
30432
30577
|
const shouldUpdate = Boolean(
|
|
30433
30578
|
applyEnv && deps.updateCli && (versionAutoUpdateAction(cliReport) === "npm" || capabilityGap)
|
|
30434
|
-
);
|
|
30579
|
+
) && spendOnce("cli-self-update");
|
|
30435
30580
|
if (!shouldUpdate) {
|
|
30436
30581
|
const cli = checkCliVersion(cliInput, releasedNote);
|
|
30437
30582
|
if (cli) {
|
|
@@ -30461,29 +30606,40 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30461
30606
|
const target = cliReport.releasedVersion;
|
|
30462
30607
|
const intent = capabilityGap && cliReport.ok ? `mmi-cli \u2014 self-updating to ${target} via npm install -g (missing commands: ${missing.join(", ")})` : `mmi-cli \u2014 self-updating ${cliReport.currentVersion} \u2192 ${target} via npm install -g`;
|
|
30463
30608
|
healIntent(intent);
|
|
30609
|
+
traceHeal("cli-self-update");
|
|
30464
30610
|
const heal = await deps.updateCli(target, healStep);
|
|
30611
|
+
if (heal.skipped) spentHeals.delete("cli-self-update");
|
|
30612
|
+
if (heal.ok && !heal.skipped) markHealChanged();
|
|
30465
30613
|
const healEvidence = [
|
|
30466
30614
|
`running: ${cliReport.currentVersion}`,
|
|
30467
30615
|
`published: ${target ?? "(unknown)"}`,
|
|
30468
30616
|
`heal: ${heal.detail}`,
|
|
30469
30617
|
...missing.length ? [`missing commands before heal: ${missing.join(", ")}`] : []
|
|
30470
30618
|
];
|
|
30471
|
-
|
|
30472
|
-
|
|
30473
|
-
|
|
30474
|
-
|
|
30475
|
-
|
|
30476
|
-
|
|
30477
|
-
|
|
30478
|
-
|
|
30479
|
-
|
|
30480
|
-
|
|
30481
|
-
|
|
30482
|
-
|
|
30483
|
-
|
|
30484
|
-
|
|
30485
|
-
|
|
30486
|
-
|
|
30619
|
+
if (heal.ok) {
|
|
30620
|
+
const healedRow = {
|
|
30621
|
+
id: "cli-version",
|
|
30622
|
+
ok: true,
|
|
30623
|
+
label: "mmi-cli",
|
|
30624
|
+
detail: capabilityGap && cliReport.ok ? `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g (was missing: ${missing.join(", ")}); the next mmi-cli invocation runs the new version` : `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g; the next mmi-cli invocation runs the new version`,
|
|
30625
|
+
verbose: healEvidence
|
|
30626
|
+
};
|
|
30627
|
+
spentRows.set("cli-self-update", healedRow);
|
|
30628
|
+
emitNow(healedRow);
|
|
30629
|
+
return;
|
|
30630
|
+
}
|
|
30631
|
+
emitNow(
|
|
30632
|
+
{
|
|
30633
|
+
id: "cli-version",
|
|
30634
|
+
ok: false,
|
|
30635
|
+
label: "mmi-cli",
|
|
30636
|
+
detail: capabilityGap && cliReport.ok ? `missing commands: ${missing.join(", ")}` : `${cliReport.currentVersion} \u2192 ${target}`,
|
|
30637
|
+
// #3489: same split as the plugin heal above — a lock-contention skip is not this run's gap.
|
|
30638
|
+
...heal.skipped ? { reportOnly: true } : {},
|
|
30639
|
+
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(target)}\``,
|
|
30640
|
+
verbose: healEvidence
|
|
30641
|
+
}
|
|
30642
|
+
);
|
|
30487
30643
|
}
|
|
30488
30644
|
async function runGithubAuthRow() {
|
|
30489
30645
|
emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
|
|
@@ -30505,6 +30661,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30505
30661
|
if (gi.ok) {
|
|
30506
30662
|
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
|
|
30507
30663
|
} else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
|
|
30664
|
+
traceHeal("repo-cleans");
|
|
30665
|
+
markHealChanged();
|
|
30508
30666
|
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
30509
30667
|
restartPending = true;
|
|
30510
30668
|
} else {
|
|
@@ -30512,8 +30670,100 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30512
30670
|
}
|
|
30513
30671
|
}
|
|
30514
30672
|
async function runPluginCacheRow() {
|
|
30673
|
+
if (applyEnv && deps.prunePluginCache && spendOnce("cache-prune")) {
|
|
30674
|
+
const cache = deps.pluginCache();
|
|
30675
|
+
if (cache.stale.length === 0 && cache.staging.length === 0) {
|
|
30676
|
+
emitNow(checkPluginCache(cache));
|
|
30677
|
+
return;
|
|
30678
|
+
}
|
|
30679
|
+
healIntent(`plugin cache \u2014 pruning ${cache.stale.length} superseded version(s) (guarded)`);
|
|
30680
|
+
traceHeal("cache-prune");
|
|
30681
|
+
const outcome = deps.prunePluginCache();
|
|
30682
|
+
if (outcome.removed.length) markHealChanged();
|
|
30683
|
+
const evidence = [
|
|
30684
|
+
...outcome.removed.map((v) => `pruned: ${v}`),
|
|
30685
|
+
...outcome.held.map((h) => `still held: ${h.version} (${h.error}) \u2014 never forced`),
|
|
30686
|
+
...outcome.kept.map((k) => `kept: ${k.version} \u2014 ${k.reason}`)
|
|
30687
|
+
];
|
|
30688
|
+
if (outcome.held.length === 0) {
|
|
30689
|
+
emitNow({
|
|
30690
|
+
id: "plugin-cache",
|
|
30691
|
+
ok: true,
|
|
30692
|
+
label: "plugin cache",
|
|
30693
|
+
detail: outcome.removed.length ? `pruned ${outcome.removed.length} superseded version(s)` : "nothing provably superseded (guards kept every candidate)",
|
|
30694
|
+
verbose: evidence
|
|
30695
|
+
});
|
|
30696
|
+
} else {
|
|
30697
|
+
emitNow({
|
|
30698
|
+
id: "plugin-cache",
|
|
30699
|
+
ok: false,
|
|
30700
|
+
reportOnly: true,
|
|
30701
|
+
label: "plugin cache",
|
|
30702
|
+
detail: `${outcome.removed.length} pruned; ${outcome.held.length} still held by a live session`,
|
|
30703
|
+
fix: "close the session holding it and re-run `mmi-cli doctor`",
|
|
30704
|
+
verbose: evidence
|
|
30705
|
+
});
|
|
30706
|
+
}
|
|
30707
|
+
return;
|
|
30708
|
+
}
|
|
30515
30709
|
emitNow(checkPluginCache(deps.pluginCache()));
|
|
30516
30710
|
}
|
|
30711
|
+
async function runPiPluginRow() {
|
|
30712
|
+
const state = deps.piPluginState?.();
|
|
30713
|
+
if (!state) return;
|
|
30714
|
+
if (!state.settingsReadable) {
|
|
30715
|
+
emitNow({
|
|
30716
|
+
id: "pi-plugin",
|
|
30717
|
+
ok: false,
|
|
30718
|
+
reportOnly: true,
|
|
30719
|
+
label: "pi plugin",
|
|
30720
|
+
detail: "settings.json unreadable",
|
|
30721
|
+
fix: "repair ~/.pi/agent/settings.json, then re-run doctor",
|
|
30722
|
+
verbose: ["~/.pi/agent exists but settings.json could not be parsed \u2014 fail closed, nothing written"]
|
|
30723
|
+
});
|
|
30724
|
+
return;
|
|
30725
|
+
}
|
|
30726
|
+
const current = state.registeredPath === state.expectedPath;
|
|
30727
|
+
if (current) {
|
|
30728
|
+
emitNow({
|
|
30729
|
+
id: "pi-plugin",
|
|
30730
|
+
ok: true,
|
|
30731
|
+
label: "pi plugin",
|
|
30732
|
+
detail: "package registered in settings.json",
|
|
30733
|
+
verbose: [`registered: ${state.registeredPath}`]
|
|
30734
|
+
});
|
|
30735
|
+
return;
|
|
30736
|
+
}
|
|
30737
|
+
if (applyEnv && deps.healPiPlugin) {
|
|
30738
|
+
healIntent(`pi plugin \u2014 registering ${state.expectedPath}`);
|
|
30739
|
+
traceHeal("env-heals");
|
|
30740
|
+
const heal = deps.healPiPlugin();
|
|
30741
|
+
if (heal.ok) markHealChanged();
|
|
30742
|
+
emitNow(heal.ok ? {
|
|
30743
|
+
id: "pi-plugin",
|
|
30744
|
+
ok: true,
|
|
30745
|
+
label: "pi plugin",
|
|
30746
|
+
detail: state.registeredPath ? "replaced stale package path" : "registered package in settings.json",
|
|
30747
|
+
verbose: [`was: ${state.registeredPath ?? "(absent)"}`, `now: ${state.expectedPath}`, `heal: ${heal.detail}`]
|
|
30748
|
+
} : {
|
|
30749
|
+
id: "pi-plugin",
|
|
30750
|
+
ok: false,
|
|
30751
|
+
label: "pi plugin",
|
|
30752
|
+
detail: state.registeredPath ? "stale package path" : "package not registered",
|
|
30753
|
+
fix: `heal failed (${heal.detail}) \u2014 add ${state.expectedPath} to packages[] in ~/.pi/agent/settings.json`,
|
|
30754
|
+
verbose: [`expected: ${state.expectedPath}`]
|
|
30755
|
+
});
|
|
30756
|
+
return;
|
|
30757
|
+
}
|
|
30758
|
+
emitNow({
|
|
30759
|
+
id: "pi-plugin",
|
|
30760
|
+
ok: false,
|
|
30761
|
+
label: "pi plugin",
|
|
30762
|
+
detail: state.registeredPath ? "stale package path" : "package not registered",
|
|
30763
|
+
fix: "run `mmi-cli doctor` to register the mmi .pi-plugin in ~/.pi/agent/settings.json",
|
|
30764
|
+
verbose: [`expected: ${state.expectedPath}`, `registered: ${state.registeredPath ?? "(absent)"}`]
|
|
30765
|
+
});
|
|
30766
|
+
}
|
|
30517
30767
|
async function runSessionPayloadRow() {
|
|
30518
30768
|
const payload = checkSessionPayload(deps.sessionPayload());
|
|
30519
30769
|
if (payload) emitNow(payload);
|
|
@@ -30523,7 +30773,11 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30523
30773
|
const healed = deps.healMarketplacePins();
|
|
30524
30774
|
if (healed) {
|
|
30525
30775
|
healIntent(`marketplace pins \u2014 ${healed.detail}`);
|
|
30526
|
-
if (healed.wrote)
|
|
30776
|
+
if (healed.wrote) {
|
|
30777
|
+
traceHeal("env-heals");
|
|
30778
|
+
markHealChanged();
|
|
30779
|
+
restartPending = true;
|
|
30780
|
+
}
|
|
30527
30781
|
}
|
|
30528
30782
|
}
|
|
30529
30783
|
for (const row of deps.marketplaceRows()) emitNow(row);
|
|
@@ -30566,6 +30820,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30566
30820
|
let healedWrite = false;
|
|
30567
30821
|
if (applyRepo && probe?.drift && deps.healDocsIndex) {
|
|
30568
30822
|
healIntent("docs index \u2014 regenerating docs/index.md");
|
|
30823
|
+
traceHeal("repo-cleans");
|
|
30824
|
+
markHealChanged();
|
|
30569
30825
|
try {
|
|
30570
30826
|
probe = deps.healDocsIndex(root);
|
|
30571
30827
|
healedWrite = !probe.drift;
|
|
@@ -30786,7 +31042,11 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30786
31042
|
`skipped: ${sweep.skipped}`
|
|
30787
31043
|
]
|
|
30788
31044
|
});
|
|
30789
|
-
if (sweep.removed.length)
|
|
31045
|
+
if (sweep.removed.length) {
|
|
31046
|
+
traceHeal("repo-cleans");
|
|
31047
|
+
markHealChanged();
|
|
31048
|
+
restartPending = true;
|
|
31049
|
+
}
|
|
30790
31050
|
} catch (e) {
|
|
30791
31051
|
const message = e instanceof Error ? e.message : String(e);
|
|
30792
31052
|
emitNow({
|
|
@@ -30824,7 +31084,11 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30824
31084
|
...r.failed.map((f) => `FAILED: ${f}`)
|
|
30825
31085
|
] : ["nothing reapable"]
|
|
30826
31086
|
});
|
|
30827
|
-
if (reaped)
|
|
31087
|
+
if (reaped) {
|
|
31088
|
+
traceHeal("repo-cleans");
|
|
31089
|
+
markHealChanged();
|
|
31090
|
+
restartPending = true;
|
|
31091
|
+
}
|
|
30828
31092
|
} else {
|
|
30829
31093
|
const n = gcReapable(plan);
|
|
30830
31094
|
const gcEvidence = [
|
|
@@ -30851,36 +31115,62 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30851
31115
|
const applied = deps.executeScratchGc(repoRoot2, { apply: true });
|
|
30852
31116
|
const pruned = applied.applied?.pruned.length ?? 0;
|
|
30853
31117
|
emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
|
|
30854
|
-
if (pruned)
|
|
31118
|
+
if (pruned) {
|
|
31119
|
+
traceHeal("repo-cleans");
|
|
31120
|
+
markHealChanged();
|
|
31121
|
+
restartPending = true;
|
|
31122
|
+
}
|
|
30855
31123
|
} else {
|
|
30856
31124
|
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` (without --no-repo-writes)", verbose: scratchEvidence });
|
|
30857
31125
|
}
|
|
30858
31126
|
}
|
|
30859
31127
|
const prefix = [
|
|
30860
|
-
{ id: "plugin", when: true, run: runPluginRow },
|
|
30861
31128
|
{ id: "cli-version", when: true, run: runCliRow },
|
|
31129
|
+
{ id: "plugin", when: true, run: runPluginRow },
|
|
31130
|
+
{ id: "plugin-cache", when: true, run: runPluginCacheRow },
|
|
30862
31131
|
{ id: "github-auth", when: true, run: runGithubAuthRow },
|
|
30863
31132
|
{ id: "aws-identity", when: true, run: runAwsRow },
|
|
30864
31133
|
{ id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
|
|
30865
|
-
{ id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
|
|
30866
|
-
{ id: "plugin-cache", when: true, run: runPluginCacheRow },
|
|
30867
31134
|
{ id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
|
|
30868
|
-
{ id: "marketplace", when: true, run: runMarketplaceRows }
|
|
30869
|
-
|
|
30870
|
-
|
|
30871
|
-
const parallel = [
|
|
30872
|
-
{ id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
|
|
30873
|
-
// Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
|
|
30874
|
-
{ id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
|
|
30875
|
-
{ id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
|
|
30876
|
-
{ id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
|
|
30877
|
-
];
|
|
30878
|
-
await Promise.all(parallel.filter((entry) => entry.when).map((entry) => entry.run()));
|
|
30879
|
-
const suffix = [
|
|
30880
|
-
{ id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
|
|
30881
|
-
{ id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
|
|
31135
|
+
{ id: "marketplace", when: true, run: runMarketplaceRows },
|
|
31136
|
+
{ id: "pi-plugin", when: true, run: runPiPluginRow },
|
|
31137
|
+
{ id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow }
|
|
30882
31138
|
];
|
|
30883
|
-
|
|
31139
|
+
const maxPasses = 3;
|
|
31140
|
+
let passes = 0;
|
|
31141
|
+
let prevFingerprint;
|
|
31142
|
+
let streamingPass = true;
|
|
31143
|
+
for (; ; ) {
|
|
31144
|
+
passes += 1;
|
|
31145
|
+
opts.onPass?.(passes);
|
|
31146
|
+
checks.length = 0;
|
|
31147
|
+
healChangedThisPass = false;
|
|
31148
|
+
for (const entry of prefix) if (entry.when) await entry.run();
|
|
31149
|
+
const parallelRows = [
|
|
31150
|
+
{ id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
|
|
31151
|
+
// Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
|
|
31152
|
+
{ id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
|
|
31153
|
+
{ id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
|
|
31154
|
+
{ id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
|
|
31155
|
+
];
|
|
31156
|
+
await Promise.all(parallelRows.filter((entry) => entry.when).map((entry) => entry.run()));
|
|
31157
|
+
const suffix = [
|
|
31158
|
+
{ id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
|
|
31159
|
+
{ id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
|
|
31160
|
+
];
|
|
31161
|
+
for (const entry of suffix) if (entry.when) await entry.run();
|
|
31162
|
+
if (!lane.full) break;
|
|
31163
|
+
const fingerprint = doctorSnapshotFingerprint(checks);
|
|
31164
|
+
const stillActionable = checks.some((c) => !c.ok && !c.reportOnly);
|
|
31165
|
+
if (!healChangedThisPass || !stillActionable || fingerprint === prevFingerprint) break;
|
|
31166
|
+
if (passes >= maxPasses) {
|
|
31167
|
+
healIntent(`convergence \u2014 pass bound (${maxPasses}) reached with rows still moving; re-run doctor to continue`);
|
|
31168
|
+
break;
|
|
31169
|
+
}
|
|
31170
|
+
prevFingerprint = fingerprint;
|
|
31171
|
+
streamingPass = false;
|
|
31172
|
+
healIntent(`convergence \u2014 pass ${passes} healed; re-measuring (pass ${passes + 1}/${maxPasses})`);
|
|
31173
|
+
}
|
|
30884
31174
|
const exitCode = doctorReportExitCode(checks);
|
|
30885
31175
|
if (opts.json) {
|
|
30886
31176
|
const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
|
|
@@ -30893,7 +31183,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30893
31183
|
return exitCode;
|
|
30894
31184
|
}
|
|
30895
31185
|
if (opts.banner) {
|
|
30896
|
-
const actionable = checks.filter((c) => (!c.ok || c.warn) && !
|
|
31186
|
+
const actionable = checks.filter((c) => (!c.ok || c.warn) && !alreadyShown(c));
|
|
30897
31187
|
for (const c of actionable) {
|
|
30898
31188
|
io.log(renderReport([c], { restartPending: false }));
|
|
30899
31189
|
if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
@@ -30901,7 +31191,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
30901
31191
|
if (restartPending) io.log(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
|
|
30902
31192
|
return 0;
|
|
30903
31193
|
}
|
|
30904
|
-
const rest = checks.filter((c) => !
|
|
31194
|
+
const rest = checks.filter((c) => !alreadyShown(c));
|
|
30905
31195
|
const shown = opts.verbose ? rest : rest.filter((c) => !c.ok || c.warn);
|
|
30906
31196
|
const lines = [];
|
|
30907
31197
|
for (const check of shown) {
|
|
@@ -30933,17 +31223,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
30933
31223
|
}
|
|
30934
31224
|
function ghHostsConfigPath(env, platform2) {
|
|
30935
31225
|
const sep3 = platform2 === "win32" ? "\\" : "/";
|
|
30936
|
-
const
|
|
31226
|
+
const join34 = (...parts) => parts.join(sep3);
|
|
30937
31227
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
30938
|
-
if (explicit) return
|
|
31228
|
+
if (explicit) return join34(explicit, "hosts.yml");
|
|
30939
31229
|
if (platform2 === "win32") {
|
|
30940
31230
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
30941
|
-
return appData ?
|
|
31231
|
+
return appData ? join34(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
30942
31232
|
}
|
|
30943
31233
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
30944
|
-
if (xdg) return
|
|
31234
|
+
if (xdg) return join34(xdg, "gh", "hosts.yml");
|
|
30945
31235
|
const home = env.HOME?.trim();
|
|
30946
|
-
return home ?
|
|
31236
|
+
return home ? join34(home, ".config", "gh", "hosts.yml") : void 0;
|
|
30947
31237
|
}
|
|
30948
31238
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
30949
31239
|
let hostIndent = null;
|
|
@@ -30993,9 +31283,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
30993
31283
|
}
|
|
30994
31284
|
|
|
30995
31285
|
// src/doctor-io.ts
|
|
30996
|
-
var
|
|
31286
|
+
var import_node_fs39 = require("node:fs");
|
|
30997
31287
|
var import_node_os14 = require("node:os");
|
|
30998
|
-
var
|
|
31288
|
+
var import_node_path37 = require("node:path");
|
|
30999
31289
|
var import_node_child_process17 = require("node:child_process");
|
|
31000
31290
|
var import_node_util8 = require("node:util");
|
|
31001
31291
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process17.execFile);
|
|
@@ -31003,7 +31293,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
31003
31293
|
function installedClaudePluginVersion() {
|
|
31004
31294
|
try {
|
|
31005
31295
|
const file = JSON.parse(
|
|
31006
|
-
(0,
|
|
31296
|
+
(0, import_node_fs39.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
31007
31297
|
);
|
|
31008
31298
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
31009
31299
|
if (versions.length === 0) return void 0;
|
|
@@ -31014,7 +31304,7 @@ function installedClaudePluginVersion() {
|
|
|
31014
31304
|
}
|
|
31015
31305
|
function manifestVersion(path2) {
|
|
31016
31306
|
try {
|
|
31017
|
-
const manifest = JSON.parse((0,
|
|
31307
|
+
const manifest = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
|
|
31018
31308
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
31019
31309
|
} catch {
|
|
31020
31310
|
return void 0;
|
|
@@ -31024,22 +31314,22 @@ function installedSurfacePluginVersion(surface) {
|
|
|
31024
31314
|
const token = surfaceToken(surface);
|
|
31025
31315
|
if (token === "kilo") {
|
|
31026
31316
|
try {
|
|
31027
|
-
const stamp = (0,
|
|
31317
|
+
const stamp = (0, import_node_fs39.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
31028
31318
|
return stamp || void 0;
|
|
31029
31319
|
} catch {
|
|
31030
31320
|
return void 0;
|
|
31031
31321
|
}
|
|
31032
31322
|
}
|
|
31033
31323
|
if (token === "cursor") {
|
|
31034
|
-
return manifestVersion((0,
|
|
31324
|
+
return manifestVersion((0, import_node_path37.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
31035
31325
|
}
|
|
31036
31326
|
if (token === "jervcode") {
|
|
31037
31327
|
const entry = mmiPiWrapperEntry();
|
|
31038
31328
|
if (!entry) return void 0;
|
|
31039
|
-
return manifestVersion((0,
|
|
31329
|
+
return manifestVersion((0, import_node_path37.join)(decodeURIComponent(entry.replace(/^file:\/\/\/?/, "")), "package.json"));
|
|
31040
31330
|
}
|
|
31041
31331
|
if (token === "kimi") {
|
|
31042
|
-
return manifestVersion((0,
|
|
31332
|
+
return manifestVersion((0, import_node_path37.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
31043
31333
|
}
|
|
31044
31334
|
if (token === "claude") return installedClaudePluginVersion();
|
|
31045
31335
|
if (token !== "codex") return void 0;
|
|
@@ -31077,13 +31367,13 @@ function worktreeRootSync() {
|
|
|
31077
31367
|
}
|
|
31078
31368
|
var gitignorePath = () => {
|
|
31079
31369
|
const root = worktreeRootSync();
|
|
31080
|
-
return root === null ? null : (0,
|
|
31370
|
+
return root === null ? null : (0, import_node_path37.join)(root, ".gitignore");
|
|
31081
31371
|
};
|
|
31082
31372
|
function readGitignore() {
|
|
31083
31373
|
const path2 = gitignorePath();
|
|
31084
31374
|
if (path2 === null) return null;
|
|
31085
31375
|
try {
|
|
31086
|
-
return (0,
|
|
31376
|
+
return (0, import_node_fs39.readFileSync)(path2, "utf8");
|
|
31087
31377
|
} catch {
|
|
31088
31378
|
return null;
|
|
31089
31379
|
}
|
|
@@ -31092,7 +31382,7 @@ function writeGitignore(content) {
|
|
|
31092
31382
|
const path2 = gitignorePath();
|
|
31093
31383
|
if (path2 === null) return false;
|
|
31094
31384
|
try {
|
|
31095
|
-
(0,
|
|
31385
|
+
(0, import_node_fs39.writeFileSync)(path2, content, "utf8");
|
|
31096
31386
|
return true;
|
|
31097
31387
|
} catch {
|
|
31098
31388
|
return false;
|
|
@@ -31116,7 +31406,7 @@ async function repoRoot() {
|
|
|
31116
31406
|
}
|
|
31117
31407
|
function hasRepoLocalWorktrees() {
|
|
31118
31408
|
const root = worktreeRootSync();
|
|
31119
|
-
return root !== null && (0,
|
|
31409
|
+
return root !== null && (0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, ".worktrees"));
|
|
31120
31410
|
}
|
|
31121
31411
|
|
|
31122
31412
|
// src/index.ts
|
|
@@ -31152,8 +31442,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
31152
31442
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
31153
31443
|
try {
|
|
31154
31444
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
31155
|
-
if (!hostsPath || !(0,
|
|
31156
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
31445
|
+
if (!hostsPath || !(0, import_node_fs40.existsSync)(hostsPath)) return void 0;
|
|
31446
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs40.readFileSync)(hostsPath, "utf8")));
|
|
31157
31447
|
} catch {
|
|
31158
31448
|
return void 0;
|
|
31159
31449
|
}
|
|
@@ -31161,7 +31451,7 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
31161
31451
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
31162
31452
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
31163
31453
|
function envHealLockPath(home) {
|
|
31164
|
-
return (0,
|
|
31454
|
+
return (0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
31165
31455
|
}
|
|
31166
31456
|
async function withEnvHealLock(what, run) {
|
|
31167
31457
|
try {
|
|
@@ -31276,6 +31566,45 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
31276
31566
|
stagingBytes: plan.stagingBytes
|
|
31277
31567
|
};
|
|
31278
31568
|
},
|
|
31569
|
+
// #4199: guarded auto-prune (docs/doctor-contract.md § Guarded cache prune). The plan is rebuilt
|
|
31570
|
+
// HERE, from live evidence, immediately before the delete — plan is the revalidation. Guards live in
|
|
31571
|
+
// `selectPrunablePluginVersions` (never running/newest/installed; unreadable evidence keeps the dir,
|
|
31572
|
+
// named). No `force`: a dir whose delete refuses (a session holds it) surfaces as `held`, never freed
|
|
31573
|
+
// out from under the holder.
|
|
31574
|
+
prunePluginCache: () => {
|
|
31575
|
+
const surface = detectSurface(process.env);
|
|
31576
|
+
const configRoot = surfaceConfigRoot(surface);
|
|
31577
|
+
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
31578
|
+
const installed = installedActivePluginVersion(surface);
|
|
31579
|
+
const plan = buildPluginCachePlan(
|
|
31580
|
+
(0, import_node_os15.homedir)(),
|
|
31581
|
+
running,
|
|
31582
|
+
pluginCacheFsDeps(configRoot, () => 0),
|
|
31583
|
+
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
31584
|
+
);
|
|
31585
|
+
const result = applyPluginCachePlan(
|
|
31586
|
+
plan,
|
|
31587
|
+
(p) => (0, import_node_fs40.rmSync)(p, { recursive: true }),
|
|
31588
|
+
stagingApplyFsGuard(configRoot)
|
|
31589
|
+
);
|
|
31590
|
+
return {
|
|
31591
|
+
removed: [...result.removed, ...result.removedStaging],
|
|
31592
|
+
held: [
|
|
31593
|
+
...result.failed,
|
|
31594
|
+
...result.failedStaging.map((f) => ({ version: f.name, error: f.error }))
|
|
31595
|
+
],
|
|
31596
|
+
kept: [
|
|
31597
|
+
...plan.keep.map((version) => ({
|
|
31598
|
+
version,
|
|
31599
|
+
reason: version === running ? "running" : version === installed ? "installed" : "newest / keep policy"
|
|
31600
|
+
})),
|
|
31601
|
+
...result.skippedStaging.map((s) => ({ version: s.name, reason: s.reason }))
|
|
31602
|
+
]
|
|
31603
|
+
};
|
|
31604
|
+
},
|
|
31605
|
+
// #4201: mmi `.pi-plugin` registration in ~/.pi/agent/settings.json — mirror of jerv-cli's own heal.
|
|
31606
|
+
piPluginState: () => readPiPluginState((0, import_node_os15.homedir)(), process.env),
|
|
31607
|
+
healPiPlugin: () => healPiPluginRegistration((0, import_node_os15.homedir)(), process.env),
|
|
31279
31608
|
// #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
|
|
31280
31609
|
// A local record read ? cheap enough for every lane, including the banner.
|
|
31281
31610
|
sessionPayload: () => readSessionPayload(process.cwd()),
|
|
@@ -31287,14 +31616,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
31287
31616
|
const home = (0, import_node_os15.homedir)();
|
|
31288
31617
|
const rows = marketplaceRows(
|
|
31289
31618
|
MMI_MARKETPLACE_NAME,
|
|
31290
|
-
readFileSyncSafe((0,
|
|
31291
|
-
readFileSyncSafe((0,
|
|
31619
|
+
readFileSyncSafe((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
|
|
31620
|
+
readFileSyncSafe((0, import_node_path38.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync),
|
|
31292
31621
|
// #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
|
|
31293
31622
|
// edit. Unconditional ? it is a fact about mmi-cli, not about the lane this run is on.
|
|
31294
31623
|
true
|
|
31295
31624
|
);
|
|
31296
31625
|
const pending = readMarketplacePinPending(
|
|
31297
|
-
(0,
|
|
31626
|
+
(0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
|
|
31298
31627
|
MMI_MARKETPLACE_NAME
|
|
31299
31628
|
);
|
|
31300
31629
|
if (!pending) return rows;
|
|
@@ -31320,9 +31649,9 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
31320
31649
|
if (detectSurface(process.env) === "codex") return void 0;
|
|
31321
31650
|
const home = (0, import_node_os15.homedir)();
|
|
31322
31651
|
const names = [MMI_MARKETPLACE_NAME];
|
|
31323
|
-
const result = applyOrgMarketplacePins((0,
|
|
31652
|
+
const result = applyOrgMarketplacePins((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
|
|
31324
31653
|
if (result?.wrote) {
|
|
31325
|
-
writeMarketplacePinPending((0,
|
|
31654
|
+
writeMarketplacePinPending((0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
|
|
31326
31655
|
}
|
|
31327
31656
|
return result;
|
|
31328
31657
|
} catch {
|
|
@@ -31338,7 +31667,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
31338
31667
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
31339
31668
|
// get a permanent ? demanding an artifact it never asked for.
|
|
31340
31669
|
docsIndexState: (root) => {
|
|
31341
|
-
if (!(0,
|
|
31670
|
+
if (!(0, import_node_fs40.existsSync)((0, import_node_path38.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
31342
31671
|
const real = createDocsIndexDeps(root);
|
|
31343
31672
|
let docs2;
|
|
31344
31673
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -31347,7 +31676,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
31347
31676
|
},
|
|
31348
31677
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
31349
31678
|
healDocsIndex: (root) => {
|
|
31350
|
-
if (!(0,
|
|
31679
|
+
if (!(0, import_node_fs40.existsSync)((0, import_node_path38.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
31351
31680
|
const real = createDocsIndexDeps(root);
|
|
31352
31681
|
let docs2;
|
|
31353
31682
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -31681,19 +32010,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
31681
32010
|
});
|
|
31682
32011
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
31683
32012
|
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) => {
|
|
31684
|
-
const path2 = (0,
|
|
31685
|
-
const current = (0,
|
|
32013
|
+
const path2 = (0, import_node_path38.join)(process.cwd(), ".gitignore");
|
|
32014
|
+
const current = (0, import_node_fs40.existsSync)(path2) ? (0, import_node_fs40.readFileSync)(path2, "utf8") : null;
|
|
31686
32015
|
const plan = planManagedGitignore(current);
|
|
31687
32016
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
31688
32017
|
if (opts.json) {
|
|
31689
|
-
if (opts.write && plan.changed) (0,
|
|
32018
|
+
if (opts.write && plan.changed) (0, import_node_fs40.writeFileSync)(path2, plan.content, "utf8");
|
|
31690
32019
|
console.log(JSON.stringify(plan, null, 2));
|
|
31691
32020
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
31692
32021
|
return;
|
|
31693
32022
|
}
|
|
31694
32023
|
if (opts.write) {
|
|
31695
32024
|
if (plan.changed) {
|
|
31696
|
-
(0,
|
|
32025
|
+
(0, import_node_fs40.writeFileSync)(path2, plan.content, "utf8");
|
|
31697
32026
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
31698
32027
|
} else {
|
|
31699
32028
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -31850,8 +32179,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
31850
32179
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
31851
32180
|
let root;
|
|
31852
32181
|
if (o.root !== void 0) {
|
|
31853
|
-
root = (0,
|
|
31854
|
-
if (!(0,
|
|
32182
|
+
root = (0, import_node_path38.resolve)(o.root);
|
|
32183
|
+
if (!(0, import_node_fs40.existsSync)(root) || !(0, import_node_fs40.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
31855
32184
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
31856
32185
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
31857
32186
|
return fail(`worktree gc: --root ${root} contains this checkout ? name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -31930,7 +32259,7 @@ async function currentWorktreeRemovalContext(command, force) {
|
|
|
31930
32259
|
};
|
|
31931
32260
|
}
|
|
31932
32261
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
31933
|
-
if (!(0,
|
|
32262
|
+
if (!(0, import_node_fs40.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
31934
32263
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
31935
32264
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
31936
32265
|
if (!registered.length) {
|
|
@@ -31952,26 +32281,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
31952
32281
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
31953
32282
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
31954
32283
|
const take = () => {
|
|
31955
|
-
const fd = (0,
|
|
32284
|
+
const fd = (0, import_node_fs40.openSync)(lockPath, "wx");
|
|
31956
32285
|
try {
|
|
31957
|
-
(0,
|
|
32286
|
+
(0, import_node_fs40.writeSync)(fd, String(Date.now()));
|
|
31958
32287
|
} finally {
|
|
31959
|
-
(0,
|
|
32288
|
+
(0, import_node_fs40.closeSync)(fd);
|
|
31960
32289
|
}
|
|
31961
32290
|
return () => {
|
|
31962
32291
|
try {
|
|
31963
|
-
(0,
|
|
32292
|
+
(0, import_node_fs40.rmSync)(lockPath, { force: true });
|
|
31964
32293
|
} catch {
|
|
31965
32294
|
}
|
|
31966
32295
|
};
|
|
31967
32296
|
};
|
|
31968
32297
|
try {
|
|
31969
|
-
(0,
|
|
32298
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path38.dirname)(lockPath), { recursive: true });
|
|
31970
32299
|
return take();
|
|
31971
32300
|
} catch {
|
|
31972
32301
|
try {
|
|
31973
|
-
if (Date.now() - (0,
|
|
31974
|
-
(0,
|
|
32302
|
+
if (Date.now() - (0, import_node_fs40.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
32303
|
+
(0, import_node_fs40.rmSync)(lockPath, { force: true });
|
|
31975
32304
|
return take();
|
|
31976
32305
|
}
|
|
31977
32306
|
} catch {
|
|
@@ -32832,7 +33161,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
32832
33161
|
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`);
|
|
32833
33162
|
if (o.secretsFile) {
|
|
32834
33163
|
try {
|
|
32835
|
-
vars.push(`secrets=${(0,
|
|
33164
|
+
vars.push(`secrets=${(0, import_node_fs40.readFileSync)(o.secretsFile, "utf8")}`);
|
|
32836
33165
|
} catch (e) {
|
|
32837
33166
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
32838
33167
|
}
|
|
@@ -33584,11 +33913,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
33584
33913
|
}
|
|
33585
33914
|
});
|
|
33586
33915
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
33587
|
-
const wfDir = (0,
|
|
33588
|
-
if (!(0,
|
|
33589
|
-
return (0,
|
|
33916
|
+
const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
|
|
33917
|
+
if (!(0, import_node_fs40.existsSync)(wfDir)) return [];
|
|
33918
|
+
return (0, import_node_fs40.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
33590
33919
|
try {
|
|
33591
|
-
return workflowReportsPrChecks((0,
|
|
33920
|
+
return workflowReportsPrChecks((0, import_node_fs40.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
|
|
33592
33921
|
} catch {
|
|
33593
33922
|
return true;
|
|
33594
33923
|
}
|
|
@@ -33620,16 +33949,16 @@ function ciAuditDeps() {
|
|
|
33620
33949
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
33621
33950
|
readSeedFile: (path2) => {
|
|
33622
33951
|
if (!root) return null;
|
|
33623
|
-
const fullPath = (0,
|
|
33624
|
-
return (0,
|
|
33952
|
+
const fullPath = (0, import_node_path38.join)(root, path2);
|
|
33953
|
+
return (0, import_node_fs40.existsSync)(fullPath) ? (0, import_node_fs40.readFileSync)(fullPath, "utf8") : null;
|
|
33625
33954
|
}
|
|
33626
33955
|
};
|
|
33627
33956
|
}
|
|
33628
33957
|
function hubRoot() {
|
|
33629
|
-
const fromPkg = (0,
|
|
33958
|
+
const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
|
|
33630
33959
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
33631
|
-
if ((0,
|
|
33632
|
-
if ((0,
|
|
33960
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
|
|
33961
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path38.join)(process.cwd(), marker))) return process.cwd();
|
|
33633
33962
|
return null;
|
|
33634
33963
|
}
|
|
33635
33964
|
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) => {
|
|
@@ -33940,7 +34269,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
33940
34269
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
33941
34270
|
beforeWorktrees,
|
|
33942
34271
|
startingPath,
|
|
33943
|
-
pathExists: (p) => (0,
|
|
34272
|
+
pathExists: (p) => (0, import_node_fs40.existsSync)(p),
|
|
33944
34273
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
33945
34274
|
teardownWorktreeStage,
|
|
33946
34275
|
deferredStore,
|
|
@@ -34434,19 +34763,19 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
34434
34763
|
targets = resolution.targets;
|
|
34435
34764
|
}
|
|
34436
34765
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
34437
|
-
const fileMatrix = (0,
|
|
34766
|
+
const fileMatrix = (0, import_node_fs40.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs40.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
34438
34767
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
34439
34768
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
34440
|
-
const fileContracts = (0,
|
|
34769
|
+
const fileContracts = (0, import_node_fs40.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs40.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
34441
34770
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
34442
|
-
const sanctioned = (0,
|
|
34771
|
+
const sanctioned = (0, import_node_fs40.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs40.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
34443
34772
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
34444
34773
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
34445
34774
|
if (!report.ok) process.exitCode = 1;
|
|
34446
34775
|
});
|
|
34447
34776
|
access.command("capabilities").description("enumerate your effective vault reach ? every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
34448
34777
|
var isWin2 = process.platform === "win32";
|
|
34449
|
-
program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft ? repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "
|
|
34778
|
+
program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft ? repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes (#4199, canon); heal env drift with a plain run or --no-repo-writes").option("--verbose", "print the evidence behind every check ? probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format ? repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only ? never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals env drift (CLI, plugin, marketplace pins) and cleans repo cruft (gitignore block,\nmerged branches, dead worktrees, aged scratch) automatically (#3975). --no-repo-writes keeps the\nworking tree untouched for train preflights; --banner/--fast/--self are read-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
|
|
34450
34779
|
if (opts.guide) {
|
|
34451
34780
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
34452
34781
|
return;
|
|
@@ -34471,16 +34800,16 @@ function directoryBytes(path2) {
|
|
|
34471
34800
|
let total = 0;
|
|
34472
34801
|
let entries;
|
|
34473
34802
|
try {
|
|
34474
|
-
entries = (0,
|
|
34803
|
+
entries = (0, import_node_fs40.readdirSync)(path2, { withFileTypes: true });
|
|
34475
34804
|
} catch {
|
|
34476
34805
|
return 0;
|
|
34477
34806
|
}
|
|
34478
34807
|
for (const entry of entries) {
|
|
34479
|
-
const child2 = (0,
|
|
34808
|
+
const child2 = (0, import_node_path38.join)(path2, entry.name);
|
|
34480
34809
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
34481
34810
|
else {
|
|
34482
34811
|
try {
|
|
34483
|
-
total += (0,
|
|
34812
|
+
total += (0, import_node_fs40.statSync)(child2).size;
|
|
34484
34813
|
} catch {
|
|
34485
34814
|
}
|
|
34486
34815
|
}
|
|
@@ -34488,25 +34817,25 @@ function directoryBytes(path2) {
|
|
|
34488
34817
|
return total;
|
|
34489
34818
|
}
|
|
34490
34819
|
function listDirEntries(dir) {
|
|
34491
|
-
return (0,
|
|
34820
|
+
return (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
34492
34821
|
}
|
|
34493
34822
|
function readInstalledPluginRefs(configRoot) {
|
|
34494
34823
|
const p = installedPluginsPathForConfig(configRoot);
|
|
34495
|
-
if (!(0,
|
|
34824
|
+
if (!(0, import_node_fs40.existsSync)(p)) return [];
|
|
34496
34825
|
try {
|
|
34497
|
-
return installedPluginPaths((0,
|
|
34826
|
+
return installedPluginPaths((0, import_node_fs40.readFileSync)(p, "utf8"));
|
|
34498
34827
|
} catch {
|
|
34499
34828
|
return null;
|
|
34500
34829
|
}
|
|
34501
34830
|
}
|
|
34502
34831
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
34503
34832
|
return {
|
|
34504
|
-
exists: (p) => (0,
|
|
34505
|
-
listVersionDirs: (root) => (0,
|
|
34833
|
+
exists: (p) => (0, import_node_fs40.existsSync)(p),
|
|
34834
|
+
listVersionDirs: (root) => (0, import_node_fs40.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
34506
34835
|
dirBytes,
|
|
34507
|
-
listStagingDirs: (root) => (0,
|
|
34836
|
+
listStagingDirs: (root) => (0, import_node_fs40.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
34508
34837
|
try {
|
|
34509
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
34838
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path38.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs40.statSync)(p).mtimeMs) };
|
|
34510
34839
|
} catch {
|
|
34511
34840
|
return { name: d.name, mtimeMs: Date.now() };
|
|
34512
34841
|
}
|
|
@@ -34520,10 +34849,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
34520
34849
|
return {
|
|
34521
34850
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
34522
34851
|
mtimeMs: (name) => {
|
|
34523
|
-
const p = (0,
|
|
34524
|
-
if (!(0,
|
|
34852
|
+
const p = (0, import_node_path38.join)(stagingRoot, name);
|
|
34853
|
+
if (!(0, import_node_fs40.existsSync)(p)) return null;
|
|
34525
34854
|
try {
|
|
34526
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
34855
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs40.statSync)(q).mtimeMs);
|
|
34527
34856
|
} catch {
|
|
34528
34857
|
return null;
|
|
34529
34858
|
}
|
|
@@ -34549,7 +34878,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
34549
34878
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
34550
34879
|
);
|
|
34551
34880
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
34552
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
34881
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs40.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
34553
34882
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
34554
34883
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
34555
34884
|
else console.log(renderPluginCachePlan(plan, result));
|