@mutmutco/cli 3.93.0 → 3.95.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 +1695 -574
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,8 +3416,8 @@ var program = new Command();
|
|
|
3416
3416
|
|
|
3417
3417
|
// src/index.ts
|
|
3418
3418
|
var import_promises11 = require("node:fs/promises");
|
|
3419
|
-
var
|
|
3420
|
-
var
|
|
3419
|
+
var import_node_fs39 = require("node:fs");
|
|
3420
|
+
var import_node_child_process18 = require("node:child_process");
|
|
3421
3421
|
|
|
3422
3422
|
// src/cli-shared.ts
|
|
3423
3423
|
var import_node_child_process3 = require("node:child_process");
|
|
@@ -6993,11 +6993,13 @@ function commandLadderHint() {
|
|
|
6993
6993
|
}
|
|
6994
6994
|
lines.push("[mmi] Schedules doctrine (#3228): scheduled jobs + LLM crons go through the Hub registry \u2014 eight-field header \u2192 PR \u2192 `mmi-cli org schedules register`; LLM crons ONLY via the harbour (llm: yes \u21D2 executor cursor-agent). Never hand-build an unregistered cron. Opt out per repo: `mmi-cli org schedules mode self-managed`.");
|
|
6995
6995
|
lines.push('[mmi] Registering \u2260 armed (#3281): `org schedules register` writes SCHEDULE# rows; the fleet-clock reconciler arms them within ~15 min. Report "registered successfully \u2014 arms on the next fleet-clock tick", never "armed"/"live". The success JSON carries an `arming` block. Before that window an absent clock means "not reconciled yet", NOT "will never fire" \u2014 verify with `mmi-cli org schedules` after it.');
|
|
6996
|
+
lines.push("[mmi] Current-state doors (#4120): prefer `org schedules`, `runtime box list`, `org project get`, `board`/`status`/`next`, `docs index` over markdown inventories or janitor prose \u2014 see docs/Architecture/compute-at-read.md.");
|
|
6997
|
+
lines.push("[mmi] Estate structure search (#4133): `mmi-cli repo-index search <q>` hits Hub cloud (lexical/hybrid; `--semantic` for meaning). Pointer hits only \u2014 never invent wiki prose or trust living `docs/**` current-state. Local rebuild is optional (`--local`); laptops do not index the estate.");
|
|
6996
6998
|
return lines.join("\n");
|
|
6997
6999
|
}
|
|
6998
7000
|
|
|
6999
7001
|
// src/index.ts
|
|
7000
|
-
var
|
|
7002
|
+
var import_node_path38 = require("node:path");
|
|
7001
7003
|
|
|
7002
7004
|
// src/merge-ci-policy.ts
|
|
7003
7005
|
function resolveMergeCiPolicy(input) {
|
|
@@ -11706,6 +11708,29 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
|
|
|
11706
11708
|
function sameContexts(left, right) {
|
|
11707
11709
|
return left.length === right.length && left.every((context, index) => context === right[index]);
|
|
11708
11710
|
}
|
|
11711
|
+
function resolveProductRulesetReconcilePlan(input) {
|
|
11712
|
+
const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
|
|
11713
|
+
const liveIsActive = input.liveEnforcement === "active";
|
|
11714
|
+
if (liveIsActive && !liveNeedsContextConvergence) {
|
|
11715
|
+
return { shouldActivate: false, targetEnforcement: "active" };
|
|
11716
|
+
}
|
|
11717
|
+
if (input.unsafeContexts.length > 0) {
|
|
11718
|
+
const unsafe = [...input.unsafeContexts];
|
|
11719
|
+
return {
|
|
11720
|
+
shouldActivate: false,
|
|
11721
|
+
targetEnforcement: "disabled",
|
|
11722
|
+
holdReason: `product ruleset left non-enforcing \u2014 [${unsafe.join(", ")}] ${unsafe.length === 1 ? "is" : "are"} emitted ONLY by path-filtered workflow(s), so the context is not reported on a PR outside those paths and requiring it would block that PR forever (#3836). Give the gate a companion job that runs unconditionally, then re-run this command`
|
|
11723
|
+
};
|
|
11724
|
+
}
|
|
11725
|
+
if (!input.gateProvenGreen) {
|
|
11726
|
+
return {
|
|
11727
|
+
shouldActivate: false,
|
|
11728
|
+
targetEnforcement: "disabled",
|
|
11729
|
+
holdReason: "product ruleset left non-enforcing \u2014 the gate is not green on development; activating it would block every PR (#3694)"
|
|
11730
|
+
};
|
|
11731
|
+
}
|
|
11732
|
+
return { shouldActivate: true, targetEnforcement: "active" };
|
|
11733
|
+
}
|
|
11709
11734
|
async function contentExists(deps, repo, branch, path2) {
|
|
11710
11735
|
try {
|
|
11711
11736
|
const encodedPath = path2.split("/").map(encodeURIComponent).join("/");
|
|
@@ -12409,46 +12434,39 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
12409
12434
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
12410
12435
|
}
|
|
12411
12436
|
const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
|
|
12412
|
-
const
|
|
12413
|
-
const
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
const
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
if (
|
|
12432
|
-
|
|
12433
|
-
result.
|
|
12434
|
-
return finalizeCiReconcile(repo, deps, result, report, reason);
|
|
12435
|
-
}
|
|
12436
|
-
}
|
|
12437
|
-
if (liveNeedsConvergence) {
|
|
12438
|
-
try {
|
|
12439
|
-
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, enforcement);
|
|
12440
|
-
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
12441
|
-
else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
12442
|
-
} catch (e) {
|
|
12443
|
-
result.errors.push(e.message);
|
|
12444
|
-
return finalizeCiReconcile(repo, deps, result, report);
|
|
12437
|
+
const prWorkflows = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, "deployable") ?? [];
|
|
12438
|
+
const gateFiles = gateWorkflowFiles(prWorkflows);
|
|
12439
|
+
const allPrWorkflows = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
|
|
12440
|
+
const bodies = [];
|
|
12441
|
+
for (const path2 of allPrWorkflows) {
|
|
12442
|
+
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
12443
|
+
if (body) bodies.push({ path: path2, body });
|
|
12444
|
+
}
|
|
12445
|
+
const filteredPaths = new Set(pathFilteredPullRequestWorkflows(bodies).filter((p) => !p.endsWith("/agent-pr.yml")));
|
|
12446
|
+
const safeContexts = new Set(collectPullRequestWorkflowContexts(bodies.filter((b) => !filteredPaths.has(b.path))));
|
|
12447
|
+
const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => authority.contexts.includes(c) && !safeContexts.has(c));
|
|
12448
|
+
const plan = resolveProductRulesetReconcilePlan({
|
|
12449
|
+
liveEnforcement: live?.enforcement,
|
|
12450
|
+
liveContexts,
|
|
12451
|
+
authorityContexts: authority.contexts,
|
|
12452
|
+
gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
|
|
12453
|
+
unsafeContexts: unsafe
|
|
12454
|
+
});
|
|
12455
|
+
if (!plan.shouldActivate) {
|
|
12456
|
+
if (plan.holdReason) {
|
|
12457
|
+
result.skipped.push(plan.holdReason);
|
|
12458
|
+
return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
|
|
12445
12459
|
}
|
|
12446
|
-
} else {
|
|
12447
12460
|
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts already match ${authority.source}`);
|
|
12461
|
+
return finalizeCiReconcile(repo, deps, result, report);
|
|
12448
12462
|
}
|
|
12449
|
-
|
|
12450
|
-
const
|
|
12451
|
-
|
|
12463
|
+
try {
|
|
12464
|
+
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
|
|
12465
|
+
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
12466
|
+
else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
12467
|
+
} catch (e) {
|
|
12468
|
+
result.errors.push(e.message);
|
|
12469
|
+
return finalizeCiReconcile(repo, deps, result, report);
|
|
12452
12470
|
}
|
|
12453
12471
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
12454
12472
|
}
|
|
@@ -13303,7 +13321,7 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
|
|
|
13303
13321
|
}
|
|
13304
13322
|
|
|
13305
13323
|
// src/index.ts
|
|
13306
|
-
var
|
|
13324
|
+
var import_node_os15 = require("node:os");
|
|
13307
13325
|
|
|
13308
13326
|
// src/board.ts
|
|
13309
13327
|
var import_node_child_process9 = require("node:child_process");
|
|
@@ -15089,9 +15107,9 @@ function parseSecretsUseArgv(tail) {
|
|
|
15089
15107
|
const flags = {};
|
|
15090
15108
|
const keys = [];
|
|
15091
15109
|
const firstSep = tail.indexOf("--");
|
|
15092
|
-
const
|
|
15093
|
-
const head =
|
|
15094
|
-
let command =
|
|
15110
|
+
const sep3 = firstSep !== -1 && separatorIsOurs(tail.slice(0, firstSep)) ? firstSep : -1;
|
|
15111
|
+
const head = sep3 === -1 ? tail : tail.slice(0, sep3);
|
|
15112
|
+
let command = sep3 === -1 ? [] : tail.slice(sep3 + 1).slice();
|
|
15095
15113
|
for (let i = 0; i < head.length; ) {
|
|
15096
15114
|
const tok = head[i];
|
|
15097
15115
|
const eq = tok.indexOf("=");
|
|
@@ -17215,11 +17233,11 @@ var PRIMARY_GROUPS = [
|
|
|
17215
17233
|
// `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
|
|
17216
17234
|
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
|
|
17217
17235
|
// joins them on the same footing (#3979).
|
|
17218
|
-
["Setup and support", ["bootstrap", "secrets", "docs", "tests", "spawn"]],
|
|
17236
|
+
["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn"]],
|
|
17219
17237
|
["Coordinate and improve", ["wave", "report", "skill-lesson"]]
|
|
17220
17238
|
];
|
|
17221
17239
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
|
|
17222
|
-
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "wave", "report", "skill-lesson"]);
|
|
17240
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "tests", "spawn", "wave", "report", "skill-lesson"]);
|
|
17223
17241
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
17224
17242
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
17225
17243
|
var topLevelPosition = 0;
|
|
@@ -17258,6 +17276,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
17258
17276
|
bootstrap: { module_owner: "cli/src/bootstrap-commands.ts", consumer: "repo-bootstrap" },
|
|
17259
17277
|
secrets: { module_owner: "cli/src/secrets-commands.ts", consumer: "authenticated-operator" },
|
|
17260
17278
|
docs: { module_owner: "cli/src/docs-index-command.ts", consumer: "repo-gates" },
|
|
17279
|
+
"repo-index": { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
|
|
17261
17280
|
tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
|
|
17262
17281
|
spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
|
|
17263
17282
|
wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
|
|
@@ -19282,210 +19301,866 @@ function renderAccessReport(report) {
|
|
|
19282
19301
|
return lines.join("\n");
|
|
19283
19302
|
}
|
|
19284
19303
|
|
|
19285
|
-
// src/
|
|
19304
|
+
// src/repo-index.ts
|
|
19305
|
+
var import_node_crypto4 = require("node:crypto");
|
|
19286
19306
|
var import_node_child_process11 = require("node:child_process");
|
|
19287
19307
|
var import_node_fs22 = require("node:fs");
|
|
19288
19308
|
var import_node_path20 = require("node:path");
|
|
19289
|
-
var
|
|
19290
|
-
var
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
/
|
|
19294
|
-
/
|
|
19295
|
-
|
|
19296
|
-
/\.
|
|
19297
|
-
/\.
|
|
19309
|
+
var REPO_INDEX_SCHEMA = 1;
|
|
19310
|
+
var HARD_DENY = [
|
|
19311
|
+
/(^|\/)\.env(\.|$)/i,
|
|
19312
|
+
/(^|\/)\.env\./i,
|
|
19313
|
+
/credentials/i,
|
|
19314
|
+
/secrets?\.json$/i,
|
|
19315
|
+
/\.pem$/i,
|
|
19316
|
+
/\.p12$/i,
|
|
19317
|
+
/\.key$/i,
|
|
19318
|
+
/(^|\/)id_rsa/i,
|
|
19319
|
+
/(^|\/)id_ed25519/i,
|
|
19320
|
+
/\.keystore$/i
|
|
19298
19321
|
];
|
|
19299
|
-
|
|
19300
|
-
|
|
19322
|
+
var INDEXABLE_EXT = /* @__PURE__ */ new Set([
|
|
19323
|
+
".ts",
|
|
19324
|
+
".tsx",
|
|
19325
|
+
".js",
|
|
19326
|
+
".jsx",
|
|
19327
|
+
".mjs",
|
|
19328
|
+
".cjs",
|
|
19329
|
+
".py",
|
|
19330
|
+
".go",
|
|
19331
|
+
".rs",
|
|
19332
|
+
".java",
|
|
19333
|
+
".kt",
|
|
19334
|
+
".md",
|
|
19335
|
+
".yml",
|
|
19336
|
+
".yaml",
|
|
19337
|
+
".json",
|
|
19338
|
+
".toml",
|
|
19339
|
+
".sh",
|
|
19340
|
+
".ps1",
|
|
19341
|
+
".css",
|
|
19342
|
+
".html"
|
|
19343
|
+
]);
|
|
19344
|
+
var SYMBOL_RE = /^(?:export\s+)?(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+([A-Za-z_$][\w$]*)/gm;
|
|
19345
|
+
function repoIndexStorePath(cwd) {
|
|
19346
|
+
return repoRuntimeStatePath(cwd, "repo-index", "index.json");
|
|
19347
|
+
}
|
|
19348
|
+
function isHardDeniedPath(relPosix) {
|
|
19349
|
+
return HARD_DENY.some((re) => re.test(relPosix));
|
|
19350
|
+
}
|
|
19351
|
+
function isIndexablePath(relPosix) {
|
|
19352
|
+
if (isHardDeniedPath(relPosix)) return false;
|
|
19353
|
+
if (relPosix.startsWith(".git/")) return false;
|
|
19354
|
+
if (relPosix.includes("node_modules/")) return false;
|
|
19355
|
+
if (relPosix.includes("dist/")) return false;
|
|
19356
|
+
const dot = relPosix.lastIndexOf(".");
|
|
19357
|
+
if (dot < 0) return false;
|
|
19358
|
+
return INDEXABLE_EXT.has(relPosix.slice(dot).toLowerCase());
|
|
19359
|
+
}
|
|
19360
|
+
function extractSymbols(source) {
|
|
19361
|
+
const out = /* @__PURE__ */ new Set();
|
|
19362
|
+
SYMBOL_RE.lastIndex = 0;
|
|
19363
|
+
let m;
|
|
19364
|
+
while ((m = SYMBOL_RE.exec(source)) !== null) {
|
|
19365
|
+
if (m[1]) out.add(m[1]);
|
|
19366
|
+
}
|
|
19367
|
+
return [...out].sort((a, b) => a.localeCompare(b));
|
|
19301
19368
|
}
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19369
|
+
var BLURB_CAP = 200;
|
|
19370
|
+
function extractModuleBlurb(source) {
|
|
19371
|
+
const lines = source.split(/\r?\n/);
|
|
19372
|
+
let i = 0;
|
|
19373
|
+
while (i < lines.length) {
|
|
19374
|
+
const t = lines[i].trim();
|
|
19375
|
+
if (!t || t.startsWith("#!") || /^['"]use (strict|client|server)['"]/.test(t)) {
|
|
19376
|
+
i++;
|
|
19377
|
+
continue;
|
|
19310
19378
|
}
|
|
19379
|
+
break;
|
|
19311
19380
|
}
|
|
19312
|
-
return
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
const call = new RegExp(CALL_SOURCE, "g");
|
|
19324
|
-
const found = [];
|
|
19325
|
-
let m;
|
|
19326
|
-
while (m = call.exec(src)) {
|
|
19327
|
-
const callee = m[2];
|
|
19328
|
-
if (!imported.has(callee)) continue;
|
|
19329
|
-
const open2 = m.index + m[0].length - 1;
|
|
19330
|
-
const args = callArgs(src, open2);
|
|
19331
|
-
if (!args) continue;
|
|
19332
|
-
if (args.includes("windowsHide")) continue;
|
|
19333
|
-
const line = src.slice(0, m.index + m[1].length).split("\n").length;
|
|
19334
|
-
if (/windows-hide-exempt:/.test(rawLines[line - 2] ?? "")) continue;
|
|
19335
|
-
if (/\.{3}\s*\w+/.test(args)) continue;
|
|
19336
|
-
if (/\b(opts|options|spawnOptions)\b/.test(args)) continue;
|
|
19337
|
-
const identifiers = new Set(args.match(/\b[A-Za-z_$][\w$]*\b/g) ?? []);
|
|
19338
|
-
let satisfied = false;
|
|
19339
|
-
for (const id of identifiers) {
|
|
19340
|
-
if (SPAWNERS.includes(id)) continue;
|
|
19341
|
-
const decl = new RegExp(String.raw`\b(?:const|let|var)\s+${id}\b[^;]*`, "s").exec(src);
|
|
19342
|
-
if (decl && decl[0].includes("windowsHide")) {
|
|
19343
|
-
satisfied = true;
|
|
19344
|
-
break;
|
|
19345
|
-
}
|
|
19346
|
-
if (new RegExp(String.raw`\b${id}\s*:\s*\{[^}]*windowsHide`, "s").test(src)) {
|
|
19347
|
-
satisfied = true;
|
|
19348
|
-
break;
|
|
19349
|
-
}
|
|
19381
|
+
if (i >= lines.length) return void 0;
|
|
19382
|
+
const first = lines[i].trim();
|
|
19383
|
+
if (first.startsWith("/**")) {
|
|
19384
|
+
const same = first.replace(/^\/\*\*\s*/, "").replace(/\*\/\s*$/, "").replace(/^\*\s?/, "").trim();
|
|
19385
|
+
if (same && !same.startsWith("*") && same !== "/") return same.slice(0, BLURB_CAP);
|
|
19386
|
+
for (let j = i + 1; j < Math.min(i + 12, lines.length); j++) {
|
|
19387
|
+
const raw = lines[j].trim();
|
|
19388
|
+
if (raw.startsWith("*/")) break;
|
|
19389
|
+
const content = raw.replace(/^\*\s?/, "").trim();
|
|
19390
|
+
if (!content || content.startsWith("@")) continue;
|
|
19391
|
+
return content.slice(0, BLURB_CAP);
|
|
19350
19392
|
}
|
|
19351
|
-
|
|
19352
|
-
found.push({ line, callee });
|
|
19393
|
+
return void 0;
|
|
19353
19394
|
}
|
|
19354
|
-
|
|
19395
|
+
if (first.startsWith("//")) {
|
|
19396
|
+
const content = first.replace(/^\/\/\s?/, "").trim();
|
|
19397
|
+
return content ? content.slice(0, BLURB_CAP) : void 0;
|
|
19398
|
+
}
|
|
19399
|
+
if (first.startsWith("#")) {
|
|
19400
|
+
const content = first.replace(/^#+\s?/, "").trim();
|
|
19401
|
+
return content ? content.slice(0, BLURB_CAP) : void 0;
|
|
19402
|
+
}
|
|
19403
|
+
return void 0;
|
|
19355
19404
|
}
|
|
19356
|
-
function
|
|
19357
|
-
const
|
|
19358
|
-
|
|
19359
|
-
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19405
|
+
function extractReadmeSectionHeaders(markdown, limit = 6) {
|
|
19406
|
+
const out = [];
|
|
19407
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
19408
|
+
const m = /^(#{1,3})\s+(.+?)\s*$/.exec(line.trim());
|
|
19409
|
+
if (!m?.[2]) continue;
|
|
19410
|
+
const title = m[2].replace(/#+\s*$/, "").trim();
|
|
19411
|
+
if (!title) continue;
|
|
19412
|
+
out.push(title);
|
|
19413
|
+
if (out.length >= limit) break;
|
|
19365
19414
|
}
|
|
19366
|
-
return
|
|
19415
|
+
return out;
|
|
19367
19416
|
}
|
|
19368
|
-
function
|
|
19369
|
-
const
|
|
19370
|
-
const
|
|
19371
|
-
|
|
19372
|
-
|
|
19417
|
+
function loadReadmeHints(cwd, candidatePaths) {
|
|
19418
|
+
const hints = /* @__PURE__ */ new Map();
|
|
19419
|
+
const readmes = /* @__PURE__ */ new Set();
|
|
19420
|
+
if (candidatePaths.includes("README.md")) readmes.add("README.md");
|
|
19421
|
+
for (const p of candidatePaths) {
|
|
19422
|
+
const parts = p.split("/");
|
|
19423
|
+
if (parts.length === 2 && parts[1] === "README.md" && parts[0]) readmes.add(p);
|
|
19424
|
+
}
|
|
19425
|
+
for (const rel of readmes) {
|
|
19426
|
+
if (isHardDeniedPath(rel)) continue;
|
|
19427
|
+
const abs = (0, import_node_path20.join)(cwd, ...rel.split("/"));
|
|
19428
|
+
if (!(0, import_node_fs22.existsSync)(abs)) continue;
|
|
19429
|
+
let text;
|
|
19373
19430
|
try {
|
|
19374
|
-
|
|
19431
|
+
text = (0, import_node_fs22.readFileSync)(abs, "utf8");
|
|
19375
19432
|
} catch {
|
|
19376
19433
|
continue;
|
|
19377
19434
|
}
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
detail: `${file}:${v.line} ${v.callee}(...) has no windowsHide`
|
|
19384
|
-
});
|
|
19385
|
-
}
|
|
19435
|
+
if (text.length > 2e5) continue;
|
|
19436
|
+
const headers = extractReadmeSectionHeaders(text);
|
|
19437
|
+
if (!headers.length) continue;
|
|
19438
|
+
const key = rel.includes("/") ? rel.split("/")[0] : "";
|
|
19439
|
+
hints.set(key, headers.join("; ").slice(0, BLURB_CAP));
|
|
19386
19440
|
}
|
|
19387
|
-
return
|
|
19441
|
+
return hints;
|
|
19388
19442
|
}
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
|
|
19412
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
out += ".*";
|
|
19419
|
-
i += 1;
|
|
19420
|
-
}
|
|
19421
|
-
} else {
|
|
19422
|
-
out += "[^/]*";
|
|
19423
|
-
}
|
|
19443
|
+
function toPosix(p) {
|
|
19444
|
+
return p.split(import_node_path20.sep).join("/");
|
|
19445
|
+
}
|
|
19446
|
+
function listCandidatePaths(cwd, exec = import_node_child_process11.execFileSync) {
|
|
19447
|
+
try {
|
|
19448
|
+
const out = exec("git", ["ls-files", "-z", "-c", "-o", "--exclude-standard"], {
|
|
19449
|
+
cwd,
|
|
19450
|
+
encoding: "utf8",
|
|
19451
|
+
maxBuffer: 64 * 1024 * 1024
|
|
19452
|
+
});
|
|
19453
|
+
return out.split("\0").filter(Boolean).map(toPosix);
|
|
19454
|
+
} catch {
|
|
19455
|
+
return [];
|
|
19456
|
+
}
|
|
19457
|
+
}
|
|
19458
|
+
function rebuildRepoIndex(cwd, repoSlug2) {
|
|
19459
|
+
const candidates = listCandidatePaths(cwd).filter(isIndexablePath);
|
|
19460
|
+
const ignored = defaultIsIgnored(cwd, candidates);
|
|
19461
|
+
const readmeHints = loadReadmeHints(cwd, candidates.filter((p) => !ignored.has(p)));
|
|
19462
|
+
const entries = [];
|
|
19463
|
+
for (const rel of candidates) {
|
|
19464
|
+
if (ignored.has(rel)) continue;
|
|
19465
|
+
if (isHardDeniedPath(rel)) continue;
|
|
19466
|
+
const abs = (0, import_node_path20.join)(cwd, ...rel.split("/"));
|
|
19467
|
+
if (!(0, import_node_fs22.existsSync)(abs)) continue;
|
|
19468
|
+
let text;
|
|
19469
|
+
try {
|
|
19470
|
+
text = (0, import_node_fs22.readFileSync)(abs, "utf8");
|
|
19471
|
+
} catch {
|
|
19424
19472
|
continue;
|
|
19425
19473
|
}
|
|
19426
|
-
if (
|
|
19427
|
-
|
|
19428
|
-
|
|
19429
|
-
|
|
19430
|
-
|
|
19431
|
-
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19474
|
+
if (text.length > 15e5) continue;
|
|
19475
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(text).digest("hex").slice(0, 16);
|
|
19476
|
+
const symbols = extractSymbols(text);
|
|
19477
|
+
const docBlurb = extractModuleBlurb(text);
|
|
19478
|
+
const top = rel.includes("/") ? rel.split("/")[0] : "";
|
|
19479
|
+
const hint = docBlurb ?? (top ? readmeHints.get(top) : void 0) ?? readmeHints.get("");
|
|
19480
|
+
entries.push({
|
|
19481
|
+
path: rel,
|
|
19482
|
+
hash,
|
|
19483
|
+
symbols,
|
|
19484
|
+
...hint ? { blurb: hint.slice(0, BLURB_CAP) } : {}
|
|
19485
|
+
});
|
|
19436
19486
|
}
|
|
19437
|
-
|
|
19487
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
19488
|
+
const projection = {
|
|
19489
|
+
schema: REPO_INDEX_SCHEMA,
|
|
19490
|
+
repo: repoSlug2,
|
|
19491
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19492
|
+
entries
|
|
19493
|
+
};
|
|
19494
|
+
const store = repoIndexStorePath(cwd);
|
|
19495
|
+
(0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(store), { recursive: true });
|
|
19496
|
+
(0, import_node_fs22.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
|
|
19497
|
+
`, "utf8");
|
|
19498
|
+
return projection;
|
|
19438
19499
|
}
|
|
19439
|
-
function
|
|
19440
|
-
|
|
19500
|
+
function loadRepoIndex(cwd) {
|
|
19501
|
+
const store = repoIndexStorePath(cwd);
|
|
19502
|
+
if (!(0, import_node_fs22.existsSync)(store)) return null;
|
|
19503
|
+
try {
|
|
19504
|
+
const raw = JSON.parse((0, import_node_fs22.readFileSync)(store, "utf8"));
|
|
19505
|
+
if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
|
|
19506
|
+
return raw;
|
|
19507
|
+
} catch {
|
|
19508
|
+
return null;
|
|
19509
|
+
}
|
|
19441
19510
|
}
|
|
19442
|
-
function
|
|
19443
|
-
|
|
19511
|
+
function repoIndexStatus(cwd) {
|
|
19512
|
+
const path2 = repoIndexStorePath(cwd);
|
|
19513
|
+
const idx = loadRepoIndex(cwd);
|
|
19514
|
+
if (!idx) return { present: false, path: path2 };
|
|
19515
|
+
const symbolCount = idx.entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
19516
|
+
return {
|
|
19517
|
+
present: true,
|
|
19518
|
+
path: path2,
|
|
19519
|
+
schema: idx.schema,
|
|
19520
|
+
repo: idx.repo,
|
|
19521
|
+
builtAt: idx.builtAt,
|
|
19522
|
+
fileCount: idx.entries.length,
|
|
19523
|
+
symbolCount
|
|
19524
|
+
};
|
|
19444
19525
|
}
|
|
19445
|
-
function
|
|
19446
|
-
const
|
|
19447
|
-
if (
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19526
|
+
function searchRepoIndex(idx, query, limit = 20) {
|
|
19527
|
+
const q = query.trim().toLowerCase();
|
|
19528
|
+
if (!q) return [];
|
|
19529
|
+
const hits = [];
|
|
19530
|
+
for (const e of idx.entries) {
|
|
19531
|
+
const pathLower = e.path.toLowerCase();
|
|
19532
|
+
if (pathLower.includes(q) || pathLower.endsWith(`/${q}`) || pathLower === q) {
|
|
19533
|
+
hits.push({
|
|
19534
|
+
repo: idx.repo,
|
|
19535
|
+
path: e.path,
|
|
19536
|
+
kind: "path",
|
|
19537
|
+
why: pathLower === q || pathLower.endsWith(`/${q}`) ? "exact path segment" : "path substring",
|
|
19538
|
+
score: pathLower === q ? 1 : pathLower.endsWith(`/${q}`) ? 0.95 : 0.7
|
|
19539
|
+
});
|
|
19540
|
+
}
|
|
19541
|
+
for (const sym of e.symbols) {
|
|
19542
|
+
const sl = sym.toLowerCase();
|
|
19543
|
+
if (sl === q || sl.startsWith(q)) {
|
|
19544
|
+
hits.push({
|
|
19545
|
+
repo: idx.repo,
|
|
19546
|
+
path: e.path,
|
|
19547
|
+
kind: "symbol",
|
|
19548
|
+
symbol: sym,
|
|
19549
|
+
why: sl === q ? "exact symbol" : "symbol prefix",
|
|
19550
|
+
score: sl === q ? 1 : 0.85
|
|
19551
|
+
});
|
|
19552
|
+
}
|
|
19553
|
+
}
|
|
19554
|
+
}
|
|
19555
|
+
hits.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
19556
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19557
|
+
const out = [];
|
|
19558
|
+
for (const h of hits) {
|
|
19559
|
+
const key = `${h.kind}:${h.path}:${h.symbol ?? ""}`;
|
|
19560
|
+
if (seen.has(key)) continue;
|
|
19561
|
+
seen.add(key);
|
|
19562
|
+
out.push(h);
|
|
19563
|
+
if (out.length >= limit) break;
|
|
19452
19564
|
}
|
|
19565
|
+
return out;
|
|
19453
19566
|
}
|
|
19454
|
-
function
|
|
19567
|
+
function inferRepoSlug(cwd, exec = import_node_child_process11.execFileSync) {
|
|
19455
19568
|
try {
|
|
19456
|
-
|
|
19569
|
+
const url = String(exec("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" })).trim();
|
|
19570
|
+
const m = /[:/]([^/]+\/[^/]+?)(?:\.git)?$/.exec(url);
|
|
19571
|
+
if (m?.[1]) return m[1];
|
|
19457
19572
|
} catch {
|
|
19458
|
-
return null;
|
|
19459
19573
|
}
|
|
19574
|
+
return (0, import_node_path20.basename)(cwd) || "local";
|
|
19460
19575
|
}
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
|
|
19464
|
-
|
|
19465
|
-
}
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
|
|
19576
|
+
|
|
19577
|
+
// src/repo-index-cloud-client.ts
|
|
19578
|
+
var RETRY_ATTEMPTS2 = 3;
|
|
19579
|
+
async function publishRepoIndexCloud(payload, deps) {
|
|
19580
|
+
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
19581
|
+
const token = await deps.token();
|
|
19582
|
+
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
19583
|
+
try {
|
|
19584
|
+
const res = await fetchWithRetry(
|
|
19585
|
+
deps.fetch ?? fetch,
|
|
19586
|
+
`${deps.baseUrl.replace(/\/$/, "")}/repo-index/publish`,
|
|
19587
|
+
{
|
|
19588
|
+
method: "POST",
|
|
19589
|
+
headers: {
|
|
19590
|
+
...clientVersionHeaders(),
|
|
19591
|
+
Authorization: `Bearer ${token}`,
|
|
19592
|
+
"content-type": "application/json"
|
|
19593
|
+
},
|
|
19594
|
+
body: JSON.stringify(payload)
|
|
19595
|
+
},
|
|
19596
|
+
{
|
|
19597
|
+
attempts: RETRY_ATTEMPTS2,
|
|
19598
|
+
// Embed publish can take longer than a registry read.
|
|
19599
|
+
timeoutMs: payload.embed ? 12e4 : deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS,
|
|
19600
|
+
sleep: deps.retrySleep
|
|
19601
|
+
}
|
|
19602
|
+
);
|
|
19603
|
+
if (res.status === 426) return { ok: false, error: upgradeRequiredError(res, await res.json().catch(() => null)), status: 426 };
|
|
19604
|
+
const body = await res.json().catch(() => ({}));
|
|
19605
|
+
if (!res.ok) {
|
|
19606
|
+
return { ok: false, error: typeof body.error === "string" ? body.error : `publish HTTP ${res.status}`, status: res.status };
|
|
19478
19607
|
}
|
|
19608
|
+
return { ok: true, body };
|
|
19609
|
+
} catch (e) {
|
|
19610
|
+
return { ok: false, error: e.message };
|
|
19479
19611
|
}
|
|
19480
|
-
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
19481
|
-
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
19482
19612
|
}
|
|
19483
|
-
function
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19487
|
-
const
|
|
19488
|
-
|
|
19613
|
+
async function searchRepoIndexCloud(query, opts, deps) {
|
|
19614
|
+
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
19615
|
+
const token = await deps.token();
|
|
19616
|
+
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
19617
|
+
const params = new URLSearchParams({
|
|
19618
|
+
q: query,
|
|
19619
|
+
mode: opts.mode ?? "hybrid",
|
|
19620
|
+
limit: String(opts.limit ?? 20)
|
|
19621
|
+
});
|
|
19622
|
+
if (opts.repo) params.set("repo", opts.repo);
|
|
19623
|
+
try {
|
|
19624
|
+
const res = await fetchWithRetry(
|
|
19625
|
+
deps.fetch ?? fetch,
|
|
19626
|
+
`${deps.baseUrl.replace(/\/$/, "")}/repo-index/search?${params}`,
|
|
19627
|
+
{ method: "GET", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}` } },
|
|
19628
|
+
{ attempts: RETRY_ATTEMPTS2, timeoutMs: opts.mode === "semantic" || opts.mode === "hybrid" ? 6e4 : deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep }
|
|
19629
|
+
);
|
|
19630
|
+
if (res.status === 426) return { ok: false, error: upgradeRequiredError(res, await res.json().catch(() => null)), status: 426 };
|
|
19631
|
+
const body = await res.json().catch(() => ({}));
|
|
19632
|
+
if (!res.ok) return { ok: false, error: body.error ?? `search HTTP ${res.status}`, status: res.status };
|
|
19633
|
+
return {
|
|
19634
|
+
ok: true,
|
|
19635
|
+
query: body.query ?? query,
|
|
19636
|
+
mode: body.mode ?? opts.mode ?? "hybrid",
|
|
19637
|
+
hits: Array.isArray(body.hits) ? body.hits : [],
|
|
19638
|
+
note: body.note
|
|
19639
|
+
};
|
|
19640
|
+
} catch (e) {
|
|
19641
|
+
return { ok: false, error: e.message };
|
|
19642
|
+
}
|
|
19643
|
+
}
|
|
19644
|
+
function isRepoIndexStatusError(st) {
|
|
19645
|
+
return typeof st === "object" && st !== null && st.ok === false && typeof st.error === "string" && typeof st.code === "string";
|
|
19646
|
+
}
|
|
19647
|
+
function explainRepoIndexStatusError(err) {
|
|
19648
|
+
switch (err.code) {
|
|
19649
|
+
case "deploy-lag":
|
|
19650
|
+
return "repo-index status: the Hub API did not recognize /repo-index/status (HTTP 404). The deployed Hub predates the estate index routes \u2014 not an auth error. Ship a Hub release from development that includes /repo-index/*, then run `repo-index-reconcile` (or `mmi-cli repo-index sync-estate`) so projections exist.";
|
|
19651
|
+
case "auth":
|
|
19652
|
+
return `repo-index status: ${err.error} \u2014 run \`gh auth login\` / refresh the Hub session.`;
|
|
19653
|
+
case "config":
|
|
19654
|
+
return `repo-index status: ${err.error}`;
|
|
19655
|
+
default:
|
|
19656
|
+
return `repo-index status: ${err.error}`;
|
|
19657
|
+
}
|
|
19658
|
+
}
|
|
19659
|
+
async function statusRepoIndexCloud(repo, deps) {
|
|
19660
|
+
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured", code: "config" };
|
|
19661
|
+
const token = await deps.token();
|
|
19662
|
+
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)", code: "auth" };
|
|
19663
|
+
const params = new URLSearchParams();
|
|
19664
|
+
if (repo) params.set("repo", repo);
|
|
19665
|
+
const q = params.toString();
|
|
19666
|
+
try {
|
|
19667
|
+
const res = await fetchWithRetry(
|
|
19668
|
+
deps.fetch ?? fetch,
|
|
19669
|
+
`${deps.baseUrl.replace(/\/$/, "")}/repo-index/status${q ? `?${q}` : ""}`,
|
|
19670
|
+
{ method: "GET", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}` } },
|
|
19671
|
+
{ attempts: RETRY_ATTEMPTS2, timeoutMs: deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep }
|
|
19672
|
+
);
|
|
19673
|
+
if (res.status === 401 || res.status === 403) {
|
|
19674
|
+
return { ok: false, error: `status HTTP ${res.status}`, code: "auth", status: res.status };
|
|
19675
|
+
}
|
|
19676
|
+
if (res.status === 404) {
|
|
19677
|
+
return {
|
|
19678
|
+
ok: false,
|
|
19679
|
+
error: "the Hub API did not recognize /repo-index/status (HTTP 404) \u2014 deployed Hub predates estate index",
|
|
19680
|
+
code: "deploy-lag",
|
|
19681
|
+
status: 404
|
|
19682
|
+
};
|
|
19683
|
+
}
|
|
19684
|
+
if (!res.ok) return { ok: false, error: `status HTTP ${res.status}`, code: "http", status: res.status };
|
|
19685
|
+
return await res.json();
|
|
19686
|
+
} catch (e) {
|
|
19687
|
+
return { ok: false, error: e.message, code: "network" };
|
|
19688
|
+
}
|
|
19689
|
+
}
|
|
19690
|
+
async function gcRepoIndexCloud(deps) {
|
|
19691
|
+
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
19692
|
+
const token = await deps.token();
|
|
19693
|
+
if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
|
|
19694
|
+
try {
|
|
19695
|
+
const res = await fetchWithRetry(
|
|
19696
|
+
deps.fetch ?? fetch,
|
|
19697
|
+
`${deps.baseUrl.replace(/\/$/, "")}/repo-index/gc`,
|
|
19698
|
+
{
|
|
19699
|
+
method: "POST",
|
|
19700
|
+
headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
19701
|
+
body: "{}"
|
|
19702
|
+
},
|
|
19703
|
+
{ attempts: RETRY_ATTEMPTS2, timeoutMs: deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep }
|
|
19704
|
+
);
|
|
19705
|
+
const body = await res.json().catch(() => ({}));
|
|
19706
|
+
if (!res.ok) return { ok: false, error: body.error ?? `gc HTTP ${res.status}` };
|
|
19707
|
+
return { ok: true, removed: body.removed ?? [] };
|
|
19708
|
+
} catch (e) {
|
|
19709
|
+
return { ok: false, error: e.message };
|
|
19710
|
+
}
|
|
19711
|
+
}
|
|
19712
|
+
|
|
19713
|
+
// src/repo-index-sync.ts
|
|
19714
|
+
var import_node_fs23 = require("node:fs");
|
|
19715
|
+
var import_node_os9 = require("node:os");
|
|
19716
|
+
var import_node_path21 = require("node:path");
|
|
19717
|
+
var import_node_child_process12 = require("node:child_process");
|
|
19718
|
+
var MAX_EMBED_BACKFILL_ROUNDS = 40;
|
|
19719
|
+
function normalizeRepo(raw) {
|
|
19720
|
+
const t = raw.trim().replace(/\.git$/, "");
|
|
19721
|
+
if (t.includes("/")) return t;
|
|
19722
|
+
return `mutmutco/${t}`;
|
|
19723
|
+
}
|
|
19724
|
+
function rosterRepos(projects) {
|
|
19725
|
+
const set = /* @__PURE__ */ new Set();
|
|
19726
|
+
for (const p of projects) {
|
|
19727
|
+
if (p.class === "content") continue;
|
|
19728
|
+
for (const r of p.repos ?? []) set.add(normalizeRepo(r));
|
|
19729
|
+
}
|
|
19730
|
+
return [...set].sort((a, b) => a.localeCompare(b));
|
|
19731
|
+
}
|
|
19732
|
+
function shallowClone(repo, dest, token) {
|
|
19733
|
+
(0, import_node_child_process12.execFileSync)(
|
|
19734
|
+
"git",
|
|
19735
|
+
["-c", `http.extraHeader=Authorization: Bearer ${token}`, "clone", "--depth", "1", "--single-branch", `https://github.com/${repo}.git`, dest],
|
|
19736
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
|
19737
|
+
);
|
|
19738
|
+
}
|
|
19739
|
+
async function syncEstateRepoIndex(opts) {
|
|
19740
|
+
const projects = await fetchProjectsList(opts.deps);
|
|
19741
|
+
if (!projects) {
|
|
19742
|
+
return { ok: false, published: [], failed: [{ repo: "*", error: "could not read /projects/list" }], skipped: [] };
|
|
19743
|
+
}
|
|
19744
|
+
let repos = rosterRepos(projects);
|
|
19745
|
+
if (opts.repo) {
|
|
19746
|
+
const want = normalizeRepo(opts.repo);
|
|
19747
|
+
repos = repos.filter((r) => r.toLowerCase() === want.toLowerCase());
|
|
19748
|
+
if (repos.length === 0) {
|
|
19749
|
+
return { ok: false, published: [], failed: [{ repo: want, error: "not on registry code roster" }], skipped: [] };
|
|
19750
|
+
}
|
|
19751
|
+
}
|
|
19752
|
+
const published = [];
|
|
19753
|
+
const failed = [];
|
|
19754
|
+
const skipped = [];
|
|
19755
|
+
for (const repo of repos) {
|
|
19756
|
+
const dir = (0, import_node_fs23.mkdtempSync)((0, import_node_path21.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
|
|
19757
|
+
try {
|
|
19758
|
+
shallowClone(repo, dir, opts.githubToken);
|
|
19759
|
+
const built = rebuildRepoIndex(dir, repo);
|
|
19760
|
+
let pub = await publishRepoIndexCloud(
|
|
19761
|
+
{
|
|
19762
|
+
repo,
|
|
19763
|
+
builtAt: built.builtAt,
|
|
19764
|
+
entries: built.entries,
|
|
19765
|
+
embed: opts.embed === true
|
|
19766
|
+
},
|
|
19767
|
+
opts.deps
|
|
19768
|
+
);
|
|
19769
|
+
if (!pub.ok) {
|
|
19770
|
+
failed.push({ repo, error: pub.error });
|
|
19771
|
+
continue;
|
|
19772
|
+
}
|
|
19773
|
+
let rounds = 1;
|
|
19774
|
+
while (opts.embed === true && pub.ok && pub.body.embTruncated === true && rounds < MAX_EMBED_BACKFILL_ROUNDS) {
|
|
19775
|
+
pub = await publishRepoIndexCloud(
|
|
19776
|
+
{
|
|
19777
|
+
repo,
|
|
19778
|
+
builtAt: built.builtAt,
|
|
19779
|
+
entries: built.entries,
|
|
19780
|
+
embed: true
|
|
19781
|
+
},
|
|
19782
|
+
opts.deps
|
|
19783
|
+
);
|
|
19784
|
+
if (!pub.ok) {
|
|
19785
|
+
failed.push({ repo, error: pub.error });
|
|
19786
|
+
break;
|
|
19787
|
+
}
|
|
19788
|
+
rounds++;
|
|
19789
|
+
}
|
|
19790
|
+
if (!pub.ok) continue;
|
|
19791
|
+
const fileCount = Number(pub.body.fileCount ?? built.entries.length);
|
|
19792
|
+
const embCount = typeof pub.body.embCount === "number" ? pub.body.embCount : void 0;
|
|
19793
|
+
const embGap = typeof pub.body.embGap === "number" ? pub.body.embGap : typeof embCount === "number" ? Math.max(0, fileCount - embCount) : void 0;
|
|
19794
|
+
published.push({
|
|
19795
|
+
repo,
|
|
19796
|
+
fileCount,
|
|
19797
|
+
embCount,
|
|
19798
|
+
embGap,
|
|
19799
|
+
embTruncated: pub.body.embTruncated === true,
|
|
19800
|
+
embedRounds: opts.embed ? rounds : void 0
|
|
19801
|
+
});
|
|
19802
|
+
} catch (e) {
|
|
19803
|
+
failed.push({ repo, error: e.message });
|
|
19804
|
+
} finally {
|
|
19805
|
+
try {
|
|
19806
|
+
(0, import_node_fs23.rmSync)(dir, { recursive: true, force: true });
|
|
19807
|
+
} catch {
|
|
19808
|
+
}
|
|
19809
|
+
}
|
|
19810
|
+
}
|
|
19811
|
+
return { ok: failed.length === 0, published, failed, skipped };
|
|
19812
|
+
}
|
|
19813
|
+
|
|
19814
|
+
// src/repo-index-health.ts
|
|
19815
|
+
var import_node_fs24 = require("node:fs");
|
|
19816
|
+
var import_node_path22 = require("node:path");
|
|
19817
|
+
function defaultGoldenPath(repoRoot2) {
|
|
19818
|
+
return (0, import_node_path22.join)(repoRoot2, "cli", "testdata", "repo-index-golden-queries.json");
|
|
19819
|
+
}
|
|
19820
|
+
function loadGoldenSuite(path2) {
|
|
19821
|
+
const raw = JSON.parse((0, import_node_fs24.readFileSync)(path2, "utf8"));
|
|
19822
|
+
if (!raw || raw.schema !== 1 || !Array.isArray(raw.queries)) {
|
|
19823
|
+
throw new Error(`invalid golden suite at ${path2}`);
|
|
19824
|
+
}
|
|
19825
|
+
return raw;
|
|
19826
|
+
}
|
|
19827
|
+
function evaluateQueryHits(query, hits) {
|
|
19828
|
+
if (!hits.length) {
|
|
19829
|
+
return { ok: false, code: "empty-hits", detail: `${query.id}: no hits for ${JSON.stringify(query.q)}` };
|
|
19830
|
+
}
|
|
19831
|
+
const top = hits[0];
|
|
19832
|
+
const minScore = query.expect.minScore ?? 0;
|
|
19833
|
+
if (typeof top.score === "number" && top.score < minScore) {
|
|
19834
|
+
return {
|
|
19835
|
+
ok: false,
|
|
19836
|
+
code: "low-score",
|
|
19837
|
+
detail: `${query.id}: top score ${top.score} < ${minScore}`
|
|
19838
|
+
};
|
|
19839
|
+
}
|
|
19840
|
+
if (query.expect.topRepos?.length) {
|
|
19841
|
+
const repo = String(top.repo ?? "");
|
|
19842
|
+
if (!query.expect.topRepos.some((r) => r.toLowerCase() === repo.toLowerCase())) {
|
|
19843
|
+
return {
|
|
19844
|
+
ok: false,
|
|
19845
|
+
code: "wrong-repo",
|
|
19846
|
+
detail: `${query.id}: top repo ${JSON.stringify(repo)} not in ${JSON.stringify(query.expect.topRepos)}`
|
|
19847
|
+
};
|
|
19848
|
+
}
|
|
19849
|
+
}
|
|
19850
|
+
if (query.expect.topPaths?.length) {
|
|
19851
|
+
const path2 = String(top.path ?? "");
|
|
19852
|
+
if (!query.expect.topPaths.some((p) => path2 === p || path2.endsWith(p) || path2.includes(p))) {
|
|
19853
|
+
return {
|
|
19854
|
+
ok: false,
|
|
19855
|
+
code: "wrong-path",
|
|
19856
|
+
detail: `${query.id}: top path ${JSON.stringify(path2)} not in ${JSON.stringify(query.expect.topPaths)}`
|
|
19857
|
+
};
|
|
19858
|
+
}
|
|
19859
|
+
}
|
|
19860
|
+
return { ok: true, code: "ok", detail: `${query.id}: top hit accepted` };
|
|
19861
|
+
}
|
|
19862
|
+
function evaluateCloudStatus(status, golden) {
|
|
19863
|
+
const findings = [];
|
|
19864
|
+
const count = typeof status.count === "number" ? status.count : Array.isArray(status.repos) ? status.repos.length : null;
|
|
19865
|
+
if (count === 0 || Array.isArray(status.repos) && status.repos.length === 0 && status.present !== true) {
|
|
19866
|
+
findings.push({ ok: false, code: "projections-empty", detail: "cloud repo-index has zero projections" });
|
|
19867
|
+
}
|
|
19868
|
+
const repos = [];
|
|
19869
|
+
if (Array.isArray(status.repos)) {
|
|
19870
|
+
for (const r of status.repos) {
|
|
19871
|
+
if (r && typeof r === "object") repos.push(r);
|
|
19872
|
+
}
|
|
19873
|
+
} else if (status.present === true && typeof status.repo === "string") {
|
|
19874
|
+
repos.push(status);
|
|
19875
|
+
}
|
|
19876
|
+
const stale = repos.filter((r) => r.stale === true);
|
|
19877
|
+
if (stale.length) {
|
|
19878
|
+
findings.push({
|
|
19879
|
+
ok: false,
|
|
19880
|
+
code: "stale-projections",
|
|
19881
|
+
detail: `${stale.length} stale projection(s) (threshold ${golden.staleThresholdHours}h): ${stale.slice(0, 5).map((r) => r.repo).join(", ")}`
|
|
19882
|
+
});
|
|
19883
|
+
}
|
|
19884
|
+
const hub = repos.find((r) => r.repo?.toLowerCase() === golden.hubRepo.toLowerCase()) ?? (typeof status.repo === "string" && status.repo.toLowerCase() === golden.hubRepo.toLowerCase() ? status : void 0);
|
|
19885
|
+
if (!hub || hub.present === false) {
|
|
19886
|
+
findings.push({ ok: false, code: "hub-missing", detail: `${golden.hubRepo} projection missing` });
|
|
19887
|
+
} else {
|
|
19888
|
+
const fileCount = Number(hub.fileCount ?? 0);
|
|
19889
|
+
const embCount = Number(hub.embCount ?? 0);
|
|
19890
|
+
if (fileCount <= 0) {
|
|
19891
|
+
findings.push({ ok: false, code: "hub-empty", detail: `${golden.hubRepo} has fileCount=${fileCount}` });
|
|
19892
|
+
}
|
|
19893
|
+
const denom = Math.min(fileCount, golden.maxSyncEmbeds);
|
|
19894
|
+
const coverage = denom > 0 ? embCount / denom : 0;
|
|
19895
|
+
if (fileCount > 0 && coverage < golden.hubEmbMinCoverage) {
|
|
19896
|
+
findings.push({
|
|
19897
|
+
ok: false,
|
|
19898
|
+
code: "hub-emb-gap",
|
|
19899
|
+
detail: `${golden.hubRepo} embCoverage=${coverage.toFixed(2)} (emb=${embCount} files=${fileCount} cap=${golden.maxSyncEmbeds}) < ${golden.hubEmbMinCoverage}`
|
|
19900
|
+
});
|
|
19901
|
+
}
|
|
19902
|
+
}
|
|
19903
|
+
if (!findings.length) {
|
|
19904
|
+
findings.push({ ok: true, code: "status-ok", detail: `status healthy (${repos.length || count || 0} repo projection(s))` });
|
|
19905
|
+
}
|
|
19906
|
+
return findings;
|
|
19907
|
+
}
|
|
19908
|
+
async function runRepoIndexHealth(opts) {
|
|
19909
|
+
const findings = [];
|
|
19910
|
+
if (opts.live) {
|
|
19911
|
+
const st = await opts.live.status();
|
|
19912
|
+
if (st.ok === false && typeof st.error === "string") {
|
|
19913
|
+
findings.push({ ok: false, code: "status-error", detail: st.error });
|
|
19914
|
+
} else {
|
|
19915
|
+
findings.push(...evaluateCloudStatus(st, opts.golden));
|
|
19916
|
+
}
|
|
19917
|
+
for (const q of opts.golden.queries) {
|
|
19918
|
+
const res = await opts.live.search(q.q, q.mode);
|
|
19919
|
+
if (!res.ok) {
|
|
19920
|
+
const semantic503 = q.mode === "semantic" && (res.status === 503 || /semantic unavailable|503/i.test(res.error));
|
|
19921
|
+
findings.push({
|
|
19922
|
+
ok: false,
|
|
19923
|
+
code: semantic503 ? "semantic-503" : "search-error",
|
|
19924
|
+
detail: `${q.id}: ${res.error}`
|
|
19925
|
+
});
|
|
19926
|
+
continue;
|
|
19927
|
+
}
|
|
19928
|
+
findings.push(evaluateQueryHits(q, res.hits));
|
|
19929
|
+
}
|
|
19930
|
+
} else if (opts.fixtures) {
|
|
19931
|
+
for (const q of opts.golden.queries) {
|
|
19932
|
+
const fx = opts.fixtures[q.id];
|
|
19933
|
+
if (!fx) {
|
|
19934
|
+
findings.push({ ok: false, code: "fixture-missing", detail: `${q.id}: no fixture` });
|
|
19935
|
+
continue;
|
|
19936
|
+
}
|
|
19937
|
+
if (fx.error) {
|
|
19938
|
+
const semantic503 = q.mode === "semantic" && (fx.status === 503 || /semantic unavailable|503/i.test(fx.error));
|
|
19939
|
+
findings.push({
|
|
19940
|
+
ok: false,
|
|
19941
|
+
code: semantic503 ? "semantic-503" : "search-error",
|
|
19942
|
+
detail: `${q.id}: ${fx.error}`
|
|
19943
|
+
});
|
|
19944
|
+
continue;
|
|
19945
|
+
}
|
|
19946
|
+
findings.push(evaluateQueryHits(q, fx.hits ?? []));
|
|
19947
|
+
}
|
|
19948
|
+
} else {
|
|
19949
|
+
for (const q of opts.golden.queries) {
|
|
19950
|
+
if (!q.id || !q.q || !q.mode) {
|
|
19951
|
+
findings.push({ ok: false, code: "golden-invalid", detail: `query missing id/q/mode` });
|
|
19952
|
+
} else {
|
|
19953
|
+
findings.push({ ok: true, code: "golden-ok", detail: `${q.id}: shape ok` });
|
|
19954
|
+
}
|
|
19955
|
+
}
|
|
19956
|
+
}
|
|
19957
|
+
return { ok: findings.every((f) => f.ok), findings };
|
|
19958
|
+
}
|
|
19959
|
+
|
|
19960
|
+
// src/spawn-policy-core.ts
|
|
19961
|
+
var import_node_child_process13 = require("node:child_process");
|
|
19962
|
+
var import_node_fs25 = require("node:fs");
|
|
19963
|
+
var import_node_path23 = require("node:path");
|
|
19964
|
+
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
19965
|
+
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
19966
|
+
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
19967
|
+
var EXCLUDED = [
|
|
19968
|
+
/(^|\/)node_modules\//,
|
|
19969
|
+
/(^|\/)(dist|build|coverage)\//,
|
|
19970
|
+
/(^|\/)(test|tests|__tests__|fixtures|__fixtures__)\//,
|
|
19971
|
+
/\.(test|spec)\.[cm]?[jt]s$/,
|
|
19972
|
+
/\.d\.[cm]?ts$/
|
|
19973
|
+
];
|
|
19974
|
+
function stripComments(src) {
|
|
19975
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/g, (m, p) => p + " ".repeat(m.length - p.length));
|
|
19976
|
+
}
|
|
19977
|
+
function callArgs(src, open2) {
|
|
19978
|
+
let depth = 0;
|
|
19979
|
+
for (let i = open2; i < src.length; i++) {
|
|
19980
|
+
const c = src[i];
|
|
19981
|
+
if (c === "(") depth++;
|
|
19982
|
+
else if (c === ")") {
|
|
19983
|
+
depth--;
|
|
19984
|
+
if (depth === 0) return src.slice(open2, i + 1);
|
|
19985
|
+
}
|
|
19986
|
+
}
|
|
19987
|
+
return "";
|
|
19988
|
+
}
|
|
19989
|
+
function findViolationsInSource(raw) {
|
|
19990
|
+
if (!/from\s+["']node:child_process["']/.test(raw)) return [];
|
|
19991
|
+
const importBlock = /import\s*(?:type\s*)?\{([^}]*)\}\s*from\s+["']node:child_process["']/.exec(raw);
|
|
19992
|
+
const imported = new Set(
|
|
19993
|
+
(importBlock?.[1] ?? "").split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean)
|
|
19994
|
+
);
|
|
19995
|
+
if (imported.size === 0) return [];
|
|
19996
|
+
const rawLines = raw.split("\n");
|
|
19997
|
+
const src = stripComments(raw);
|
|
19998
|
+
const call = new RegExp(CALL_SOURCE, "g");
|
|
19999
|
+
const found = [];
|
|
20000
|
+
let m;
|
|
20001
|
+
while (m = call.exec(src)) {
|
|
20002
|
+
const callee = m[2];
|
|
20003
|
+
if (!imported.has(callee)) continue;
|
|
20004
|
+
const open2 = m.index + m[0].length - 1;
|
|
20005
|
+
const args = callArgs(src, open2);
|
|
20006
|
+
if (!args) continue;
|
|
20007
|
+
if (args.includes("windowsHide")) continue;
|
|
20008
|
+
const line = src.slice(0, m.index + m[1].length).split("\n").length;
|
|
20009
|
+
if (/windows-hide-exempt:/.test(rawLines[line - 2] ?? "")) continue;
|
|
20010
|
+
if (/\.{3}\s*\w+/.test(args)) continue;
|
|
20011
|
+
if (/\b(opts|options|spawnOptions)\b/.test(args)) continue;
|
|
20012
|
+
const identifiers = new Set(args.match(/\b[A-Za-z_$][\w$]*\b/g) ?? []);
|
|
20013
|
+
let satisfied = false;
|
|
20014
|
+
for (const id of identifiers) {
|
|
20015
|
+
if (SPAWNERS.includes(id)) continue;
|
|
20016
|
+
const decl = new RegExp(String.raw`\b(?:const|let|var)\s+${id}\b[^;]*`, "s").exec(src);
|
|
20017
|
+
if (decl && decl[0].includes("windowsHide")) {
|
|
20018
|
+
satisfied = true;
|
|
20019
|
+
break;
|
|
20020
|
+
}
|
|
20021
|
+
if (new RegExp(String.raw`\b${id}\s*:\s*\{[^}]*windowsHide`, "s").test(src)) {
|
|
20022
|
+
satisfied = true;
|
|
20023
|
+
break;
|
|
20024
|
+
}
|
|
20025
|
+
}
|
|
20026
|
+
if (satisfied) continue;
|
|
20027
|
+
found.push({ line, callee });
|
|
20028
|
+
}
|
|
20029
|
+
return found;
|
|
20030
|
+
}
|
|
20031
|
+
function policedFiles(root) {
|
|
20032
|
+
const r = (0, import_node_child_process13.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
20033
|
+
cwd: root,
|
|
20034
|
+
encoding: "utf8",
|
|
20035
|
+
windowsHide: true,
|
|
20036
|
+
maxBuffer: 32 * 1024 * 1024
|
|
20037
|
+
});
|
|
20038
|
+
if (r.status !== 0) {
|
|
20039
|
+
throw new Error(`spawn policy: git ls-files failed: ${(r.stderr || "").trim() || `exit ${r.status}`}`);
|
|
20040
|
+
}
|
|
20041
|
+
return (r.stdout ?? "").split("\0").filter(Boolean).filter((f) => SOURCE_EXT.test(f)).filter((f) => !EXCLUDED.some((re) => re.test(f)));
|
|
20042
|
+
}
|
|
20043
|
+
function runSpawnPolicy(root) {
|
|
20044
|
+
const files = policedFiles(root);
|
|
20045
|
+
const findings = [];
|
|
20046
|
+
for (const file of files) {
|
|
20047
|
+
let raw;
|
|
20048
|
+
try {
|
|
20049
|
+
raw = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
|
|
20050
|
+
} catch {
|
|
20051
|
+
continue;
|
|
20052
|
+
}
|
|
20053
|
+
for (const v of findViolationsInSource(raw)) {
|
|
20054
|
+
findings.push({
|
|
20055
|
+
file,
|
|
20056
|
+
line: v.line,
|
|
20057
|
+
callee: v.callee,
|
|
20058
|
+
detail: `${file}:${v.line} ${v.callee}(...) has no windowsHide`
|
|
20059
|
+
});
|
|
20060
|
+
}
|
|
20061
|
+
}
|
|
20062
|
+
return { ok: findings.length === 0, scannedCount: files.length, findings };
|
|
20063
|
+
}
|
|
20064
|
+
|
|
20065
|
+
// src/test-policy-core.ts
|
|
20066
|
+
var import_node_child_process14 = require("node:child_process");
|
|
20067
|
+
var import_node_fs26 = require("node:fs");
|
|
20068
|
+
var import_node_path24 = require("node:path");
|
|
20069
|
+
var POLICY_FILE = "test-policy.json";
|
|
20070
|
+
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
20071
|
+
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
20072
|
+
var TRAILER_KEY = "Test-Policy-Override";
|
|
20073
|
+
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
20074
|
+
var REC = "";
|
|
20075
|
+
var FLD = "";
|
|
20076
|
+
var WAIVABLE_KINDS = [
|
|
20077
|
+
"mandatory-zone-untested",
|
|
20078
|
+
"unrequested-test-file",
|
|
20079
|
+
"protected-removed",
|
|
20080
|
+
"stale-protected-entry",
|
|
20081
|
+
"stale-satisfied-by"
|
|
20082
|
+
];
|
|
20083
|
+
function translate(glob) {
|
|
20084
|
+
let out = "";
|
|
20085
|
+
for (let i = 0; i < glob.length; i++) {
|
|
20086
|
+
const c = glob[i];
|
|
20087
|
+
if (c === "*") {
|
|
20088
|
+
if (glob[i + 1] === "*") {
|
|
20089
|
+
if (glob[i + 2] === "/") {
|
|
20090
|
+
out += "(?:.*/)?";
|
|
20091
|
+
i += 2;
|
|
20092
|
+
} else {
|
|
20093
|
+
out += ".*";
|
|
20094
|
+
i += 1;
|
|
20095
|
+
}
|
|
20096
|
+
} else {
|
|
20097
|
+
out += "[^/]*";
|
|
20098
|
+
}
|
|
20099
|
+
continue;
|
|
20100
|
+
}
|
|
20101
|
+
if (c === "{") {
|
|
20102
|
+
const close = glob.indexOf("}", i);
|
|
20103
|
+
if (close !== -1) {
|
|
20104
|
+
const alts = glob.slice(i + 1, close).split(",").map((a) => translate(a));
|
|
20105
|
+
out += `(?:${alts.join("|")})`;
|
|
20106
|
+
i = close;
|
|
20107
|
+
continue;
|
|
20108
|
+
}
|
|
20109
|
+
}
|
|
20110
|
+
out += /[.+?^${}()|[\]\\]/.test(c) ? `\\${c}` : c;
|
|
20111
|
+
}
|
|
20112
|
+
return out;
|
|
20113
|
+
}
|
|
20114
|
+
function globToRegExp(glob) {
|
|
20115
|
+
return new RegExp(`^${translate(glob)}$`);
|
|
20116
|
+
}
|
|
20117
|
+
function isTestPath(path2) {
|
|
20118
|
+
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
20119
|
+
}
|
|
20120
|
+
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
20121
|
+
const raw = readFile9((0, import_node_path24.join)(root, POLICY_FILE));
|
|
20122
|
+
if (raw == null) return { mandatory: [], declared: false };
|
|
20123
|
+
try {
|
|
20124
|
+
return { ...JSON.parse(raw), declared: true };
|
|
20125
|
+
} catch (error) {
|
|
20126
|
+
throw new Error(`${POLICY_FILE} is not valid JSON: ${error.message}`);
|
|
20127
|
+
}
|
|
20128
|
+
}
|
|
20129
|
+
function readFileOrNull2(path2) {
|
|
20130
|
+
try {
|
|
20131
|
+
return (0, import_node_fs26.readFileSync)(path2, "utf8");
|
|
20132
|
+
} catch {
|
|
20133
|
+
return null;
|
|
20134
|
+
}
|
|
20135
|
+
}
|
|
20136
|
+
function removedPaths(changed) {
|
|
20137
|
+
return new Set(
|
|
20138
|
+
changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string")
|
|
20139
|
+
);
|
|
20140
|
+
}
|
|
20141
|
+
function classify(changed, policy, present = () => false) {
|
|
20142
|
+
const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp(m.glob) }));
|
|
20143
|
+
const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
|
|
20144
|
+
const testChanges = changed.filter((f) => isTestPath(f.path));
|
|
20145
|
+
const addedTests = testChanges.filter((f) => f.status === "A");
|
|
20146
|
+
const removed = removedPaths(changed);
|
|
20147
|
+
const discharged = (m) => (m.satisfiedBy?.length ?? 0) > 0 && m.satisfiedBy.every((p) => present(p) && !removed.has(p));
|
|
20148
|
+
const untestedHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path) && !discharged(m)));
|
|
20149
|
+
const protectedBy = new Map((policy.protected ?? []).map((p) => [p.path, p.why ?? ""]));
|
|
20150
|
+
for (const m of policy.mandatory ?? []) {
|
|
20151
|
+
for (const p of m.satisfiedBy ?? []) {
|
|
20152
|
+
if (!protectedBy.has(p)) protectedBy.set(p, `the standing coverage \`${m.glob}\` is discharged by. Removing it silently empties that glob.`);
|
|
20153
|
+
}
|
|
20154
|
+
}
|
|
20155
|
+
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
20156
|
+
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
20157
|
+
}
|
|
20158
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
|
|
20159
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path24.join)(root, p)));
|
|
20160
|
+
}
|
|
20161
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
|
|
20162
|
+
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
20163
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path24.join)(root, p)));
|
|
19489
20164
|
}
|
|
19490
20165
|
function evaluate(changed, policy, present = () => false) {
|
|
19491
20166
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -19518,14 +20193,14 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
19518
20193
|
return findings;
|
|
19519
20194
|
}
|
|
19520
20195
|
function git(args, cwd) {
|
|
19521
|
-
return (0,
|
|
20196
|
+
return (0, import_node_child_process14.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
19522
20197
|
}
|
|
19523
20198
|
var COAUTHOR_KEY = "Co-authored-by";
|
|
19524
20199
|
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
19525
20200
|
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
19526
20201
|
function parseTrailers(message, cwd) {
|
|
19527
20202
|
try {
|
|
19528
|
-
return (0,
|
|
20203
|
+
return (0, import_node_child_process14.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
19529
20204
|
windowsHide: true,
|
|
19530
20205
|
cwd,
|
|
19531
20206
|
input: message,
|
|
@@ -19667,13 +20342,13 @@ function changedFilesSince(base, cwd) {
|
|
|
19667
20342
|
}
|
|
19668
20343
|
function runTestPolicy(root, deps = {}) {
|
|
19669
20344
|
const policy = deps.policy ?? loadPolicy(root);
|
|
19670
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
20345
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs26.existsSync)(path2));
|
|
19671
20346
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
19672
20347
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
19673
20348
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
19674
20349
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
19675
20350
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
19676
|
-
const present = (path2) => exists((0,
|
|
20351
|
+
const present = (path2) => exists((0, import_node_path24.join)(root, path2));
|
|
19677
20352
|
const removedByThisDiff = removedPaths(changed);
|
|
19678
20353
|
const staleFindings = [];
|
|
19679
20354
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -19829,8 +20504,8 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
19829
20504
|
}
|
|
19830
20505
|
|
|
19831
20506
|
// src/project-info-sync.ts
|
|
19832
|
-
var
|
|
19833
|
-
var
|
|
20507
|
+
var import_node_fs27 = require("node:fs");
|
|
20508
|
+
var import_node_path25 = require("node:path");
|
|
19834
20509
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
19835
20510
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
19836
20511
|
projectV2 { id }
|
|
@@ -19875,14 +20550,14 @@ function sharedName(entries, fallback) {
|
|
|
19875
20550
|
}
|
|
19876
20551
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
19877
20552
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
19878
|
-
const readmePath = (0,
|
|
19879
|
-
if (!(0,
|
|
20553
|
+
const readmePath = (0, import_node_path25.join)(repoRoot2, "README.md");
|
|
20554
|
+
if (!(0, import_node_fs27.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
19880
20555
|
const entries = entriesFor(project2, projects);
|
|
19881
20556
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
19882
20557
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
19883
20558
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
19884
20559
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
19885
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
20560
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs27.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
19886
20561
|
const lines = [
|
|
19887
20562
|
`# ${projectName}`,
|
|
19888
20563
|
"",
|
|
@@ -19901,8 +20576,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
19901
20576
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
19902
20577
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
19903
20578
|
const orgDocs = [
|
|
19904
|
-
(0,
|
|
19905
|
-
(0,
|
|
20579
|
+
(0, import_node_fs27.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
20580
|
+
(0, import_node_fs27.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
19906
20581
|
].filter(Boolean);
|
|
19907
20582
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
19908
20583
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -20761,9 +21436,9 @@ function writeError(res) {
|
|
|
20761
21436
|
}
|
|
20762
21437
|
|
|
20763
21438
|
// src/secrets-commands.ts
|
|
20764
|
-
var
|
|
20765
|
-
var
|
|
20766
|
-
var
|
|
21439
|
+
var import_node_fs28 = require("node:fs");
|
|
21440
|
+
var import_node_path26 = require("node:path");
|
|
21441
|
+
var import_node_os10 = require("node:os");
|
|
20767
21442
|
|
|
20768
21443
|
// src/project-runtime.ts
|
|
20769
21444
|
function hasRuntimeSecretContract(contract) {
|
|
@@ -20886,18 +21561,18 @@ function collectMap(value, previous = []) {
|
|
|
20886
21561
|
return [...previous, value];
|
|
20887
21562
|
}
|
|
20888
21563
|
async function decryptRailsCredentials(input) {
|
|
20889
|
-
const appDir = (0,
|
|
21564
|
+
const appDir = (0, import_node_path26.resolve)(input.appDir ?? process.cwd());
|
|
20890
21565
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
20891
21566
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
20892
|
-
const credentialsPath = (0,
|
|
20893
|
-
const masterKeyPath = (0,
|
|
21567
|
+
const credentialsPath = (0, import_node_path26.resolve)(appDir, credentialsFile);
|
|
21568
|
+
const masterKeyPath = (0, import_node_path26.resolve)(appDir, masterKeyFile);
|
|
20894
21569
|
const env = {
|
|
20895
21570
|
...process.env,
|
|
20896
21571
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
20897
21572
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
20898
21573
|
};
|
|
20899
|
-
if ((0,
|
|
20900
|
-
env.RAILS_MASTER_KEY = (0,
|
|
21574
|
+
if ((0, import_node_fs28.existsSync)(masterKeyPath)) {
|
|
21575
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs28.readFileSync)(masterKeyPath, "utf8").trim();
|
|
20901
21576
|
}
|
|
20902
21577
|
const script = [
|
|
20903
21578
|
'require "json"',
|
|
@@ -20907,9 +21582,9 @@ async function decryptRailsCredentials(input) {
|
|
|
20907
21582
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
20908
21583
|
"puts JSON.generate(config.config)"
|
|
20909
21584
|
].join("\n");
|
|
20910
|
-
const scriptDir = (0,
|
|
20911
|
-
const scriptPath = (0,
|
|
20912
|
-
(0,
|
|
21585
|
+
const scriptDir = (0, import_node_fs28.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
|
|
21586
|
+
const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
|
|
21587
|
+
(0, import_node_fs28.writeFileSync)(scriptPath, script, "utf8");
|
|
20913
21588
|
try {
|
|
20914
21589
|
const args = ["exec", "ruby", scriptPath];
|
|
20915
21590
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -20921,7 +21596,7 @@ async function decryptRailsCredentials(input) {
|
|
|
20921
21596
|
});
|
|
20922
21597
|
return JSON.parse(stdout);
|
|
20923
21598
|
} finally {
|
|
20924
|
-
(0,
|
|
21599
|
+
(0, import_node_fs28.rmSync)(scriptDir, { recursive: true, force: true });
|
|
20925
21600
|
}
|
|
20926
21601
|
}
|
|
20927
21602
|
async function readSecretStdin() {
|
|
@@ -21011,7 +21686,7 @@ function registerSecretsCommands(program3) {
|
|
|
21011
21686
|
let body;
|
|
21012
21687
|
if (o.file) {
|
|
21013
21688
|
try {
|
|
21014
|
-
body = (0,
|
|
21689
|
+
body = (0, import_node_fs28.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
|
|
21015
21690
|
} catch (e) {
|
|
21016
21691
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
21017
21692
|
}
|
|
@@ -21116,7 +21791,7 @@ function registerSecretsCommands(program3) {
|
|
|
21116
21791
|
{
|
|
21117
21792
|
...d,
|
|
21118
21793
|
decryptRailsCredentials,
|
|
21119
|
-
removeFile: (path2) => (0,
|
|
21794
|
+
removeFile: (path2) => (0, import_node_fs28.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
|
|
21120
21795
|
},
|
|
21121
21796
|
{
|
|
21122
21797
|
repo: o.repo,
|
|
@@ -21161,7 +21836,7 @@ function registerSecretsCommands(program3) {
|
|
|
21161
21836
|
}
|
|
21162
21837
|
|
|
21163
21838
|
// src/app-actor.ts
|
|
21164
|
-
var
|
|
21839
|
+
var import_node_crypto5 = require("node:crypto");
|
|
21165
21840
|
var APP_ACTOR_ENV = "MMI_ACTOR";
|
|
21166
21841
|
var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
21167
21842
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
@@ -21205,7 +21880,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
|
|
|
21205
21880
|
exp: now + APP_JWT_TTL_S,
|
|
21206
21881
|
iss: appId
|
|
21207
21882
|
}));
|
|
21208
|
-
const signer = (0,
|
|
21883
|
+
const signer = (0, import_node_crypto5.createSign)("RSA-SHA256");
|
|
21209
21884
|
signer.update(`${header}.${payload}`);
|
|
21210
21885
|
return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
|
|
21211
21886
|
}
|
|
@@ -21280,7 +21955,7 @@ async function activateAppActor(commandPath3, env, mint) {
|
|
|
21280
21955
|
}
|
|
21281
21956
|
|
|
21282
21957
|
// src/box-commands.ts
|
|
21283
|
-
var
|
|
21958
|
+
var import_node_fs29 = require("node:fs");
|
|
21284
21959
|
|
|
21285
21960
|
// src/box.ts
|
|
21286
21961
|
var BOX_KEYS = {
|
|
@@ -21483,7 +22158,7 @@ function registerBoxCommands(program3) {
|
|
|
21483
22158
|
}
|
|
21484
22159
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
21485
22160
|
else if (o.ssh && o.script) {
|
|
21486
|
-
(0,
|
|
22161
|
+
(0, import_node_fs29.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
21487
22162
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
21488
22163
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
21489
22164
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -21498,7 +22173,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
21498
22173
|
|
|
21499
22174
|
// src/schedules-commands.ts
|
|
21500
22175
|
var import_promises4 = require("node:fs/promises");
|
|
21501
|
-
var
|
|
22176
|
+
var import_node_child_process15 = require("node:child_process");
|
|
21502
22177
|
var import_node_util7 = require("node:util");
|
|
21503
22178
|
|
|
21504
22179
|
// src/schedules.ts
|
|
@@ -21622,9 +22297,9 @@ function extractWorkflowCrons(yamlText) {
|
|
|
21622
22297
|
function workflowEntry(repo, workflowPath, yamlText) {
|
|
21623
22298
|
const crons = extractWorkflowCrons(yamlText);
|
|
21624
22299
|
if (!crons.length) return null;
|
|
21625
|
-
const
|
|
22300
|
+
const basename5 = workflowPath.split("/").pop() ?? workflowPath;
|
|
21626
22301
|
return {
|
|
21627
|
-
name: `${repo}/${
|
|
22302
|
+
name: `${repo}/${basename5.replace(/\.ya?ml$/, "")}`,
|
|
21628
22303
|
cadence: crons.join(" + "),
|
|
21629
22304
|
executor: "github-actions",
|
|
21630
22305
|
llm: llmFromHeader(yamlText),
|
|
@@ -21737,6 +22412,9 @@ var DECLARED_ENTRIES = [
|
|
|
21737
22412
|
// unavailable exactly when it is needed. This one is root cron on the box: no checkout, no node, and
|
|
21738
22413
|
// a no-op below 90% disk, which is why 15 minutes is affordable.
|
|
21739
22414
|
{ name: "mmi-runner-hygiene-oob (mmi-runner)", cadence: "*/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-hygiene \u2192 /opt/mmi-control/runner-hygiene-oob.sh; acts only at >=90% disk (verify: mmi-cli runtime box get mmi-runner --ssh)" },
|
|
22415
|
+
// #4118: Listening + systemd-active ≠ GitHub online after acquirejob 503. restart.conf cannot see
|
|
22416
|
+
// it; an Actions workflow on mmi-live cannot heal it when half the fleet is offline. Root cron.
|
|
22417
|
+
{ name: "mmi-runner-online-reconcile (mmi-runner)", cadence: "*/5 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-online \u2192 /opt/mmi-control/runner-online-reconcile.sh; API-offline + _diag acquirejob, max 2 restarts/tick (verify: mmi-cli runtime box get mmi-runner --ssh)" },
|
|
21740
22418
|
{ name: "zuber-bake", cadence: "*:05/30 (every 30 min)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake.timer \u2192 rolling overlay bake, metro tier, next ~6h window (verify over ssh)" },
|
|
21741
22419
|
{ name: "zuber-bake-full", cadence: "00:30 UTC (03:30 TRT, daily)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake-full.timer \u2192 full 24h overlay rebuild, all 81 il (verify over ssh)" }
|
|
21742
22420
|
];
|
|
@@ -21932,7 +22610,7 @@ function assembleReconciliation(githubEntries2, readRepos, registry2, activeWork
|
|
|
21932
22610
|
}
|
|
21933
22611
|
|
|
21934
22612
|
// src/schedules-commands.ts
|
|
21935
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
22613
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process15.execFile);
|
|
21936
22614
|
var AWS_REGION = "eu-central-1";
|
|
21937
22615
|
var AWS_TIMEOUT_MS = 3e4;
|
|
21938
22616
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -21966,8 +22644,8 @@ async function repoWorkflowEntries(client, repo) {
|
|
|
21966
22644
|
);
|
|
21967
22645
|
if (typeof contents?.content !== "string" || contents.encoding !== "base64") continue;
|
|
21968
22646
|
const text = Buffer.from(contents.content, "base64").toString("utf8");
|
|
21969
|
-
const
|
|
21970
|
-
const name = `${repo}/${
|
|
22647
|
+
const basename5 = wf.path.split("/").pop() ?? wf.path;
|
|
22648
|
+
const name = `${repo}/${basename5.replace(/\.ya?ml$/, "")}`;
|
|
21971
22649
|
workflows.push({ name, yamlText: text });
|
|
21972
22650
|
const entry = workflowEntry(repo, wf.path, text);
|
|
21973
22651
|
if (entry) entries.push(entry);
|
|
@@ -22098,7 +22776,7 @@ function reportDrift(drift) {
|
|
|
22098
22776
|
for (const d of drift) console.error(`org schedules: DRIFT \u2014 ${d}`);
|
|
22099
22777
|
}
|
|
22100
22778
|
function registerSchedulesCommands(program3) {
|
|
22101
|
-
const schedules = program3.command("schedules").description("the org schedules notebook \u2014 every armed cron, timer, and scheduled LLM lane, resolved live (jerv side lives in jerv-cli schedules)").option("--json", "machine-readable output (consumed by jerv-cli schedules --all)").option("--doc <path>", "
|
|
22779
|
+
const schedules = program3.command("schedules").description("the org schedules notebook \u2014 every armed cron, timer, and scheduled LLM lane, resolved live (jerv side lives in jerv-cli schedules)").option("--json", "machine-readable output (consumed by jerv-cli schedules --all)").option("--doc <path>", "optional snapshot only \u2014 splice inventory between schedules:inventory markers; live `org schedules` / --json remains SSOT (Hub#4120)").action(async (o) => {
|
|
22102
22780
|
try {
|
|
22103
22781
|
const { entries, incomplete, drift, reconciliation } = await fetchNotebook();
|
|
22104
22782
|
if (o.doc) {
|
|
@@ -22169,7 +22847,7 @@ function registerSchedulesCommands(program3) {
|
|
|
22169
22847
|
|
|
22170
22848
|
// src/file-lock.ts
|
|
22171
22849
|
var import_promises5 = require("node:fs/promises");
|
|
22172
|
-
var
|
|
22850
|
+
var import_node_path27 = require("node:path");
|
|
22173
22851
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
22174
22852
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
22175
22853
|
var FileLockBusyError = class extends Error {
|
|
@@ -22254,7 +22932,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
22254
22932
|
}
|
|
22255
22933
|
async function withFileLock(lockPath, opts, fn) {
|
|
22256
22934
|
const resolved = resolveFileLockOpts(opts);
|
|
22257
|
-
await (0, import_promises5.mkdir)((0,
|
|
22935
|
+
await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
22258
22936
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
22259
22937
|
try {
|
|
22260
22938
|
return await fn();
|
|
@@ -22265,7 +22943,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
22265
22943
|
|
|
22266
22944
|
// src/schedules-lift-command.ts
|
|
22267
22945
|
var import_promises6 = require("node:fs/promises");
|
|
22268
|
-
var
|
|
22946
|
+
var import_node_path28 = require("node:path");
|
|
22269
22947
|
|
|
22270
22948
|
// src/schedules-lift.ts
|
|
22271
22949
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -22287,8 +22965,8 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
22287
22965
|
const crons = extractWorkflowCrons(yamlText);
|
|
22288
22966
|
const header = parseScheduleHeader(yamlText);
|
|
22289
22967
|
if (!crons.length && !header.schedule) return null;
|
|
22290
|
-
const
|
|
22291
|
-
const expectedId = `${repo}/${
|
|
22968
|
+
const basename5 = workflowPath.split("/").pop() ?? workflowPath;
|
|
22969
|
+
const expectedId = `${repo}/${basename5.replace(/\.ya?ml$/, "")}`;
|
|
22292
22970
|
const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
|
|
22293
22971
|
if (missing.length) {
|
|
22294
22972
|
throw new Error(`schedules register: ${expectedId} (${workflowPath}) is a scheduled workflow but is missing the eight-field entry-template header field(s): ${missing.join(", ")} \u2014 see docs/schedules.md "The entry template".`);
|
|
@@ -22371,7 +23049,7 @@ async function readWorkflowFiles(dir) {
|
|
|
22371
23049
|
const files = [];
|
|
22372
23050
|
for (const name of names.sort()) {
|
|
22373
23051
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
22374
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0,
|
|
23052
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path28.join)(dir, name), "utf8") });
|
|
22375
23053
|
}
|
|
22376
23054
|
return files;
|
|
22377
23055
|
}
|
|
@@ -22913,9 +23591,9 @@ function registerQueryCommands(program3) {
|
|
|
22913
23591
|
}
|
|
22914
23592
|
|
|
22915
23593
|
// src/bootstrap-commands.ts
|
|
22916
|
-
var
|
|
22917
|
-
var
|
|
22918
|
-
var
|
|
23594
|
+
var import_node_fs30 = require("node:fs");
|
|
23595
|
+
var import_node_os11 = require("node:os");
|
|
23596
|
+
var import_node_path29 = require("node:path");
|
|
22919
23597
|
|
|
22920
23598
|
// src/bootstrap-drift.ts
|
|
22921
23599
|
function byteComparableSeeds(manifest, cls) {
|
|
@@ -23445,6 +24123,12 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
23445
24123
|
detail: optionDetail(missing)
|
|
23446
24124
|
});
|
|
23447
24125
|
} else if (repoClass === "deployable") {
|
|
24126
|
+
const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
|
|
24127
|
+
checks.push({
|
|
24128
|
+
ok: productRuleset?.enforcement === "active",
|
|
24129
|
+
label: "product required-check ruleset enforcement active",
|
|
24130
|
+
detail: productRuleset?.enforcement !== "active" ? `${PRODUCT_RULESET_NAME} is ${productRuleset?.enforcement ?? "missing"} \u2014 run mmi-cli ci reconcile --apply --repo ${repo} once the gate is green` : void 0
|
|
24131
|
+
});
|
|
23448
24132
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
23449
24133
|
const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
|
|
23450
24134
|
checks.push({
|
|
@@ -23622,13 +24306,13 @@ function registerBootstrapCommands(program3) {
|
|
|
23622
24306
|
client: defaultGitHubClient(),
|
|
23623
24307
|
projectMeta: meta,
|
|
23624
24308
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
23625
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
24309
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs30.existsSync)(path2) ? (0, import_node_fs30.readFileSync)(path2, "utf8") : null,
|
|
23626
24310
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
23627
24311
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
23628
24312
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
23629
24313
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
23630
24314
|
// sanction, which is the pre-#3664 behaviour.
|
|
23631
|
-
sanctionedAdmins: (0,
|
|
24315
|
+
sanctionedAdmins: (0, import_node_fs30.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs30.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
23632
24316
|
requiredGcpApis: (() => {
|
|
23633
24317
|
const v = meta?.requiredGcpApis;
|
|
23634
24318
|
if (Array.isArray(v)) return v;
|
|
@@ -23681,12 +24365,12 @@ function registerBootstrapCommands(program3) {
|
|
|
23681
24365
|
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 () => {
|
|
23682
24366
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
23683
24367
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23684
|
-
if (!(0,
|
|
23685
|
-
const manifest = loadBootstrapSeeds((0,
|
|
24368
|
+
if (!(0, import_node_fs30.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`);
|
|
24369
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs30.readFileSync)(manifestPath, "utf8"));
|
|
23686
24370
|
const hubContents = /* @__PURE__ */ new Map();
|
|
23687
24371
|
for (const s of manifest.seeds) {
|
|
23688
24372
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
23689
|
-
hubContents.set(s.target, (0,
|
|
24373
|
+
hubContents.set(s.target, (0, import_node_fs30.existsSync)(s.target) ? (0, import_node_fs30.readFileSync)(s.target, "utf8") : null);
|
|
23690
24374
|
}
|
|
23691
24375
|
let targets;
|
|
23692
24376
|
let classOf = (_repo) => "deployable";
|
|
@@ -23763,8 +24447,8 @@ function registerBootstrapCommands(program3) {
|
|
|
23763
24447
|
return fail(`bootstrap apply: ${e.message}`);
|
|
23764
24448
|
}
|
|
23765
24449
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23766
|
-
if (!(0,
|
|
23767
|
-
const manifest = loadBootstrapSeeds((0,
|
|
24450
|
+
if (!(0, import_node_fs30.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`);
|
|
24451
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs30.readFileSync)(manifestPath, "utf8"));
|
|
23768
24452
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
23769
24453
|
const slug = parsedRepo.slug;
|
|
23770
24454
|
const onlyTarget = o.only.trim();
|
|
@@ -23775,16 +24459,16 @@ function registerBootstrapCommands(program3) {
|
|
|
23775
24459
|
${known}`);
|
|
23776
24460
|
}
|
|
23777
24461
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
23778
|
-
const readFile9 = (p) => (0,
|
|
24462
|
+
const readFile9 = (p) => (0, import_node_fs30.existsSync)(p) ? (0, import_node_fs30.readFileSync)(p, "utf8") : null;
|
|
23779
24463
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
23780
24464
|
const putSeed = async (target, content, ref, sha) => {
|
|
23781
|
-
const tmp = (0,
|
|
23782
|
-
(0,
|
|
24465
|
+
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`);
|
|
24466
|
+
(0, import_node_fs30.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
23783
24467
|
try {
|
|
23784
24468
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
23785
24469
|
} finally {
|
|
23786
24470
|
try {
|
|
23787
|
-
(0,
|
|
24471
|
+
(0, import_node_fs30.unlinkSync)(tmp);
|
|
23788
24472
|
} catch {
|
|
23789
24473
|
}
|
|
23790
24474
|
}
|
|
@@ -24041,12 +24725,12 @@ LIVE apply to ${repo}:
|
|
|
24041
24725
|
}
|
|
24042
24726
|
|
|
24043
24727
|
// src/stage-commands.ts
|
|
24044
|
-
var
|
|
24045
|
-
var
|
|
24728
|
+
var import_node_fs32 = require("node:fs");
|
|
24729
|
+
var import_node_path31 = require("node:path");
|
|
24046
24730
|
|
|
24047
24731
|
// src/port-registry.ts
|
|
24048
|
-
var
|
|
24049
|
-
var
|
|
24732
|
+
var import_node_fs31 = require("node:fs");
|
|
24733
|
+
var import_node_path30 = require("node:path");
|
|
24050
24734
|
|
|
24051
24735
|
// ../infra/port-geometry.mjs
|
|
24052
24736
|
var PORT_BLOCK = 100;
|
|
@@ -24060,8 +24744,8 @@ function nextPortBlock(registry2) {
|
|
|
24060
24744
|
return [base, base + PORT_SPAN];
|
|
24061
24745
|
}
|
|
24062
24746
|
function loadPortRegistry(path2) {
|
|
24063
|
-
if (!(0,
|
|
24064
|
-
const raw = JSON.parse((0,
|
|
24747
|
+
if (!(0, import_node_fs31.existsSync)(path2)) return {};
|
|
24748
|
+
const raw = JSON.parse((0, import_node_fs31.readFileSync)(path2, "utf8"));
|
|
24065
24749
|
const out = {};
|
|
24066
24750
|
for (const [key, value] of Object.entries(raw)) {
|
|
24067
24751
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -24075,9 +24759,9 @@ function ensurePortRange(repo, path2) {
|
|
|
24075
24759
|
const existing = registry2[repo];
|
|
24076
24760
|
if (existing) return existing;
|
|
24077
24761
|
const range = nextPortBlock(registry2);
|
|
24078
|
-
const raw = (0,
|
|
24762
|
+
const raw = (0, import_node_fs31.existsSync)(path2) ? JSON.parse((0, import_node_fs31.readFileSync)(path2, "utf8")) : {};
|
|
24079
24763
|
raw[repo] = range;
|
|
24080
|
-
(0,
|
|
24764
|
+
(0, import_node_fs31.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
24081
24765
|
return range;
|
|
24082
24766
|
}
|
|
24083
24767
|
function portCursorSeed(registry2) {
|
|
@@ -24099,22 +24783,22 @@ function existingPortRange(repo, registry2) {
|
|
|
24099
24783
|
return registry2[repo] ?? null;
|
|
24100
24784
|
}
|
|
24101
24785
|
function portRangeInfraAt(root, source) {
|
|
24102
|
-
const registryPath = (0,
|
|
24103
|
-
const ddbScriptPath = (0,
|
|
24104
|
-
if (!(0,
|
|
24786
|
+
const registryPath = (0, import_node_path30.join)(root, "infra", "port-ranges.json");
|
|
24787
|
+
const ddbScriptPath = (0, import_node_path30.join)(root, "infra", "port-ddb.mjs");
|
|
24788
|
+
if (!(0, import_node_fs31.existsSync)(registryPath) || !(0, import_node_fs31.existsSync)(ddbScriptPath)) return null;
|
|
24105
24789
|
return { root, source, registryPath, ddbScriptPath };
|
|
24106
24790
|
}
|
|
24107
24791
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
24108
24792
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
24109
24793
|
if (direct) return direct;
|
|
24110
|
-
for (let dir = cwd; ; dir = (0,
|
|
24111
|
-
const sibling = portRangeInfraAt((0,
|
|
24794
|
+
for (let dir = cwd; ; dir = (0, import_node_path30.dirname)(dir)) {
|
|
24795
|
+
const sibling = portRangeInfraAt((0, import_node_path30.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
24112
24796
|
if (sibling) return sibling;
|
|
24113
|
-
const parent = (0,
|
|
24797
|
+
const parent = (0, import_node_path30.dirname)(dir);
|
|
24114
24798
|
if (parent === dir) break;
|
|
24115
24799
|
}
|
|
24116
24800
|
if (packageDir) {
|
|
24117
|
-
const pkgRoot = (0,
|
|
24801
|
+
const pkgRoot = (0, import_node_path30.join)(packageDir, "..", "..");
|
|
24118
24802
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
24119
24803
|
if (pkgFrom) return pkgFrom;
|
|
24120
24804
|
}
|
|
@@ -24308,8 +24992,8 @@ function registerStageCommands(program3) {
|
|
|
24308
24992
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
24309
24993
|
return decideStage({
|
|
24310
24994
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
24311
|
-
hasCompose: (0,
|
|
24312
|
-
hasEnvExample: (0,
|
|
24995
|
+
hasCompose: (0, import_node_fs32.existsSync)((0, import_node_path31.join)(process.cwd(), "docker-compose.yml")),
|
|
24996
|
+
hasEnvExample: (0, import_node_fs32.existsSync)((0, import_node_path31.join)(process.cwd(), ".env.example"))
|
|
24313
24997
|
});
|
|
24314
24998
|
}
|
|
24315
24999
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -24747,11 +25431,11 @@ function registerBoardCommands(program3) {
|
|
|
24747
25431
|
}
|
|
24748
25432
|
|
|
24749
25433
|
// src/merge-cleanup.ts
|
|
24750
|
-
var
|
|
25434
|
+
var import_node_fs33 = require("node:fs");
|
|
24751
25435
|
var import_promises8 = require("node:fs/promises");
|
|
24752
|
-
var
|
|
24753
|
-
var
|
|
24754
|
-
var
|
|
25436
|
+
var import_node_path33 = require("node:path");
|
|
25437
|
+
var import_node_os12 = require("node:os");
|
|
25438
|
+
var import_node_child_process16 = require("node:child_process");
|
|
24755
25439
|
|
|
24756
25440
|
// src/board-advance.ts
|
|
24757
25441
|
function repoOf2(ref) {
|
|
@@ -24837,7 +25521,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
24837
25521
|
|
|
24838
25522
|
// src/deferred-registry-store.ts
|
|
24839
25523
|
var import_promises7 = require("node:fs/promises");
|
|
24840
|
-
var
|
|
25524
|
+
var import_node_path32 = require("node:path");
|
|
24841
25525
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
24842
25526
|
async function atomicWrite(target, contents) {
|
|
24843
25527
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -24888,12 +25572,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
24888
25572
|
},
|
|
24889
25573
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
24890
25574
|
write: async (entries) => {
|
|
24891
|
-
await (0, import_promises7.mkdir)((0,
|
|
25575
|
+
await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
|
|
24892
25576
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
24893
25577
|
},
|
|
24894
25578
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
24895
25579
|
update: async (mutate) => {
|
|
24896
|
-
await (0, import_promises7.mkdir)((0,
|
|
25580
|
+
await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
|
|
24897
25581
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
24898
25582
|
for (; ; ) {
|
|
24899
25583
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -25041,7 +25725,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
25041
25725
|
);
|
|
25042
25726
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
25043
25727
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
25044
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
25728
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path33.dirname)((0, import_node_path33.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
25045
25729
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
25046
25730
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
25047
25731
|
const removalNow = Date.now();
|
|
@@ -25072,7 +25756,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
25072
25756
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
25073
25757
|
beforeWorktrees,
|
|
25074
25758
|
startingPath: branch.worktreePath,
|
|
25075
|
-
pathExists: (p) => (0,
|
|
25759
|
+
pathExists: (p) => (0, import_node_fs33.existsSync)(p),
|
|
25076
25760
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
25077
25761
|
teardownWorktreeStage,
|
|
25078
25762
|
deferredStore,
|
|
@@ -25100,7 +25784,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
25100
25784
|
let removalAttempted = false;
|
|
25101
25785
|
try {
|
|
25102
25786
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
25103
|
-
realpath: (path2) => (0,
|
|
25787
|
+
realpath: (path2) => (0, import_node_fs33.realpathSync)(path2)
|
|
25104
25788
|
});
|
|
25105
25789
|
if (!cleanupTarget.ok) {
|
|
25106
25790
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -25186,13 +25870,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
25186
25870
|
const commits = JSON.parse(raw).commits ?? [];
|
|
25187
25871
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
25188
25872
|
if (!body) return void 0;
|
|
25189
|
-
const dir = (0,
|
|
25190
|
-
const path2 = (0,
|
|
25191
|
-
(0,
|
|
25873
|
+
const dir = (0, import_node_fs33.mkdtempSync)((0, import_node_path33.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
|
|
25874
|
+
const path2 = (0, import_node_path33.join)(dir, "body.txt");
|
|
25875
|
+
(0, import_node_fs33.writeFileSync)(path2, `${body}
|
|
25192
25876
|
`, "utf8");
|
|
25193
25877
|
return { path: path2, cleanup: () => {
|
|
25194
25878
|
try {
|
|
25195
|
-
(0,
|
|
25879
|
+
(0, import_node_fs33.rmSync)(dir, { recursive: true, force: true });
|
|
25196
25880
|
} catch {
|
|
25197
25881
|
}
|
|
25198
25882
|
} };
|
|
@@ -25300,7 +25984,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
25300
25984
|
}
|
|
25301
25985
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
25302
25986
|
function spawnDeferredGcSweep() {
|
|
25303
|
-
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn:
|
|
25987
|
+
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
25304
25988
|
}
|
|
25305
25989
|
async function createDeferredWorktreeStore() {
|
|
25306
25990
|
try {
|
|
@@ -25314,13 +25998,13 @@ var realWorktreeDirRemover = {
|
|
|
25314
25998
|
probe: (p) => {
|
|
25315
25999
|
let st;
|
|
25316
26000
|
try {
|
|
25317
|
-
st = (0,
|
|
26001
|
+
st = (0, import_node_fs33.lstatSync)(p);
|
|
25318
26002
|
} catch {
|
|
25319
26003
|
return null;
|
|
25320
26004
|
}
|
|
25321
26005
|
if (st.isSymbolicLink()) return "link";
|
|
25322
26006
|
try {
|
|
25323
|
-
(0,
|
|
26007
|
+
(0, import_node_fs33.readlinkSync)(p);
|
|
25324
26008
|
return "link";
|
|
25325
26009
|
} catch {
|
|
25326
26010
|
}
|
|
@@ -25328,7 +26012,7 @@ var realWorktreeDirRemover = {
|
|
|
25328
26012
|
},
|
|
25329
26013
|
readdir: (p) => {
|
|
25330
26014
|
try {
|
|
25331
|
-
return (0,
|
|
26015
|
+
return (0, import_node_fs33.readdirSync)(p);
|
|
25332
26016
|
} catch {
|
|
25333
26017
|
return [];
|
|
25334
26018
|
}
|
|
@@ -25337,9 +26021,9 @@ var realWorktreeDirRemover = {
|
|
|
25337
26021
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
25338
26022
|
detachLink: (p) => {
|
|
25339
26023
|
try {
|
|
25340
|
-
(0,
|
|
26024
|
+
(0, import_node_fs33.rmdirSync)(p);
|
|
25341
26025
|
} catch {
|
|
25342
|
-
(0,
|
|
26026
|
+
(0, import_node_fs33.unlinkSync)(p);
|
|
25343
26027
|
}
|
|
25344
26028
|
},
|
|
25345
26029
|
removeTree: (p) => (0, import_promises8.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -25372,9 +26056,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
25372
26056
|
}
|
|
25373
26057
|
}
|
|
25374
26058
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
25375
|
-
if (!(0,
|
|
26059
|
+
if (!(0, import_node_fs33.existsSync)(statePath)) return false;
|
|
25376
26060
|
try {
|
|
25377
|
-
const state = JSON.parse((0,
|
|
26061
|
+
const state = JSON.parse((0, import_node_fs33.readFileSync)(statePath, "utf8"));
|
|
25378
26062
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
25379
26063
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
25380
26064
|
} catch {
|
|
@@ -25534,24 +26218,48 @@ var RUNNER_INFRA_ANNOTATION_TITLES = /* @__PURE__ */ new Set([
|
|
|
25534
26218
|
CI_DISK_ANNOTATION_TITLE,
|
|
25535
26219
|
CI_TOOLCHAIN_ANNOTATION_TITLE
|
|
25536
26220
|
]);
|
|
26221
|
+
var RUNNER_INFRA_ANNOTATION_MESSAGE_PATTERNS = [
|
|
26222
|
+
/not acquired by Runner/i,
|
|
26223
|
+
/Service Unavailable/i,
|
|
26224
|
+
/Failed to resolve action download info/i
|
|
26225
|
+
];
|
|
26226
|
+
var RUNNER_INFRA_PRESTART_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
26227
|
+
"cancelled",
|
|
26228
|
+
"startup_failure"
|
|
26229
|
+
]);
|
|
25537
26230
|
function isErrorAnnotation(a) {
|
|
25538
26231
|
const level = a.annotation_level?.toLowerCase();
|
|
25539
26232
|
return level === "failure" || level === "error";
|
|
25540
26233
|
}
|
|
26234
|
+
function isRunnerInfraAnnotation(a) {
|
|
26235
|
+
const title = a.title?.trim() ?? "";
|
|
26236
|
+
if (RUNNER_INFRA_ANNOTATION_TITLES.has(title)) return true;
|
|
26237
|
+
const text = `${title}
|
|
26238
|
+
${a.message?.trim() ?? ""}`;
|
|
26239
|
+
return RUNNER_INFRA_ANNOTATION_MESSAGE_PATTERNS.some((re) => re.test(text));
|
|
26240
|
+
}
|
|
25541
26241
|
function classifyFailedChecks(failing) {
|
|
25542
26242
|
const infraFailures = [];
|
|
25543
26243
|
const otherFailures = [];
|
|
25544
26244
|
for (const check of failing) {
|
|
25545
26245
|
const errors = check.annotations.filter(isErrorAnnotation);
|
|
25546
|
-
|
|
25547
|
-
|
|
26246
|
+
if (errors.length > 0) {
|
|
26247
|
+
const allInfra = errors.every((a) => isRunnerInfraAnnotation(a));
|
|
26248
|
+
(allInfra ? infraFailures : otherFailures).push(check.name);
|
|
26249
|
+
continue;
|
|
26250
|
+
}
|
|
26251
|
+
if (RUNNER_INFRA_PRESTART_CONCLUSIONS.has(check.conclusion?.trim() ?? "")) {
|
|
26252
|
+
infraFailures.push(check.name);
|
|
26253
|
+
continue;
|
|
26254
|
+
}
|
|
26255
|
+
otherFailures.push(check.name);
|
|
25548
26256
|
}
|
|
25549
26257
|
if (infraFailures.length && !otherFailures.length) {
|
|
25550
26258
|
return {
|
|
25551
26259
|
cause: "runner-infra",
|
|
25552
26260
|
infraFailures,
|
|
25553
26261
|
otherFailures,
|
|
25554
|
-
reason: `${infraFailures.length} check(s) failed on RUNNER INFRASTRUCTURE, not your diff: ${infraFailures.join(", ")}. No test failed \u2014 the shared runner hit a wall-clock budget kill, ran out of disk,
|
|
26262
|
+
reason: `${infraFailures.length} check(s) failed on RUNNER INFRASTRUCTURE, not your diff: ${infraFailures.join(", ")}. No test failed \u2014 the shared runner hit a wall-clock budget kill, ran out of disk, was missing a toolchain, could not acquire an mmi-live lane, or Actions returned Service Unavailable before checkout (the check annotation / conclusion names which). Re-run once the runner drains/heals (gh run rerun --failed); do not debug the diff.`
|
|
25555
26263
|
};
|
|
25556
26264
|
}
|
|
25557
26265
|
return {
|
|
@@ -25579,6 +26287,7 @@ async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
|
|
|
25579
26287
|
if (!failing.length) return null;
|
|
25580
26288
|
const annotated = await Promise.all(failing.map(async (run) => ({
|
|
25581
26289
|
name: run.name ?? `check-run ${run.id}`,
|
|
26290
|
+
conclusion: run.conclusion ?? null,
|
|
25582
26291
|
annotations: JSON.parse(await gh([`repos/${repo}/check-runs/${run.id}/annotations`]))
|
|
25583
26292
|
})));
|
|
25584
26293
|
return classifyFailedChecks(annotated);
|
|
@@ -25680,9 +26389,9 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
25680
26389
|
}
|
|
25681
26390
|
|
|
25682
26391
|
// src/worktree-lifecycle-commands.ts
|
|
25683
|
-
var
|
|
26392
|
+
var import_node_fs34 = require("node:fs");
|
|
25684
26393
|
var import_promises9 = require("node:fs/promises");
|
|
25685
|
-
var
|
|
26394
|
+
var import_node_path34 = require("node:path");
|
|
25686
26395
|
var GH_TIMEOUT_MS = 2e4;
|
|
25687
26396
|
var DEFAULT_BASE = "origin/development";
|
|
25688
26397
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -25828,7 +26537,7 @@ function classifyStaleLeaks(input) {
|
|
|
25828
26537
|
var defaultOrphanDirScanDeps = {
|
|
25829
26538
|
listDirs: (root) => {
|
|
25830
26539
|
try {
|
|
25831
|
-
return (0,
|
|
26540
|
+
return (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path34.join)(root, e.name));
|
|
25832
26541
|
} catch {
|
|
25833
26542
|
return [];
|
|
25834
26543
|
}
|
|
@@ -25981,13 +26690,13 @@ function registerWorktreeCommands(program3) {
|
|
|
25981
26690
|
const detached = headBorn && !symbolicBranch;
|
|
25982
26691
|
const branch = symbolicBranch || (detached ? "HEAD" : "");
|
|
25983
26692
|
if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
|
|
25984
|
-
const gitFile = (0,
|
|
25985
|
-
const isLinked = (0,
|
|
26693
|
+
const gitFile = (0, import_node_path34.join)(wtPath, ".git");
|
|
26694
|
+
const isLinked = (0, import_node_fs34.existsSync)(gitFile) && (0, import_node_fs34.statSync)(gitFile).isFile();
|
|
25986
26695
|
if (apply && !isLinked) {
|
|
25987
26696
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
25988
26697
|
}
|
|
25989
26698
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25990
|
-
const primaryCheckout = commonDir ? (0,
|
|
26699
|
+
const primaryCheckout = commonDir ? (0, import_node_path34.dirname)(commonDir) : wtPath;
|
|
25991
26700
|
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);
|
|
25992
26701
|
const orphan = classifyOrphanedWorktree({
|
|
25993
26702
|
branch,
|
|
@@ -26198,10 +26907,10 @@ async function gatherWorktreeContext() {
|
|
|
26198
26907
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
26199
26908
|
}
|
|
26200
26909
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
26201
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
26910
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path34.dirname)((0, import_node_path34.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
26202
26911
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
26203
26912
|
let orphanDirs = [];
|
|
26204
|
-
if ((0,
|
|
26913
|
+
if ((0, import_node_fs34.existsSync)(wtRoot)) {
|
|
26205
26914
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
26206
26915
|
...defaultOrphanDirScanDeps,
|
|
26207
26916
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -26227,8 +26936,8 @@ ${err.stderr ?? ""}`;
|
|
|
26227
26936
|
}
|
|
26228
26937
|
|
|
26229
26938
|
// src/issue-commands.ts
|
|
26230
|
-
var
|
|
26231
|
-
var
|
|
26939
|
+
var import_node_fs35 = require("node:fs");
|
|
26940
|
+
var import_node_crypto6 = require("node:crypto");
|
|
26232
26941
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
26233
26942
|
var ReparentConflictError = class extends Error {
|
|
26234
26943
|
constructor(message, payload) {
|
|
@@ -26245,7 +26954,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
26245
26954
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
26246
26955
|
const patch = {};
|
|
26247
26956
|
let bodyChanged = false;
|
|
26248
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
26957
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs35.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
26249
26958
|
if (options.titleFile !== void 0) {
|
|
26250
26959
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
26251
26960
|
} else if (options.title !== void 0) {
|
|
@@ -26478,7 +27187,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
26478
27187
|
const identity = `${spec.type}
|
|
26479
27188
|
${spec.title.trim()}
|
|
26480
27189
|
${spec.body ?? ""}`;
|
|
26481
|
-
const hash = (0,
|
|
27190
|
+
const hash = (0, import_node_crypto6.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
26482
27191
|
return `${batchKey}:${hash}`;
|
|
26483
27192
|
}
|
|
26484
27193
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -26808,7 +27517,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
26808
27517
|
if (opts.batch) {
|
|
26809
27518
|
let specs;
|
|
26810
27519
|
try {
|
|
26811
|
-
const raw = (0,
|
|
27520
|
+
const raw = (0, import_node_fs35.readFileSync)(opts.batch, "utf8");
|
|
26812
27521
|
specs = JSON.parse(raw);
|
|
26813
27522
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
26814
27523
|
} catch (e) {
|
|
@@ -26882,8 +27591,8 @@ ${lines}`, {
|
|
|
26882
27591
|
}
|
|
26883
27592
|
|
|
26884
27593
|
// src/train-commands.ts
|
|
26885
|
-
var
|
|
26886
|
-
var
|
|
27594
|
+
var import_node_fs36 = require("node:fs");
|
|
27595
|
+
var import_node_path35 = require("node:path");
|
|
26887
27596
|
|
|
26888
27597
|
// src/train-status.ts
|
|
26889
27598
|
function buildTrainStatusReport(input) {
|
|
@@ -26923,7 +27632,7 @@ function formatTrainStatus(r) {
|
|
|
26923
27632
|
// src/train-commands.ts
|
|
26924
27633
|
function readRepoVersion() {
|
|
26925
27634
|
try {
|
|
26926
|
-
return JSON.parse((0,
|
|
27635
|
+
return JSON.parse((0, import_node_fs36.readFileSync)((0, import_node_path35.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
26927
27636
|
} catch {
|
|
26928
27637
|
return void 0;
|
|
26929
27638
|
}
|
|
@@ -27069,9 +27778,9 @@ function registerDeployCommands(program3) {
|
|
|
27069
27778
|
}
|
|
27070
27779
|
|
|
27071
27780
|
// src/discovery-commands.ts
|
|
27072
|
-
var
|
|
27073
|
-
var
|
|
27074
|
-
var
|
|
27781
|
+
var import_node_fs37 = require("node:fs");
|
|
27782
|
+
var import_node_os13 = require("node:os");
|
|
27783
|
+
var import_node_path36 = require("node:path");
|
|
27075
27784
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
27076
27785
|
async function collectStatus() {
|
|
27077
27786
|
const repo = await resolveRepo();
|
|
@@ -27247,10 +27956,10 @@ async function collectOnboardStatus() {
|
|
|
27247
27956
|
else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
|
|
27248
27957
|
else nextCommand = "mmi-cli board read \u2014 no claimable items found";
|
|
27249
27958
|
}
|
|
27250
|
-
const home = (0,
|
|
27959
|
+
const home = (0, import_node_os13.homedir)();
|
|
27251
27960
|
const plugin = onboardPluginGate({
|
|
27252
|
-
readKnown: () => readFileSyncSafe((0,
|
|
27253
|
-
readSettings: () => readFileSyncSafe((0,
|
|
27961
|
+
readKnown: () => readFileSyncSafe((0, import_node_path36.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs37.readFileSync),
|
|
27962
|
+
readSettings: () => readFileSyncSafe((0, import_node_path36.join)(home, ".claude", "settings.json"), import_node_fs37.readFileSync)
|
|
27254
27963
|
});
|
|
27255
27964
|
return { track, board, registry: registry2, secrets, plugin, nextCommand };
|
|
27256
27965
|
}
|
|
@@ -27313,6 +28022,7 @@ var LOOP_PLAYBOOKS = {
|
|
|
27313
28022
|
title: "Agent",
|
|
27314
28023
|
steps: [
|
|
27315
28024
|
{ label: "Orient in the current repository", command: "mmi-cli onboard" },
|
|
28025
|
+
{ label: "Structure search via Hub cloud (paths/symbols/meaning) before grepping docs/", command: "mmi-cli repo-index search <q>" },
|
|
27316
28026
|
{ label: "Read the next board item", command: "mmi-cli board read" },
|
|
27317
28027
|
{ label: "Claim it and create an isolated worktree", command: "mmi-cli worktree create <issue-number> --claim --from origin/development" },
|
|
27318
28028
|
{ label: "Build and test in the touched package", command: "npm test && npm run build" },
|
|
@@ -28858,7 +29568,8 @@ function diagnoseSurface(evidence) {
|
|
|
28858
29568
|
return { ...base, state: "repair-failed", repairDetail: evidence.repair.detail };
|
|
28859
29569
|
}
|
|
28860
29570
|
if (evidence.repair?.attempted && evidence.repair.ok) {
|
|
28861
|
-
|
|
29571
|
+
const behindReleased = evidence.installedVersion && evidence.releasedVersion && compareVersions(evidence.installedVersion, evidence.releasedVersion) < 0;
|
|
29572
|
+
return { ...base, state: behindReleased ? "pending-reload" : "clean", repairDetail: evidence.repair.detail };
|
|
28862
29573
|
}
|
|
28863
29574
|
if (!evidence.installRecordPresent) return { ...base, state: "missing" };
|
|
28864
29575
|
if (!evidence.deliveryPresent || !evidence.payloadPresent || evidence.manifest === "missing") {
|
|
@@ -28876,7 +29587,7 @@ function reloadInstruction(descriptor) {
|
|
|
28876
29587
|
return `${verb} ${descriptor.displayName}`;
|
|
28877
29588
|
}
|
|
28878
29589
|
function planSurfaceRepair(diagnosis) {
|
|
28879
|
-
if (diagnosis.state === "clean" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
29590
|
+
if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
28880
29591
|
const descriptor = diagnosis.descriptor;
|
|
28881
29592
|
if (descriptor.repairOwner !== "mmi-cli") {
|
|
28882
29593
|
return {
|
|
@@ -28901,6 +29612,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
28901
29612
|
const detailByState = {
|
|
28902
29613
|
skipped: "skipped \u2014 host not active",
|
|
28903
29614
|
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
29615
|
+
"pending-reload": `pending ${descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
|
|
28904
29616
|
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
28905
29617
|
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
28906
29618
|
missing: "missing \u2014 no install record",
|
|
@@ -28912,11 +29624,14 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
28912
29624
|
id: `${descriptor.token}-plugin`,
|
|
28913
29625
|
surface: descriptor.token,
|
|
28914
29626
|
state,
|
|
28915
|
-
ok: state === "clean" || state === "skipped",
|
|
29627
|
+
ok: state === "clean" || state === "skipped" || state === "pending-reload",
|
|
28916
29628
|
...state === "freshness-unknown" ? { reportOnly: true } : {},
|
|
29629
|
+
// Nothing is broken, so this is not a ✗ — but it is the one OK row an operator must act on, and the
|
|
29630
|
+
// short default lane and the SessionStart banner both filter to failures unless a row says otherwise.
|
|
29631
|
+
...state === "pending-reload" ? { warn: true } : {},
|
|
28917
29632
|
label: `${descriptor.displayName} plugin`,
|
|
28918
29633
|
detail: detailByState[state],
|
|
28919
|
-
...plan ? { fix: plan.instruction } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
29634
|
+
...plan ? { fix: plan.instruction } : state === "pending-reload" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
28920
29635
|
verbose: [
|
|
28921
29636
|
// The three numbers the legacy builder used to print. They belong here now that this is the only
|
|
28922
29637
|
// plugin row, and a report that names a state without naming the versions behind it is not evidence.
|
|
@@ -29197,6 +29912,102 @@ function checkDocsIndex(probe) {
|
|
|
29197
29912
|
verbose: evidence
|
|
29198
29913
|
};
|
|
29199
29914
|
}
|
|
29915
|
+
function checkRepoIndexCloud(probe) {
|
|
29916
|
+
if (!probe) return null;
|
|
29917
|
+
const evidence = [
|
|
29918
|
+
probe.repo ? `repo: ${probe.repo}` : "repo: (estate)",
|
|
29919
|
+
probe.builtAt ? `builtAt: ${probe.builtAt}` : "builtAt: n/a",
|
|
29920
|
+
typeof probe.fileCount === "number" ? `fileCount: ${probe.fileCount}` : "fileCount: n/a",
|
|
29921
|
+
typeof probe.embCount === "number" ? `embCount: ${probe.embCount}` : "embCount: n/a",
|
|
29922
|
+
typeof probe.localPresent === "boolean" ? `local cache: ${probe.localPresent ? "present" : "absent"}` : "local cache: n/a"
|
|
29923
|
+
];
|
|
29924
|
+
switch (probe.kind) {
|
|
29925
|
+
case "healthy":
|
|
29926
|
+
return {
|
|
29927
|
+
ok: true,
|
|
29928
|
+
id: "repo-index",
|
|
29929
|
+
label: "repo-index",
|
|
29930
|
+
detail: probe.detail ?? `cloud ok${typeof probe.fileCount === "number" ? ` \u2014 ${probe.fileCount} files` : ""}`,
|
|
29931
|
+
verbose: evidence
|
|
29932
|
+
};
|
|
29933
|
+
case "command-absent":
|
|
29934
|
+
return {
|
|
29935
|
+
ok: false,
|
|
29936
|
+
id: "repo-index",
|
|
29937
|
+
label: "repo-index",
|
|
29938
|
+
detail: "this mmi-cli build has no repo-index command",
|
|
29939
|
+
fix: "run `mmi-cli doctor` to heal the global CLI, or `npm install -g @mutmutco/cli`",
|
|
29940
|
+
verbose: evidence
|
|
29941
|
+
};
|
|
29942
|
+
case "deploy-lag":
|
|
29943
|
+
return {
|
|
29944
|
+
ok: false,
|
|
29945
|
+
id: "repo-index",
|
|
29946
|
+
label: "repo-index",
|
|
29947
|
+
detail: "Hub /repo-index/* not deployed yet (HTTP 404)",
|
|
29948
|
+
fix: "ship a Hub release carrying estate index routes, then run repo-index-reconcile",
|
|
29949
|
+
reportOnly: true,
|
|
29950
|
+
verbose: evidence
|
|
29951
|
+
};
|
|
29952
|
+
case "missing-projection":
|
|
29953
|
+
return {
|
|
29954
|
+
ok: false,
|
|
29955
|
+
id: "repo-index",
|
|
29956
|
+
label: "repo-index",
|
|
29957
|
+
detail: probe.detail ?? "no cloud projection for this repo",
|
|
29958
|
+
fix: "run harbour repo-index-reconcile or `mmi-cli repo-index sync-estate --repo <owner/name>`",
|
|
29959
|
+
verbose: evidence
|
|
29960
|
+
};
|
|
29961
|
+
case "stale":
|
|
29962
|
+
return {
|
|
29963
|
+
ok: false,
|
|
29964
|
+
id: "repo-index",
|
|
29965
|
+
label: "repo-index",
|
|
29966
|
+
detail: probe.detail ?? "cloud projection looks stale vs recent pushes",
|
|
29967
|
+
fix: "trigger `repo-index-reconcile` (workflow_dispatch) or wait for the 6h harbour tick",
|
|
29968
|
+
verbose: evidence
|
|
29969
|
+
};
|
|
29970
|
+
case "auth":
|
|
29971
|
+
return {
|
|
29972
|
+
ok: false,
|
|
29973
|
+
id: "repo-index",
|
|
29974
|
+
label: "repo-index",
|
|
29975
|
+
detail: "Hub session missing or unauthorized for repo-index",
|
|
29976
|
+
fix: "run `gh auth login` and ensure a Hub session exists (`mmi-cli whoami`)",
|
|
29977
|
+
verbose: evidence
|
|
29978
|
+
};
|
|
29979
|
+
case "config":
|
|
29980
|
+
return {
|
|
29981
|
+
ok: false,
|
|
29982
|
+
id: "repo-index",
|
|
29983
|
+
label: "repo-index",
|
|
29984
|
+
detail: probe.detail ?? "Hub API URL not configured",
|
|
29985
|
+
fix: "run `mmi-cli doctor` to repair Hub wiring",
|
|
29986
|
+
verbose: evidence
|
|
29987
|
+
};
|
|
29988
|
+
case "network":
|
|
29989
|
+
return {
|
|
29990
|
+
ok: false,
|
|
29991
|
+
id: "repo-index",
|
|
29992
|
+
label: "repo-index",
|
|
29993
|
+
detail: probe.detail ?? "could not reach Hub repo-index",
|
|
29994
|
+
fix: "retry when the network is up; structure search stays fail-soft",
|
|
29995
|
+
reportOnly: true,
|
|
29996
|
+
verbose: evidence
|
|
29997
|
+
};
|
|
29998
|
+
case "local-advisory":
|
|
29999
|
+
return {
|
|
30000
|
+
ok: true,
|
|
30001
|
+
id: "repo-index",
|
|
30002
|
+
label: "repo-index",
|
|
30003
|
+
detail: probe.detail ?? "local cache absent (cloud search does not need it)",
|
|
30004
|
+
warn: true,
|
|
30005
|
+
verbose: evidence
|
|
30006
|
+
};
|
|
30007
|
+
default:
|
|
30008
|
+
return null;
|
|
30009
|
+
}
|
|
30010
|
+
}
|
|
29200
30011
|
function planGitignore(current) {
|
|
29201
30012
|
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
29202
30013
|
return changed ? { ok: false, content } : { ok: true };
|
|
@@ -29386,6 +30197,36 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
29386
30197
|
const docs2 = checkDocsIndex(probe);
|
|
29387
30198
|
if (docs2) emitNow(docs2);
|
|
29388
30199
|
}
|
|
30200
|
+
async function runRepoIndexRow() {
|
|
30201
|
+
if (lane.preflight && !opts.self && !lane.full) {
|
|
30202
|
+
emitNow({
|
|
30203
|
+
ok: true,
|
|
30204
|
+
id: "repo-index",
|
|
30205
|
+
label: "repo-index",
|
|
30206
|
+
detail: "command present on this CLI (cloud probe skipped on --preflight)",
|
|
30207
|
+
verbose: ["preflight lane: no Hub network"]
|
|
30208
|
+
});
|
|
30209
|
+
return;
|
|
30210
|
+
}
|
|
30211
|
+
let probe;
|
|
30212
|
+
try {
|
|
30213
|
+
probe = await deps.repoIndexCloudState(await deps.repoRoot());
|
|
30214
|
+
} catch (e) {
|
|
30215
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
30216
|
+
emitNow({
|
|
30217
|
+
ok: false,
|
|
30218
|
+
id: "repo-index",
|
|
30219
|
+
label: "repo-index",
|
|
30220
|
+
detail: `could not be read \u2014 ${message}`,
|
|
30221
|
+
fix: "repair Hub session / network, then re-run doctor",
|
|
30222
|
+
reportOnly: true,
|
|
30223
|
+
verbose: [`probe threw: ${message}`]
|
|
30224
|
+
});
|
|
30225
|
+
return;
|
|
30226
|
+
}
|
|
30227
|
+
const row = checkRepoIndexCloud(probe);
|
|
30228
|
+
if (row) emitNow(row);
|
|
30229
|
+
}
|
|
29389
30230
|
async function runHousekeeperRows() {
|
|
29390
30231
|
const repoRoot2 = await deps.repoRoot();
|
|
29391
30232
|
try {
|
|
@@ -29459,6 +30300,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
29459
30300
|
// A `docs/` tree walk plus one batched `git check-ignore` — cheap next to the two rows below it, but a
|
|
29460
30301
|
// disk walk and a subprocess all the same, so full lane only (#4091).
|
|
29461
30302
|
{ id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
|
|
30303
|
+
// Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
|
|
30304
|
+
{ id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
|
|
29462
30305
|
// The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
|
|
29463
30306
|
// and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
|
|
29464
30307
|
{ id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
|
|
@@ -29516,18 +30359,18 @@ function parseOriginRepo(remoteUrl) {
|
|
|
29516
30359
|
return `${match[1]}/${match[2]}`;
|
|
29517
30360
|
}
|
|
29518
30361
|
function ghHostsConfigPath(env, platform2) {
|
|
29519
|
-
const
|
|
29520
|
-
const
|
|
30362
|
+
const sep3 = platform2 === "win32" ? "\\" : "/";
|
|
30363
|
+
const join34 = (...parts) => parts.join(sep3);
|
|
29521
30364
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
29522
|
-
if (explicit) return
|
|
30365
|
+
if (explicit) return join34(explicit, "hosts.yml");
|
|
29523
30366
|
if (platform2 === "win32") {
|
|
29524
30367
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
29525
|
-
return appData ?
|
|
30368
|
+
return appData ? join34(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
29526
30369
|
}
|
|
29527
30370
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
29528
|
-
if (xdg) return
|
|
30371
|
+
if (xdg) return join34(xdg, "gh", "hosts.yml");
|
|
29529
30372
|
const home = env.HOME?.trim();
|
|
29530
|
-
return home ?
|
|
30373
|
+
return home ? join34(home, ".config", "gh", "hosts.yml") : void 0;
|
|
29531
30374
|
}
|
|
29532
30375
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
29533
30376
|
let hostIndent = null;
|
|
@@ -29577,17 +30420,17 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
29577
30420
|
}
|
|
29578
30421
|
|
|
29579
30422
|
// src/doctor-io.ts
|
|
29580
|
-
var
|
|
29581
|
-
var
|
|
29582
|
-
var
|
|
29583
|
-
var
|
|
30423
|
+
var import_node_fs38 = require("node:fs");
|
|
30424
|
+
var import_node_os14 = require("node:os");
|
|
30425
|
+
var import_node_path37 = require("node:path");
|
|
30426
|
+
var import_node_child_process17 = require("node:child_process");
|
|
29584
30427
|
var import_node_util8 = require("node:util");
|
|
29585
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
30428
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process17.execFile);
|
|
29586
30429
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
29587
30430
|
function installedClaudePluginVersion() {
|
|
29588
30431
|
try {
|
|
29589
30432
|
const file = JSON.parse(
|
|
29590
|
-
(0,
|
|
30433
|
+
(0, import_node_fs38.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
29591
30434
|
);
|
|
29592
30435
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
29593
30436
|
if (versions.length === 0) return void 0;
|
|
@@ -29598,7 +30441,7 @@ function installedClaudePluginVersion() {
|
|
|
29598
30441
|
}
|
|
29599
30442
|
function manifestVersion(path2) {
|
|
29600
30443
|
try {
|
|
29601
|
-
const manifest = JSON.parse((0,
|
|
30444
|
+
const manifest = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
|
|
29602
30445
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
29603
30446
|
} catch {
|
|
29604
30447
|
return void 0;
|
|
@@ -29608,27 +30451,27 @@ function installedSurfacePluginVersion(surface) {
|
|
|
29608
30451
|
const token = surfaceToken(surface);
|
|
29609
30452
|
if (token === "kilo") {
|
|
29610
30453
|
try {
|
|
29611
|
-
const stamp = (0,
|
|
30454
|
+
const stamp = (0, import_node_fs38.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
29612
30455
|
return stamp || void 0;
|
|
29613
30456
|
} catch {
|
|
29614
30457
|
return void 0;
|
|
29615
30458
|
}
|
|
29616
30459
|
}
|
|
29617
30460
|
if (token === "cursor") {
|
|
29618
|
-
return manifestVersion((0,
|
|
30461
|
+
return manifestVersion((0, import_node_path37.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
29619
30462
|
}
|
|
29620
30463
|
if (token === "kimi") {
|
|
29621
|
-
return manifestVersion((0,
|
|
30464
|
+
return manifestVersion((0, import_node_path37.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
29622
30465
|
}
|
|
29623
30466
|
if (token === "claude") return installedClaudePluginVersion();
|
|
29624
30467
|
if (token !== "codex") return void 0;
|
|
29625
30468
|
try {
|
|
29626
|
-
const raw = process.platform === "win32" ? (0,
|
|
30469
|
+
const raw = process.platform === "win32" ? (0, import_node_child_process17.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
|
|
29627
30470
|
encoding: "utf8",
|
|
29628
30471
|
stdio: ["ignore", "pipe", "ignore"],
|
|
29629
30472
|
timeout: 15e3,
|
|
29630
30473
|
windowsHide: true
|
|
29631
|
-
}) : (0,
|
|
30474
|
+
}) : (0, import_node_child_process17.execFileSync)("codex", ["plugin", "list", "--json"], {
|
|
29632
30475
|
encoding: "utf8",
|
|
29633
30476
|
stdio: ["ignore", "pipe", "ignore"],
|
|
29634
30477
|
timeout: 15e3,
|
|
@@ -29646,7 +30489,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
|
29646
30489
|
}
|
|
29647
30490
|
function worktreeRootSync() {
|
|
29648
30491
|
try {
|
|
29649
|
-
const out = (0,
|
|
30492
|
+
const out = (0, import_node_child_process17.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
29650
30493
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
29651
30494
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
29652
30495
|
return root || null;
|
|
@@ -29656,13 +30499,13 @@ function worktreeRootSync() {
|
|
|
29656
30499
|
}
|
|
29657
30500
|
var gitignorePath = () => {
|
|
29658
30501
|
const root = worktreeRootSync();
|
|
29659
|
-
return root === null ? null : (0,
|
|
30502
|
+
return root === null ? null : (0, import_node_path37.join)(root, ".gitignore");
|
|
29660
30503
|
};
|
|
29661
30504
|
function readGitignore() {
|
|
29662
30505
|
const path2 = gitignorePath();
|
|
29663
30506
|
if (path2 === null) return null;
|
|
29664
30507
|
try {
|
|
29665
|
-
return (0,
|
|
30508
|
+
return (0, import_node_fs38.readFileSync)(path2, "utf8");
|
|
29666
30509
|
} catch {
|
|
29667
30510
|
return null;
|
|
29668
30511
|
}
|
|
@@ -29671,7 +30514,7 @@ function writeGitignore(content) {
|
|
|
29671
30514
|
const path2 = gitignorePath();
|
|
29672
30515
|
if (path2 === null) return false;
|
|
29673
30516
|
try {
|
|
29674
|
-
(0,
|
|
30517
|
+
(0, import_node_fs38.writeFileSync)(path2, content, "utf8");
|
|
29675
30518
|
return true;
|
|
29676
30519
|
} catch {
|
|
29677
30520
|
return false;
|
|
@@ -29695,7 +30538,7 @@ async function repoRoot() {
|
|
|
29695
30538
|
}
|
|
29696
30539
|
function hasRepoLocalWorktrees() {
|
|
29697
30540
|
const root = worktreeRootSync();
|
|
29698
|
-
return root !== null && (0,
|
|
30541
|
+
return root !== null && (0, import_node_fs38.existsSync)((0, import_node_path37.join)(root, ".worktrees"));
|
|
29699
30542
|
}
|
|
29700
30543
|
|
|
29701
30544
|
// src/index.ts
|
|
@@ -29731,8 +30574,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
29731
30574
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
29732
30575
|
try {
|
|
29733
30576
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
29734
|
-
if (!hostsPath || !(0,
|
|
29735
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
30577
|
+
if (!hostsPath || !(0, import_node_fs39.existsSync)(hostsPath)) return void 0;
|
|
30578
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs39.readFileSync)(hostsPath, "utf8")));
|
|
29736
30579
|
} catch {
|
|
29737
30580
|
return void 0;
|
|
29738
30581
|
}
|
|
@@ -29740,20 +30583,20 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
29740
30583
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
29741
30584
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
29742
30585
|
function envHealLockPath(home) {
|
|
29743
|
-
return (0,
|
|
30586
|
+
return (0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
29744
30587
|
}
|
|
29745
30588
|
async function withEnvHealLock(what, run) {
|
|
29746
30589
|
try {
|
|
29747
30590
|
return await withFileLock(
|
|
29748
|
-
envHealLockPath((0,
|
|
30591
|
+
envHealLockPath((0, import_node_os15.homedir)()),
|
|
29749
30592
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
29750
30593
|
run
|
|
29751
30594
|
);
|
|
29752
30595
|
} catch (e) {
|
|
29753
30596
|
if (e instanceof FileLockBusyError) {
|
|
29754
|
-
return { ok: false, skipped: true, detail: `${what} skipped
|
|
30597
|
+
return { ok: false, skipped: true, detail: `${what} skipped ? ${e.message}` };
|
|
29755
30598
|
}
|
|
29756
|
-
return { ok: false, detail: `${what} could not take the env-heal lock
|
|
30599
|
+
return { ok: false, detail: `${what} could not take the env-heal lock ? ${e.message}` };
|
|
29757
30600
|
}
|
|
29758
30601
|
}
|
|
29759
30602
|
function throttledReleasedVersion() {
|
|
@@ -29809,13 +30652,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29809
30652
|
pluginTrustState: () => codexHookTrustState(),
|
|
29810
30653
|
releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
|
|
29811
30654
|
releasedVersionNote: throttled ? throttled.note : void 0,
|
|
29812
|
-
// #3272: the --apply self-heal for a stale running CLI
|
|
30655
|
+
// #3272: the --apply self-heal for a stale running CLI ? npm global, shadows any plugin shim (#2879).
|
|
29813
30656
|
// #3489: serialised. Both heals mutate machine-global state (the npm global prefix; the Claude
|
|
29814
30657
|
// marketplace clone + plugin cache) and neither is idempotent under concurrency. They share ONE lock
|
|
29815
30658
|
// rather than one each, because they are not independent: the plugin heal reinstalls a bundle that
|
|
29816
30659
|
// carries a CLI shim, so a concurrent npm global install races the same PATH surface.
|
|
29817
30660
|
updateCli: (target, onStep) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target, onStep)),
|
|
29818
|
-
// #3282: the --apply self-heal for a stale/unresolved Claude plugin
|
|
30661
|
+
// #3282: the --apply self-heal for a stale/unresolved Claude plugin ? the same marketplace reinstall
|
|
29819
30662
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
29820
30663
|
healPlugin: (onStep) => {
|
|
29821
30664
|
const surface = detectSurface(process.env);
|
|
@@ -29833,7 +30676,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29833
30676
|
// #2759: same fetch+ff-only sync SessionStart runs, wired here too so an on-demand `mmi-cli doctor`
|
|
29834
30677
|
// self-heals a checkout a long-running session left stale.
|
|
29835
30678
|
syncTrain: () => syncLocalTrainBranches(execFileGitRun),
|
|
29836
|
-
// #2903: doctor DETECTS stale cached plugin versions and defers the delete to `plugin-prune`
|
|
30679
|
+
// #2903: doctor DETECTS stale cached plugin versions and defers the delete to `plugin-prune` ? it never
|
|
29837
30680
|
// writes to the harness-owned cache itself. Same plan builder the verb uses, so the two never disagree.
|
|
29838
30681
|
// No `withBytes` here: doctor runs on EVERY SessionStart, and sizing means recursively stat'ing every
|
|
29839
30682
|
// stale version tree. The count comes from one cheap readdir; `plugin-prune` reports the MB.
|
|
@@ -29842,7 +30685,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29842
30685
|
const configRoot = surfaceConfigRoot(surface);
|
|
29843
30686
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
29844
30687
|
const plan = buildPluginCachePlan(
|
|
29845
|
-
(0,
|
|
30688
|
+
(0, import_node_os15.homedir)(),
|
|
29846
30689
|
running,
|
|
29847
30690
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
29848
30691
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -29856,24 +30699,24 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29856
30699
|
};
|
|
29857
30700
|
},
|
|
29858
30701
|
// #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
|
|
29859
|
-
// A local record read
|
|
30702
|
+
// A local record read ? cheap enough for every lane, including the banner.
|
|
29860
30703
|
sessionPayload: () => readSessionPayload(process.cwd()),
|
|
29861
|
-
// #3485 items 9 and 8: why the MMI plugin has never printed an "updated
|
|
30704
|
+
// #3485 items 9 and 8: why the MMI plugin has never printed an "updated ? please restart" notice, and
|
|
29862
30705
|
// which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
|
|
29863
30706
|
marketplaceRows: () => {
|
|
29864
30707
|
try {
|
|
29865
30708
|
if (detectSurface(process.env) === "codex") return [];
|
|
29866
|
-
const home = (0,
|
|
30709
|
+
const home = (0, import_node_os15.homedir)();
|
|
29867
30710
|
const rows = marketplaceRows(
|
|
29868
30711
|
MMI_MARKETPLACE_NAME,
|
|
29869
|
-
readFileSyncSafe((0,
|
|
29870
|
-
readFileSyncSafe((0,
|
|
30712
|
+
readFileSyncSafe((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs39.readFileSync),
|
|
30713
|
+
readFileSyncSafe((0, import_node_path38.join)(home, ".claude", "settings.json"), import_node_fs39.readFileSync),
|
|
29871
30714
|
// #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
|
|
29872
|
-
// edit. Unconditional
|
|
30715
|
+
// edit. Unconditional ? it is a fact about mmi-cli, not about the lane this run is on.
|
|
29873
30716
|
true
|
|
29874
30717
|
);
|
|
29875
30718
|
const pending = readMarketplacePinPending(
|
|
29876
|
-
(0,
|
|
30719
|
+
(0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
|
|
29877
30720
|
MMI_MARKETPLACE_NAME
|
|
29878
30721
|
);
|
|
29879
30722
|
if (!pending) return rows;
|
|
@@ -29884,7 +30727,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29884
30727
|
...rest,
|
|
29885
30728
|
ok: true,
|
|
29886
30729
|
warn: true,
|
|
29887
|
-
detail: `pending restart
|
|
30730
|
+
detail: `pending restart ? doctor pinned main at ${pending.at}; restart Claude Code to load it`,
|
|
29888
30731
|
verbose: [...row.verbose ?? [], "known_marketplaces.json was rewritten by the running Claude host after the verified doctor write"]
|
|
29889
30732
|
};
|
|
29890
30733
|
});
|
|
@@ -29892,16 +30735,16 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29892
30735
|
return [];
|
|
29893
30736
|
}
|
|
29894
30737
|
},
|
|
29895
|
-
// #3974: the heal behind those rows, under --apply only. Same Codex carve-out as the rows above
|
|
30738
|
+
// #3974: the heal behind those rows, under --apply only. Same Codex carve-out as the rows above ?
|
|
29896
30739
|
// Codex keeps its registration in config.toml, so there is no ~/.claude registration to pin.
|
|
29897
30740
|
healMarketplacePins: () => {
|
|
29898
30741
|
try {
|
|
29899
30742
|
if (detectSurface(process.env) === "codex") return void 0;
|
|
29900
|
-
const home = (0,
|
|
30743
|
+
const home = (0, import_node_os15.homedir)();
|
|
29901
30744
|
const names = [MMI_MARKETPLACE_NAME];
|
|
29902
|
-
const result = applyOrgMarketplacePins((0,
|
|
30745
|
+
const result = applyOrgMarketplacePins((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
|
|
29903
30746
|
if (result?.wrote) {
|
|
29904
|
-
writeMarketplacePinPending((0,
|
|
30747
|
+
writeMarketplacePinPending((0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
|
|
29905
30748
|
}
|
|
29906
30749
|
return result;
|
|
29907
30750
|
} catch {
|
|
@@ -29909,20 +30752,73 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29909
30752
|
}
|
|
29910
30753
|
},
|
|
29911
30754
|
// #4091: the same `docs index --check` comparison the required CI gate runs, in-process. Rooted at the
|
|
29912
|
-
// repo root doctor already resolved
|
|
30755
|
+
// repo root doctor already resolved ? never `process.cwd()`, which would have `mmi-cli doctor` run from
|
|
29913
30756
|
// `cli/` measure a `cli/docs/` tree that does not exist.
|
|
29914
30757
|
//
|
|
29915
30758
|
// `existsSync` on `docs/index.md` is the adoption gate, and it sits UNDER the table's org-repo gate
|
|
29916
30759
|
// rather than replacing it: a missing index is drift by construction, so without this a repo that never
|
|
29917
30760
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
29918
|
-
// get a permanent
|
|
30761
|
+
// get a permanent ? demanding an artifact it never asked for.
|
|
29919
30762
|
docsIndexState: (root) => {
|
|
29920
|
-
if (!(0,
|
|
30763
|
+
if (!(0, import_node_fs39.existsSync)((0, import_node_path38.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
29921
30764
|
const real = createDocsIndexDeps(root);
|
|
29922
30765
|
let docs2;
|
|
29923
30766
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
29924
30767
|
const drift = docsIndex({ ...real, listDocs }, { check: true }).drift;
|
|
29925
30768
|
return { drift, docCount: listDocs().length };
|
|
30769
|
+
},
|
|
30770
|
+
// #4156: short-timeout Hub status for the current repo. Fail-soft ? never throws to doctor.
|
|
30771
|
+
repoIndexCloudState: async (root) => {
|
|
30772
|
+
const local = repoIndexStatus(root);
|
|
30773
|
+
try {
|
|
30774
|
+
const cfg = await loadConfig();
|
|
30775
|
+
const repo = inferRepoSlug(root);
|
|
30776
|
+
const st = await statusRepoIndexCloud(repo, {
|
|
30777
|
+
...registryClientDeps(cfg),
|
|
30778
|
+
timeoutMs: 4e3
|
|
30779
|
+
});
|
|
30780
|
+
if (isRepoIndexStatusError(st)) {
|
|
30781
|
+
if (st.code === "deploy-lag") return { kind: "deploy-lag", repo, localPresent: local.present };
|
|
30782
|
+
if (st.code === "auth") return { kind: "auth", repo, localPresent: local.present };
|
|
30783
|
+
if (st.code === "config") return { kind: "config", repo, detail: st.error, localPresent: local.present };
|
|
30784
|
+
return { kind: "network", repo, detail: st.error, localPresent: local.present };
|
|
30785
|
+
}
|
|
30786
|
+
const present = st.present;
|
|
30787
|
+
const builtAt = typeof st.builtAt === "string" ? st.builtAt : void 0;
|
|
30788
|
+
const fileCount = typeof st.fileCount === "number" ? st.fileCount : void 0;
|
|
30789
|
+
const embCount = typeof st.embCount === "number" ? st.embCount : void 0;
|
|
30790
|
+
if (present === false) {
|
|
30791
|
+
return { kind: "missing-projection", repo, localPresent: local.present, fileCount: 0, embCount: 0 };
|
|
30792
|
+
}
|
|
30793
|
+
if (builtAt) {
|
|
30794
|
+
const ageMs = Date.now() - Date.parse(builtAt);
|
|
30795
|
+
if (Number.isFinite(ageMs) && ageMs > 48 * 60 * 60 * 1e3) {
|
|
30796
|
+
return {
|
|
30797
|
+
kind: "stale",
|
|
30798
|
+
repo,
|
|
30799
|
+
builtAt,
|
|
30800
|
+
fileCount,
|
|
30801
|
+
embCount,
|
|
30802
|
+
localPresent: local.present,
|
|
30803
|
+
detail: `projection builtAt ${builtAt} (>48h old)`
|
|
30804
|
+
};
|
|
30805
|
+
}
|
|
30806
|
+
}
|
|
30807
|
+
return {
|
|
30808
|
+
kind: "healthy",
|
|
30809
|
+
repo: typeof st.repo === "string" ? st.repo : repo,
|
|
30810
|
+
builtAt,
|
|
30811
|
+
fileCount,
|
|
30812
|
+
embCount,
|
|
30813
|
+
localPresent: local.present
|
|
30814
|
+
};
|
|
30815
|
+
} catch (e) {
|
|
30816
|
+
return {
|
|
30817
|
+
kind: "network",
|
|
30818
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
30819
|
+
localPresent: local.present
|
|
30820
|
+
};
|
|
30821
|
+
}
|
|
29926
30822
|
}
|
|
29927
30823
|
};
|
|
29928
30824
|
}
|
|
@@ -29970,7 +30866,7 @@ function argvWantsJson2() {
|
|
|
29970
30866
|
var unknownFlagJsonHandled = false;
|
|
29971
30867
|
var PARSE_HINT_SENTINEL = "@@mmi-parse-hint@@";
|
|
29972
30868
|
var DISCOVERY_HINT = "run `mmi-cli commands` for the agentic-coding map, `mmi-cli explain <command>` for detail, or `mmi-cli commands --json --primary` for machine discovery";
|
|
29973
|
-
var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, your installed mmi-cli may be stale
|
|
30869
|
+
var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, your installed mmi-cli may be stale ? update it (see: mmi-cli doctor)`;
|
|
29974
30870
|
var lastParseErrorKind = "other";
|
|
29975
30871
|
var lastUnknownCommand;
|
|
29976
30872
|
function classifyParseError(plain) {
|
|
@@ -30036,7 +30932,7 @@ function resolveParseHint() {
|
|
|
30036
30932
|
const path2 = commandPath2(cmd);
|
|
30037
30933
|
return [
|
|
30038
30934
|
`Usage: mmi-cli ${path2} ${cmd.usage()}`,
|
|
30039
|
-
"This command exists and your mmi-cli is not the problem
|
|
30935
|
+
"This command exists and your mmi-cli is not the problem ? the ARGUMENTS did not parse.",
|
|
30040
30936
|
`Run \`mmi-cli ${path2} --help\` for its signature, \`mmi-cli explain ${path2}\` for detail, or \`mmi-cli commands\` for the full map.`
|
|
30041
30937
|
].join("\n");
|
|
30042
30938
|
}
|
|
@@ -30097,7 +30993,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
30097
30993
|
process.stderr.write(str);
|
|
30098
30994
|
}
|
|
30099
30995
|
var program2 = new Command();
|
|
30100
|
-
program2.name("mmi-cli").description("MMI Future Hub CLI
|
|
30996
|
+
program2.name("mmi-cli").description("MMI Future Hub CLI ? the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError(PARSE_HINT_SENTINEL);
|
|
30101
30997
|
function appActorDeps() {
|
|
30102
30998
|
return {
|
|
30103
30999
|
fetchSecret: async (key) => fetchSecretValue(makeSecretsDeps(await loadConfig()), key, { repo: APP_VAULT_REPO }),
|
|
@@ -30129,19 +31025,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
30129
31025
|
});
|
|
30130
31026
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
30131
31027
|
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) => {
|
|
30132
|
-
const path2 = (0,
|
|
30133
|
-
const current = (0,
|
|
31028
|
+
const path2 = (0, import_node_path38.join)(process.cwd(), ".gitignore");
|
|
31029
|
+
const current = (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null;
|
|
30134
31030
|
const plan = planManagedGitignore(current);
|
|
30135
31031
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
30136
31032
|
if (opts.json) {
|
|
30137
|
-
if (opts.write && plan.changed) (0,
|
|
31033
|
+
if (opts.write && plan.changed) (0, import_node_fs39.writeFileSync)(path2, plan.content, "utf8");
|
|
30138
31034
|
console.log(JSON.stringify(plan, null, 2));
|
|
30139
31035
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
30140
31036
|
return;
|
|
30141
31037
|
}
|
|
30142
31038
|
if (opts.write) {
|
|
30143
31039
|
if (plan.changed) {
|
|
30144
|
-
(0,
|
|
31040
|
+
(0, import_node_fs39.writeFileSync)(path2, plan.content, "utf8");
|
|
30145
31041
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
30146
31042
|
} else {
|
|
30147
31043
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -30149,7 +31045,7 @@ rules.command("gitignore").option("--write", "upsert the managed block into .git
|
|
|
30149
31045
|
return;
|
|
30150
31046
|
}
|
|
30151
31047
|
if (plan.changed) {
|
|
30152
|
-
console.error(`mmi-cli org rules gitignore: managed block drift (${drift})
|
|
31048
|
+
console.error(`mmi-cli org rules gitignore: managed block drift (${drift}) ? run \`mmi-cli org rules gitignore --write\` and commit`);
|
|
30153
31049
|
process.exitCode = 1;
|
|
30154
31050
|
} else {
|
|
30155
31051
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -30255,7 +31151,7 @@ wave.command("land").description("serial merge open PRs to development with reba
|
|
|
30255
31151
|
});
|
|
30256
31152
|
var gcCmd = program2.command("gc").description("dry-run cleanup for merged/closed PR local+remote branches, linked worktrees, stale tracking refs, and worktree metadata (--scratch: safe local scratch + confirmed-synced plans)");
|
|
30257
31153
|
var DEFERRED_SWEEP_HARD_TIMEOUT_MS = 12e4;
|
|
30258
|
-
gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree removals with backoff (#1932)
|
|
31154
|
+
gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree removals with backoff (#1932) ? SessionStart + pr land spawn this detached").option("--quiet", "silent when registry empty or all cleared").option("--json", "machine-readable output").action(async (o) => {
|
|
30259
31155
|
await runWithSweepWatchdog(async () => {
|
|
30260
31156
|
try {
|
|
30261
31157
|
const deferredStore = await createDeferredWorktreeStore();
|
|
@@ -30263,7 +31159,7 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
30263
31159
|
const result = await sweepDeferredWorktreesWithRetry(
|
|
30264
31160
|
deferredStore,
|
|
30265
31161
|
// #2841: `-c core.fsmonitor=false` stops a git fsmonitor daemon from inheriting this detached
|
|
30266
|
-
// worker's stdio pipe (the likely Windows hang
|
|
31162
|
+
// worker's stdio pipe (the likely Windows hang ? execFile then never sees EOF and never resolves).
|
|
30267
31163
|
worktreeRemoveDeps(async (args) => (await execFileP2("git", ["-c", "core.fsmonitor=false", ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
|
|
30268
31164
|
{ removalContext }
|
|
30269
31165
|
);
|
|
@@ -30278,11 +31174,11 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
30278
31174
|
fail(`worktree gc sweep-deferred: ${e.message}`);
|
|
30279
31175
|
}
|
|
30280
31176
|
}, DEFERRED_SWEEP_HARD_TIMEOUT_MS, () => {
|
|
30281
|
-
console.error("worktree gc sweep-deferred: exceeded hard timeout
|
|
31177
|
+
console.error("worktree gc sweep-deferred: exceeded hard timeout ? exiting so the detached worker cannot leak (#2841)");
|
|
30282
31178
|
process.exit(process.exitCode ?? 0);
|
|
30283
31179
|
});
|
|
30284
31180
|
});
|
|
30285
|
-
gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").option("--force", "remove worktrees even when the ownership registry shows another session created or worked in them recently (#3580)").option("--root <path>", "sweep an explicitly named worktrees root instead of the authoritative ../mmi-worktrees (#3471)
|
|
31181
|
+
gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").option("--force", "remove worktrees even when the ownership registry shows another session created or worked in them recently (#3580)").option("--root <path>", "sweep an explicitly named worktrees root instead of the authoritative ../mmi-worktrees (#3471) ? descends into this repo container when present; ownership, dead-dir, and content guards still apply").action(async (o) => {
|
|
30286
31182
|
if (o.apply && o.dryRun) return fail("worktree gc: choose either --dry-run or --apply");
|
|
30287
31183
|
if (o.scratch) {
|
|
30288
31184
|
try {
|
|
@@ -30298,11 +31194,11 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
30298
31194
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
30299
31195
|
let root;
|
|
30300
31196
|
if (o.root !== void 0) {
|
|
30301
|
-
root = (0,
|
|
30302
|
-
if (!(0,
|
|
31197
|
+
root = (0, import_node_path38.resolve)(o.root);
|
|
31198
|
+
if (!(0, import_node_fs39.existsSync)(root) || !(0, import_node_fs39.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
30303
31199
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
30304
31200
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
30305
|
-
return fail(`worktree gc: --root ${root} contains this checkout
|
|
31201
|
+
return fail(`worktree gc: --root ${root} contains this checkout ? name a worktrees root, not the repo or an ancestor of it`);
|
|
30306
31202
|
}
|
|
30307
31203
|
}
|
|
30308
31204
|
try {
|
|
@@ -30316,7 +31212,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
30316
31212
|
const removalContext = await currentWorktreeRemovalContext("worktree gc", o.force);
|
|
30317
31213
|
await sweepDeferredWorktrees(
|
|
30318
31214
|
deferredStore,
|
|
30319
|
-
// #2841: same `-c core.fsmonitor=false` guard as the detached `gc sweep-deferred` worker
|
|
31215
|
+
// #2841: same `-c core.fsmonitor=false` guard as the detached `gc sweep-deferred` worker ? keep a
|
|
30320
31216
|
// git fsmonitor daemon from inheriting this sweep's stdio pipe and wedging the git call on Windows.
|
|
30321
31217
|
worktreeRemoveDeps(async (args) => (await execFileP2("git", ["-c", "core.fsmonitor=false", ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
|
|
30322
31218
|
removalContext
|
|
@@ -30341,11 +31237,11 @@ var WORKTREE_SETUP_LOCK_TTL_MS = 10 * 6e4;
|
|
|
30341
31237
|
function runWorktreeInstall(command, cwd, quiet, opts) {
|
|
30342
31238
|
const stdio = quiet ? "ignore" : "inherit";
|
|
30343
31239
|
return new Promise((resolve5, reject) => {
|
|
30344
|
-
const child2 = opts?.shell ? (0,
|
|
31240
|
+
const child2 = opts?.shell ? (0, import_node_child_process18.spawn)(command, { cwd, stdio, windowsHide: true, shell: true }) : (() => {
|
|
30345
31241
|
const [bin, ...args] = command.split(" ");
|
|
30346
31242
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
30347
31243
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
30348
|
-
return (0,
|
|
31244
|
+
return (0, import_node_child_process18.spawn)(file, spawnArgs, { cwd, stdio, windowsHide: true });
|
|
30349
31245
|
})();
|
|
30350
31246
|
const timer = setTimeout(() => {
|
|
30351
31247
|
try {
|
|
@@ -30378,14 +31274,14 @@ async function currentWorktreeRemovalContext(command, force) {
|
|
|
30378
31274
|
};
|
|
30379
31275
|
}
|
|
30380
31276
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
30381
|
-
if (!(0,
|
|
31277
|
+
if (!(0, import_node_fs39.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
30382
31278
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
30383
31279
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
30384
31280
|
if (!registered.length) {
|
|
30385
31281
|
return `'git worktree list' could not be read from ${repoRoot2}, so ${wtPath} could not be proven registered`;
|
|
30386
31282
|
}
|
|
30387
31283
|
if (!registered.some((w) => samePath(w.path, wtPath))) {
|
|
30388
|
-
return `${wtPath} exists on disk but 'git worktree list' does not name it
|
|
31284
|
+
return `${wtPath} exists on disk but 'git worktree list' does not name it ? its git metadata is gone, so it is not a usable worktree`;
|
|
30389
31285
|
}
|
|
30390
31286
|
return void 0;
|
|
30391
31287
|
}
|
|
@@ -30400,26 +31296,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
30400
31296
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
30401
31297
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
30402
31298
|
const take = () => {
|
|
30403
|
-
const fd = (0,
|
|
31299
|
+
const fd = (0, import_node_fs39.openSync)(lockPath, "wx");
|
|
30404
31300
|
try {
|
|
30405
|
-
(0,
|
|
31301
|
+
(0, import_node_fs39.writeSync)(fd, String(Date.now()));
|
|
30406
31302
|
} finally {
|
|
30407
|
-
(0,
|
|
31303
|
+
(0, import_node_fs39.closeSync)(fd);
|
|
30408
31304
|
}
|
|
30409
31305
|
return () => {
|
|
30410
31306
|
try {
|
|
30411
|
-
(0,
|
|
31307
|
+
(0, import_node_fs39.rmSync)(lockPath, { force: true });
|
|
30412
31308
|
} catch {
|
|
30413
31309
|
}
|
|
30414
31310
|
};
|
|
30415
31311
|
};
|
|
30416
31312
|
try {
|
|
30417
|
-
(0,
|
|
31313
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(lockPath), { recursive: true });
|
|
30418
31314
|
return take();
|
|
30419
31315
|
} catch {
|
|
30420
31316
|
try {
|
|
30421
|
-
if (Date.now() - (0,
|
|
30422
|
-
(0,
|
|
31317
|
+
if (Date.now() - (0, import_node_fs39.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
31318
|
+
(0, import_node_fs39.rmSync)(lockPath, { force: true });
|
|
30423
31319
|
return take();
|
|
30424
31320
|
}
|
|
30425
31321
|
} catch {
|
|
@@ -30427,15 +31323,15 @@ function acquireWorktreeSetupLock(worktreeRoot) {
|
|
|
30427
31323
|
return null;
|
|
30428
31324
|
}
|
|
30429
31325
|
}
|
|
30430
|
-
var worktree = program2.command("worktree").description("self-provisioning worktrees
|
|
31326
|
+
var worktree = program2.command("worktree").description("self-provisioning worktrees ? install deps + copy local-only config");
|
|
30431
31327
|
withExamples(mutating(
|
|
30432
|
-
worktree.command("create <branch-or-issue>").description("create a worktree from a base ref and provision it (install deps + copy local-only config); an issue ref derives the <issue>-<slug> branch, and --claim claims its board item (#2996, formerly worktree new)").option("--from <ref>", "base ref to branch from", "origin/development").option("--base <ref>", "alias of --from (git's own word for the base ref)").option("--path <path>", "worktree path (default: ../mmi-worktrees/<RepoName>/<branch>
|
|
31328
|
+
worktree.command("create <branch-or-issue>").description("create a worktree from a base ref and provision it (install deps + copy local-only config); an issue ref derives the <issue>-<slug> branch, and --claim claims its board item (#2996, formerly worktree new)").option("--from <ref>", "base ref to branch from", "origin/development").option("--base <ref>", "alias of --from (git's own word for the base ref)").option("--path <path>", "worktree path (default: ../mmi-worktrees/<RepoName>/<branch> ? authoritative; the <RepoName> segment is what makes ownership provable from the path, #3471. gc/list read this root in BOTH the nested and the legacy flat shape)").option("--remote <name>", "remote to fetch when --from is a <remote>/<branch> ref", "origin").option("--slug <slug>", "issue-ref form: override the slug derived from the issue title").option("--claim", "issue-ref form: claim the issue's board item after provisioning").option("--for <login>", "with --claim: assign the board item to this login instead of @me"),
|
|
30433
31329
|
worktreeCreatePlan
|
|
30434
31330
|
).action(async (target, o, cmd) => {
|
|
30435
31331
|
let step = "resolve the branch name";
|
|
30436
31332
|
try {
|
|
30437
31333
|
if (o.base !== void 0 && cmd.getOptionValueSource("from") === "cli" && o.base !== o.from) {
|
|
30438
|
-
return fail(`worktree create: --from and --base are the same option (--base is an alias); got --from ${o.from} and --base ${o.base}
|
|
31334
|
+
return fail(`worktree create: --from and --base are the same option (--base is an alias); got --from ${o.from} and --base ${o.base} ? pass one`);
|
|
30439
31335
|
}
|
|
30440
31336
|
const fromRef = o.base ?? o.from;
|
|
30441
31337
|
const issueForm = isIssueRef(target);
|
|
@@ -30448,7 +31344,7 @@ withExamples(mutating(
|
|
|
30448
31344
|
const slug = o.slug ?? await fetchIssueTitle(selector.repo, selector.number).then((t) => t ? slugifyIssueTitle(t) : "");
|
|
30449
31345
|
branch = buildNewBranchName(selector.number, slug ?? "");
|
|
30450
31346
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
30451
|
-
return fail(`worktree create: generated branch name '${branch}' collides with a protected train branch
|
|
31347
|
+
return fail(`worktree create: generated branch name '${branch}' collides with a protected train branch ? pass a branch name instead`);
|
|
30452
31348
|
}
|
|
30453
31349
|
} else if (o.claim || o.for) {
|
|
30454
31350
|
return fail("worktree create: --claim/--for need an issue-ref argument (e.g. worktree create 2687 --claim)");
|
|
@@ -30472,7 +31368,7 @@ withExamples(mutating(
|
|
|
30472
31368
|
if (preferRemote && await revParseRef(preferRemote)) base = preferRemote;
|
|
30473
31369
|
if (!o.json) {
|
|
30474
31370
|
const baseSha = (await revParseRef(base))?.slice(0, 12) ?? "unresolved";
|
|
30475
|
-
const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote}
|
|
31371
|
+
const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} ? local ref)` : "";
|
|
30476
31372
|
console.error(` base ${base} ${baseSha}${localOnly}`);
|
|
30477
31373
|
}
|
|
30478
31374
|
const registered = parseWorktreePorcelainEntries((await execFileP2(
|
|
@@ -30488,7 +31384,7 @@ withExamples(mutating(
|
|
|
30488
31384
|
return fail(`worktree create: ${wtPath} is already registered for '${exact.branch ?? "detached HEAD"}', not '${branch}'`);
|
|
30489
31385
|
}
|
|
30490
31386
|
const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
30491
|
-
if (status) return fail(`worktree create: refusing to resume ${wtPath}
|
|
31387
|
+
if (status) return fail(`worktree create: refusing to resume ${wtPath} ? it has uncommitted changes`);
|
|
30492
31388
|
const head = await revParseRef(`refs/heads/${branch}`);
|
|
30493
31389
|
const baseOid = await revParseRef(base);
|
|
30494
31390
|
if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
|
|
@@ -30498,7 +31394,7 @@ withExamples(mutating(
|
|
|
30498
31394
|
{ timeout: GIT_TIMEOUT_MS }
|
|
30499
31395
|
).then(() => true).catch(() => false);
|
|
30500
31396
|
if (!canFastForward) {
|
|
30501
|
-
return fail(`worktree create: refusing to resume '${branch}'
|
|
31397
|
+
return fail(`worktree create: refusing to resume '${branch}' ? it has local commits or diverges from ${base}`);
|
|
30502
31398
|
}
|
|
30503
31399
|
await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
|
|
30504
31400
|
resumed = true;
|
|
@@ -30571,11 +31467,11 @@ withExamples(mutating(
|
|
|
30571
31467
|
console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
|
|
30572
31468
|
console.log(` copied: ${report.copied.join(", ") || "none"}`);
|
|
30573
31469
|
if (issueForm && o.claim && selector) {
|
|
30574
|
-
if (claimError) console.error(` warning: board claim failed (${claimError})
|
|
31470
|
+
if (claimError) console.error(` warning: board claim failed (${claimError}) ? worktree is ready, finish the claim manually`);
|
|
30575
31471
|
else console.log(` board item ${selector.repo}#${selector.number} claimed`);
|
|
30576
31472
|
}
|
|
30577
31473
|
} catch (e) {
|
|
30578
|
-
fail(`worktree create: failed at step '${step}'
|
|
31474
|
+
fail(`worktree create: failed at step '${step}' ? ${e.message}`);
|
|
30579
31475
|
}
|
|
30580
31476
|
}), [
|
|
30581
31477
|
"mmi-cli worktree create fix/gc-sweep-2687",
|
|
@@ -30586,7 +31482,7 @@ worktree.command("setup [path]").description("provision an existing worktree (in
|
|
|
30586
31482
|
const root = path2 ?? process.cwd();
|
|
30587
31483
|
const release = acquireWorktreeSetupLock(root);
|
|
30588
31484
|
if (!release) {
|
|
30589
|
-
if (!o.quiet && !o.json) console.log("worktree setup: another provision is in progress
|
|
31485
|
+
if (!o.quiet && !o.json) console.log("worktree setup: another provision is in progress ? skipping");
|
|
30590
31486
|
return;
|
|
30591
31487
|
}
|
|
30592
31488
|
try {
|
|
@@ -30606,7 +31502,7 @@ worktree.command("setup [path]").description("provision an existing worktree (in
|
|
|
30606
31502
|
release();
|
|
30607
31503
|
}
|
|
30608
31504
|
});
|
|
30609
|
-
worktree.command("events").description("who created and who removed this repo's worktrees
|
|
31505
|
+
worktree.command("events").description("who created and who removed this repo's worktrees ? the append-only attribution log (#3580)").option("--limit <n>", "most recent events to show", "50").option("--json", "machine-readable output").action(async (o) => {
|
|
30610
31506
|
const limit = Number.parseInt(o.limit, 10);
|
|
30611
31507
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree events: --limit must be a positive integer");
|
|
30612
31508
|
const primaryRoot = await primaryCheckoutRoot(process.cwd()) ?? process.cwd();
|
|
@@ -30624,11 +31520,11 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
30624
31520
|
try {
|
|
30625
31521
|
cfg = await loadConfigForRepo(targetRepo2);
|
|
30626
31522
|
} catch (e) {
|
|
30627
|
-
console.error(`issue create: board attach skipped
|
|
31523
|
+
console.error(`issue create: board attach skipped ? ${e.message}`);
|
|
30628
31524
|
return { onBoard: false };
|
|
30629
31525
|
}
|
|
30630
31526
|
if (!cfg.projectId) {
|
|
30631
|
-
console.error(`issue create: board attach skipped
|
|
31527
|
+
console.error(`issue create: board attach skipped ? no Hub registry board META for ${targetRepo2 ?? "current repo"}; run \`mmi-cli org project get ${targetRepo2 ?? "<owner/repo>"}\` and backfill board coords`);
|
|
30632
31528
|
return { onBoard: false };
|
|
30633
31529
|
}
|
|
30634
31530
|
try {
|
|
@@ -30674,7 +31570,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
30674
31570
|
try {
|
|
30675
31571
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
30676
31572
|
if (o.repo) args.push("--repo", o.repo);
|
|
30677
|
-
spawnDetachedSelf(args, { spawn:
|
|
31573
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process18.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
30678
31574
|
} catch {
|
|
30679
31575
|
}
|
|
30680
31576
|
}
|
|
@@ -30683,14 +31579,236 @@ registerEdgeCommands(program2);
|
|
|
30683
31579
|
registerBoxCommands(program2);
|
|
30684
31580
|
registerSchedulesCommands(program2);
|
|
30685
31581
|
registerSchedulesLiftCommand(program2);
|
|
30686
|
-
var
|
|
30687
|
-
|
|
31582
|
+
var repoIndex = program2.command("repo-index").description("Hub cloud + local pointer index ? paths/symbols only, never wiki prose (Hub#4133)");
|
|
31583
|
+
repoIndex.command("rebuild").description("walk the checkout (git ls-files + gitignore/secrets walls), rebuild the local pointer index, drop orphans").option("--json", "machine-readable projection summary").action(async (o) => {
|
|
31584
|
+
try {
|
|
31585
|
+
const root = await repoRoot();
|
|
31586
|
+
const repo = inferRepoSlug(root);
|
|
31587
|
+
const built = rebuildRepoIndex(root, repo);
|
|
31588
|
+
const symbolCount = built.entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
31589
|
+
if (o.json) {
|
|
31590
|
+
consoleIo.log(JSON.stringify({
|
|
31591
|
+
ok: true,
|
|
31592
|
+
repo: built.repo,
|
|
31593
|
+
builtAt: built.builtAt,
|
|
31594
|
+
fileCount: built.entries.length,
|
|
31595
|
+
symbolCount,
|
|
31596
|
+
store: repoIndexStatus(root).path
|
|
31597
|
+
}, null, 2));
|
|
31598
|
+
return;
|
|
31599
|
+
}
|
|
31600
|
+
console.log(`repo-index: rebuilt ${built.repo} ? ${built.entries.length} files, ${symbolCount} symbols`);
|
|
31601
|
+
} catch (e) {
|
|
31602
|
+
await failGraceful(e.message);
|
|
31603
|
+
}
|
|
31604
|
+
});
|
|
31605
|
+
repoIndex.command("publish").description("publish this checkout's projection to Hub cloud (rebuilds locally first; estate indexer also runs on a schedule)").option("--embed", "request Titan file embeddings on the Hub (capped per publish)").option("--json", "machine-readable result").action(async (o) => {
|
|
31606
|
+
try {
|
|
31607
|
+
const root = await repoRoot();
|
|
31608
|
+
const repo = inferRepoSlug(root);
|
|
31609
|
+
const built = rebuildRepoIndex(root, repo);
|
|
31610
|
+
const cfg = await loadConfig();
|
|
31611
|
+
const res = await publishRepoIndexCloud(
|
|
31612
|
+
{ repo, builtAt: built.builtAt, entries: built.entries, embed: o.embed === true },
|
|
31613
|
+
registryClientDeps(cfg)
|
|
31614
|
+
);
|
|
31615
|
+
if (!res.ok) return await failGraceful(res.error);
|
|
31616
|
+
if (o.json) {
|
|
31617
|
+
consoleIo.log(JSON.stringify(res.body, null, 2));
|
|
31618
|
+
return;
|
|
31619
|
+
}
|
|
31620
|
+
console.log(`repo-index: published ${res.body.repo} ? ${res.body.fileCount} files, emb=${res.body.embCount ?? 0}`);
|
|
31621
|
+
} catch (e) {
|
|
31622
|
+
return await failGraceful(e.message);
|
|
31623
|
+
}
|
|
31624
|
+
});
|
|
31625
|
+
repoIndex.command("search").description("search Hub cloud by default (lexical/hybrid/semantic); use --local for checkout-only").argument("<query>", "path fragment, symbol, or meaning phrase").option("--limit <n>", "max hits", "20").option("--json", "machine-readable hits").option("--local", "search only the local projection (rebuild if missing)").option("--cloud", "force Hub cloud search (default when not --local)").option("--semantic", "semantic mode (Titan cosine over path+symbols embeddings)").option("--lexical", "lexical-only mode (paths/symbols)").option("--repo <owner/name>", "limit cloud search to one repo").action(async (query, o) => {
|
|
31626
|
+
try {
|
|
31627
|
+
const limit = Math.max(1, Math.min(100, Number(o.limit ?? 20) || 20));
|
|
31628
|
+
const useLocal = o.local === true && o.cloud !== true;
|
|
31629
|
+
if (useLocal) {
|
|
31630
|
+
const root = await repoRoot();
|
|
31631
|
+
let idx = loadRepoIndex(root);
|
|
31632
|
+
if (!idx) idx = rebuildRepoIndex(root, inferRepoSlug(root));
|
|
31633
|
+
const hits = searchRepoIndex(idx, query, limit);
|
|
31634
|
+
if (o.json) {
|
|
31635
|
+
consoleIo.log(JSON.stringify({ ok: true, source: "local", repo: idx.repo, query, hits }, null, 2));
|
|
31636
|
+
return;
|
|
31637
|
+
}
|
|
31638
|
+
if (hits.length === 0) {
|
|
31639
|
+
console.log(`repo-index: no local hits for ${JSON.stringify(query)}`);
|
|
31640
|
+
return;
|
|
31641
|
+
}
|
|
31642
|
+
for (const h of hits) {
|
|
31643
|
+
const sym = h.symbol ? ` ${h.symbol}` : "";
|
|
31644
|
+
console.log(`${h.score.toFixed(2)} ${h.kind.padEnd(8)} ${h.path}${sym} (${h.why})`);
|
|
31645
|
+
}
|
|
31646
|
+
return;
|
|
31647
|
+
}
|
|
31648
|
+
const mode = o.semantic ? "semantic" : o.lexical ? "lexical" : "hybrid";
|
|
31649
|
+
const cfg = await loadConfig();
|
|
31650
|
+
const res = await searchRepoIndexCloud(query, { mode, limit, repo: o.repo }, registryClientDeps(cfg));
|
|
31651
|
+
if (!res.ok) return await failGraceful(res.error);
|
|
31652
|
+
if (o.json) {
|
|
31653
|
+
consoleIo.log(JSON.stringify({ source: "cloud", ...res }, null, 2));
|
|
31654
|
+
return;
|
|
31655
|
+
}
|
|
31656
|
+
if (res.hits.length === 0) {
|
|
31657
|
+
console.log(`repo-index: no cloud hits for ${JSON.stringify(query)} (${res.mode})`);
|
|
31658
|
+
return;
|
|
31659
|
+
}
|
|
31660
|
+
for (const h of res.hits) {
|
|
31661
|
+
const sym = h.symbol ? ` ${h.symbol}` : "";
|
|
31662
|
+
console.log(`${h.score.toFixed(2)} ${h.kind.padEnd(8)} ${h.repo} ${h.path}${sym} (${h.why})`);
|
|
31663
|
+
}
|
|
31664
|
+
} catch (e) {
|
|
31665
|
+
return await failGraceful(e.message);
|
|
31666
|
+
}
|
|
31667
|
+
});
|
|
31668
|
+
repoIndex.command("status").description("show local and/or cloud repo-index status").option("--json", "machine-readable status").option("--cloud", "query Hub cloud status (estate or --repo)").option("--repo <owner/name>", "cloud status for one repo").action(async (o) => {
|
|
31669
|
+
try {
|
|
31670
|
+
if (o.cloud || o.repo) {
|
|
31671
|
+
const cfg = await loadConfig();
|
|
31672
|
+
const st2 = await statusRepoIndexCloud(o.repo, registryClientDeps(cfg));
|
|
31673
|
+
if (isRepoIndexStatusError(st2)) {
|
|
31674
|
+
if (o.json) {
|
|
31675
|
+
consoleIo.log(JSON.stringify(st2, null, 2));
|
|
31676
|
+
return await failGraceful(st2.error);
|
|
31677
|
+
}
|
|
31678
|
+
return await failGraceful(explainRepoIndexStatusError(st2));
|
|
31679
|
+
}
|
|
31680
|
+
if (o.json) {
|
|
31681
|
+
consoleIo.log(JSON.stringify(st2, null, 2));
|
|
31682
|
+
return;
|
|
31683
|
+
}
|
|
31684
|
+
if (st2.present === true && typeof st2.repo === "string") {
|
|
31685
|
+
const fileCount = Number(st2.fileCount ?? 0);
|
|
31686
|
+
const embCount = Number(st2.embCount ?? 0);
|
|
31687
|
+
const embGap = Number(st2.embGap ?? Math.max(0, fileCount - embCount));
|
|
31688
|
+
const stale = st2.staleBuiltAt === true ? " stale-builtAt" : "";
|
|
31689
|
+
console.log(
|
|
31690
|
+
`repo-index: cloud ${st2.repo} built ${st2.builtAt} ? ${fileCount} files, emb=${embCount}/${fileCount} gap=${embGap}${stale}`
|
|
31691
|
+
);
|
|
31692
|
+
return;
|
|
31693
|
+
}
|
|
31694
|
+
consoleIo.log(JSON.stringify(st2, null, 2));
|
|
31695
|
+
const count = typeof st2.count === "number" ? st2.count : void 0;
|
|
31696
|
+
const present = st2.present;
|
|
31697
|
+
if (count === 0 || present === false) {
|
|
31698
|
+
console.error(
|
|
31699
|
+
"repo-index: no cloud projection yet ? after Hub deploy, run harbour `repo-index-reconcile` or `mmi-cli repo-index sync-estate` (see docs/Guides/repo-index-runbook.md)."
|
|
31700
|
+
);
|
|
31701
|
+
}
|
|
31702
|
+
return;
|
|
31703
|
+
}
|
|
31704
|
+
const root = await repoRoot();
|
|
31705
|
+
const st = repoIndexStatus(root);
|
|
31706
|
+
if (o.json) {
|
|
31707
|
+
consoleIo.log(JSON.stringify(st, null, 2));
|
|
31708
|
+
return;
|
|
31709
|
+
}
|
|
31710
|
+
if (!st.present) {
|
|
31711
|
+
console.log(`repo-index: local missing ? cloud search needs no rebuild; optional \`repo-index rebuild\` for --local`);
|
|
31712
|
+
return;
|
|
31713
|
+
}
|
|
31714
|
+
console.log(`repo-index: local ${st.repo} built ${st.builtAt} ? ${st.fileCount} files, ${st.symbolCount} symbols`);
|
|
31715
|
+
} catch (e) {
|
|
31716
|
+
return await failGraceful(e.message);
|
|
31717
|
+
}
|
|
31718
|
+
});
|
|
31719
|
+
repoIndex.command("health").description("post-deploy health gate: status + golden lexical/semantic queries (Hub#4149)").option("--live", "hit Hub cloud (status + searches); omit for offline golden-shape check").option("--golden <path>", "golden queries JSON (default: cli/testdata/repo-index-golden-queries.json)").option("--json", "machine-readable findings").action(async (o) => {
|
|
31720
|
+
try {
|
|
31721
|
+
const root = await repoRoot();
|
|
31722
|
+
const goldenPath = o.golden || defaultGoldenPath(root);
|
|
31723
|
+
const golden = loadGoldenSuite(goldenPath);
|
|
31724
|
+
const result = o.live ? await runRepoIndexHealth({
|
|
31725
|
+
golden,
|
|
31726
|
+
live: {
|
|
31727
|
+
status: async () => {
|
|
31728
|
+
const cfg = await loadConfig();
|
|
31729
|
+
const st = await statusRepoIndexCloud(void 0, registryClientDeps(cfg));
|
|
31730
|
+
if ("error" in st && st.ok === false) return { ok: false, error: st.error };
|
|
31731
|
+
return st;
|
|
31732
|
+
},
|
|
31733
|
+
search: async (q, mode) => {
|
|
31734
|
+
const cfg = await loadConfig();
|
|
31735
|
+
const res = await searchRepoIndexCloud(q, { mode, limit: 10 }, registryClientDeps(cfg));
|
|
31736
|
+
if (!res.ok) {
|
|
31737
|
+
return {
|
|
31738
|
+
ok: false,
|
|
31739
|
+
error: res.error,
|
|
31740
|
+
status: res.status
|
|
31741
|
+
};
|
|
31742
|
+
}
|
|
31743
|
+
return { ok: true, hits: res.hits };
|
|
31744
|
+
}
|
|
31745
|
+
}
|
|
31746
|
+
}) : await runRepoIndexHealth({ golden });
|
|
31747
|
+
if (o.json) {
|
|
31748
|
+
consoleIo.log(JSON.stringify({ ok: result.ok, findings: result.findings, golden: goldenPath }, null, 2));
|
|
31749
|
+
} else {
|
|
31750
|
+
for (const f of result.findings) {
|
|
31751
|
+
console.log(`${f.ok ? "ok" : "FAIL"} ${f.code} ${f.detail}`);
|
|
31752
|
+
}
|
|
31753
|
+
console.log(result.ok ? "repo-index health: PASS" : "repo-index health: FAIL");
|
|
31754
|
+
}
|
|
31755
|
+
if (!result.ok) process.exitCode = 1;
|
|
31756
|
+
} catch (e) {
|
|
31757
|
+
return await failGraceful(e.message);
|
|
31758
|
+
}
|
|
31759
|
+
});
|
|
31760
|
+
repoIndex.command("gc").description("remove cloud projections for repos no longer on the registry roster").option("--cloud", "required ? GC only applies to Hub cloud").option("--json", "machine-readable result").action(async (o) => {
|
|
31761
|
+
try {
|
|
31762
|
+
if (!o.cloud) return await failGraceful("repo-index gc requires --cloud");
|
|
31763
|
+
const cfg = await loadConfig();
|
|
31764
|
+
const res = await gcRepoIndexCloud(registryClientDeps(cfg));
|
|
31765
|
+
if (!res.ok) return await failGraceful(res.error);
|
|
31766
|
+
if (o.json) {
|
|
31767
|
+
consoleIo.log(JSON.stringify(res, null, 2));
|
|
31768
|
+
return;
|
|
31769
|
+
}
|
|
31770
|
+
console.log(`repo-index: gc removed ${res.removed.length} orphan projection(s)`);
|
|
31771
|
+
} catch (e) {
|
|
31772
|
+
return await failGraceful(e.message);
|
|
31773
|
+
}
|
|
31774
|
+
});
|
|
31775
|
+
repoIndex.command("sync-estate").description("Hub indexer: shallow-clone registry repos, rebuild, publish to cloud (CI / operator)").option("--repo <owner/name>", "limit to one registered repo").option("--embed", "request Titan embeddings on publish").option("--json", "machine-readable result").action(async (o) => {
|
|
31776
|
+
try {
|
|
31777
|
+
const cfg = await loadConfig();
|
|
31778
|
+
const gh = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
|
|
31779
|
+
if (!gh) await failGraceful("sync-estate needs GH_TOKEN or GITHUB_TOKEN with contents:read on target repos");
|
|
31780
|
+
const res = await syncEstateRepoIndex({
|
|
31781
|
+
deps: registryClientDeps(cfg),
|
|
31782
|
+
repo: o.repo,
|
|
31783
|
+
embed: o.embed === true,
|
|
31784
|
+
githubToken: gh
|
|
31785
|
+
});
|
|
31786
|
+
if (o.json) {
|
|
31787
|
+
consoleIo.log(JSON.stringify(res, null, 2));
|
|
31788
|
+
if (!res.ok) process.exitCode = 1;
|
|
31789
|
+
return;
|
|
31790
|
+
}
|
|
31791
|
+
for (const p of res.published) {
|
|
31792
|
+
const gap = p.embGap ?? Math.max(0, p.fileCount - (p.embCount ?? 0));
|
|
31793
|
+
const trunc = p.embTruncated ? " truncated" : "";
|
|
31794
|
+
console.log(`repo-index: published ${p.repo} ? ${p.fileCount} files emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap}${trunc}`);
|
|
31795
|
+
}
|
|
31796
|
+
for (const f of res.failed) {
|
|
31797
|
+
console.error(`repo-index: FAILED ${f.repo}: ${f.error}`);
|
|
31798
|
+
}
|
|
31799
|
+
if (!res.ok) await failGraceful(`sync-estate incomplete (${res.failed.length} failed)`);
|
|
31800
|
+
} catch (e) {
|
|
31801
|
+
await failGraceful(e.message);
|
|
31802
|
+
}
|
|
31803
|
+
});
|
|
31804
|
+
var docs = program2.command("docs").description("generated docs surfaces ? the routing index (org knowledge layer)");
|
|
31805
|
+
docs.command("index").description("regenerate docs/index.md from the docs/ tree (--write, the default) or fail on drift (--check) ? the generated routing index, never hand-maintained").option("--check", "compare against the committed docs/index.md and exit 1 on drift; never write").option("--write", "regenerate docs/index.md when it has drifted (the default)").action(async (o) => {
|
|
30688
31806
|
try {
|
|
30689
31807
|
const root = await repoRoot();
|
|
30690
31808
|
const result = docsIndex(createDocsIndexDeps(root), { check: Boolean(o.check) });
|
|
30691
31809
|
if (o.check) {
|
|
30692
31810
|
if (result.drift) {
|
|
30693
|
-
return failGraceful(`docs index: ${DOCS_INDEX_PATH} is stale
|
|
31811
|
+
return failGraceful(`docs index: ${DOCS_INDEX_PATH} is stale ? run \`mmi-cli docs index --write\` and commit the result`);
|
|
30694
31812
|
}
|
|
30695
31813
|
console.log(`docs index: ${DOCS_INDEX_PATH} is current`);
|
|
30696
31814
|
return;
|
|
@@ -30700,7 +31818,7 @@ docs.command("index").description("regenerate docs/index.md from the docs/ tree
|
|
|
30700
31818
|
await failGraceful(e.message);
|
|
30701
31819
|
}
|
|
30702
31820
|
});
|
|
30703
|
-
docs.command("refs").description("deterministic doc reference gate: every backticked repo path, relative .md link, `mmi-cli` command ref, and `<!-- pinned by
|
|
31821
|
+
docs.command("refs").description("deterministic doc reference gate: every backticked repo path, relative .md link, `mmi-cli` command ref, and `<!-- pinned by ? -->` comment across docs/** + README.md + architecture.md must resolve, or exit 1 (fleet-portable port of the Hub gate scripts/check-doc-refs.mjs, #3339)").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").action(async (o) => {
|
|
30704
31822
|
try {
|
|
30705
31823
|
const root = await repoRoot();
|
|
30706
31824
|
const commandPaths = new Set(buildCommandManifest(program2).index.map((entry) => entry.path));
|
|
@@ -30724,7 +31842,7 @@ docs.command("refs").description("deterministic doc reference gate: every backti
|
|
|
30724
31842
|
await failGraceful(e.message);
|
|
30725
31843
|
}
|
|
30726
31844
|
});
|
|
30727
|
-
var spawnCmd = program2.command("spawn").description("this repo's process-spawn contract
|
|
31845
|
+
var spawnCmd = program2.command("spawn").description("this repo's process-spawn contract ? every child process must be unable to pop a console window");
|
|
30728
31846
|
spawnCmd.command("policy").description("enforce the windowsHide contract across this repo's tracked source: a child_process call must set windowsHide, or take its options from a named constant, a type annotation, or a forwarded caller bag that does. Waive a call the scan cannot classify with a `// windows-hide-exempt: <reason>` comment on the line above it (#3979)").option("--json", "machine-readable result: { ok, scannedCount, findings[] }").action(async (o) => {
|
|
30729
31847
|
try {
|
|
30730
31848
|
const root = await repoRoot();
|
|
@@ -30740,14 +31858,14 @@ spawnCmd.command("policy").description("enforce the windowsHide contract across
|
|
|
30740
31858
|
}
|
|
30741
31859
|
for (const f of result.findings) console.error(`spawn policy: ${f.detail}`);
|
|
30742
31860
|
console.error(
|
|
30743
|
-
"\nA process spawned without windowsHide allocates a console when it has none to inherit.\nWhere Windows Terminal is the default terminal, that console becomes a desktop window the\nhost never reaps
|
|
31861
|
+
"\nA process spawned without windowsHide allocates a console when it has none to inherit.\nWhere Windows Terminal is the default terminal, that console becomes a desktop window the\nhost never reaps ? it outlives the process as an empty frame. Set windowsHide: true, or\nroute the call through a helper that does."
|
|
30744
31862
|
);
|
|
30745
31863
|
process.exitCode = 1;
|
|
30746
31864
|
} catch (e) {
|
|
30747
31865
|
await failGraceful(e.message);
|
|
30748
31866
|
}
|
|
30749
31867
|
});
|
|
30750
|
-
var tests = program2.command("tests").description("a repo's test-policy.json
|
|
31868
|
+
var tests = program2.command("tests").description("a repo's test-policy.json ? the opt-in test contract and its enforcement");
|
|
30751
31869
|
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
|
|
30752
31870
|
try {
|
|
30753
31871
|
const root = await repoRoot();
|
|
@@ -30760,7 +31878,7 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
|
|
|
30760
31878
|
if (result.overriddenBy && result.ok) {
|
|
30761
31879
|
const { sha, reason, kinds } = result.overriddenBy;
|
|
30762
31880
|
const scope = (result.waived ?? []).map((f) => f.kind).join(", ") || "nothing";
|
|
30763
|
-
console.log(`tests policy: overridden by ${sha.slice(0, 8)} (waiving ${scope}; scope ${kinds.join(", ")})
|
|
31881
|
+
console.log(`tests policy: overridden by ${sha.slice(0, 8)} (waiving ${scope}; scope ${kinds.join(", ")}) ? ${reason}`);
|
|
30764
31882
|
return;
|
|
30765
31883
|
}
|
|
30766
31884
|
if (result.ok) {
|
|
@@ -30775,7 +31893,7 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
|
|
|
30775
31893
|
await failGraceful(e.message);
|
|
30776
31894
|
}
|
|
30777
31895
|
});
|
|
30778
|
-
var docsAudit = program2.command("docs-audit").description("the docs janitor verdict ledger
|
|
31896
|
+
var docsAudit = program2.command("docs-audit").description("the docs janitor verdict ledger ? record a dated run verdict, or read the dead-man status back");
|
|
30779
31897
|
docsAudit.command("record").description("write a dated janitor verdict for one repo to the registry ledger (master-only server-side)").option("--repo <owner/name>", "the repo the verdict is for (default: the current repo)").option("--date <YYYY-MM-DD>", "the ISO day the run examined (default: today)").requiredOption("--sha-range <a..b>", "the git range the janitor read (e.g. <lastVerdictSha>..HEAD)").addOption(new Option("--outcome <kind>", "clean | refreshed | failed").makeOptionMandatory().choices(["clean", "refreshed", "failed"])).option("--count <n>", "docs refreshed (required when --outcome refreshed)").option("--reason <text>", "why the run failed (required when --outcome failed)").requiredOption("--checker-vendor <vendor>", "which vendor's model actually ran the check").action(async (o) => {
|
|
30780
31898
|
try {
|
|
30781
31899
|
const repo = o.repo ?? await currentRepoFullName();
|
|
@@ -30791,7 +31909,7 @@ docsAudit.command("record").description("write a dated janitor verdict for one r
|
|
|
30791
31909
|
await failGraceful(e.message);
|
|
30792
31910
|
}
|
|
30793
31911
|
});
|
|
30794
|
-
docsAudit.command("status").description("read the janitor dead-man verdict back for one repo
|
|
31912
|
+
docsAudit.command("status").description("read the janitor dead-man verdict back for one repo ? missing/stale/failed is RED; a not-yet-armed registry route is an informational exit 0").option("--repo <owner/name>", "the repo to check (default: the current repo)").option("--json", "machine-readable output ? the discrete state rather than the sentence").action(async (o) => {
|
|
30795
31913
|
try {
|
|
30796
31914
|
const repo = o.repo ?? await currentRepoFullName();
|
|
30797
31915
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -30824,7 +31942,7 @@ async function reportWrite(label, res) {
|
|
|
30824
31942
|
}
|
|
30825
31943
|
if (res.error) return failGraceful(`${label}: ${res.error}`);
|
|
30826
31944
|
const detail = res.body?.error ?? "";
|
|
30827
|
-
return failGraceful(`${label}: HTTP ${res.status}${detail ? `
|
|
31945
|
+
return failGraceful(`${label}: HTTP ${res.status}${detail ? ` ? ${detail}` : ""}`);
|
|
30828
31946
|
}
|
|
30829
31947
|
var tenant = program2.command("tenant").description("tenant runtime control through Hub authority");
|
|
30830
31948
|
tenant.command("control <owner/repo> <stage> <action>").description("bounded tenant control plus value-free vault/broker verification; project-admin own dev/rc, master main").option("--watch", "block on the dispatched run and report its conclusion (status/retire/verify-secrets/verify-broker/logs watch by default)").option("--lines <n>", "logs only: trailing lines of the tenant service to return (1-2000, default 200)").option("--json", "machine-readable output").action(async (repo, stage, action, o) => {
|
|
@@ -30851,7 +31969,7 @@ tenant.command("control <owner/repo> <stage> <action>").description("bounded ten
|
|
|
30851
31969
|
printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
|
|
30852
31970
|
}
|
|
30853
31971
|
if (result.conclusion === "failure") {
|
|
30854
|
-
return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"}
|
|
31972
|
+
return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"} ? ${result.note}`);
|
|
30855
31973
|
}
|
|
30856
31974
|
} catch (e) {
|
|
30857
31975
|
return failGraceful(`runtime tenant control: ${e.message}`);
|
|
@@ -30860,7 +31978,7 @@ tenant.command("control <owner/repo> <stage> <action>").description("bounded ten
|
|
|
30860
31978
|
tenant.command("reconcile <owner/repo> <stage>").description("re-render this tenant stage's generated box assets from registry truth; project-admin own dev/rc, master main; watches by default").option("--watch", "wait for the tenant-reconcile.yml run (default)").option("--no-watch", "return after dispatch; do not redeploy until the reconcile run succeeds").option("--json", "machine-readable output").action(async (repo, stage, o) => {
|
|
30861
31979
|
try {
|
|
30862
31980
|
const result = await runTenantReconcile(trainApplyDeps(), { repo, stage, watch: o.watch });
|
|
30863
|
-
printLine(o.json ? JSON.stringify(result, null, 2) : `${result.note}${result.runUrl ? `
|
|
31981
|
+
printLine(o.json ? JSON.stringify(result, null, 2) : `${result.note}${result.runUrl ? ` ? ${result.runUrl}` : ""}`);
|
|
30864
31982
|
if (result.conclusion === "failure" || !result.dispatched) process.exitCode = 1;
|
|
30865
31983
|
} catch (e) {
|
|
30866
31984
|
return failGraceful(`runtime tenant reconcile: ${e.message}`);
|
|
@@ -30873,7 +31991,7 @@ tenant.command("status <owner/repo> <stage>").description("read tenant runtime r
|
|
|
30873
31991
|
console.log(JSON.stringify(result, null, 2));
|
|
30874
31992
|
if (result.publicProbe?.ok === false) process.exitCode = 1;
|
|
30875
31993
|
});
|
|
30876
|
-
tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev
|
|
31994
|
+
tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev ? the promoted/staging ref)").option("--watch", "block on the dispatched run and report its outcome (gh run watch --exit-status)").option("--json", "machine-readable output").action(async (repo, stage, o) => {
|
|
30877
31995
|
if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime tenant redeploy: <stage> must be dev, rc, or main");
|
|
30878
31996
|
try {
|
|
30879
31997
|
const result = await runTenantRedeploy(trainApplyDeps(), { repo, stage, ref: o.ref, watch: o.watch });
|
|
@@ -30882,9 +32000,9 @@ tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the cen
|
|
|
30882
32000
|
return failGraceful(`runtime tenant redeploy: ${e.message}`);
|
|
30883
32001
|
}
|
|
30884
32002
|
});
|
|
30885
|
-
tenant.command("sweep-rc").description("discover (and optionally retire) running rc tenant runtimes across tenant-containers
|
|
32003
|
+
tenant.command("sweep-rc").description("discover (and optionally retire) running rc tenant runtimes across tenant-containers ? orphan cleanup after a failed post-release retire (#942)").option("--retire", "retire every running rc runtime found (requires --yes) ? WARNING: tears down a legitimately-staged rc too").option("--yes", "confirm the destructive --retire").option("--json", "machine-readable output").action(async (o) => {
|
|
30886
32004
|
if (o.retire && !o.yes) {
|
|
30887
|
-
return fail("runtime tenant sweep-rc --retire is destructive (it tears down EVERY running rc, including one legitimately staged between /rcand and /release)
|
|
32005
|
+
return fail("runtime tenant sweep-rc --retire is destructive (it tears down EVERY running rc, including one legitimately staged between /rcand and /release) ? re-run with --yes to confirm");
|
|
30888
32006
|
}
|
|
30889
32007
|
const cfg = await loadConfig();
|
|
30890
32008
|
const cdeps = registryClientDeps(cfg);
|
|
@@ -30937,7 +32055,7 @@ async function buildTenantRuntimeStatusFor(target, stage, cfg) {
|
|
|
30937
32055
|
lastTenantDeployRun: (await fetchLastTenantDeployRun(slug, stage)).run
|
|
30938
32056
|
});
|
|
30939
32057
|
}
|
|
30940
|
-
var project = program2.command("project").description("the DDB org registry
|
|
32058
|
+
var project = program2.command("project").description("the DDB org registry ? list/get projects (any member); set is master-only");
|
|
30941
32059
|
async function projectTarget(commandName, explicitTarget) {
|
|
30942
32060
|
return requireProjectTarget(commandName, explicitTarget, explicitTarget ? void 0 : await resolveRepo());
|
|
30943
32061
|
}
|
|
@@ -30981,7 +32099,7 @@ project.command("list").description("list all projects (identity + board, never
|
|
|
30981
32099
|
console.log(`${p.slug ?? "?"} - ${p.name ?? ""}${p.division ? ` [${p.division}]` : ""}${p.class ? ` (${p.class})` : ""}${p.projectType ? ` <${p.projectType}>` : ""}${p.deployModel ? ` {${p.deployModel}}` : ""}`);
|
|
30982
32100
|
}
|
|
30983
32101
|
});
|
|
30984
|
-
project.command("get [owner/repo]").description("a project's META (board ids + pointers) by repo or slug; defaults to the current repo
|
|
32102
|
+
project.command("get [owner/repo]").description("a project's META (board ids + pointers) by repo or slug; defaults to the current repo ? identity, NOT deploy coords").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
30985
32103
|
const cfg = await loadConfig();
|
|
30986
32104
|
let target;
|
|
30987
32105
|
try {
|
|
@@ -30991,7 +32109,7 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
30991
32109
|
}
|
|
30992
32110
|
const read = await fetchProjectBySlugChecked(slugOf(target), registryClientDeps(cfg));
|
|
30993
32111
|
if (!read.ok) {
|
|
30994
|
-
return failGraceful(`org project get: Hub registry read failed (${read.error})
|
|
32112
|
+
return failGraceful(`org project get: Hub registry read failed (${read.error}) ? likely transient (cold start, network, or auth blip); retry shortly`);
|
|
30995
32113
|
}
|
|
30996
32114
|
if (!read.project) {
|
|
30997
32115
|
return failGraceful(`org project get: no registry META for ${target} (unknown or unbootstrapped)`);
|
|
@@ -31001,10 +32119,10 @@ project.command("get [owner/repo]").description("a project's META (board ids + p
|
|
|
31001
32119
|
const m = read.project;
|
|
31002
32120
|
const track = resolveReleaseTrack(m, void 0, target);
|
|
31003
32121
|
const stages = branchesForTrack(track).join(" -> ");
|
|
31004
|
-
const note = track === "direct" ? " (direct
|
|
32122
|
+
const note = track === "direct" ? " (direct ? no rc; /rcand refuses, /release ships development -> main)" : track === "trunk" ? " (trunk ? main only)" : "";
|
|
31005
32123
|
console.error(
|
|
31006
|
-
`${m.name ?? target}
|
|
31007
|
-
release track: ${track}
|
|
32124
|
+
`${m.name ?? target} ? class ${m.class ?? "?"} ? deploy ${m.deployModel ?? "?"}
|
|
32125
|
+
release track: ${track} ? stages: ${stages}${note}
|
|
31008
32126
|
deploys run centrally (tenant-deploy.yml); product repos carry no deploy files. Inspect nonsecret DEPLOY facts with \`mmi-cli org project deploy get\`; full coords remain OIDC-gated.`
|
|
31009
32127
|
);
|
|
31010
32128
|
}
|
|
@@ -31037,7 +32155,7 @@ projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# fa
|
|
|
31037
32155
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
31038
32156
|
console.log(JSON.stringify(payload));
|
|
31039
32157
|
});
|
|
31040
|
-
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"
|
|
32158
|
+
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
31041
32159
|
const cfg = await loadConfig();
|
|
31042
32160
|
let target;
|
|
31043
32161
|
try {
|
|
@@ -31053,7 +32171,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
31053
32171
|
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`);
|
|
31054
32172
|
if (o.secretsFile) {
|
|
31055
32173
|
try {
|
|
31056
|
-
vars.push(`secrets=${(0,
|
|
32174
|
+
vars.push(`secrets=${(0, import_node_fs39.readFileSync)(o.secretsFile, "utf8")}`);
|
|
31057
32175
|
} catch (e) {
|
|
31058
32176
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
31059
32177
|
}
|
|
@@ -31078,7 +32196,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
31078
32196
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|
|
31079
32197
|
return reportWrite("org project set", res);
|
|
31080
32198
|
});
|
|
31081
|
-
project.command("retire [owner/repo]").description("retire an orphaned registry slug (master-only): soft-delete the PROJECT# META row + drop the repo's board items; secrets/vault left alone. DRY-RUN by default
|
|
32199
|
+
project.command("retire [owner/repo]").description("retire an orphaned registry slug (master-only): soft-delete the PROJECT# META row + drop the repo's board items; secrets/vault left alone. DRY-RUN by default ? pass --apply to actually delete; defaults to the current repo").option("--apply", "actually delete (default is a dry-run preview of what would be removed)").option("--skip-board", "retire the registry META only ? leave board items in place").option("--json", "machine-readable {slug, removedMeta, removedBoardItem} output").action(async (repoOrSlug, o) => {
|
|
31082
32200
|
const cfg = await loadConfig();
|
|
31083
32201
|
let target;
|
|
31084
32202
|
try {
|
|
@@ -31110,9 +32228,9 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
31110
32228
|
const verb = result.applied ? "retired" : "WOULD retire (dry-run; pass --apply to delete)";
|
|
31111
32229
|
printLine(`org project retire: ${verb} ${result.slug} (${result.repo})`);
|
|
31112
32230
|
if (result.applied) {
|
|
31113
|
-
printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED
|
|
32231
|
+
printLine(` META: ${result.removedMeta.ok ? `removed=${result.removedMeta.removed ?? "unknown"}` : `FAILED ? ${result.removedMeta.error}`}`);
|
|
31114
32232
|
} else {
|
|
31115
|
-
printLine(` META: ${result.removedMeta.ok ? result.removedMeta.existing ? "live row present
|
|
32233
|
+
printLine(` META: ${result.removedMeta.ok ? result.removedMeta.existing ? "live row present ? would be tombstoned" : "no live row (already absent/tombstoned)" : `read failed ? ${result.removedMeta.error}`}`);
|
|
31116
32234
|
}
|
|
31117
32235
|
if (result.removedBoardItem) {
|
|
31118
32236
|
const b = result.removedBoardItem;
|
|
@@ -31123,7 +32241,7 @@ project.command("retire [owner/repo]").description("retire an orphaned registry
|
|
|
31123
32241
|
printLine(` ${result.vaultNote}`);
|
|
31124
32242
|
}
|
|
31125
32243
|
if (result.applied && !result.removedMeta.ok) {
|
|
31126
|
-
return failGraceful(`org project retire: META delete failed
|
|
32244
|
+
return failGraceful(`org project retire: META delete failed ? ${result.removedMeta.error}`);
|
|
31127
32245
|
}
|
|
31128
32246
|
});
|
|
31129
32247
|
var fullTrack = program2.command("full-track").description("direct-to-train readiness audits");
|
|
@@ -31151,7 +32269,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
|
|
|
31151
32269
|
console.log(JSON.stringify(report, null, 2));
|
|
31152
32270
|
if (!report.rcand.canApply) process.exitCode = 1;
|
|
31153
32271
|
});
|
|
31154
|
-
project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row
|
|
32272
|
+
project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row ? project-admin may set only --no-env-file on their own existing dev/rc row; master may seed/change all coords and main; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").makeOptionMandatory().choices(["dev", "rc", "main"])).option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row ? `mmi-cli runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").addOption(new Option("--substrate <substrate>", "hetzner-ssh | s3-cloudfront; omit to leave the row unchanged").choices([...DEPLOY_SUBSTRATES])).option("--deploy-role-arn <arn>", "s3-cloudfront: the scoped OIDC deployer role tenant-deploy.yml assumes for this stage").option("--app-bucket <bucket>", "s3-cloudfront: the S3 bucket the built dist/ is synced into").option("--distribution-id <id>", "s3-cloudfront: the CloudFront distribution invalidated after the sync").option("--distribution-domain <domain>", "s3-cloudfront: the host the post-deploy security-header proof is measured against").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--clear-aliases", "remove EVERY serving alias from the row (#2986) ? omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) ? own-repo project-admin dev/rc; master main; true = env passthrough with no .env symlink").option("--force", "explicit recovery override: skip fileless-compose verification only after independent proof").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
31155
32273
|
const cfg = await loadConfig();
|
|
31156
32274
|
let target;
|
|
31157
32275
|
try {
|
|
@@ -31177,7 +32295,7 @@ ${filelessTransitionGuide(target, o.stage)}`);
|
|
|
31177
32295
|
if (verdict.warn) console.error(`org project set-deploy: ${verdict.warn}`);
|
|
31178
32296
|
} else if (!o.force) {
|
|
31179
32297
|
return fail(
|
|
31180
|
-
`org project set-deploy: cannot verify ${target} ${branch} from this checkout
|
|
32298
|
+
`org project set-deploy: cannot verify ${target} ${branch} from this checkout ? refusing to change noEnvFile. Run from the target repo with origin/${branch} fetched.
|
|
31181
32299
|
${filelessTransitionGuide(target, o.stage)}`
|
|
31182
32300
|
);
|
|
31183
32301
|
} else {
|
|
@@ -31212,14 +32330,14 @@ ${filelessTransitionGuide(target, o.stage)}`
|
|
|
31212
32330
|
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
31213
32331
|
return reportWrite("org project set-deploy", res);
|
|
31214
32332
|
});
|
|
31215
|
-
var registry = program2.command("registry").description("the DDB org registry
|
|
32333
|
+
var registry = program2.command("registry").description("the DDB org registry ? org-level constants");
|
|
31216
32334
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
31217
32335
|
const cfg = await loadConfig();
|
|
31218
32336
|
const org = await fetchOrgConfig(registryClientDeps(cfg));
|
|
31219
32337
|
if (!org) return failGraceful("org config get: Hub API unreachable, unseeded, or this repo is not bootstrapped");
|
|
31220
32338
|
console.log(JSON.stringify(org));
|
|
31221
32339
|
});
|
|
31222
|
-
var oauth = program2.command("oauth").description("per-repo Google OAuth
|
|
32340
|
+
var oauth = program2.command("oauth").description("per-repo Google OAuth ? plan the canonical URI set, verify the client is port-agnostic");
|
|
31223
32341
|
oauth.command("plan", { isDefault: true }).description("print the canonical JS origins + redirect URIs + SSM cred param names for this repo").option("--repo <owner/repo>", "slug source (defaults to the current repo)").option("--json", "machine-readable output").action(async (o) => {
|
|
31224
32342
|
const cfg = await loadConfig();
|
|
31225
32343
|
const slug = (o.repo ? o.repo.split("/").pop() : cfg.project ?? await repoSlug()).toLowerCase();
|
|
@@ -31233,7 +32351,7 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
31233
32351
|
return failGraceful(
|
|
31234
32352
|
`org oauth plan: ${message}. Declare it in the registry META first:
|
|
31235
32353
|
mmi-cli org project set ${o.repo ?? `mutmutco/${slug}`} --var 'oauth={"subdomains":["${defaultSubdomain(slug)}"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}'
|
|
31236
|
-
New repo? The GCP project and its Google Auth Platform consent screen must exist too
|
|
32354
|
+
New repo? The GCP project and its Google Auth Platform consent screen must exist too ? docs/Guides/oauth-provision.md ? New repo.`
|
|
31237
32355
|
);
|
|
31238
32356
|
}
|
|
31239
32357
|
return failGraceful(`org oauth plan: ${message}`);
|
|
@@ -31259,7 +32377,7 @@ SSM cred params (under /mmi-future/${slug}/):`);
|
|
|
31259
32377
|
oauth.command("set-creds").description('store the OAuth client into the canonical stageless GOOGLE_CLIENT_ID/SECRET pair (pipe the Console "Download JSON" on stdin)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
|
|
31260
32378
|
const raw = await readStdin();
|
|
31261
32379
|
if (!raw.trim()) {
|
|
31262
|
-
return fail("org oauth set-creds: pipe the Google client JSON on stdin
|
|
32380
|
+
return fail("org oauth set-creds: pipe the Google client JSON on stdin ? e.g.\n mmi-cli org oauth set-creds --repo <owner/repo> < client.json");
|
|
31263
32381
|
}
|
|
31264
32382
|
let creds;
|
|
31265
32383
|
try {
|
|
@@ -31309,9 +32427,9 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
|
|
|
31309
32427
|
if (o.json) {
|
|
31310
32428
|
console.log(JSON.stringify({ slug, redirectUri, portAgnostic: !mismatch }));
|
|
31311
32429
|
} else if (mismatch) {
|
|
31312
|
-
console.error(`FAIL ${slug}: redirect_uri_mismatch for ${redirectUri}
|
|
32430
|
+
console.error(`FAIL ${slug}: redirect_uri_mismatch for ${redirectUri} ? client is not port-agnostic (run /oauth-provision)`);
|
|
31313
32431
|
} else {
|
|
31314
|
-
console.log(`PASS ${slug}: ${redirectUri} accepted
|
|
32432
|
+
console.log(`PASS ${slug}: ${redirectUri} accepted ? port-agnostic OAuth is live`);
|
|
31315
32433
|
}
|
|
31316
32434
|
if (mismatch) process.exitCode = 1;
|
|
31317
32435
|
});
|
|
@@ -31324,7 +32442,7 @@ function resolveCreatePriority(raw, command) {
|
|
|
31324
32442
|
try {
|
|
31325
32443
|
return normalizePriority(raw);
|
|
31326
32444
|
} catch {
|
|
31327
|
-
fail(`${command}: unknown priority "${raw}"
|
|
32445
|
+
fail(`${command}: unknown priority "${raw}" ? expected one of: ${CLI_PRIORITIES.join(", ")}`, {
|
|
31328
32446
|
code: ERROR_CODES.ERR_BAD_ENUM,
|
|
31329
32447
|
offending_flag: "--priority",
|
|
31330
32448
|
expected: [...CLI_PRIORITIES]
|
|
@@ -31334,15 +32452,15 @@ function resolveCreatePriority(raw, command) {
|
|
|
31334
32452
|
function resolveCreateType(raw, command, labels) {
|
|
31335
32453
|
if (raw === void 0 || raw === "") {
|
|
31336
32454
|
const nearMiss = labels?.find((l) => ISSUE_TYPES.includes(l));
|
|
31337
|
-
const hint = nearMiss ? ` (you passed --label ${nearMiss}
|
|
32455
|
+
const hint = nearMiss ? ` (you passed --label ${nearMiss} ? did you mean --type ${nearMiss}?)` : "";
|
|
31338
32456
|
fail(`${command}: --type is required (bug | feature | task) unless --batch supplies it per row${hint}`, {
|
|
31339
32457
|
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
31340
32458
|
offending_flag: "--type",
|
|
31341
|
-
...nearMiss ? { corrected_command: `${command} --type ${nearMiss}
|
|
32459
|
+
...nearMiss ? { corrected_command: `${command} --type ${nearMiss} ?` } : {}
|
|
31342
32460
|
});
|
|
31343
32461
|
}
|
|
31344
32462
|
if (!ISSUE_TYPES.includes(raw)) {
|
|
31345
|
-
fail(`${command}: unknown type "${raw}"
|
|
32463
|
+
fail(`${command}: unknown type "${raw}" ? expected one of: ${ISSUE_TYPES.join(", ")}`, {
|
|
31346
32464
|
code: ERROR_CODES.ERR_BAD_ENUM,
|
|
31347
32465
|
offending_flag: "--type",
|
|
31348
32466
|
expected: [...ISSUE_TYPES]
|
|
@@ -31356,9 +32474,9 @@ function resolveCreateSurface(opts) {
|
|
|
31356
32474
|
function surfaceWaived() {
|
|
31357
32475
|
return rawFlag("--no-surface");
|
|
31358
32476
|
}
|
|
31359
|
-
var issue = program2.command("issue").description("issues
|
|
32477
|
+
var issue = program2.command("issue").description("issues ? reliable create with structured output");
|
|
31360
32478
|
withExamples(mutating(
|
|
31361
|
-
issue.command("create").description("create an issue (type
|
|
32479
|
+
issue.command("create").description("create an issue (type ? label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) ? prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only ? never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").option("--no-surface", "file without a surface label on a repo that requires one ? for a genuinely exempt filing (e.g. a coop proof issue that spans every surface)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
31362
32480
|
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
31363
32481
|
// and surface contract as the real action. A plan that echoes the title-file PATH instead of its value
|
|
31364
32482
|
// is not a plan of the mutation that will run (#3914).
|
|
@@ -31425,7 +32543,7 @@ withExamples(mutating(
|
|
|
31425
32543
|
}
|
|
31426
32544
|
targetRepo2 = await resolveRepo(o.repo);
|
|
31427
32545
|
if (!targetRepo2) {
|
|
31428
|
-
return fail("issue create: could not resolve the target repo
|
|
32546
|
+
return fail("issue create: could not resolve the target repo ? run inside a git checkout or pass --repo <owner/repo>");
|
|
31429
32547
|
}
|
|
31430
32548
|
args = buildIssueArgs({
|
|
31431
32549
|
type: issueType,
|
|
@@ -31454,7 +32572,7 @@ withExamples(mutating(
|
|
|
31454
32572
|
labels: extraLabels.length ? extraLabels : void 0
|
|
31455
32573
|
});
|
|
31456
32574
|
process.stderr.write(
|
|
31457
|
-
`warning: --surface ${surfaceFlagLabel} was dropped
|
|
32575
|
+
`warning: --surface ${surfaceFlagLabel} was dropped ? ${targetRepo2} defines no surface:* labels, and creating one here would switch the one-surface-label rule on for every later filing in it. Use --label ${surfaceFlagLabel} if you really mean to start that taxonomy.
|
|
31458
32576
|
`
|
|
31459
32577
|
);
|
|
31460
32578
|
}
|
|
@@ -31503,7 +32621,7 @@ async function readParentField(number, repo) {
|
|
|
31503
32621
|
}
|
|
31504
32622
|
return resolveParentField(payload);
|
|
31505
32623
|
}
|
|
31506
|
-
issue.command("view <number>").description('read an issue as structured JSON
|
|
32624
|
+
issue.command("view <number>").description('read an issue as structured JSON ? the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags ? in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) ? the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
|
|
31507
32625
|
const n = Number(number);
|
|
31508
32626
|
if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
|
|
31509
32627
|
const repo = await resolveRepo(o.repo);
|
|
@@ -31597,7 +32715,7 @@ jsonParity(issue.command("comment <ref>").description("post a Markdown comment t
|
|
|
31597
32715
|
return fail(`issue comment: ${e.message}`);
|
|
31598
32716
|
}
|
|
31599
32717
|
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
31600
|
-
if (!repo) return fail("issue comment: could not resolve repo
|
|
32718
|
+
if (!repo) return fail("issue comment: could not resolve repo ? pass --repo owner/repo");
|
|
31601
32719
|
try {
|
|
31602
32720
|
const result = await postIssueComment(defaultGitHubClient(), { ref, defaultRepo: repo, body });
|
|
31603
32721
|
console.log(JSON.stringify(result));
|
|
@@ -31606,7 +32724,7 @@ jsonParity(issue.command("comment <ref>").description("post a Markdown comment t
|
|
|
31606
32724
|
return failGraceful(`issue comment: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
31607
32725
|
}
|
|
31608
32726
|
});
|
|
31609
|
-
jsonParity(issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match
|
|
32727
|
+
jsonParity(issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match ? exact item text, else a unique substring").option("--off", "untick the item ([x] ? [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)")).action(async (ref, o) => {
|
|
31610
32728
|
let parsed;
|
|
31611
32729
|
try {
|
|
31612
32730
|
parsed = parseIssueRef(ref);
|
|
@@ -31614,7 +32732,7 @@ jsonParity(issue.command("check <ref>").description("tick (or with --off untick)
|
|
|
31614
32732
|
return fail(`issue check: ${e.message}`);
|
|
31615
32733
|
}
|
|
31616
32734
|
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
31617
|
-
if (!repo) return fail("issue check: could not resolve repo
|
|
32735
|
+
if (!repo) return fail("issue check: could not resolve repo ? pass --repo owner/repo");
|
|
31618
32736
|
const checked = o.off !== true;
|
|
31619
32737
|
let body;
|
|
31620
32738
|
try {
|
|
@@ -31627,7 +32745,7 @@ jsonParity(issue.command("check <ref>").description("tick (or with --off untick)
|
|
|
31627
32745
|
if (!result.ok) {
|
|
31628
32746
|
if (result.reason === "ambiguous") {
|
|
31629
32747
|
const list = result.matches.map((m) => ` - ${m.text}`).join("\n");
|
|
31630
|
-
return fail(`issue check: "${o.item}" matches ${result.matches.length} checklist items in ${repo}#${parsed.number}
|
|
32748
|
+
return fail(`issue check: "${o.item}" matches ${result.matches.length} checklist items in ${repo}#${parsed.number} ? narrow the text:
|
|
31631
32749
|
${list}`);
|
|
31632
32750
|
}
|
|
31633
32751
|
return fail(`issue check: no checklist item matching "${o.item}" in ${repo}#${parsed.number}`);
|
|
@@ -31646,7 +32764,7 @@ ${list}`);
|
|
|
31646
32764
|
}
|
|
31647
32765
|
console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
|
|
31648
32766
|
});
|
|
31649
|
-
program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare
|
|
32767
|
+
program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare ? usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default ? report always prints JSON; #682)").action(async (o) => {
|
|
31650
32768
|
let body;
|
|
31651
32769
|
let priority;
|
|
31652
32770
|
let title;
|
|
@@ -31656,7 +32774,7 @@ program2.command("report").description("file a friction report on the Hub board
|
|
|
31656
32774
|
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises11.readFile, readStdin });
|
|
31657
32775
|
priority = resolveCreatePriority(o.priority, "report");
|
|
31658
32776
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
31659
|
-
throw new Error(`unknown issue type "${o.type}"
|
|
32777
|
+
throw new Error(`unknown issue type "${o.type}" ? expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
31660
32778
|
}
|
|
31661
32779
|
} catch (e) {
|
|
31662
32780
|
const m = e.message;
|
|
@@ -31693,7 +32811,7 @@ async function resolvePluginSha() {
|
|
|
31693
32811
|
return void 0;
|
|
31694
32812
|
}
|
|
31695
32813
|
}
|
|
31696
|
-
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").addOption(new Option("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).makeOptionMandatory().choices([...SKILL_NAMES])).option("--title <title>", "one-line summary of what misfired").option("--title-file <path|->", "read the one-line summary from a UTF-8 file, or from stdin with -").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 (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO3})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default
|
|
32814
|
+
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").addOption(new Option("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).makeOptionMandatory().choices([...SKILL_NAMES])).option("--title <title>", "one-line summary of what misfired").option("--title-file <path|->", "read the one-line summary from a UTF-8 file, or from stdin with -").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 (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO3})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default ? skill-lesson always prints JSON)").action(async (o) => {
|
|
31697
32815
|
const targetRepo2 = o.repo ?? HUB_REPO3;
|
|
31698
32816
|
const sourceRepo = await resolveRepo(void 0);
|
|
31699
32817
|
const pluginSha = await resolvePluginSha();
|
|
@@ -31748,7 +32866,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
31748
32866
|
repo: targetRepo2,
|
|
31749
32867
|
labels: [SKILL_LESSON_LABEL],
|
|
31750
32868
|
command: "skill-lesson",
|
|
31751
|
-
waiver: { reason: "tooling lesson spans product surfaces
|
|
32869
|
+
waiver: { reason: "tooling lesson spans product surfaces ? coop-proof class (#3789)" }
|
|
31752
32870
|
});
|
|
31753
32871
|
if (surfaceWarn) process.stderr.write(`${surfaceWarn}
|
|
31754
32872
|
`);
|
|
@@ -31760,7 +32878,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
31760
32878
|
const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo2, priority);
|
|
31761
32879
|
console.log(JSON.stringify({ ...created, deduped: false, label: SKILL_LESSON_LABEL, skill, priority, projectItemId, onBoard }));
|
|
31762
32880
|
});
|
|
31763
|
-
var pr = program2.command("pr").description("pull requests
|
|
32881
|
+
var pr = program2.command("pr").description("pull requests ? reliable create with structured output");
|
|
31764
32882
|
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").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)").option("--draft", "open the PR in draft state (#2667)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
|
|
31765
32883
|
let body;
|
|
31766
32884
|
let title;
|
|
@@ -31773,7 +32891,7 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
31773
32891
|
body = normalizeClosingDirectives(body);
|
|
31774
32892
|
const docsCheck = await checkDocsIndexAtHead({ repo: o.repo, head: o.head }, createPrCreateDocsIndexDeps());
|
|
31775
32893
|
if (docsCheck && !docsCheck.ok) {
|
|
31776
|
-
return fail(`pr create: ${docsCheck.detail}
|
|
32894
|
+
return fail(`pr create: ${docsCheck.detail} ? ${docsCheck.fix}`);
|
|
31777
32895
|
}
|
|
31778
32896
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
31779
32897
|
console.log(JSON.stringify(created));
|
|
@@ -31784,7 +32902,7 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
31784
32902
|
"--head and --base default to the current branch and the repo default; only pass them to override.",
|
|
31785
32903
|
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown."
|
|
31786
32904
|
]);
|
|
31787
|
-
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit)
|
|
32905
|
+
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) ? the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags ? in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) ? read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (number, o) => {
|
|
31788
32906
|
const n = Number(number);
|
|
31789
32907
|
if (!Number.isInteger(n) || n <= 0) return fail("pr view: <number> must be a positive integer");
|
|
31790
32908
|
const repo = await resolveRepo(o.repo);
|
|
@@ -31805,11 +32923,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
31805
32923
|
}
|
|
31806
32924
|
});
|
|
31807
32925
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
31808
|
-
const wfDir = (0,
|
|
31809
|
-
if (!(0,
|
|
31810
|
-
return (0,
|
|
32926
|
+
const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
|
|
32927
|
+
if (!(0, import_node_fs39.existsSync)(wfDir)) return [];
|
|
32928
|
+
return (0, import_node_fs39.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
31811
32929
|
try {
|
|
31812
|
-
return workflowReportsPrChecks((0,
|
|
32930
|
+
return workflowReportsPrChecks((0, import_node_fs39.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
|
|
31813
32931
|
} catch {
|
|
31814
32932
|
return true;
|
|
31815
32933
|
}
|
|
@@ -31841,16 +32959,16 @@ function ciAuditDeps() {
|
|
|
31841
32959
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
31842
32960
|
readSeedFile: (path2) => {
|
|
31843
32961
|
if (!root) return null;
|
|
31844
|
-
const fullPath = (0,
|
|
31845
|
-
return (0,
|
|
32962
|
+
const fullPath = (0, import_node_path38.join)(root, path2);
|
|
32963
|
+
return (0, import_node_fs39.existsSync)(fullPath) ? (0, import_node_fs39.readFileSync)(fullPath, "utf8") : null;
|
|
31846
32964
|
}
|
|
31847
32965
|
};
|
|
31848
32966
|
}
|
|
31849
32967
|
function hubRoot() {
|
|
31850
|
-
const fromPkg = (0,
|
|
32968
|
+
const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
|
|
31851
32969
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
31852
|
-
if ((0,
|
|
31853
|
-
if ((0,
|
|
32970
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
|
|
32971
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(process.cwd(), marker))) return process.cwd();
|
|
31854
32972
|
return null;
|
|
31855
32973
|
}
|
|
31856
32974
|
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) => {
|
|
@@ -31858,7 +32976,7 @@ pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs
|
|
|
31858
32976
|
if (o.json) return printLine(JSON.stringify(result));
|
|
31859
32977
|
printLine(`merge CI policy: ${result.policy} (${result.reason})`);
|
|
31860
32978
|
});
|
|
31861
|
-
pr.command("checks-wait <number>").description(`bounded wait for PR checks; skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR
|
|
32979
|
+
pr.command("checks-wait <number>").description(`bounded wait for PR checks; skips immediately on no-ci repos (#1432), fails immediately on a CONFLICTING PR ? GitHub never queues checks for one (#2970). REST-only polling with a pool floor (#3024). Default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m; --timeout raises it. Exit 1 = a check FAILED or the PR is CONFLICTING, exit ${PR_CHECKS_TIMEOUT_EXIT_CODE} = the wait window expired or the API pool ran dry (re-arm)`).option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--timeout <minutes>", `wait budget in minutes (default ${PR_CHECKS_TIMEOUT_MS / 6e4}) ? raise it for serial self-hosted e2e queues`).action(async (number, o) => {
|
|
31862
32980
|
let timeoutMs;
|
|
31863
32981
|
if (o.timeout !== void 0) {
|
|
31864
32982
|
const minutes = Number(o.timeout);
|
|
@@ -31883,24 +33001,27 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
31883
33001
|
timeoutMs,
|
|
31884
33002
|
// Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
|
|
31885
33003
|
// an agent harness kills it on its own (shorter) deadline before the verdict ever prints (#2940).
|
|
31886
|
-
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state}
|
|
33004
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state} ? ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
31887
33005
|
});
|
|
31888
33006
|
if (o.json) printLine(JSON.stringify(result));
|
|
31889
33007
|
else if (result.status === "conflicting") {
|
|
31890
|
-
printLine(`pr checks-wait: conflicting
|
|
33008
|
+
printLine(`pr checks-wait: conflicting ? ${result.reason}`);
|
|
31891
33009
|
} else if (result.status === "timeout") {
|
|
31892
|
-
|
|
33010
|
+
const stuckQueuing = /no-checks-reported/i.test(result.detail ?? "");
|
|
33011
|
+
printLine(
|
|
33012
|
+
`pr checks-wait: timeout ? waited ${Math.round((result.waitedMs ?? 0) / 6e4)}m, last state: ${result.detail ?? "pending"}. No check failed; re-run to keep waiting, or pass --timeout <minutes>.` + (stuckQueuing ? " Tip: jobs never appeared ? mmi-live may be saturated or Actions may be failing before checkout; check runner health (docs/Guides/gh-runner-runbook.md) and re-run the workflow." : "")
|
|
33013
|
+
);
|
|
31893
33014
|
} else if (result.status === "rate-limited") {
|
|
31894
|
-
printLine(`pr checks-wait: rate-limited
|
|
33015
|
+
printLine(`pr checks-wait: rate-limited ? ${result.reason ?? "REST pool below floor"}. No check failed; re-run after the pool resets.`);
|
|
31895
33016
|
} else if (result.detail === "runner-infra") {
|
|
31896
|
-
printLine(`pr checks-wait: failure (runner infrastructure, NOT a test failure)
|
|
33017
|
+
printLine(`pr checks-wait: failure (runner infrastructure, NOT a test failure) ? ${result.reason}`);
|
|
31897
33018
|
} else if (result.detail === "stale-head") {
|
|
31898
|
-
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure)
|
|
31899
|
-
} else printLine(`pr checks-wait: ${result.status}${result.reason ? `
|
|
33019
|
+
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure) ? ${result.reason}`);
|
|
33020
|
+
} else printLine(`pr checks-wait: ${result.status}${result.reason ? ` ? ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
31900
33021
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
31901
33022
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
31902
33023
|
});
|
|
31903
|
-
pr.command("land <number>").description("agent merge path (#1440): train probe
|
|
33024
|
+
pr.command("land <number>").description("agent merge path (#1440): train probe ? checks-wait ? merge --auto ? poll enqueued ? development PRs only").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--preserve-worktree", "after merge, keep the local PR worktree/stage/branch for an active batch (#1888)").option("--force", "acknowledge and land past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword refusal (#3718)").action(async (number, o) => {
|
|
31904
33025
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
31905
33026
|
const startingPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
31906
33027
|
assertPrMergeHousekeepingClean(startingPath || process.cwd(), "pr land", { force: o.force });
|
|
@@ -31926,8 +33047,8 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
31926
33047
|
} catch {
|
|
31927
33048
|
}
|
|
31928
33049
|
if (isPromotionBase(base, track)) {
|
|
31929
|
-
const shape = track ? `${track} track` : "unresolved track
|
|
31930
|
-
throw new Error(`pr land: base branch ${base} is a promotion target (${shape})
|
|
33050
|
+
const shape = track ? `${track} track` : "unresolved track ? strict reading";
|
|
33051
|
+
throw new Error(`pr land: base branch ${base} is a promotion target (${shape}) ? promotion merges stay human-only`);
|
|
31931
33052
|
}
|
|
31932
33053
|
return repo;
|
|
31933
33054
|
},
|
|
@@ -31935,23 +33056,23 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
31935
33056
|
resolveCiPolicy: (repo) => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
31936
33057
|
waitForChecks: (prNumber, repo) => waitForPrChecks({
|
|
31937
33058
|
resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
31938
|
-
// #3024: REST-only wait loop
|
|
33059
|
+
// #3024: REST-only wait loop ? runPrLand's resolveRepo already guarantees `repo` is set.
|
|
31939
33060
|
pollChecks: () => pollRestPrChecks(prNumber, repo),
|
|
31940
33061
|
// #2970: `pr land` only ever lands PRs based on development (resolveRepo above already rejects any
|
|
31941
33062
|
// other base), so the fast-fail message's base branch is always 'development' here.
|
|
31942
33063
|
pollMergeable: () => pollRestPrMergeable(prNumber, repo),
|
|
31943
33064
|
pollRateLimit: () => fetchRestCorePool(),
|
|
31944
|
-
// #3388: `pr land` is the batch path
|
|
33065
|
+
// #3388: `pr land` is the batch path ? the one most likely to self-DOS the shared runner and
|
|
31945
33066
|
// then read its own wall-clock kill as a broken diff.
|
|
31946
33067
|
diagnoseFailure: () => diagnoseFailedRestChecks(prNumber, repo),
|
|
31947
33068
|
baseBranch: "development",
|
|
31948
33069
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
31949
33070
|
log: (message) => console.warn(message),
|
|
31950
|
-
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness
|
|
33071
|
+
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness ? otherwise the
|
|
31951
33072
|
// 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
|
|
31952
|
-
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks
|
|
33073
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr land: waiting on checks ? ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
31953
33074
|
}),
|
|
31954
|
-
// #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying
|
|
33075
|
+
// #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying ? a
|
|
31955
33076
|
// fresh read of state/mergeable/checks, independent of the merge call's own (possibly stale) error.
|
|
31956
33077
|
probeMergeReady: async (prNumber, repo) => {
|
|
31957
33078
|
const [snapshot, checks] = await Promise.all([
|
|
@@ -31981,7 +33102,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
31981
33102
|
});
|
|
31982
33103
|
if (result.status !== "failed") {
|
|
31983
33104
|
const repoFlag = result.repo ? ` --repo ${result.repo}` : "";
|
|
31984
|
-
console.warn(`pr land: merge confirmed; cleanup pending
|
|
33105
|
+
console.warn(`pr land: merge confirmed; cleanup pending ? if this process is interrupted, resume with: mmi-cli pr merge ${number}${repoFlag} --squash`);
|
|
31985
33106
|
}
|
|
31986
33107
|
if (result.status !== "failed") {
|
|
31987
33108
|
try {
|
|
@@ -32010,13 +33131,13 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
32010
33131
|
}
|
|
32011
33132
|
if (o.json) printLine(JSON.stringify(result));
|
|
32012
33133
|
else {
|
|
32013
|
-
printLine(`pr land: ${result.status}${result.error ? `
|
|
33134
|
+
printLine(`pr land: ${result.status}${result.error ? ` ? ${result.error}` : ""}`);
|
|
32014
33135
|
for (const line of renderPrLandCleanupLines(result.cleanup)) printLine(line);
|
|
32015
33136
|
if (result.cleanupError) printLine(`pr land cleanup: ${result.cleanupError}`);
|
|
32016
33137
|
}
|
|
32017
33138
|
if (result.status === "failed" || result.cleanupError) process.exitCode = 1;
|
|
32018
33139
|
});
|
|
32019
|
-
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default) and clean up its branch + worktree
|
|
33140
|
+
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default) and clean up its branch + worktree ? no leftover local branch; on no-ci repos run pr ci-policy / checks-wait first (#1432)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge ? merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "keep the local PR worktree/stage/branch for an active multi-issue batch (#1888)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword refusal (#3718)")).action(async (number, o) => {
|
|
32020
33141
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
32021
33142
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
32022
33143
|
const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
|
|
@@ -32049,10 +33170,10 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
32049
33170
|
baseBranch,
|
|
32050
33171
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
32051
33172
|
log: (message) => console.warn(message),
|
|
32052
|
-
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks
|
|
33173
|
+
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks ? ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
32053
33174
|
});
|
|
32054
33175
|
if (wait.status !== "success" && wait.status !== "skipped") {
|
|
32055
|
-
console.warn(`pr merge: --wait stopped before merge
|
|
33176
|
+
console.warn(`pr merge: --wait stopped before merge ? ${wait.status}${wait.reason ? `: ${wait.reason}` : ""}${wait.detail ? ` (${wait.detail})` : ""}`);
|
|
32056
33177
|
process.exitCode = wait.status === "timeout" || wait.status === "rate-limited" ? PR_CHECKS_TIMEOUT_EXIT_CODE : 1;
|
|
32057
33178
|
return;
|
|
32058
33179
|
}
|
|
@@ -32091,7 +33212,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
32091
33212
|
return;
|
|
32092
33213
|
}
|
|
32093
33214
|
if (!o.auto && basePolicyBlocksImmediateMerge(message)) {
|
|
32094
|
-
console.warn(`pr merge: the base-branch policy blocks an immediate merge
|
|
33215
|
+
console.warn(`pr merge: the base-branch policy blocks an immediate merge ? upgrading to --auto (merges once required checks pass).`);
|
|
32095
33216
|
upgradedToAuto = true;
|
|
32096
33217
|
await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
32097
33218
|
const m2 = String(e2.message || "");
|
|
@@ -32122,7 +33243,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
32122
33243
|
const state = stateRead.ok ? stateRead.state : "";
|
|
32123
33244
|
const enqueued = describePrMergeEnqueuedReason(await pollGhPrChecks(number, repoArgs).catch(() => void 0));
|
|
32124
33245
|
console.log(JSON.stringify({ mergeStatus: "auto-merge-enqueued", enqueuedReason: enqueued.reason, pr: number, branch: headRef, state: state || "unknown", upgradedToAuto: upgradedToAuto || void 0 }));
|
|
32125
|
-
console.warn(`pr merge: PR #${number} is ENQUEUED, not merged
|
|
33246
|
+
console.warn(`pr merge: PR #${number} is ENQUEUED, not merged ? ${enqueued.message}.`);
|
|
32126
33247
|
if (upgradedToAuto && !o.auto) {
|
|
32127
33248
|
console.warn(`pr merge: exiting ${PR_MERGE_ENQUEUED_EXIT_CODE} so a chained command does not treat this as a completed merge.`);
|
|
32128
33249
|
process.exitCode = PR_MERGE_ENQUEUED_EXIT_CODE;
|
|
@@ -32138,7 +33259,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
32138
33259
|
state: stateRead.ok ? stateRead.state : "unknown",
|
|
32139
33260
|
cleanupStatus: "skipped"
|
|
32140
33261
|
}));
|
|
32141
|
-
console.error(`pr merge: ${gate.message}. Nothing was deleted
|
|
33262
|
+
console.error(`pr merge: ${gate.message}. Nothing was deleted ? re-check the PR and retry.`);
|
|
32142
33263
|
process.exitCode = 1;
|
|
32143
33264
|
return;
|
|
32144
33265
|
}
|
|
@@ -32158,7 +33279,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
32158
33279
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
32159
33280
|
beforeWorktrees,
|
|
32160
33281
|
startingPath,
|
|
32161
|
-
pathExists: (p) => (0,
|
|
33282
|
+
pathExists: (p) => (0, import_node_fs39.existsSync)(p),
|
|
32162
33283
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
32163
33284
|
teardownWorktreeStage,
|
|
32164
33285
|
deferredStore,
|
|
@@ -32232,7 +33353,7 @@ function trainApplyDeps() {
|
|
|
32232
33353
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
32233
33354
|
return verdict.ok ? { ok: true, role: verdict.authority.role, train: verdict.authority.train } : verdict;
|
|
32234
33355
|
},
|
|
32235
|
-
// Hub-App-authority dispatch of the central tenant deploy (#953)
|
|
33356
|
+
// Hub-App-authority dispatch of the central tenant deploy (#953) ? the Hub fires the
|
|
32236
33357
|
// workflow_dispatch with its App token, so the caller needs no MMI-Hub Actions write.
|
|
32237
33358
|
dispatchTenantDeploy: async ({ repo, stage, ref }) => {
|
|
32238
33359
|
const res = await tenantDeploy({ repo, stage, ref }, registryClientDeps(await loadConfig()));
|
|
@@ -32241,7 +33362,7 @@ function trainApplyDeps() {
|
|
|
32241
33362
|
throw new Error(`tenant deploy dispatch failed: ${detail}`);
|
|
32242
33363
|
}
|
|
32243
33364
|
},
|
|
32244
|
-
// Hub-App-authority dispatch of the central tenant-control.yml (#1717)
|
|
33365
|
+
// Hub-App-authority dispatch of the central tenant-control.yml (#1717) ? the Hub fires the
|
|
32245
33366
|
// workflow_dispatch with its App token. Never throws for an expected rejection: it returns the dispatch
|
|
32246
33367
|
// outcome so runTenantControl can map a 5xx (transport-failed, retryable) vs a 4xx (rejected) vs ok.
|
|
32247
33368
|
dispatchTenantControl: async ({ repo, stage, action, lines }) => {
|
|
@@ -32257,7 +33378,7 @@ function trainApplyDeps() {
|
|
|
32257
33378
|
return { ok: false, category: body?.category, error: body?.error ?? res.error };
|
|
32258
33379
|
},
|
|
32259
33380
|
// Hotfix-coverage guard (#958): runs against the local clone via real git. manifestPaths exempts the
|
|
32260
|
-
// release version fold (#976)
|
|
33381
|
+
// release version fold (#976) ? a main-only commit touching ONLY the root package manifest is the
|
|
32261
33382
|
// fold's version metadata, which the candidate replaces with its own. (The Hub's wider distribution
|
|
32262
33383
|
// set never reaches this guard: the Hub is direct-track.)
|
|
32263
33384
|
hotfixCoverage: (input) => checkHotfixCoverage({ ...input, manifestPaths: ["package.json", "package-lock.json"] }),
|
|
@@ -32292,13 +33413,13 @@ function renderDeployLine(d) {
|
|
|
32292
33413
|
else if (d.runUrl) parts.push(`run ${d.runUrl}`);
|
|
32293
33414
|
if (d.deployStatus === "success") parts.push("deploy: SUCCEEDED");
|
|
32294
33415
|
else if (d.deployStatus === "failure") parts.push("deploy: FAILED (promotion stands; retry the deploy, do not re-tag)");
|
|
32295
|
-
else if (d.runId != null) parts.push(`deploy: UNVERIFIED
|
|
32296
|
-
else if (d.workflowRuns?.length) parts.push("deploy: UNVERIFIED
|
|
32297
|
-
else parts.push("deploy: UNVERIFIED
|
|
33416
|
+
else if (d.runId != null) parts.push(`deploy: UNVERIFIED ? dispatched, not resolved (watch: gh run watch ${d.runId} --repo mutmutco/MMI-Hub --exit-status)`);
|
|
33417
|
+
else if (d.workflowRuns?.length) parts.push("deploy: UNVERIFIED ? the runs above are enumerated, not resolved; watch each to conclusion before calling this release healthy (#3322)");
|
|
33418
|
+
else parts.push("deploy: UNVERIFIED ? no run correlated or watched; resolve every workflow run on the release SHA before calling this release healthy (#3322)");
|
|
32298
33419
|
return parts.join("; ");
|
|
32299
33420
|
}
|
|
32300
33421
|
function renderReleaseResume(r) {
|
|
32301
|
-
const lines = [`mmi-cli release --resume: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]
|
|
33422
|
+
const lines = [`mmi-cli release --resume: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}] ? ${r.note}`];
|
|
32302
33423
|
for (const step of r.steps) lines.push(` - ${step}`);
|
|
32303
33424
|
if (r.releaseUrl) lines.push(` release: ${r.releaseUrl}`);
|
|
32304
33425
|
if (r.announceNote) lines.push(` announce: ${r.announceNote}`);
|
|
@@ -32308,23 +33429,23 @@ function renderReleaseResume(r) {
|
|
|
32308
33429
|
return lines.join("\n");
|
|
32309
33430
|
}
|
|
32310
33431
|
function renderReleaseAbort(r) {
|
|
32311
|
-
return `mmi-cli release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)})
|
|
33432
|
+
return `mmi-cli release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) ? ${r.note}`;
|
|
32312
33433
|
}
|
|
32313
33434
|
function renderReleasePublishRetry(r) {
|
|
32314
|
-
return `mmi-cli release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)})
|
|
33435
|
+
return `mmi-cli release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) ? ${r.note}; ${r.runUrl}`;
|
|
32315
33436
|
}
|
|
32316
33437
|
function renderRcandResume(r) {
|
|
32317
|
-
return `mmi-cli rcand --resume: promoted ${r.repo}
|
|
33438
|
+
return `mmi-cli rcand --resume: promoted ${r.repo} ? rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
|
|
32318
33439
|
}
|
|
32319
33440
|
function renderAlignment(label, alignment) {
|
|
32320
33441
|
if (alignment.status !== "pr-pending") return `${label}: ${alignment.note}`;
|
|
32321
33442
|
if (alignment.autoMergeEnqueued) {
|
|
32322
33443
|
return `${label}: alignment PR #${alignment.prNumber ?? "?"} AUTO-MERGE ENQUEUED (merges when checks pass)${alignment.prUrl ? ` (${alignment.prUrl})` : ""}`;
|
|
32323
33444
|
}
|
|
32324
|
-
return `${label}: ALIGNMENT PR PENDING
|
|
33445
|
+
return `${label}: ALIGNMENT PR PENDING ? land it with \`mmi-cli pr merge ${alignment.prNumber ?? "<number>"} --auto --merge\`${alignment.prUrl ? ` (${alignment.prUrl})` : ""}`;
|
|
32325
33446
|
}
|
|
32326
33447
|
function renderTrainApply(commandName, r) {
|
|
32327
|
-
let base = `mmi-cli ${commandName}: promoted ${r.repo}
|
|
33448
|
+
let base = `mmi-cli ${commandName}: promoted ${r.repo} ? ${r.stage} at ${r.tag} [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
32328
33449
|
if (r.versionFold) base = `${base}; ${r.versionFold}`;
|
|
32329
33450
|
if (r.resumeNote) base = `${base}; ${r.resumeNote}`;
|
|
32330
33451
|
if (r.devNote) base = `${base}; ${r.devNote}`;
|
|
@@ -32382,16 +33503,16 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
32382
33503
|
return fail(`${commandName}: ${e.message}`);
|
|
32383
33504
|
}
|
|
32384
33505
|
if (o.ack && commandName !== "release") {
|
|
32385
|
-
return fail(`${commandName}: --ack applies only to release
|
|
33506
|
+
return fail(`${commandName}: --ack applies only to release ? it overrides the rc -> main hotfix-coverage guard, which rcand does not run. Run: mmi-cli release --ack <shas>`);
|
|
32386
33507
|
}
|
|
32387
33508
|
if (o.dev && commandName !== "release") {
|
|
32388
|
-
return fail(`${commandName}: --dev applies only to release
|
|
33509
|
+
return fail(`${commandName}: --dev applies only to release ? it ships development -> main skipping rc, which rcand cannot do. Run: mmi-cli release --dev`);
|
|
32389
33510
|
}
|
|
32390
33511
|
if (o.announceSummaryFile && commandName !== "release") {
|
|
32391
|
-
return fail(`${commandName}: --announce-summary-file applies only to release
|
|
33512
|
+
return fail(`${commandName}: --announce-summary-file applies only to release ? rcand posts no Hub Slack announcement. Run: mmi-cli release --announce-summary-file <path>`);
|
|
32392
33513
|
}
|
|
32393
33514
|
if (o.abort && commandName !== "release") {
|
|
32394
|
-
return fail(`${commandName}: --abort applies only to release
|
|
33515
|
+
return fail(`${commandName}: --abort applies only to release ? it rolls back a proven unpublished Hub release tag. Run: mmi-cli release --abort --apply`);
|
|
32395
33516
|
}
|
|
32396
33517
|
if (o.retryPublish && commandName !== "release") {
|
|
32397
33518
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli release --retry-publish <run-id> --apply --watch`);
|
|
@@ -32413,7 +33534,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
32413
33534
|
}
|
|
32414
33535
|
}
|
|
32415
33536
|
if (o.abort) {
|
|
32416
|
-
if (o.resume) return fail("release: --abort and --resume are mutually exclusive
|
|
33537
|
+
if (o.resume) return fail("release: --abort and --resume are mutually exclusive ? abort removes an unpublished tag while resume preserves and promotes it");
|
|
32417
33538
|
if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
|
|
32418
33539
|
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
32419
33540
|
return fail("release: --abort accepts only --apply, --repo and --json; promotion flags cannot be combined with rollback");
|
|
@@ -32430,7 +33551,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
32430
33551
|
}
|
|
32431
33552
|
}
|
|
32432
33553
|
if (o.resume) {
|
|
32433
|
-
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive
|
|
33554
|
+
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive ? --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
|
|
32434
33555
|
try {
|
|
32435
33556
|
if (commandName === "rcand") {
|
|
32436
33557
|
const result2 = await runRcandResume(trainApplyDeps(), { watch: o.watch });
|
|
@@ -32478,7 +33599,7 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
32478
33599
|
projectInfoSync = await runProjectInfoSync(result.repo, true);
|
|
32479
33600
|
} catch (e) {
|
|
32480
33601
|
const error = e.message;
|
|
32481
|
-
projectInfoSync = { applied: false, note: `FAILED
|
|
33602
|
+
projectInfoSync = { applied: false, note: `FAILED ? ${error}`, error };
|
|
32482
33603
|
}
|
|
32483
33604
|
}
|
|
32484
33605
|
const alignmentPending = result.devRollForward?.status === "pr-pending" || result.rcAlignment?.status === "pr-pending";
|
|
@@ -32533,13 +33654,13 @@ function renderHotfixRelease(r) {
|
|
|
32533
33654
|
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
32534
33655
|
` - ${r.verifyNote}`,
|
|
32535
33656
|
...r.announceNote ? [` - announce: ${r.announceNote}`] : [],
|
|
32536
|
-
` - next: mmi-cli hotfix status ${r.tag} (no back-merge
|
|
33657
|
+
` - next: mmi-cli hotfix status ${r.tag} (no back-merge ? development already has the fix; the next /release back-merge aligns the version manifests)`
|
|
32537
33658
|
].join("\n");
|
|
32538
33659
|
}
|
|
32539
33660
|
function renderHotfixStatus(r) {
|
|
32540
33661
|
return [
|
|
32541
|
-
`mmi-cli hotfix status: ${r.tag} on ${r.repo}
|
|
32542
|
-
` - branch: ${r.branchExists ? "pushed" : "absent"}
|
|
33662
|
+
`mmi-cli hotfix status: ${r.tag} on ${r.repo} ? ${r.state}`,
|
|
33663
|
+
` - branch: ${r.branchExists ? "pushed" : "absent"} ? PR: ${r.pr ? `#${r.pr.number} ${r.pr.state}` : "none"} ? tag: ${r.tagPushed ? "pushed" : "absent"} ? Release: ${r.releaseExists ? "exists" : "absent"}`,
|
|
32543
33664
|
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
32544
33665
|
` - npm @mutmutco/cli: ${r.npmVersion}`,
|
|
32545
33666
|
` - next: ${r.next}`,
|
|
@@ -32555,13 +33676,13 @@ async function runHotfixSub(sub, body, json, render) {
|
|
|
32555
33676
|
return failGraceful(`hotfix ${sub}: ${e.message}`);
|
|
32556
33677
|
}
|
|
32557
33678
|
}
|
|
32558
|
-
var hotfixCmd = program2.command("hotfix").description("stepwise hotfix orchestrator: start
|
|
33679
|
+
var hotfixCmd = program2.command("hotfix").description("stepwise hotfix orchestrator: start ? release, with status (bare command prints the dry-run plan; no back-merge ? #839)").option("--json", "machine-readable output").option("--apply", "not a verb; use the hotfix subcommands (start/release/status)").action(async (o) => {
|
|
32559
33680
|
try {
|
|
32560
33681
|
await requireFreshTrainCli("hotfix");
|
|
32561
33682
|
} catch (e) {
|
|
32562
33683
|
return fail(`hotfix: ${e.message}`);
|
|
32563
33684
|
}
|
|
32564
|
-
if (o.apply) return fail("hotfix: use the stepwise subcommands
|
|
33685
|
+
if (o.apply) return fail("hotfix: use the stepwise subcommands ? mmi-cli hotfix start --from <pr#|sha> ? release <vX.Y.Z> ? status [vX.Y.Z]");
|
|
32565
33686
|
const steps = trainPlan("hotfix");
|
|
32566
33687
|
console.log(o.json ? JSON.stringify({ command: "hotfix", steps }, null, 2) : renderSteps("mmi-cli hotfix: dry-run plan", steps));
|
|
32567
33688
|
});
|
|
@@ -32576,7 +33697,7 @@ ci.command("audit").description("read-only fleet scan: gate workflow, ruleset co
|
|
|
32576
33697
|
else console.log(renderCiAuditText(report));
|
|
32577
33698
|
if (!report.ok) process.exitCode = 1;
|
|
32578
33699
|
});
|
|
32579
|
-
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it
|
|
33700
|
+
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it ? keeps its required contexts (master role required)").action(async (o) => {
|
|
32580
33701
|
if (o.apply && o.parkRuleset) {
|
|
32581
33702
|
return fail("ci reconcile: --apply and --park-ruleset ask for opposite things; pass one");
|
|
32582
33703
|
}
|
|
@@ -32599,7 +33720,7 @@ ci.command("reconcile").description("audit + optionally apply merge settings and
|
|
|
32599
33720
|
${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${r.errors.length ? ` errors=[${r.errors.join("; ")}]` : ""}`);
|
|
32600
33721
|
}
|
|
32601
33722
|
} else {
|
|
32602
|
-
console.log("\nDry-run
|
|
33723
|
+
console.log("\nDry-run ? re-run with --apply to patch merge settings and activate product rulesets (master-admin).");
|
|
32603
33724
|
}
|
|
32604
33725
|
}
|
|
32605
33726
|
const applyFailed = applyResults.some((result) => result.errors.length > 0 || result.postApply?.state === "failed");
|
|
@@ -32627,7 +33748,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
|
|
|
32627
33748
|
if (!a.train) process.exitCode = 1;
|
|
32628
33749
|
return;
|
|
32629
33750
|
}
|
|
32630
|
-
console.log(`${a.repo}: @${a.login} is ${a.role}
|
|
33751
|
+
console.log(`${a.repo}: @${a.login} is ${a.role} ? train ${a.train ? "AUTHORIZED" : "not authorized"}${a.hubTrainMasterOnly ? " (Hub train is master-only)" : ""}`);
|
|
32631
33752
|
if (!a.train) process.exitCode = 1;
|
|
32632
33753
|
});
|
|
32633
33754
|
access.command("audit").description("audit collaborator roles + train-branch push allowlists vs the locked state; read-only, emits gh remediation, never applies").option("--json", "machine-readable output").option("--repo <owner/repo>", "audit a single repo instead of the whole org").option("--class <class>", "repo class for --repo (deployable | content)", "deployable").action(async () => {
|
|
@@ -32652,19 +33773,19 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
32652
33773
|
targets = resolution.targets;
|
|
32653
33774
|
}
|
|
32654
33775
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
32655
|
-
const fileMatrix = (0,
|
|
33776
|
+
const fileMatrix = (0, import_node_fs39.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs39.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
32656
33777
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
32657
33778
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
32658
|
-
const fileContracts = (0,
|
|
33779
|
+
const fileContracts = (0, import_node_fs39.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs39.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
32659
33780
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
32660
|
-
const sanctioned = (0,
|
|
33781
|
+
const sanctioned = (0, import_node_fs39.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs39.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
32661
33782
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
32662
33783
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
32663
33784
|
if (!report.ok) process.exitCode = 1;
|
|
32664
33785
|
});
|
|
32665
|
-
access.command("capabilities").description("enumerate your effective vault reach
|
|
33786
|
+
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)));
|
|
32666
33787
|
var isWin2 = process.platform === "win32";
|
|
32667
|
-
program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft
|
|
33788
|
+
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", "eager version/plugin-heal (env repairs only ? never the repo working tree) with upfront notice when stale; silent when healthy (#1871)").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) => {
|
|
32668
33789
|
if (opts.guide) {
|
|
32669
33790
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
32670
33791
|
return;
|
|
@@ -32689,16 +33810,16 @@ function directoryBytes(path2) {
|
|
|
32689
33810
|
let total = 0;
|
|
32690
33811
|
let entries;
|
|
32691
33812
|
try {
|
|
32692
|
-
entries = (0,
|
|
33813
|
+
entries = (0, import_node_fs39.readdirSync)(path2, { withFileTypes: true });
|
|
32693
33814
|
} catch {
|
|
32694
33815
|
return 0;
|
|
32695
33816
|
}
|
|
32696
33817
|
for (const entry of entries) {
|
|
32697
|
-
const child2 = (0,
|
|
33818
|
+
const child2 = (0, import_node_path38.join)(path2, entry.name);
|
|
32698
33819
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
32699
33820
|
else {
|
|
32700
33821
|
try {
|
|
32701
|
-
total += (0,
|
|
33822
|
+
total += (0, import_node_fs39.statSync)(child2).size;
|
|
32702
33823
|
} catch {
|
|
32703
33824
|
}
|
|
32704
33825
|
}
|
|
@@ -32706,25 +33827,25 @@ function directoryBytes(path2) {
|
|
|
32706
33827
|
return total;
|
|
32707
33828
|
}
|
|
32708
33829
|
function listDirEntries(dir) {
|
|
32709
|
-
return (0,
|
|
33830
|
+
return (0, import_node_fs39.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
32710
33831
|
}
|
|
32711
33832
|
function readInstalledPluginRefs(configRoot) {
|
|
32712
33833
|
const p = installedPluginsPathForConfig(configRoot);
|
|
32713
|
-
if (!(0,
|
|
33834
|
+
if (!(0, import_node_fs39.existsSync)(p)) return [];
|
|
32714
33835
|
try {
|
|
32715
|
-
return installedPluginPaths((0,
|
|
33836
|
+
return installedPluginPaths((0, import_node_fs39.readFileSync)(p, "utf8"));
|
|
32716
33837
|
} catch {
|
|
32717
33838
|
return null;
|
|
32718
33839
|
}
|
|
32719
33840
|
}
|
|
32720
33841
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
32721
33842
|
return {
|
|
32722
|
-
exists: (p) => (0,
|
|
32723
|
-
listVersionDirs: (root) => (0,
|
|
33843
|
+
exists: (p) => (0, import_node_fs39.existsSync)(p),
|
|
33844
|
+
listVersionDirs: (root) => (0, import_node_fs39.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
32724
33845
|
dirBytes,
|
|
32725
|
-
listStagingDirs: (root) => (0,
|
|
33846
|
+
listStagingDirs: (root) => (0, import_node_fs39.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
32726
33847
|
try {
|
|
32727
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
33848
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path38.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs39.statSync)(p).mtimeMs) };
|
|
32728
33849
|
} catch {
|
|
32729
33850
|
return { name: d.name, mtimeMs: Date.now() };
|
|
32730
33851
|
}
|
|
@@ -32738,10 +33859,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
32738
33859
|
return {
|
|
32739
33860
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
32740
33861
|
mtimeMs: (name) => {
|
|
32741
|
-
const p = (0,
|
|
32742
|
-
if (!(0,
|
|
33862
|
+
const p = (0, import_node_path38.join)(stagingRoot, name);
|
|
33863
|
+
if (!(0, import_node_fs39.existsSync)(p)) return null;
|
|
32743
33864
|
try {
|
|
32744
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
33865
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs39.statSync)(q).mtimeMs);
|
|
32745
33866
|
} catch {
|
|
32746
33867
|
return null;
|
|
32747
33868
|
}
|
|
@@ -32761,13 +33882,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
32761
33882
|
return;
|
|
32762
33883
|
}
|
|
32763
33884
|
const plan = buildPluginCachePlan(
|
|
32764
|
-
(0,
|
|
33885
|
+
(0, import_node_os15.homedir)(),
|
|
32765
33886
|
running,
|
|
32766
33887
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
32767
33888
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
32768
33889
|
);
|
|
32769
33890
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
32770
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
33891
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs39.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
32771
33892
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
32772
33893
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
32773
33894
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -32775,7 +33896,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
32775
33896
|
});
|
|
32776
33897
|
program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
|
|
32777
33898
|
if (isInsideRepoSubdir(process.cwd())) {
|
|
32778
|
-
console.error("[mmi-hook] plugin session-start: cwd is a repository SUBDIRECTORY
|
|
33899
|
+
console.error("[mmi-hook] plugin session-start: cwd is a repository SUBDIRECTORY ? skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
|
|
32779
33900
|
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
|
|
32780
33901
|
return;
|
|
32781
33902
|
}
|
|
@@ -32788,7 +33909,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
32788
33909
|
const bannerIo = measured.io;
|
|
32789
33910
|
const { parallel, sequential } = buildSessionStartPlan({
|
|
32790
33911
|
// whoami (#879): surface the resolved human so agents act --for them without asking. Silent
|
|
32791
|
-
// when unknown
|
|
33912
|
+
// when unknown ? a missing gh login must not noise or fail the banner.
|
|
32792
33913
|
whoami: async (io) => {
|
|
32793
33914
|
const report = await resolveWhoami({
|
|
32794
33915
|
hubSession: async () => hubAuthSession({ baseUrl: (await loadConfig()).sagaApiUrl ?? defaultHubUrl(), githubToken }),
|
|
@@ -32799,15 +33920,15 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
32799
33920
|
const authority = authorityLine(report);
|
|
32800
33921
|
if (authority) io.log(authority);
|
|
32801
33922
|
},
|
|
32802
|
-
// command-ladder hint (#2609): compact gh
|
|
32803
|
-
// not after a denied gh call. <1KB, pure in-memory
|
|
33923
|
+
// command-ladder hint (#2609): compact gh?mmi-cli cheat sheet so agents reach for mmi-cli first,
|
|
33924
|
+
// not after a denied gh call. <1KB, pure in-memory ? no network, no fs, fail-soft.
|
|
32804
33925
|
commandLadderHint: async (io) => {
|
|
32805
33926
|
const hint = commandLadderHint();
|
|
32806
33927
|
if (hint) io.log(hint);
|
|
32807
33928
|
},
|
|
32808
33929
|
// stale worktrees: fast LOCAL-only git check (worktree list + branch -vv gone status). Flags leaked
|
|
32809
33930
|
// worktrees / merged-but-undeleted branches so the session never starts atop leftover state. No
|
|
32810
|
-
// network
|
|
33931
|
+
// network ? never the ~20s `gh pr list` path of `worktree list --stale`. Fail-soft.
|
|
32811
33932
|
staleWorktrees: async (io) => {
|
|
32812
33933
|
const line = await gatherStaleWorktreeWarning();
|
|
32813
33934
|
if (line) io.log(line);
|
|
@@ -32820,7 +33941,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
32820
33941
|
const line = localTrainSyncBannerLine(result);
|
|
32821
33942
|
if (line) io.log(line);
|
|
32822
33943
|
},
|
|
32823
|
-
// #3485 item 7: the ONLY lane that throttles the npm read
|
|
33944
|
+
// #3485 item 7: the ONLY lane that throttles the npm read ? it runs on every session start, on a
|
|
32824
33945
|
// blocking hook. Every interactive lane still reads live.
|
|
32825
33946
|
doctor: async (io) => {
|
|
32826
33947
|
await runDoctorClean({ banner: true }, io, mmiDoctorDeps({ throttleReleasedRead: true }));
|
|
@@ -32830,7 +33951,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
32830
33951
|
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
32831
33952
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
32832
33953
|
if (worktreeBanner) {
|
|
32833
|
-
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn:
|
|
33954
|
+
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process18.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
32834
33955
|
bannerIo.log(worktreeBanner);
|
|
32835
33956
|
}
|
|
32836
33957
|
if (isLinkedWorktree(process.cwd())) {
|