@mutmutco/cli 3.33.0 → 3.35.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 +554 -288
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3408,8 +3408,8 @@ function useColor() {
|
|
|
3408
3408
|
var program = new Command();
|
|
3409
3409
|
|
|
3410
3410
|
// src/index.ts
|
|
3411
|
-
var
|
|
3412
|
-
var
|
|
3411
|
+
var import_promises7 = require("node:fs/promises");
|
|
3412
|
+
var import_node_fs28 = require("node:fs");
|
|
3413
3413
|
var import_node_child_process12 = require("node:child_process");
|
|
3414
3414
|
|
|
3415
3415
|
// src/cli-shared.ts
|
|
@@ -4475,7 +4475,7 @@ function trackedPathStatus(c, realPath, repoAnchor) {
|
|
|
4475
4475
|
return null;
|
|
4476
4476
|
}
|
|
4477
4477
|
}
|
|
4478
|
-
function rootScratchDirSnapshot(root,
|
|
4478
|
+
function rootScratchDirSnapshot(root, readdir2, stat2) {
|
|
4479
4479
|
try {
|
|
4480
4480
|
const rootStat = stat2(root);
|
|
4481
4481
|
let mtimeMs = rootStat.mtimeMs;
|
|
@@ -4483,7 +4483,7 @@ function rootScratchDirSnapshot(root, readdir, stat2) {
|
|
|
4483
4483
|
const stack = [root];
|
|
4484
4484
|
while (stack.length) {
|
|
4485
4485
|
const current = stack.pop();
|
|
4486
|
-
for (const ent of
|
|
4486
|
+
for (const ent of readdir2(current, { withFileTypes: true })) {
|
|
4487
4487
|
const child2 = (0, import_node_path4.join)(current, ent.name);
|
|
4488
4488
|
if (ent.isSymbolicLink?.()) return null;
|
|
4489
4489
|
try {
|
|
@@ -4503,13 +4503,13 @@ function rootScratchDirSnapshot(root, readdir, stat2) {
|
|
|
4503
4503
|
}
|
|
4504
4504
|
}
|
|
4505
4505
|
function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
4506
|
-
const
|
|
4506
|
+
const readdir2 = deps.readdir ?? import_node_fs6.readdirSync;
|
|
4507
4507
|
const stat2 = deps.stat ?? import_node_fs6.statSync;
|
|
4508
|
-
const
|
|
4508
|
+
const readFile6 = deps.readFile ?? import_node_fs6.readFileSync;
|
|
4509
4509
|
const plansRoot = (0, import_node_path4.join)(repoRoot2, "plans");
|
|
4510
4510
|
const rootScratchFiles = [];
|
|
4511
4511
|
try {
|
|
4512
|
-
for (const ent of
|
|
4512
|
+
for (const ent of readdir2(repoRoot2, { withFileTypes: true })) {
|
|
4513
4513
|
const isDir = ent.isDirectory();
|
|
4514
4514
|
const isFile = ent.isFile();
|
|
4515
4515
|
if (!(isDir && ROOT_SCRATCH_DIRS.has(ent.name)) && !(isFile && ROOT_SCRATCH_FILE_PREFIXES.some((prefix) => ent.name.startsWith(prefix)))) {
|
|
@@ -4518,7 +4518,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
|
4518
4518
|
const full = (0, import_node_path4.join)(repoRoot2, ent.name);
|
|
4519
4519
|
try {
|
|
4520
4520
|
if (isDir) {
|
|
4521
|
-
const snap = rootScratchDirSnapshot(full,
|
|
4521
|
+
const snap = rootScratchDirSnapshot(full, readdir2, stat2);
|
|
4522
4522
|
if (snap) rootScratchFiles.push({ path: full, dir: repoRoot2, name: ent.name, mtimeMs: snap.mtimeMs, bytes: snap.bytes, kind: "dir" });
|
|
4523
4523
|
} else {
|
|
4524
4524
|
const st = stat2(full);
|
|
@@ -4531,12 +4531,12 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
|
4531
4531
|
}
|
|
4532
4532
|
const planMdFiles = [];
|
|
4533
4533
|
try {
|
|
4534
|
-
for (const ent of
|
|
4534
|
+
for (const ent of readdir2(plansRoot, { withFileTypes: true })) {
|
|
4535
4535
|
if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
|
|
4536
4536
|
const full = (0, import_node_path4.join)(plansRoot, ent.name);
|
|
4537
4537
|
try {
|
|
4538
4538
|
const st = stat2(full);
|
|
4539
|
-
const raw =
|
|
4539
|
+
const raw = readFile6(full, "utf8");
|
|
4540
4540
|
planMdFiles.push({ path: full, dir: plansRoot, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size, kind: "file", hash: hashContent(normalizeEol(raw)) });
|
|
4541
4541
|
} catch {
|
|
4542
4542
|
}
|
|
@@ -4545,7 +4545,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
|
4545
4545
|
}
|
|
4546
4546
|
let planMeta = {};
|
|
4547
4547
|
try {
|
|
4548
|
-
planMeta = parseMeta(
|
|
4548
|
+
planMeta = parseMeta(readFile6((0, import_node_path4.join)(plansRoot, ".plan-meta.json"), "utf8"));
|
|
4549
4549
|
} catch {
|
|
4550
4550
|
planMeta = {};
|
|
4551
4551
|
}
|
|
@@ -4553,7 +4553,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
|
|
|
4553
4553
|
const syncQueueSlugs = (() => {
|
|
4554
4554
|
let queueRaw;
|
|
4555
4555
|
try {
|
|
4556
|
-
queueRaw =
|
|
4556
|
+
queueRaw = readFile6((0, import_node_path4.join)(plansRoot, ".sync-queue.json"), "utf8");
|
|
4557
4557
|
} catch (e) {
|
|
4558
4558
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
4559
4559
|
return code === "ENOENT" || code === "ENOTDIR" ? /* @__PURE__ */ new Set() : null;
|
|
@@ -5062,7 +5062,7 @@ async function fetchOrgConfig(deps) {
|
|
|
5062
5062
|
}
|
|
5063
5063
|
}
|
|
5064
5064
|
async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
|
|
5065
|
-
if (!deps.baseUrl) return { ok: false, status: 0, body: null, error: "no Hub API URL (set MMI_HUB_URL or use a current MMI CLI/plugin build)" };
|
|
5065
|
+
if (!deps.baseUrl) return { ok: false, status: 0, body: null, error: "no Hub API URL (set MMI_HUB_URL or use a current MMI CLI/plugin build)", unreachable: "no-base-url" };
|
|
5066
5066
|
const token = await deps.token();
|
|
5067
5067
|
if (!token) return { ok: false, status: 0, body: null, error: "no Hub session token (run `gh auth login`)" };
|
|
5068
5068
|
const timeoutMs = opts.timeoutMs ?? deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS;
|
|
@@ -5081,7 +5081,7 @@ async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
|
|
|
5081
5081
|
}
|
|
5082
5082
|
return { ok: res.ok, status: res.status, body };
|
|
5083
5083
|
} catch (e) {
|
|
5084
|
-
return { ok: false, status: 0, body: null, error: e.message };
|
|
5084
|
+
return { ok: false, status: 0, body: null, error: e.message, unreachable: "transport" };
|
|
5085
5085
|
}
|
|
5086
5086
|
}
|
|
5087
5087
|
async function registerProject(payload, deps) {
|
|
@@ -5099,6 +5099,11 @@ async function setDeployCoords(slug, payload, deps) {
|
|
|
5099
5099
|
async function recordDocsAudit(verdict, deps) {
|
|
5100
5100
|
return postJson("/docs-audit/record", { ...verdict }, deps);
|
|
5101
5101
|
}
|
|
5102
|
+
async function postSchedulesLift(repo, schedules, deps) {
|
|
5103
|
+
const res = await postJson("/schedules/lift", { repo, schedules }, deps);
|
|
5104
|
+
if (!res.ok && res.status === 404) return { ...res, unreachable: "route-absent" };
|
|
5105
|
+
return res;
|
|
5106
|
+
}
|
|
5102
5107
|
async function tenantControl(payload, deps) {
|
|
5103
5108
|
return postJson("/tenant-control", payload, deps, "POST", { noRetry: true });
|
|
5104
5109
|
}
|
|
@@ -8919,6 +8924,14 @@ function defaultWorktreePath(repoRoot2, branch) {
|
|
|
8919
8924
|
const safe = branch.replace(/[/\\]+/g, "-");
|
|
8920
8925
|
return (0, import_node_path11.join)((0, import_node_path11.dirname)(repoRoot2), "mmi-worktrees", safe);
|
|
8921
8926
|
}
|
|
8927
|
+
async function primaryCheckoutRootOf(git) {
|
|
8928
|
+
try {
|
|
8929
|
+
const out = (await git(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
|
|
8930
|
+
return out ? (0, import_node_path11.dirname)(out) : void 0;
|
|
8931
|
+
} catch {
|
|
8932
|
+
return void 0;
|
|
8933
|
+
}
|
|
8934
|
+
}
|
|
8922
8935
|
function resolveWorktreeBase(from, remote) {
|
|
8923
8936
|
const remotePrefix = `${remote}/`;
|
|
8924
8937
|
const fetchBranch = from.startsWith(remotePrefix) ? from.slice(remotePrefix.length) : void 0;
|
|
@@ -9017,7 +9030,7 @@ function commandLadderHint() {
|
|
|
9017
9030
|
}
|
|
9018
9031
|
|
|
9019
9032
|
// src/index.ts
|
|
9020
|
-
var
|
|
9033
|
+
var import_node_path25 = require("node:path");
|
|
9021
9034
|
|
|
9022
9035
|
// src/merge-ci-policy.ts
|
|
9023
9036
|
function resolveMergeCiPolicy(input) {
|
|
@@ -9307,6 +9320,27 @@ function workflowTriggersPullRequest(yaml) {
|
|
|
9307
9320
|
if (!onBlock) return false;
|
|
9308
9321
|
return /\bpull_request\b/.test(onBlock);
|
|
9309
9322
|
}
|
|
9323
|
+
function workflowTriggersSchedule(yaml) {
|
|
9324
|
+
const onBlock = extractOnBlock(yaml);
|
|
9325
|
+
if (!onBlock) return false;
|
|
9326
|
+
if (/^(["']?)on\1:/.test(onBlock)) {
|
|
9327
|
+
const value = onBlock.replace(/^(["']?)on\1:\s*/, "");
|
|
9328
|
+
if (/^\s*\[/.test(value)) {
|
|
9329
|
+
return /(^|[,\s\[])["']?schedule["']?([,\s\]]|$)/.test(value);
|
|
9330
|
+
}
|
|
9331
|
+
return /\bschedule["']?\s*:/.test(value);
|
|
9332
|
+
}
|
|
9333
|
+
const content = onBlock.split(/\r?\n/).filter((l) => {
|
|
9334
|
+
const t = l.trim();
|
|
9335
|
+
return t.length > 0 && !t.startsWith("#");
|
|
9336
|
+
});
|
|
9337
|
+
if (!content.length) return false;
|
|
9338
|
+
const minIndent = Math.min(...content.map((l) => l.match(/^(\s*)/)?.[1].length ?? 0));
|
|
9339
|
+
return content.some((line) => {
|
|
9340
|
+
const indent = line.match(/^(\s*)/)?.[1].length ?? 0;
|
|
9341
|
+
return indent === minIndent && /^\s*schedule\s*:/.test(line);
|
|
9342
|
+
});
|
|
9343
|
+
}
|
|
9310
9344
|
function workflowReportsPrChecks(yaml) {
|
|
9311
9345
|
const onBlock = extractOnBlock(yaml);
|
|
9312
9346
|
if (!onBlock) return false;
|
|
@@ -9322,6 +9356,9 @@ function extractOnBlock(yaml) {
|
|
|
9322
9356
|
if (/^(["']?)on\1:\s*$/.test(line)) {
|
|
9323
9357
|
inOn = true;
|
|
9324
9358
|
onIndent = 0;
|
|
9359
|
+
} else if (/^(["']?)on\1:\s+#/.test(line)) {
|
|
9360
|
+
inOn = true;
|
|
9361
|
+
onIndent = 0;
|
|
9325
9362
|
} else if (/^(["']?)on\1:\s+\S/.test(line)) {
|
|
9326
9363
|
return line;
|
|
9327
9364
|
}
|
|
@@ -9468,6 +9505,63 @@ function planManagedGitignore(current) {
|
|
|
9468
9505
|
return { changed, content, added, removed };
|
|
9469
9506
|
}
|
|
9470
9507
|
|
|
9508
|
+
// src/gate-budget.ts
|
|
9509
|
+
var BLESSED_RUN_WITH_BUDGET_SHA = "b6ec6e4eafcda73b543fc0754a6356551fe762a1";
|
|
9510
|
+
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
9511
|
+
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
9512
|
+
var FULL_SHA = /^[0-9a-f]{40}$/;
|
|
9513
|
+
var MAX_SECONDS_CEILING = 1800;
|
|
9514
|
+
function isGateWorkflowPath(path2) {
|
|
9515
|
+
const base = path2.split(/[/\\]/).pop() ?? path2;
|
|
9516
|
+
return /^(?:.+[-_.])?gate\.ya?ml$/i.test(base);
|
|
9517
|
+
}
|
|
9518
|
+
function checkGateBudget(files, opts = {}) {
|
|
9519
|
+
const failures = [];
|
|
9520
|
+
const notes = [];
|
|
9521
|
+
for (const { path: path2, body } of files) {
|
|
9522
|
+
const uses = [...body.matchAll(/^\s*-?\s*uses:\s*(\S+)/gm)].map((m) => m[1].replace(/^['"]|['"]$/g, "")).filter((ref) => ref.includes("run-with-budget"));
|
|
9523
|
+
if (uses.length === 0) {
|
|
9524
|
+
failures.push(`${path2}: check command does not run via run-with-budget \u2014 the gate has no wall-clock ceiling`);
|
|
9525
|
+
continue;
|
|
9526
|
+
}
|
|
9527
|
+
for (const ref of uses) {
|
|
9528
|
+
if (ref === LOCAL_USE) {
|
|
9529
|
+
if (!opts.hubLocalAllowed) {
|
|
9530
|
+
failures.push(`${path2}: local ${LOCAL_USE} reference \u2014 consume the org-shared action pinned by SHA (mutmutco/MMI-Hub/.github/actions/run-with-budget@<full sha>)`);
|
|
9531
|
+
}
|
|
9532
|
+
continue;
|
|
9533
|
+
}
|
|
9534
|
+
const remote = REMOTE_USE.exec(ref);
|
|
9535
|
+
if (!remote) {
|
|
9536
|
+
failures.push(`${path2}: unrecognized run-with-budget reference '${ref}' \u2014 use mutmutco/MMI-Hub/.github/actions/run-with-budget@<full sha>`);
|
|
9537
|
+
continue;
|
|
9538
|
+
}
|
|
9539
|
+
if (!FULL_SHA.test(remote[1])) {
|
|
9540
|
+
failures.push(`${path2}: run-with-budget pinned to '${remote[1]}' \u2014 pin the full 40-hex commit SHA, not a branch or tag`);
|
|
9541
|
+
continue;
|
|
9542
|
+
}
|
|
9543
|
+
if (remote[1] !== BLESSED_RUN_WITH_BUDGET_SHA) {
|
|
9544
|
+
notes.push(`${path2}: run-with-budget pinned to ${remote[1].slice(0, 12)} (blessed ${BLESSED_RUN_WITH_BUDGET_SHA.slice(0, 12)}) \u2014 valid but outdated`);
|
|
9545
|
+
}
|
|
9546
|
+
}
|
|
9547
|
+
const ceilingLines = [...body.matchAll(/^\s*max-seconds:\s*(.+?)\s*$/gm)].map((m) => m[1].replace(/\s+#.*$/, ""));
|
|
9548
|
+
if (ceilingLines.length === 0) {
|
|
9549
|
+
failures.push(`${path2}: run-with-budget has no max-seconds input \u2014 the budget step guards nothing`);
|
|
9550
|
+
continue;
|
|
9551
|
+
}
|
|
9552
|
+
for (const raw of ceilingLines) {
|
|
9553
|
+
const literal = /^['"]?(\d+)['"]?$/.exec(raw);
|
|
9554
|
+
const seconds = literal ? Number(literal[1]) : NaN;
|
|
9555
|
+
if (!literal || seconds < 1) {
|
|
9556
|
+
failures.push(`${path2}: max-seconds '${raw}' is not a literal positive integer`);
|
|
9557
|
+
} else if (seconds > MAX_SECONDS_CEILING) {
|
|
9558
|
+
failures.push(`${path2}: max-seconds ${seconds} exceeds the org ceiling ${MAX_SECONDS_CEILING} \u2014 a budget that generous guards nothing; measure the gate and set a real ceiling`);
|
|
9559
|
+
}
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9562
|
+
return { ok: failures.length === 0, failures, notes };
|
|
9563
|
+
}
|
|
9564
|
+
|
|
9471
9565
|
// src/bootstrap-apply.ts
|
|
9472
9566
|
function parseOwnerRepo(repo) {
|
|
9473
9567
|
const trimmed = repo.trim();
|
|
@@ -9481,6 +9575,7 @@ function parseOwnerRepo(repo) {
|
|
|
9481
9575
|
return { owner, name, slug: name.toLowerCase(), fullName: `${owner}/${name}` };
|
|
9482
9576
|
}
|
|
9483
9577
|
var DEFAULT_GATE_CMD = "npm run check";
|
|
9578
|
+
var DEFAULT_GATE_MAX_SECONDS = "300";
|
|
9484
9579
|
var DEFAULT_GATE_PY_VERSION = "3.11";
|
|
9485
9580
|
var GATE_RUNTIME_DEFAULTS = {
|
|
9486
9581
|
node: { cmd: DEFAULT_GATE_CMD, install: "npm ci" },
|
|
@@ -9494,7 +9589,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
|
|
|
9494
9589
|
GATE_INSTALL_CMD: rt.install,
|
|
9495
9590
|
GATE_WORKDIR: ".",
|
|
9496
9591
|
GATE_CACHE_DEP_PATH: "package-lock.json",
|
|
9497
|
-
GATE_PY_VERSION: DEFAULT_GATE_PY_VERSION
|
|
9592
|
+
GATE_PY_VERSION: DEFAULT_GATE_PY_VERSION,
|
|
9593
|
+
GATE_MAX_SECONDS: DEFAULT_GATE_MAX_SECONDS,
|
|
9594
|
+
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
9595
|
+
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA
|
|
9498
9596
|
};
|
|
9499
9597
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
9500
9598
|
if (track === "trunk") {
|
|
@@ -9540,6 +9638,8 @@ function gateConfigToVars(gate) {
|
|
|
9540
9638
|
if (typeof gate.workdir === "string" && gate.workdir.trim()) out.GATE_WORKDIR = gate.workdir;
|
|
9541
9639
|
if (typeof gate.cacheDepPath === "string" && gate.cacheDepPath.trim()) out.GATE_CACHE_DEP_PATH = gate.cacheDepPath;
|
|
9542
9640
|
if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
|
|
9641
|
+
const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
|
|
9642
|
+
if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
|
|
9543
9643
|
return out;
|
|
9544
9644
|
}
|
|
9545
9645
|
function seedMatchesDeployModel(seed, deployModel) {
|
|
@@ -9601,10 +9701,10 @@ function labelsToPrune(orgLabelNames) {
|
|
|
9601
9701
|
const org = new Set(orgLabelNames);
|
|
9602
9702
|
return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
|
|
9603
9703
|
}
|
|
9604
|
-
function resolveSeedContent(seed, vars,
|
|
9605
|
-
if (seed.source === "self") return
|
|
9704
|
+
function resolveSeedContent(seed, vars, readFile6) {
|
|
9705
|
+
if (seed.source === "self") return readFile6(seed.target);
|
|
9606
9706
|
if (seed.source.startsWith("seed:")) {
|
|
9607
|
-
const tmpl =
|
|
9707
|
+
const tmpl = readFile6(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
|
|
9608
9708
|
return tmpl == null ? null : renderSeed(tmpl, vars);
|
|
9609
9709
|
}
|
|
9610
9710
|
return null;
|
|
@@ -10020,11 +10120,22 @@ async function auditRepoCi(repo, deps) {
|
|
|
10020
10120
|
const allPaths = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
|
|
10021
10121
|
const prPaths = await filterPullRequestTriggered(deps, repo, baseBranch, allPaths);
|
|
10022
10122
|
const missing = [];
|
|
10123
|
+
const gateBodies = [];
|
|
10023
10124
|
for (const path2 of prPaths) {
|
|
10024
10125
|
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
10025
10126
|
if (body == null) continue;
|
|
10127
|
+
if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
|
|
10026
10128
|
if (!/^\s*concurrency:/m.test(body) || !/cancel-in-progress:\s*true/.test(body)) missing.push(path2);
|
|
10027
10129
|
}
|
|
10130
|
+
if (gateBodies.length > 0) {
|
|
10131
|
+
const budget = checkGateBudget(gateBodies, { hubLocalAllowed: repoClass === "hub" });
|
|
10132
|
+
checks.push({
|
|
10133
|
+
ok: budget.ok,
|
|
10134
|
+
label: "gate runs under the org wall-clock budget (#3178)",
|
|
10135
|
+
detail: [...budget.failures, ...budget.notes].join("; ") || void 0,
|
|
10136
|
+
remediation: budget.ok ? void 0 : `Wrap the gate's check command in mutmutco/MMI-Hub/.github/actions/run-with-budget@${BLESSED_RUN_WITH_BUDGET_SHA} with a measured max-seconds \u2014 see skills/bootstrap/seeds/gate.template.yml`
|
|
10137
|
+
});
|
|
10138
|
+
}
|
|
10028
10139
|
if (prPaths.length > 0) {
|
|
10029
10140
|
checks.push({
|
|
10030
10141
|
ok: missing.length === 0,
|
|
@@ -10378,6 +10489,14 @@ function earlyExitGraceMs() {
|
|
|
10378
10489
|
}
|
|
10379
10490
|
return EARLY_EXIT_GRACE_MS;
|
|
10380
10491
|
}
|
|
10492
|
+
var HEALTH_POLL_INTERVAL_MS = 1e3;
|
|
10493
|
+
function healthPollIntervalMs() {
|
|
10494
|
+
if (process.env.NODE_ENV === "test") {
|
|
10495
|
+
const testOverride = Number(process.env.MMI_CLI_TEST_HEALTH_POLL_MS);
|
|
10496
|
+
if (Number.isFinite(testOverride) && testOverride >= 10) return testOverride;
|
|
10497
|
+
}
|
|
10498
|
+
return HEALTH_POLL_INTERVAL_MS;
|
|
10499
|
+
}
|
|
10381
10500
|
function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
|
|
10382
10501
|
return new Promise((resolve5, reject) => {
|
|
10383
10502
|
let settled = false;
|
|
@@ -10793,7 +10912,7 @@ function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
|
10793
10912
|
async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
10794
10913
|
await killTree(state.pid);
|
|
10795
10914
|
if (state.teardown?.command.trim()) {
|
|
10796
|
-
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, timeoutMs);
|
|
10915
|
+
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
|
|
10797
10916
|
}
|
|
10798
10917
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
10799
10918
|
(0, import_node_fs14.rmSync)(path2, { force: true });
|
|
@@ -10834,7 +10953,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
|
|
|
10834
10953
|
} catch (e) {
|
|
10835
10954
|
last = e.message;
|
|
10836
10955
|
}
|
|
10837
|
-
await new Promise((resolve5) => setTimeout(resolve5,
|
|
10956
|
+
await new Promise((resolve5) => setTimeout(resolve5, healthPollIntervalMs()));
|
|
10838
10957
|
}
|
|
10839
10958
|
throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
|
|
10840
10959
|
}
|
|
@@ -11275,29 +11394,6 @@ function resolveIssueTitle(input, deps) {
|
|
|
11275
11394
|
noun: "title"
|
|
11276
11395
|
});
|
|
11277
11396
|
}
|
|
11278
|
-
var NORTH_STAR_BODY_PREFIX = "North Star:";
|
|
11279
|
-
var NORTH_STAR_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
11280
|
-
function normalizeNorthStarSlug(raw) {
|
|
11281
|
-
const slug = raw.trim();
|
|
11282
|
-
if (!slug || !NORTH_STAR_SLUG_RE.test(slug)) {
|
|
11283
|
-
throw new Error(`invalid North Star slug "${raw}" \u2014 expected kebab-case like agent-session-workflow`);
|
|
11284
|
-
}
|
|
11285
|
-
return slug;
|
|
11286
|
-
}
|
|
11287
|
-
function northStarLabel(slug) {
|
|
11288
|
-
return `northstar:${slug}`;
|
|
11289
|
-
}
|
|
11290
|
-
function appendNorthStarLine(body, slug) {
|
|
11291
|
-
const line = `${NORTH_STAR_BODY_PREFIX} ${slug}`;
|
|
11292
|
-
const existing = new RegExp(`^${NORTH_STAR_BODY_PREFIX}\\s+\\S+\\s*$`, "m");
|
|
11293
|
-
if (existing.test(body)) return body;
|
|
11294
|
-
const trimmed = body.replace(/\s+$/, "");
|
|
11295
|
-
return trimmed ? `${trimmed}
|
|
11296
|
-
|
|
11297
|
-
${line}
|
|
11298
|
-
` : `${line}
|
|
11299
|
-
`;
|
|
11300
|
-
}
|
|
11301
11397
|
|
|
11302
11398
|
// src/issue-view-json.ts
|
|
11303
11399
|
var DEFAULT_ISSUE_VIEW_FIELDS = "number,title,state,url,labels,author,assignees,milestone,body";
|
|
@@ -12345,6 +12441,8 @@ function parseVerifyBroker(stdout) {
|
|
|
12345
12441
|
}
|
|
12346
12442
|
|
|
12347
12443
|
// src/train-apply.ts
|
|
12444
|
+
var import_node_fs16 = require("node:fs");
|
|
12445
|
+
var import_node_path14 = require("node:path");
|
|
12348
12446
|
function resolveDeployModel2(meta, repo) {
|
|
12349
12447
|
const m = meta?.deployModel;
|
|
12350
12448
|
if (isDeployModel(m)) return m;
|
|
@@ -13188,6 +13286,34 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
13188
13286
|
}
|
|
13189
13287
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
13190
13288
|
}
|
|
13289
|
+
function readLocalGateWorkflows() {
|
|
13290
|
+
const dir = (0, import_node_path14.join)(".github", "workflows");
|
|
13291
|
+
let names;
|
|
13292
|
+
try {
|
|
13293
|
+
names = (0, import_node_fs16.readdirSync)(dir);
|
|
13294
|
+
} catch {
|
|
13295
|
+
return null;
|
|
13296
|
+
}
|
|
13297
|
+
const files = [];
|
|
13298
|
+
for (const name of names.filter(isGateWorkflowPath)) {
|
|
13299
|
+
try {
|
|
13300
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs16.readFileSync)((0, import_node_path14.join)(dir, name), "utf8") });
|
|
13301
|
+
} catch {
|
|
13302
|
+
}
|
|
13303
|
+
}
|
|
13304
|
+
return files;
|
|
13305
|
+
}
|
|
13306
|
+
function enforceGateBudget(deps, repo) {
|
|
13307
|
+
const files = (deps.readGateWorkflows ?? readLocalGateWorkflows)();
|
|
13308
|
+
if (files == null || files.length === 0) return;
|
|
13309
|
+
const budget = checkGateBudget(files, { hubLocalAllowed: repo.toLowerCase() === "mutmutco/mmi-hub" });
|
|
13310
|
+
for (const note of budget.notes) deps.warn?.(`gate budget: ${note}`);
|
|
13311
|
+
if (!budget.ok) {
|
|
13312
|
+
throw new Error(
|
|
13313
|
+
`release refused \u2014 the gate runs without the org wall-clock budget (#3178): ${budget.failures.join("; ")}. Wrap the check command in the pinned run-with-budget step (see skills/bootstrap/seeds/gate.template.yml in MMI-Hub), or run \`mmi-cli ci audit --repo ${repo}\` for the full picture.`
|
|
13314
|
+
);
|
|
13315
|
+
}
|
|
13316
|
+
}
|
|
13191
13317
|
async function preflight(deps, ctx, stage, meta) {
|
|
13192
13318
|
const model = resolveDeployModel2(meta, ctx.repo);
|
|
13193
13319
|
if (model === "content") {
|
|
@@ -13197,6 +13323,7 @@ async function preflight(deps, ctx, stage, meta) {
|
|
|
13197
13323
|
throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
|
|
13198
13324
|
}
|
|
13199
13325
|
await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
|
|
13326
|
+
enforceGateBudget(deps, ctx.repo);
|
|
13200
13327
|
return model;
|
|
13201
13328
|
}
|
|
13202
13329
|
async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix, realignMessage) {
|
|
@@ -15037,8 +15164,8 @@ function renderAccessReport(report) {
|
|
|
15037
15164
|
}
|
|
15038
15165
|
|
|
15039
15166
|
// src/docs-index-command.ts
|
|
15040
|
-
var
|
|
15041
|
-
var
|
|
15167
|
+
var import_node_fs17 = require("node:fs");
|
|
15168
|
+
var import_node_path15 = require("node:path");
|
|
15042
15169
|
var DOCS_INDEX_PATH = "docs/index.md";
|
|
15043
15170
|
function isRoutableDocsPath(relPath) {
|
|
15044
15171
|
const normalized = relPath.replace(/\\/g, "/");
|
|
@@ -15116,25 +15243,25 @@ function walkMarkdown(dir) {
|
|
|
15116
15243
|
const stack = [dir];
|
|
15117
15244
|
while (stack.length) {
|
|
15118
15245
|
const current = stack.pop();
|
|
15119
|
-
for (const entry of (0,
|
|
15120
|
-
const full = (0,
|
|
15246
|
+
for (const entry of (0, import_node_fs17.readdirSync)(current, { withFileTypes: true })) {
|
|
15247
|
+
const full = (0, import_node_path15.join)(current, entry.name);
|
|
15121
15248
|
if (entry.isDirectory()) {
|
|
15122
15249
|
stack.push(full);
|
|
15123
15250
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
15124
|
-
out.push((0,
|
|
15251
|
+
out.push((0, import_node_path15.relative)(dir, full).split(import_node_path15.sep).join("/"));
|
|
15125
15252
|
}
|
|
15126
15253
|
}
|
|
15127
15254
|
}
|
|
15128
15255
|
return out;
|
|
15129
15256
|
}
|
|
15130
15257
|
function createDocsIndexDeps(repoRoot2) {
|
|
15131
|
-
const docsDir = (0,
|
|
15132
|
-
const indexPath = (0,
|
|
15258
|
+
const docsDir = (0, import_node_path15.join)(repoRoot2, "docs");
|
|
15259
|
+
const indexPath = (0, import_node_path15.join)(repoRoot2, DOCS_INDEX_PATH);
|
|
15133
15260
|
return {
|
|
15134
|
-
listDocs: () => (0,
|
|
15135
|
-
readDoc: (relPath) => (0,
|
|
15136
|
-
readIndex: () => (0,
|
|
15137
|
-
writeIndex: (content) => (0,
|
|
15261
|
+
listDocs: () => (0, import_node_fs17.existsSync)(docsDir) ? walkMarkdown(docsDir).filter(isRoutableDocsPath).sort() : [],
|
|
15262
|
+
readDoc: (relPath) => (0, import_node_fs17.readFileSync)((0, import_node_path15.join)(docsDir, relPath), "utf8"),
|
|
15263
|
+
readIndex: () => (0, import_node_fs17.existsSync)(indexPath) ? (0, import_node_fs17.readFileSync)(indexPath, "utf8") : null,
|
|
15264
|
+
writeIndex: (content) => (0, import_node_fs17.writeFileSync)(indexPath, content, "utf8")
|
|
15138
15265
|
};
|
|
15139
15266
|
}
|
|
15140
15267
|
|
|
@@ -16061,8 +16188,8 @@ function writeError(res) {
|
|
|
16061
16188
|
}
|
|
16062
16189
|
|
|
16063
16190
|
// src/secrets-commands.ts
|
|
16064
|
-
var
|
|
16065
|
-
var
|
|
16191
|
+
var import_node_fs18 = require("node:fs");
|
|
16192
|
+
var import_node_path16 = require("node:path");
|
|
16066
16193
|
var import_node_os5 = require("node:os");
|
|
16067
16194
|
|
|
16068
16195
|
// src/project-runtime.ts
|
|
@@ -16186,18 +16313,18 @@ function collectMap(value, previous = []) {
|
|
|
16186
16313
|
return [...previous, value];
|
|
16187
16314
|
}
|
|
16188
16315
|
async function decryptRailsCredentials(input) {
|
|
16189
|
-
const appDir = (0,
|
|
16316
|
+
const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
|
|
16190
16317
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
16191
16318
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
16192
|
-
const credentialsPath = (0,
|
|
16193
|
-
const masterKeyPath = (0,
|
|
16319
|
+
const credentialsPath = (0, import_node_path16.resolve)(appDir, credentialsFile);
|
|
16320
|
+
const masterKeyPath = (0, import_node_path16.resolve)(appDir, masterKeyFile);
|
|
16194
16321
|
const env = {
|
|
16195
16322
|
...process.env,
|
|
16196
16323
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
16197
16324
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
16198
16325
|
};
|
|
16199
|
-
if ((0,
|
|
16200
|
-
env.RAILS_MASTER_KEY = (0,
|
|
16326
|
+
if ((0, import_node_fs18.existsSync)(masterKeyPath)) {
|
|
16327
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs18.readFileSync)(masterKeyPath, "utf8").trim();
|
|
16201
16328
|
}
|
|
16202
16329
|
const script = [
|
|
16203
16330
|
'require "json"',
|
|
@@ -16207,9 +16334,9 @@ async function decryptRailsCredentials(input) {
|
|
|
16207
16334
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
16208
16335
|
"puts JSON.generate(config.config)"
|
|
16209
16336
|
].join("\n");
|
|
16210
|
-
const scriptDir = (0,
|
|
16211
|
-
const scriptPath = (0,
|
|
16212
|
-
(0,
|
|
16337
|
+
const scriptDir = (0, import_node_fs18.mkdtempSync)((0, import_node_path16.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
|
|
16338
|
+
const scriptPath = (0, import_node_path16.join)(scriptDir, "decrypt.rb");
|
|
16339
|
+
(0, import_node_fs18.writeFileSync)(scriptPath, script, "utf8");
|
|
16213
16340
|
try {
|
|
16214
16341
|
const args = ["exec", "ruby", scriptPath];
|
|
16215
16342
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -16221,7 +16348,7 @@ async function decryptRailsCredentials(input) {
|
|
|
16221
16348
|
});
|
|
16222
16349
|
return JSON.parse(stdout);
|
|
16223
16350
|
} finally {
|
|
16224
|
-
(0,
|
|
16351
|
+
(0, import_node_fs18.rmSync)(scriptDir, { recursive: true, force: true });
|
|
16225
16352
|
}
|
|
16226
16353
|
}
|
|
16227
16354
|
async function readSecretStdin() {
|
|
@@ -16296,7 +16423,7 @@ function registerSecretsCommands(program3) {
|
|
|
16296
16423
|
secrets.command("org-catalog").description("MASTER-ONLY: set the _org/<provider>/* catalog declarations from a JSON file (#2244)").requiredOption("--file <path>", 'JSON file: { "entries": { "<provider>/<KEY>": {key,purpose,group,owner,provider,stages[],consumers[]} } }').action((o) => withSecrets(async (d) => {
|
|
16297
16424
|
let body;
|
|
16298
16425
|
try {
|
|
16299
|
-
body = (0,
|
|
16426
|
+
body = (0, import_node_fs18.readFileSync)((0, import_node_path16.resolve)(o.file), "utf8");
|
|
16300
16427
|
} catch (e) {
|
|
16301
16428
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
16302
16429
|
}
|
|
@@ -16399,7 +16526,7 @@ function registerSecretsCommands(program3) {
|
|
|
16399
16526
|
{
|
|
16400
16527
|
...d,
|
|
16401
16528
|
decryptRailsCredentials,
|
|
16402
|
-
removeFile: (path2) => (0,
|
|
16529
|
+
removeFile: (path2) => (0, import_node_fs18.unlinkSync)((0, import_node_path16.resolve)(o.appDir ?? process.cwd(), path2))
|
|
16403
16530
|
},
|
|
16404
16531
|
{
|
|
16405
16532
|
repo: o.repo,
|
|
@@ -16812,9 +16939,9 @@ function extractWorkflowCrons(yamlText) {
|
|
|
16812
16939
|
function workflowEntry(repo, workflowPath, yamlText) {
|
|
16813
16940
|
const crons = extractWorkflowCrons(yamlText);
|
|
16814
16941
|
if (!crons.length) return null;
|
|
16815
|
-
const
|
|
16942
|
+
const basename3 = workflowPath.split("/").pop() ?? workflowPath;
|
|
16816
16943
|
return {
|
|
16817
|
-
name: `${repo}/${
|
|
16944
|
+
name: `${repo}/${basename3.replace(/\.ya?ml$/, "")}`,
|
|
16818
16945
|
cadence: crons.join(" + "),
|
|
16819
16946
|
executor: "github-actions",
|
|
16820
16947
|
llm: llmFromHeader(yamlText),
|
|
@@ -16918,6 +17045,10 @@ function spliceDoc(docText, generatedSection) {
|
|
|
16918
17045
|
}
|
|
16919
17046
|
return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
|
|
16920
17047
|
}
|
|
17048
|
+
var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
|
|
17049
|
+
function isHarbourLlmLauncher(executor) {
|
|
17050
|
+
return HARBOUR_LLM_LAUNCHERS.has(executor);
|
|
17051
|
+
}
|
|
16921
17052
|
function splitCadence(cadence) {
|
|
16922
17053
|
return cadence.split("+").map((c) => c.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
16923
17054
|
}
|
|
@@ -16979,6 +17110,38 @@ function reconcileGithubActions(liveGithub, registry2, readRepos) {
|
|
|
16979
17110
|
function renderDrift(d) {
|
|
16980
17111
|
return `${d.class}: ${d.name} (${d.executor}) \u2014 ${d.detail}; remedy: ${d.remedy}`;
|
|
16981
17112
|
}
|
|
17113
|
+
function strayCronDrift(name) {
|
|
17114
|
+
return {
|
|
17115
|
+
class: "stray-cron",
|
|
17116
|
+
name,
|
|
17117
|
+
executor: "github-actions",
|
|
17118
|
+
detail: "workflow still carries on: schedule",
|
|
17119
|
+
remedy: "strip on: schedule \u2192 workflow_dispatch and let the harbour dispatcher fire on the registry cadence (docs/schedules.md rule 1)"
|
|
17120
|
+
};
|
|
17121
|
+
}
|
|
17122
|
+
function strayCronDrifts(workflows) {
|
|
17123
|
+
return workflows.filter((w) => workflowTriggersSchedule(w.yamlText)).map((w) => strayCronDrift(w.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
17124
|
+
}
|
|
17125
|
+
function unlauncheredLlmDrift(entry) {
|
|
17126
|
+
if (entry.llm !== "yes") return null;
|
|
17127
|
+
if (isHarbourLlmLauncher(entry.executor)) return null;
|
|
17128
|
+
const allowed = [...HARBOUR_LLM_LAUNCHERS].join(", ");
|
|
17129
|
+
return {
|
|
17130
|
+
class: "unlaunchered-llm",
|
|
17131
|
+
name: entry.name,
|
|
17132
|
+
executor: entry.executor,
|
|
17133
|
+
detail: `llm: yes with executor outside the harbour launcher set (${allowed})`,
|
|
17134
|
+
remedy: `move the lane to executor: ${allowed} and run it through the Hub harbour key (docs/schedules.md rule 4)`
|
|
17135
|
+
};
|
|
17136
|
+
}
|
|
17137
|
+
function unlauncheredLlmDrifts(entries) {
|
|
17138
|
+
const drifts = [];
|
|
17139
|
+
for (const e of entries) {
|
|
17140
|
+
const d = unlauncheredLlmDrift(e);
|
|
17141
|
+
if (d) drifts.push(d);
|
|
17142
|
+
}
|
|
17143
|
+
return drifts.sort((a, b) => a.name.localeCompare(b.name));
|
|
17144
|
+
}
|
|
16982
17145
|
function assembleReconciliation(githubEntries2, readRepos, registry2) {
|
|
16983
17146
|
if (registry2 === null) {
|
|
16984
17147
|
return {
|
|
@@ -17013,10 +17176,11 @@ async function listWorkflows(client, repo) {
|
|
|
17013
17176
|
}
|
|
17014
17177
|
}
|
|
17015
17178
|
async function repoWorkflowEntries(client, repo) {
|
|
17016
|
-
const
|
|
17179
|
+
const workflowsList = await listWorkflows(client, repo);
|
|
17017
17180
|
const entries = [];
|
|
17018
17181
|
const failures = [];
|
|
17019
|
-
|
|
17182
|
+
const workflows = [];
|
|
17183
|
+
for (const wf of workflowsList) {
|
|
17020
17184
|
if (wf?.state !== "active" || typeof wf.path !== "string" || !wf.path) continue;
|
|
17021
17185
|
if (!wf.path.startsWith(".github/workflows/")) continue;
|
|
17022
17186
|
try {
|
|
@@ -17026,6 +17190,9 @@ async function repoWorkflowEntries(client, repo) {
|
|
|
17026
17190
|
);
|
|
17027
17191
|
if (typeof contents?.content !== "string" || contents.encoding !== "base64") continue;
|
|
17028
17192
|
const text = Buffer.from(contents.content, "base64").toString("utf8");
|
|
17193
|
+
const basename3 = wf.path.split("/").pop() ?? wf.path;
|
|
17194
|
+
const name = `${repo}/${basename3.replace(/\.ya?ml$/, "")}`;
|
|
17195
|
+
workflows.push({ name, yamlText: text });
|
|
17029
17196
|
const entry = workflowEntry(repo, wf.path, text);
|
|
17030
17197
|
if (entry) entries.push(entry);
|
|
17031
17198
|
} catch (e) {
|
|
@@ -17034,19 +17201,20 @@ async function repoWorkflowEntries(client, repo) {
|
|
|
17034
17201
|
else throw e;
|
|
17035
17202
|
}
|
|
17036
17203
|
}
|
|
17037
|
-
return { entries, failures };
|
|
17204
|
+
return { entries, failures, workflows };
|
|
17038
17205
|
}
|
|
17039
17206
|
async function githubEntries(client) {
|
|
17040
17207
|
const entries = [];
|
|
17041
17208
|
const incomplete = [];
|
|
17042
17209
|
const drift = [];
|
|
17043
17210
|
const readRepos = [];
|
|
17211
|
+
const workflows = [];
|
|
17044
17212
|
let repos;
|
|
17045
17213
|
try {
|
|
17046
17214
|
const listing = await client.restPaginate(`/orgs/${ORG}/repos?per_page=100`);
|
|
17047
17215
|
repos = listing.filter((r) => typeof r?.name === "string" && r.archived !== true).map((r) => r.name).sort();
|
|
17048
17216
|
} catch (e) {
|
|
17049
|
-
return { entries, incomplete: [`github: could not list ${ORG} repos \u2014 ${e.message}`], drift, readRepos };
|
|
17217
|
+
return { entries, incomplete: [`github: could not list ${ORG} repos \u2014 ${e.message}`], drift, reconciliation: [], readRepos };
|
|
17050
17218
|
}
|
|
17051
17219
|
const results = await Promise.all(
|
|
17052
17220
|
repos.map(async (repo) => {
|
|
@@ -17062,12 +17230,15 @@ async function githubEntries(client) {
|
|
|
17062
17230
|
else {
|
|
17063
17231
|
readRepos.push(r.repo);
|
|
17064
17232
|
entries.push(...r.entries);
|
|
17233
|
+
workflows.push(...r.workflows);
|
|
17065
17234
|
if (r.failures.length) {
|
|
17066
17235
|
drift.push(`${r.repo}: ${r.failures.length} workflow record(s) listed active with no file on the default branch (deleted one-offs?) \u2014 deregister them: ${r.failures.map((f) => f.split(": ")[1]).join(", ")}`);
|
|
17067
17236
|
}
|
|
17068
17237
|
}
|
|
17069
17238
|
}
|
|
17070
|
-
|
|
17239
|
+
const reconciliation = [...strayCronDrifts(workflows), ...unlauncheredLlmDrifts(entries)];
|
|
17240
|
+
drift.push(...reconciliation.map(renderDrift));
|
|
17241
|
+
return { entries, incomplete, drift, reconciliation, readRepos };
|
|
17071
17242
|
}
|
|
17072
17243
|
async function awsJson(args) {
|
|
17073
17244
|
const run = async () => {
|
|
@@ -17102,7 +17273,8 @@ async function awsEntries() {
|
|
|
17102
17273
|
} catch (e) {
|
|
17103
17274
|
incomplete.push(`aws: scheduler listing failed \u2014 ${e.message}`);
|
|
17104
17275
|
}
|
|
17105
|
-
|
|
17276
|
+
const reconciliation = unlauncheredLlmDrifts(entries);
|
|
17277
|
+
return { entries, incomplete, drift: reconciliation.map(renderDrift), reconciliation };
|
|
17106
17278
|
}
|
|
17107
17279
|
async function defaultRegistryRead() {
|
|
17108
17280
|
return fetchSchedulesList(registryClientDeps(await loadConfig()));
|
|
@@ -17118,7 +17290,7 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
17118
17290
|
entries: sortEntries([...gh.entries, ...aws.entries, ...DECLARED_ENTRIES]),
|
|
17119
17291
|
incomplete: [...gh.incomplete, ...aws.incomplete, ...recon.incomplete],
|
|
17120
17292
|
drift: [...gh.drift, ...aws.drift, ...recon.driftLines],
|
|
17121
|
-
reconciliation: recon.reconciliation
|
|
17293
|
+
reconciliation: [...gh.reconciliation, ...aws.reconciliation, ...recon.reconciliation]
|
|
17122
17294
|
};
|
|
17123
17295
|
}
|
|
17124
17296
|
function warnIncomplete2(incomplete) {
|
|
@@ -17159,6 +17331,169 @@ function registerSchedulesCommands(program3) {
|
|
|
17159
17331
|
});
|
|
17160
17332
|
}
|
|
17161
17333
|
|
|
17334
|
+
// src/schedules-lift-command.ts
|
|
17335
|
+
var import_promises3 = require("node:fs/promises");
|
|
17336
|
+
var import_node_path17 = require("node:path");
|
|
17337
|
+
|
|
17338
|
+
// src/schedules-lift.ts
|
|
17339
|
+
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
17340
|
+
function parseScheduleHeader(yamlText) {
|
|
17341
|
+
const out = {};
|
|
17342
|
+
for (const rawLine of yamlText.split("\n")) {
|
|
17343
|
+
const m = /^#\s*(schedule|what|owner|cadence|executor|llm|output|breaks|kill):\s*(.+?)\s*$/.exec(rawLine);
|
|
17344
|
+
if (m && out[m[1]] === void 0) out[m[1]] = m[2].trim();
|
|
17345
|
+
}
|
|
17346
|
+
return out;
|
|
17347
|
+
}
|
|
17348
|
+
function cadenceContainsCron(cadence, cron) {
|
|
17349
|
+
const cadenceTokens = cadence.split(/\s+/).filter(Boolean);
|
|
17350
|
+
const cronTokens = cron.split(/\s+/).filter(Boolean);
|
|
17351
|
+
if (!cronTokens.length || cronTokens.length > cadenceTokens.length) return false;
|
|
17352
|
+
return cadenceTokens.some((_, i) => i + cronTokens.length <= cadenceTokens.length && cronTokens.every((t, j) => cadenceTokens[i + j] === t));
|
|
17353
|
+
}
|
|
17354
|
+
function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
17355
|
+
const crons = extractWorkflowCrons(yamlText);
|
|
17356
|
+
const header = parseScheduleHeader(yamlText);
|
|
17357
|
+
if (!crons.length && !header.schedule) return null;
|
|
17358
|
+
const basename3 = workflowPath.split("/").pop() ?? workflowPath;
|
|
17359
|
+
const expectedId = `${repo}/${basename3.replace(/\.ya?ml$/, "")}`;
|
|
17360
|
+
const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
|
|
17361
|
+
if (missing.length) {
|
|
17362
|
+
throw new Error(`schedules lift: ${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".`);
|
|
17363
|
+
}
|
|
17364
|
+
const h = header;
|
|
17365
|
+
if (h.schedule !== expectedId) {
|
|
17366
|
+
throw new Error(`schedules lift: ${workflowPath} header \`schedule: ${h.schedule}\` must equal the join key \`${expectedId}\` (<repo>/<workflow-basename>).`);
|
|
17367
|
+
}
|
|
17368
|
+
for (const cron of crons) {
|
|
17369
|
+
if (!cadenceContainsCron(h.cadence, cron)) {
|
|
17370
|
+
throw new Error(`schedules lift: ${expectedId} cadence \`${h.cadence}\` does not contain the workflow cron \`${cron}\` (whole-token comparison) \u2014 the declared cadence disagrees with the actual schedule.`);
|
|
17371
|
+
}
|
|
17372
|
+
}
|
|
17373
|
+
if (llmFromHeader(yamlText) === "unknown") {
|
|
17374
|
+
throw new Error(`schedules lift: ${expectedId} header \`llm: ${h.llm}\` does not classify (yes / no / embeddings) \u2014 an unreadable declaration would register a row whose LLM column lies.`);
|
|
17375
|
+
}
|
|
17376
|
+
return {
|
|
17377
|
+
id: expectedId,
|
|
17378
|
+
what: h.what,
|
|
17379
|
+
owner: h.owner,
|
|
17380
|
+
cadence: h.cadence,
|
|
17381
|
+
executor: h.executor,
|
|
17382
|
+
llm: h.llm,
|
|
17383
|
+
output: h.output,
|
|
17384
|
+
breaks: h.breaks,
|
|
17385
|
+
kill: h.kill,
|
|
17386
|
+
repo,
|
|
17387
|
+
sourcePath: workflowPath
|
|
17388
|
+
};
|
|
17389
|
+
}
|
|
17390
|
+
function scheduleRecordsFromWorkflows(repo, files) {
|
|
17391
|
+
const records = [];
|
|
17392
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17393
|
+
for (const { path: path2, text } of files) {
|
|
17394
|
+
const rec = scheduleRecordFromWorkflow(repo, path2, text);
|
|
17395
|
+
if (!rec) continue;
|
|
17396
|
+
if (seen.has(rec.id)) {
|
|
17397
|
+
throw new Error(`schedules lift: duplicate schedule id \`${rec.id}\` \u2014 two workflow files in ${repo} share one join key (${rec.sourcePath}).`);
|
|
17398
|
+
}
|
|
17399
|
+
seen.add(rec.id);
|
|
17400
|
+
records.push(rec);
|
|
17401
|
+
}
|
|
17402
|
+
return records.sort((a, b) => a.id.localeCompare(b.id));
|
|
17403
|
+
}
|
|
17404
|
+
|
|
17405
|
+
// src/schedules-lift-command.ts
|
|
17406
|
+
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
17407
|
+
var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
|
|
17408
|
+
var SchedulesLiftUsageError = class extends Error {
|
|
17409
|
+
constructor(message) {
|
|
17410
|
+
super(message);
|
|
17411
|
+
this.name = "SchedulesLiftUsageError";
|
|
17412
|
+
}
|
|
17413
|
+
};
|
|
17414
|
+
var SCHEDULES_LIFT_EXIT_UNREACHABLE = 75;
|
|
17415
|
+
var RegistryUnreachableError = class extends Error {
|
|
17416
|
+
exitCode = SCHEDULES_LIFT_EXIT_UNREACHABLE;
|
|
17417
|
+
constructor(message) {
|
|
17418
|
+
super(message);
|
|
17419
|
+
this.name = "RegistryUnreachableError";
|
|
17420
|
+
}
|
|
17421
|
+
};
|
|
17422
|
+
async function readWorkflowFiles(dir) {
|
|
17423
|
+
let names;
|
|
17424
|
+
try {
|
|
17425
|
+
names = await (0, import_promises3.readdir)(dir);
|
|
17426
|
+
} catch {
|
|
17427
|
+
return [];
|
|
17428
|
+
}
|
|
17429
|
+
const files = [];
|
|
17430
|
+
for (const name of names.sort()) {
|
|
17431
|
+
if (!/\.ya?ml$/.test(name)) continue;
|
|
17432
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises3.readFile)((0, import_node_path17.join)(dir, name), "utf8") });
|
|
17433
|
+
}
|
|
17434
|
+
return files;
|
|
17435
|
+
}
|
|
17436
|
+
function repoNameFromRemoteUrl(url) {
|
|
17437
|
+
const base = url.trim().replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop();
|
|
17438
|
+
return base ?? null;
|
|
17439
|
+
}
|
|
17440
|
+
async function defaultPostLift(repo, records) {
|
|
17441
|
+
return postSchedulesLift(repo, records, registryClientDeps(await loadConfig()));
|
|
17442
|
+
}
|
|
17443
|
+
async function runSchedulesLift(opts, deps = {}) {
|
|
17444
|
+
let repo = opts.repo?.trim();
|
|
17445
|
+
if (!repo) {
|
|
17446
|
+
const url = await (deps.originUrl ?? (() => gitOut(["remote", "get-url", "origin"])))();
|
|
17447
|
+
repo = repoNameFromRemoteUrl(url) ?? void 0;
|
|
17448
|
+
if (!repo) {
|
|
17449
|
+
throw new Error("schedules lift: could not resolve the repo name from the origin remote \u2014 pass --repo <name>.");
|
|
17450
|
+
}
|
|
17451
|
+
}
|
|
17452
|
+
if (!SCHEDULE_REPO_RE.test(repo)) {
|
|
17453
|
+
throw new SchedulesLiftUsageError(`schedules lift: repo \`${repo}\` is not a bare repo segment (allowed: A-Z a-z 0-9 _ . -; no \`/\` or \`#\`) \u2014 the schedule id is <repo>/<name>, so a separator would form an invalid multi-segment id.`);
|
|
17454
|
+
}
|
|
17455
|
+
const dir = opts.dir ?? DEFAULT_WORKFLOWS_DIR;
|
|
17456
|
+
const files = await (deps.readFiles ?? readWorkflowFiles)(dir);
|
|
17457
|
+
if (!files.length) {
|
|
17458
|
+
throw new Error(`schedules lift: no workflow files under ${dir} \u2014 refusing to post an empty lift (the route prunes; run from the repo checkout or pass --dir).`);
|
|
17459
|
+
}
|
|
17460
|
+
const records = scheduleRecordsFromWorkflows(repo, files);
|
|
17461
|
+
const payload = { repo, schedules: records };
|
|
17462
|
+
if (opts.dryRun) return { repo, records, payload };
|
|
17463
|
+
const response = await (deps.postLift ?? defaultPostLift)(repo, records);
|
|
17464
|
+
if (!response.ok) {
|
|
17465
|
+
const unreachable = response.unreachable ?? (response.status === 404 ? "route-absent" : void 0);
|
|
17466
|
+
if (unreachable) {
|
|
17467
|
+
throw new RegistryUnreachableError(`schedules lift: registry unreachable (${unreachable}) \u2014 ${response.error ?? `HTTP ${response.status}`}`);
|
|
17468
|
+
}
|
|
17469
|
+
if (response.error) throw new Error(`schedules lift: ${response.error}`);
|
|
17470
|
+
const detail = response.body?.error ?? "";
|
|
17471
|
+
throw new Error(`schedules lift: HTTP ${response.status}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
17472
|
+
}
|
|
17473
|
+
return { repo, records, payload, response };
|
|
17474
|
+
}
|
|
17475
|
+
function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
17476
|
+
const schedules = program3.commands.find((c) => c.name() === "schedules");
|
|
17477
|
+
if (!schedules) throw new Error("schedules lift: registerSchedulesCommands must run first \u2014 the lift attaches to the `schedules` command");
|
|
17478
|
+
schedules.command("lift").description("lift this repo's eight-field workflow headers into SCHEDULE# registry rows (C2, #3186) \u2014 a per-repo replace; aborts the whole lift on any header violation; exits 75 when the registry is unreachable (#3187)").option("--repo <name>", "bare repo name for the join key (defaults to the origin remote basename, case-preserved)").option("--dir <path>", "workflows directory to lift (defaults to .github/workflows)").option("--dry-run", "print the {repo, schedules} body that would be POSTed; never write").action(async (o) => {
|
|
17479
|
+
try {
|
|
17480
|
+
const result = await runSchedulesLift({ repo: o.repo, dir: o.dir, dryRun: Boolean(o.dryRun) }, deps);
|
|
17481
|
+
if (o.dryRun) {
|
|
17482
|
+
console.log(JSON.stringify(result.payload, null, 2));
|
|
17483
|
+
return;
|
|
17484
|
+
}
|
|
17485
|
+
console.log(JSON.stringify(result.response?.body));
|
|
17486
|
+
} catch (e) {
|
|
17487
|
+
const err = e;
|
|
17488
|
+
if (err instanceof RegistryUnreachableError) {
|
|
17489
|
+
console.error(`mmi-cli ${err.message}`);
|
|
17490
|
+
return await cleanExit(err.exitCode);
|
|
17491
|
+
}
|
|
17492
|
+
return await failGraceful(err.message);
|
|
17493
|
+
}
|
|
17494
|
+
});
|
|
17495
|
+
}
|
|
17496
|
+
|
|
17162
17497
|
// src/edge-tunnel.ts
|
|
17163
17498
|
var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
|
|
17164
17499
|
var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
|
|
@@ -17618,7 +17953,7 @@ function registerQueryCommands(program3) {
|
|
|
17618
17953
|
}
|
|
17619
17954
|
|
|
17620
17955
|
// src/bootstrap-commands.ts
|
|
17621
|
-
var
|
|
17956
|
+
var import_node_fs19 = require("node:fs");
|
|
17622
17957
|
|
|
17623
17958
|
// src/bootstrap-verify.ts
|
|
17624
17959
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
@@ -18166,7 +18501,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18166
18501
|
client: defaultGitHubClient(),
|
|
18167
18502
|
projectMeta: meta,
|
|
18168
18503
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
18169
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
18504
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null,
|
|
18170
18505
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
18171
18506
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
18172
18507
|
requiredGcpApis: (() => {
|
|
@@ -18217,12 +18552,12 @@ function registerBootstrapCommands(program3) {
|
|
|
18217
18552
|
return fail(`bootstrap apply: ${e.message}`);
|
|
18218
18553
|
}
|
|
18219
18554
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
18220
|
-
if (!(0,
|
|
18221
|
-
const manifest = loadBootstrapSeeds((0,
|
|
18555
|
+
if (!(0, import_node_fs19.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`);
|
|
18556
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs19.readFileSync)(manifestPath, "utf8"));
|
|
18222
18557
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
18223
18558
|
const slug = parsedRepo.slug;
|
|
18224
18559
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
18225
|
-
const
|
|
18560
|
+
const readFile6 = (p) => (0, import_node_fs19.existsSync)(p) ? (0, import_node_fs19.readFileSync)(p, "utf8") : null;
|
|
18226
18561
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
18227
18562
|
const rawVars = {};
|
|
18228
18563
|
for (const value of cmdOpts.var ?? []) {
|
|
@@ -18300,7 +18635,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18300
18635
|
}
|
|
18301
18636
|
const planned = planSeedAction(resolved, exists);
|
|
18302
18637
|
const isBlock = resolved.source === "managed-block";
|
|
18303
|
-
const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars,
|
|
18638
|
+
const content = planned.action === "create" || planned.action === "update" ? isBlock ? upsertManagedGitignoreBlock(remoteContent).content : resolveSeedContent(resolved, vars, readFile6) : null;
|
|
18304
18639
|
const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
|
|
18305
18640
|
actions.push(action);
|
|
18306
18641
|
if (o.execute && (action.action === "create" || action.action === "update")) {
|
|
@@ -18355,7 +18690,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18355
18690
|
}
|
|
18356
18691
|
const rulesetSeed = manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
|
|
18357
18692
|
if (rulesetSeed) {
|
|
18358
|
-
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars,
|
|
18693
|
+
const rulesetContent = resolveSeedContent({ ...rulesetSeed, target: rulesetSeed.target.replace("{{REPO_SLUG}}", slug) }, vars, readFile6);
|
|
18359
18694
|
if (rulesetContent) {
|
|
18360
18695
|
try {
|
|
18361
18696
|
const activation = await activateProductRuleset(repo, stripRulesetComment(rulesetContent), defaultGitHubClient());
|
|
@@ -18418,12 +18753,12 @@ LIVE apply to ${repo}:
|
|
|
18418
18753
|
}
|
|
18419
18754
|
|
|
18420
18755
|
// src/stage-commands.ts
|
|
18421
|
-
var
|
|
18422
|
-
var
|
|
18756
|
+
var import_node_fs21 = require("node:fs");
|
|
18757
|
+
var import_node_path19 = require("node:path");
|
|
18423
18758
|
|
|
18424
18759
|
// src/port-registry.ts
|
|
18425
|
-
var
|
|
18426
|
-
var
|
|
18760
|
+
var import_node_fs20 = require("node:fs");
|
|
18761
|
+
var import_node_path18 = require("node:path");
|
|
18427
18762
|
|
|
18428
18763
|
// ../infra/port-geometry.mjs
|
|
18429
18764
|
var PORT_BLOCK = 100;
|
|
@@ -18437,8 +18772,8 @@ function nextPortBlock(registry2) {
|
|
|
18437
18772
|
return [base, base + PORT_SPAN];
|
|
18438
18773
|
}
|
|
18439
18774
|
function loadPortRegistry(path2) {
|
|
18440
|
-
if (!(0,
|
|
18441
|
-
const raw = JSON.parse((0,
|
|
18775
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return {};
|
|
18776
|
+
const raw = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
18442
18777
|
const out = {};
|
|
18443
18778
|
for (const [key, value] of Object.entries(raw)) {
|
|
18444
18779
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -18452,9 +18787,9 @@ function ensurePortRange(repo, path2) {
|
|
|
18452
18787
|
const existing = registry2[repo];
|
|
18453
18788
|
if (existing) return existing;
|
|
18454
18789
|
const range = nextPortBlock(registry2);
|
|
18455
|
-
const raw = (0,
|
|
18790
|
+
const raw = (0, import_node_fs20.existsSync)(path2) ? JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8")) : {};
|
|
18456
18791
|
raw[repo] = range;
|
|
18457
|
-
(0,
|
|
18792
|
+
(0, import_node_fs20.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
18458
18793
|
return range;
|
|
18459
18794
|
}
|
|
18460
18795
|
function portCursorSeed(registry2) {
|
|
@@ -18476,22 +18811,22 @@ function existingPortRange(repo, registry2) {
|
|
|
18476
18811
|
return registry2[repo] ?? null;
|
|
18477
18812
|
}
|
|
18478
18813
|
function portRangeInfraAt(root, source) {
|
|
18479
|
-
const registryPath = (0,
|
|
18480
|
-
const ddbScriptPath = (0,
|
|
18481
|
-
if (!(0,
|
|
18814
|
+
const registryPath = (0, import_node_path18.join)(root, "infra", "port-ranges.json");
|
|
18815
|
+
const ddbScriptPath = (0, import_node_path18.join)(root, "infra", "port-ddb.mjs");
|
|
18816
|
+
if (!(0, import_node_fs20.existsSync)(registryPath) || !(0, import_node_fs20.existsSync)(ddbScriptPath)) return null;
|
|
18482
18817
|
return { root, source, registryPath, ddbScriptPath };
|
|
18483
18818
|
}
|
|
18484
18819
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
18485
18820
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
18486
18821
|
if (direct) return direct;
|
|
18487
|
-
for (let dir = cwd; ; dir = (0,
|
|
18488
|
-
const sibling = portRangeInfraAt((0,
|
|
18822
|
+
for (let dir = cwd; ; dir = (0, import_node_path18.dirname)(dir)) {
|
|
18823
|
+
const sibling = portRangeInfraAt((0, import_node_path18.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
18489
18824
|
if (sibling) return sibling;
|
|
18490
|
-
const parent = (0,
|
|
18825
|
+
const parent = (0, import_node_path18.dirname)(dir);
|
|
18491
18826
|
if (parent === dir) break;
|
|
18492
18827
|
}
|
|
18493
18828
|
if (packageDir) {
|
|
18494
|
-
const pkgRoot = (0,
|
|
18829
|
+
const pkgRoot = (0, import_node_path18.join)(packageDir, "..", "..");
|
|
18495
18830
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
18496
18831
|
if (pkgFrom) return pkgFrom;
|
|
18497
18832
|
}
|
|
@@ -18667,8 +19002,8 @@ function registerStageCommands(program3) {
|
|
|
18667
19002
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
18668
19003
|
return decideStage({
|
|
18669
19004
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
18670
|
-
hasCompose: (0,
|
|
18671
|
-
hasEnvExample: (0,
|
|
19005
|
+
hasCompose: (0, import_node_fs21.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
|
|
19006
|
+
hasEnvExample: (0, import_node_fs21.existsSync)((0, import_node_path19.join)(process.cwd(), ".env.example"))
|
|
18672
19007
|
});
|
|
18673
19008
|
}
|
|
18674
19009
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -19106,8 +19441,8 @@ function registerBoardCommands(program3) {
|
|
|
19106
19441
|
}
|
|
19107
19442
|
|
|
19108
19443
|
// src/merge-cleanup.ts
|
|
19109
|
-
var
|
|
19110
|
-
var
|
|
19444
|
+
var import_node_fs22 = require("node:fs");
|
|
19445
|
+
var import_promises5 = require("node:fs/promises");
|
|
19111
19446
|
var import_node_child_process10 = require("node:child_process");
|
|
19112
19447
|
|
|
19113
19448
|
// src/board-advance.ts
|
|
@@ -19193,23 +19528,23 @@ function boardAdvanceFailureMessage(result) {
|
|
|
19193
19528
|
}
|
|
19194
19529
|
|
|
19195
19530
|
// src/deferred-registry-store.ts
|
|
19196
|
-
var
|
|
19197
|
-
var
|
|
19531
|
+
var import_promises4 = require("node:fs/promises");
|
|
19532
|
+
var import_node_path20 = require("node:path");
|
|
19198
19533
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
19199
19534
|
async function atomicWrite(target, contents) {
|
|
19200
19535
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
19201
|
-
await (0,
|
|
19536
|
+
await (0, import_promises4.writeFile)(tmp, contents, "utf8");
|
|
19202
19537
|
try {
|
|
19203
19538
|
await renameWithRetry(tmp, target);
|
|
19204
19539
|
} catch (e) {
|
|
19205
|
-
await (0,
|
|
19540
|
+
await (0, import_promises4.unlink)(tmp).catch(() => void 0);
|
|
19206
19541
|
throw e;
|
|
19207
19542
|
}
|
|
19208
19543
|
}
|
|
19209
19544
|
async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
|
|
19210
19545
|
for (let i = 0; ; i++) {
|
|
19211
19546
|
try {
|
|
19212
|
-
await (0,
|
|
19547
|
+
await (0, import_promises4.rename)(from, to);
|
|
19213
19548
|
return;
|
|
19214
19549
|
} catch (e) {
|
|
19215
19550
|
const code = e.code;
|
|
@@ -19221,7 +19556,7 @@ async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
|
|
|
19221
19556
|
async function readStrict(registryPath) {
|
|
19222
19557
|
let text;
|
|
19223
19558
|
try {
|
|
19224
|
-
text = await (0,
|
|
19559
|
+
text = await (0, import_promises4.readFile)(registryPath, "utf8");
|
|
19225
19560
|
} catch (e) {
|
|
19226
19561
|
if (e.code === "ENOENT") return [];
|
|
19227
19562
|
throw e;
|
|
@@ -19232,15 +19567,15 @@ async function acquireLock(lockPath, opts, deadline) {
|
|
|
19232
19567
|
for (; ; ) {
|
|
19233
19568
|
let handle;
|
|
19234
19569
|
try {
|
|
19235
|
-
handle = await (0,
|
|
19570
|
+
handle = await (0, import_promises4.open)(lockPath, "wx");
|
|
19236
19571
|
} catch (e) {
|
|
19237
19572
|
const code = e.code;
|
|
19238
19573
|
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
19239
19574
|
try {
|
|
19240
|
-
const age = Date.now() - (await (0,
|
|
19575
|
+
const age = Date.now() - (await (0, import_promises4.stat)(lockPath)).mtimeMs;
|
|
19241
19576
|
if (age > opts.staleMs) {
|
|
19242
19577
|
try {
|
|
19243
|
-
await (0,
|
|
19578
|
+
await (0, import_promises4.unlink)(lockPath);
|
|
19244
19579
|
continue;
|
|
19245
19580
|
} catch (unlinkError) {
|
|
19246
19581
|
const code2 = unlinkError.code;
|
|
@@ -19269,7 +19604,7 @@ async function acquireLock(lockPath, opts, deadline) {
|
|
|
19269
19604
|
}
|
|
19270
19605
|
async function lockHeldBy(lockPath, token) {
|
|
19271
19606
|
try {
|
|
19272
|
-
return await (0,
|
|
19607
|
+
return await (0, import_promises4.readFile)(lockPath, "utf8") === token;
|
|
19273
19608
|
} catch {
|
|
19274
19609
|
return false;
|
|
19275
19610
|
}
|
|
@@ -19277,7 +19612,7 @@ async function lockHeldBy(lockPath, token) {
|
|
|
19277
19612
|
async function releaseLock(lockPath, guard) {
|
|
19278
19613
|
await guard.handle.close().catch(() => void 0);
|
|
19279
19614
|
if (await lockHeldBy(lockPath, guard.token)) {
|
|
19280
|
-
await (0,
|
|
19615
|
+
await (0, import_promises4.unlink)(lockPath).catch(() => void 0);
|
|
19281
19616
|
}
|
|
19282
19617
|
}
|
|
19283
19618
|
function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
@@ -19291,19 +19626,19 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
19291
19626
|
// Lenient read for the sweep's initial fetch: any error (missing/corrupt) yields an empty queue.
|
|
19292
19627
|
read: async () => {
|
|
19293
19628
|
try {
|
|
19294
|
-
return parseDeferredWorktreesFile(await (0,
|
|
19629
|
+
return parseDeferredWorktreesFile(await (0, import_promises4.readFile)(registryPath, "utf8"));
|
|
19295
19630
|
} catch {
|
|
19296
19631
|
return [];
|
|
19297
19632
|
}
|
|
19298
19633
|
},
|
|
19299
19634
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
19300
19635
|
write: async (entries) => {
|
|
19301
|
-
await (0,
|
|
19636
|
+
await (0, import_promises4.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
19302
19637
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
19303
19638
|
},
|
|
19304
19639
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
19305
19640
|
update: async (mutate) => {
|
|
19306
|
-
await (0,
|
|
19641
|
+
await (0, import_promises4.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
19307
19642
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
19308
19643
|
for (; ; ) {
|
|
19309
19644
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -19430,7 +19765,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19430
19765
|
return cleanupPrMergeLocalBranch(branch.branch, {
|
|
19431
19766
|
beforeWorktrees,
|
|
19432
19767
|
startingPath: branch.worktreePath,
|
|
19433
|
-
pathExists: (p) => (0,
|
|
19768
|
+
pathExists: (p) => (0, import_node_fs22.existsSync)(p),
|
|
19434
19769
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
19435
19770
|
teardownWorktreeStage,
|
|
19436
19771
|
deferredStore,
|
|
@@ -19456,7 +19791,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19456
19791
|
for (const wt of plan.worktreeDirs) {
|
|
19457
19792
|
try {
|
|
19458
19793
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
19459
|
-
realpath: (path2) => (0,
|
|
19794
|
+
realpath: (path2) => (0, import_node_fs22.realpathSync)(path2)
|
|
19460
19795
|
});
|
|
19461
19796
|
if (!cleanupTarget.ok) {
|
|
19462
19797
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -19583,13 +19918,13 @@ var realWorktreeDirRemover = {
|
|
|
19583
19918
|
probe: (p) => {
|
|
19584
19919
|
let st;
|
|
19585
19920
|
try {
|
|
19586
|
-
st = (0,
|
|
19921
|
+
st = (0, import_node_fs22.lstatSync)(p);
|
|
19587
19922
|
} catch {
|
|
19588
19923
|
return null;
|
|
19589
19924
|
}
|
|
19590
19925
|
if (st.isSymbolicLink()) return "link";
|
|
19591
19926
|
try {
|
|
19592
|
-
(0,
|
|
19927
|
+
(0, import_node_fs22.readlinkSync)(p);
|
|
19593
19928
|
return "link";
|
|
19594
19929
|
} catch {
|
|
19595
19930
|
}
|
|
@@ -19597,7 +19932,7 @@ var realWorktreeDirRemover = {
|
|
|
19597
19932
|
},
|
|
19598
19933
|
readdir: (p) => {
|
|
19599
19934
|
try {
|
|
19600
|
-
return (0,
|
|
19935
|
+
return (0, import_node_fs22.readdirSync)(p);
|
|
19601
19936
|
} catch {
|
|
19602
19937
|
return [];
|
|
19603
19938
|
}
|
|
@@ -19606,12 +19941,12 @@ var realWorktreeDirRemover = {
|
|
|
19606
19941
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
19607
19942
|
detachLink: (p) => {
|
|
19608
19943
|
try {
|
|
19609
|
-
(0,
|
|
19944
|
+
(0, import_node_fs22.rmdirSync)(p);
|
|
19610
19945
|
} catch {
|
|
19611
|
-
(0,
|
|
19946
|
+
(0, import_node_fs22.unlinkSync)(p);
|
|
19612
19947
|
}
|
|
19613
19948
|
},
|
|
19614
|
-
removeTree: (p) => (0,
|
|
19949
|
+
removeTree: (p) => (0, import_promises5.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
19615
19950
|
};
|
|
19616
19951
|
async function resolvePrimaryCheckout(execGit) {
|
|
19617
19952
|
try {
|
|
@@ -19641,9 +19976,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
19641
19976
|
}
|
|
19642
19977
|
}
|
|
19643
19978
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
19644
|
-
if (!(0,
|
|
19979
|
+
if (!(0, import_node_fs22.existsSync)(statePath)) return false;
|
|
19645
19980
|
try {
|
|
19646
|
-
const state = JSON.parse((0,
|
|
19981
|
+
const state = JSON.parse((0, import_node_fs22.readFileSync)(statePath, "utf8"));
|
|
19647
19982
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
19648
19983
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
19649
19984
|
} catch {
|
|
@@ -19771,8 +20106,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
19771
20106
|
}
|
|
19772
20107
|
|
|
19773
20108
|
// src/worktree-lifecycle-commands.ts
|
|
19774
|
-
var
|
|
19775
|
-
var
|
|
20109
|
+
var import_node_fs23 = require("node:fs");
|
|
20110
|
+
var import_node_path21 = require("node:path");
|
|
19776
20111
|
var GH_TIMEOUT_MS = 2e4;
|
|
19777
20112
|
var DEFAULT_BASE = "origin/development";
|
|
19778
20113
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -19815,7 +20150,6 @@ function classifyStaleLeaks(input) {
|
|
|
19815
20150
|
const protectedBranches = input.protectedBranches ?? PROTECTED_BRANCHES2;
|
|
19816
20151
|
const leaks = [];
|
|
19817
20152
|
const worktreeBranches2 = new Set(input.worktrees.filter((w) => !w.primary).map((w) => w.branch));
|
|
19818
|
-
const worktreeByBranch = new Map(input.worktrees.map((w) => [w.branch, w.path]));
|
|
19819
20153
|
const stageByPath = new Map(input.stages.map((s) => [s.path, s.port]));
|
|
19820
20154
|
for (const wt of input.worktrees) {
|
|
19821
20155
|
if (wt.primary) continue;
|
|
@@ -19910,13 +20244,13 @@ function registerWorktreeCommands(program3) {
|
|
|
19910
20244
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
19911
20245
|
return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
19912
20246
|
}
|
|
19913
|
-
const gitFile = (0,
|
|
19914
|
-
const isLinked = (0,
|
|
20247
|
+
const gitFile = (0, import_node_path21.join)(wtPath, ".git");
|
|
20248
|
+
const isLinked = (0, import_node_fs23.existsSync)(gitFile) && (0, import_node_fs23.statSync)(gitFile).isFile();
|
|
19915
20249
|
if (apply && !isLinked) {
|
|
19916
20250
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
19917
20251
|
}
|
|
19918
20252
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
19919
|
-
const primaryCheckout = commonDir ? (0,
|
|
20253
|
+
const primaryCheckout = commonDir ? (0, import_node_path21.dirname)(commonDir) : wtPath;
|
|
19920
20254
|
const stageSummary = readStageSummary(wtPath);
|
|
19921
20255
|
const hasStage = stageSummary != null;
|
|
19922
20256
|
const steps = landSteps(branch, true, hasStage);
|
|
@@ -20028,10 +20362,10 @@ async function gatherWorktreeContext() {
|
|
|
20028
20362
|
}
|
|
20029
20363
|
const orphanDirs = [];
|
|
20030
20364
|
const wtRoot = siblingMmiWorktreesRoot(repoRoot2);
|
|
20031
|
-
if ((0,
|
|
20365
|
+
if ((0, import_node_fs23.existsSync)(wtRoot)) {
|
|
20032
20366
|
let entries = [];
|
|
20033
20367
|
try {
|
|
20034
|
-
entries = (0,
|
|
20368
|
+
entries = (0, import_node_fs23.readdirSync)(wtRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path21.join)(wtRoot, e.name));
|
|
20035
20369
|
} catch {
|
|
20036
20370
|
}
|
|
20037
20371
|
const known = new Set(worktrees.map((w) => w.path));
|
|
@@ -20056,7 +20390,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
20056
20390
|
}
|
|
20057
20391
|
|
|
20058
20392
|
// src/issue-commands.ts
|
|
20059
|
-
var
|
|
20393
|
+
var import_node_fs24 = require("node:fs");
|
|
20060
20394
|
var import_node_crypto5 = require("node:crypto");
|
|
20061
20395
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
20062
20396
|
async function editIssue(client, options, deps = {}) {
|
|
@@ -20066,39 +20400,23 @@ async function editIssue(client, options, deps = {}) {
|
|
|
20066
20400
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
20067
20401
|
const patch = {};
|
|
20068
20402
|
let bodyChanged = false;
|
|
20069
|
-
let northStarSlug;
|
|
20070
20403
|
if (options.title !== void 0) patch.title = options.title;
|
|
20071
20404
|
if (options.body !== void 0 || options.bodyFile !== void 0) {
|
|
20072
20405
|
let body;
|
|
20073
20406
|
if (options.bodyFile) {
|
|
20074
|
-
const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
20407
|
+
const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs24.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
20075
20408
|
body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
|
|
20076
20409
|
} else {
|
|
20077
20410
|
body = options.body ?? "";
|
|
20078
20411
|
}
|
|
20079
|
-
if (options.northStar) {
|
|
20080
|
-
northStarSlug = normalizeNorthStarSlug(options.northStar);
|
|
20081
|
-
body = appendNorthStarLine(body, northStarSlug);
|
|
20082
|
-
}
|
|
20083
20412
|
patch.body = body;
|
|
20084
20413
|
bodyChanged = true;
|
|
20085
|
-
} else if (options.northStar) {
|
|
20086
|
-
northStarSlug = normalizeNorthStarSlug(options.northStar);
|
|
20087
|
-
const current = await client.rest("GET", `repos/${repo}/issues/${parsed.number}`);
|
|
20088
|
-
patch.body = appendNorthStarLine(current?.body ?? "", northStarSlug);
|
|
20089
|
-
bodyChanged = true;
|
|
20090
20414
|
}
|
|
20091
20415
|
if (patch.title !== void 0 || patch.body !== void 0) {
|
|
20092
20416
|
await client.rest("PATCH", `repos/${repo}/issues/${parsed.number}`, { body: patch });
|
|
20093
20417
|
}
|
|
20094
20418
|
const labelsAdded = [];
|
|
20095
20419
|
const labelsRemoved = [];
|
|
20096
|
-
if (options.northStar && northStarSlug) {
|
|
20097
|
-
const nsLabel = northStarLabel(northStarSlug);
|
|
20098
|
-
if (!options.addLabel?.includes(nsLabel)) {
|
|
20099
|
-
(options.addLabel ??= []).push(nsLabel);
|
|
20100
|
-
}
|
|
20101
|
-
}
|
|
20102
20420
|
if (options.addLabel?.length) {
|
|
20103
20421
|
await client.rest("POST", `repos/${repo}/issues/${parsed.number}/labels`, { body: { labels: options.addLabel } });
|
|
20104
20422
|
labelsAdded.push(...options.addLabel);
|
|
@@ -20128,8 +20446,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
20128
20446
|
bodyChanged,
|
|
20129
20447
|
labelsAdded,
|
|
20130
20448
|
labelsRemoved,
|
|
20131
|
-
...parentResult ? { parent: parentResult } : {}
|
|
20132
|
-
...northStarSlug ? { northStarSlug } : {}
|
|
20449
|
+
...parentResult ? { parent: parentResult } : {}
|
|
20133
20450
|
};
|
|
20134
20451
|
}
|
|
20135
20452
|
async function closeIssue(client, options, deps = {}) {
|
|
@@ -20309,7 +20626,7 @@ ${spec.body ?? ""}`;
|
|
|
20309
20626
|
const hash = (0, import_node_crypto5.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
20310
20627
|
return `${batchKey}:${hash}`;
|
|
20311
20628
|
}
|
|
20312
|
-
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "
|
|
20629
|
+
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "parent", "repo"]);
|
|
20313
20630
|
function validateBatchSpecs(specs) {
|
|
20314
20631
|
const errors = [];
|
|
20315
20632
|
const validated = [];
|
|
@@ -20349,28 +20666,6 @@ function validateBatchSpecs(specs) {
|
|
|
20349
20666
|
}
|
|
20350
20667
|
return { ok: errors.length === 0, errors, validated };
|
|
20351
20668
|
}
|
|
20352
|
-
var NORTH_STAR_LABEL_COLOR = "5319e7";
|
|
20353
|
-
function northStarLabelsForBatch(specs) {
|
|
20354
|
-
const labels = /* @__PURE__ */ new Set();
|
|
20355
|
-
for (const spec of specs) {
|
|
20356
|
-
if (spec.northStar) labels.add(northStarLabel(normalizeNorthStarSlug(spec.northStar)));
|
|
20357
|
-
for (const l of spec.labels ?? []) if (l.startsWith("northstar:")) labels.add(l);
|
|
20358
|
-
}
|
|
20359
|
-
return [...labels].sort((a, b) => a.localeCompare(b));
|
|
20360
|
-
}
|
|
20361
|
-
async function ensureNorthStarLabels(client, repo, specs) {
|
|
20362
|
-
const minted = [];
|
|
20363
|
-
for (const name of northStarLabelsForBatch(specs)) {
|
|
20364
|
-
try {
|
|
20365
|
-
await client.rest("POST", `repos/${repo}/labels`, {
|
|
20366
|
-
body: { name, color: NORTH_STAR_LABEL_COLOR, description: `North Star: ${name.slice("northstar:".length)}` }
|
|
20367
|
-
});
|
|
20368
|
-
minted.push(name);
|
|
20369
|
-
} catch {
|
|
20370
|
-
}
|
|
20371
|
-
}
|
|
20372
|
-
return minted;
|
|
20373
|
-
}
|
|
20374
20669
|
async function createIssuesBatch(specs, options, deps = {}) {
|
|
20375
20670
|
const client = deps.client ?? defaultGitHubClient();
|
|
20376
20671
|
const validation = validateBatchSpecs(specs);
|
|
@@ -20384,14 +20679,6 @@ ${lines}`);
|
|
|
20384
20679
|
throw new Error("could not resolve repo \u2014 pass --repo owner/repo or set repo per row");
|
|
20385
20680
|
}
|
|
20386
20681
|
const rowRepo = (spec) => spec.repo ?? defaultRepo;
|
|
20387
|
-
const byRepo = /* @__PURE__ */ new Map();
|
|
20388
|
-
for (const v of validation.validated) {
|
|
20389
|
-
const r = rowRepo(v.spec);
|
|
20390
|
-
byRepo.set(r, [...byRepo.get(r) ?? [], v.spec]);
|
|
20391
|
-
}
|
|
20392
|
-
for (const [r, specsForRepo] of byRepo) {
|
|
20393
|
-
await ensureNorthStarLabels(client, r, specsForRepo);
|
|
20394
|
-
}
|
|
20395
20682
|
const created = [];
|
|
20396
20683
|
const failures = [];
|
|
20397
20684
|
for (const entry of validation.validated) {
|
|
@@ -20406,12 +20693,6 @@ ${lines}`);
|
|
|
20406
20693
|
}
|
|
20407
20694
|
}
|
|
20408
20695
|
let body = spec.body ?? "";
|
|
20409
|
-
if (spec.northStar) {
|
|
20410
|
-
const slug = normalizeNorthStarSlug(spec.northStar);
|
|
20411
|
-
body = appendNorthStarLine(body, slug);
|
|
20412
|
-
const nsLabel = northStarLabel(slug);
|
|
20413
|
-
if (!spec.labels?.includes(nsLabel)) (spec.labels ??= []).push(nsLabel);
|
|
20414
|
-
}
|
|
20415
20696
|
if (rowKey) {
|
|
20416
20697
|
body = appendIdempotencyMarker(body, rowKey);
|
|
20417
20698
|
}
|
|
@@ -20465,7 +20746,7 @@ function registerIssueLifecycleCommands(program3) {
|
|
|
20465
20746
|
const issue2 = program3.commands.find((c) => c.name() === "issue");
|
|
20466
20747
|
if (!issue2) return;
|
|
20467
20748
|
mutating(
|
|
20468
|
-
issue2.command("edit <ref>").description("edit an issue title/body/labels
|
|
20749
|
+
issue2.command("edit <ref>").description("edit an issue title/body/labels or reparent it (--parent) \u2014 reparenting is impossible via raw gh").option("--title <title>", "new title").option("--body <body>", "new body (markdown)").option("--body-file <path|->", "read new body from a UTF-8 file, or stdin with -").option("--add-label <label...>", "label(s) to add (repeatable)").option("--remove-label <label...>", "label(s) to remove (repeatable)").option("--parent <ref>", "reparent: link under this parent (#123, owner/repo#123, or URL)").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)"),
|
|
20469
20750
|
(opts, args) => ({ command: "issue edit", ref: args[0], fields: Object.keys(opts).filter((k) => k !== "repo" && opts[k] !== void 0) })
|
|
20470
20751
|
).action(async (ref, o) => {
|
|
20471
20752
|
try {
|
|
@@ -20478,8 +20759,7 @@ function registerIssueLifecycleCommands(program3) {
|
|
|
20478
20759
|
bodyFile: o.bodyFile,
|
|
20479
20760
|
addLabel: o.addLabel,
|
|
20480
20761
|
removeLabel: o.removeLabel,
|
|
20481
|
-
parent: o.parent
|
|
20482
|
-
northStar: o.northStar
|
|
20762
|
+
parent: o.parent
|
|
20483
20763
|
});
|
|
20484
20764
|
console.log(JSON.stringify(result));
|
|
20485
20765
|
} catch (e) {
|
|
@@ -20598,7 +20878,7 @@ function extendCreateCommand(issue2) {
|
|
|
20598
20878
|
if (opts.batch) {
|
|
20599
20879
|
let specs;
|
|
20600
20880
|
try {
|
|
20601
|
-
const raw = (0,
|
|
20881
|
+
const raw = (0, import_node_fs24.readFileSync)(opts.batch, "utf8");
|
|
20602
20882
|
specs = JSON.parse(raw);
|
|
20603
20883
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
20604
20884
|
} catch (e) {
|
|
@@ -20642,12 +20922,12 @@ ${lines}`);
|
|
|
20642
20922
|
}
|
|
20643
20923
|
|
|
20644
20924
|
// src/train-commands.ts
|
|
20645
|
-
var
|
|
20646
|
-
var
|
|
20925
|
+
var import_node_fs26 = require("node:fs");
|
|
20926
|
+
var import_node_path23 = require("node:path");
|
|
20647
20927
|
|
|
20648
20928
|
// src/plugin-guard-io.ts
|
|
20649
|
-
var
|
|
20650
|
-
var
|
|
20929
|
+
var import_node_fs25 = require("node:fs");
|
|
20930
|
+
var import_node_path22 = require("node:path");
|
|
20651
20931
|
var import_node_os6 = require("node:os");
|
|
20652
20932
|
|
|
20653
20933
|
// src/plugin-guard.ts
|
|
@@ -20777,11 +21057,11 @@ function runHostBin(bin, args, opts) {
|
|
|
20777
21057
|
}
|
|
20778
21058
|
var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
|
|
20779
21059
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
20780
|
-
return (0,
|
|
21060
|
+
return (0, import_node_path22.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
20781
21061
|
};
|
|
20782
21062
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
20783
21063
|
try {
|
|
20784
|
-
return JSON.parse((0,
|
|
21064
|
+
return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
|
|
20785
21065
|
} catch {
|
|
20786
21066
|
return null;
|
|
20787
21067
|
}
|
|
@@ -20789,13 +21069,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
20789
21069
|
function marketplaceCloneCandidates(surface, home) {
|
|
20790
21070
|
if (surface === "codex") {
|
|
20791
21071
|
return [
|
|
20792
|
-
(0,
|
|
20793
|
-
(0,
|
|
21072
|
+
(0, import_node_path22.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
21073
|
+
(0, import_node_path22.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
20794
21074
|
];
|
|
20795
21075
|
}
|
|
20796
|
-
return [(0,
|
|
21076
|
+
return [(0, import_node_path22.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
20797
21077
|
}
|
|
20798
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
21078
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
|
|
20799
21079
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
20800
21080
|
}
|
|
20801
21081
|
async function fetchNpmReleasedVersion() {
|
|
@@ -20813,7 +21093,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
20813
21093
|
isOrgRepo,
|
|
20814
21094
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
20815
21095
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
20816
|
-
pluginCachePresent: (0,
|
|
21096
|
+
pluginCachePresent: (0, import_node_fs25.existsSync)((0, import_node_path22.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
20817
21097
|
};
|
|
20818
21098
|
}
|
|
20819
21099
|
function claudePluginGuardState(isOrgRepo) {
|
|
@@ -20937,7 +21217,7 @@ function formatTrainStatus(r) {
|
|
|
20937
21217
|
// src/train-commands.ts
|
|
20938
21218
|
function readRepoVersion() {
|
|
20939
21219
|
try {
|
|
20940
|
-
return JSON.parse((0,
|
|
21220
|
+
return JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path23.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
20941
21221
|
} catch {
|
|
20942
21222
|
return void 0;
|
|
20943
21223
|
}
|
|
@@ -21437,7 +21717,7 @@ function registerExplainCommand(program3) {
|
|
|
21437
21717
|
}
|
|
21438
21718
|
|
|
21439
21719
|
// src/pr-commands.ts
|
|
21440
|
-
var
|
|
21720
|
+
var import_promises6 = require("node:fs/promises");
|
|
21441
21721
|
var GC_GH_TIMEOUT_MS4 = 2e4;
|
|
21442
21722
|
var CHECKS_WATCH_POLL_MS = 15e3;
|
|
21443
21723
|
var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -21581,10 +21861,10 @@ function registerPrLifecycleCommands(program3) {
|
|
|
21581
21861
|
let body;
|
|
21582
21862
|
try {
|
|
21583
21863
|
if (o.title || o.titleFile) {
|
|
21584
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
21864
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises6.readFile, readStdin });
|
|
21585
21865
|
}
|
|
21586
21866
|
if (o.body || o.bodyFile) {
|
|
21587
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
21867
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
|
|
21588
21868
|
}
|
|
21589
21869
|
} catch (e) {
|
|
21590
21870
|
return fail(`pr edit: ${e.message}`);
|
|
@@ -21614,7 +21894,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
21614
21894
|
}
|
|
21615
21895
|
let body;
|
|
21616
21896
|
try {
|
|
21617
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
21897
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises6.readFile, readStdin });
|
|
21618
21898
|
} catch (e) {
|
|
21619
21899
|
return fail(`pr comment: ${e.message}`);
|
|
21620
21900
|
}
|
|
@@ -22167,7 +22447,7 @@ function checkSchedules(probe) {
|
|
|
22167
22447
|
ok: false,
|
|
22168
22448
|
label: "schedules",
|
|
22169
22449
|
detail: `${probe.drift.length} drift finding(s)`,
|
|
22170
|
-
fix: "run `mmi-cli org schedules` \u2014
|
|
22450
|
+
fix: "run `mmi-cli org schedules` \u2014 each drift line names its per-class remedy (file/registry/live mismatch or harbour enforcement), applied in the owning repo",
|
|
22171
22451
|
verbose: evidence
|
|
22172
22452
|
};
|
|
22173
22453
|
}
|
|
@@ -22339,9 +22619,9 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
22339
22619
|
}
|
|
22340
22620
|
|
|
22341
22621
|
// src/doctor-io.ts
|
|
22342
|
-
var
|
|
22622
|
+
var import_node_fs27 = require("node:fs");
|
|
22343
22623
|
var import_node_os7 = require("node:os");
|
|
22344
|
-
var
|
|
22624
|
+
var import_node_path24 = require("node:path");
|
|
22345
22625
|
var import_node_child_process11 = require("node:child_process");
|
|
22346
22626
|
var import_node_util8 = require("node:util");
|
|
22347
22627
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process11.execFile);
|
|
@@ -22349,7 +22629,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
22349
22629
|
function installedClaudePluginVersion() {
|
|
22350
22630
|
try {
|
|
22351
22631
|
const file = JSON.parse(
|
|
22352
|
-
(0,
|
|
22632
|
+
(0, import_node_fs27.readFileSync)((0, import_node_path24.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
22353
22633
|
);
|
|
22354
22634
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
22355
22635
|
if (versions.length === 0) return void 0;
|
|
@@ -22370,13 +22650,13 @@ function worktreeRootSync() {
|
|
|
22370
22650
|
}
|
|
22371
22651
|
var gitignorePath = () => {
|
|
22372
22652
|
const root = worktreeRootSync();
|
|
22373
|
-
return root === null ? null : (0,
|
|
22653
|
+
return root === null ? null : (0, import_node_path24.join)(root, ".gitignore");
|
|
22374
22654
|
};
|
|
22375
22655
|
function readGitignore() {
|
|
22376
22656
|
const path2 = gitignorePath();
|
|
22377
22657
|
if (path2 === null) return null;
|
|
22378
22658
|
try {
|
|
22379
|
-
return (0,
|
|
22659
|
+
return (0, import_node_fs27.readFileSync)(path2, "utf8");
|
|
22380
22660
|
} catch {
|
|
22381
22661
|
return null;
|
|
22382
22662
|
}
|
|
@@ -22385,7 +22665,7 @@ function writeGitignore(content) {
|
|
|
22385
22665
|
const path2 = gitignorePath();
|
|
22386
22666
|
if (path2 === null) return false;
|
|
22387
22667
|
try {
|
|
22388
|
-
(0,
|
|
22668
|
+
(0, import_node_fs27.writeFileSync)(path2, content, "utf8");
|
|
22389
22669
|
return true;
|
|
22390
22670
|
} catch {
|
|
22391
22671
|
return false;
|
|
@@ -22409,7 +22689,7 @@ async function repoRoot() {
|
|
|
22409
22689
|
}
|
|
22410
22690
|
function hasRepoLocalWorktrees() {
|
|
22411
22691
|
const root = worktreeRootSync();
|
|
22412
|
-
return root !== null && (0,
|
|
22692
|
+
return root !== null && (0, import_node_fs27.existsSync)((0, import_node_path24.join)(root, ".worktrees"));
|
|
22413
22693
|
}
|
|
22414
22694
|
|
|
22415
22695
|
// src/index.ts
|
|
@@ -22582,19 +22862,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
22582
22862
|
});
|
|
22583
22863
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
22584
22864
|
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) => {
|
|
22585
|
-
const path2 = (0,
|
|
22586
|
-
const current = (0,
|
|
22865
|
+
const path2 = (0, import_node_path25.join)(process.cwd(), ".gitignore");
|
|
22866
|
+
const current = (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null;
|
|
22587
22867
|
const plan = planManagedGitignore(current);
|
|
22588
22868
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
22589
22869
|
if (opts.json) {
|
|
22590
|
-
if (opts.write && plan.changed) (0,
|
|
22870
|
+
if (opts.write && plan.changed) (0, import_node_fs28.writeFileSync)(path2, plan.content, "utf8");
|
|
22591
22871
|
console.log(JSON.stringify(plan, null, 2));
|
|
22592
22872
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
22593
22873
|
return;
|
|
22594
22874
|
}
|
|
22595
22875
|
if (opts.write) {
|
|
22596
22876
|
if (plan.changed) {
|
|
22597
|
-
(0,
|
|
22877
|
+
(0, import_node_fs28.writeFileSync)(path2, plan.content, "utf8");
|
|
22598
22878
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
22599
22879
|
} else {
|
|
22600
22880
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -22771,13 +23051,8 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
22771
23051
|
});
|
|
22772
23052
|
});
|
|
22773
23053
|
}
|
|
22774
|
-
async function primaryCheckoutRoot(
|
|
22775
|
-
|
|
22776
|
-
const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
22777
|
-
return out ? (0, import_node_path23.dirname)(out) : void 0;
|
|
22778
|
-
} catch {
|
|
22779
|
-
return void 0;
|
|
22780
|
-
}
|
|
23054
|
+
async function primaryCheckoutRoot(from) {
|
|
23055
|
+
return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
22781
23056
|
}
|
|
22782
23057
|
function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
22783
23058
|
return {
|
|
@@ -22789,26 +23064,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
22789
23064
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
22790
23065
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
22791
23066
|
const take = () => {
|
|
22792
|
-
const fd = (0,
|
|
23067
|
+
const fd = (0, import_node_fs28.openSync)(lockPath, "wx");
|
|
22793
23068
|
try {
|
|
22794
|
-
(0,
|
|
23069
|
+
(0, import_node_fs28.writeSync)(fd, String(Date.now()));
|
|
22795
23070
|
} finally {
|
|
22796
|
-
(0,
|
|
23071
|
+
(0, import_node_fs28.closeSync)(fd);
|
|
22797
23072
|
}
|
|
22798
23073
|
return () => {
|
|
22799
23074
|
try {
|
|
22800
|
-
(0,
|
|
23075
|
+
(0, import_node_fs28.rmSync)(lockPath, { force: true });
|
|
22801
23076
|
} catch {
|
|
22802
23077
|
}
|
|
22803
23078
|
};
|
|
22804
23079
|
};
|
|
22805
23080
|
try {
|
|
22806
|
-
(0,
|
|
23081
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(lockPath), { recursive: true });
|
|
22807
23082
|
return take();
|
|
22808
23083
|
} catch {
|
|
22809
23084
|
try {
|
|
22810
|
-
if (Date.now() - (0,
|
|
22811
|
-
(0,
|
|
23085
|
+
if (Date.now() - (0, import_node_fs28.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
23086
|
+
(0, import_node_fs28.rmSync)(lockPath, { force: true });
|
|
22812
23087
|
return take();
|
|
22813
23088
|
}
|
|
22814
23089
|
} catch {
|
|
@@ -22841,7 +23116,7 @@ withExamples(mutating(
|
|
|
22841
23116
|
} else if (o.claim || o.for) {
|
|
22842
23117
|
return fail("worktree create: --claim/--for need an issue-ref argument (e.g. worktree create 2687 --claim)");
|
|
22843
23118
|
}
|
|
22844
|
-
const repoRoot2 =
|
|
23119
|
+
const repoRoot2 = await primaryCheckoutRoot(process.cwd()) ?? process.cwd();
|
|
22845
23120
|
const wtPath = o.path ?? defaultWorktreePath(repoRoot2, branch);
|
|
22846
23121
|
const { base, fetchBranch } = resolveWorktreeBase(fromRef, o.remote);
|
|
22847
23122
|
if (fetchBranch) {
|
|
@@ -22996,6 +23271,7 @@ registerSecretsCommands(program2);
|
|
|
22996
23271
|
registerEdgeCommands(program2);
|
|
22997
23272
|
registerBoxCommands(program2);
|
|
22998
23273
|
registerSchedulesCommands(program2);
|
|
23274
|
+
registerSchedulesLiftCommand(program2);
|
|
22999
23275
|
var docs = program2.command("docs").description("generated docs surfaces \u2014 the routing index (org knowledge layer)");
|
|
23000
23276
|
docs.command("index").description("regenerate docs/index.md from the docs/ tree (--write, the default) or fail on drift (--check) \u2014 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) => {
|
|
23001
23277
|
try {
|
|
@@ -23228,7 +23504,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
23228
23504
|
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`);
|
|
23229
23505
|
if (o.secretsFile) {
|
|
23230
23506
|
try {
|
|
23231
|
-
vars.push(`secrets=${(0,
|
|
23507
|
+
vars.push(`secrets=${(0, import_node_fs28.readFileSync)(o.secretsFile, "utf8")}`);
|
|
23232
23508
|
} catch (e) {
|
|
23233
23509
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
23234
23510
|
}
|
|
@@ -23512,7 +23788,7 @@ function resolveCreateType(raw, command) {
|
|
|
23512
23788
|
}
|
|
23513
23789
|
var issue = program2.command("issue").description("issues \u2014 reliable create with structured output");
|
|
23514
23790
|
withExamples(mutating(
|
|
23515
|
-
issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").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, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 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("--
|
|
23791
|
+
issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").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, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 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("--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"),
|
|
23516
23792
|
// --dry-run/--validate-only plan: validate --type + --priority (mirrors the action — a bad enum fails
|
|
23517
23793
|
// ERR_BAD_ENUM, a missing priority defaults to medium) then echo the resolved create intent. Refs
|
|
23518
23794
|
// (`--parent`) and title-source are validated by the action on a real run.
|
|
@@ -23527,23 +23803,14 @@ withExamples(mutating(
|
|
|
23527
23803
|
let body;
|
|
23528
23804
|
let title;
|
|
23529
23805
|
let issueType;
|
|
23530
|
-
let northStarSlug;
|
|
23531
23806
|
let extraLabels = [];
|
|
23532
23807
|
try {
|
|
23533
23808
|
issueType = resolveCreateType(o.type, "issue create");
|
|
23534
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
23535
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
23536
|
-
if (o.northStar !== void 0) {
|
|
23537
|
-
northStarSlug = normalizeNorthStarSlug(o.northStar);
|
|
23538
|
-
body = appendNorthStarLine(body, northStarSlug);
|
|
23539
|
-
}
|
|
23809
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
23810
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
23540
23811
|
if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
|
|
23541
23812
|
priority = resolveCreatePriority(o.priority, "issue create");
|
|
23542
23813
|
extraLabels = [...o.label ?? []];
|
|
23543
|
-
if (northStarSlug) {
|
|
23544
|
-
const nsLabel = northStarLabel(northStarSlug);
|
|
23545
|
-
if (!extraLabels.includes(nsLabel)) extraLabels.push(nsLabel);
|
|
23546
|
-
}
|
|
23547
23814
|
args = buildIssueArgs({
|
|
23548
23815
|
type: issueType,
|
|
23549
23816
|
title,
|
|
@@ -23585,7 +23852,6 @@ withExamples(mutating(
|
|
|
23585
23852
|
priority,
|
|
23586
23853
|
projectItemId,
|
|
23587
23854
|
onBoard,
|
|
23588
|
-
...northStarSlug ? { northStarSlug } : {},
|
|
23589
23855
|
...parentLinkFields(parent, parentLinkError)
|
|
23590
23856
|
}));
|
|
23591
23857
|
}), [
|
|
@@ -23677,7 +23943,7 @@ issue.command("comment <ref>").description("post a Markdown comment to an issue
|
|
|
23677
23943
|
}
|
|
23678
23944
|
let body;
|
|
23679
23945
|
try {
|
|
23680
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
23946
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
23681
23947
|
} catch (e) {
|
|
23682
23948
|
return fail(`issue comment: ${e.message}`);
|
|
23683
23949
|
}
|
|
@@ -23737,8 +24003,8 @@ program2.command("report").description("file a friction report on the Hub board
|
|
|
23737
24003
|
let title;
|
|
23738
24004
|
const sourceRepo = o.repo ?? await resolveRepo(void 0);
|
|
23739
24005
|
try {
|
|
23740
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
23741
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
24006
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
24007
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
23742
24008
|
priority = resolveCreatePriority(o.priority, "report");
|
|
23743
24009
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
23744
24010
|
throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
@@ -23787,8 +24053,8 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
23787
24053
|
let args;
|
|
23788
24054
|
try {
|
|
23789
24055
|
skill = assertSkillName(o.skill);
|
|
23790
|
-
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
23791
|
-
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
24056
|
+
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
24057
|
+
const rawTitle = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
23792
24058
|
title = buildSkillLessonTitle(skill, rawTitle);
|
|
23793
24059
|
priority = resolveCreatePriority(o.priority, "skill-lesson");
|
|
23794
24060
|
body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
|
|
@@ -23839,8 +24105,8 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
23839
24105
|
let body;
|
|
23840
24106
|
let title;
|
|
23841
24107
|
try {
|
|
23842
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
23843
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
24108
|
+
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
24109
|
+
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
23844
24110
|
} catch (e) {
|
|
23845
24111
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
23846
24112
|
}
|
|
@@ -23875,11 +24141,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
23875
24141
|
}
|
|
23876
24142
|
});
|
|
23877
24143
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
23878
|
-
const wfDir = (0,
|
|
23879
|
-
if (!(0,
|
|
23880
|
-
return (0,
|
|
24144
|
+
const wfDir = (0, import_node_path25.join)(cwd, ".github", "workflows");
|
|
24145
|
+
if (!(0, import_node_fs28.existsSync)(wfDir)) return [];
|
|
24146
|
+
return (0, import_node_fs28.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
23881
24147
|
try {
|
|
23882
|
-
return workflowReportsPrChecks((0,
|
|
24148
|
+
return workflowReportsPrChecks((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(wfDir, name), "utf8"));
|
|
23883
24149
|
} catch {
|
|
23884
24150
|
return true;
|
|
23885
24151
|
}
|
|
@@ -23906,16 +24172,16 @@ function ciAuditDeps() {
|
|
|
23906
24172
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
23907
24173
|
readSeedFile: (path2) => {
|
|
23908
24174
|
if (!root) return null;
|
|
23909
|
-
const fullPath = (0,
|
|
23910
|
-
return (0,
|
|
24175
|
+
const fullPath = (0, import_node_path25.join)(root, path2);
|
|
24176
|
+
return (0, import_node_fs28.existsSync)(fullPath) ? (0, import_node_fs28.readFileSync)(fullPath, "utf8") : null;
|
|
23911
24177
|
}
|
|
23912
24178
|
};
|
|
23913
24179
|
}
|
|
23914
24180
|
function hubRoot() {
|
|
23915
|
-
const fromPkg = (0,
|
|
24181
|
+
const fromPkg = (0, import_node_path25.join)(__dirname, "..", "..");
|
|
23916
24182
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
23917
|
-
if ((0,
|
|
23918
|
-
if ((0,
|
|
24183
|
+
if ((0, import_node_fs28.existsSync)((0, import_node_path25.join)(fromPkg, marker))) return fromPkg;
|
|
24184
|
+
if ((0, import_node_fs28.existsSync)((0, import_node_path25.join)(process.cwd(), marker))) return process.cwd();
|
|
23919
24185
|
return null;
|
|
23920
24186
|
}
|
|
23921
24187
|
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) => {
|
|
@@ -24133,7 +24399,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
24133
24399
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
24134
24400
|
beforeWorktrees,
|
|
24135
24401
|
startingPath,
|
|
24136
|
-
pathExists: (p) => (0,
|
|
24402
|
+
pathExists: (p) => (0, import_node_fs28.existsSync)(p),
|
|
24137
24403
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
24138
24404
|
teardownWorktreeStage,
|
|
24139
24405
|
deferredStore,
|
|
@@ -24243,8 +24509,8 @@ function trainApplyDeps() {
|
|
|
24243
24509
|
// Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
|
|
24244
24510
|
announce: (args) => announceRelease({
|
|
24245
24511
|
run: async (file, cmdArgs) => (await execFileP2(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
|
|
24246
|
-
readFile: (path2) => (0,
|
|
24247
|
-
removeFile: (path2) => (0,
|
|
24512
|
+
readFile: (path2) => (0, import_promises7.readFile)(path2, "utf8"),
|
|
24513
|
+
removeFile: (path2) => (0, import_promises7.unlink)(path2)
|
|
24248
24514
|
}, args),
|
|
24249
24515
|
fetchEdgeDomains: async (slug) => {
|
|
24250
24516
|
const proj = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig()));
|
|
@@ -24473,10 +24739,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
24473
24739
|
targets = resolution.targets;
|
|
24474
24740
|
}
|
|
24475
24741
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
24476
|
-
const fileMatrix = (0,
|
|
24742
|
+
const fileMatrix = (0, import_node_fs28.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs28.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
24477
24743
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
24478
24744
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
24479
|
-
const fileContracts = (0,
|
|
24745
|
+
const fileContracts = (0, import_node_fs28.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs28.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
24480
24746
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
24481
24747
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
24482
24748
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -24509,16 +24775,16 @@ function directoryBytes(path2) {
|
|
|
24509
24775
|
let total = 0;
|
|
24510
24776
|
let entries;
|
|
24511
24777
|
try {
|
|
24512
|
-
entries = (0,
|
|
24778
|
+
entries = (0, import_node_fs28.readdirSync)(path2, { withFileTypes: true });
|
|
24513
24779
|
} catch {
|
|
24514
24780
|
return 0;
|
|
24515
24781
|
}
|
|
24516
24782
|
for (const entry of entries) {
|
|
24517
|
-
const child2 = (0,
|
|
24783
|
+
const child2 = (0, import_node_path25.join)(path2, entry.name);
|
|
24518
24784
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
24519
24785
|
else {
|
|
24520
24786
|
try {
|
|
24521
|
-
total += (0,
|
|
24787
|
+
total += (0, import_node_fs28.statSync)(child2).size;
|
|
24522
24788
|
} catch {
|
|
24523
24789
|
}
|
|
24524
24790
|
}
|
|
@@ -24526,25 +24792,25 @@ function directoryBytes(path2) {
|
|
|
24526
24792
|
return total;
|
|
24527
24793
|
}
|
|
24528
24794
|
function listDirEntries(dir) {
|
|
24529
|
-
return (0,
|
|
24795
|
+
return (0, import_node_fs28.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
24530
24796
|
}
|
|
24531
24797
|
function readInstalledPluginRefs(home) {
|
|
24532
24798
|
const p = installedPluginsPath(home);
|
|
24533
|
-
if (!(0,
|
|
24799
|
+
if (!(0, import_node_fs28.existsSync)(p)) return [];
|
|
24534
24800
|
try {
|
|
24535
|
-
return installedPluginPaths((0,
|
|
24801
|
+
return installedPluginPaths((0, import_node_fs28.readFileSync)(p, "utf8"));
|
|
24536
24802
|
} catch {
|
|
24537
24803
|
return null;
|
|
24538
24804
|
}
|
|
24539
24805
|
}
|
|
24540
24806
|
function pluginCacheFsDeps(home, dirBytes) {
|
|
24541
24807
|
return {
|
|
24542
|
-
exists: (p) => (0,
|
|
24543
|
-
listVersionDirs: (root) => (0,
|
|
24808
|
+
exists: (p) => (0, import_node_fs28.existsSync)(p),
|
|
24809
|
+
listVersionDirs: (root) => (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
24544
24810
|
dirBytes,
|
|
24545
|
-
listStagingDirs: (root) => (0,
|
|
24811
|
+
listStagingDirs: (root) => (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
24546
24812
|
try {
|
|
24547
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
24813
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path25.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs28.statSync)(p).mtimeMs) };
|
|
24548
24814
|
} catch {
|
|
24549
24815
|
return { name: d.name, mtimeMs: Date.now() };
|
|
24550
24816
|
}
|
|
@@ -24558,10 +24824,10 @@ function stagingApplyFsGuard(home) {
|
|
|
24558
24824
|
return {
|
|
24559
24825
|
referencedPaths: () => readInstalledPluginRefs(home),
|
|
24560
24826
|
mtimeMs: (name) => {
|
|
24561
|
-
const p = (0,
|
|
24562
|
-
if (!(0,
|
|
24827
|
+
const p = (0, import_node_path25.join)(stagingRoot, name);
|
|
24828
|
+
if (!(0, import_node_fs28.existsSync)(p)) return null;
|
|
24563
24829
|
try {
|
|
24564
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
24830
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs28.statSync)(q).mtimeMs);
|
|
24565
24831
|
} catch {
|
|
24566
24832
|
return null;
|
|
24567
24833
|
}
|
|
@@ -24577,7 +24843,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
24577
24843
|
{ withBytes: true }
|
|
24578
24844
|
);
|
|
24579
24845
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
24580
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
24846
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs28.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os8.homedir)())) : void 0;
|
|
24581
24847
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
24582
24848
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
24583
24849
|
else console.log(renderPluginCachePlan(plan, result));
|