@mutmutco/cli 3.105.8 → 3.105.10
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 +337 -247
- 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,6 +11502,30 @@ 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"]);
|
|
@@ -11400,6 +11538,10 @@ async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "H
|
|
|
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);
|
|
@@ -20386,6 +20528,46 @@ async function cherryPickWithToleratedPaths(deps, sha, tolerated) {
|
|
|
20386
20528
|
return unmerged;
|
|
20387
20529
|
}
|
|
20388
20530
|
}
|
|
20531
|
+
function parseCherryPickBlockingPaths(message) {
|
|
20532
|
+
const m = /conflicted on untolerated path\(s\): ([^(]+)/.exec(message);
|
|
20533
|
+
if (!m) return [];
|
|
20534
|
+
return m[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
20535
|
+
}
|
|
20536
|
+
function formatHotfixBatchPreflightRefusal(failures, fromSpecs) {
|
|
20537
|
+
const lines = failures.map((f) => ` - ${f.label}: ${f.blocking.join(", ")}`);
|
|
20538
|
+
const batchFrom = fromSpecs.join(",");
|
|
20539
|
+
return `hotfix start batch preflight refused: ${failures.length} pick(s) would hard-stop on origin/main:
|
|
20540
|
+
${lines.join("\n")}
|
|
20541
|
+
Land ONE main-clean synthesis development PR for the whole batch (cut from origin/main, resolve every listed conflict for cherry-pick onto current origin/main, land to development \u2014 pattern #4467), then rerun \`mmi-cli hotfix start --from ${batchFrom}\` with the port merge SHA(s) \u2014 never hand-resolve onto main; never N serial main-clean ports (#3167).`;
|
|
20542
|
+
}
|
|
20543
|
+
async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
20544
|
+
if (sources.length === 0) return [];
|
|
20545
|
+
const startBranch = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
20546
|
+
const preflightBranch = `__mmi_hotfix_preflight_${Date.now()}`;
|
|
20547
|
+
const failures = [];
|
|
20548
|
+
try {
|
|
20549
|
+
await deps.run("git", ["checkout", "-B", preflightBranch, "origin/main"]);
|
|
20550
|
+
for (const source of sources) {
|
|
20551
|
+
try {
|
|
20552
|
+
await cherryPickWithToleratedPaths(deps, source.sha, pickTolerated);
|
|
20553
|
+
} catch (e) {
|
|
20554
|
+
const message = e.message ?? String(e);
|
|
20555
|
+
const blocking = parseCherryPickBlockingPaths(message);
|
|
20556
|
+
failures.push({
|
|
20557
|
+
label: source.label,
|
|
20558
|
+
sha: source.sha,
|
|
20559
|
+
blocking: blocking.length > 0 ? blocking : [message.split("\n")[0] ?? message]
|
|
20560
|
+
});
|
|
20561
|
+
}
|
|
20562
|
+
}
|
|
20563
|
+
} finally {
|
|
20564
|
+
if (startBranch && startBranch !== "HEAD") {
|
|
20565
|
+
await deps.run("git", ["checkout", startBranch]).catch(() => void 0);
|
|
20566
|
+
}
|
|
20567
|
+
await deps.run("git", ["branch", "-D", preflightBranch]).catch(() => void 0);
|
|
20568
|
+
}
|
|
20569
|
+
return failures;
|
|
20570
|
+
}
|
|
20389
20571
|
async function restoreAfterFailedPort(deps, opts) {
|
|
20390
20572
|
const { startBranch, branch, created } = opts;
|
|
20391
20573
|
if (!startBranch || startBranch === "HEAD" || startBranch === branch) {
|
|
@@ -20525,7 +20707,7 @@ async function runHotfixStart(deps, options) {
|
|
|
20525
20707
|
}
|
|
20526
20708
|
const label = sources.map((s) => s.label).join(", ");
|
|
20527
20709
|
const foldPaths = deployModel === "hub-serverless" || deployModel === "registry-publish" ? await resolveFoldPaths(deps, deployModel) : [];
|
|
20528
|
-
const pickTolerated = deployModel === "hub-serverless" ? [...foldPaths, ...HOTFIX_SKILL_TOLERATED_ROOTS] : foldPaths;
|
|
20710
|
+
const pickTolerated = deployModel === "hub-serverless" || deployModel === "registry-publish" ? [...foldPaths, ...HOTFIX_SKILL_TOLERATED_ROOTS] : foldPaths;
|
|
20529
20711
|
if (deployModel === "hub-serverless") {
|
|
20530
20712
|
await deps.run("node", ["scripts/release-distribution.mjs", "verify-deps"]);
|
|
20531
20713
|
}
|
|
@@ -20535,6 +20717,10 @@ async function runHotfixStart(deps, options) {
|
|
|
20535
20717
|
await deps.run("git", ["pull", "--ff-only", "origin", branch]);
|
|
20536
20718
|
notes.push(`branch ${branch} already on origin \u2014 reused (cherry-pick/bump assumed present; PR step resumes)`);
|
|
20537
20719
|
} else {
|
|
20720
|
+
const preflightFailures = await preflightHotfixBatchPicks(deps, sources, pickTolerated);
|
|
20721
|
+
if (preflightFailures.length > 0) {
|
|
20722
|
+
throw new Error(formatHotfixBatchPreflightRefusal(preflightFailures, specs));
|
|
20723
|
+
}
|
|
20538
20724
|
const startBranch = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
20539
20725
|
const preexistingLocal = clean3(await deps.run("git", ["branch", "--list", branch]));
|
|
20540
20726
|
await deps.run("git", ["checkout", "-B", branch, "origin/main"]);
|
|
@@ -21481,7 +21667,7 @@ function renderAccessReport(report) {
|
|
|
21481
21667
|
var import_node_crypto4 = require("node:crypto");
|
|
21482
21668
|
var import_node_child_process12 = require("node:child_process");
|
|
21483
21669
|
var import_node_fs23 = require("node:fs");
|
|
21484
|
-
var
|
|
21670
|
+
var import_node_path22 = require("node:path");
|
|
21485
21671
|
var REPO_INDEX_SCHEMA = 1;
|
|
21486
21672
|
var HARD_DENY = [
|
|
21487
21673
|
/(^|\/)\.env(\.|$)/i,
|
|
@@ -21606,7 +21792,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
21606
21792
|
}
|
|
21607
21793
|
for (const rel of readmes) {
|
|
21608
21794
|
if (isHardDeniedPath(rel)) continue;
|
|
21609
|
-
const abs = (0,
|
|
21795
|
+
const abs = (0, import_node_path22.join)(cwd, ...rel.split("/"));
|
|
21610
21796
|
if (!(0, import_node_fs23.existsSync)(abs)) continue;
|
|
21611
21797
|
let text;
|
|
21612
21798
|
try {
|
|
@@ -21623,7 +21809,7 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
21623
21809
|
return hints;
|
|
21624
21810
|
}
|
|
21625
21811
|
function toPosix(p) {
|
|
21626
|
-
return p.split(
|
|
21812
|
+
return p.split(import_node_path22.sep).join("/");
|
|
21627
21813
|
}
|
|
21628
21814
|
function listCandidatePaths(cwd, exec = import_node_child_process12.execFileSync) {
|
|
21629
21815
|
try {
|
|
@@ -21645,7 +21831,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
21645
21831
|
for (const rel of candidates) {
|
|
21646
21832
|
if (ignored.has(rel)) continue;
|
|
21647
21833
|
if (isHardDeniedPath(rel)) continue;
|
|
21648
|
-
const abs = (0,
|
|
21834
|
+
const abs = (0, import_node_path22.join)(cwd, ...rel.split("/"));
|
|
21649
21835
|
if (!(0, import_node_fs23.existsSync)(abs)) continue;
|
|
21650
21836
|
let text;
|
|
21651
21837
|
try {
|
|
@@ -21674,7 +21860,7 @@ function rebuildRepoIndex(cwd, repoSlug2) {
|
|
|
21674
21860
|
entries
|
|
21675
21861
|
};
|
|
21676
21862
|
const store = repoIndexStorePath(cwd);
|
|
21677
|
-
(0, import_node_fs23.mkdirSync)((0,
|
|
21863
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path22.dirname)(store), { recursive: true });
|
|
21678
21864
|
(0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
|
|
21679
21865
|
`, "utf8");
|
|
21680
21866
|
return projection;
|
|
@@ -21755,7 +21941,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process12.execFileSync) {
|
|
|
21755
21941
|
if (m?.[1]) return m[1].toLowerCase();
|
|
21756
21942
|
} catch {
|
|
21757
21943
|
}
|
|
21758
|
-
return ((0,
|
|
21944
|
+
return ((0, import_node_path22.basename)(cwd) || "local").toLowerCase();
|
|
21759
21945
|
}
|
|
21760
21946
|
|
|
21761
21947
|
// src/repo-index-cloud-client.ts
|
|
@@ -21897,7 +22083,7 @@ async function gcRepoIndexCloud(deps) {
|
|
|
21897
22083
|
// src/repo-index-sync.ts
|
|
21898
22084
|
var import_node_fs24 = require("node:fs");
|
|
21899
22085
|
var import_node_os9 = require("node:os");
|
|
21900
|
-
var
|
|
22086
|
+
var import_node_path23 = require("node:path");
|
|
21901
22087
|
var import_node_child_process13 = require("node:child_process");
|
|
21902
22088
|
var MAX_EMBED_BACKFILL_ROUNDS = 40;
|
|
21903
22089
|
function normalizeRepo(raw) {
|
|
@@ -21941,7 +22127,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
21941
22127
|
const failed = [];
|
|
21942
22128
|
const skipped = [];
|
|
21943
22129
|
for (const repo of repos) {
|
|
21944
|
-
const dir = (0, import_node_fs24.mkdtempSync)((0,
|
|
22130
|
+
const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
|
|
21945
22131
|
try {
|
|
21946
22132
|
shallowClone(repo, dir, opts.githubToken);
|
|
21947
22133
|
const built = rebuildRepoIndex(dir, repo);
|
|
@@ -22187,7 +22373,7 @@ async function runRepoIndexHealth(opts) {
|
|
|
22187
22373
|
// src/spawn-policy-core.ts
|
|
22188
22374
|
var import_node_child_process14 = require("node:child_process");
|
|
22189
22375
|
var import_node_fs26 = require("node:fs");
|
|
22190
|
-
var
|
|
22376
|
+
var import_node_path24 = require("node:path");
|
|
22191
22377
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
22192
22378
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
22193
22379
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -22273,7 +22459,7 @@ function runSpawnPolicy(root) {
|
|
|
22273
22459
|
for (const file of files) {
|
|
22274
22460
|
let raw;
|
|
22275
22461
|
try {
|
|
22276
|
-
raw = (0, import_node_fs26.readFileSync)((0,
|
|
22462
|
+
raw = (0, import_node_fs26.readFileSync)((0, import_node_path24.join)(root, file), "utf8");
|
|
22277
22463
|
} catch {
|
|
22278
22464
|
continue;
|
|
22279
22465
|
}
|
|
@@ -22292,7 +22478,7 @@ function runSpawnPolicy(root) {
|
|
|
22292
22478
|
// src/test-policy-core.ts
|
|
22293
22479
|
var import_node_child_process15 = require("node:child_process");
|
|
22294
22480
|
var import_node_fs27 = require("node:fs");
|
|
22295
|
-
var
|
|
22481
|
+
var import_node_path25 = require("node:path");
|
|
22296
22482
|
var POLICY_FILE = "test-policy.json";
|
|
22297
22483
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
22298
22484
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -22345,7 +22531,7 @@ function isTestPath(path2) {
|
|
|
22345
22531
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
22346
22532
|
}
|
|
22347
22533
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
22348
|
-
const raw = readFile9((0,
|
|
22534
|
+
const raw = readFile9((0, import_node_path25.join)(root, POLICY_FILE));
|
|
22349
22535
|
if (raw == null) return { mandatory: [], declared: false };
|
|
22350
22536
|
try {
|
|
22351
22537
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -22383,11 +22569,11 @@ function classify(changed, policy, present = () => false) {
|
|
|
22383
22569
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
22384
22570
|
}
|
|
22385
22571
|
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
|
|
22386
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
22572
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path25.join)(root, p)));
|
|
22387
22573
|
}
|
|
22388
22574
|
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
|
|
22389
22575
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
22390
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
22576
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path25.join)(root, p)));
|
|
22391
22577
|
}
|
|
22392
22578
|
function evaluate(changed, policy, present = () => false) {
|
|
22393
22579
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -22575,7 +22761,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
22575
22761
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
22576
22762
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
22577
22763
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
22578
|
-
const present = (path2) => exists((0,
|
|
22764
|
+
const present = (path2) => exists((0, import_node_path25.join)(root, path2));
|
|
22579
22765
|
const removedByThisDiff = removedPaths(changed);
|
|
22580
22766
|
const staleFindings = [];
|
|
22581
22767
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -22614,7 +22800,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
22614
22800
|
|
|
22615
22801
|
// src/project-info-sync.ts
|
|
22616
22802
|
var import_node_fs28 = require("node:fs");
|
|
22617
|
-
var
|
|
22803
|
+
var import_node_path26 = require("node:path");
|
|
22618
22804
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
22619
22805
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
22620
22806
|
projectV2 { id }
|
|
@@ -22659,7 +22845,7 @@ function sharedName(entries, fallback) {
|
|
|
22659
22845
|
}
|
|
22660
22846
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
22661
22847
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
22662
|
-
const readmePath = (0,
|
|
22848
|
+
const readmePath = (0, import_node_path26.join)(repoRoot2, "README.md");
|
|
22663
22849
|
if (!(0, import_node_fs28.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
22664
22850
|
const entries = entriesFor(project2, projects);
|
|
22665
22851
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
@@ -22685,8 +22871,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
22685
22871
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
22686
22872
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
22687
22873
|
const orgDocs = [
|
|
22688
|
-
(0, import_node_fs28.existsSync)((0,
|
|
22689
|
-
(0, import_node_fs28.existsSync)((0,
|
|
22874
|
+
(0, import_node_fs28.existsSync)((0, import_node_path26.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
22875
|
+
(0, import_node_fs28.existsSync)((0, import_node_path26.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
22690
22876
|
].filter(Boolean);
|
|
22691
22877
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
22692
22878
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -23564,7 +23750,7 @@ function writeError(res) {
|
|
|
23564
23750
|
|
|
23565
23751
|
// src/secrets-commands.ts
|
|
23566
23752
|
var import_node_fs29 = require("node:fs");
|
|
23567
|
-
var
|
|
23753
|
+
var import_node_path27 = require("node:path");
|
|
23568
23754
|
var import_node_os10 = require("node:os");
|
|
23569
23755
|
|
|
23570
23756
|
// src/project-runtime.ts
|
|
@@ -23688,11 +23874,11 @@ function collectMap(value, previous = []) {
|
|
|
23688
23874
|
return [...previous, value];
|
|
23689
23875
|
}
|
|
23690
23876
|
async function decryptRailsCredentials(input) {
|
|
23691
|
-
const appDir = (0,
|
|
23877
|
+
const appDir = (0, import_node_path27.resolve)(input.appDir ?? process.cwd());
|
|
23692
23878
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
23693
23879
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
23694
|
-
const credentialsPath = (0,
|
|
23695
|
-
const masterKeyPath = (0,
|
|
23880
|
+
const credentialsPath = (0, import_node_path27.resolve)(appDir, credentialsFile);
|
|
23881
|
+
const masterKeyPath = (0, import_node_path27.resolve)(appDir, masterKeyFile);
|
|
23696
23882
|
const env = {
|
|
23697
23883
|
...process.env,
|
|
23698
23884
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
@@ -23709,8 +23895,8 @@ async function decryptRailsCredentials(input) {
|
|
|
23709
23895
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
23710
23896
|
"puts JSON.generate(config.config)"
|
|
23711
23897
|
].join("\n");
|
|
23712
|
-
const scriptDir = (0, import_node_fs29.mkdtempSync)((0,
|
|
23713
|
-
const scriptPath = (0,
|
|
23898
|
+
const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
|
|
23899
|
+
const scriptPath = (0, import_node_path27.join)(scriptDir, "decrypt.rb");
|
|
23714
23900
|
(0, import_node_fs29.writeFileSync)(scriptPath, script, "utf8");
|
|
23715
23901
|
try {
|
|
23716
23902
|
const args = ["exec", "ruby", scriptPath];
|
|
@@ -23813,7 +23999,7 @@ function registerSecretsCommands(program3) {
|
|
|
23813
23999
|
let body;
|
|
23814
24000
|
if (o.file) {
|
|
23815
24001
|
try {
|
|
23816
|
-
body = (0, import_node_fs29.readFileSync)((0,
|
|
24002
|
+
body = (0, import_node_fs29.readFileSync)((0, import_node_path27.resolve)(o.file), "utf8");
|
|
23817
24003
|
} catch (e) {
|
|
23818
24004
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
23819
24005
|
}
|
|
@@ -23918,7 +24104,7 @@ function registerSecretsCommands(program3) {
|
|
|
23918
24104
|
{
|
|
23919
24105
|
...d,
|
|
23920
24106
|
decryptRailsCredentials,
|
|
23921
|
-
removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0,
|
|
24107
|
+
removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path27.resolve)(o.appDir ?? process.cwd(), path2))
|
|
23922
24108
|
},
|
|
23923
24109
|
{
|
|
23924
24110
|
repo: o.repo,
|
|
@@ -24299,7 +24485,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
24299
24485
|
}
|
|
24300
24486
|
|
|
24301
24487
|
// src/schedules-commands.ts
|
|
24302
|
-
var
|
|
24488
|
+
var import_promises5 = require("node:fs/promises");
|
|
24303
24489
|
var import_node_child_process16 = require("node:child_process");
|
|
24304
24490
|
var import_node_util7 = require("node:util");
|
|
24305
24491
|
|
|
@@ -24964,9 +25150,9 @@ function registerSchedulesCommands(program3) {
|
|
|
24964
25150
|
await failGraceful("org schedules --doc: refusing to regenerate the doc from an incomplete read (see warnings above).");
|
|
24965
25151
|
return;
|
|
24966
25152
|
}
|
|
24967
|
-
const docText = await (0,
|
|
25153
|
+
const docText = await (0, import_promises5.readFile)(o.doc, "utf8");
|
|
24968
25154
|
const spliced = spliceDoc(docText, renderDocSection(entries, (/* @__PURE__ */ new Date()).toISOString()));
|
|
24969
|
-
await (0,
|
|
25155
|
+
await (0, import_promises5.writeFile)(o.doc, spliced, "utf8");
|
|
24970
25156
|
reportDrift(drift);
|
|
24971
25157
|
console.log(`org schedules: wrote ${entries.length} entries into ${o.doc}`);
|
|
24972
25158
|
return;
|
|
@@ -25049,102 +25235,6 @@ function registerSchedulesCommands(program3) {
|
|
|
25049
25235
|
});
|
|
25050
25236
|
}
|
|
25051
25237
|
|
|
25052
|
-
// src/file-lock.ts
|
|
25053
|
-
var import_promises5 = require("node:fs/promises");
|
|
25054
|
-
var import_node_path27 = require("node:path");
|
|
25055
|
-
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
25056
|
-
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
25057
|
-
var FileLockBusyError = class extends Error {
|
|
25058
|
-
lockPath;
|
|
25059
|
-
constructor(label, lockPath, maxWaitMs) {
|
|
25060
|
-
super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
|
|
25061
|
-
this.name = "FileLockBusyError";
|
|
25062
|
-
this.lockPath = lockPath;
|
|
25063
|
-
}
|
|
25064
|
-
};
|
|
25065
|
-
function resolveFileLockOpts(opts = {}) {
|
|
25066
|
-
return {
|
|
25067
|
-
staleMs: opts.staleMs ?? 3e4,
|
|
25068
|
-
retryMs: opts.retryMs ?? 50,
|
|
25069
|
-
maxWaitMs: opts.maxWaitMs ?? 5e3,
|
|
25070
|
-
label: opts.label ?? "file lock"
|
|
25071
|
-
};
|
|
25072
|
-
}
|
|
25073
|
-
async function acquireFileLock(lockPath, opts, deadline) {
|
|
25074
|
-
let immediateRetries = 0;
|
|
25075
|
-
for (; ; ) {
|
|
25076
|
-
let handle;
|
|
25077
|
-
try {
|
|
25078
|
-
handle = await (0, import_promises5.open)(lockPath, "wx");
|
|
25079
|
-
} catch (e) {
|
|
25080
|
-
const code = e.code;
|
|
25081
|
-
if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
25082
|
-
const retryNow = async () => {
|
|
25083
|
-
if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
25084
|
-
if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
|
|
25085
|
-
};
|
|
25086
|
-
try {
|
|
25087
|
-
const age = Date.now() - (await (0, import_promises5.stat)(lockPath)).mtimeMs;
|
|
25088
|
-
if (age > opts.staleMs) {
|
|
25089
|
-
try {
|
|
25090
|
-
await (0, import_promises5.unlink)(lockPath);
|
|
25091
|
-
await retryNow();
|
|
25092
|
-
continue;
|
|
25093
|
-
} catch (unlinkError) {
|
|
25094
|
-
if (unlinkError instanceof FileLockBusyError) throw unlinkError;
|
|
25095
|
-
const unlinkCode = unlinkError.code;
|
|
25096
|
-
if (unlinkCode === "ENOENT") {
|
|
25097
|
-
await retryNow();
|
|
25098
|
-
continue;
|
|
25099
|
-
}
|
|
25100
|
-
}
|
|
25101
|
-
}
|
|
25102
|
-
} catch (statError) {
|
|
25103
|
-
if (statError instanceof FileLockBusyError) throw statError;
|
|
25104
|
-
if (statError.code !== "ENOENT") throw statError;
|
|
25105
|
-
await retryNow();
|
|
25106
|
-
continue;
|
|
25107
|
-
}
|
|
25108
|
-
if (Date.now() >= deadline) {
|
|
25109
|
-
throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
|
|
25110
|
-
}
|
|
25111
|
-
await sleep(opts.retryMs);
|
|
25112
|
-
continue;
|
|
25113
|
-
}
|
|
25114
|
-
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
25115
|
-
try {
|
|
25116
|
-
await handle.writeFile(token);
|
|
25117
|
-
} catch (e) {
|
|
25118
|
-
await handle.close().catch(() => void 0);
|
|
25119
|
-
throw e;
|
|
25120
|
-
}
|
|
25121
|
-
return { token, handle };
|
|
25122
|
-
}
|
|
25123
|
-
}
|
|
25124
|
-
async function fileLockHeldBy(lockPath, token) {
|
|
25125
|
-
try {
|
|
25126
|
-
return await (0, import_promises5.readFile)(lockPath, "utf8") === token;
|
|
25127
|
-
} catch {
|
|
25128
|
-
return false;
|
|
25129
|
-
}
|
|
25130
|
-
}
|
|
25131
|
-
async function releaseFileLock(lockPath, guard) {
|
|
25132
|
-
await guard.handle.close().catch(() => void 0);
|
|
25133
|
-
if (await fileLockHeldBy(lockPath, guard.token)) {
|
|
25134
|
-
await (0, import_promises5.unlink)(lockPath).catch(() => void 0);
|
|
25135
|
-
}
|
|
25136
|
-
}
|
|
25137
|
-
async function withFileLock(lockPath, opts, fn) {
|
|
25138
|
-
const resolved = resolveFileLockOpts(opts);
|
|
25139
|
-
await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
25140
|
-
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
25141
|
-
try {
|
|
25142
|
-
return await fn();
|
|
25143
|
-
} finally {
|
|
25144
|
-
await releaseFileLock(lockPath, guard);
|
|
25145
|
-
}
|
|
25146
|
-
}
|
|
25147
|
-
|
|
25148
25238
|
// src/schedules-lift-command.ts
|
|
25149
25239
|
var import_promises6 = require("node:fs/promises");
|
|
25150
25240
|
var import_node_path28 = require("node:path");
|
|
@@ -35330,7 +35420,7 @@ withExamples(mutating(
|
|
|
35330
35420
|
}
|
|
35331
35421
|
if (!resumed) {
|
|
35332
35422
|
step = `git worktree add ${wtPath}`;
|
|
35333
|
-
await addWorktreeRobust(wtPath, branch, base, {
|
|
35423
|
+
await withWorktreeAddLock(repoRoot2, () => addWorktreeRobust(wtPath, branch, base, {
|
|
35334
35424
|
git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
|
|
35335
35425
|
revParse: async (ref) => {
|
|
35336
35426
|
try {
|
|
@@ -35344,7 +35434,7 @@ withExamples(mutating(
|
|
|
35344
35434
|
log: (m) => {
|
|
35345
35435
|
if (!o.json) console.error(` ${m}`);
|
|
35346
35436
|
}
|
|
35347
|
-
});
|
|
35437
|
+
}));
|
|
35348
35438
|
}
|
|
35349
35439
|
step = "install deps + copy local-only config";
|
|
35350
35440
|
const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
|