@mutmutco/cli 2.27.0 → 2.28.1
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/README.md +1 -0
- package/dist/main.cjs +213 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ mmi-cli doctor --json
|
|
|
44
44
|
- `mmi-cli oauth plan|verify` prints a repo's canonical Google OAuth URI set when the registry declares an `oauth` block and verifies the client is port-agnostic.
|
|
45
45
|
- `mmi-cli issue create` creates typed, prioritized GitHub issues (priority sets the board field, not a label) and queues related-issue discovery. `--parent <ref>` files the new issue as a native GitHub sub-issue of a parent (works cross-repo); `mmi-cli issue link-child <parent> <child>` links two existing issues the same way.
|
|
46
46
|
- `mmi-cli report` files a friction report on the Hub board with your GitHub identity, deduping against the open `report`-labeled issues (a confident duplicate becomes a +1 comment, not a new issue). No repo-local `.env`, no API key, no copied report script.
|
|
47
|
+
- `mmi-cli skill-lesson --skill <name>` files a **skill-lesson** on the Hub board when a skill's own instructions misfire — the cross-skill generalization of grind's Retro. GitHub identity, its own `skill-lesson` label + dedup pool (a confident duplicate becomes a +1 comment), and a footer pinning the source checkout + plugin SHA. Advisory: the fix lands via a reviewed PR to the skill in MMI-Hub, never a live edit.
|
|
47
48
|
- `mmi-cli pr create` and `pr merge` create PRs and land them with branch/worktree cleanup; `mmi-cli gc` dry-runs cleanup of merged/closed PR branches + stale tracking refs.
|
|
48
49
|
- `mmi-cli board read|claim|show|move|done|backfill-priority` reads and moves GitHub Project work.
|
|
49
50
|
- `mmi-cli tenant control <owner/repo> <stage> <status|start|stop|restart>` runs bounded dev/rc box control for project-admins through the Hub API; main remains master-only.
|
package/dist/main.cjs
CHANGED
|
@@ -5038,6 +5038,38 @@ function buildDupComment(duplicateNumber, body, sourceRepo) {
|
|
|
5038
5038
|
${buildReportBody(body, sourceRepo)}`;
|
|
5039
5039
|
}
|
|
5040
5040
|
|
|
5041
|
+
// src/skill-lesson.ts
|
|
5042
|
+
var SKILL_LESSON_LABEL = "skill-lesson";
|
|
5043
|
+
var SKILL_NAMES = ["bootstrap", "grind", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
|
|
5044
|
+
function assertSkillName(name) {
|
|
5045
|
+
const match = SKILL_NAMES.find((skill) => skill === name);
|
|
5046
|
+
if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
|
|
5047
|
+
return match;
|
|
5048
|
+
}
|
|
5049
|
+
function buildSkillLessonTitle(skill, what) {
|
|
5050
|
+
return `skill-lesson(${skill}): ${what.trim()}`;
|
|
5051
|
+
}
|
|
5052
|
+
function buildSkillLessonBody(body, sourceRepo, pluginSha) {
|
|
5053
|
+
const where = sourceRepo ?? "an unidentified checkout";
|
|
5054
|
+
const at = pluginSha ? ` @ ${pluginSha}` : "";
|
|
5055
|
+
return `${body.trimEnd()}
|
|
5056
|
+
|
|
5057
|
+
---
|
|
5058
|
+
Filed via \`mmi-cli skill-lesson\` from ${where}${at}.`;
|
|
5059
|
+
}
|
|
5060
|
+
function skillLessonDupMarker(duplicateNumber) {
|
|
5061
|
+
return `<!-- mmi-skill-lesson-dup:${duplicateNumber} -->`;
|
|
5062
|
+
}
|
|
5063
|
+
function buildSkillLessonDupComment(duplicateNumber, body, sourceRepo, pluginSha) {
|
|
5064
|
+
return `${skillLessonDupMarker(duplicateNumber)}
|
|
5065
|
+
+1 \u2014 same lesson surfaced again:
|
|
5066
|
+
|
|
5067
|
+
${buildSkillLessonBody(body, sourceRepo, pluginSha)}`;
|
|
5068
|
+
}
|
|
5069
|
+
function findDuplicateLesson(source, openLessons) {
|
|
5070
|
+
return findDuplicateReport(source, openLessons);
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5041
5073
|
// src/board.ts
|
|
5042
5074
|
var import_node_child_process5 = require("node:child_process");
|
|
5043
5075
|
var import_node_util5 = require("node:util");
|
|
@@ -6711,6 +6743,28 @@ function buildMmiPluginCacheCleanupCheck(input) {
|
|
|
6711
6743
|
}))
|
|
6712
6744
|
};
|
|
6713
6745
|
}
|
|
6746
|
+
var NESTED_PLUGIN_TREE_LABEL = "self-nested MMI plugin cache tree (#1126)";
|
|
6747
|
+
var NESTED_PLUGIN_TREE_FIX = "run `mmi-cli doctor` to surface the MAX_PATH-safe robocopy cleanup for a self-nested MMI plugin cache tree (#1126)";
|
|
6748
|
+
function nestedPluginTreeCleanupCommand(paths, isWindows) {
|
|
6749
|
+
if (paths.length === 0) return "";
|
|
6750
|
+
if (isWindows) {
|
|
6751
|
+
const clears = paths.map((p) => `robocopy $e "${p}" /MIR /NJH /NJS /NFL /NDL | Out-Null; Remove-Item -LiteralPath "${p}" -Recurse -Force`).join("; ");
|
|
6752
|
+
return `$e=Join-Path $env:TEMP 'mmi-empty'; New-Item -ItemType Directory -Force $e | Out-Null; ${clears}; Remove-Item -LiteralPath $e -Recurse -Force; ${CLAUDE_PLUGIN_RECOVERY}`;
|
|
6753
|
+
}
|
|
6754
|
+
return `${paths.map((p) => `rm -rf "${p}"`).join(" && ")} && ${CLAUDE_PLUGIN_RECOVERY}`;
|
|
6755
|
+
}
|
|
6756
|
+
function buildNestedPluginTreeCheck(input) {
|
|
6757
|
+
const base = { ok: true, label: NESTED_PLUGIN_TREE_LABEL, fix: NESTED_PLUGIN_TREE_FIX };
|
|
6758
|
+
if (!input.isOrgRepo) return base;
|
|
6759
|
+
const nested = input.entries.filter((e) => e.nested).map((e) => ({ surface: e.surface, path: e.path }));
|
|
6760
|
+
if (nested.length === 0) return base;
|
|
6761
|
+
return {
|
|
6762
|
+
...base,
|
|
6763
|
+
ok: false,
|
|
6764
|
+
nested,
|
|
6765
|
+
fix: `${nested.length} self-nested MMI plugin cache tree(s) (#1126) exceed MAX_PATH and can't self-clean \u2014 run: ${nestedPluginTreeCleanupCommand(nested.map((n) => n.path), input.isWindows)}`
|
|
6766
|
+
};
|
|
6767
|
+
}
|
|
6714
6768
|
function detectSurface(env) {
|
|
6715
6769
|
const has = (k) => Boolean(env[k]?.trim());
|
|
6716
6770
|
if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_HOME") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
|
|
@@ -6739,8 +6793,18 @@ function reloadAction(surface) {
|
|
|
6739
6793
|
return "restart Claude Code (or run /reload-plugins)";
|
|
6740
6794
|
}
|
|
6741
6795
|
}
|
|
6796
|
+
var CLAUDE_PLUGIN_RECOVERY = "claude plugin marketplace remove mmi && claude plugin marketplace add mutmutco/MMI-Hub && claude plugin install mmi@mmi";
|
|
6797
|
+
var CLAUDE_PLUGIN_HEAL_STEPS = [
|
|
6798
|
+
{ args: ["plugin", "marketplace", "remove", "mmi"], gated: false },
|
|
6799
|
+
{ args: ["plugin", "marketplace", "add", "mutmutco/MMI-Hub"], gated: true },
|
|
6800
|
+
{ args: ["plugin", "install", "mmi@mmi"], gated: true },
|
|
6801
|
+
{ args: ["plugin", "enable", "mmi@mmi"], gated: false }
|
|
6802
|
+
];
|
|
6803
|
+
function healStepAborts(step, ok) {
|
|
6804
|
+
return !ok && step.gated;
|
|
6805
|
+
}
|
|
6742
6806
|
function pluginRecoveryFix(surface) {
|
|
6743
|
-
const claude =
|
|
6807
|
+
const claude = CLAUDE_PLUGIN_RECOVERY;
|
|
6744
6808
|
switch (surface) {
|
|
6745
6809
|
case "claude-vscode":
|
|
6746
6810
|
case "claude-cli":
|
|
@@ -6755,7 +6819,7 @@ function pluginRecoveryFix(surface) {
|
|
|
6755
6819
|
}
|
|
6756
6820
|
}
|
|
6757
6821
|
var PLUGIN_UPDATE_RECIPES = {
|
|
6758
|
-
claude: [
|
|
6822
|
+
claude: [CLAUDE_PLUGIN_RECOVERY],
|
|
6759
6823
|
codex: ["codex plugin marketplace upgrade mmi", "codex plugin list # verify mmi@mmi shows the new version"],
|
|
6760
6824
|
cli: ["npm install -g @mutmutco/cli@latest"]
|
|
6761
6825
|
};
|
|
@@ -6810,7 +6874,7 @@ function isSemverVersion(v) {
|
|
|
6810
6874
|
return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
|
|
6811
6875
|
}
|
|
6812
6876
|
function staleRecordCommand(surface) {
|
|
6813
|
-
return surface === "codex" ? "codex plugin marketplace upgrade mmi && codex plugin add mmi@mmi" :
|
|
6877
|
+
return surface === "codex" ? "codex plugin marketplace upgrade mmi && codex plugin add mmi@mmi" : CLAUDE_PLUGIN_RECOVERY;
|
|
6814
6878
|
}
|
|
6815
6879
|
function staleSurfacesFix(stale, releasedVersion) {
|
|
6816
6880
|
const parts = stale.map((s) => {
|
|
@@ -7533,6 +7597,51 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
|
|
|
7533
7597
|
}
|
|
7534
7598
|
return [...contexts];
|
|
7535
7599
|
}
|
|
7600
|
+
async function findOpenAlignmentPr(deps, ctx) {
|
|
7601
|
+
const out = clean(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
|
|
7602
|
+
if (!out) return void 0;
|
|
7603
|
+
const rows = JSON.parse(out);
|
|
7604
|
+
const row = rows.find((r) => typeof r.number === "number" && typeof r.url === "string");
|
|
7605
|
+
return row ? { number: row.number, url: row.url } : void 0;
|
|
7606
|
+
}
|
|
7607
|
+
function parsePrNumber(url) {
|
|
7608
|
+
const n = Number.parseInt(url.split("/").pop() ?? "", 10);
|
|
7609
|
+
return Number.isFinite(n) ? n : void 0;
|
|
7610
|
+
}
|
|
7611
|
+
async function rollDevelopmentForward(deps, ctx, tag) {
|
|
7612
|
+
const required = await discoverRequiredCheckContexts(deps, ctx, "development");
|
|
7613
|
+
if (required.length === 0) {
|
|
7614
|
+
await deps.run("git", ["checkout", "development"]);
|
|
7615
|
+
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
7616
|
+
await deps.run("git", ["merge", "main", "--no-edit"]);
|
|
7617
|
+
await deps.run("git", ["push", "origin", "development"]);
|
|
7618
|
+
return { status: "pushed", note: "development rolled forward to the released main (development has no required checks)" };
|
|
7619
|
+
}
|
|
7620
|
+
const existing = await findOpenAlignmentPr(deps, ctx);
|
|
7621
|
+
if (existing) {
|
|
7622
|
+
return {
|
|
7623
|
+
status: "pr-pending",
|
|
7624
|
+
prNumber: existing.number,
|
|
7625
|
+
prUrl: existing.url,
|
|
7626
|
+
note: `alignment PR already open: ${existing.url} \u2014 land it with \`gh pr merge ${existing.number} --merge\``
|
|
7627
|
+
};
|
|
7628
|
+
}
|
|
7629
|
+
const ahead = clean(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
|
|
7630
|
+
if (ahead === "0") {
|
|
7631
|
+
return { status: "aligned", note: "development already contains the released main; nothing to roll forward" };
|
|
7632
|
+
}
|
|
7633
|
+
const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
|
|
7634
|
+
|
|
7635
|
+
\`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). Land it with a **true merge** (\`gh pr merge --merge\`, not squash) so the merge parentage survives and the misalignment guard stays satisfied.`;
|
|
7636
|
+
const url = clean(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
|
|
7637
|
+
const number = parsePrNumber(url);
|
|
7638
|
+
return {
|
|
7639
|
+
status: "pr-pending",
|
|
7640
|
+
prNumber: number,
|
|
7641
|
+
prUrl: url || void 0,
|
|
7642
|
+
note: `development requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"} \u2014 land it with \`gh pr merge ${number ?? "<number>"} --merge\``
|
|
7643
|
+
};
|
|
7644
|
+
}
|
|
7536
7645
|
function resolveContextState(context, checkRuns, statuses) {
|
|
7537
7646
|
let sawFailure = false;
|
|
7538
7647
|
for (const r of checkRuns) {
|
|
@@ -7791,10 +7900,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7791
7900
|
const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
7792
7901
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
7793
7902
|
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
7794
|
-
await deps
|
|
7795
|
-
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
7796
|
-
await deps.run("git", ["merge", "main", "--no-edit"]);
|
|
7797
|
-
await deps.run("git", ["push", "origin", "development"]);
|
|
7903
|
+
const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
|
|
7798
7904
|
return {
|
|
7799
7905
|
...ctx,
|
|
7800
7906
|
command,
|
|
@@ -7812,6 +7918,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7812
7918
|
deployStatus: d2.deployStatus,
|
|
7813
7919
|
rcRetirement: "not-applicable",
|
|
7814
7920
|
rcRetirementNote: "direct-track release skips rc; no rc runtime to retire",
|
|
7921
|
+
devRollForward: devRollForward2,
|
|
7815
7922
|
announceNote: announceNote2,
|
|
7816
7923
|
// #1062: --dev on a direct-track repo is a friendly no-op — it already releases from development.
|
|
7817
7924
|
devNote: options.dev ? "--dev is a no-op on a direct-track repo \u2014 it already releases development -> main" : void 0,
|
|
@@ -7863,10 +7970,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7863
7970
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
7864
7971
|
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
7865
7972
|
const retirement2 = await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease);
|
|
7866
|
-
await deps
|
|
7867
|
-
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
7868
|
-
await deps.run("git", ["merge", "main", "--no-edit"]);
|
|
7869
|
-
await deps.run("git", ["push", "origin", "development"]);
|
|
7973
|
+
const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
|
|
7870
7974
|
let rcAlignment2;
|
|
7871
7975
|
try {
|
|
7872
7976
|
await deps.run("git", ["push", "origin", "main:rc"]);
|
|
@@ -7894,6 +7998,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7894
7998
|
rcRetirementNote: retirement2.note,
|
|
7895
7999
|
rcRetirementCategory: retirement2.category,
|
|
7896
8000
|
rcAlignment: rcAlignment2,
|
|
8001
|
+
devRollForward: devRollForward2,
|
|
7897
8002
|
announceNote: announceNote2,
|
|
7898
8003
|
devNote: "released development -> main (--dev), skipping the rc candidate",
|
|
7899
8004
|
release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
|
|
@@ -7943,10 +8048,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7943
8048
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
7944
8049
|
const d = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report");
|
|
7945
8050
|
const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
|
|
7946
|
-
await deps
|
|
7947
|
-
await deps.run("git", ["pull", "--ff-only", "origin", "development"]);
|
|
7948
|
-
await deps.run("git", ["merge", "main", "--no-edit"]);
|
|
7949
|
-
await deps.run("git", ["push", "origin", "development"]);
|
|
8051
|
+
const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
|
|
7950
8052
|
let rcAlignment;
|
|
7951
8053
|
try {
|
|
7952
8054
|
await deps.run("git", ["push", "origin", "main:rc"]);
|
|
@@ -7974,6 +8076,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
7974
8076
|
rcRetirementNote: retirement.note,
|
|
7975
8077
|
rcRetirementCategory: retirement.category,
|
|
7976
8078
|
rcAlignment,
|
|
8079
|
+
devRollForward,
|
|
7977
8080
|
announceNote,
|
|
7978
8081
|
release: { tag, url: releaseUrl, targetSha: releaseSha },
|
|
7979
8082
|
environments
|
|
@@ -11635,10 +11738,10 @@ async function runClaudePlugin(args) {
|
|
|
11635
11738
|
}
|
|
11636
11739
|
async function applyClaudePluginHeal(surface, log) {
|
|
11637
11740
|
if (surface !== "claude-cli" && surface !== "claude-vscode") return false;
|
|
11638
|
-
log(" \u21BB
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11741
|
+
log(" \u21BB reinstalling the MMI plugin via `claude plugin` (marketplace remove \u2192 add \u2192 install)\u2026");
|
|
11742
|
+
for (const step of CLAUDE_PLUGIN_HEAL_STEPS) {
|
|
11743
|
+
if (healStepAborts(step, await runClaudePlugin([...step.args]))) return false;
|
|
11744
|
+
}
|
|
11642
11745
|
return true;
|
|
11643
11746
|
}
|
|
11644
11747
|
var program2 = new Command();
|
|
@@ -12584,6 +12687,74 @@ program2.command("report").description("file a friction report on the Hub board
|
|
|
12584
12687
|
const projectItemId = await attachToProject(created.number, targetRepo2, priority);
|
|
12585
12688
|
console.log(JSON.stringify({ ...created, deduped: false, label: REPORT_LABEL, priority, projectItemId }));
|
|
12586
12689
|
});
|
|
12690
|
+
async function resolvePluginSha() {
|
|
12691
|
+
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
12692
|
+
if (!root) return void 0;
|
|
12693
|
+
try {
|
|
12694
|
+
const { stdout } = await execFileP2("git", ["-C", root, "rev-parse", "--short", "HEAD"], { timeout: 5e3 });
|
|
12695
|
+
return stdout.trim() || void 0;
|
|
12696
|
+
} catch {
|
|
12697
|
+
return void 0;
|
|
12698
|
+
}
|
|
12699
|
+
}
|
|
12700
|
+
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").requiredOption("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).requiredOption("--title <title>", "one-line summary of what misfired").option("--body <body>", "lesson body: what misfired, the evidence, and the proposed amendment (markdown)").option("--body-file <path|->", "read the lesson body from a UTF-8 file, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (board Priority field when configured)", "medium").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 skill-lesson always prints JSON)").action(async (o) => {
|
|
12701
|
+
const targetRepo2 = o.repo ?? HUB_REPO;
|
|
12702
|
+
const sourceRepo = await resolveRepo(void 0);
|
|
12703
|
+
const pluginSha = await resolvePluginSha();
|
|
12704
|
+
let skill;
|
|
12705
|
+
let rawBody;
|
|
12706
|
+
let title;
|
|
12707
|
+
let priority;
|
|
12708
|
+
let body;
|
|
12709
|
+
let args;
|
|
12710
|
+
try {
|
|
12711
|
+
skill = assertSkillName(o.skill);
|
|
12712
|
+
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises3.readFile, readStdin });
|
|
12713
|
+
title = buildSkillLessonTitle(skill, o.title);
|
|
12714
|
+
priority = normalizePriority(o.priority);
|
|
12715
|
+
body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
|
|
12716
|
+
args = buildIssueArgs({ type: "task", title, body, priority, repo: targetRepo2, labels: [SKILL_LESSON_LABEL] });
|
|
12717
|
+
} catch (e) {
|
|
12718
|
+
return fail(`skill-lesson: ${e.message}`);
|
|
12719
|
+
}
|
|
12720
|
+
if (!o.force) {
|
|
12721
|
+
let openLessons = [];
|
|
12722
|
+
try {
|
|
12723
|
+
openLessons = await ghJson([
|
|
12724
|
+
"issue",
|
|
12725
|
+
"list",
|
|
12726
|
+
"--repo",
|
|
12727
|
+
targetRepo2,
|
|
12728
|
+
"--state",
|
|
12729
|
+
"open",
|
|
12730
|
+
"--label",
|
|
12731
|
+
SKILL_LESSON_LABEL,
|
|
12732
|
+
"--limit",
|
|
12733
|
+
"100",
|
|
12734
|
+
"--json",
|
|
12735
|
+
"number,title,body,url"
|
|
12736
|
+
]);
|
|
12737
|
+
} catch {
|
|
12738
|
+
}
|
|
12739
|
+
const dup = findDuplicateLesson({ title, body: rawBody }, openLessons);
|
|
12740
|
+
if (dup) {
|
|
12741
|
+
try {
|
|
12742
|
+
await execFileP2("gh", ["issue", "comment", String(dup.number), "--repo", targetRepo2, "--body", buildSkillLessonDupComment(dup.number, rawBody, sourceRepo, pluginSha)], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
12743
|
+
} catch (e) {
|
|
12744
|
+
const err = e;
|
|
12745
|
+
return fail(`skill-lesson: duplicate of #${dup.number} (${dup.url}) but the +1 comment failed: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
12746
|
+
}
|
|
12747
|
+
return console.log(JSON.stringify({ deduped: true, number: dup.number, url: dup.url, score: dup.score }));
|
|
12748
|
+
}
|
|
12749
|
+
}
|
|
12750
|
+
try {
|
|
12751
|
+
await execFileP2("gh", ["label", "create", SKILL_LESSON_LABEL, "--color", "c2e0c6", "--repo", targetRepo2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
12752
|
+
} catch {
|
|
12753
|
+
}
|
|
12754
|
+
const created = await ghCreate(args);
|
|
12755
|
+
const projectItemId = await attachToProject(created.number, targetRepo2, priority);
|
|
12756
|
+
console.log(JSON.stringify({ ...created, deduped: false, label: SKILL_LESSON_LABEL, skill, priority, projectItemId }));
|
|
12757
|
+
});
|
|
12587
12758
|
var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
12588
12759
|
pr.command("create").description("create a PR and print {number,url} JSON").requiredOption("--title <title>", "PR title").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
|
|
12589
12760
|
let body;
|
|
@@ -13080,6 +13251,10 @@ function renderTrainApply(commandName, r) {
|
|
|
13080
13251
|
if (r.resumeNote) base = `${base}; ${r.resumeNote}`;
|
|
13081
13252
|
if (r.devNote) base = `${base}; ${r.devNote}`;
|
|
13082
13253
|
if (r.rcRetirement) base = `${base}; rc retirement: ${r.rcRetirement.toUpperCase()} (${r.rcRetirementNote ?? ""})`;
|
|
13254
|
+
if (r.devRollForward) {
|
|
13255
|
+
const f = r.devRollForward;
|
|
13256
|
+
base = f.status === "pr-pending" ? `${base}; dev roll-forward: ALIGNMENT PR PENDING \u2014 land it with \`gh pr merge ${f.prNumber ?? "<number>"} --merge\`${f.prUrl ? ` (${f.prUrl})` : ""}` : `${base}; dev roll-forward: ${f.note}`;
|
|
13257
|
+
}
|
|
13083
13258
|
return r.announceNote ? `${base}; announce: ${r.announceNote}` : base;
|
|
13084
13259
|
}
|
|
13085
13260
|
function renderTenantRedeploy(r) {
|
|
@@ -13576,6 +13751,18 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
13576
13751
|
}
|
|
13577
13752
|
});
|
|
13578
13753
|
}
|
|
13754
|
+
function hasNestedMmiChild(versionDir) {
|
|
13755
|
+
try {
|
|
13756
|
+
return (0, import_node_fs11.statSync)((0, import_node_path10.join)(versionDir, "mmi")).isDirectory();
|
|
13757
|
+
} catch {
|
|
13758
|
+
return false;
|
|
13759
|
+
}
|
|
13760
|
+
}
|
|
13761
|
+
function nestedPluginTreeSnapshot() {
|
|
13762
|
+
return mmiPluginCacheRootSnapshots().filter((root) => root.surface === "claude").flatMap(
|
|
13763
|
+
(root) => root.entries.filter((e) => e.isDirectory && /^v?\d+\.\d+\.\d+/.test(e.name)).map((e) => ({ surface: "claude", path: e.path, nested: hasNestedMmiChild(e.path) }))
|
|
13764
|
+
);
|
|
13765
|
+
}
|
|
13579
13766
|
function uniqueQuarantineTarget(path2) {
|
|
13580
13767
|
if (!(0, import_node_fs11.existsSync)(path2)) return path2;
|
|
13581
13768
|
for (let i = 1; i < 100; i += 1) {
|
|
@@ -13768,6 +13955,13 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
13768
13955
|
};
|
|
13769
13956
|
}
|
|
13770
13957
|
checks.push(cacheCleanupCheck);
|
|
13958
|
+
checks.push(
|
|
13959
|
+
buildNestedPluginTreeCheck({
|
|
13960
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
13961
|
+
isWindows: isWin,
|
|
13962
|
+
entries: nestedPluginTreeSnapshot()
|
|
13963
|
+
})
|
|
13964
|
+
);
|
|
13771
13965
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
13772
13966
|
checks.push(
|
|
13773
13967
|
buildCursorPluginInstallCheck({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.1",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|