@mutmutco/cli 3.34.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 +526 -183
- 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
|
}
|
|
@@ -9025,7 +9030,7 @@ function commandLadderHint() {
|
|
|
9025
9030
|
}
|
|
9026
9031
|
|
|
9027
9032
|
// src/index.ts
|
|
9028
|
-
var
|
|
9033
|
+
var import_node_path25 = require("node:path");
|
|
9029
9034
|
|
|
9030
9035
|
// src/merge-ci-policy.ts
|
|
9031
9036
|
function resolveMergeCiPolicy(input) {
|
|
@@ -9315,6 +9320,27 @@ function workflowTriggersPullRequest(yaml) {
|
|
|
9315
9320
|
if (!onBlock) return false;
|
|
9316
9321
|
return /\bpull_request\b/.test(onBlock);
|
|
9317
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
|
+
}
|
|
9318
9344
|
function workflowReportsPrChecks(yaml) {
|
|
9319
9345
|
const onBlock = extractOnBlock(yaml);
|
|
9320
9346
|
if (!onBlock) return false;
|
|
@@ -9330,6 +9356,9 @@ function extractOnBlock(yaml) {
|
|
|
9330
9356
|
if (/^(["']?)on\1:\s*$/.test(line)) {
|
|
9331
9357
|
inOn = true;
|
|
9332
9358
|
onIndent = 0;
|
|
9359
|
+
} else if (/^(["']?)on\1:\s+#/.test(line)) {
|
|
9360
|
+
inOn = true;
|
|
9361
|
+
onIndent = 0;
|
|
9333
9362
|
} else if (/^(["']?)on\1:\s+\S/.test(line)) {
|
|
9334
9363
|
return line;
|
|
9335
9364
|
}
|
|
@@ -9476,6 +9505,63 @@ function planManagedGitignore(current) {
|
|
|
9476
9505
|
return { changed, content, added, removed };
|
|
9477
9506
|
}
|
|
9478
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
|
+
|
|
9479
9565
|
// src/bootstrap-apply.ts
|
|
9480
9566
|
function parseOwnerRepo(repo) {
|
|
9481
9567
|
const trimmed = repo.trim();
|
|
@@ -9489,6 +9575,7 @@ function parseOwnerRepo(repo) {
|
|
|
9489
9575
|
return { owner, name, slug: name.toLowerCase(), fullName: `${owner}/${name}` };
|
|
9490
9576
|
}
|
|
9491
9577
|
var DEFAULT_GATE_CMD = "npm run check";
|
|
9578
|
+
var DEFAULT_GATE_MAX_SECONDS = "300";
|
|
9492
9579
|
var DEFAULT_GATE_PY_VERSION = "3.11";
|
|
9493
9580
|
var GATE_RUNTIME_DEFAULTS = {
|
|
9494
9581
|
node: { cmd: DEFAULT_GATE_CMD, install: "npm ci" },
|
|
@@ -9502,7 +9589,10 @@ function gateSeedVars(cls, releaseTrack, runtime = "node") {
|
|
|
9502
9589
|
GATE_INSTALL_CMD: rt.install,
|
|
9503
9590
|
GATE_WORKDIR: ".",
|
|
9504
9591
|
GATE_CACHE_DEP_PATH: "package-lock.json",
|
|
9505
|
-
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
|
|
9506
9596
|
};
|
|
9507
9597
|
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
9508
9598
|
if (track === "trunk") {
|
|
@@ -9548,6 +9638,8 @@ function gateConfigToVars(gate) {
|
|
|
9548
9638
|
if (typeof gate.workdir === "string" && gate.workdir.trim()) out.GATE_WORKDIR = gate.workdir;
|
|
9549
9639
|
if (typeof gate.cacheDepPath === "string" && gate.cacheDepPath.trim()) out.GATE_CACHE_DEP_PATH = gate.cacheDepPath;
|
|
9550
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();
|
|
9551
9643
|
return out;
|
|
9552
9644
|
}
|
|
9553
9645
|
function seedMatchesDeployModel(seed, deployModel) {
|
|
@@ -9609,10 +9701,10 @@ function labelsToPrune(orgLabelNames) {
|
|
|
9609
9701
|
const org = new Set(orgLabelNames);
|
|
9610
9702
|
return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
|
|
9611
9703
|
}
|
|
9612
|
-
function resolveSeedContent(seed, vars,
|
|
9613
|
-
if (seed.source === "self") return
|
|
9704
|
+
function resolveSeedContent(seed, vars, readFile6) {
|
|
9705
|
+
if (seed.source === "self") return readFile6(seed.target);
|
|
9614
9706
|
if (seed.source.startsWith("seed:")) {
|
|
9615
|
-
const tmpl =
|
|
9707
|
+
const tmpl = readFile6(`skills/bootstrap/seeds/${seed.source.slice("seed:".length)}`);
|
|
9616
9708
|
return tmpl == null ? null : renderSeed(tmpl, vars);
|
|
9617
9709
|
}
|
|
9618
9710
|
return null;
|
|
@@ -10028,11 +10120,22 @@ async function auditRepoCi(repo, deps) {
|
|
|
10028
10120
|
const allPaths = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
|
|
10029
10121
|
const prPaths = await filterPullRequestTriggered(deps, repo, baseBranch, allPaths);
|
|
10030
10122
|
const missing = [];
|
|
10123
|
+
const gateBodies = [];
|
|
10031
10124
|
for (const path2 of prPaths) {
|
|
10032
10125
|
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
10033
10126
|
if (body == null) continue;
|
|
10127
|
+
if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
|
|
10034
10128
|
if (!/^\s*concurrency:/m.test(body) || !/cancel-in-progress:\s*true/.test(body)) missing.push(path2);
|
|
10035
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
|
+
}
|
|
10036
10139
|
if (prPaths.length > 0) {
|
|
10037
10140
|
checks.push({
|
|
10038
10141
|
ok: missing.length === 0,
|
|
@@ -12338,6 +12441,8 @@ function parseVerifyBroker(stdout) {
|
|
|
12338
12441
|
}
|
|
12339
12442
|
|
|
12340
12443
|
// src/train-apply.ts
|
|
12444
|
+
var import_node_fs16 = require("node:fs");
|
|
12445
|
+
var import_node_path14 = require("node:path");
|
|
12341
12446
|
function resolveDeployModel2(meta, repo) {
|
|
12342
12447
|
const m = meta?.deployModel;
|
|
12343
12448
|
if (isDeployModel(m)) return m;
|
|
@@ -13181,6 +13286,34 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
13181
13286
|
}
|
|
13182
13287
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
13183
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
|
+
}
|
|
13184
13317
|
async function preflight(deps, ctx, stage, meta) {
|
|
13185
13318
|
const model = resolveDeployModel2(meta, ctx.repo);
|
|
13186
13319
|
if (model === "content") {
|
|
@@ -13190,6 +13323,7 @@ async function preflight(deps, ctx, stage, meta) {
|
|
|
13190
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`);
|
|
13191
13324
|
}
|
|
13192
13325
|
await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
|
|
13326
|
+
enforceGateBudget(deps, ctx.repo);
|
|
13193
13327
|
return model;
|
|
13194
13328
|
}
|
|
13195
13329
|
async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix, realignMessage) {
|
|
@@ -15030,8 +15164,8 @@ function renderAccessReport(report) {
|
|
|
15030
15164
|
}
|
|
15031
15165
|
|
|
15032
15166
|
// src/docs-index-command.ts
|
|
15033
|
-
var
|
|
15034
|
-
var
|
|
15167
|
+
var import_node_fs17 = require("node:fs");
|
|
15168
|
+
var import_node_path15 = require("node:path");
|
|
15035
15169
|
var DOCS_INDEX_PATH = "docs/index.md";
|
|
15036
15170
|
function isRoutableDocsPath(relPath) {
|
|
15037
15171
|
const normalized = relPath.replace(/\\/g, "/");
|
|
@@ -15109,25 +15243,25 @@ function walkMarkdown(dir) {
|
|
|
15109
15243
|
const stack = [dir];
|
|
15110
15244
|
while (stack.length) {
|
|
15111
15245
|
const current = stack.pop();
|
|
15112
|
-
for (const entry of (0,
|
|
15113
|
-
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);
|
|
15114
15248
|
if (entry.isDirectory()) {
|
|
15115
15249
|
stack.push(full);
|
|
15116
15250
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
15117
|
-
out.push((0,
|
|
15251
|
+
out.push((0, import_node_path15.relative)(dir, full).split(import_node_path15.sep).join("/"));
|
|
15118
15252
|
}
|
|
15119
15253
|
}
|
|
15120
15254
|
}
|
|
15121
15255
|
return out;
|
|
15122
15256
|
}
|
|
15123
15257
|
function createDocsIndexDeps(repoRoot2) {
|
|
15124
|
-
const docsDir = (0,
|
|
15125
|
-
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);
|
|
15126
15260
|
return {
|
|
15127
|
-
listDocs: () => (0,
|
|
15128
|
-
readDoc: (relPath) => (0,
|
|
15129
|
-
readIndex: () => (0,
|
|
15130
|
-
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")
|
|
15131
15265
|
};
|
|
15132
15266
|
}
|
|
15133
15267
|
|
|
@@ -16054,8 +16188,8 @@ function writeError(res) {
|
|
|
16054
16188
|
}
|
|
16055
16189
|
|
|
16056
16190
|
// src/secrets-commands.ts
|
|
16057
|
-
var
|
|
16058
|
-
var
|
|
16191
|
+
var import_node_fs18 = require("node:fs");
|
|
16192
|
+
var import_node_path16 = require("node:path");
|
|
16059
16193
|
var import_node_os5 = require("node:os");
|
|
16060
16194
|
|
|
16061
16195
|
// src/project-runtime.ts
|
|
@@ -16179,18 +16313,18 @@ function collectMap(value, previous = []) {
|
|
|
16179
16313
|
return [...previous, value];
|
|
16180
16314
|
}
|
|
16181
16315
|
async function decryptRailsCredentials(input) {
|
|
16182
|
-
const appDir = (0,
|
|
16316
|
+
const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
|
|
16183
16317
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
16184
16318
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
16185
|
-
const credentialsPath = (0,
|
|
16186
|
-
const masterKeyPath = (0,
|
|
16319
|
+
const credentialsPath = (0, import_node_path16.resolve)(appDir, credentialsFile);
|
|
16320
|
+
const masterKeyPath = (0, import_node_path16.resolve)(appDir, masterKeyFile);
|
|
16187
16321
|
const env = {
|
|
16188
16322
|
...process.env,
|
|
16189
16323
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
16190
16324
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
16191
16325
|
};
|
|
16192
|
-
if ((0,
|
|
16193
|
-
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();
|
|
16194
16328
|
}
|
|
16195
16329
|
const script = [
|
|
16196
16330
|
'require "json"',
|
|
@@ -16200,9 +16334,9 @@ async function decryptRailsCredentials(input) {
|
|
|
16200
16334
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
16201
16335
|
"puts JSON.generate(config.config)"
|
|
16202
16336
|
].join("\n");
|
|
16203
|
-
const scriptDir = (0,
|
|
16204
|
-
const scriptPath = (0,
|
|
16205
|
-
(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");
|
|
16206
16340
|
try {
|
|
16207
16341
|
const args = ["exec", "ruby", scriptPath];
|
|
16208
16342
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -16214,7 +16348,7 @@ async function decryptRailsCredentials(input) {
|
|
|
16214
16348
|
});
|
|
16215
16349
|
return JSON.parse(stdout);
|
|
16216
16350
|
} finally {
|
|
16217
|
-
(0,
|
|
16351
|
+
(0, import_node_fs18.rmSync)(scriptDir, { recursive: true, force: true });
|
|
16218
16352
|
}
|
|
16219
16353
|
}
|
|
16220
16354
|
async function readSecretStdin() {
|
|
@@ -16289,7 +16423,7 @@ function registerSecretsCommands(program3) {
|
|
|
16289
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) => {
|
|
16290
16424
|
let body;
|
|
16291
16425
|
try {
|
|
16292
|
-
body = (0,
|
|
16426
|
+
body = (0, import_node_fs18.readFileSync)((0, import_node_path16.resolve)(o.file), "utf8");
|
|
16293
16427
|
} catch (e) {
|
|
16294
16428
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
16295
16429
|
}
|
|
@@ -16392,7 +16526,7 @@ function registerSecretsCommands(program3) {
|
|
|
16392
16526
|
{
|
|
16393
16527
|
...d,
|
|
16394
16528
|
decryptRailsCredentials,
|
|
16395
|
-
removeFile: (path2) => (0,
|
|
16529
|
+
removeFile: (path2) => (0, import_node_fs18.unlinkSync)((0, import_node_path16.resolve)(o.appDir ?? process.cwd(), path2))
|
|
16396
16530
|
},
|
|
16397
16531
|
{
|
|
16398
16532
|
repo: o.repo,
|
|
@@ -16911,6 +17045,10 @@ function spliceDoc(docText, generatedSection) {
|
|
|
16911
17045
|
}
|
|
16912
17046
|
return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
|
|
16913
17047
|
}
|
|
17048
|
+
var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
|
|
17049
|
+
function isHarbourLlmLauncher(executor) {
|
|
17050
|
+
return HARBOUR_LLM_LAUNCHERS.has(executor);
|
|
17051
|
+
}
|
|
16914
17052
|
function splitCadence(cadence) {
|
|
16915
17053
|
return cadence.split("+").map((c) => c.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
16916
17054
|
}
|
|
@@ -16972,6 +17110,38 @@ function reconcileGithubActions(liveGithub, registry2, readRepos) {
|
|
|
16972
17110
|
function renderDrift(d) {
|
|
16973
17111
|
return `${d.class}: ${d.name} (${d.executor}) \u2014 ${d.detail}; remedy: ${d.remedy}`;
|
|
16974
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
|
+
}
|
|
16975
17145
|
function assembleReconciliation(githubEntries2, readRepos, registry2) {
|
|
16976
17146
|
if (registry2 === null) {
|
|
16977
17147
|
return {
|
|
@@ -17006,10 +17176,11 @@ async function listWorkflows(client, repo) {
|
|
|
17006
17176
|
}
|
|
17007
17177
|
}
|
|
17008
17178
|
async function repoWorkflowEntries(client, repo) {
|
|
17009
|
-
const
|
|
17179
|
+
const workflowsList = await listWorkflows(client, repo);
|
|
17010
17180
|
const entries = [];
|
|
17011
17181
|
const failures = [];
|
|
17012
|
-
|
|
17182
|
+
const workflows = [];
|
|
17183
|
+
for (const wf of workflowsList) {
|
|
17013
17184
|
if (wf?.state !== "active" || typeof wf.path !== "string" || !wf.path) continue;
|
|
17014
17185
|
if (!wf.path.startsWith(".github/workflows/")) continue;
|
|
17015
17186
|
try {
|
|
@@ -17019,6 +17190,9 @@ async function repoWorkflowEntries(client, repo) {
|
|
|
17019
17190
|
);
|
|
17020
17191
|
if (typeof contents?.content !== "string" || contents.encoding !== "base64") continue;
|
|
17021
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 });
|
|
17022
17196
|
const entry = workflowEntry(repo, wf.path, text);
|
|
17023
17197
|
if (entry) entries.push(entry);
|
|
17024
17198
|
} catch (e) {
|
|
@@ -17027,19 +17201,20 @@ async function repoWorkflowEntries(client, repo) {
|
|
|
17027
17201
|
else throw e;
|
|
17028
17202
|
}
|
|
17029
17203
|
}
|
|
17030
|
-
return { entries, failures };
|
|
17204
|
+
return { entries, failures, workflows };
|
|
17031
17205
|
}
|
|
17032
17206
|
async function githubEntries(client) {
|
|
17033
17207
|
const entries = [];
|
|
17034
17208
|
const incomplete = [];
|
|
17035
17209
|
const drift = [];
|
|
17036
17210
|
const readRepos = [];
|
|
17211
|
+
const workflows = [];
|
|
17037
17212
|
let repos;
|
|
17038
17213
|
try {
|
|
17039
17214
|
const listing = await client.restPaginate(`/orgs/${ORG}/repos?per_page=100`);
|
|
17040
17215
|
repos = listing.filter((r) => typeof r?.name === "string" && r.archived !== true).map((r) => r.name).sort();
|
|
17041
17216
|
} catch (e) {
|
|
17042
|
-
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 };
|
|
17043
17218
|
}
|
|
17044
17219
|
const results = await Promise.all(
|
|
17045
17220
|
repos.map(async (repo) => {
|
|
@@ -17055,12 +17230,15 @@ async function githubEntries(client) {
|
|
|
17055
17230
|
else {
|
|
17056
17231
|
readRepos.push(r.repo);
|
|
17057
17232
|
entries.push(...r.entries);
|
|
17233
|
+
workflows.push(...r.workflows);
|
|
17058
17234
|
if (r.failures.length) {
|
|
17059
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(", ")}`);
|
|
17060
17236
|
}
|
|
17061
17237
|
}
|
|
17062
17238
|
}
|
|
17063
|
-
|
|
17239
|
+
const reconciliation = [...strayCronDrifts(workflows), ...unlauncheredLlmDrifts(entries)];
|
|
17240
|
+
drift.push(...reconciliation.map(renderDrift));
|
|
17241
|
+
return { entries, incomplete, drift, reconciliation, readRepos };
|
|
17064
17242
|
}
|
|
17065
17243
|
async function awsJson(args) {
|
|
17066
17244
|
const run = async () => {
|
|
@@ -17095,7 +17273,8 @@ async function awsEntries() {
|
|
|
17095
17273
|
} catch (e) {
|
|
17096
17274
|
incomplete.push(`aws: scheduler listing failed \u2014 ${e.message}`);
|
|
17097
17275
|
}
|
|
17098
|
-
|
|
17276
|
+
const reconciliation = unlauncheredLlmDrifts(entries);
|
|
17277
|
+
return { entries, incomplete, drift: reconciliation.map(renderDrift), reconciliation };
|
|
17099
17278
|
}
|
|
17100
17279
|
async function defaultRegistryRead() {
|
|
17101
17280
|
return fetchSchedulesList(registryClientDeps(await loadConfig()));
|
|
@@ -17111,7 +17290,7 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
17111
17290
|
entries: sortEntries([...gh.entries, ...aws.entries, ...DECLARED_ENTRIES]),
|
|
17112
17291
|
incomplete: [...gh.incomplete, ...aws.incomplete, ...recon.incomplete],
|
|
17113
17292
|
drift: [...gh.drift, ...aws.drift, ...recon.driftLines],
|
|
17114
|
-
reconciliation: recon.reconciliation
|
|
17293
|
+
reconciliation: [...gh.reconciliation, ...aws.reconciliation, ...recon.reconciliation]
|
|
17115
17294
|
};
|
|
17116
17295
|
}
|
|
17117
17296
|
function warnIncomplete2(incomplete) {
|
|
@@ -17152,6 +17331,169 @@ function registerSchedulesCommands(program3) {
|
|
|
17152
17331
|
});
|
|
17153
17332
|
}
|
|
17154
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
|
+
|
|
17155
17497
|
// src/edge-tunnel.ts
|
|
17156
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])?)+$/;
|
|
17157
17499
|
var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
|
|
@@ -17611,7 +17953,7 @@ function registerQueryCommands(program3) {
|
|
|
17611
17953
|
}
|
|
17612
17954
|
|
|
17613
17955
|
// src/bootstrap-commands.ts
|
|
17614
|
-
var
|
|
17956
|
+
var import_node_fs19 = require("node:fs");
|
|
17615
17957
|
|
|
17616
17958
|
// src/bootstrap-verify.ts
|
|
17617
17959
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
@@ -18159,7 +18501,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18159
18501
|
client: defaultGitHubClient(),
|
|
18160
18502
|
projectMeta: meta,
|
|
18161
18503
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
18162
|
-
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,
|
|
18163
18505
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
18164
18506
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
18165
18507
|
requiredGcpApis: (() => {
|
|
@@ -18210,12 +18552,12 @@ function registerBootstrapCommands(program3) {
|
|
|
18210
18552
|
return fail(`bootstrap apply: ${e.message}`);
|
|
18211
18553
|
}
|
|
18212
18554
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
18213
|
-
if (!(0,
|
|
18214
|
-
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"));
|
|
18215
18557
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
18216
18558
|
const slug = parsedRepo.slug;
|
|
18217
18559
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
18218
|
-
const
|
|
18560
|
+
const readFile6 = (p) => (0, import_node_fs19.existsSync)(p) ? (0, import_node_fs19.readFileSync)(p, "utf8") : null;
|
|
18219
18561
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
18220
18562
|
const rawVars = {};
|
|
18221
18563
|
for (const value of cmdOpts.var ?? []) {
|
|
@@ -18293,7 +18635,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18293
18635
|
}
|
|
18294
18636
|
const planned = planSeedAction(resolved, exists);
|
|
18295
18637
|
const isBlock = resolved.source === "managed-block";
|
|
18296
|
-
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;
|
|
18297
18639
|
const action = reconcileSeedAction(planned, content, isBlock, remoteContent);
|
|
18298
18640
|
actions.push(action);
|
|
18299
18641
|
if (o.execute && (action.action === "create" || action.action === "update")) {
|
|
@@ -18348,7 +18690,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18348
18690
|
}
|
|
18349
18691
|
const rulesetSeed = manifest.seeds.find((s) => s.target === ".github/rulesets/mmi-product-required-checks.json");
|
|
18350
18692
|
if (rulesetSeed) {
|
|
18351
|
-
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);
|
|
18352
18694
|
if (rulesetContent) {
|
|
18353
18695
|
try {
|
|
18354
18696
|
const activation = await activateProductRuleset(repo, stripRulesetComment(rulesetContent), defaultGitHubClient());
|
|
@@ -18411,12 +18753,12 @@ LIVE apply to ${repo}:
|
|
|
18411
18753
|
}
|
|
18412
18754
|
|
|
18413
18755
|
// src/stage-commands.ts
|
|
18414
|
-
var
|
|
18415
|
-
var
|
|
18756
|
+
var import_node_fs21 = require("node:fs");
|
|
18757
|
+
var import_node_path19 = require("node:path");
|
|
18416
18758
|
|
|
18417
18759
|
// src/port-registry.ts
|
|
18418
|
-
var
|
|
18419
|
-
var
|
|
18760
|
+
var import_node_fs20 = require("node:fs");
|
|
18761
|
+
var import_node_path18 = require("node:path");
|
|
18420
18762
|
|
|
18421
18763
|
// ../infra/port-geometry.mjs
|
|
18422
18764
|
var PORT_BLOCK = 100;
|
|
@@ -18430,8 +18772,8 @@ function nextPortBlock(registry2) {
|
|
|
18430
18772
|
return [base, base + PORT_SPAN];
|
|
18431
18773
|
}
|
|
18432
18774
|
function loadPortRegistry(path2) {
|
|
18433
|
-
if (!(0,
|
|
18434
|
-
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"));
|
|
18435
18777
|
const out = {};
|
|
18436
18778
|
for (const [key, value] of Object.entries(raw)) {
|
|
18437
18779
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -18445,9 +18787,9 @@ function ensurePortRange(repo, path2) {
|
|
|
18445
18787
|
const existing = registry2[repo];
|
|
18446
18788
|
if (existing) return existing;
|
|
18447
18789
|
const range = nextPortBlock(registry2);
|
|
18448
|
-
const raw = (0,
|
|
18790
|
+
const raw = (0, import_node_fs20.existsSync)(path2) ? JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8")) : {};
|
|
18449
18791
|
raw[repo] = range;
|
|
18450
|
-
(0,
|
|
18792
|
+
(0, import_node_fs20.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
18451
18793
|
return range;
|
|
18452
18794
|
}
|
|
18453
18795
|
function portCursorSeed(registry2) {
|
|
@@ -18469,22 +18811,22 @@ function existingPortRange(repo, registry2) {
|
|
|
18469
18811
|
return registry2[repo] ?? null;
|
|
18470
18812
|
}
|
|
18471
18813
|
function portRangeInfraAt(root, source) {
|
|
18472
|
-
const registryPath = (0,
|
|
18473
|
-
const ddbScriptPath = (0,
|
|
18474
|
-
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;
|
|
18475
18817
|
return { root, source, registryPath, ddbScriptPath };
|
|
18476
18818
|
}
|
|
18477
18819
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
18478
18820
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
18479
18821
|
if (direct) return direct;
|
|
18480
|
-
for (let dir = cwd; ; dir = (0,
|
|
18481
|
-
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");
|
|
18482
18824
|
if (sibling) return sibling;
|
|
18483
|
-
const parent = (0,
|
|
18825
|
+
const parent = (0, import_node_path18.dirname)(dir);
|
|
18484
18826
|
if (parent === dir) break;
|
|
18485
18827
|
}
|
|
18486
18828
|
if (packageDir) {
|
|
18487
|
-
const pkgRoot = (0,
|
|
18829
|
+
const pkgRoot = (0, import_node_path18.join)(packageDir, "..", "..");
|
|
18488
18830
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
18489
18831
|
if (pkgFrom) return pkgFrom;
|
|
18490
18832
|
}
|
|
@@ -18660,8 +19002,8 @@ function registerStageCommands(program3) {
|
|
|
18660
19002
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
18661
19003
|
return decideStage({
|
|
18662
19004
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
18663
|
-
hasCompose: (0,
|
|
18664
|
-
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"))
|
|
18665
19007
|
});
|
|
18666
19008
|
}
|
|
18667
19009
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -19099,8 +19441,8 @@ function registerBoardCommands(program3) {
|
|
|
19099
19441
|
}
|
|
19100
19442
|
|
|
19101
19443
|
// src/merge-cleanup.ts
|
|
19102
|
-
var
|
|
19103
|
-
var
|
|
19444
|
+
var import_node_fs22 = require("node:fs");
|
|
19445
|
+
var import_promises5 = require("node:fs/promises");
|
|
19104
19446
|
var import_node_child_process10 = require("node:child_process");
|
|
19105
19447
|
|
|
19106
19448
|
// src/board-advance.ts
|
|
@@ -19186,23 +19528,23 @@ function boardAdvanceFailureMessage(result) {
|
|
|
19186
19528
|
}
|
|
19187
19529
|
|
|
19188
19530
|
// src/deferred-registry-store.ts
|
|
19189
|
-
var
|
|
19190
|
-
var
|
|
19531
|
+
var import_promises4 = require("node:fs/promises");
|
|
19532
|
+
var import_node_path20 = require("node:path");
|
|
19191
19533
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
19192
19534
|
async function atomicWrite(target, contents) {
|
|
19193
19535
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
19194
|
-
await (0,
|
|
19536
|
+
await (0, import_promises4.writeFile)(tmp, contents, "utf8");
|
|
19195
19537
|
try {
|
|
19196
19538
|
await renameWithRetry(tmp, target);
|
|
19197
19539
|
} catch (e) {
|
|
19198
|
-
await (0,
|
|
19540
|
+
await (0, import_promises4.unlink)(tmp).catch(() => void 0);
|
|
19199
19541
|
throw e;
|
|
19200
19542
|
}
|
|
19201
19543
|
}
|
|
19202
19544
|
async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
|
|
19203
19545
|
for (let i = 0; ; i++) {
|
|
19204
19546
|
try {
|
|
19205
|
-
await (0,
|
|
19547
|
+
await (0, import_promises4.rename)(from, to);
|
|
19206
19548
|
return;
|
|
19207
19549
|
} catch (e) {
|
|
19208
19550
|
const code = e.code;
|
|
@@ -19214,7 +19556,7 @@ async function renameWithRetry(from, to, attempts = 5, backoffMs = 20) {
|
|
|
19214
19556
|
async function readStrict(registryPath) {
|
|
19215
19557
|
let text;
|
|
19216
19558
|
try {
|
|
19217
|
-
text = await (0,
|
|
19559
|
+
text = await (0, import_promises4.readFile)(registryPath, "utf8");
|
|
19218
19560
|
} catch (e) {
|
|
19219
19561
|
if (e.code === "ENOENT") return [];
|
|
19220
19562
|
throw e;
|
|
@@ -19225,15 +19567,15 @@ async function acquireLock(lockPath, opts, deadline) {
|
|
|
19225
19567
|
for (; ; ) {
|
|
19226
19568
|
let handle;
|
|
19227
19569
|
try {
|
|
19228
|
-
handle = await (0,
|
|
19570
|
+
handle = await (0, import_promises4.open)(lockPath, "wx");
|
|
19229
19571
|
} catch (e) {
|
|
19230
19572
|
const code = e.code;
|
|
19231
19573
|
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
19232
19574
|
try {
|
|
19233
|
-
const age = Date.now() - (await (0,
|
|
19575
|
+
const age = Date.now() - (await (0, import_promises4.stat)(lockPath)).mtimeMs;
|
|
19234
19576
|
if (age > opts.staleMs) {
|
|
19235
19577
|
try {
|
|
19236
|
-
await (0,
|
|
19578
|
+
await (0, import_promises4.unlink)(lockPath);
|
|
19237
19579
|
continue;
|
|
19238
19580
|
} catch (unlinkError) {
|
|
19239
19581
|
const code2 = unlinkError.code;
|
|
@@ -19262,7 +19604,7 @@ async function acquireLock(lockPath, opts, deadline) {
|
|
|
19262
19604
|
}
|
|
19263
19605
|
async function lockHeldBy(lockPath, token) {
|
|
19264
19606
|
try {
|
|
19265
|
-
return await (0,
|
|
19607
|
+
return await (0, import_promises4.readFile)(lockPath, "utf8") === token;
|
|
19266
19608
|
} catch {
|
|
19267
19609
|
return false;
|
|
19268
19610
|
}
|
|
@@ -19270,7 +19612,7 @@ async function lockHeldBy(lockPath, token) {
|
|
|
19270
19612
|
async function releaseLock(lockPath, guard) {
|
|
19271
19613
|
await guard.handle.close().catch(() => void 0);
|
|
19272
19614
|
if (await lockHeldBy(lockPath, guard.token)) {
|
|
19273
|
-
await (0,
|
|
19615
|
+
await (0, import_promises4.unlink)(lockPath).catch(() => void 0);
|
|
19274
19616
|
}
|
|
19275
19617
|
}
|
|
19276
19618
|
function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
@@ -19284,19 +19626,19 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
19284
19626
|
// Lenient read for the sweep's initial fetch: any error (missing/corrupt) yields an empty queue.
|
|
19285
19627
|
read: async () => {
|
|
19286
19628
|
try {
|
|
19287
|
-
return parseDeferredWorktreesFile(await (0,
|
|
19629
|
+
return parseDeferredWorktreesFile(await (0, import_promises4.readFile)(registryPath, "utf8"));
|
|
19288
19630
|
} catch {
|
|
19289
19631
|
return [];
|
|
19290
19632
|
}
|
|
19291
19633
|
},
|
|
19292
19634
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
19293
19635
|
write: async (entries) => {
|
|
19294
|
-
await (0,
|
|
19636
|
+
await (0, import_promises4.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
19295
19637
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
19296
19638
|
},
|
|
19297
19639
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
19298
19640
|
update: async (mutate) => {
|
|
19299
|
-
await (0,
|
|
19641
|
+
await (0, import_promises4.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
19300
19642
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
19301
19643
|
for (; ; ) {
|
|
19302
19644
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -19423,7 +19765,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19423
19765
|
return cleanupPrMergeLocalBranch(branch.branch, {
|
|
19424
19766
|
beforeWorktrees,
|
|
19425
19767
|
startingPath: branch.worktreePath,
|
|
19426
|
-
pathExists: (p) => (0,
|
|
19768
|
+
pathExists: (p) => (0, import_node_fs22.existsSync)(p),
|
|
19427
19769
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
19428
19770
|
teardownWorktreeStage,
|
|
19429
19771
|
deferredStore,
|
|
@@ -19449,7 +19791,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19449
19791
|
for (const wt of plan.worktreeDirs) {
|
|
19450
19792
|
try {
|
|
19451
19793
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
19452
|
-
realpath: (path2) => (0,
|
|
19794
|
+
realpath: (path2) => (0, import_node_fs22.realpathSync)(path2)
|
|
19453
19795
|
});
|
|
19454
19796
|
if (!cleanupTarget.ok) {
|
|
19455
19797
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -19576,13 +19918,13 @@ var realWorktreeDirRemover = {
|
|
|
19576
19918
|
probe: (p) => {
|
|
19577
19919
|
let st;
|
|
19578
19920
|
try {
|
|
19579
|
-
st = (0,
|
|
19921
|
+
st = (0, import_node_fs22.lstatSync)(p);
|
|
19580
19922
|
} catch {
|
|
19581
19923
|
return null;
|
|
19582
19924
|
}
|
|
19583
19925
|
if (st.isSymbolicLink()) return "link";
|
|
19584
19926
|
try {
|
|
19585
|
-
(0,
|
|
19927
|
+
(0, import_node_fs22.readlinkSync)(p);
|
|
19586
19928
|
return "link";
|
|
19587
19929
|
} catch {
|
|
19588
19930
|
}
|
|
@@ -19590,7 +19932,7 @@ var realWorktreeDirRemover = {
|
|
|
19590
19932
|
},
|
|
19591
19933
|
readdir: (p) => {
|
|
19592
19934
|
try {
|
|
19593
|
-
return (0,
|
|
19935
|
+
return (0, import_node_fs22.readdirSync)(p);
|
|
19594
19936
|
} catch {
|
|
19595
19937
|
return [];
|
|
19596
19938
|
}
|
|
@@ -19599,12 +19941,12 @@ var realWorktreeDirRemover = {
|
|
|
19599
19941
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
19600
19942
|
detachLink: (p) => {
|
|
19601
19943
|
try {
|
|
19602
|
-
(0,
|
|
19944
|
+
(0, import_node_fs22.rmdirSync)(p);
|
|
19603
19945
|
} catch {
|
|
19604
|
-
(0,
|
|
19946
|
+
(0, import_node_fs22.unlinkSync)(p);
|
|
19605
19947
|
}
|
|
19606
19948
|
},
|
|
19607
|
-
removeTree: (p) => (0,
|
|
19949
|
+
removeTree: (p) => (0, import_promises5.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
19608
19950
|
};
|
|
19609
19951
|
async function resolvePrimaryCheckout(execGit) {
|
|
19610
19952
|
try {
|
|
@@ -19634,9 +19976,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
19634
19976
|
}
|
|
19635
19977
|
}
|
|
19636
19978
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
19637
|
-
if (!(0,
|
|
19979
|
+
if (!(0, import_node_fs22.existsSync)(statePath)) return false;
|
|
19638
19980
|
try {
|
|
19639
|
-
const state = JSON.parse((0,
|
|
19981
|
+
const state = JSON.parse((0, import_node_fs22.readFileSync)(statePath, "utf8"));
|
|
19640
19982
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
19641
19983
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
19642
19984
|
} catch {
|
|
@@ -19764,8 +20106,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
19764
20106
|
}
|
|
19765
20107
|
|
|
19766
20108
|
// src/worktree-lifecycle-commands.ts
|
|
19767
|
-
var
|
|
19768
|
-
var
|
|
20109
|
+
var import_node_fs23 = require("node:fs");
|
|
20110
|
+
var import_node_path21 = require("node:path");
|
|
19769
20111
|
var GH_TIMEOUT_MS = 2e4;
|
|
19770
20112
|
var DEFAULT_BASE = "origin/development";
|
|
19771
20113
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -19902,13 +20244,13 @@ function registerWorktreeCommands(program3) {
|
|
|
19902
20244
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
19903
20245
|
return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
19904
20246
|
}
|
|
19905
|
-
const gitFile = (0,
|
|
19906
|
-
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();
|
|
19907
20249
|
if (apply && !isLinked) {
|
|
19908
20250
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
19909
20251
|
}
|
|
19910
20252
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
19911
|
-
const primaryCheckout = commonDir ? (0,
|
|
20253
|
+
const primaryCheckout = commonDir ? (0, import_node_path21.dirname)(commonDir) : wtPath;
|
|
19912
20254
|
const stageSummary = readStageSummary(wtPath);
|
|
19913
20255
|
const hasStage = stageSummary != null;
|
|
19914
20256
|
const steps = landSteps(branch, true, hasStage);
|
|
@@ -20020,10 +20362,10 @@ async function gatherWorktreeContext() {
|
|
|
20020
20362
|
}
|
|
20021
20363
|
const orphanDirs = [];
|
|
20022
20364
|
const wtRoot = siblingMmiWorktreesRoot(repoRoot2);
|
|
20023
|
-
if ((0,
|
|
20365
|
+
if ((0, import_node_fs23.existsSync)(wtRoot)) {
|
|
20024
20366
|
let entries = [];
|
|
20025
20367
|
try {
|
|
20026
|
-
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));
|
|
20027
20369
|
} catch {
|
|
20028
20370
|
}
|
|
20029
20371
|
const known = new Set(worktrees.map((w) => w.path));
|
|
@@ -20048,7 +20390,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
20048
20390
|
}
|
|
20049
20391
|
|
|
20050
20392
|
// src/issue-commands.ts
|
|
20051
|
-
var
|
|
20393
|
+
var import_node_fs24 = require("node:fs");
|
|
20052
20394
|
var import_node_crypto5 = require("node:crypto");
|
|
20053
20395
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
20054
20396
|
async function editIssue(client, options, deps = {}) {
|
|
@@ -20062,7 +20404,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
20062
20404
|
if (options.body !== void 0 || options.bodyFile !== void 0) {
|
|
20063
20405
|
let body;
|
|
20064
20406
|
if (options.bodyFile) {
|
|
20065
|
-
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("") };
|
|
20066
20408
|
body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
|
|
20067
20409
|
} else {
|
|
20068
20410
|
body = options.body ?? "";
|
|
@@ -20536,7 +20878,7 @@ function extendCreateCommand(issue2) {
|
|
|
20536
20878
|
if (opts.batch) {
|
|
20537
20879
|
let specs;
|
|
20538
20880
|
try {
|
|
20539
|
-
const raw = (0,
|
|
20881
|
+
const raw = (0, import_node_fs24.readFileSync)(opts.batch, "utf8");
|
|
20540
20882
|
specs = JSON.parse(raw);
|
|
20541
20883
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
20542
20884
|
} catch (e) {
|
|
@@ -20580,12 +20922,12 @@ ${lines}`);
|
|
|
20580
20922
|
}
|
|
20581
20923
|
|
|
20582
20924
|
// src/train-commands.ts
|
|
20583
|
-
var
|
|
20584
|
-
var
|
|
20925
|
+
var import_node_fs26 = require("node:fs");
|
|
20926
|
+
var import_node_path23 = require("node:path");
|
|
20585
20927
|
|
|
20586
20928
|
// src/plugin-guard-io.ts
|
|
20587
|
-
var
|
|
20588
|
-
var
|
|
20929
|
+
var import_node_fs25 = require("node:fs");
|
|
20930
|
+
var import_node_path22 = require("node:path");
|
|
20589
20931
|
var import_node_os6 = require("node:os");
|
|
20590
20932
|
|
|
20591
20933
|
// src/plugin-guard.ts
|
|
@@ -20715,11 +21057,11 @@ function runHostBin(bin, args, opts) {
|
|
|
20715
21057
|
}
|
|
20716
21058
|
var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
|
|
20717
21059
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
20718
|
-
return (0,
|
|
21060
|
+
return (0, import_node_path22.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
20719
21061
|
};
|
|
20720
21062
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
20721
21063
|
try {
|
|
20722
|
-
return JSON.parse((0,
|
|
21064
|
+
return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
|
|
20723
21065
|
} catch {
|
|
20724
21066
|
return null;
|
|
20725
21067
|
}
|
|
@@ -20727,13 +21069,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
20727
21069
|
function marketplaceCloneCandidates(surface, home) {
|
|
20728
21070
|
if (surface === "codex") {
|
|
20729
21071
|
return [
|
|
20730
|
-
(0,
|
|
20731
|
-
(0,
|
|
21072
|
+
(0, import_node_path22.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
21073
|
+
(0, import_node_path22.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
20732
21074
|
];
|
|
20733
21075
|
}
|
|
20734
|
-
return [(0,
|
|
21076
|
+
return [(0, import_node_path22.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
20735
21077
|
}
|
|
20736
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
21078
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
|
|
20737
21079
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
20738
21080
|
}
|
|
20739
21081
|
async function fetchNpmReleasedVersion() {
|
|
@@ -20751,7 +21093,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
20751
21093
|
isOrgRepo,
|
|
20752
21094
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
20753
21095
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
20754
|
-
pluginCachePresent: (0,
|
|
21096
|
+
pluginCachePresent: (0, import_node_fs25.existsSync)((0, import_node_path22.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
20755
21097
|
};
|
|
20756
21098
|
}
|
|
20757
21099
|
function claudePluginGuardState(isOrgRepo) {
|
|
@@ -20875,7 +21217,7 @@ function formatTrainStatus(r) {
|
|
|
20875
21217
|
// src/train-commands.ts
|
|
20876
21218
|
function readRepoVersion() {
|
|
20877
21219
|
try {
|
|
20878
|
-
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;
|
|
20879
21221
|
} catch {
|
|
20880
21222
|
return void 0;
|
|
20881
21223
|
}
|
|
@@ -21375,7 +21717,7 @@ function registerExplainCommand(program3) {
|
|
|
21375
21717
|
}
|
|
21376
21718
|
|
|
21377
21719
|
// src/pr-commands.ts
|
|
21378
|
-
var
|
|
21720
|
+
var import_promises6 = require("node:fs/promises");
|
|
21379
21721
|
var GC_GH_TIMEOUT_MS4 = 2e4;
|
|
21380
21722
|
var CHECKS_WATCH_POLL_MS = 15e3;
|
|
21381
21723
|
var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -21519,10 +21861,10 @@ function registerPrLifecycleCommands(program3) {
|
|
|
21519
21861
|
let body;
|
|
21520
21862
|
try {
|
|
21521
21863
|
if (o.title || o.titleFile) {
|
|
21522
|
-
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 });
|
|
21523
21865
|
}
|
|
21524
21866
|
if (o.body || o.bodyFile) {
|
|
21525
|
-
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 });
|
|
21526
21868
|
}
|
|
21527
21869
|
} catch (e) {
|
|
21528
21870
|
return fail(`pr edit: ${e.message}`);
|
|
@@ -21552,7 +21894,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
21552
21894
|
}
|
|
21553
21895
|
let body;
|
|
21554
21896
|
try {
|
|
21555
|
-
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 });
|
|
21556
21898
|
} catch (e) {
|
|
21557
21899
|
return fail(`pr comment: ${e.message}`);
|
|
21558
21900
|
}
|
|
@@ -22105,7 +22447,7 @@ function checkSchedules(probe) {
|
|
|
22105
22447
|
ok: false,
|
|
22106
22448
|
label: "schedules",
|
|
22107
22449
|
detail: `${probe.drift.length} drift finding(s)`,
|
|
22108
|
-
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",
|
|
22109
22451
|
verbose: evidence
|
|
22110
22452
|
};
|
|
22111
22453
|
}
|
|
@@ -22277,9 +22619,9 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
22277
22619
|
}
|
|
22278
22620
|
|
|
22279
22621
|
// src/doctor-io.ts
|
|
22280
|
-
var
|
|
22622
|
+
var import_node_fs27 = require("node:fs");
|
|
22281
22623
|
var import_node_os7 = require("node:os");
|
|
22282
|
-
var
|
|
22624
|
+
var import_node_path24 = require("node:path");
|
|
22283
22625
|
var import_node_child_process11 = require("node:child_process");
|
|
22284
22626
|
var import_node_util8 = require("node:util");
|
|
22285
22627
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process11.execFile);
|
|
@@ -22287,7 +22629,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
22287
22629
|
function installedClaudePluginVersion() {
|
|
22288
22630
|
try {
|
|
22289
22631
|
const file = JSON.parse(
|
|
22290
|
-
(0,
|
|
22632
|
+
(0, import_node_fs27.readFileSync)((0, import_node_path24.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
22291
22633
|
);
|
|
22292
22634
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
22293
22635
|
if (versions.length === 0) return void 0;
|
|
@@ -22308,13 +22650,13 @@ function worktreeRootSync() {
|
|
|
22308
22650
|
}
|
|
22309
22651
|
var gitignorePath = () => {
|
|
22310
22652
|
const root = worktreeRootSync();
|
|
22311
|
-
return root === null ? null : (0,
|
|
22653
|
+
return root === null ? null : (0, import_node_path24.join)(root, ".gitignore");
|
|
22312
22654
|
};
|
|
22313
22655
|
function readGitignore() {
|
|
22314
22656
|
const path2 = gitignorePath();
|
|
22315
22657
|
if (path2 === null) return null;
|
|
22316
22658
|
try {
|
|
22317
|
-
return (0,
|
|
22659
|
+
return (0, import_node_fs27.readFileSync)(path2, "utf8");
|
|
22318
22660
|
} catch {
|
|
22319
22661
|
return null;
|
|
22320
22662
|
}
|
|
@@ -22323,7 +22665,7 @@ function writeGitignore(content) {
|
|
|
22323
22665
|
const path2 = gitignorePath();
|
|
22324
22666
|
if (path2 === null) return false;
|
|
22325
22667
|
try {
|
|
22326
|
-
(0,
|
|
22668
|
+
(0, import_node_fs27.writeFileSync)(path2, content, "utf8");
|
|
22327
22669
|
return true;
|
|
22328
22670
|
} catch {
|
|
22329
22671
|
return false;
|
|
@@ -22347,7 +22689,7 @@ async function repoRoot() {
|
|
|
22347
22689
|
}
|
|
22348
22690
|
function hasRepoLocalWorktrees() {
|
|
22349
22691
|
const root = worktreeRootSync();
|
|
22350
|
-
return root !== null && (0,
|
|
22692
|
+
return root !== null && (0, import_node_fs27.existsSync)((0, import_node_path24.join)(root, ".worktrees"));
|
|
22351
22693
|
}
|
|
22352
22694
|
|
|
22353
22695
|
// src/index.ts
|
|
@@ -22520,19 +22862,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
22520
22862
|
});
|
|
22521
22863
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
22522
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) => {
|
|
22523
|
-
const path2 = (0,
|
|
22524
|
-
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;
|
|
22525
22867
|
const plan = planManagedGitignore(current);
|
|
22526
22868
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
22527
22869
|
if (opts.json) {
|
|
22528
|
-
if (opts.write && plan.changed) (0,
|
|
22870
|
+
if (opts.write && plan.changed) (0, import_node_fs28.writeFileSync)(path2, plan.content, "utf8");
|
|
22529
22871
|
console.log(JSON.stringify(plan, null, 2));
|
|
22530
22872
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
22531
22873
|
return;
|
|
22532
22874
|
}
|
|
22533
22875
|
if (opts.write) {
|
|
22534
22876
|
if (plan.changed) {
|
|
22535
|
-
(0,
|
|
22877
|
+
(0, import_node_fs28.writeFileSync)(path2, plan.content, "utf8");
|
|
22536
22878
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
22537
22879
|
} else {
|
|
22538
22880
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -22722,26 +23064,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
22722
23064
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
22723
23065
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
22724
23066
|
const take = () => {
|
|
22725
|
-
const fd = (0,
|
|
23067
|
+
const fd = (0, import_node_fs28.openSync)(lockPath, "wx");
|
|
22726
23068
|
try {
|
|
22727
|
-
(0,
|
|
23069
|
+
(0, import_node_fs28.writeSync)(fd, String(Date.now()));
|
|
22728
23070
|
} finally {
|
|
22729
|
-
(0,
|
|
23071
|
+
(0, import_node_fs28.closeSync)(fd);
|
|
22730
23072
|
}
|
|
22731
23073
|
return () => {
|
|
22732
23074
|
try {
|
|
22733
|
-
(0,
|
|
23075
|
+
(0, import_node_fs28.rmSync)(lockPath, { force: true });
|
|
22734
23076
|
} catch {
|
|
22735
23077
|
}
|
|
22736
23078
|
};
|
|
22737
23079
|
};
|
|
22738
23080
|
try {
|
|
22739
|
-
(0,
|
|
23081
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(lockPath), { recursive: true });
|
|
22740
23082
|
return take();
|
|
22741
23083
|
} catch {
|
|
22742
23084
|
try {
|
|
22743
|
-
if (Date.now() - (0,
|
|
22744
|
-
(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 });
|
|
22745
23087
|
return take();
|
|
22746
23088
|
}
|
|
22747
23089
|
} catch {
|
|
@@ -22929,6 +23271,7 @@ registerSecretsCommands(program2);
|
|
|
22929
23271
|
registerEdgeCommands(program2);
|
|
22930
23272
|
registerBoxCommands(program2);
|
|
22931
23273
|
registerSchedulesCommands(program2);
|
|
23274
|
+
registerSchedulesLiftCommand(program2);
|
|
22932
23275
|
var docs = program2.command("docs").description("generated docs surfaces \u2014 the routing index (org knowledge layer)");
|
|
22933
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) => {
|
|
22934
23277
|
try {
|
|
@@ -23161,7 +23504,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
23161
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`);
|
|
23162
23505
|
if (o.secretsFile) {
|
|
23163
23506
|
try {
|
|
23164
|
-
vars.push(`secrets=${(0,
|
|
23507
|
+
vars.push(`secrets=${(0, import_node_fs28.readFileSync)(o.secretsFile, "utf8")}`);
|
|
23165
23508
|
} catch (e) {
|
|
23166
23509
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
23167
23510
|
}
|
|
@@ -23463,8 +23806,8 @@ withExamples(mutating(
|
|
|
23463
23806
|
let extraLabels = [];
|
|
23464
23807
|
try {
|
|
23465
23808
|
issueType = resolveCreateType(o.type, "issue create");
|
|
23466
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
23467
|
-
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
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 });
|
|
23468
23811
|
if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
|
|
23469
23812
|
priority = resolveCreatePriority(o.priority, "issue create");
|
|
23470
23813
|
extraLabels = [...o.label ?? []];
|
|
@@ -23600,7 +23943,7 @@ issue.command("comment <ref>").description("post a Markdown comment to an issue
|
|
|
23600
23943
|
}
|
|
23601
23944
|
let body;
|
|
23602
23945
|
try {
|
|
23603
|
-
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 });
|
|
23604
23947
|
} catch (e) {
|
|
23605
23948
|
return fail(`issue comment: ${e.message}`);
|
|
23606
23949
|
}
|
|
@@ -23660,8 +24003,8 @@ program2.command("report").description("file a friction report on the Hub board
|
|
|
23660
24003
|
let title;
|
|
23661
24004
|
const sourceRepo = o.repo ?? await resolveRepo(void 0);
|
|
23662
24005
|
try {
|
|
23663
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
23664
|
-
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 });
|
|
23665
24008
|
priority = resolveCreatePriority(o.priority, "report");
|
|
23666
24009
|
if (!ISSUE_TYPES.includes(o.type)) {
|
|
23667
24010
|
throw new Error(`unknown issue type "${o.type}" \u2014 expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
@@ -23710,8 +24053,8 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
23710
24053
|
let args;
|
|
23711
24054
|
try {
|
|
23712
24055
|
skill = assertSkillName(o.skill);
|
|
23713
|
-
rawBody = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile:
|
|
23714
|
-
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 });
|
|
23715
24058
|
title = buildSkillLessonTitle(skill, rawTitle);
|
|
23716
24059
|
priority = resolveCreatePriority(o.priority, "skill-lesson");
|
|
23717
24060
|
body = buildSkillLessonBody(rawBody, sourceRepo, pluginSha);
|
|
@@ -23762,8 +24105,8 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
23762
24105
|
let body;
|
|
23763
24106
|
let title;
|
|
23764
24107
|
try {
|
|
23765
|
-
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile:
|
|
23766
|
-
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 });
|
|
23767
24110
|
} catch (e) {
|
|
23768
24111
|
return fail(`pr create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
23769
24112
|
}
|
|
@@ -23798,11 +24141,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
23798
24141
|
}
|
|
23799
24142
|
});
|
|
23800
24143
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
23801
|
-
const wfDir = (0,
|
|
23802
|
-
if (!(0,
|
|
23803
|
-
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) => {
|
|
23804
24147
|
try {
|
|
23805
|
-
return workflowReportsPrChecks((0,
|
|
24148
|
+
return workflowReportsPrChecks((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(wfDir, name), "utf8"));
|
|
23806
24149
|
} catch {
|
|
23807
24150
|
return true;
|
|
23808
24151
|
}
|
|
@@ -23829,16 +24172,16 @@ function ciAuditDeps() {
|
|
|
23829
24172
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
23830
24173
|
readSeedFile: (path2) => {
|
|
23831
24174
|
if (!root) return null;
|
|
23832
|
-
const fullPath = (0,
|
|
23833
|
-
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;
|
|
23834
24177
|
}
|
|
23835
24178
|
};
|
|
23836
24179
|
}
|
|
23837
24180
|
function hubRoot() {
|
|
23838
|
-
const fromPkg = (0,
|
|
24181
|
+
const fromPkg = (0, import_node_path25.join)(__dirname, "..", "..");
|
|
23839
24182
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
23840
|
-
if ((0,
|
|
23841
|
-
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();
|
|
23842
24185
|
return null;
|
|
23843
24186
|
}
|
|
23844
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) => {
|
|
@@ -24056,7 +24399,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
24056
24399
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
24057
24400
|
beforeWorktrees,
|
|
24058
24401
|
startingPath,
|
|
24059
|
-
pathExists: (p) => (0,
|
|
24402
|
+
pathExists: (p) => (0, import_node_fs28.existsSync)(p),
|
|
24060
24403
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
24061
24404
|
teardownWorktreeStage,
|
|
24062
24405
|
deferredStore,
|
|
@@ -24166,8 +24509,8 @@ function trainApplyDeps() {
|
|
|
24166
24509
|
// Slack release announcement (#883): Hub-only + best-effort inside announceRelease itself.
|
|
24167
24510
|
announce: (args) => announceRelease({
|
|
24168
24511
|
run: async (file, cmdArgs) => (await execFileP2(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS })).stdout,
|
|
24169
|
-
readFile: (path2) => (0,
|
|
24170
|
-
removeFile: (path2) => (0,
|
|
24512
|
+
readFile: (path2) => (0, import_promises7.readFile)(path2, "utf8"),
|
|
24513
|
+
removeFile: (path2) => (0, import_promises7.unlink)(path2)
|
|
24171
24514
|
}, args),
|
|
24172
24515
|
fetchEdgeDomains: async (slug) => {
|
|
24173
24516
|
const proj = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig()));
|
|
@@ -24396,10 +24739,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
24396
24739
|
targets = resolution.targets;
|
|
24397
24740
|
}
|
|
24398
24741
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
24399
|
-
const fileMatrix = (0,
|
|
24742
|
+
const fileMatrix = (0, import_node_fs28.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs28.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
24400
24743
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
24401
24744
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
24402
|
-
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: {} };
|
|
24403
24746
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
24404
24747
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
24405
24748
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -24432,16 +24775,16 @@ function directoryBytes(path2) {
|
|
|
24432
24775
|
let total = 0;
|
|
24433
24776
|
let entries;
|
|
24434
24777
|
try {
|
|
24435
|
-
entries = (0,
|
|
24778
|
+
entries = (0, import_node_fs28.readdirSync)(path2, { withFileTypes: true });
|
|
24436
24779
|
} catch {
|
|
24437
24780
|
return 0;
|
|
24438
24781
|
}
|
|
24439
24782
|
for (const entry of entries) {
|
|
24440
|
-
const child2 = (0,
|
|
24783
|
+
const child2 = (0, import_node_path25.join)(path2, entry.name);
|
|
24441
24784
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
24442
24785
|
else {
|
|
24443
24786
|
try {
|
|
24444
|
-
total += (0,
|
|
24787
|
+
total += (0, import_node_fs28.statSync)(child2).size;
|
|
24445
24788
|
} catch {
|
|
24446
24789
|
}
|
|
24447
24790
|
}
|
|
@@ -24449,25 +24792,25 @@ function directoryBytes(path2) {
|
|
|
24449
24792
|
return total;
|
|
24450
24793
|
}
|
|
24451
24794
|
function listDirEntries(dir) {
|
|
24452
|
-
return (0,
|
|
24795
|
+
return (0, import_node_fs28.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
24453
24796
|
}
|
|
24454
24797
|
function readInstalledPluginRefs(home) {
|
|
24455
24798
|
const p = installedPluginsPath(home);
|
|
24456
|
-
if (!(0,
|
|
24799
|
+
if (!(0, import_node_fs28.existsSync)(p)) return [];
|
|
24457
24800
|
try {
|
|
24458
|
-
return installedPluginPaths((0,
|
|
24801
|
+
return installedPluginPaths((0, import_node_fs28.readFileSync)(p, "utf8"));
|
|
24459
24802
|
} catch {
|
|
24460
24803
|
return null;
|
|
24461
24804
|
}
|
|
24462
24805
|
}
|
|
24463
24806
|
function pluginCacheFsDeps(home, dirBytes) {
|
|
24464
24807
|
return {
|
|
24465
|
-
exists: (p) => (0,
|
|
24466
|
-
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),
|
|
24467
24810
|
dirBytes,
|
|
24468
|
-
listStagingDirs: (root) => (0,
|
|
24811
|
+
listStagingDirs: (root) => (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
24469
24812
|
try {
|
|
24470
|
-
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) };
|
|
24471
24814
|
} catch {
|
|
24472
24815
|
return { name: d.name, mtimeMs: Date.now() };
|
|
24473
24816
|
}
|
|
@@ -24481,10 +24824,10 @@ function stagingApplyFsGuard(home) {
|
|
|
24481
24824
|
return {
|
|
24482
24825
|
referencedPaths: () => readInstalledPluginRefs(home),
|
|
24483
24826
|
mtimeMs: (name) => {
|
|
24484
|
-
const p = (0,
|
|
24485
|
-
if (!(0,
|
|
24827
|
+
const p = (0, import_node_path25.join)(stagingRoot, name);
|
|
24828
|
+
if (!(0, import_node_fs28.existsSync)(p)) return null;
|
|
24486
24829
|
try {
|
|
24487
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
24830
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs28.statSync)(q).mtimeMs);
|
|
24488
24831
|
} catch {
|
|
24489
24832
|
return null;
|
|
24490
24833
|
}
|
|
@@ -24500,7 +24843,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
24500
24843
|
{ withBytes: true }
|
|
24501
24844
|
);
|
|
24502
24845
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
24503
|
-
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;
|
|
24504
24847
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
24505
24848
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
24506
24849
|
else console.log(renderPluginCachePlan(plan, result));
|