@mutmutco/cli 3.105.7 → 3.105.9
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 +384 -263
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -8291,7 +8291,105 @@ function appendHookActivity(cwd, entry) {
|
|
|
8291
8291
|
|
|
8292
8292
|
// src/worktree.ts
|
|
8293
8293
|
var import_node_fs12 = require("node:fs");
|
|
8294
|
+
var import_node_path12 = require("node:path");
|
|
8295
|
+
|
|
8296
|
+
// src/file-lock.ts
|
|
8297
|
+
var import_promises2 = require("node:fs/promises");
|
|
8294
8298
|
var import_node_path11 = require("node:path");
|
|
8299
|
+
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
8300
|
+
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
8301
|
+
var FileLockBusyError = class extends Error {
|
|
8302
|
+
lockPath;
|
|
8303
|
+
constructor(label, lockPath, maxWaitMs) {
|
|
8304
|
+
super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
|
|
8305
|
+
this.name = "FileLockBusyError";
|
|
8306
|
+
this.lockPath = lockPath;
|
|
8307
|
+
}
|
|
8308
|
+
};
|
|
8309
|
+
function resolveFileLockOpts(opts = {}) {
|
|
8310
|
+
return {
|
|
8311
|
+
staleMs: opts.staleMs ?? 3e4,
|
|
8312
|
+
retryMs: opts.retryMs ?? 50,
|
|
8313
|
+
maxWaitMs: opts.maxWaitMs ?? 5e3,
|
|
8314
|
+
label: opts.label ?? "file lock"
|
|
8315
|
+
};
|
|
8316
|
+
}
|
|
8317
|
+
async function acquireFileLock(lockPath, opts, deadline) {
|
|
8318
|
+
let immediateRetries = 0;
|
|
8319
|
+
for (; ; ) {
|
|
8320
|
+
let handle;
|
|
8321
|
+
try {
|
|
8322
|
+
handle = await (0, import_promises2.open)(lockPath, "wx");
|
|
8323
|
+
} catch (e) {
|
|
8324
|
+
const code = e.code;
|
|
8325
|
+
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
8326
|
+
const retryNow = async () => {
|
|
8327
|
+
if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
8328
|
+
if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
|
|
8329
|
+
};
|
|
8330
|
+
try {
|
|
8331
|
+
const age = Date.now() - (await (0, import_promises2.stat)(lockPath)).mtimeMs;
|
|
8332
|
+
if (age > opts.staleMs) {
|
|
8333
|
+
try {
|
|
8334
|
+
await (0, import_promises2.unlink)(lockPath);
|
|
8335
|
+
await retryNow();
|
|
8336
|
+
continue;
|
|
8337
|
+
} catch (unlinkError) {
|
|
8338
|
+
if (unlinkError instanceof FileLockBusyError) throw unlinkError;
|
|
8339
|
+
const unlinkCode = unlinkError.code;
|
|
8340
|
+
if (unlinkCode === "ENOENT") {
|
|
8341
|
+
await retryNow();
|
|
8342
|
+
continue;
|
|
8343
|
+
}
|
|
8344
|
+
}
|
|
8345
|
+
}
|
|
8346
|
+
} catch (statError) {
|
|
8347
|
+
if (statError instanceof FileLockBusyError) throw statError;
|
|
8348
|
+
if (statError.code !== "ENOENT") throw statError;
|
|
8349
|
+
await retryNow();
|
|
8350
|
+
continue;
|
|
8351
|
+
}
|
|
8352
|
+
if (Date.now() >= deadline) {
|
|
8353
|
+
throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
8354
|
+
}
|
|
8355
|
+
await sleep(opts.retryMs);
|
|
8356
|
+
continue;
|
|
8357
|
+
}
|
|
8358
|
+
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
8359
|
+
try {
|
|
8360
|
+
await handle.writeFile(token);
|
|
8361
|
+
} catch (e) {
|
|
8362
|
+
await handle.close().catch(() => void 0);
|
|
8363
|
+
throw e;
|
|
8364
|
+
}
|
|
8365
|
+
return { token, handle };
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8368
|
+
async function fileLockHeldBy(lockPath, token) {
|
|
8369
|
+
try {
|
|
8370
|
+
return await (0, import_promises2.readFile)(lockPath, "utf8") === token;
|
|
8371
|
+
} catch {
|
|
8372
|
+
return false;
|
|
8373
|
+
}
|
|
8374
|
+
}
|
|
8375
|
+
async function releaseFileLock(lockPath, guard) {
|
|
8376
|
+
await guard.handle.close().catch(() => void 0);
|
|
8377
|
+
if (await fileLockHeldBy(lockPath, guard.token)) {
|
|
8378
|
+
await (0, import_promises2.unlink)(lockPath).catch(() => void 0);
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
8381
|
+
async function withFileLock(lockPath, opts, fn) {
|
|
8382
|
+
const resolved = resolveFileLockOpts(opts);
|
|
8383
|
+
await (0, import_promises2.mkdir)((0, import_node_path11.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
8384
|
+
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
8385
|
+
try {
|
|
8386
|
+
return await fn();
|
|
8387
|
+
} finally {
|
|
8388
|
+
await releaseFileLock(lockPath, guard);
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
|
|
8392
|
+
// src/worktree.ts
|
|
8295
8393
|
var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
|
|
8296
8394
|
var PKG = "package.json";
|
|
8297
8395
|
var LOCKFILE = "package-lock.json";
|
|
@@ -8337,7 +8435,7 @@ var realFsProbe = {
|
|
|
8337
8435
|
}
|
|
8338
8436
|
};
|
|
8339
8437
|
function declaredProvision(fs2, abs) {
|
|
8340
|
-
const raw = fs2.readFile?.((0,
|
|
8438
|
+
const raw = fs2.readFile?.((0, import_node_path12.join)(abs, PKG));
|
|
8341
8439
|
if (raw === void 0) return void 0;
|
|
8342
8440
|
try {
|
|
8343
8441
|
const scripts = JSON.parse(raw).scripts;
|
|
@@ -8349,13 +8447,13 @@ function declaredProvision(fs2, abs) {
|
|
|
8349
8447
|
}
|
|
8350
8448
|
function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
8351
8449
|
const factsFor = (dir) => {
|
|
8352
|
-
const abs = dir ? (0,
|
|
8353
|
-
const match = LOCKFILE_INSTALLS.find((c) => fs2.isFile((0,
|
|
8354
|
-
const hasPackageJson = fs2.isFile((0,
|
|
8450
|
+
const abs = dir ? (0, import_node_path12.join)(root, dir) : root;
|
|
8451
|
+
const match = LOCKFILE_INSTALLS.find((c) => fs2.isFile((0, import_node_path12.join)(abs, c.lockfile)));
|
|
8452
|
+
const hasPackageJson = fs2.isFile((0, import_node_path12.join)(abs, PKG));
|
|
8355
8453
|
return {
|
|
8356
8454
|
dir,
|
|
8357
8455
|
hasPackageJson,
|
|
8358
|
-
hasNodeModules: fs2.isDir((0,
|
|
8456
|
+
hasNodeModules: fs2.isDir((0, import_node_path12.join)(abs, NODE_MODULES)),
|
|
8359
8457
|
install: match?.command,
|
|
8360
8458
|
provision: hasPackageJson ? declaredProvision(fs2, abs) : void 0
|
|
8361
8459
|
};
|
|
@@ -8371,7 +8469,7 @@ function npmInstallTargets(dirs) {
|
|
|
8371
8469
|
}));
|
|
8372
8470
|
}
|
|
8373
8471
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
8374
|
-
return fs2.isFile((0,
|
|
8472
|
+
return fs2.isFile((0, import_node_path12.join)(root, ".git"));
|
|
8375
8473
|
}
|
|
8376
8474
|
function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
8377
8475
|
if (!isLinkedWorktree(root, fs2)) return null;
|
|
@@ -8381,7 +8479,7 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
|
8381
8479
|
return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
|
|
8382
8480
|
}
|
|
8383
8481
|
function defaultCopyFile(from, to) {
|
|
8384
|
-
(0, import_node_fs12.mkdirSync)((0,
|
|
8482
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path12.dirname)(to), { recursive: true });
|
|
8385
8483
|
(0, import_node_fs12.copyFileSync)(from, to);
|
|
8386
8484
|
}
|
|
8387
8485
|
async function runDeclaredProvision(target, cwd, runInstall) {
|
|
@@ -8412,7 +8510,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8412
8510
|
const targets = npmInstallTargets(allDirs);
|
|
8413
8511
|
if (deps.validateInstall) {
|
|
8414
8512
|
for (const dir of allDirs.filter((d) => d.hasPackageJson && (d.provision ?? d.install) && d.hasNodeModules)) {
|
|
8415
|
-
const cwd = dir.dir ? (0,
|
|
8513
|
+
const cwd = dir.dir ? (0, import_node_path12.join)(worktreeRoot, dir.dir) : worktreeRoot;
|
|
8416
8514
|
if (!await deps.validateInstall(cwd)) {
|
|
8417
8515
|
targets.push({
|
|
8418
8516
|
dir: dir.dir,
|
|
@@ -8426,7 +8524,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8426
8524
|
const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules && !targetDirs.has(d.dir)).map((d) => d.dir);
|
|
8427
8525
|
const installed = [];
|
|
8428
8526
|
for (const target of targets) {
|
|
8429
|
-
const cwd = target.dir ? (0,
|
|
8527
|
+
const cwd = target.dir ? (0, import_node_path12.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
8430
8528
|
log(`installing deps: ${target.command} in ${target.dir || "."}`);
|
|
8431
8529
|
if (target.declared) await runDeclaredProvision(target, cwd, deps.runInstall);
|
|
8432
8530
|
else await deps.runInstall(target.command, cwd);
|
|
@@ -8436,7 +8534,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8436
8534
|
const copySkipped = [];
|
|
8437
8535
|
const primary = await deps.primaryCheckout();
|
|
8438
8536
|
for (const rel of LOCAL_ONLY_FILES) {
|
|
8439
|
-
const dest = (0,
|
|
8537
|
+
const dest = (0, import_node_path12.join)(worktreeRoot, rel);
|
|
8440
8538
|
if (fs2.isFile(dest)) {
|
|
8441
8539
|
copySkipped.push({ file: rel, reason: "already-present" });
|
|
8442
8540
|
continue;
|
|
@@ -8445,11 +8543,11 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8445
8543
|
copySkipped.push({ file: rel, reason: "no-primary" });
|
|
8446
8544
|
continue;
|
|
8447
8545
|
}
|
|
8448
|
-
if (!fs2.isFile((0,
|
|
8546
|
+
if (!fs2.isFile((0, import_node_path12.join)(primary, rel))) {
|
|
8449
8547
|
copySkipped.push({ file: rel, reason: "absent-in-primary" });
|
|
8450
8548
|
continue;
|
|
8451
8549
|
}
|
|
8452
|
-
copyFile((0,
|
|
8550
|
+
copyFile((0, import_node_path12.join)(primary, rel), dest);
|
|
8453
8551
|
copied.push(rel);
|
|
8454
8552
|
log(`copied local config: ${rel}`);
|
|
8455
8553
|
}
|
|
@@ -8463,12 +8561,12 @@ function capWorktreeDirName(name, max = 40) {
|
|
|
8463
8561
|
}
|
|
8464
8562
|
function defaultWorktreePath(repoRoot2, branch) {
|
|
8465
8563
|
const safe = capWorktreeDirName(branch.replace(/[/\\]+/g, "-"));
|
|
8466
|
-
return (0,
|
|
8564
|
+
return (0, import_node_path12.join)((0, import_node_path12.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path12.basename)(repoRoot2), safe);
|
|
8467
8565
|
}
|
|
8468
8566
|
async function primaryCheckoutRootOf(git2) {
|
|
8469
8567
|
try {
|
|
8470
8568
|
const out = (await git2(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
|
|
8471
|
-
return out ? (0,
|
|
8569
|
+
return out ? (0, import_node_path12.dirname)(out) : void 0;
|
|
8472
8570
|
} catch {
|
|
8473
8571
|
return void 0;
|
|
8474
8572
|
}
|
|
@@ -8480,10 +8578,26 @@ function resolveWorktreeBase(from, remote) {
|
|
|
8480
8578
|
if (SHA_LIKE_RE.test(from)) return { base: from };
|
|
8481
8579
|
return { base: from, fetchBranch: from, preferRemote: `${remotePrefix}${from}` };
|
|
8482
8580
|
}
|
|
8483
|
-
var GIT_CONFIG_LOCK_RE = /could not lock config file|unable to write upstream branch configuration/i;
|
|
8581
|
+
var GIT_CONFIG_LOCK_RE = /could not lock config file|unable to write upstream branch configuration|unable to access ['"]?\.git\/config['"]?: Permission denied|unknown error occurred while reading the configuration files/i;
|
|
8484
8582
|
function isGitConfigLockError(error) {
|
|
8485
8583
|
return GIT_CONFIG_LOCK_RE.test(error instanceof Error ? error.message : String(error));
|
|
8486
8584
|
}
|
|
8585
|
+
function worktreeAddLockPath(repoRoot2) {
|
|
8586
|
+
return repoRuntimeStatePath(repoRoot2, "worktree-add.lock");
|
|
8587
|
+
}
|
|
8588
|
+
var WORKTREE_ADD_LOCK_STALE_MS = 6e4;
|
|
8589
|
+
var WORKTREE_ADD_LOCK_MAX_WAIT_MS = 12e4;
|
|
8590
|
+
async function withWorktreeAddLock(repoRoot2, fn) {
|
|
8591
|
+
return withFileLock(
|
|
8592
|
+
worktreeAddLockPath(repoRoot2),
|
|
8593
|
+
{
|
|
8594
|
+
staleMs: WORKTREE_ADD_LOCK_STALE_MS,
|
|
8595
|
+
maxWaitMs: WORKTREE_ADD_LOCK_MAX_WAIT_MS,
|
|
8596
|
+
label: "worktree-add lock"
|
|
8597
|
+
},
|
|
8598
|
+
fn
|
|
8599
|
+
);
|
|
8600
|
+
}
|
|
8487
8601
|
var ADD_WORKTREE_BACKOFF_MS = [100, 250, 500, 750, 1e3];
|
|
8488
8602
|
var ADD_WORKTREE_MAX_ATTEMPTS = 6;
|
|
8489
8603
|
async function cleanupOrphanBranch(branch, base, preExistingOid, deps) {
|
|
@@ -9251,12 +9365,12 @@ function planManagedGitignore(current) {
|
|
|
9251
9365
|
|
|
9252
9366
|
// src/docs-index-command.ts
|
|
9253
9367
|
var import_node_fs14 = require("node:fs");
|
|
9254
|
-
var
|
|
9368
|
+
var import_node_path14 = require("node:path");
|
|
9255
9369
|
|
|
9256
9370
|
// src/doc-refs-core.ts
|
|
9257
9371
|
var import_node_child_process5 = require("node:child_process");
|
|
9258
9372
|
var import_node_fs13 = require("node:fs");
|
|
9259
|
-
var
|
|
9373
|
+
var import_node_path13 = require("node:path");
|
|
9260
9374
|
var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
|
|
9261
9375
|
var PIN_MENTION_RE = /<!--\s*pinned by\b/;
|
|
9262
9376
|
var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
|
|
@@ -9315,7 +9429,7 @@ function checkPins(root, readFile9, docs2) {
|
|
|
9315
9429
|
findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
|
|
9316
9430
|
continue;
|
|
9317
9431
|
}
|
|
9318
|
-
const source = readFile9((0,
|
|
9432
|
+
const source = readFile9((0, import_node_path13.join)(root, pin.file));
|
|
9319
9433
|
if (source == null) {
|
|
9320
9434
|
findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
|
|
9321
9435
|
continue;
|
|
@@ -9377,11 +9491,11 @@ function checkRefs(root, deps, docs2) {
|
|
|
9377
9491
|
for (const { ref } of extractRefs(markdown)) allFirstSegments.add(refFirstSegment(ref));
|
|
9378
9492
|
}
|
|
9379
9493
|
const tracked = allFirstSegments.size ? deps.trackedFirstSegments?.([...allFirstSegments]) ?? null : null;
|
|
9380
|
-
const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0,
|
|
9494
|
+
const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path13.join)(root, first));
|
|
9381
9495
|
const candidates = [];
|
|
9382
9496
|
const direct = [];
|
|
9383
9497
|
for (const [doc, markdown] of Object.entries(docs2)) {
|
|
9384
|
-
const docDir =
|
|
9498
|
+
const docDir = import_node_path13.posix.dirname(doc);
|
|
9385
9499
|
const base = docDir === "." ? "" : docDir;
|
|
9386
9500
|
const covered = /* @__PURE__ */ new Set();
|
|
9387
9501
|
const markers = [];
|
|
@@ -9390,21 +9504,21 @@ function checkRefs(root, deps, docs2) {
|
|
|
9390
9504
|
direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
|
|
9391
9505
|
continue;
|
|
9392
9506
|
}
|
|
9393
|
-
const docRel =
|
|
9394
|
-
const rootRel =
|
|
9507
|
+
const docRel = import_node_path13.posix.normalize(import_node_path13.posix.join(base, fwd.target));
|
|
9508
|
+
const rootRel = import_node_path13.posix.normalize(fwd.target.replace(/^\/+/, ""));
|
|
9395
9509
|
markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
|
|
9396
9510
|
covered.add(docRel);
|
|
9397
9511
|
covered.add(rootRel);
|
|
9398
9512
|
}
|
|
9399
9513
|
const links = extractLinks(markdown).map(({ target, line }) => {
|
|
9400
|
-
const resolved =
|
|
9401
|
-
return { target, line, resolved, missing: !exists((0,
|
|
9514
|
+
const resolved = import_node_path13.posix.normalize(import_node_path13.posix.join(base, target));
|
|
9515
|
+
return { target, line, resolved, missing: !exists((0, import_node_path13.join)(root, resolved)) };
|
|
9402
9516
|
});
|
|
9403
9517
|
for (const marker of markers) {
|
|
9404
9518
|
const coversMissing = links.some(
|
|
9405
9519
|
(l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
|
|
9406
9520
|
);
|
|
9407
|
-
if (!coversMissing && (exists((0,
|
|
9521
|
+
if (!coversMissing && (exists((0, import_node_path13.join)(root, marker.docRel)) || exists((0, import_node_path13.join)(root, marker.rootRel)))) {
|
|
9408
9522
|
direct.push({
|
|
9409
9523
|
kind: "stale-forward-ref",
|
|
9410
9524
|
doc,
|
|
@@ -9415,7 +9529,7 @@ function checkRefs(root, deps, docs2) {
|
|
|
9415
9529
|
}
|
|
9416
9530
|
for (const { ref, line } of extractRefs(markdown)) {
|
|
9417
9531
|
if (!firstVerifiable(refFirstSegment(ref))) continue;
|
|
9418
|
-
if (!exists((0,
|
|
9532
|
+
if (!exists((0, import_node_path13.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
|
|
9419
9533
|
}
|
|
9420
9534
|
for (const { target, line, resolved, missing } of links) {
|
|
9421
9535
|
if (resolved.startsWith("..")) {
|
|
@@ -9467,18 +9581,18 @@ function readFileOrNull(path2) {
|
|
|
9467
9581
|
}
|
|
9468
9582
|
function walk(dir, root, out) {
|
|
9469
9583
|
for (const entry of (0, import_node_fs13.readdirSync)(dir)) {
|
|
9470
|
-
const full = (0,
|
|
9584
|
+
const full = (0, import_node_path13.join)(dir, entry);
|
|
9471
9585
|
if ((0, import_node_fs13.statSync)(full).isDirectory()) walk(full, root, out);
|
|
9472
9586
|
else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
|
|
9473
9587
|
}
|
|
9474
9588
|
return out;
|
|
9475
9589
|
}
|
|
9476
9590
|
function defaultListDocs(root) {
|
|
9477
|
-
const docsDir = (0,
|
|
9591
|
+
const docsDir = (0, import_node_path13.join)(root, "docs");
|
|
9478
9592
|
const docs2 = ((0, import_node_fs13.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
|
|
9479
9593
|
(rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
|
|
9480
9594
|
);
|
|
9481
|
-
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs13.existsSync)((0,
|
|
9595
|
+
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs13.existsSync)((0, import_node_path13.join)(root, rel))), ...docs2];
|
|
9482
9596
|
}
|
|
9483
9597
|
var CHECK_IGNORE_MAX_BUFFER = 32 * 1024 * 1024;
|
|
9484
9598
|
function defaultIsIgnored(root, relPaths, exec = import_node_child_process5.execFileSync) {
|
|
@@ -9538,7 +9652,7 @@ function runDocRefs(root, deps = {}) {
|
|
|
9538
9652
|
const walked = listDocs(root);
|
|
9539
9653
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
9540
9654
|
const docs2 = Object.fromEntries(
|
|
9541
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0,
|
|
9655
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path13.join)(root, rel))]).filter(([, body]) => body != null)
|
|
9542
9656
|
);
|
|
9543
9657
|
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs2);
|
|
9544
9658
|
const findings = [
|
|
@@ -9646,19 +9760,19 @@ function walkMarkdown(dir) {
|
|
|
9646
9760
|
while (stack.length) {
|
|
9647
9761
|
const current = stack.pop();
|
|
9648
9762
|
for (const entry of (0, import_node_fs14.readdirSync)(current, { withFileTypes: true })) {
|
|
9649
|
-
const full = (0,
|
|
9763
|
+
const full = (0, import_node_path14.join)(current, entry.name);
|
|
9650
9764
|
if (entry.isDirectory()) {
|
|
9651
9765
|
stack.push(full);
|
|
9652
9766
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9653
|
-
out.push((0,
|
|
9767
|
+
out.push((0, import_node_path14.relative)(dir, full).split(import_node_path14.sep).join("/"));
|
|
9654
9768
|
}
|
|
9655
9769
|
}
|
|
9656
9770
|
}
|
|
9657
9771
|
return out;
|
|
9658
9772
|
}
|
|
9659
9773
|
function createDocsIndexDeps(repoRoot2) {
|
|
9660
|
-
const docsDir = (0,
|
|
9661
|
-
const indexPath = (0,
|
|
9774
|
+
const docsDir = (0, import_node_path14.join)(repoRoot2, "docs");
|
|
9775
|
+
const indexPath = (0, import_node_path14.join)(repoRoot2, DOCS_INDEX_PATH);
|
|
9662
9776
|
return {
|
|
9663
9777
|
listDocs: () => {
|
|
9664
9778
|
if (!(0, import_node_fs14.existsSync)(docsDir)) return [];
|
|
@@ -9667,7 +9781,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
9667
9781
|
const ignored = defaultIsIgnored(repoRoot2, walked.map((rel) => `docs/${rel}`));
|
|
9668
9782
|
return walked.filter((rel) => !ignored.has(`docs/${rel}`)).sort();
|
|
9669
9783
|
},
|
|
9670
|
-
readDoc: (relPath) => (0, import_node_fs14.readFileSync)((0,
|
|
9784
|
+
readDoc: (relPath) => (0, import_node_fs14.readFileSync)((0, import_node_path14.join)(docsDir, relPath), "utf8"),
|
|
9671
9785
|
readIndex: () => (0, import_node_fs14.existsSync)(indexPath) ? (0, import_node_fs14.readFileSync)(indexPath, "utf8") : null,
|
|
9672
9786
|
writeIndex: (content) => (0, import_node_fs14.writeFileSync)(indexPath, content, "utf8")
|
|
9673
9787
|
};
|
|
@@ -10317,13 +10431,13 @@ function parseVerifyBroker(stdout) {
|
|
|
10317
10431
|
|
|
10318
10432
|
// src/train-apply.ts
|
|
10319
10433
|
var import_node_fs16 = require("node:fs");
|
|
10320
|
-
var
|
|
10321
|
-
var
|
|
10434
|
+
var import_promises3 = require("node:fs/promises");
|
|
10435
|
+
var import_node_path16 = require("node:path");
|
|
10322
10436
|
|
|
10323
10437
|
// src/plugin-guard-io.ts
|
|
10324
10438
|
var import_node_fs15 = require("node:fs");
|
|
10325
10439
|
var import_node_child_process6 = require("node:child_process");
|
|
10326
|
-
var
|
|
10440
|
+
var import_node_path15 = require("node:path");
|
|
10327
10441
|
var import_node_os4 = require("node:os");
|
|
10328
10442
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
10329
10443
|
|
|
@@ -10614,15 +10728,15 @@ function runHostBin(bin, args, opts) {
|
|
|
10614
10728
|
return step ? execFileHard(file, argv, { ...shared, step }) : execFileP2(file, argv, shared);
|
|
10615
10729
|
}
|
|
10616
10730
|
function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
10617
|
-
if (surface === "codex") return env.CODEX_HOME?.trim() || (0,
|
|
10618
|
-
if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0,
|
|
10619
|
-
if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0,
|
|
10620
|
-
if (surface === "cursor") return (0,
|
|
10621
|
-
if (surface === "jervcode") return env.PI_CODING_AGENT_DIR?.trim() || (0,
|
|
10622
|
-
return (0,
|
|
10731
|
+
if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path15.join)(home, ".codex");
|
|
10732
|
+
if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path15.join)(home, ".kimi-code");
|
|
10733
|
+
if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path15.join)(home, ".config", "kilo");
|
|
10734
|
+
if (surface === "cursor") return (0, import_node_path15.join)(home, ".cursor");
|
|
10735
|
+
if (surface === "jervcode") return env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path15.join)(home, ".pi", "agent");
|
|
10736
|
+
return (0, import_node_path15.join)(home, ".claude");
|
|
10623
10737
|
}
|
|
10624
10738
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
10625
|
-
return (0,
|
|
10739
|
+
return (0, import_node_path15.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
|
|
10626
10740
|
};
|
|
10627
10741
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
10628
10742
|
try {
|
|
@@ -10635,15 +10749,15 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
|
|
|
10635
10749
|
if (surface === "codex") {
|
|
10636
10750
|
const root = surfaceConfigRoot(surface, env, home);
|
|
10637
10751
|
return [
|
|
10638
|
-
(0,
|
|
10639
|
-
(0,
|
|
10752
|
+
(0, import_node_path15.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
|
|
10753
|
+
(0, import_node_path15.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
|
|
10640
10754
|
];
|
|
10641
10755
|
}
|
|
10642
10756
|
if (surface === "kimi") return [];
|
|
10643
10757
|
if (surface === "kilo") return [];
|
|
10644
10758
|
if (surface === "cursor") return [];
|
|
10645
10759
|
if (surface === "jervcode") return [];
|
|
10646
|
-
return [(0,
|
|
10760
|
+
return [(0, import_node_path15.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
10647
10761
|
}
|
|
10648
10762
|
function marketplaceClonePresent(surface, home, exists = import_node_fs15.existsSync, env = process.env) {
|
|
10649
10763
|
return marketplaceCloneCandidates(surface, home, env).some(exists);
|
|
@@ -10694,11 +10808,11 @@ function codexHookTrustState(status = codexPluginStatus()) {
|
|
|
10694
10808
|
return { applicable: false, trusted: false, trustedCount: 0, requiredCount: 0 };
|
|
10695
10809
|
}
|
|
10696
10810
|
const root = surfaceConfigRoot("codex");
|
|
10697
|
-
const hooksPath = (0,
|
|
10811
|
+
const hooksPath = (0, import_node_path15.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version, "hooks", "codex-hooks.json");
|
|
10698
10812
|
const requiredCount = countCodexHookCommands(hooksPath);
|
|
10699
10813
|
let config = "";
|
|
10700
10814
|
try {
|
|
10701
|
-
config = (0, import_node_fs15.readFileSync)((0,
|
|
10815
|
+
config = (0, import_node_fs15.readFileSync)((0, import_node_path15.join)(root, "config.toml"), "utf8");
|
|
10702
10816
|
} catch {
|
|
10703
10817
|
return { applicable: true, trusted: false, trustedCount: 0, requiredCount };
|
|
10704
10818
|
}
|
|
@@ -10734,9 +10848,9 @@ async function npmSelfUpdateCli(target, onStep) {
|
|
|
10734
10848
|
}
|
|
10735
10849
|
function kiloConfigListsPlugin(configRoot, home = (0, import_node_os4.homedir)(), read = (p) => (0, import_node_fs15.readFileSync)(p, "utf8"), exists = import_node_fs15.existsSync) {
|
|
10736
10850
|
const candidates = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "config.json"];
|
|
10737
|
-
for (const dir of [configRoot, (0,
|
|
10851
|
+
for (const dir of [configRoot, (0, import_node_path15.join)(home, ".kilo")]) {
|
|
10738
10852
|
for (const file of candidates) {
|
|
10739
|
-
const path2 = (0,
|
|
10853
|
+
const path2 = (0, import_node_path15.join)(dir, file);
|
|
10740
10854
|
if (!exists(path2)) continue;
|
|
10741
10855
|
try {
|
|
10742
10856
|
const stripped = read(path2).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
@@ -10753,7 +10867,7 @@ function kiloConfigListsPlugin(configRoot, home = (0, import_node_os4.homedir)()
|
|
|
10753
10867
|
return false;
|
|
10754
10868
|
}
|
|
10755
10869
|
function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
10756
|
-
return (0,
|
|
10870
|
+
return (0, import_node_path15.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
|
|
10757
10871
|
}
|
|
10758
10872
|
function cursorPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
|
|
10759
10873
|
return [
|
|
@@ -10762,14 +10876,14 @@ function cursorPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
|
|
|
10762
10876
|
"hooks/cursor-hooks.json",
|
|
10763
10877
|
"scripts/hook-run.mjs",
|
|
10764
10878
|
"scripts/hook-policy.mjs"
|
|
10765
|
-
].every((path2) => exists((0,
|
|
10879
|
+
].every((path2) => exists((0, import_node_path15.join)(root, ...path2.split("/"))));
|
|
10766
10880
|
}
|
|
10767
10881
|
function kimiPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
|
|
10768
10882
|
return [
|
|
10769
10883
|
".kimi-plugin/plugin.json",
|
|
10770
10884
|
"skills/mmi/SKILL.md",
|
|
10771
10885
|
"scripts/hook-run.mjs"
|
|
10772
|
-
].every((path2) => exists((0,
|
|
10886
|
+
].every((path2) => exists((0, import_node_path15.join)(root, ...path2.split("/"))));
|
|
10773
10887
|
}
|
|
10774
10888
|
var JERVCODE_WRAPPER_DIR = ".pi-plugin";
|
|
10775
10889
|
function normalizePiEntry(value) {
|
|
@@ -10792,7 +10906,7 @@ function jervcodePackageFamily(entry) {
|
|
|
10792
10906
|
function isMmiOwnedPiEntry(entry) {
|
|
10793
10907
|
if (typeof entry !== "string") return false;
|
|
10794
10908
|
try {
|
|
10795
|
-
const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0,
|
|
10909
|
+
const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path15.join)(piEntryFsPath(entry), "package.json"), "utf8"));
|
|
10796
10910
|
return pkg.name === "mmi";
|
|
10797
10911
|
} catch {
|
|
10798
10912
|
return false;
|
|
@@ -10824,7 +10938,7 @@ function readPiSettings(path2) {
|
|
|
10824
10938
|
}
|
|
10825
10939
|
}
|
|
10826
10940
|
function mmiPiWrapperEntry(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
10827
|
-
const settings = readPiSettings((0,
|
|
10941
|
+
const settings = readPiSettings((0, import_node_path15.join)(surfaceConfigRoot("jervcode", env, home), "settings.json"));
|
|
10828
10942
|
const entries = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
10829
10943
|
for (const entry of entries) {
|
|
10830
10944
|
if (typeof entry !== "string") continue;
|
|
@@ -10837,17 +10951,17 @@ function mmiPiWrapperEntry(env = process.env, home = (0, import_node_os4.homedir
|
|
|
10837
10951
|
function mmiPiWrapperHealthy(entry) {
|
|
10838
10952
|
if (!entry) return false;
|
|
10839
10953
|
const wrapper = piEntryFsPath(entry);
|
|
10840
|
-
return isMmiOwnedPiEntry(entry) && (0, import_node_fs15.existsSync)((0,
|
|
10954
|
+
return isMmiOwnedPiEntry(entry) && (0, import_node_fs15.existsSync)((0, import_node_path15.join)((0, import_node_path15.dirname)(wrapper), "skills", "mmi", "SKILL.md"));
|
|
10841
10955
|
}
|
|
10842
10956
|
function findMmiPiSourceClone(home = (0, import_node_os4.homedir)()) {
|
|
10843
|
-
const cacheRoot = (0,
|
|
10957
|
+
const cacheRoot = (0, import_node_path15.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi");
|
|
10844
10958
|
let best = null;
|
|
10845
10959
|
try {
|
|
10846
10960
|
for (const entry of (0, import_node_fs15.readdirSync)(cacheRoot, { withFileTypes: true })) {
|
|
10847
10961
|
if (!entry.isDirectory() || !/^\d+\.\d+\.\d+$/.test(entry.name)) continue;
|
|
10848
|
-
if (!(0, import_node_fs15.existsSync)((0,
|
|
10962
|
+
if (!(0, import_node_fs15.existsSync)((0, import_node_path15.join)(cacheRoot, entry.name, JERVCODE_WRAPPER_DIR, "package.json"))) continue;
|
|
10849
10963
|
if (!best || compareVersions(entry.name, best.version) > 0) {
|
|
10850
|
-
best = { path: (0,
|
|
10964
|
+
best = { path: (0, import_node_path15.join)(cacheRoot, entry.name), version: entry.name };
|
|
10851
10965
|
}
|
|
10852
10966
|
}
|
|
10853
10967
|
} catch {
|
|
@@ -10885,7 +10999,7 @@ function healJervCodePackageRegistration(opts = {}) {
|
|
|
10885
10999
|
return { available: true, ok: true, changed: false, version: null, detail: "skipped \u2014 no installed MMI clone carries the pi wrapper (install the Claude plugin first)" };
|
|
10886
11000
|
}
|
|
10887
11001
|
const packagePath = `${clone.path.replace(/\\/g, "/").replace(/\/+$/, "")}/${JERVCODE_WRAPPER_DIR}`;
|
|
10888
|
-
const settingsPath2 = (0,
|
|
11002
|
+
const settingsPath2 = (0, import_node_path15.join)(agentDir, "settings.json");
|
|
10889
11003
|
const release = (0, import_node_fs15.existsSync)(settingsPath2) ? acquirePiSettingsLock(settingsPath2) : void 0;
|
|
10890
11004
|
if ((0, import_node_fs15.existsSync)(settingsPath2) && !release) {
|
|
10891
11005
|
return {
|
|
@@ -10915,7 +11029,7 @@ function healJervCodePackageRegistration(opts = {}) {
|
|
|
10915
11029
|
}
|
|
10916
11030
|
current.packages = merged.next;
|
|
10917
11031
|
try {
|
|
10918
|
-
(0, import_node_fs15.mkdirSync)((0,
|
|
11032
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path15.dirname)(settingsPath2), { recursive: true });
|
|
10919
11033
|
const tmp = `${settingsPath2}.tmp-${process.pid}`;
|
|
10920
11034
|
(0, import_node_fs15.writeFileSync)(tmp, `${JSON.stringify(current, null, 2)}
|
|
10921
11035
|
`, "utf8");
|
|
@@ -10948,7 +11062,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
10948
11062
|
return {
|
|
10949
11063
|
isOrgRepo,
|
|
10950
11064
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi's managed plugin directory is its native install record; it has no Claude-style ledger.
|
|
10951
|
-
surface === "kimi" && (0, import_node_fs15.existsSync)((0,
|
|
11065
|
+
surface === "kimi" && (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
|
|
10952
11066
|
surface === "kilo" && kiloConfigListsPlugin(root) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
|
|
10953
11067
|
surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs15.existsSync)(cursorLocalPluginRoot()),
|
|
10954
11068
|
// Kilo has no marketplace to clone — the config file IS the install record, so this dimension of
|
|
@@ -10957,9 +11071,9 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
10957
11071
|
// Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
|
|
10958
11072
|
// Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
|
|
10959
11073
|
// version stamp, so the stamp's presence is the cache signal.
|
|
10960
|
-
pluginCachePresent: surface === "jervcode" ? mmiPiWrapperHealthy(piEntry) : surface === "kilo" ? (0, import_node_fs15.existsSync)((0,
|
|
10961
|
-
codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs15.existsSync)((0,
|
|
10962
|
-
) : (0, import_node_fs15.existsSync)((0,
|
|
11074
|
+
pluginCachePresent: surface === "jervcode" ? mmiPiWrapperHealthy(piEntry) : surface === "kilo" ? (0, import_node_fs15.existsSync)((0, import_node_path15.join)((0, import_node_os4.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path15.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
|
|
11075
|
+
codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
|
|
11076
|
+
) : (0, import_node_fs15.existsSync)((0, import_node_path15.join)(root, "plugins", "cache", "mutmutco", "mmi"))
|
|
10963
11077
|
};
|
|
10964
11078
|
}
|
|
10965
11079
|
async function runHostBinLogged(bin, args, opts) {
|
|
@@ -10979,9 +11093,9 @@ async function runPluginCli(bin, args, log) {
|
|
|
10979
11093
|
function captureCodexHookLauncher() {
|
|
10980
11094
|
const status = codexPluginStatus();
|
|
10981
11095
|
if (!status.installed || !status.enabled || !status.version) return void 0;
|
|
10982
|
-
const root = (0,
|
|
11096
|
+
const root = (0, import_node_path15.join)(surfaceConfigRoot("codex"), "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version);
|
|
10983
11097
|
const files = ["mmi-hook", "mmi-hook.exe"].flatMap((name) => {
|
|
10984
|
-
const path2 = (0,
|
|
11098
|
+
const path2 = (0, import_node_path15.join)(root, "bin", name);
|
|
10985
11099
|
try {
|
|
10986
11100
|
return [{ name, content: (0, import_node_fs15.readFileSync)(path2) }];
|
|
10987
11101
|
} catch {
|
|
@@ -10991,11 +11105,11 @@ function captureCodexHookLauncher() {
|
|
|
10991
11105
|
return files.length === 2 ? { root, files } : void 0;
|
|
10992
11106
|
}
|
|
10993
11107
|
function restoreCodexHookLauncher(snapshot) {
|
|
10994
|
-
if (!snapshot || (0, import_node_fs15.existsSync)((0,
|
|
10995
|
-
const bin = (0,
|
|
11108
|
+
if (!snapshot || (0, import_node_fs15.existsSync)((0, import_node_path15.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
|
|
11109
|
+
const bin = (0, import_node_path15.join)(snapshot.root, "bin");
|
|
10996
11110
|
(0, import_node_fs15.mkdirSync)(bin, { recursive: true });
|
|
10997
11111
|
for (const file of snapshot.files) {
|
|
10998
|
-
const path2 = (0,
|
|
11112
|
+
const path2 = (0, import_node_path15.join)(bin, file.name);
|
|
10999
11113
|
(0, import_node_fs15.writeFileSync)(path2, file.content);
|
|
11000
11114
|
if (file.name === "mmi-hook") (0, import_node_fs15.chmodSync)(path2, 493);
|
|
11001
11115
|
}
|
|
@@ -11006,8 +11120,8 @@ function canonicalCursorRemote(remote) {
|
|
|
11006
11120
|
}
|
|
11007
11121
|
async function installCursorPluginCheckout(env = process.env) {
|
|
11008
11122
|
const configRoot = surfaceConfigRoot("cursor", env);
|
|
11009
|
-
const pluginsRoot = (0,
|
|
11010
|
-
const target = (0,
|
|
11123
|
+
const pluginsRoot = (0, import_node_path15.join)(configRoot, "plugins");
|
|
11124
|
+
const target = (0, import_node_path15.join)(pluginsRoot, "local", "mmi");
|
|
11011
11125
|
const source = env.MMI_CURSOR_PLUGIN_SOURCE?.trim();
|
|
11012
11126
|
if ((0, import_node_fs15.existsSync)(target) && !source) {
|
|
11013
11127
|
try {
|
|
@@ -11019,12 +11133,12 @@ async function installCursorPluginCheckout(env = process.env) {
|
|
|
11019
11133
|
return { ok: false, detail: `refused to replace unmanaged Cursor plugin directory at ${target}` };
|
|
11020
11134
|
}
|
|
11021
11135
|
}
|
|
11022
|
-
(0, import_node_fs15.mkdirSync)((0,
|
|
11023
|
-
(0, import_node_fs15.mkdirSync)((0,
|
|
11024
|
-
(0, import_node_fs15.mkdirSync)((0,
|
|
11136
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path15.join)(pluginsRoot, "local"), { recursive: true });
|
|
11137
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path15.join)(pluginsRoot, "staging"), { recursive: true });
|
|
11138
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path15.join)(pluginsRoot, "quarantine"), { recursive: true });
|
|
11025
11139
|
const suffix = `${Date.now()}-${process.pid}`;
|
|
11026
|
-
const staged = (0,
|
|
11027
|
-
const quarantined = (0,
|
|
11140
|
+
const staged = (0, import_node_path15.join)(pluginsRoot, "staging", `mmi-${suffix}`);
|
|
11141
|
+
const quarantined = (0, import_node_path15.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
|
|
11028
11142
|
try {
|
|
11029
11143
|
if (source) {
|
|
11030
11144
|
(0, import_node_fs15.cpSync)(source, staged, {
|
|
@@ -11107,7 +11221,7 @@ async function applyPluginHeal(surface, log, opts) {
|
|
|
11107
11221
|
const refSupported = await marketplaceAddRefSupported(bin);
|
|
11108
11222
|
const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
|
|
11109
11223
|
log(healBannerLine(bin, token, refSupported));
|
|
11110
|
-
const pinsPath = (0,
|
|
11224
|
+
const pinsPath = (0, import_node_path15.join)((0, import_node_os4.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
|
|
11111
11225
|
const pins = token === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
|
|
11112
11226
|
try {
|
|
11113
11227
|
for (const step of steps) {
|
|
@@ -11224,7 +11338,7 @@ function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsR
|
|
|
11224
11338
|
return writeMarketplacePinsOnDisk(
|
|
11225
11339
|
path2,
|
|
11226
11340
|
pins,
|
|
11227
|
-
(restored) => hostIsRunning() ? `${restoredPinsLine(restored)}, but Claude Code is running and can rewrite this file from its own copy \u2014 restart it, then \`mmi-cli doctor
|
|
11341
|
+
(restored) => hostIsRunning() ? `${restoredPinsLine(restored)}, but Claude Code is running and can rewrite this file from its own copy \u2014 restart it, then \`mmi-cli doctor\` if the pins did not survive` : restoredPinsLine(restored),
|
|
11228
11342
|
"restore"
|
|
11229
11343
|
);
|
|
11230
11344
|
}
|
|
@@ -11238,13 +11352,13 @@ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunni
|
|
|
11238
11352
|
return `pinned ${[...pins.keys()].join(", ")} to ${ORG_MARKETPLACE_PINS.ref} with auto-update on`;
|
|
11239
11353
|
},
|
|
11240
11354
|
"pin",
|
|
11241
|
-
() => hostIsRunning() ? "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor
|
|
11355
|
+
() => hostIsRunning() ? "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor`" : void 0
|
|
11242
11356
|
);
|
|
11243
11357
|
return detail === void 0 ? void 0 : { detail, wrote: landed };
|
|
11244
11358
|
}
|
|
11245
11359
|
function writeMarketplacePinPending(path2, names, now = Date.now()) {
|
|
11246
11360
|
try {
|
|
11247
|
-
(0, import_node_fs15.mkdirSync)((0,
|
|
11361
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path15.dirname)(path2), { recursive: true });
|
|
11248
11362
|
(0, import_node_fs15.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
|
|
11249
11363
|
`, "utf8");
|
|
11250
11364
|
} catch {
|
|
@@ -11388,18 +11502,46 @@ async function resolveFoldPaths(deps, model) {
|
|
|
11388
11502
|
}
|
|
11389
11503
|
return ["package.json", "package-lock.json"];
|
|
11390
11504
|
}
|
|
11505
|
+
async function alignExistingHotfixReleaseMarker(deps, version) {
|
|
11506
|
+
const script = [
|
|
11507
|
+
"const fs = require('fs');",
|
|
11508
|
+
"const p = 'package.json';",
|
|
11509
|
+
"if (!fs.existsSync(p)) { process.stdout.write('absent'); process.exit(0); }",
|
|
11510
|
+
"const j = JSON.parse(fs.readFileSync(p, 'utf8'));",
|
|
11511
|
+
"const marker = j.jervaiseRelease;",
|
|
11512
|
+
"const hasHotfixMarker = !!(marker && typeof marker === 'object' && marker.kind === 'hotfix');",
|
|
11513
|
+
"const requiresMarker = fs.existsSync('scripts/check-release-kind.mjs');",
|
|
11514
|
+
"if (!hasHotfixMarker && !requiresMarker) {",
|
|
11515
|
+
" process.stdout.write('skip');",
|
|
11516
|
+
" process.exit(0);",
|
|
11517
|
+
"}",
|
|
11518
|
+
`const version = ${JSON.stringify(version)};`,
|
|
11519
|
+
"if (hasHotfixMarker && marker.version === version) { process.stdout.write('ok'); process.exit(0); }",
|
|
11520
|
+
"j.jervaiseRelease = { kind: 'hotfix', version };",
|
|
11521
|
+
"fs.writeFileSync(p, JSON.stringify(j, null, 2) + '\\n');",
|
|
11522
|
+
"process.stdout.write(hasHotfixMarker ? 'updated' : 'stamped');"
|
|
11523
|
+
].join("");
|
|
11524
|
+
const out = (await deps.run("node", ["-e", script])).trim();
|
|
11525
|
+
if (out === "updated") return `jervaiseRelease hotfix marker aligned to ${version}`;
|
|
11526
|
+
if (out === "stamped") return `jervaiseRelease hotfix marker stamped for ${version}`;
|
|
11527
|
+
return null;
|
|
11528
|
+
}
|
|
11391
11529
|
async function installAppFoldDeps(deps) {
|
|
11392
11530
|
const hasLock = await deps.run("git", ["cat-file", "-e", "HEAD:package-lock.json"]).then(() => true).catch(() => false);
|
|
11393
11531
|
await deps.run("npm", hasLock ? ["ci"] : ["install"]);
|
|
11394
11532
|
}
|
|
11395
|
-
async function foldReleaseVersion(deps, model, tag, foldPaths) {
|
|
11533
|
+
async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "HEAD") {
|
|
11396
11534
|
if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
|
|
11397
11535
|
const version = tag.replace(/^v/, "");
|
|
11398
11536
|
if (model === "hub-serverless") {
|
|
11399
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit",
|
|
11537
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", sourceCommit]);
|
|
11400
11538
|
} else {
|
|
11401
11539
|
await installAppFoldDeps(deps);
|
|
11402
11540
|
await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
|
|
11541
|
+
const patch = Number(version.split(".")[2] ?? "0");
|
|
11542
|
+
if (model === "registry-publish" && Number.isFinite(patch) && patch > 0) {
|
|
11543
|
+
await alignExistingHotfixReleaseMarker(deps, version);
|
|
11544
|
+
}
|
|
11403
11545
|
}
|
|
11404
11546
|
for (const path2 of foldPaths) {
|
|
11405
11547
|
await deps.run("git", ["add", "--", path2]).catch(() => void 0);
|
|
@@ -12356,7 +12498,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
12356
12498
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
12357
12499
|
}
|
|
12358
12500
|
function readLocalGateWorkflows() {
|
|
12359
|
-
const dir = (0,
|
|
12501
|
+
const dir = (0, import_node_path16.join)(".github", "workflows");
|
|
12360
12502
|
let names;
|
|
12361
12503
|
try {
|
|
12362
12504
|
names = (0, import_node_fs16.readdirSync)(dir);
|
|
@@ -12366,7 +12508,7 @@ function readLocalGateWorkflows() {
|
|
|
12366
12508
|
const files = [];
|
|
12367
12509
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
12368
12510
|
try {
|
|
12369
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs16.readFileSync)((0,
|
|
12511
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs16.readFileSync)((0, import_node_path16.join)(dir, name), "utf8") });
|
|
12370
12512
|
} catch {
|
|
12371
12513
|
}
|
|
12372
12514
|
}
|
|
@@ -12491,7 +12633,7 @@ async function waitForFoldIndexLock(deps, startBranch, resumeCommand) {
|
|
|
12491
12633
|
const deadline = now() + GIT_INDEX_LOCK_WAIT_MS;
|
|
12492
12634
|
for (; ; ) {
|
|
12493
12635
|
try {
|
|
12494
|
-
await (0,
|
|
12636
|
+
await (0, import_promises3.stat)(lockPath);
|
|
12495
12637
|
} catch (e) {
|
|
12496
12638
|
if (e.code === "ENOENT") return;
|
|
12497
12639
|
throw foldFailureGuidance(
|
|
@@ -14473,7 +14615,7 @@ var import_node_fs18 = require("node:fs");
|
|
|
14473
14615
|
// src/stage-runner.ts
|
|
14474
14616
|
var import_node_child_process7 = require("node:child_process");
|
|
14475
14617
|
var import_node_fs17 = require("node:fs");
|
|
14476
|
-
var
|
|
14618
|
+
var import_node_path17 = require("node:path");
|
|
14477
14619
|
var import_node_net = require("node:net");
|
|
14478
14620
|
var import_node_util5 = require("node:util");
|
|
14479
14621
|
|
|
@@ -14612,11 +14754,11 @@ function appendForceRecreate(up) {
|
|
|
14612
14754
|
return `${up.trimEnd()} --force-recreate`;
|
|
14613
14755
|
}
|
|
14614
14756
|
function stageStatePath(cwd = process.cwd()) {
|
|
14615
|
-
return (0,
|
|
14757
|
+
return (0, import_node_path17.join)(cwd, "tmp", "stage", "state.json");
|
|
14616
14758
|
}
|
|
14617
14759
|
function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
14618
|
-
const dir = (0,
|
|
14619
|
-
return (0,
|
|
14760
|
+
const dir = (0, import_node_path17.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path17.resolve)(cwd, gitCommonDir);
|
|
14761
|
+
return (0, import_node_path17.join)(dir, "mmi", "stage", "state.json");
|
|
14620
14762
|
}
|
|
14621
14763
|
function normPath2(path2) {
|
|
14622
14764
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -14840,8 +14982,8 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
14840
14982
|
}
|
|
14841
14983
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
14842
14984
|
if (!config.ensureEnv) return;
|
|
14843
|
-
const target = (0,
|
|
14844
|
-
const example = (0,
|
|
14985
|
+
const target = (0, import_node_path17.join)(cwd, config.ensureEnv.target);
|
|
14986
|
+
const example = (0, import_node_path17.join)(cwd, config.ensureEnv.example);
|
|
14845
14987
|
if (!(0, import_node_fs17.existsSync)(target) && (0, import_node_fs17.existsSync)(example)) {
|
|
14846
14988
|
(0, import_node_fs17.copyFileSync)(example, target);
|
|
14847
14989
|
} else if ((0, import_node_fs17.existsSync)(target) && (0, import_node_fs17.existsSync)(example)) {
|
|
@@ -15212,7 +15354,7 @@ var import_node_os17 = require("node:os");
|
|
|
15212
15354
|
var import_node_child_process9 = require("node:child_process");
|
|
15213
15355
|
var import_node_fs21 = require("node:fs");
|
|
15214
15356
|
var import_node_os7 = require("node:os");
|
|
15215
|
-
var
|
|
15357
|
+
var import_node_path20 = require("node:path");
|
|
15216
15358
|
var import_node_util6 = require("node:util");
|
|
15217
15359
|
|
|
15218
15360
|
// src/board-priority.ts
|
|
@@ -15567,7 +15709,7 @@ function boardConfigFromProject(meta, floor = {}) {
|
|
|
15567
15709
|
|
|
15568
15710
|
// src/cli-doctor-shared.ts
|
|
15569
15711
|
var import_node_fs19 = require("node:fs");
|
|
15570
|
-
var
|
|
15712
|
+
var import_node_path19 = require("node:path");
|
|
15571
15713
|
var import_node_fs20 = require("node:fs");
|
|
15572
15714
|
|
|
15573
15715
|
// src/readiness-audit.ts
|
|
@@ -15688,9 +15830,9 @@ var import_node_child_process8 = require("node:child_process");
|
|
|
15688
15830
|
var import_node_os6 = require("node:os");
|
|
15689
15831
|
|
|
15690
15832
|
// src/gh-create.ts
|
|
15691
|
-
var
|
|
15833
|
+
var import_promises4 = require("node:fs/promises");
|
|
15692
15834
|
var import_node_os5 = require("node:os");
|
|
15693
|
-
var
|
|
15835
|
+
var import_node_path18 = require("node:path");
|
|
15694
15836
|
var import_node_crypto3 = require("node:crypto");
|
|
15695
15837
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
15696
15838
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -15740,12 +15882,12 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
15740
15882
|
const i = args.indexOf("--body");
|
|
15741
15883
|
if (i === -1 || i + 1 >= args.length) return { args, cleanup: async () => {
|
|
15742
15884
|
} };
|
|
15743
|
-
const write = deps.write ??
|
|
15744
|
-
const remove2 = deps.remove ??
|
|
15745
|
-
const ensureDir = deps.ensureDir ??
|
|
15885
|
+
const write = deps.write ?? import_promises4.writeFile;
|
|
15886
|
+
const remove2 = deps.remove ?? import_promises4.unlink;
|
|
15887
|
+
const ensureDir = deps.ensureDir ?? import_promises4.mkdir;
|
|
15746
15888
|
const dir = deps.dir ?? (0, import_node_os5.tmpdir)();
|
|
15747
|
-
const file = (0,
|
|
15748
|
-
await ensureDir((0,
|
|
15889
|
+
const file = (0, import_node_path18.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
15890
|
+
await ensureDir((0, import_node_path18.dirname)(file), { recursive: true }).catch(() => {
|
|
15749
15891
|
});
|
|
15750
15892
|
await write(file, args[i + 1], "utf8");
|
|
15751
15893
|
return {
|
|
@@ -17269,7 +17411,7 @@ async function localBranchHeads() {
|
|
|
17269
17411
|
}
|
|
17270
17412
|
async function currentRepoWorktreeGitRoot(repoRoot2) {
|
|
17271
17413
|
const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
17272
|
-
return gitCommonDir ? (0,
|
|
17414
|
+
return gitCommonDir ? (0, import_node_path19.resolve)(repoRoot2, gitCommonDir, "worktrees") : "";
|
|
17273
17415
|
}
|
|
17274
17416
|
async function worktreeBranches() {
|
|
17275
17417
|
const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
@@ -17289,7 +17431,7 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
|
|
|
17289
17431
|
const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
|
|
17290
17432
|
if (!match?.[1]) return void 0;
|
|
17291
17433
|
const raw = match[1].trim();
|
|
17292
|
-
return (0,
|
|
17434
|
+
return (0, import_node_path19.isAbsolute)(raw) ? raw : (0, import_node_path19.resolve)(worktreePath, raw);
|
|
17293
17435
|
}
|
|
17294
17436
|
function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
17295
17437
|
if (!worktreeGitRoot) return false;
|
|
@@ -17298,9 +17440,9 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
|
17298
17440
|
for (const ent of entries) {
|
|
17299
17441
|
if (!ent.isDirectory()) continue;
|
|
17300
17442
|
try {
|
|
17301
|
-
const gitdirPath = (0, import_node_fs19.readFileSync)((0,
|
|
17302
|
-
const resolvedGitdir = (0,
|
|
17303
|
-
if (sameWorktreeMetadataPath((0,
|
|
17443
|
+
const gitdirPath = (0, import_node_fs19.readFileSync)((0, import_node_path19.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
|
|
17444
|
+
const resolvedGitdir = (0, import_node_path19.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path19.resolve)(worktreeGitRoot, ent.name, gitdirPath);
|
|
17445
|
+
if (sameWorktreeMetadataPath((0, import_node_path19.dirname)(resolvedGitdir), worktreePath)) return true;
|
|
17304
17446
|
} catch {
|
|
17305
17447
|
}
|
|
17306
17448
|
}
|
|
@@ -17319,7 +17461,7 @@ function pathExistsKnown(path2) {
|
|
|
17319
17461
|
}
|
|
17320
17462
|
}
|
|
17321
17463
|
function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
17322
|
-
const gitPath = (0,
|
|
17464
|
+
const gitPath = (0, import_node_path19.join)(path2, ".git");
|
|
17323
17465
|
let st;
|
|
17324
17466
|
try {
|
|
17325
17467
|
st = (0, import_node_fs20.lstatSync)(gitPath);
|
|
@@ -17373,7 +17515,7 @@ async function preservedBranches() {
|
|
|
17373
17515
|
async function siblingWorktreeDirs(explicitRoot) {
|
|
17374
17516
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
17375
17517
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
17376
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
17518
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path19.dirname)((0, import_node_path19.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
17377
17519
|
try {
|
|
17378
17520
|
const dirs = explicitRoot ? listDirsIn(resolveExplicitScanRoot(explicitRoot, primaryRepoRoot)) : worktreeScanDirs(siblingMmiWorktreesRoot(primaryRepoRoot), primaryRepoRoot, listDirsIn, isRepoCheckoutDir);
|
|
17379
17521
|
return dirs.map((dir) => inspectSiblingWorktreeDir(dir, worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
@@ -17383,13 +17525,13 @@ async function siblingWorktreeDirs(explicitRoot) {
|
|
|
17383
17525
|
}
|
|
17384
17526
|
function listDirsIn(dir) {
|
|
17385
17527
|
try {
|
|
17386
|
-
return (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0,
|
|
17528
|
+
return (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path19.join)(dir, ent.name));
|
|
17387
17529
|
} catch {
|
|
17388
17530
|
return [];
|
|
17389
17531
|
}
|
|
17390
17532
|
}
|
|
17391
17533
|
function isRepoCheckoutDir(dir) {
|
|
17392
|
-
return (0, import_node_fs20.existsSync)((0,
|
|
17534
|
+
return (0, import_node_fs20.existsSync)((0, import_node_path19.join)(dir, ".git"));
|
|
17393
17535
|
}
|
|
17394
17536
|
function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
|
|
17395
17537
|
let rootDirs;
|
|
@@ -18751,14 +18893,14 @@ function probeLocalClaimSession(marker, now = Date.now()) {
|
|
|
18751
18893
|
claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
|
|
18752
18894
|
return state;
|
|
18753
18895
|
};
|
|
18754
|
-
const root = (0,
|
|
18896
|
+
const root = (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".claude", "projects");
|
|
18755
18897
|
try {
|
|
18756
18898
|
const wanted = `${marker.session}.jsonl`.toLowerCase();
|
|
18757
18899
|
const pending = [root];
|
|
18758
18900
|
while (pending.length) {
|
|
18759
18901
|
const dir = pending.pop();
|
|
18760
18902
|
for (const entry of (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true })) {
|
|
18761
|
-
const path2 = (0,
|
|
18903
|
+
const path2 = (0, import_node_path20.join)(dir, entry.name);
|
|
18762
18904
|
if (entry.isDirectory()) pending.push(path2);
|
|
18763
18905
|
else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
|
|
18764
18906
|
return remember(now - (0, import_node_fs21.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
|
|
@@ -19578,7 +19720,7 @@ function consolidateCommandNamespaces(program3) {
|
|
|
19578
19720
|
|
|
19579
19721
|
// src/pi-plugin-registration.ts
|
|
19580
19722
|
var import_node_fs22 = require("node:fs");
|
|
19581
|
-
var
|
|
19723
|
+
var import_node_path21 = require("node:path");
|
|
19582
19724
|
|
|
19583
19725
|
// src/plugin-cache-prune.ts
|
|
19584
19726
|
var PLUGIN_CACHE_KEEP = 2;
|
|
@@ -19837,26 +19979,26 @@ function newestExistingPiPlugin(home) {
|
|
|
19837
19979
|
return void 0;
|
|
19838
19980
|
}
|
|
19839
19981
|
for (const version of names.filter(isVersionDirName).sort((a, b) => compareVersions(b, a))) {
|
|
19840
|
-
const candidate = (0,
|
|
19982
|
+
const candidate = (0, import_node_path21.join)(cacheRoot, version, ".pi-plugin");
|
|
19841
19983
|
if ((0, import_node_fs22.existsSync)(candidate)) return candidate;
|
|
19842
19984
|
}
|
|
19843
19985
|
return void 0;
|
|
19844
19986
|
}
|
|
19845
19987
|
function expectedPiPluginPath(home, env, installedVersion) {
|
|
19846
19988
|
const root = env.CLAUDE_PLUGIN_ROOT?.trim();
|
|
19847
|
-
if (root && /[\\/]mutmutco[\\/]mmi[\\/]/.test(root)) return (0,
|
|
19989
|
+
if (root && /[\\/]mutmutco[\\/]mmi[\\/]/.test(root)) return (0, import_node_path21.join)(root, ".pi-plugin");
|
|
19848
19990
|
const version = installedVersion ?? runningPluginVersion(env);
|
|
19849
19991
|
if (version) {
|
|
19850
|
-
const pinned = (0,
|
|
19992
|
+
const pinned = (0, import_node_path21.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi", version, ".pi-plugin");
|
|
19851
19993
|
if ((0, import_node_fs22.existsSync)(pinned)) return pinned;
|
|
19852
19994
|
}
|
|
19853
19995
|
return newestExistingPiPlugin(home);
|
|
19854
19996
|
}
|
|
19855
19997
|
function settingsPath(home) {
|
|
19856
|
-
return (0,
|
|
19998
|
+
return (0, import_node_path21.join)(home, ".pi", "agent", "settings.json");
|
|
19857
19999
|
}
|
|
19858
20000
|
function readPiPluginState(home, env, installedVersion) {
|
|
19859
|
-
if (!(0, import_node_fs22.existsSync)((0,
|
|
20001
|
+
if (!(0, import_node_fs22.existsSync)((0, import_node_path21.join)(home, ".pi", "agent"))) return void 0;
|
|
19860
20002
|
const expectedPath = expectedPiPluginPath(home, env, installedVersion);
|
|
19861
20003
|
if (!expectedPath) return void 0;
|
|
19862
20004
|
const file = settingsPath(home);
|
|
@@ -20516,7 +20658,14 @@ async function runHotfixStart(deps, options) {
|
|
|
20516
20658
|
notes.push(`deleted stale origin/${branch} left by the incomplete train`);
|
|
20517
20659
|
}
|
|
20518
20660
|
}
|
|
20519
|
-
const
|
|
20661
|
+
const specs = splitCarrySpecs([options.from]);
|
|
20662
|
+
if (specs.length === 0) throw new Error("hotfix start: --from named no PR or SHA");
|
|
20663
|
+
const sources = [];
|
|
20664
|
+
for (const spec of specs) {
|
|
20665
|
+
const resolved = await resolveHotfixSource(deps, ctx, spec);
|
|
20666
|
+
if (!sources.some((s) => s.sha === resolved.sha)) sources.push(resolved);
|
|
20667
|
+
}
|
|
20668
|
+
const label = sources.map((s) => s.label).join(", ");
|
|
20520
20669
|
const foldPaths = deployModel === "hub-serverless" || deployModel === "registry-publish" ? await resolveFoldPaths(deps, deployModel) : [];
|
|
20521
20670
|
const pickTolerated = deployModel === "hub-serverless" ? [...foldPaths, ...HOTFIX_SKILL_TOLERATED_ROOTS] : foldPaths;
|
|
20522
20671
|
if (deployModel === "hub-serverless") {
|
|
@@ -20532,9 +20681,11 @@ async function runHotfixStart(deps, options) {
|
|
|
20532
20681
|
const preexistingLocal = clean3(await deps.run("git", ["branch", "--list", branch]));
|
|
20533
20682
|
await deps.run("git", ["checkout", "-B", branch, "origin/main"]);
|
|
20534
20683
|
try {
|
|
20535
|
-
const
|
|
20536
|
-
|
|
20537
|
-
|
|
20684
|
+
for (const source of sources) {
|
|
20685
|
+
const autoResolved = await cherryPickWithToleratedPaths(deps, source.sha, pickTolerated);
|
|
20686
|
+
if (autoResolved.length > 0) {
|
|
20687
|
+
notes.push(`auto-resolved regenerable cherry-pick conflict(s) for ${source.label}: ${autoResolved.join(", ")} (regenerated in bump step)`);
|
|
20688
|
+
}
|
|
20538
20689
|
}
|
|
20539
20690
|
} catch (e) {
|
|
20540
20691
|
const recovery = await restoreAfterFailedPort(deps, { startBranch, branch, created: !preexistingLocal });
|
|
@@ -20575,10 +20726,12 @@ async function runHotfixStart(deps, options) {
|
|
|
20575
20726
|
branch,
|
|
20576
20727
|
"--title",
|
|
20577
20728
|
`[hotfix] ${tag}`,
|
|
20729
|
+
// Every picked sha goes in the marker (#4411) — `hotfix release` reads it when --carries is omitted,
|
|
20730
|
+
// so a multi-fix cycle proves ALL of its targets present before tagging, not just the first.
|
|
20578
20731
|
"--body",
|
|
20579
20732
|
`Hotfix ${tag}: cherry-pick of ${label} onto origin/main${bumpNote}.
|
|
20580
20733
|
|
|
20581
|
-
<!-- mmi-hotfix-carries: ${sha} -->
|
|
20734
|
+
<!-- mmi-hotfix-carries: ${sources.map((s) => s.sha).join(",")} -->
|
|
20582
20735
|
|
|
20583
20736
|
Merge this PR (human-initiated), then run \`mmi-cli hotfix release ${tag}\`.`
|
|
20584
20737
|
]));
|
|
@@ -20621,6 +20774,61 @@ async function watchReleaseRun(deps, ctx, workflow, sha) {
|
|
|
20621
20774
|
}
|
|
20622
20775
|
return { workflow, conclusion: "not-found" };
|
|
20623
20776
|
}
|
|
20777
|
+
function devFoldBranch(tag) {
|
|
20778
|
+
return `hotfix-fold/${tag}`;
|
|
20779
|
+
}
|
|
20780
|
+
async function portFoldToDevelopment(deps, ctx, deployModel, tag) {
|
|
20781
|
+
const foldPaths = deployModel === "hub-serverless" || deployModel === "registry-publish" ? await resolveFoldPaths(deps, deployModel) : [];
|
|
20782
|
+
if (foldPaths.length === 0) return `fold port skipped (deployModel=${deployModel} folds no version manifest)`;
|
|
20783
|
+
const branch = devFoldBranch(tag);
|
|
20784
|
+
const listed = await deps.run("gh", [
|
|
20785
|
+
"pr",
|
|
20786
|
+
"list",
|
|
20787
|
+
"--repo",
|
|
20788
|
+
ctx.repo,
|
|
20789
|
+
"--head",
|
|
20790
|
+
branch,
|
|
20791
|
+
"--base",
|
|
20792
|
+
"development",
|
|
20793
|
+
"--state",
|
|
20794
|
+
"all",
|
|
20795
|
+
"--limit",
|
|
20796
|
+
"10",
|
|
20797
|
+
"--json",
|
|
20798
|
+
"number,state,url"
|
|
20799
|
+
]);
|
|
20800
|
+
const existing = JSON.parse(listed || "[]").filter((r) => r.state === "OPEN" || r.state === "MERGED").sort((a, b) => (b.number ?? 0) - (a.number ?? 0))[0];
|
|
20801
|
+
if (existing) return `development fold PR #${existing.number} for ${tag} is ${existing.state} \u2014 reused`;
|
|
20802
|
+
const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
20803
|
+
try {
|
|
20804
|
+
await deps.run("git", ["fetch", "origin", "development"]);
|
|
20805
|
+
await deps.run("git", ["checkout", "-B", branch, "origin/development"]);
|
|
20806
|
+
const durableSource = clean3(await deps.run("git", ["merge-base", "HEAD", "origin/development"]));
|
|
20807
|
+
const foldNote = await foldReleaseVersion(deps, deployModel, tag, foldPaths, durableSource);
|
|
20808
|
+
const committed = clean3(await deps.run("git", ["rev-list", "--count", "origin/development..HEAD"]));
|
|
20809
|
+
if (committed === "0") return `development already carries ${tag} \u2014 no fold PR needed (${foldNote})`;
|
|
20810
|
+
await deps.run("git", ["push", "-u", "origin", branch]);
|
|
20811
|
+
const prUrl = clean3(await deps.run("gh", [
|
|
20812
|
+
"pr",
|
|
20813
|
+
"create",
|
|
20814
|
+
"--repo",
|
|
20815
|
+
ctx.repo,
|
|
20816
|
+
"--base",
|
|
20817
|
+
"development",
|
|
20818
|
+
"--head",
|
|
20819
|
+
branch,
|
|
20820
|
+
"--title",
|
|
20821
|
+
`chore(release): port the ${tag} version fold to development`,
|
|
20822
|
+
"--body",
|
|
20823
|
+
`Regenerated version fold for ${tag} (#4410). \`main\` carries the released version; without this development declares the previous one and every PR into it fails catalog-lockstep.
|
|
20824
|
+
|
|
20825
|
+
Generated by \`mmi-cli hotfix release\` \u2014 no main-parented commit is merged in, so the squash parents stay clean (#4365/#4371).`
|
|
20826
|
+
]));
|
|
20827
|
+
return `opened development fold PR ${prUrl} (${foldNote})`;
|
|
20828
|
+
} finally {
|
|
20829
|
+
if (previousRef && previousRef !== "HEAD") await deps.run("git", ["checkout", previousRef]).catch(() => void 0);
|
|
20830
|
+
}
|
|
20831
|
+
}
|
|
20624
20832
|
async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
20625
20833
|
const ctx = await buildTrainApplyContext(deps);
|
|
20626
20834
|
const deployModel = await resolveHotfixDeployModel(deps, ctx);
|
|
@@ -20746,6 +20954,12 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
20746
20954
|
} else {
|
|
20747
20955
|
verifyNote = `distribution verify skipped (deployModel=${deployModel}, Hub-only step)`;
|
|
20748
20956
|
}
|
|
20957
|
+
let foldNote;
|
|
20958
|
+
try {
|
|
20959
|
+
foldNote = await portFoldToDevelopment(deps, ctx, deployModel, tag);
|
|
20960
|
+
} catch (e) {
|
|
20961
|
+
foldNote = `development fold port FAILED: ${e.message ?? e} \u2014 the release stands; port it by hand: git checkout -B ${devFoldBranch(tag)} origin/development && node scripts/release-distribution.mjs prepare ${version}, then open a development-base PR`;
|
|
20962
|
+
}
|
|
20749
20963
|
return {
|
|
20750
20964
|
...ctx,
|
|
20751
20965
|
command: "hotfix-release",
|
|
@@ -20758,6 +20972,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
20758
20972
|
runs,
|
|
20759
20973
|
deployNote,
|
|
20760
20974
|
verifyNote,
|
|
20975
|
+
foldNote,
|
|
20761
20976
|
announceNote
|
|
20762
20977
|
};
|
|
20763
20978
|
}
|
|
@@ -21408,7 +21623,7 @@ function renderAccessReport(report) {
|
|
|
21408
21623
|
var import_node_crypto4 = require("node:crypto");
|
|
21409
21624
|
var import_node_child_process12 = require("node:child_process");
|
|
21410
21625
|
var import_node_fs23 = require("node:fs");
|
|
21411
|
-
var
|
|
21626
|
+
var import_node_path22 = require("node:path");
|
|
21412
21627
|
var REPO_INDEX_SCHEMA = 1;
|
|
21413
21628
|
var HARD_DENY = [
|
|
21414
21629
|
/(^|\/)\.env(\.|$)/i,
|
|
@@ -21533,7 +21748,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
21533
21748
|
}
|
|
21534
21749
|
for (const rel of readmes) {
|
|
21535
21750
|
if (isHardDeniedPath(rel)) continue;
|
|
21536
|
-
const abs = (0,
|
|
21751
|
+
const abs = (0, import_node_path22.join)(cwd, ...rel.split("/"));
|
|
21537
21752
|
if (!(0, import_node_fs23.existsSync)(abs)) continue;
|
|
21538
21753
|
let text;
|
|
21539
21754
|
try {
|
|
@@ -21550,7 +21765,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
21550
21765
|
return hints;
|
|
21551
21766
|
}
|
|
21552
21767
|
function toPosix(p) {
|
|
21553
|
-
return p.split(
|
|
21768
|
+
return p.split(import_node_path22.sep).join("/");
|
|
21554
21769
|
}
|
|
21555
21770
|
function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync) {
|
|
21556
21771
|
try {
|
|
@@ -21572,7 +21787,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
21572
21787
|
for (const rel of candidates) {
|
|
21573
21788
|
if (ignored.has(rel)) continue;
|
|
21574
21789
|
if (isHardDeniedPath(rel)) continue;
|
|
21575
|
-
const abs = (0,
|
|
21790
|
+
const abs = (0, import_node_path22.join)(cwd, ...rel.split("/"));
|
|
21576
21791
|
if (!(0, import_node_fs23.existsSync)(abs)) continue;
|
|
21577
21792
|
let text;
|
|
21578
21793
|
try {
|
|
@@ -21601,7 +21816,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
21601
21816
|
entries
|
|
21602
21817
|
};
|
|
21603
21818
|
const store = repoIndexStorePath(cwd);
|
|
21604
|
-
(0, import_node_fs23.mkdirSync)((0,
|
|
21819
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path22.dirname)(store), { recursive: true });
|
|
21605
21820
|
(0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
|
|
21606
21821
|
`, "utf8");
|
|
21607
21822
|
return projection;
|
|
@@ -21682,7 +21897,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
|
|
|
21682
21897
|
if (m?.[1]) return m[1].toLowerCase();
|
|
21683
21898
|
} catch {
|
|
21684
21899
|
}
|
|
21685
|
-
return ((0,
|
|
21900
|
+
return ((0, import_node_path22.basename)(cwd) || "local").toLowerCase();
|
|
21686
21901
|
}
|
|
21687
21902
|
|
|
21688
21903
|
// src/repo-index-cloud-client.ts
|
|
@@ -21824,7 +22039,7 @@ async function gcRepoIndexCloud(deps) {
|
|
|
21824
22039
|
// src/repo-index-sync.ts
|
|
21825
22040
|
var import_node_fs24 = require("node:fs");
|
|
21826
22041
|
var import_node_os9 = require("node:os");
|
|
21827
|
-
var
|
|
22042
|
+
var import_node_path23 = require("node:path");
|
|
21828
22043
|
var import_node_child_process13 = require("node:child_process");
|
|
21829
22044
|
var MAX_EMBED_BACKFILL_ROUNDS = 40;
|
|
21830
22045
|
function normalizeRepo(raw) {
|
|
@@ -21868,7 +22083,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
21868
22083
|
const failed = [];
|
|
21869
22084
|
const skipped = [];
|
|
21870
22085
|
for (const repo of repos) {
|
|
21871
|
-
const dir = (0, import_node_fs24.mkdtempSync)((0,
|
|
22086
|
+
const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
|
|
21872
22087
|
try {
|
|
21873
22088
|
shallowClone(repo, dir, opts.githubToken);
|
|
21874
22089
|
const built = rebuildRepoIndex(dir, repo);
|
|
@@ -22114,7 +22329,7 @@ async function runRepoIndexHealth(opts) {
|
|
|
22114
22329
|
// src/spawn-policy-core.ts
|
|
22115
22330
|
var import_node_child_process14 = require("node:child_process");
|
|
22116
22331
|
var import_node_fs26 = require("node:fs");
|
|
22117
|
-
var
|
|
22332
|
+
var import_node_path24 = require("node:path");
|
|
22118
22333
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
22119
22334
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
22120
22335
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -22200,7 +22415,7 @@ function runSpawnPolicy(root) {
|
|
|
22200
22415
|
for (const file of files) {
|
|
22201
22416
|
let raw;
|
|
22202
22417
|
try {
|
|
22203
|
-
raw = (0, import_node_fs26.readFileSync)((0,
|
|
22418
|
+
raw = (0, import_node_fs26.readFileSync)((0, import_node_path24.join)(root, file), "utf8");
|
|
22204
22419
|
} catch {
|
|
22205
22420
|
continue;
|
|
22206
22421
|
}
|
|
@@ -22219,7 +22434,7 @@ function runSpawnPolicy(root) {
|
|
|
22219
22434
|
// src/test-policy-core.ts
|
|
22220
22435
|
var import_node_child_process15 = require("node:child_process");
|
|
22221
22436
|
var import_node_fs27 = require("node:fs");
|
|
22222
|
-
var
|
|
22437
|
+
var import_node_path25 = require("node:path");
|
|
22223
22438
|
var POLICY_FILE = "test-policy.json";
|
|
22224
22439
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
22225
22440
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -22272,7 +22487,7 @@ function isTestPath(path2) {
|
|
|
22272
22487
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
22273
22488
|
}
|
|
22274
22489
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
22275
|
-
const raw = readFile9((0,
|
|
22490
|
+
const raw = readFile9((0, import_node_path25.join)(root, POLICY_FILE));
|
|
22276
22491
|
if (raw == null) return { mandatory: [], declared: false };
|
|
22277
22492
|
try {
|
|
22278
22493
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -22310,11 +22525,11 @@ function classify(changed, policy, present = () => false) {
|
|
|
22310
22525
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
22311
22526
|
}
|
|
22312
22527
|
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
|
|
22313
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
22528
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path25.join)(root, p)));
|
|
22314
22529
|
}
|
|
22315
22530
|
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
|
|
22316
22531
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
22317
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
22532
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path25.join)(root, p)));
|
|
22318
22533
|
}
|
|
22319
22534
|
function evaluate(changed, policy, present = () => false) {
|
|
22320
22535
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -22502,7 +22717,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
22502
22717
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
22503
22718
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
22504
22719
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
22505
|
-
const present = (path2) => exists((0,
|
|
22720
|
+
const present = (path2) => exists((0, import_node_path25.join)(root, path2));
|
|
22506
22721
|
const removedByThisDiff = removedPaths(changed);
|
|
22507
22722
|
const staleFindings = [];
|
|
22508
22723
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -22541,7 +22756,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
22541
22756
|
|
|
22542
22757
|
// src/project-info-sync.ts
|
|
22543
22758
|
var import_node_fs28 = require("node:fs");
|
|
22544
|
-
var
|
|
22759
|
+
var import_node_path26 = require("node:path");
|
|
22545
22760
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
22546
22761
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
22547
22762
|
projectV2 { id }
|
|
@@ -22586,7 +22801,7 @@ function sharedName(entries, fallback) {
|
|
|
22586
22801
|
}
|
|
22587
22802
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
22588
22803
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
22589
|
-
const readmePath = (0,
|
|
22804
|
+
const readmePath = (0, import_node_path26.join)(repoRoot2, "README.md");
|
|
22590
22805
|
if (!(0, import_node_fs28.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
22591
22806
|
const entries = entriesFor(project2, projects);
|
|
22592
22807
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
@@ -22612,8 +22827,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
22612
22827
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
22613
22828
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
22614
22829
|
const orgDocs = [
|
|
22615
|
-
(0, import_node_fs28.existsSync)((0,
|
|
22616
|
-
(0, import_node_fs28.existsSync)((0,
|
|
22830
|
+
(0, import_node_fs28.existsSync)((0, import_node_path26.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
22831
|
+
(0, import_node_fs28.existsSync)((0, import_node_path26.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
22617
22832
|
].filter(Boolean);
|
|
22618
22833
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
22619
22834
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -23491,7 +23706,7 @@ function writeError(res) {
|
|
|
23491
23706
|
|
|
23492
23707
|
// src/secrets-commands.ts
|
|
23493
23708
|
var import_node_fs29 = require("node:fs");
|
|
23494
|
-
var
|
|
23709
|
+
var import_node_path27 = require("node:path");
|
|
23495
23710
|
var import_node_os10 = require("node:os");
|
|
23496
23711
|
|
|
23497
23712
|
// src/project-runtime.ts
|
|
@@ -23615,11 +23830,11 @@ function collectMap(value, previous = []) {
|
|
|
23615
23830
|
return [...previous, value];
|
|
23616
23831
|
}
|
|
23617
23832
|
async function decryptRailsCredentials(input) {
|
|
23618
|
-
const appDir = (0,
|
|
23833
|
+
const appDir = (0, import_node_path27.resolve)(input.appDir ?? process.cwd());
|
|
23619
23834
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
23620
23835
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
23621
|
-
const credentialsPath = (0,
|
|
23622
|
-
const masterKeyPath = (0,
|
|
23836
|
+
const credentialsPath = (0, import_node_path27.resolve)(appDir, credentialsFile);
|
|
23837
|
+
const masterKeyPath = (0, import_node_path27.resolve)(appDir, masterKeyFile);
|
|
23623
23838
|
const env = {
|
|
23624
23839
|
...process.env,
|
|
23625
23840
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
@@ -23636,8 +23851,8 @@ async function decryptRailsCredentials(input) {
|
|
|
23636
23851
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
23637
23852
|
"puts JSON.generate(config.config)"
|
|
23638
23853
|
].join("\n");
|
|
23639
|
-
const scriptDir = (0, import_node_fs29.mkdtempSync)((0,
|
|
23640
|
-
const scriptPath = (0,
|
|
23854
|
+
const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
|
|
23855
|
+
const scriptPath = (0, import_node_path27.join)(scriptDir, "decrypt.rb");
|
|
23641
23856
|
(0, import_node_fs29.writeFileSync)(scriptPath, script, "utf8");
|
|
23642
23857
|
try {
|
|
23643
23858
|
const args = ["exec", "ruby", scriptPath];
|
|
@@ -23740,7 +23955,7 @@ function registerSecretsCommands(program3) {
|
|
|
23740
23955
|
let body;
|
|
23741
23956
|
if (o.file) {
|
|
23742
23957
|
try {
|
|
23743
|
-
body = (0, import_node_fs29.readFileSync)((0,
|
|
23958
|
+
body = (0, import_node_fs29.readFileSync)((0, import_node_path27.resolve)(o.file), "utf8");
|
|
23744
23959
|
} catch (e) {
|
|
23745
23960
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
23746
23961
|
}
|
|
@@ -23845,7 +24060,7 @@ function registerSecretsCommands(program3) {
|
|
|
23845
24060
|
{
|
|
23846
24061
|
...d,
|
|
23847
24062
|
decryptRailsCredentials,
|
|
23848
|
-
removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0,
|
|
24063
|
+
removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path27.resolve)(o.appDir ?? process.cwd(), path2))
|
|
23849
24064
|
},
|
|
23850
24065
|
{
|
|
23851
24066
|
repo: o.repo,
|
|
@@ -24226,7 +24441,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
24226
24441
|
}
|
|
24227
24442
|
|
|
24228
24443
|
// src/schedules-commands.ts
|
|
24229
|
-
var
|
|
24444
|
+
var import_promises5 = require("node:fs/promises");
|
|
24230
24445
|
var import_node_child_process16 = require("node:child_process");
|
|
24231
24446
|
var import_node_util7 = require("node:util");
|
|
24232
24447
|
|
|
@@ -24891,9 +25106,9 @@ function registerSchedulesCommands(program3) {
|
|
|
24891
25106
|
await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
|
|
24892
25107
|
return;
|
|
24893
25108
|
}
|
|
24894
|
-
const docText = await (0,
|
|
25109
|
+
const docText = await (0, import_promises5.readFile)(o.doc, "utf8");
|
|
24895
25110
|
const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
|
|
24896
|
-
await (0,
|
|
25111
|
+
await (0, import_promises5.writeFile)(o.doc, spliced, "utf8");
|
|
24897
25112
|
reportDrift(drift);
|
|
24898
25113
|
console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
24899
25114
|
return;
|
|
@@ -24976,102 +25191,6 @@ function registerSchedulesCommands(program3) {
|
|
|
24976
25191
|
});
|
|
24977
25192
|
}
|
|
24978
25193
|
|
|
24979
|
-
// src/file-lock.ts
|
|
24980
|
-
var import_promises5 = require("node:fs/promises");
|
|
24981
|
-
var import_node_path27 = require("node:path");
|
|
24982
|
-
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
24983
|
-
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
24984
|
-
var FileLockBusyError = class extends Error {
|
|
24985
|
-
lockPath;
|
|
24986
|
-
constructor(label, lockPath, maxWaitMs) {
|
|
24987
|
-
super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
|
|
24988
|
-
this.name = "FileLockBusyError";
|
|
24989
|
-
this.lockPath = lockPath;
|
|
24990
|
-
}
|
|
24991
|
-
};
|
|
24992
|
-
function resolveFileLockOpts(opts = {}) {
|
|
24993
|
-
return {
|
|
24994
|
-
staleMs: opts.staleMs ?? 3e4,
|
|
24995
|
-
retryMs: opts.retryMs ?? 50,
|
|
24996
|
-
maxWaitMs: opts.maxWaitMs ?? 5e3,
|
|
24997
|
-
label: opts.label ?? "file lock"
|
|
24998
|
-
};
|
|
24999
|
-
}
|
|
25000
|
-
async function acquireFileLock(lockPath, opts, deadline) {
|
|
25001
|
-
let immediateRetries = 0;
|
|
25002
|
-
for (; ; ) {
|
|
25003
|
-
let handle;
|
|
25004
|
-
try {
|
|
25005
|
-
handle = await (0, import_promises5.open)(lockPath, "wx");
|
|
25006
|
-
} catch (e) {
|
|
25007
|
-
const code = e.code;
|
|
25008
|
-
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
25009
|
-
const retryNow = async () => {
|
|
25010
|
-
if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
25011
|
-
if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
|
|
25012
|
-
};
|
|
25013
|
-
try {
|
|
25014
|
-
const age = Date.now() - (await (0, import_promises5.stat)(lockPath)).mtimeMs;
|
|
25015
|
-
if (age > opts.staleMs) {
|
|
25016
|
-
try {
|
|
25017
|
-
await (0, import_promises5.unlink)(lockPath);
|
|
25018
|
-
await retryNow();
|
|
25019
|
-
continue;
|
|
25020
|
-
} catch (unlinkError) {
|
|
25021
|
-
if (unlinkError instanceof FileLockBusyError) throw unlinkError;
|
|
25022
|
-
const unlinkCode = unlinkError.code;
|
|
25023
|
-
if (unlinkCode === "ENOENT") {
|
|
25024
|
-
await retryNow();
|
|
25025
|
-
continue;
|
|
25026
|
-
}
|
|
25027
|
-
}
|
|
25028
|
-
}
|
|
25029
|
-
} catch (statError) {
|
|
25030
|
-
if (statError instanceof FileLockBusyError) throw statError;
|
|
25031
|
-
if (statError.code !== "ENOENT") throw statError;
|
|
25032
|
-
await retryNow();
|
|
25033
|
-
continue;
|
|
25034
|
-
}
|
|
25035
|
-
if (Date.now() >= deadline) {
|
|
25036
|
-
throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
25037
|
-
}
|
|
25038
|
-
await sleep(opts.retryMs);
|
|
25039
|
-
continue;
|
|
25040
|
-
}
|
|
25041
|
-
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
25042
|
-
try {
|
|
25043
|
-
await handle.writeFile(token);
|
|
25044
|
-
} catch (e) {
|
|
25045
|
-
await handle.close().catch(() => void 0);
|
|
25046
|
-
throw e;
|
|
25047
|
-
}
|
|
25048
|
-
return { token, handle };
|
|
25049
|
-
}
|
|
25050
|
-
}
|
|
25051
|
-
async function fileLockHeldBy(lockPath, token) {
|
|
25052
|
-
try {
|
|
25053
|
-
return await (0, import_promises5.readFile)(lockPath, "utf8") === token;
|
|
25054
|
-
} catch {
|
|
25055
|
-
return false;
|
|
25056
|
-
}
|
|
25057
|
-
}
|
|
25058
|
-
async function releaseFileLock(lockPath, guard) {
|
|
25059
|
-
await guard.handle.close().catch(() => void 0);
|
|
25060
|
-
if (await fileLockHeldBy(lockPath, guard.token)) {
|
|
25061
|
-
await (0, import_promises5.unlink)(lockPath).catch(() => void 0);
|
|
25062
|
-
}
|
|
25063
|
-
}
|
|
25064
|
-
async function withFileLock(lockPath, opts, fn) {
|
|
25065
|
-
const resolved = resolveFileLockOpts(opts);
|
|
25066
|
-
await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
25067
|
-
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
25068
|
-
try {
|
|
25069
|
-
return await fn();
|
|
25070
|
-
} finally {
|
|
25071
|
-
await releaseFileLock(lockPath, guard);
|
|
25072
|
-
}
|
|
25073
|
-
}
|
|
25074
|
-
|
|
25075
25194
|
// src/schedules-lift-command.ts
|
|
25076
25195
|
var import_promises6 = require("node:fs/promises");
|
|
25077
25196
|
var import_node_path28 = require("node:path");
|
|
@@ -31099,8 +31218,8 @@ var LOOP_PLAYBOOKS = {
|
|
|
31099
31218
|
{ label: "Read the next board item", command: "mmi-cli board read" },
|
|
31100
31219
|
{ label: "Claim it and create an isolated worktree", command: "mmi-cli worktree create <issue-number> --claim --from origin/development" },
|
|
31101
31220
|
{ label: "Build and test in the touched package", command: "npm test && npm run build" },
|
|
31102
|
-
{ label: "Publish the branch", command: "git push
|
|
31103
|
-
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
31221
|
+
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
31222
|
+
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
31104
31223
|
{ label: "Wait for checks and land to development", command: "mmi-cli pr checks-wait <PR-number> && mmi-cli pr land <PR-number>" },
|
|
31105
31224
|
{ label: "Release only after the gated train is authorized", command: "mmi-cli release --apply" }
|
|
31106
31225
|
]
|
|
@@ -31117,8 +31236,8 @@ var LOOP_PLAYBOOKS = {
|
|
|
31117
31236
|
"ship-pr": {
|
|
31118
31237
|
title: "Ship PR",
|
|
31119
31238
|
steps: [
|
|
31120
|
-
{ label: "Publish the branch", command: "git push
|
|
31121
|
-
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file PR_BODY.md --base development' },
|
|
31239
|
+
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
31240
|
+
{ label: "Open the development-base PR", command: 'mmi-cli pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
31122
31241
|
{ label: "Wait for CI checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
31123
31242
|
{ label: "Land the PR (merge to development)", command: "mmi-cli pr land <PR-number>" }
|
|
31124
31243
|
]
|
|
@@ -31126,9 +31245,9 @@ var LOOP_PLAYBOOKS = {
|
|
|
31126
31245
|
"hotfix": {
|
|
31127
31246
|
title: "Hotfix",
|
|
31128
31247
|
steps: [
|
|
31129
|
-
{ label: "Create the main-base hotfix PR from
|
|
31248
|
+
{ label: "Create the main-base hotfix PR from every already-merged fix this cycle carries", command: "mmi-cli hotfix start --from <pr#|sha>[,<pr#|sha>...]" },
|
|
31130
31249
|
{ label: "Wait for the hotfix PR checks", command: "mmi-cli pr checks-wait <PR-number>" },
|
|
31131
|
-
{ label: "After the PR is merged, run the gated release", command: "mmi-cli hotfix release <vX.Y.Z> --carries <pr#|sha>" }
|
|
31250
|
+
{ label: "After the PR is merged, run the gated release", command: "mmi-cli hotfix release <vX.Y.Z> --carries <pr#|sha>[,<pr#|sha>...]" }
|
|
31132
31251
|
]
|
|
31133
31252
|
}
|
|
31134
31253
|
};
|
|
@@ -35257,7 +35376,7 @@ withExamples(mutating(
|
|
|
35257
35376
|
}
|
|
35258
35377
|
if (!resumed) {
|
|
35259
35378
|
step = `git worktree add ${wtPath}`;
|
|
35260
|
-
await addWorktreeRobust(wtPath, branch, base, {
|
|
35379
|
+
await withWorktreeAddLock(repoRoot2, () => addWorktreeRobust(wtPath, branch, base, {
|
|
35261
35380
|
git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
|
|
35262
35381
|
revParse: async (ref) => {
|
|
35263
35382
|
try {
|
|
@@ -35271,7 +35390,7 @@ withExamples(mutating(
|
|
|
35271
35390
|
log: (m) => {
|
|
35272
35391
|
if (!o.json) console.error(` ${m}`);
|
|
35273
35392
|
}
|
|
35274
|
-
});
|
|
35393
|
+
}));
|
|
35275
35394
|
}
|
|
35276
35395
|
step = "install deps + copy local-only config";
|
|
35277
35396
|
const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
|
|
@@ -36747,10 +36866,11 @@ withExamples(pr.command("create").description("create a PR and print {number,url
|
|
|
36747
36866
|
console.log(JSON.stringify(created));
|
|
36748
36867
|
}), [
|
|
36749
36868
|
'mmi-cli pr create --title "Add the schema" --body "Closes #2680"',
|
|
36750
|
-
'mmi-cli pr create --title "Add the schema" --body-file PR_BODY.md --draft'
|
|
36869
|
+
'mmi-cli pr create --title "Add the schema" --body-file .jerv/PR_BODY.md --draft'
|
|
36751
36870
|
], [
|
|
36752
36871
|
"--head and --base default to the current branch and the repo default; only pass them to override.",
|
|
36753
|
-
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown."
|
|
36872
|
+
"Use --body-file for multiline PR bodies instead of shell-escaped inline markdown.",
|
|
36873
|
+
"Write that file under .jerv/ inside a worktree (#4405): any other untracked path makes `worktree land` refuse cleanup as untracked-files."
|
|
36754
36874
|
]);
|
|
36755
36875
|
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) ? the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags ? in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) ? read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (number, o) => {
|
|
36756
36876
|
const n = Number(number);
|
|
@@ -37515,7 +37635,8 @@ function renderHotfixRelease(r) {
|
|
|
37515
37635
|
...r.runs.map((run) => ` - ${run.workflow}: ${run.conclusion}${run.url ? ` (${run.url})` : ""}`),
|
|
37516
37636
|
` - ${r.verifyNote}`,
|
|
37517
37637
|
...r.announceNote ? [` - announce: ${r.announceNote}`] : [],
|
|
37518
|
-
` -
|
|
37638
|
+
` - fold: ${r.foldNote}`,
|
|
37639
|
+
` - next: mmi-cli hotfix status ${r.tag} (no back-merge of the FIX ? development already has it; the version fold above is ported for you, #4410)`
|
|
37519
37640
|
].join("\n");
|
|
37520
37641
|
}
|
|
37521
37642
|
function renderHotfixStatus(r) {
|
|
@@ -37547,7 +37668,7 @@ var hotfixCmd = program2.command("hotfix").description("stepwise hotfix orchestr
|
|
|
37547
37668
|
const steps = trainPlan("hotfix");
|
|
37548
37669
|
console.log(o.json ? JSON.stringify({ command: "hotfix", steps }, null, 2) : renderSteps("mmi-cli hotfix: dry-run plan", steps));
|
|
37549
37670
|
});
|
|
37550
|
-
hotfixCmd.command("start").description("cherry-pick
|
|
37671
|
+
hotfixCmd.command("start").description("cherry-pick one or more merged development PRs (or SHAs) onto hotfix/vX.Y.Z from origin/main, bump the distribution, open the main-base PR").requiredOption("--from <pr#|sha[,pr#|sha...]>", "merged development PR number(s) or commit SHA(s) to cherry-pick, in pick order \u2014 one hotfix cycle carries as many fixes as you name (#4411)").option("--json", "machine-readable output").action(async (o) => runHotfixSub("start", () => runHotfixStart(trainApplyDeps(), { from: o.from }), o.json, renderHotfixStart));
|
|
37551
37672
|
hotfixCmd.command("release <version>").description("after the hotfix PR is merged + checks green: tag, GitHub Release, watch deploy/publish, verify distribution (idempotent)").option("--json", "machine-readable output").option("--announce-summary-file <path>", "agent-curated summary lines for the Hub Slack announcement (#883)").option("--carries <pr#|sha[,pr#|sha...]>", "declared fix target(s) this hotfix must carry; each must be proven present before tagging (#3056)").action(async (version, o) => runHotfixSub("release", () => runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [] }), o.json, renderHotfixRelease));
|
|
37552
37673
|
hotfixCmd.command("status [version]").description("derive the full hotfix pipeline state from live git/gh reads and name the exact next subcommand").option("--json", "machine-readable output").action(async (version, o) => runHotfixSub("status", () => runHotfixStatus(trainApplyDeps(), version), o.json, renderHotfixStatus));
|
|
37553
37674
|
var ci = program2.command("ci").description("org CI + merge-readiness audit and reconcile");
|