@mutmutco/cli 3.90.0 → 3.92.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 +262 -10
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -9313,11 +9313,27 @@ function readKnownMarketplacesFile(path2) {
|
|
|
9313
9313
|
return void 0;
|
|
9314
9314
|
}
|
|
9315
9315
|
}
|
|
9316
|
-
function
|
|
9316
|
+
function claudeCodeIsRunning(env = process.env, listProcesses = defaultProcessList) {
|
|
9317
|
+
if (env.CLAUDE_CODE_SESSION_ID?.trim() || env.CLAUDECODE?.trim() || env.CLAUDE_PLUGIN_ROOT?.trim()) return true;
|
|
9318
|
+
let table;
|
|
9319
|
+
try {
|
|
9320
|
+
table = listProcesses();
|
|
9321
|
+
} catch {
|
|
9322
|
+
return true;
|
|
9323
|
+
}
|
|
9324
|
+
if (!table.trim()) return true;
|
|
9325
|
+
return table.split(/\r?\n/).some((line) => /(^|[/\\])claude(\.(exe|cmd|ps1))?\s/.test(`${line.trim()} `));
|
|
9326
|
+
}
|
|
9327
|
+
function defaultProcessList() {
|
|
9328
|
+
return isWin ? (0, import_node_child_process6.execFileSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, maxBuffer: 32 * 1024 * 1024, timeout: 15e3 }) : (0, import_node_child_process6.execFileSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
9329
|
+
}
|
|
9330
|
+
function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineWhileHostLive) {
|
|
9317
9331
|
if (pins.size === 0) return void 0;
|
|
9318
9332
|
const after = readKnownMarketplacesFile(path2);
|
|
9319
9333
|
const next = restoreMarketplacePins(after, pins);
|
|
9320
9334
|
if (next === null) return void 0;
|
|
9335
|
+
const declined = declineWhileHostLive?.();
|
|
9336
|
+
if (declined) return declined;
|
|
9321
9337
|
try {
|
|
9322
9338
|
(0, import_node_fs15.writeFileSync)(path2, next, "utf8");
|
|
9323
9339
|
} catch {
|
|
@@ -9330,16 +9346,27 @@ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb) {
|
|
|
9330
9346
|
});
|
|
9331
9347
|
return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
|
|
9332
9348
|
}
|
|
9333
|
-
function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
9334
|
-
return writeMarketplacePinsOnDisk(path2, pins, restoredPinsLine, "restore");
|
|
9335
|
-
}
|
|
9336
|
-
function applyOrgMarketplacePins(path2, names) {
|
|
9349
|
+
function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsRunning) {
|
|
9337
9350
|
return writeMarketplacePinsOnDisk(
|
|
9351
|
+
path2,
|
|
9352
|
+
pins,
|
|
9353
|
+
(restored) => hostIsRunning() ? `${restoredPinsLine(restored)}, but Claude Code is running and can rewrite this file from its own copy \u2014 restart it, then \`mmi-cli doctor --apply\` if the pins did not survive` : restoredPinsLine(restored),
|
|
9354
|
+
"restore"
|
|
9355
|
+
);
|
|
9356
|
+
}
|
|
9357
|
+
function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunning) {
|
|
9358
|
+
let landed = false;
|
|
9359
|
+
const detail = writeMarketplacePinsOnDisk(
|
|
9338
9360
|
path2,
|
|
9339
9361
|
new Map(names.map((name) => [name, ORG_MARKETPLACE_PINS])),
|
|
9340
|
-
(pins) =>
|
|
9341
|
-
|
|
9362
|
+
(pins) => {
|
|
9363
|
+
landed = true;
|
|
9364
|
+
return `pinned ${[...pins.keys()].join(", ")} to ${ORG_MARKETPLACE_PINS.ref} with auto-update on`;
|
|
9365
|
+
},
|
|
9366
|
+
"pin",
|
|
9367
|
+
() => hostIsRunning() ? "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor --apply`" : void 0
|
|
9342
9368
|
);
|
|
9369
|
+
return detail === void 0 ? void 0 : { detail, wrote: landed };
|
|
9343
9370
|
}
|
|
9344
9371
|
function writeMarketplacePinPending(path2, names, now = Date.now()) {
|
|
9345
9372
|
try {
|
|
@@ -22949,12 +22976,42 @@ var canonicalPriorityColors = { Urgent: "RED", High: "ORANGE", Medium: "YELLOW",
|
|
|
22949
22976
|
function miscoloredOptions(options, canon) {
|
|
22950
22977
|
return (options ?? []).filter((o) => canon[o.name] != null && o.color != null && o.color !== canon[o.name]).map((o) => `${o.name}=${o.color} (want ${canon[o.name]})`);
|
|
22951
22978
|
}
|
|
22979
|
+
function missingBoardViews(views, required) {
|
|
22980
|
+
return required.filter((req) => !views.some((v) => v.name === req.name && v.layout === req.layout));
|
|
22981
|
+
}
|
|
22982
|
+
function boardGroupingDrift(board, wantColumn, wantSwimlane) {
|
|
22983
|
+
if (!board) return ["no Board view found"];
|
|
22984
|
+
const problems = [];
|
|
22985
|
+
if (!board.verticalGroupByFields.includes(wantColumn)) {
|
|
22986
|
+
problems.push(`columns: ${board.verticalGroupByFields.join(", ") || "none"} (want ${wantColumn})`);
|
|
22987
|
+
}
|
|
22988
|
+
if (!board.groupByFields.includes(wantSwimlane)) {
|
|
22989
|
+
problems.push(`swimlanes: ${board.groupByFields.join(", ") || "none"} (want ${wantSwimlane})`);
|
|
22990
|
+
}
|
|
22991
|
+
return problems;
|
|
22992
|
+
}
|
|
22993
|
+
function boardCardFieldDrift(visibleFields, canonical) {
|
|
22994
|
+
const have = new Set(visibleFields);
|
|
22995
|
+
const want = new Set(canonical);
|
|
22996
|
+
return {
|
|
22997
|
+
missing: canonical.filter((f) => !have.has(f)),
|
|
22998
|
+
extra: visibleFields.filter((f) => !want.has(f))
|
|
22999
|
+
};
|
|
23000
|
+
}
|
|
22952
23001
|
var requiredProjectWorkflows = [
|
|
22953
23002
|
"Auto-add sub-issues to project",
|
|
22954
23003
|
"Auto-archive items",
|
|
22955
23004
|
"Item added to project",
|
|
22956
23005
|
"Item closed"
|
|
22957
23006
|
];
|
|
23007
|
+
var requiredBoardViews = [
|
|
23008
|
+
{ name: "List", layout: "TABLE_LAYOUT" },
|
|
23009
|
+
{ name: "Board", layout: "BOARD_LAYOUT" },
|
|
23010
|
+
{ name: "Roadmap", layout: "ROADMAP_LAYOUT" }
|
|
23011
|
+
];
|
|
23012
|
+
var requiredBoardColumnField = "Status";
|
|
23013
|
+
var requiredBoardSwimlaneField = "Repository";
|
|
23014
|
+
var requiredBoardCardFields = ["Title", "Assignees", "Status", "Labels", "Linked pull requests", "Parent issue", "Sub-issues progress", "Priority"];
|
|
22958
23015
|
var requiredOrgRulesetTypes = ["pull_request", "non_fast_forward", "deletion"];
|
|
22959
23016
|
var requiredHubStatusChecks = ["cli", "infra", "docs"];
|
|
22960
23017
|
var requiredProductStatusChecks = ["gate"];
|
|
@@ -23303,6 +23360,48 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
23303
23360
|
label: `Project workflow enabled: ${workflowName}`
|
|
23304
23361
|
});
|
|
23305
23362
|
}
|
|
23363
|
+
const viewsQuery = `query($login: String!, $number: Int!) { organization(login: $login) { projectV2(number: $number) { views(first: 10) { nodes { name layout groupByFields(first: 5) { nodes { ... on ProjectV2FieldCommon { name } } } verticalGroupByFields(first: 5) { nodes { ... on ProjectV2FieldCommon { name } } } visibleFields(first: 20) { nodes { ... on ProjectV2FieldCommon { name } } } } } } } }`;
|
|
23364
|
+
const boardViews = await (async () => {
|
|
23365
|
+
try {
|
|
23366
|
+
const data = await deps.client.graphql(viewsQuery, {
|
|
23367
|
+
login: config.projectOwner,
|
|
23368
|
+
number: config.projectNumber
|
|
23369
|
+
});
|
|
23370
|
+
const nodes = data.organization?.projectV2?.views?.nodes ?? [];
|
|
23371
|
+
const names = (conn) => (conn?.nodes ?? []).filter((f) => Boolean(f?.name)).map((f) => f.name);
|
|
23372
|
+
return nodes.filter((v) => Boolean(v)).map((v) => ({
|
|
23373
|
+
name: v.name,
|
|
23374
|
+
layout: v.layout,
|
|
23375
|
+
groupByFields: names(v.groupByFields),
|
|
23376
|
+
verticalGroupByFields: names(v.verticalGroupByFields),
|
|
23377
|
+
visibleFields: names(v.visibleFields)
|
|
23378
|
+
}));
|
|
23379
|
+
} catch {
|
|
23380
|
+
return [];
|
|
23381
|
+
}
|
|
23382
|
+
})();
|
|
23383
|
+
const missingViews = missingBoardViews(boardViews, requiredBoardViews);
|
|
23384
|
+
checks.push({
|
|
23385
|
+
ok: missingViews.length === 0,
|
|
23386
|
+
label: "Project view triple present: List/Board/Roadmap (#4093)",
|
|
23387
|
+
detail: missingViews.length ? `missing: ${missingViews.map((v) => `${v.name} (${v.layout})`).join(", ")} \u2014 createProjectV2View/updateProjectV2View is API-writable (name + layout)` : void 0
|
|
23388
|
+
});
|
|
23389
|
+
const boardView = boardViews.find((v) => v.layout === "BOARD_LAYOUT");
|
|
23390
|
+
const groupingDrift = boardGroupingDrift(boardView, requiredBoardColumnField, requiredBoardSwimlaneField);
|
|
23391
|
+
checks.push({
|
|
23392
|
+
ok: groupingDrift.length === 0,
|
|
23393
|
+
label: `Board view grouped by ${requiredBoardColumnField} columns / ${requiredBoardSwimlaneField} swimlanes (#4093)`,
|
|
23394
|
+
detail: groupingDrift.length ? `${groupingDrift.join("; ")} \u2014 GitHub exposes no create/update mutation for view grouping (ProjectV2ViewConfigurationInput carries only visibleFieldIds); fix in the UI: Board view \u2192 \u2699 (top-right) \u2192 Group by \u2192 ${requiredBoardColumnField}, Swimlanes \u2192 ${requiredBoardSwimlaneField} \u2192 Save view` : void 0
|
|
23395
|
+
});
|
|
23396
|
+
const cardDrift = boardCardFieldDrift(boardView?.visibleFields ?? [], requiredBoardCardFields);
|
|
23397
|
+
checks.push({
|
|
23398
|
+
ok: cardDrift.missing.length === 0 && cardDrift.extra.length === 0,
|
|
23399
|
+
label: "Board view card fields match the org standard (#4093)",
|
|
23400
|
+
detail: cardDrift.missing.length || cardDrift.extra.length ? `${[
|
|
23401
|
+
cardDrift.missing.length ? `missing: ${cardDrift.missing.join(", ")}` : null,
|
|
23402
|
+
cardDrift.extra.length ? `extra: ${cardDrift.extra.join(", ")}` : null
|
|
23403
|
+
].filter(Boolean).join("; ")} \u2014 API-writable via updateProjectV2View(configuration:{visibleFieldIds:[...]}); fix in the UI (Board view \u2192 Fields) or that mutation` : void 0
|
|
23404
|
+
});
|
|
23306
23405
|
}
|
|
23307
23406
|
const projectRegistry = localRegistryCheck(deps, "projects.json", (json) => Array.isArray(json?.projects) && projectRegistryIncludesRepo(json.projects, repo));
|
|
23308
23407
|
if (projectRegistry != null) checks.push({ ok: projectRegistry, label: "project registry includes repo" });
|
|
@@ -25480,6 +25579,88 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
25480
25579
|
}
|
|
25481
25580
|
}
|
|
25482
25581
|
|
|
25582
|
+
// src/pr-create-docs-check.ts
|
|
25583
|
+
var GIT_TIMEOUT_MS2 = 15e3;
|
|
25584
|
+
function createPrCreateDocsIndexDeps() {
|
|
25585
|
+
return {
|
|
25586
|
+
worktreeRoot: async () => {
|
|
25587
|
+
try {
|
|
25588
|
+
const { stdout } = await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS2 });
|
|
25589
|
+
return stdout.trim() || void 0;
|
|
25590
|
+
} catch {
|
|
25591
|
+
return void 0;
|
|
25592
|
+
}
|
|
25593
|
+
},
|
|
25594
|
+
originRepo: async (root) => {
|
|
25595
|
+
try {
|
|
25596
|
+
const { stdout } = await execFileP2("git", ["-C", root, "remote", "get-url", "origin"], { timeout: GIT_TIMEOUT_MS2 });
|
|
25597
|
+
return repoFromRemoteUrl(stdout.trim());
|
|
25598
|
+
} catch {
|
|
25599
|
+
return void 0;
|
|
25600
|
+
}
|
|
25601
|
+
},
|
|
25602
|
+
refResolves: async (root, ref) => {
|
|
25603
|
+
try {
|
|
25604
|
+
await execFileP2("git", ["-C", root, "rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { timeout: GIT_TIMEOUT_MS2 });
|
|
25605
|
+
return true;
|
|
25606
|
+
} catch {
|
|
25607
|
+
return false;
|
|
25608
|
+
}
|
|
25609
|
+
},
|
|
25610
|
+
listDocsAtRef: async (root, ref) => {
|
|
25611
|
+
try {
|
|
25612
|
+
const { stdout } = await execFileP2("git", ["-C", root, "ls-tree", "-r", "--name-only", ref, "--", "docs"], { timeout: GIT_TIMEOUT_MS2 });
|
|
25613
|
+
return stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
25614
|
+
} catch {
|
|
25615
|
+
return [];
|
|
25616
|
+
}
|
|
25617
|
+
},
|
|
25618
|
+
readAtRef: async (root, ref, path2) => {
|
|
25619
|
+
try {
|
|
25620
|
+
const { stdout } = await execFileP2("git", ["-C", root, "show", `${ref}:${path2}`], { timeout: GIT_TIMEOUT_MS2 });
|
|
25621
|
+
return stdout;
|
|
25622
|
+
} catch {
|
|
25623
|
+
return void 0;
|
|
25624
|
+
}
|
|
25625
|
+
}
|
|
25626
|
+
};
|
|
25627
|
+
}
|
|
25628
|
+
async function checkDocsIndexAtHead(opts, deps) {
|
|
25629
|
+
const root = await deps.worktreeRoot();
|
|
25630
|
+
if (!root) return void 0;
|
|
25631
|
+
if (opts.repo) {
|
|
25632
|
+
const origin = await deps.originRepo(root);
|
|
25633
|
+
if (!origin || origin.toLowerCase() !== opts.repo.toLowerCase()) return void 0;
|
|
25634
|
+
}
|
|
25635
|
+
const ref = opts.head || "HEAD";
|
|
25636
|
+
if (!await deps.refResolves(root, ref)) return void 0;
|
|
25637
|
+
const paths = await deps.listDocsAtRef(root, ref);
|
|
25638
|
+
if (!paths.includes(DOCS_INDEX_PATH)) return void 0;
|
|
25639
|
+
const relDocs = paths.filter((p) => p.startsWith("docs/") && p.endsWith(".md")).map((p) => p.slice("docs/".length)).filter(isRoutableDocsPath);
|
|
25640
|
+
const [indexContent, docContents] = await Promise.all([
|
|
25641
|
+
deps.readAtRef(root, ref, DOCS_INDEX_PATH),
|
|
25642
|
+
Promise.all(relDocs.map((rel) => deps.readAtRef(root, ref, `docs/${rel}`)))
|
|
25643
|
+
]);
|
|
25644
|
+
const contentByPath = new Map(relDocs.map((rel, i) => [rel, docContents[i] ?? ""]));
|
|
25645
|
+
const atRefDeps = {
|
|
25646
|
+
listDocs: () => relDocs,
|
|
25647
|
+
readDoc: (rel) => contentByPath.get(rel) ?? "",
|
|
25648
|
+
readIndex: () => indexContent ?? null,
|
|
25649
|
+
writeIndex: () => {
|
|
25650
|
+
throw new Error("checkDocsIndexAtHead: read-only \u2014 a commit ref is never written to");
|
|
25651
|
+
}
|
|
25652
|
+
};
|
|
25653
|
+
const result = docsIndex(atRefDeps, { check: true });
|
|
25654
|
+
if (!result.drift) {
|
|
25655
|
+
return { ok: true, detail: `${DOCS_INDEX_PATH} matches the docs/ tree at ${ref === "HEAD" ? "HEAD" : ref}` };
|
|
25656
|
+
}
|
|
25657
|
+
return {
|
|
25658
|
+
ok: false,
|
|
25659
|
+
detail: `${DOCS_INDEX_PATH} is stale at the commit this PR would open from \u2014 it no longer matches the docs/ tree there`,
|
|
25660
|
+
fix: "run `mmi-cli docs index --write`, commit docs/index.md, and push again before retrying `pr create`"
|
|
25661
|
+
};
|
|
25662
|
+
}
|
|
25663
|
+
|
|
25483
25664
|
// src/worktree-lifecycle-commands.ts
|
|
25484
25665
|
var import_node_fs31 = require("node:fs");
|
|
25485
25666
|
var import_promises9 = require("node:fs/promises");
|
|
@@ -28972,6 +29153,32 @@ function checkSessionPayload(probe) {
|
|
|
28972
29153
|
verbose: evidence
|
|
28973
29154
|
};
|
|
28974
29155
|
}
|
|
29156
|
+
function checkDocsIndex(probe) {
|
|
29157
|
+
if (!probe) return null;
|
|
29158
|
+
const evidence = [
|
|
29159
|
+
// The WORKING TREE, said out loud. This row reads disk and never asks git what is staged or committed,
|
|
29160
|
+
// so it must not claim to have measured the commit: a developer who ran `--write` and has not committed
|
|
29161
|
+
// has a current tree and a stale HEAD, and a row that said "committed: current" there would be the one
|
|
29162
|
+
// failure this check exists to prevent, one layer down. Closing that gap belongs to the surface that
|
|
29163
|
+
// knows about commits (`pr create`, #4092), not to a local hygiene reading.
|
|
29164
|
+
`${DOCS_INDEX_PATH}: read from the working tree, not from HEAD`,
|
|
29165
|
+
`records indexed: ${probe.docCount}`,
|
|
29166
|
+
// Named because it is the one thing a Windows operator reproducing a CI failure needs to trust the
|
|
29167
|
+
// verdict: the comparison normalizes CRLF, so this row and the Linux gate agree on the same content (#3411).
|
|
29168
|
+
"comparison: eol-insensitive, against the index the generator renders now"
|
|
29169
|
+
];
|
|
29170
|
+
if (!probe.drift) {
|
|
29171
|
+
return { ok: true, id: "docs-index", label: "docs index", detail: "current", verbose: evidence };
|
|
29172
|
+
}
|
|
29173
|
+
return {
|
|
29174
|
+
ok: false,
|
|
29175
|
+
id: "docs-index",
|
|
29176
|
+
label: "docs index",
|
|
29177
|
+
detail: "stale \u2014 docs/index.md no longer matches the docs/ tree",
|
|
29178
|
+
fix: "run `mmi-cli docs index --write` and commit docs/index.md",
|
|
29179
|
+
verbose: evidence
|
|
29180
|
+
};
|
|
29181
|
+
}
|
|
28975
29182
|
function planGitignore(current) {
|
|
28976
29183
|
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
28977
29184
|
return changed ? { ok: false, content } : { ok: true };
|
|
@@ -29119,8 +29326,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
29119
29326
|
if (applyEnv) {
|
|
29120
29327
|
const healed = deps.healMarketplacePins();
|
|
29121
29328
|
if (healed) {
|
|
29122
|
-
healIntent(`marketplace pins \u2014 ${healed}`);
|
|
29123
|
-
restartPending = true;
|
|
29329
|
+
healIntent(`marketplace pins \u2014 ${healed.detail}`);
|
|
29330
|
+
if (healed.wrote) restartPending = true;
|
|
29124
29331
|
}
|
|
29125
29332
|
}
|
|
29126
29333
|
for (const row of deps.marketplaceRows()) emitNow(row);
|
|
@@ -29139,6 +29346,28 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
29139
29346
|
});
|
|
29140
29347
|
}
|
|
29141
29348
|
}
|
|
29349
|
+
async function runDocsIndexRow() {
|
|
29350
|
+
let probe;
|
|
29351
|
+
try {
|
|
29352
|
+
probe = deps.docsIndexState(await deps.repoRoot());
|
|
29353
|
+
} catch (e) {
|
|
29354
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
29355
|
+
emitNow({
|
|
29356
|
+
ok: false,
|
|
29357
|
+
id: "docs-index",
|
|
29358
|
+
label: "docs index",
|
|
29359
|
+
// The measurement goes in `detail` and the one next action in `fix`, per docs/doctor-contract.md
|
|
29360
|
+
// § check model — never the error text stapled to the front of the advice, and never "re-run the
|
|
29361
|
+
// read that just failed" as the action.
|
|
29362
|
+
detail: `could not be read \u2014 ${message}`,
|
|
29363
|
+
fix: "repair the docs/ tree this repo cannot walk, then re-run doctor",
|
|
29364
|
+
verbose: [`probe threw: ${message}`]
|
|
29365
|
+
});
|
|
29366
|
+
return;
|
|
29367
|
+
}
|
|
29368
|
+
const docs2 = checkDocsIndex(probe);
|
|
29369
|
+
if (docs2) emitNow(docs2);
|
|
29370
|
+
}
|
|
29142
29371
|
async function runHousekeeperRows() {
|
|
29143
29372
|
const repoRoot2 = await deps.repoRoot();
|
|
29144
29373
|
try {
|
|
@@ -29209,6 +29438,9 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
29209
29438
|
{ id: "plugin-cache", when: true, run: runPluginCacheRow },
|
|
29210
29439
|
{ id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
|
|
29211
29440
|
{ id: "marketplace", when: true, run: runMarketplaceRows },
|
|
29441
|
+
// A `docs/` tree walk plus one batched `git check-ignore` — cheap next to the two rows below it, but a
|
|
29442
|
+
// disk walk and a subprocess all the same, so full lane only (#4091).
|
|
29443
|
+
{ id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
|
|
29212
29444
|
// The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
|
|
29213
29445
|
// and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
|
|
29214
29446
|
{ id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
|
|
@@ -29650,13 +29882,29 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29650
29882
|
const home = (0, import_node_os14.homedir)();
|
|
29651
29883
|
const names = [MMI_MARKETPLACE_NAME];
|
|
29652
29884
|
const result = applyOrgMarketplacePins((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
|
|
29653
|
-
if (result?.
|
|
29885
|
+
if (result?.wrote) {
|
|
29654
29886
|
writeMarketplacePinPending((0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
|
|
29655
29887
|
}
|
|
29656
29888
|
return result;
|
|
29657
29889
|
} catch {
|
|
29658
29890
|
return void 0;
|
|
29659
29891
|
}
|
|
29892
|
+
},
|
|
29893
|
+
// #4091: the same `docs index --check` comparison the required CI gate runs, in-process. Rooted at the
|
|
29894
|
+
// repo root doctor already resolved — never `process.cwd()`, which would have `mmi-cli doctor` run from
|
|
29895
|
+
// `cli/` measure a `cli/docs/` tree that does not exist.
|
|
29896
|
+
//
|
|
29897
|
+
// `existsSync` on `docs/index.md` is the adoption gate, and it sits UNDER the table's org-repo gate
|
|
29898
|
+
// rather than replacing it: a missing index is drift by construction, so without this a repo that never
|
|
29899
|
+
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
29900
|
+
// get a permanent ✗ demanding an artifact it never asked for.
|
|
29901
|
+
docsIndexState: (root) => {
|
|
29902
|
+
if (!(0, import_node_fs36.existsSync)((0, import_node_path35.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
29903
|
+
const real = createDocsIndexDeps(root);
|
|
29904
|
+
let docs2;
|
|
29905
|
+
const listDocs = () => docs2 ??= real.listDocs();
|
|
29906
|
+
const drift = docsIndex({ ...real, listDocs }, { check: true }).drift;
|
|
29907
|
+
return { drift, docCount: listDocs().length };
|
|
29660
29908
|
}
|
|
29661
29909
|
};
|
|
29662
29910
|
}
|
|
@@ -31501,6 +31749,10 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
31501
31749
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
31502
31750
|
}
|
|
31503
31751
|
body = normalizeClosingDirectives(body);
|
|
31752
|
+
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
31753
|
+
if (docsCheck && !docsCheck.ok) {
|
|
31754
|
+
return fail(`pr create: ${docsCheck.detail} \u2014 ${docsCheck.fix}`);
|
|
31755
|
+
}
|
|
31504
31756
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
31505
31757
|
console.log(JSON.stringify(created));
|
|
31506
31758
|
}), [
|
package/package.json
CHANGED